From f768dfaa2ee4bcc7bcfffa92b9b5e2183888b909 Mon Sep 17 00:00:00 2001 From: denz Date: Sun, 28 Jun 2015 15:19:26 +0900 Subject: [PATCH] Change timeline ui to infinite circular scroll Infinite circular scroll bug fix. Add timeline self execution time search field --- .../InfiniteCircularScroll.js | 167 +++++++++++++++ .../distributed-call-flow.directive.js | 6 +- .../features/timeline/timeline.directive.js | 201 ++++-------------- web/src/main/webapp/index.html | 1 + .../transaction-detail.controller.js | 35 ++- .../transactionDetail/transactionDetail.html | 3 + 6 files changed, 240 insertions(+), 173 deletions(-) create mode 100644 web/src/main/webapp/components/infiniteCircularScroll/InfiniteCircularScroll.js diff --git a/web/src/main/webapp/components/infiniteCircularScroll/InfiniteCircularScroll.js b/web/src/main/webapp/components/infiniteCircularScroll/InfiniteCircularScroll.js new file mode 100644 index 000000000..864c2a285 --- /dev/null +++ b/web/src/main/webapp/components/infiniteCircularScroll/InfiniteCircularScroll.js @@ -0,0 +1,167 @@ +(function(window, $) { + 'use strict'; + var ROW_SPARE_COUNT = 8; + window.InfiniteCircularScroll = $.Class({ + $init: function(options) { + this.option(options); + this._init(); + this._initVar(); + this._initEvent(); + }, + _init: function() { + this.$wrapper = this.option("wrapper"); + this._threshold = this.option("elementHeight") * (ROW_SPARE_COUNT / 4); // 2 * elementHeight + this._previousTop = 0; + this._previousTime = -1; + this._selectedRow = -1; + }, + _initVar: function() { + this._elementArray = []; + this._elementArrayStartIndex = 0; + this._elementArrayEndIndex = 0; + this._elementArraySize = 0; + this._previousTop = 0; + }, + _initEvent: function() { + var self = this; + this.option("scroller").on("scroll", function(event) { + self._scrollEventHandler(event, $(this)); + }); + }, + _calculateElementStartCount: function() { + var temp = parseInt( this._viewAreaHeight / this.option("elementHeight") ) + ROW_SPARE_COUNT; + this._elementArraySize = temp > this._source.length ? this._source.length : temp; + this._elementArrayEndIndex = this._elementArraySize - 1; + }, + _initElementArray: function() { + this.$wrapper.empty(); + for( var i = 0 ; i < this._elementArraySize ; i++ ) { + var $element = $(this.option("template")); + this._renderElement($element, i); + this._elementArray.push($element); + this.$wrapper.append($element); + } + }, + _renderElement: function($element, index) { + this._renderFunc.call( this, $element, index, this._source[index] ); + $element.attr("data-index", index).css("top", (index * this.option("elementHeight")) + "px" ); + }, + _getDataIndex: function(index) { + return parseInt(this._elementArray[index].attr("data-index")); + }, + _getTopByIndex: function(index) { + return parseInt(this._elementArray[index].css("top")); + }, + _getNextArrayIndex: function(index) { + return index + 1 >= this._elementArraySize ? 0 : index + 1; + }, + _getPreviousArrayIndex: function(index) { + return ( index - 1 < 0 ? this._elementArraySize : index ) - 1; + }, + _scrollEventHandler: function(event, $targetElement) { + var top = $targetElement.scrollTop(); + var mTime = new Date().valueOf(); + if ( top == this._previousTop ) return; +// if ( this._previousTime != -1 && ( mTime - this._previousTime ) < 16 ) return; + + var bIsDown = top - this._previousTop > 0; + if ( bIsDown ) { // 아래로 스크롤 - top 값은 커진다. + this._scrollDown(top, this._getTopByIndex(this._elementArrayStartIndex)); + } else { // 위로 스크롤 - top 값은 작아진다. + this._scrollUp(top, this._getTopByIndex(this._elementArrayEndIndex)); + } + this._previousTop = top; + this._previousTime = mTime; + }, + _scrollDown: function( top, firstElementTop ) { + var exceededDistance = top - firstElementTop - this._threshold; + if ( exceededDistance >= 0 ) { + var sourceLastIndex = this._getDataIndex(this._elementArrayEndIndex); + var nextIndex = this._elementArrayStartIndex; + var nextIndexStep = parseInt(exceededDistance / this.option("elementHeight")); + + for( var i = 0 ; i < nextIndexStep ; i++ ) { + var sourceIndex = sourceLastIndex + i + 1; + if ( sourceIndex >= this._source.length ) { + break; + } + this._renderElement(this._elementArray[nextIndex], sourceIndex); + nextIndex = this._getNextArrayIndex(nextIndex); + } + this._elementArrayStartIndex = nextIndex; + this._elementArrayEndIndex = this._getPreviousArrayIndex(nextIndex); + } + }, + _scrollUp: function( top, lastElementTop ) { + var exceededDistance = lastElementTop + this.option("elementHeight") - top - this._viewAreaHeight - this._threshold; + if ( exceededDistance >= 0 ) { + var sourceFirstIndex = this._getDataIndex(this._elementArrayStartIndex); + var previousIndex = this._elementArrayEndIndex; + var previousIndexStep = parseInt(exceededDistance / this.option("elementHeight")); + + for( var i = 0 ; i < previousIndexStep ; i++ ) { + var sourceIndex = sourceFirstIndex - (i + 1); + if ( sourceIndex < 0 ) { + break; + } + this._renderElement(this._elementArray[previousIndex], sourceIndex); + previousIndex = this._getPreviousArrayIndex(previousIndex); + } + this._elementArrayEndIndex = previousIndex; + this._elementArrayStartIndex = this._getNextArrayIndex(previousIndex); + } + }, + setSource: function( source ) { + this._source= source; + return this; + }, + setViewAreaHeight: function( viewAreaHeight ) { + this._viewAreaHeight = viewAreaHeight; + return this; + }, + setRenderFunc: function( fnRender ) { + this._renderFunc = fnRender; + return this; + }, + reset: function() { + this._initVar(); + this._calculateElementStartCount(); + this._initElementArray(); + return this; + }, + getContentsAreaHeight: function() { + return this.option("elementHeight") * this._source.length; + }, + destroy: function() { + this.$wrapper.parent().off("scroll"); + }, + resize: function( resizedViewAreaHeight ) { + var currentRow = parseInt(this.option("scroller").scrollTop() / this.option("elementHeight")); + this.setViewAreaHeight(resizedViewAreaHeight); + this.reset(); + this.moveByRow( currentRow ); + }, + moveByRow: function( row ) { // from 0 + var self = this; + setTimeout(function() { + self.option("scroller").scrollTop(row * self.option("elementHeight")); + },0); + }, + searchRow: function( from, to, func) { + var resultRow = -1; + to = to == -1 ? this._source.length : to; + for( var i = from ; i < to ; i++ ) { + if ( func(this._source[i]) ) { + resultRow = i; + break; + } + } + return resultRow; + }, + setSelectedRow: function(row, func) { + func.call(this, row); + this._selectedRow = row; + return this; + } + }); +})(window, jQuery); diff --git a/web/src/main/webapp/features/distributedCallFlow/distributed-call-flow.directive.js b/web/src/main/webapp/features/distributedCallFlow/distributed-call-flow.directive.js index b719f53e2..8cd483a17 100644 --- a/web/src/main/webapp/features/distributedCallFlow/distributed-call-flow.directive.js +++ b/web/src/main/webapp/features/distributedCallFlow/distributed-call-flow.directive.js @@ -424,13 +424,13 @@ if ( row == -1 ) { if ( index > 0 ) { selectRow( searchRowByTime(time, 0) ); - scope.$emit("transactionDetail.searchCallresult", "Loop" ); + scope.$emit("transactionDetail.calltreeSearchCallResult", "Loop" ); } else { - scope.$emit("transactionDetail.searchCallresult", "No call took longer than {time}ms." ); + scope.$emit("transactionDetail.calltreeSearchCallResult", "No call took longer than {time}ms." ); } } else { selectRow(row); - scope.$emit("transactionDetail.searchCallresult", "" ); + scope.$emit("transactionDetail.calltreeSearchCallResult", "" ); } }); searchRowByTime = function( time, index ) { diff --git a/web/src/main/webapp/features/timeline/timeline.directive.js b/web/src/main/webapp/features/timeline/timeline.directive.js index 787f189f6..804091ea3 100644 --- a/web/src/main/webapp/features/timeline/timeline.directive.js +++ b/web/src/main/webapp/features/timeline/timeline.directive.js @@ -1,163 +1,6 @@ (function( $ ) { 'use strict'; - - function InfiniteCircularScroller(options) { - this.options = options || {}; - this._init(); - this._initVar(); - this._initEvent(); - }; - InfiniteCircularScroller.prototype._init = function() { - this.options.initPlusCount = 8; - this.$wrapper = this.options.wrapper; - this._threshold = this.options.elementHeight * (this.options.initPlusCount / 4); - this._previousTop = 0; - }; - InfiniteCircularScroller.prototype._initVar = function() { - this._elementArray = []; - this._elementArrayStartIndex = 0; - this._elementArrayEndIndex = 0; - this._elementArraySize = 0; - this._previousTop = 0; - }; - InfiniteCircularScroller.prototype._initEvent = function() { - var self = this; - this.options.scroller.on("scroll", function(event) { - self._scrollEventHandler(event, $(this)); - }); - }; - InfiniteCircularScroller.prototype._calculateElementStartCount = function() { - var temp = parseInt( this._viewAreaHeight / this.options.elementHeight ) + this.options.initPlusCount; - this._elementArraySize = temp > this._source.length ? this._source.length : temp; - this._elementArrayEndIndex = this._elementArraySize - 1; - }; - InfiniteCircularScroller.prototype._initElementArray = function() { - this.$wrapper.empty(); - for( var i = 0 ; i < this._elementArraySize ; i++ ) { - var $element = $(this.options.template); - this._renderElement($element, i); - this._elementArray.push($element); - this.$wrapper.append($element); - } - }; - InfiniteCircularScroller.prototype._renderElement = function($element, index) { - this._renderFunc.call( this, $element, index, this._source[index] ); - $element.attr("data-index", index).css("top", (index * this.options.elementHeight) + "px" ); - }; - InfiniteCircularScroller.prototype._getDataIndex = function(index) { - return parseInt(this._elementArray[index].attr("data-index")); - }; - InfiniteCircularScroller.prototype._getTopByIndex = function(index) { - return parseInt(this._elementArray[index].css("top")); - }; - InfiniteCircularScroller.prototype._getNextArrayIndex = function(index) { - return index + 1 >= this._elementArraySize ? 0 : index + 1; - }; - InfiniteCircularScroller.prototype._getPreviousArrayIndex = function(index) { - return ( index - 1 < 0 ? this._elementArraySize : index ) - 1; - }; - InfiniteCircularScroller.prototype._scrollEventHandler = function(event, $targetElement) { - var top = $targetElement.scrollTop(); - if ( top == this._previousTop ) return; - if ( top < this._threshold ) return; - - //if ( top - this._previousTop > this._viewAreaHeight ) 스크롤 이벤트 입력값이 화면 보다 크게 오는 경우 - var bIsDown = top - this._previousTop > 0; - if ( bIsDown ) { // 아래로 스크롤 - top 값은 커진다. - this._scrollDown(top, this._getTopByIndex(this._elementArrayStartIndex)); - } else { // 위로 스크롤 - top 값은 작아진다. - this._scrollUp(top, this._getTopByIndex(this._elementArrayEndIndex)); - } - this._previousTop = top; - }; - InfiniteCircularScroller.prototype._scrollDown = function( top, firstElementTop ) { - var exceededDistance = top - firstElementTop - this._threshold; - if ( exceededDistance >= 0 ) { - var sourceLastIndex = this._getDataIndex(this._elementArrayEndIndex); - var nextIndex = this._elementArrayStartIndex; - var nextIndexStep = parseInt(exceededDistance / this.options.elementHeight); - - for( var i = 0 ; i < nextIndexStep ; i++ ) { - var sourceIndex = sourceLastIndex + i + 1; - if ( sourceIndex >= this._source.length ) { - break; - } - this._renderElement(this._elementArray[nextIndex], sourceIndex); - nextIndex = this._getNextArrayIndex(nextIndex); - } - this._elementArrayStartIndex = nextIndex; - this._elementArrayEndIndex = this._getPreviousArrayIndex(nextIndex); - } - }; - InfiniteCircularScroller.prototype._scrollUp = function( top, lastElementTop ) { - var exceededDistance = lastElementTop + this.options.elementHeight - top - this._viewAreaHeight - this._threshold; - if ( exceededDistance >= 0 ) { - var sourceFirstIndex = this._getDataIndex(this._elementArrayStartIndex); - var previousIndex = this._elementArrayEndIndex; - var previousIndexStep = parseInt(exceededDistance / this.options.elementHeight); - - for( var i = 0 ; i < previousIndexStep ; i++ ) { - var sourceIndex = sourceFirstIndex - (i + 1); - if ( sourceIndex < 0 ) { - break; - } - this._renderElement(this._elementArray[previousIndex], sourceIndex); - previousIndex = this._getPreviousArrayIndex(previousIndex); - } - this._elementArrayEndIndex = previousIndex; - this._elementArrayStartIndex = this._getNextArrayIndex(previousIndex); - } - }; - InfiniteCircularScroller.prototype.setSource = function( source ) { - this._source= source; - return this; - }; - InfiniteCircularScroller.prototype.setViewAreaHeight = function( viewAreaHeight ) { - this._viewAreaHeight = viewAreaHeight; - return this; - }; - InfiniteCircularScroller.prototype.setRenderFunc = function( fnRender ) { - this._renderFunc = fnRender; - return this; - }; - InfiniteCircularScroller.prototype.reset = function() { - this._initVar(); - this._calculateElementStartCount(); - this._initElementArray(); - return this; - }; - InfiniteCircularScroller.prototype.getContentsAreaHeight = function() { - return this.options.elementHeight * this._source.length; - }; - InfiniteCircularScroller.prototype.destroy = function() { - this.$wrapper.parent().off("scroll"); - }; - InfiniteCircularScroller.prototype.resize = function( resizedViewAreaHeight ) { - var currentRow = parseInt(this.options.scroller.scrollTop() / this.options.elementHeight); - this.setViewAreaHeight(resizedViewAreaHeight); - this.reset(); - this.moveByRow( currentRow ); - }; - InfiniteCircularScroller.prototype.moveByRow = function( row ) { - var self = this; - setTimeout(function() { - self.options.scroller.scrollTop(0); - },0); - }; - InfiniteCircularScroller.prototype.moveByValue = function(from, func) { - for( var i = from ; i < this._source.length ; i++ ) { - if ( func(this._source[i]) ) { - this.moveByRow(i); - break; - } - } - }; - /* - * icscroller.moveByValue( 0, function( elementData ) { - * return (elementData[scope.key.end] - elementData[scope.key.begin]) >= selfExecutionTime; - * }); - */ - + pinpointApp.directive('timelineDirective', function () { return { restrict: 'EA', @@ -166,13 +9,13 @@ link: function postLink(scope, element, attrs) { // define private variables of methods - var initialize, getColorByString, filterCallStacks, viewAreaHeight, initBarCount, renderCallStack; + var initialize, getColorByString, filterCallStacks, viewAreaHeight, initBarCount, renderCallStack, searchTime, searchStartIndex, searchSuccess; var colorSet = [ "#66CCFF", "#FFCCFF", "#66CC00", "#FFCC33", "#669999", "#FF9999", "#6666FF", "#FF6633", "#66FFCC", "#006666", "#FFFF00", "#66CCCC", "#FFCCCC", "#6699FF", "#FF99FF", "#669900", "#FF9933", "#66FFFF", "#996600", "#66FF00" ], colorSetIndex = []; - var icscroller = new InfiniteCircularScroller({ + var icscroller = new InfiniteCircularScroll({ scroller: $(element), wrapper: $(element).find("div"), elementHeight: 21, @@ -192,7 +35,8 @@ }); icscroller.setRenderFunc(function( $element, index, elementData ) { var marginLeft = getMarginLeft(elementData); - $element.find("div.clickable-bar").css({ + $element[ index == this._selectedRow ? "addClass" : "removeClass" ]("timeline-bar-selected") + .find("div.clickable-bar").css({ width : getWidth(elementData) + "px", backgroundColor : getColorByString(elementData[scope.key.applicationName]), marginLeft : marginLeft + "px" @@ -224,8 +68,10 @@ scope.newCallStacks = filterCallStacks(); icscroller.setSource( scope.newCallStacks ) .setViewAreaHeight( $(element).parentsUntil("div.wrapper").height() - 70 ) // 70 is header area height + .setSelectedRow(-1, angular.noop) .reset(); scope.maxHeight = icscroller.getContentsAreaHeight(); + searchStartIndex = 0; scope.$digest(); }; @@ -276,6 +122,39 @@ scope.$on('timelineDirective.resize', function (event) { icscroller.resize($(element).parentsUntil("div.wrapper").height() - 70); }); + scope.$on("timelineDirective.searchCall", function( event, time, index ) { + var resultIndex = searchTime( searchStartIndex, -1, time ); + if ( resultIndex == -1 ) { + if ( searchStartIndex == 0 ) { + scope.$emit("transactionDetail.timelineSearchCallResult", "No call took longer than {time}ms." ); + } else { + resultIndex = searchTime( 0, searchStartIndex, time ); + if ( resultIndex == -1 ) { + scope.$emit("transactionDetail.timelineSearchCallResult", "No call took longer than {time}ms." ); + } else { + searchSuccess(resultIndex, "Loop"); + } + } + } else { + searchSuccess(resultIndex, ""); + } + }); + searchTime = function( from, to, time ) { + return icscroller.searchRow(from, to, function( elementData ) { + if ( elementData[scope.key.end] - elementData[scope.key.begin] >= time ) { + return true; + } + return false; + }); + }; + searchSuccess = function(resultIndex, message) { + icscroller.setSelectedRow( resultIndex, function(newIndex) { + this.$wrapper.find("div[data-index=" + this._selectedRow + "]").removeClass("timeline-bar-selected"); + this.$wrapper.find("div[data-index=" + newIndex + "]").addClass("timeline-bar-selected"); + }).moveByRow( resultIndex ); + searchStartIndex = resultIndex + 1; + scope.$emit("transactionDetail.timelineSearchCallResult", message ); + } } }; }); diff --git a/web/src/main/webapp/index.html b/web/src/main/webapp/index.html index d4d8cb305..49f1d3320 100644 --- a/web/src/main/webapp/index.html +++ b/web/src/main/webapp/index.html @@ -95,6 +95,7 @@ + diff --git a/web/src/main/webapp/pages/transactionDetail/transaction-detail.controller.js b/web/src/main/webapp/pages/transactionDetail/transaction-detail.controller.js index f538d3d0f..4f57b7bd7 100644 --- a/web/src/main/webapp/pages/transactionDetail/transaction-detail.controller.js +++ b/web/src/main/webapp/pages/transactionDetail/transaction-detail.controller.js @@ -86,19 +86,26 @@ } }; initSearchVar = function() { - $("#traceTabs li:nth-child(5)").hide(); +// $("#traceTabs li:nth-child(5)").hide(); $scope.searchMinTime = 1000; - $scope.searchIndex = 0; + $scope.timelineSearchIndex = 0; + $scope.calltreeSearchIndex = 0; $scope.searchMessage = ""; }; - $scope.searchIndex = 0; + $scope.calltreeSearchIndex = 0; + $scope.timelineSearchIndex = 0; $scope.searchMinTime = 1000; // ms $scope.searchMessage = ""; $scope.searchCall = function() { - $scope.$broadcast('distributedCallFlowDirective.searchCall.forTransactionDetail', parseInt($scope.searchMinTime), parseInt($scope.searchIndex) ); + if ( $("#CallStacks").is(":visible") ) { + $scope.$broadcast('distributedCallFlowDirective.searchCall.forTransactionDetail', parseInt($scope.searchMinTime), parseInt($scope.calltreeSearchIndex) ); + } else { + $scope.$broadcast('timelineDirective.searchCall', parseInt($scope.searchMinTime), parseInt($scope.timelineSearchIndex) ); + } }; $scope.$watch( "searchMinTime", function( newVal ) { - $scope.searchIndex = 0; + $scope.calltreeSearchIndex = 0; + $scope.timelineSearchIndex = 0; }); $scope.openInNewWindow = function () { @@ -122,13 +129,23 @@ $("#traceTabs li:nth-child(1) a").trigger("click"); $scope.$broadcast('distributedCallFlowDirective.selectRow.forTransactionDetail', rowId); }); - $scope.$on("transactionDetail.searchCallresult", function(event, message) { + $scope.$on("transactionDetail.calltreeSearchCallResult", function(event, message) { if ( message == "Loop" ) { - $scope.searchIndex = 1; + $scope.calltreeSearchIndex = 1; } else { $scope.searchMessage = message.replace("{time}", $scope.searchMinTime); if ( message == "" ) { - $scope.searchIndex++; + $scope.calltreeSearchIndex++; + } + } + }); + $scope.$on("transactionDetail.timelineSearchCallResult", function(event, message) { + if ( message == "Loop" ) { + $scope.timelineSearchIndex = 1; + } else { + $scope.searchMessage = message.replace("{time}", $scope.searchMinTime); + if ( message == "" ) { + $scope.timelineSearchIndex++; } } }); @@ -137,7 +154,7 @@ $('#traceTabs li a[data-toggle="tab"]').on('shown.bs.tab', function(e) { if ( e.target.href.indexOf( "#CallStacks") != -1 ) { $at($at.CALLSTACK, $at.CLK_DISTRIBUTED_CALL_FLOW); - $("#traceTabs li:nth-child(5)").show(); +// $("#traceTabs li:nth-child(5)").show(); } }); // events binding diff --git a/web/src/main/webapp/pages/transactionDetail/transactionDetail.html b/web/src/main/webapp/pages/transactionDetail/transactionDetail.html index e47e46411..c4732e3e7 100644 --- a/web/src/main/webapp/pages/transactionDetail/transactionDetail.html +++ b/web/src/main/webapp/pages/transactionDetail/transactionDetail.html @@ -27,6 +27,9 @@ border-bottom:1px solid #D3D3D3; font-family:Verdana; } + .timeline-bar-selected { + background-color:orange; + } .timeline-bar > div { cursor:pointer; margin-top:1px;