doctype html html
head title Source Route Result link rel="stylesheet" href="https://cdn.rawgit.com/Urigo/angular-spinkit/master/build/angular-spinkit.min.css" link rel="stylesheet" href="https://cdn.rawgit.com/raykin/json-formatter/master/dist/json-formatter.min.css" link rel="stylesheet" href="https://cdn.jsdelivr.net/semantic-ui/2.2.6/semantic.min.css" script src="http://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.js" script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js" script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.0/angular.min.js" script async=true src="https://cdn.jsdelivr.net/semantic-ui/2.2.6/semantic.min.js" css: .call-level-0 {} .item.trace.call-level-1 { padding-left: 50px } .item.trace.call-level-2 { padding-left: 100px } .item.trace.call-level-3 { padding-left: 150px } .item.trace.call-level-4 { padding-left: 200px } .item.trace.call-level-5 { padding-left: 250px } .item.trace.call-level-6 { padding-left: 300px } .item.trace.call-level-7 { padding-left: 300px } .trace .main .ui.label .icon { margin-right: 0; } // level more than 7 seems not reasonable body(ng-app="SourceRoute" ng-controller="MainCtrl" ng-cloak) .ui.menu.pointing.stackable.attached .ui.container .item a.navbar-brand(href="#" ng-click="::resetTraceFilter()") ALL .item(ng-repeat="event in tpEvents" ng-class="{active: event == traceFilter.event}") a(href="#" ng-click="traceFilter.event = event" ng-bind="::event") .item .ui.buttons button.ui.labeled.icon.button.olive(ng-click="outlineTrace()" ng-class="{loading: outlineTraceLoading}") i.angle.double.right.icon span OutLine .or button.ui.right.labeled.icon.button.green(ng-click="expandAllTrace()" ng-class="{loading: expandAllTraceLoading}") i.angle.double.down.icon span Expand .right.menu .item span Trace Count .ui.teal.left.pointing.label(ng-bind="currentCounter()") .ui.container .row .ui.relaxed.items(ng-class="{{traceFilter.event}}") // track by trace.order_id doesn't always work. because order_id doesn't always exists .item.trace(ng-repeat="trace in traces | filter:traceFilter:true | filter:childParentFilterFn" ng-class="callLevelClass(trace)" ng-controller="TpTraceCtrl") .content(ng-init="showMoreDetail = false" style="display: flex; justify-content: space-between; align-items: center;") .ui.segment.padded.main a.ui.top.right.attached.label.mini(ng-show="::containsDetail(trace)" ng-click="showMoreDetail = !showMoreDetail") i.sidebar.icon .header span.bold.ui.label>(ng-bind="::(trace.order_id || '>')") span(ng-bind="::tpSelfList[trace.tp_self_refer]") i.circle.icon.grey(style="font-size: 0.3em") span.method-value(ng-bind="::trace.method_id") a.ui.bottom.right.attached.label.mini(ng-if="::hasChild()" ng-click="toggleChild()" ng-class="{loading: togglingChild}") i.icon.angle.down(ng-show="trace.childOpened" style="font-size: 10px") i.icon.angle.right(ng-hide="trace.childOpened" style="font-size: 14px") / pulse-spinner(ng-show="togglingChild") / workaround for return_value is 'false' and return_value always to be string when existed .meta(ng-if="trace.hasOwnProperty('return_value')") i.icon.pointing.right.small json-formatter(json="::trimString(trace.return_value, 30)" title="{{::trace.return_value_class}}" style="display: inline-block") .description(style="margin-left: 20px") .details.right.floated(ng-if="showMoreDetail") .ui.segments(style="border-color: blue") .ui.segment(ng-if="::trace.params_var") .ui.teal.left.ribbon.label Parameters json-formatter(open="1" json="::trace.params_var" title="{{::trace.params_var_class}}") .ui.segment(ng-if="::trace.hasOwnProperty('return_value')") .ui.grey.left.ribbon.label Return Value json-formatter(open="1" json="::trace.return_value" title="{{::trace.return_value_class}}") .ui.segment(ng-if="::trace.local_var") .ui.teal.left.ribbon.label Local Variables json-formatter(open="1" json="::trace.local_var" title="{{::trace.local_var_class}}") .ui.segment(ng-if="::trace.instance_var") .ui.blue.left.ribbon.label Instance Variables json-formatter(open="1" json="::trace.instance_var" title="{{::trace.instance_var_class}}") .ui.segment(ng-if="::containsOtherAttrs(trace)") .ui.orange.left.ribbon.label Trace Attributes json-formatter(open="1" json="::plusTraceAttrs[trace.order_id]" title="TracePoint") script src="https://cdn.rawgit.com/Urigo/angular-spinkit/master/build/angular-spinkit.min.js" script src="https://cdn.rawgit.com/raykin/json-formatter/master/dist/json-formatter.js" // script async src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular-sanitize.js" javascript: sourceRoute = angular.module('SourceRoute', ['jsonFormatter', 'angular-spinkit']) sourceRoute.controller('MainCtrl', function($scope, $filter, $timeout) { // setup different color on menu item may not be a good solution // $scope.menuColorList = ['yellow', 'olive', 'green', 'teal', 'violet', 'purple', 'brown'] $scope.trimString = function(str, length) { return str.length > length ? str.substring(0, length - 3) + '...' : str; } $scope.traces = angular.element("#trace-data").data('trace') $scope.tpSelfList = angular.element("#trace-data").data('tp-self-caches') $scope.tpEvents = angular.element("#trace-data").data('tp-events') $scope.childParentFilter = { hide_trace_ids: [] } $scope.childParentFilterFn = function(trace) { if (!trace.hasOwnProperty('parent_ids')) { return true; } if (trace.parent_ids.length == 0) { return true; } else { var shared_hide_parents = _.intersection(trace.parent_ids, $scope.childParentFilter.hide_trace_ids); if (shared_hide_parents.length > 0 ) { return false; } } return true; } $scope.expandAllTrace = function() { $scope.expandAllTraceLoading = true $timeout(function() { _.each($scope.traces, function(trace) { trace.childOpened = true; }); $scope.childParentFilter.hide_trace_ids = []; $scope.expandAllTraceLoading = false $scope.expandAllTraceLoading = false }, 100) } $scope.outlineTrace = function() { $scope.outlineTraceLoading = true $scope.childParentFilter.hide_trace_ids = []; _.chain($scope.traces).filter({parent_length: 0, event: $scope.traceFilter.event}) .each(function(trace) { trace.childOpened = false; $scope.childParentFilter.hide_trace_ids.push(trace.order_id) } ).value() $scope.outlineTraceLoading = false } $scope.traceFilter = {event: $scope.tpEvents[0]} if ($scope.tpEvents.length == 1 && angular.isUndefined($scope.traces[0].event)) { _.each($scope.traces, function(trace) { trace.event = $scope.tpEvents[0] }) } $scope.definedClasses = _.uniq(_.map($scope.traces, 'defined_class')) $scope.callLevelClass = function(trace) { if (trace.parent_length > 7) { return 'call-level-7'; } else { return 'call-level-' + trace.parent_length; } } $scope.resetTraceFilter = function() { $scope.traceFilter = {}; } $scope.currentCounter = function() { return $filter('filter')($scope.traces, $scope.traceFilter, true).length; } $scope.containsOtherAttrs = function(trace) { return trace.hasOwnProperty('path') || trace.hasOwnProperty('lineno') } $scope.containsDetail = function(trace) { return $scope.containsOtherAttrs(trace) || trace.hasOwnProperty('local_var') || trace.hasOwnProperty('instance_var') } combinedAttrs = function(trace) { var attrs = {} if (trace.hasOwnProperty('lineno') && trace.hasOwnProperty('path')) { attrs.method_defined_on = _.replace(trace.path, /.*gems\//, '') + ":" + trace.lineno; } else if (trace.hasOwnProperty('path')) { attrs.method_defined_on = _.replace(trace.path, /.*gems\//, ''); } if (trace.hasOwnProperty('defined_class')) { attrs.method_defined_in = trace.defined_class; } return attrs; } $scope.plusTraceAttrs = _.map($scope.traces, combinedAttrs) $scope.outlineTrace(); }) sourceRoute.controller('TpTraceCtrl', function($scope, $timeout) { $scope.toggleChild = function() { $scope.togglingChild = true $timeout(function() { if ($scope.trace.childOpened) { $scope.hideChild(); $scope.togglingChild = false } else { $scope.showChild(); $scope.togglingChild = false } }, 0) } $scope.showChild = function() { $scope.trace.childOpened = true; _.pull($scope.childParentFilter.hide_trace_ids, $scope.trace.order_id); if ($scope.trace.direct_child_order_ids.length > 0) { $scope.childParentFilter.hide_trace_ids.push($scope.trace.direct_child_order_ids); $scope.childParentFilter.hide_trace_ids = _.chain($scope.childParentFilter.hide_trace_ids).flatten().uniq().value(); } } $scope.hideChild = function() { $scope.trace.childOpened = false; $scope.childParentFilter.hide_trace_ids.push($scope.trace.order_id); if ($scope.trace.direct_child_order_ids.length > 0) { _.each($scope.trace.direct_child_order_ids, function(ele) { _.pull($scope.childParentFilter.hide_trace_ids, ele); }); } } $scope.hasChild = function() { return _.find($scope.traces, function(trace) { return _.includes(trace.parent_ids, $scope.trace.order_id) }); } }) .data-collect / dont use local_trace_data.to_json, because ActiveSupport override it and can introduce unexpected crash for some data #trace-data(data-trace="#{jsonify_trace_chain}" data-tp-events="#{jsonify_events}" data-tp-self-caches="#{jsonify_tp_self_caches}")