Re-construct the JS Timeline page
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
"name": "yellowlabtools",
|
||||
"dependencies": {
|
||||
"angular": "~1.3.5",
|
||||
"ngModal": "git://github.com/gmetais/ngModal.git#1.2.3",
|
||||
"angular-route": "~1.3.6",
|
||||
"angular-resource": "~1.3.6"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* Timeline colors, related to Window Performances */
|
||||
.resultsMenu {
|
||||
margin-top: 2em;
|
||||
}
|
||||
@@ -174,265 +173,6 @@ h4 {
|
||||
.notations .criteria .icon-question {
|
||||
color: transparent;
|
||||
}
|
||||
.timeline {
|
||||
margin: 2em 0 5em;
|
||||
}
|
||||
.timeline .chart {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #000;
|
||||
}
|
||||
.timeline .startTime,
|
||||
.timeline .endTime {
|
||||
position: absolute;
|
||||
bottom: 0.5em;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.timeline .startTime {
|
||||
left: 0em;
|
||||
}
|
||||
.timeline .endTime {
|
||||
right: 0em;
|
||||
}
|
||||
.timeline .chartPoints {
|
||||
display: table;
|
||||
height: 100px;
|
||||
width: 99%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.timeline .interval {
|
||||
display: table-cell;
|
||||
position: relative;
|
||||
height: 100px;
|
||||
width: 0.5%;
|
||||
}
|
||||
.timeline .interval .color {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.timeline div.interval:hover {
|
||||
background: #9C4274;
|
||||
}
|
||||
.timeline .interval:hover .color {
|
||||
background: #F04DA7;
|
||||
}
|
||||
.timeline .domComplete.interval {
|
||||
background: #ede3ff;
|
||||
}
|
||||
.timeline .domComplete .color {
|
||||
background: #c2a3ff;
|
||||
}
|
||||
.timeline .domContentLoadedEnd.interval {
|
||||
background: #d8f0f0;
|
||||
}
|
||||
.timeline .domContentLoadedEnd .color {
|
||||
background: #7ecccc;
|
||||
}
|
||||
.timeline .domContentLoaded.interval {
|
||||
background: #e0ffd1;
|
||||
}
|
||||
.timeline .domContentLoaded .color {
|
||||
background: #a7e846;
|
||||
}
|
||||
.timeline .domInteractive.interval {
|
||||
background: #fffccc;
|
||||
}
|
||||
.timeline .domInteractive .color {
|
||||
background: #ffe433;
|
||||
}
|
||||
.timeline .domCreation.interval {
|
||||
background: #ffe0cc;
|
||||
}
|
||||
.timeline .domCreation .color {
|
||||
background: #ff6600;
|
||||
}
|
||||
.timeline .tooltip.detailsOverlay {
|
||||
position: absolute;
|
||||
display: none;
|
||||
width: auto;
|
||||
padding: 0.5em 1em;
|
||||
top: -1.5em;
|
||||
right: 1em;
|
||||
}
|
||||
.timeline .interval:hover .tooltip {
|
||||
display: block;
|
||||
}
|
||||
.timeline .legend {
|
||||
display: table;
|
||||
width: 100%;
|
||||
margin-top: 1em;
|
||||
}
|
||||
.timeline .legend > div {
|
||||
display: table-row;
|
||||
}
|
||||
.timeline .legend > div > div {
|
||||
position: relative;
|
||||
display: table-cell;
|
||||
margin-top: 1em;
|
||||
}
|
||||
.timeline .titles {
|
||||
font-weight: bold;
|
||||
}
|
||||
.timeline .titles > div {
|
||||
padding: 0 1em 0 2em;
|
||||
}
|
||||
.timeline .tips {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
.timeline .tips > div {
|
||||
padding: 1em 1em 0 0;
|
||||
}
|
||||
.timeline .legend .color {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
height: 1.5em;
|
||||
width: 1.5em;
|
||||
border-radius: 0.2em;
|
||||
}
|
||||
.metrics h4 {
|
||||
padding-left: 2em;
|
||||
}
|
||||
.metrics .module {
|
||||
padding-left: 4em;
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
.metrics .legend {
|
||||
font-style: italic;
|
||||
color: #aaa;
|
||||
}
|
||||
.metrics .offenders {
|
||||
padding-left: 0em;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.metrics .offenders div {
|
||||
cursor: pointer;
|
||||
}
|
||||
.metrics .offenders ul {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
.filters {
|
||||
margin: 1em 0;
|
||||
padding: 0.5em;
|
||||
border: 1px dotted #aaa;
|
||||
}
|
||||
.slowRequestsLimit {
|
||||
width: 3em;
|
||||
font-size: 1em;
|
||||
text-align: right;
|
||||
border: 1px solid #aaa;
|
||||
}
|
||||
input.textFilter {
|
||||
box-shadow: none;
|
||||
font-size: 1em;
|
||||
padding: 0 0.2em;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: none;
|
||||
width: 15em;
|
||||
}
|
||||
.table {
|
||||
display: table;
|
||||
width: 100%;
|
||||
border-spacing: 0.25em;
|
||||
}
|
||||
.table > div {
|
||||
display: table-row;
|
||||
}
|
||||
.table > .headers > div {
|
||||
font-weight: bold;
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
.table > div > div {
|
||||
padding: 0.1em 1em;
|
||||
background: #f2f2f2;
|
||||
display: table-cell;
|
||||
text-align: left;
|
||||
}
|
||||
.table > div.jsError > .type,
|
||||
.table > div.jsError > .value {
|
||||
color: #e74c3c;
|
||||
font-weight: bold;
|
||||
}
|
||||
.table > div.windowPerformance > div,
|
||||
.table > div.windowPerformance > div.startTime {
|
||||
background: #EBD8E2;
|
||||
}
|
||||
.table > div.showingDetails > div {
|
||||
background: #f1c40f;
|
||||
}
|
||||
.table > div > .index {
|
||||
color: #bbb;
|
||||
}
|
||||
.table > div > .type {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.table > div > .value {
|
||||
width: 70%;
|
||||
word-break: break-all;
|
||||
}
|
||||
.table > div > .details {
|
||||
position: relative;
|
||||
}
|
||||
.table .details .icon-question {
|
||||
color: #f1c40f;
|
||||
cursor: pointer;
|
||||
}
|
||||
.table .icon-warning {
|
||||
display: inline-block;
|
||||
width: 0.8em;
|
||||
}
|
||||
.detailsOverlay {
|
||||
position: absolute;
|
||||
right: 3em;
|
||||
top: -3em;
|
||||
width: 45em;
|
||||
min-height: 1em;
|
||||
padding: 0 1em 1em;
|
||||
background: #fff;
|
||||
border: 2px solid #f1c40f;
|
||||
border-radius: 0.5em;
|
||||
z-index: 1;
|
||||
}
|
||||
@media screen and (max-width: 1024px) {
|
||||
.detailsOverlay {
|
||||
width: 25em;
|
||||
}
|
||||
}
|
||||
.detailsOverlay .closeBtn {
|
||||
position: absolute;
|
||||
top: 0.5em;
|
||||
right: 0.5em;
|
||||
color: #f1c40f;
|
||||
cursor: pointer;
|
||||
}
|
||||
.detailsOverlay .advice {
|
||||
color: #e74c3c;
|
||||
font-weight: bold;
|
||||
}
|
||||
.detailsOverlay .trace {
|
||||
word-break: break-all;
|
||||
}
|
||||
.table > div > .duration,
|
||||
.table > div > .startTime {
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.table > div > .startTime.domComplete {
|
||||
background: #ede3ff;
|
||||
}
|
||||
.table > div > .startTime.domContentLoadedEnd {
|
||||
background: #d8f0f0;
|
||||
}
|
||||
.table > div > .startTime.domContentLoaded {
|
||||
background: #e0ffd1;
|
||||
}
|
||||
.table > div > .startTime.domInteractive {
|
||||
background: #fffccc;
|
||||
}
|
||||
.table > div > .startTime.domCreation {
|
||||
background: #ffe0cc;
|
||||
}
|
||||
/**** NgModal popin (have a look inside bower_components) ****/
|
||||
.ng-modal {
|
||||
position: fixed;
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
/* Timeline colors, related to Window Performances */
|
||||
.timeline {
|
||||
margin: 2em 0 5em;
|
||||
}
|
||||
.timeline .chart {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #000;
|
||||
}
|
||||
.timeline .startTime,
|
||||
.timeline .endTime {
|
||||
position: absolute;
|
||||
bottom: 0.5em;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.timeline .startTime {
|
||||
left: 0em;
|
||||
}
|
||||
.timeline .endTime {
|
||||
right: 0em;
|
||||
}
|
||||
.timeline .chartPoints {
|
||||
display: table;
|
||||
height: 100px;
|
||||
width: 99%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.timeline .interval {
|
||||
display: table-cell;
|
||||
position: relative;
|
||||
height: 100px;
|
||||
width: 0.5%;
|
||||
}
|
||||
.timeline .interval .color {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.timeline div.interval:hover {
|
||||
background: #9C4274;
|
||||
}
|
||||
.timeline .interval:hover .color {
|
||||
background: #F04DA7;
|
||||
}
|
||||
.timeline .domComplete.interval {
|
||||
background: #ede3ff;
|
||||
}
|
||||
.timeline .domComplete .color {
|
||||
background: #c2a3ff;
|
||||
}
|
||||
.timeline .domContentLoadedEnd.interval {
|
||||
background: #d8f0f0;
|
||||
}
|
||||
.timeline .domContentLoadedEnd .color {
|
||||
background: #7ecccc;
|
||||
}
|
||||
.timeline .domContentLoaded.interval {
|
||||
background: #e0ffd1;
|
||||
}
|
||||
.timeline .domContentLoaded .color {
|
||||
background: #a7e846;
|
||||
}
|
||||
.timeline .domInteractive.interval {
|
||||
background: #fffccc;
|
||||
}
|
||||
.timeline .domInteractive .color {
|
||||
background: #ffe433;
|
||||
}
|
||||
.timeline .domCreation.interval {
|
||||
background: #ffe0cc;
|
||||
}
|
||||
.timeline .domCreation .color {
|
||||
background: #ff6600;
|
||||
}
|
||||
.timeline .tooltip.detailsOverlay {
|
||||
position: absolute;
|
||||
display: none;
|
||||
width: auto;
|
||||
padding: 0.5em 1em;
|
||||
top: -1.5em;
|
||||
right: 1em;
|
||||
}
|
||||
.timeline .interval:hover .tooltip {
|
||||
display: block;
|
||||
}
|
||||
.timeline .legend {
|
||||
display: table;
|
||||
width: 100%;
|
||||
margin-top: 1em;
|
||||
}
|
||||
.timeline .legend > div {
|
||||
display: table-row;
|
||||
}
|
||||
.timeline .legend > div > div {
|
||||
position: relative;
|
||||
display: table-cell;
|
||||
margin-top: 1em;
|
||||
}
|
||||
.timeline .titles {
|
||||
font-weight: bold;
|
||||
}
|
||||
.timeline .titles > div {
|
||||
padding: 0 1em 0 2em;
|
||||
}
|
||||
.timeline .tips {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
.timeline .tips > div {
|
||||
padding: 1em 1em 0 0;
|
||||
}
|
||||
.timeline .legend .color {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
height: 1.5em;
|
||||
width: 1.5em;
|
||||
border-radius: 0.2em;
|
||||
}
|
||||
.filters {
|
||||
margin: 1em 0;
|
||||
padding: 0.5em;
|
||||
border: 1px dotted #aaa;
|
||||
}
|
||||
.slowRequestsLimit {
|
||||
width: 3em;
|
||||
font-size: 1em;
|
||||
text-align: right;
|
||||
border: 1px solid #aaa;
|
||||
}
|
||||
input.textFilter {
|
||||
box-shadow: none;
|
||||
font-size: 1em;
|
||||
padding: 0 0.2em;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: none;
|
||||
width: 15em;
|
||||
}
|
||||
.table {
|
||||
display: table;
|
||||
width: 100%;
|
||||
border-spacing: 0.25em;
|
||||
}
|
||||
.table > div {
|
||||
display: table-row;
|
||||
}
|
||||
.table > .headers > div {
|
||||
font-weight: bold;
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
.table > div > div {
|
||||
padding: 0.1em 1em;
|
||||
background: #f2f2f2;
|
||||
display: table-cell;
|
||||
text-align: left;
|
||||
}
|
||||
.table > div.jsError > .type,
|
||||
.table > div.jsError > .value {
|
||||
color: #e74c3c;
|
||||
font-weight: bold;
|
||||
}
|
||||
.table > div.windowPerformance > div,
|
||||
.table > div.windowPerformance > div.startTime {
|
||||
background: #EBD8E2;
|
||||
}
|
||||
.table > div.showingDetails > div {
|
||||
background: #f1c40f;
|
||||
}
|
||||
.table > div > .index {
|
||||
color: #bbb;
|
||||
}
|
||||
.table > div > .type {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.table > div > .value {
|
||||
width: 70%;
|
||||
word-break: break-all;
|
||||
}
|
||||
.table > div > .details {
|
||||
position: relative;
|
||||
}
|
||||
.table .details .icon-question {
|
||||
color: #f1c40f;
|
||||
cursor: pointer;
|
||||
}
|
||||
.table .icon-warning {
|
||||
display: inline-block;
|
||||
width: 0.8em;
|
||||
}
|
||||
.detailsOverlay {
|
||||
position: absolute;
|
||||
right: 3em;
|
||||
top: -3em;
|
||||
width: 45em;
|
||||
min-height: 1em;
|
||||
padding: 0 1em 1em;
|
||||
background: #fff;
|
||||
border: 2px solid #f1c40f;
|
||||
border-radius: 0.5em;
|
||||
z-index: 1;
|
||||
}
|
||||
@media screen and (max-width: 1024px) {
|
||||
.detailsOverlay {
|
||||
width: 25em;
|
||||
}
|
||||
}
|
||||
.detailsOverlay .closeBtn {
|
||||
position: absolute;
|
||||
top: 0.5em;
|
||||
right: 0.5em;
|
||||
color: #f1c40f;
|
||||
cursor: pointer;
|
||||
}
|
||||
.detailsOverlay .advice {
|
||||
color: #e74c3c;
|
||||
font-weight: bold;
|
||||
}
|
||||
.detailsOverlay .trace {
|
||||
word-break: break-all;
|
||||
}
|
||||
.table > div > .duration,
|
||||
.table > div > .startTime {
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.table > div > .startTime.domComplete {
|
||||
background: #ede3ff;
|
||||
}
|
||||
.table > div > .startTime.domContentLoadedEnd {
|
||||
background: #d8f0f0;
|
||||
}
|
||||
.table > div > .startTime.domContentLoaded {
|
||||
background: #e0ffd1;
|
||||
}
|
||||
.table > div > .startTime.domInteractive {
|
||||
background: #fffccc;
|
||||
}
|
||||
.table > div > .startTime.domCreation {
|
||||
background: #ffe0cc;
|
||||
}
|
||||
.execution .icon-warning {
|
||||
color: #e74c3c;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -4,12 +4,18 @@ var yltApp = angular.module('YellowLabTools', [
|
||||
'aboutCtrl',
|
||||
'dashboardCtrl',
|
||||
'queueCtrl',
|
||||
'ruleCtrl',
|
||||
'timelineCtrl',
|
||||
'runsFactory',
|
||||
'resultsFactory',
|
||||
'menuService',
|
||||
'gradeDirective',
|
||||
]);
|
||||
|
||||
yltApp.run(function($rootScope) {
|
||||
$rootScope.loadedRunId = null;
|
||||
});
|
||||
|
||||
yltApp.config(['$routeProvider', '$locationProvider',
|
||||
function($routeProvider, $locationProvider) {
|
||||
$routeProvider.
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
var dashboardCtrl = angular.module('dashboardCtrl', ['resultsFactory', 'menuService']);
|
||||
|
||||
dashboardCtrl.controller('DashboardCtrl', ['$scope', '$routeParams', '$location', 'Results', 'Menu', function($scope, $routeParams, $location, Results, Menu) {
|
||||
dashboardCtrl.controller('DashboardCtrl', ['$scope', '$rootScope', '$routeParams', '$location', 'Results', 'Menu', function($scope, $rootScope, $routeParams, $location, Results, Menu) {
|
||||
$scope.runId = $routeParams.runId;
|
||||
$scope.Menu = Menu.setCurrentPage('dashboard', $scope.runId);
|
||||
|
||||
Results.get({runId: $routeParams.runId}, function(result) {
|
||||
$scope.result = result;
|
||||
console.log(result);
|
||||
});
|
||||
function loadResults() {
|
||||
// Load result if needed
|
||||
if (!$rootScope.loadedResult || $rootScope.loadedResult.runId !== $routeParams.runId) {
|
||||
Results.get({runId: $routeParams.runId}, function(result) {
|
||||
$rootScope.loadedResult = result;
|
||||
$scope.result = result;
|
||||
});
|
||||
} else {
|
||||
$scope.result = $rootScope.loadedResult;
|
||||
}
|
||||
}
|
||||
|
||||
$scope.showRulePage = function(ruleName) {
|
||||
$location.path('/result/' + $scope.runId + '/rule/' + ruleName);
|
||||
};
|
||||
|
||||
loadResults();
|
||||
}]);
|
||||
@@ -0,0 +1,5 @@
|
||||
var ruleCtrl = angular.module('ruleCtrl', []);
|
||||
|
||||
ruleCtrl.controller('RuleCtrl', ['$scope', function($scope) {
|
||||
|
||||
}]);
|
||||
@@ -0,0 +1,132 @@
|
||||
var timelineCtrl = angular.module('timelineCtrl', []);
|
||||
|
||||
timelineCtrl.controller('TimelineCtrl', ['$scope', '$rootScope', '$routeParams', '$timeout', 'Menu', 'Results', function($scope, $rootScope, $routeParams, $timeout, Menu, Results) {
|
||||
$scope.runId = $routeParams.runId;
|
||||
$scope.Menu = Menu.setCurrentPage('timeline', $scope.runId);
|
||||
|
||||
function loadResults() {
|
||||
// Load result if needed
|
||||
if (!$rootScope.loadedResult || $rootScope.loadedResult.runId !== $routeParams.runId) {
|
||||
Results.get({runId: $routeParams.runId}, function(result) {
|
||||
$rootScope.loadedResult = result;
|
||||
$scope.result = result;
|
||||
render();
|
||||
});
|
||||
} else {
|
||||
$scope.result = $rootScope.loadedResult;
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
function render() {
|
||||
initExecutionTree();
|
||||
initTimeline();
|
||||
$timeout(initProfiler, 100);
|
||||
}
|
||||
|
||||
function initExecutionTree() {
|
||||
var originalExecutions = $scope.result.javascriptExecutionTree.children || [];
|
||||
$scope.executionTree = [];
|
||||
|
||||
originalExecutions.forEach(function(node) {
|
||||
|
||||
// Prepare a faster angular search by creating a kind of search index
|
||||
node.searchIndex = (node.data.callDetails) ? [node.data.type].concat(node.data.callDetails.arguments).join('°°') : node.data.type;
|
||||
|
||||
$scope.executionTree.push(node);
|
||||
});
|
||||
}
|
||||
|
||||
function initTimeline() {
|
||||
|
||||
// Split the timeline into 200 intervals
|
||||
var numberOfIntervals = 199;
|
||||
var lastEvent = $scope.executionTree[$scope.executionTree.length - 1];
|
||||
$scope.endTime = lastEvent.data.timestamp + (lastEvent.data.time || 0);
|
||||
$scope.timelineIntervalDuration = $scope.endTime / numberOfIntervals;
|
||||
|
||||
// Pre-fill array of as many elements as there are milleseconds
|
||||
var millisecondsArray = Array.apply(null, new Array($scope.endTime + 1)).map(Number.prototype.valueOf,0);
|
||||
|
||||
// Create the milliseconds array from the execution tree
|
||||
$scope.executionTree.forEach(function(node) {
|
||||
if (node.data.time !== undefined) {
|
||||
|
||||
// Ignore artefacts (durations > 100ms)
|
||||
var time = Math.min(node.data.time, 100) || 1;
|
||||
|
||||
for (var i=node.data.timestamp, max=node.data.timestamp + time ; i<max ; i++) {
|
||||
millisecondsArray[i] |= 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Pre-fill array of 200 elements
|
||||
$scope.timeline = Array.apply(null, new Array(numberOfIntervals + 1)).map(Number.prototype.valueOf,0);
|
||||
|
||||
// Create the timeline from the milliseconds array
|
||||
millisecondsArray.forEach(function(value, timestamp) {
|
||||
if (value === 1) {
|
||||
$scope.timeline[Math.floor(timestamp / $scope.timelineIntervalDuration)] += 1;
|
||||
}
|
||||
});
|
||||
|
||||
// Get the maximum value of the array (needed for display)
|
||||
$scope.timelineMax = Math.max.apply(Math, $scope.timeline);
|
||||
}
|
||||
|
||||
|
||||
function initProfiler() {
|
||||
$scope.profilerData = $scope.executionTree;
|
||||
}
|
||||
|
||||
|
||||
function parseBacktrace(str) {
|
||||
if (!str) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var out = [];
|
||||
var splited = str.split(' / ');
|
||||
splited.forEach(function(trace) {
|
||||
var result = /^(\S*)\s?\(?(https?:\/\/\S+):(\d+)\)?$/g.exec(trace);
|
||||
if (result && result[2].length > 0) {
|
||||
var filePath = result[2];
|
||||
var chunks = filePath.split('/');
|
||||
var fileName = chunks[chunks.length - 1];
|
||||
|
||||
out.push({
|
||||
fnName: result[1],
|
||||
fileName: fileName,
|
||||
filePath: filePath,
|
||||
line: result[3]
|
||||
});
|
||||
}
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
$scope.filter = function(textFilter, scriptName) {
|
||||
|
||||
};
|
||||
|
||||
$scope.onNodeDetailsClick = function(node) {
|
||||
var isOpen = node.showDetails;
|
||||
if (!isOpen) {
|
||||
// Close all other nodes
|
||||
$scope.executionTree.forEach(function(currentNode) {
|
||||
currentNode.showDetails = false;
|
||||
});
|
||||
|
||||
// Parse the backtrace
|
||||
if (!node.parsedBacktrace) {
|
||||
node.parsedBacktrace = parseBacktrace(node.data.backtrace);
|
||||
}
|
||||
|
||||
}
|
||||
node.showDetails = !isOpen;
|
||||
};
|
||||
|
||||
loadResults();
|
||||
|
||||
}]);
|
||||
@@ -2,6 +2,6 @@ var resultsFactory = angular.module('resultsFactory', ['ngResource']);
|
||||
|
||||
resultsFactory.factory('Results', ['$resource', function($resource) {
|
||||
return $resource('/api/results/:runId', {
|
||||
'get': {method: 'GET', params: {runId: '@runId'}}
|
||||
|
||||
});
|
||||
}]);
|
||||
@@ -1,17 +1,4 @@
|
||||
|
||||
/* Timeline colors, related to Window Performances */
|
||||
@domCreationColor: #FF6600;
|
||||
@domCreationBg: #FFE0CC;
|
||||
@domContentLoadedColor: #A7E846;
|
||||
@domContentLoadedBg: #E0FFD1;
|
||||
@domContentLoadedEndColor: #7ECCCC;
|
||||
@domContentLoadedEndBg: #D8F0F0;
|
||||
@domCompleteColor: #C2A3FF;
|
||||
@domCompleteBg: #EDE3FF;
|
||||
@domInteractiveColor: #FFE433;
|
||||
@domInteractiveBg: #FFFCCC;
|
||||
|
||||
|
||||
.resultsMenu {
|
||||
margin-top: 2em;
|
||||
}
|
||||
@@ -181,284 +168,6 @@ h4 {
|
||||
}
|
||||
|
||||
|
||||
.timeline {
|
||||
margin: 2em 0 5em;
|
||||
}
|
||||
.timeline .chart {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #000;
|
||||
}
|
||||
.timeline .startTime, .timeline .endTime {
|
||||
position: absolute;
|
||||
bottom: 0.5em;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.timeline .startTime {
|
||||
left: 0em;
|
||||
}
|
||||
.timeline .endTime {
|
||||
right: 0em;
|
||||
}
|
||||
.timeline .chartPoints {
|
||||
display: table;
|
||||
height: 100px;
|
||||
width: 99%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.timeline .interval {
|
||||
display: table-cell;
|
||||
position: relative;
|
||||
height: 100px;
|
||||
width: 0.5%;
|
||||
}
|
||||
.timeline .interval .color {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.timeline div.interval:hover {
|
||||
background: #9C4274;
|
||||
}
|
||||
.timeline .interval:hover .color {
|
||||
background: #F04DA7;
|
||||
}
|
||||
.timeline .domComplete.interval {
|
||||
background: @domCompleteBg;
|
||||
}
|
||||
.timeline .domComplete .color {
|
||||
background: @domCompleteColor;
|
||||
}
|
||||
.timeline .domContentLoadedEnd.interval {
|
||||
background: @domContentLoadedEndBg;
|
||||
}
|
||||
.timeline .domContentLoadedEnd .color {
|
||||
background: @domContentLoadedEndColor;
|
||||
}
|
||||
.timeline .domContentLoaded.interval {
|
||||
background: @domContentLoadedBg;
|
||||
}
|
||||
.timeline .domContentLoaded .color {
|
||||
background: @domContentLoadedColor;
|
||||
}
|
||||
.timeline .domInteractive.interval {
|
||||
background: @domInteractiveBg;
|
||||
}
|
||||
.timeline .domInteractive .color {
|
||||
background: @domInteractiveColor;
|
||||
}
|
||||
.timeline .domCreation.interval {
|
||||
background: @domCreationBg;
|
||||
}
|
||||
.timeline .domCreation .color {
|
||||
background: @domCreationColor;
|
||||
}
|
||||
.timeline .tooltip.detailsOverlay {
|
||||
position: absolute;
|
||||
display: none;
|
||||
width: auto;
|
||||
padding: 0.5em 1em;
|
||||
top: -1.5em;
|
||||
right: 1em;
|
||||
}
|
||||
.timeline .interval:hover .tooltip {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.timeline .legend {
|
||||
display: table;
|
||||
width: 100%;
|
||||
margin-top: 1em;
|
||||
}
|
||||
.timeline .legend > div {
|
||||
display: table-row;
|
||||
}
|
||||
.timeline .legend > div > div {
|
||||
position: relative;
|
||||
display: table-cell;
|
||||
margin-top: 1em;
|
||||
}
|
||||
.timeline .titles {
|
||||
font-weight: bold;
|
||||
}
|
||||
.timeline .titles > div {
|
||||
padding: 0 1em 0 2em;
|
||||
}
|
||||
.timeline .tips {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
.timeline .tips > div {
|
||||
padding: 1em 1em 0 0;
|
||||
}
|
||||
.timeline .legend .color {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
height: 1.5em;
|
||||
width: 1.5em;
|
||||
border-radius: 0.2em;
|
||||
}
|
||||
|
||||
|
||||
.metrics h4 {
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
.metrics .module {
|
||||
padding-left: 4em;
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
|
||||
.metrics .legend {
|
||||
font-style: italic;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.metrics .offenders {
|
||||
padding-left: 0em;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.metrics .offenders div {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.metrics .offenders ul {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
.filters {
|
||||
margin: 1em 0;
|
||||
padding: 0.5em;
|
||||
border: 1px dotted #aaa;
|
||||
}
|
||||
|
||||
.slowRequestsLimit {
|
||||
width: 3em;
|
||||
font-size: 1em;
|
||||
text-align: right;
|
||||
border: 1px solid #aaa;
|
||||
}
|
||||
|
||||
input.textFilter {
|
||||
box-shadow: none;
|
||||
font-size: 1em;
|
||||
padding: 0 0.2em;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: none;
|
||||
width: 15em;
|
||||
}
|
||||
|
||||
.table {
|
||||
display: table;
|
||||
width: 100%;
|
||||
border-spacing: 0.25em;
|
||||
}
|
||||
|
||||
.table > div {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.table > .headers > div {
|
||||
font-weight: bold;
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
|
||||
.table > div > div {
|
||||
padding: 0.1em 1em;
|
||||
background: #f2f2f2;
|
||||
display: table-cell;
|
||||
text-align: left;
|
||||
}
|
||||
.table > div.jsError > .type, .table > div.jsError > .value {
|
||||
color: #e74c3c;
|
||||
font-weight: bold;
|
||||
}
|
||||
.table > div.windowPerformance > div, .table > div.windowPerformance > div.startTime {
|
||||
background: #EBD8E2;
|
||||
}
|
||||
.table > div.showingDetails > div {
|
||||
background: #f1c40f;
|
||||
}
|
||||
|
||||
.table > div > .index {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.table > div > .type {
|
||||
white-space:nowrap;
|
||||
}
|
||||
|
||||
.table > div > .value {
|
||||
width: 70%;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.table > div > .details {
|
||||
position: relative;
|
||||
}
|
||||
.table .details .icon-question {
|
||||
color: #f1c40f;
|
||||
cursor: pointer;
|
||||
}
|
||||
.table .icon-warning {
|
||||
display: inline-block;
|
||||
width: 0.8em;
|
||||
}
|
||||
|
||||
.detailsOverlay {
|
||||
position: absolute;
|
||||
right: 3em;
|
||||
top: -3em;
|
||||
width: 45em;
|
||||
min-height: 1em;
|
||||
padding: 0 1em 1em;
|
||||
background: #fff;
|
||||
border: 2px solid #f1c40f;
|
||||
border-radius: 0.5em;
|
||||
z-index: 1;
|
||||
}
|
||||
@media screen and (max-width: 1024px) {
|
||||
.detailsOverlay {
|
||||
width: 25em;
|
||||
}
|
||||
}
|
||||
.detailsOverlay .closeBtn {
|
||||
position: absolute;
|
||||
top: 0.5em;
|
||||
right: 0.5em;
|
||||
color: #f1c40f;
|
||||
cursor: pointer;
|
||||
}
|
||||
.detailsOverlay .advice {
|
||||
color: #e74c3c;
|
||||
font-weight: bold;
|
||||
}
|
||||
.detailsOverlay .trace {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.table > div > .duration, .table > div > .startTime {
|
||||
text-align: center;
|
||||
white-space:nowrap;
|
||||
}
|
||||
.table > div > .startTime.domComplete {
|
||||
background: @domCompleteBg;
|
||||
}
|
||||
.table > div > .startTime.domContentLoadedEnd {
|
||||
background: @domContentLoadedEndBg;
|
||||
}
|
||||
.table > div > .startTime.domContentLoaded {
|
||||
background: @domContentLoadedBg;
|
||||
}
|
||||
.table > div > .startTime.domInteractive {
|
||||
background: @domInteractiveBg;
|
||||
}
|
||||
.table > div > .startTime.domCreation {
|
||||
background: @domCreationBg;
|
||||
}
|
||||
|
||||
|
||||
/**** NgModal popin (have a look inside bower_components) ****/
|
||||
.ng-modal {
|
||||
position: fixed;
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
/* Timeline colors, related to Window Performances */
|
||||
@domCreationColor: #FF6600;
|
||||
@domCreationBg: #FFE0CC;
|
||||
@domContentLoadedColor: #A7E846;
|
||||
@domContentLoadedBg: #E0FFD1;
|
||||
@domContentLoadedEndColor: #7ECCCC;
|
||||
@domContentLoadedEndBg: #D8F0F0;
|
||||
@domCompleteColor: #C2A3FF;
|
||||
@domCompleteBg: #EDE3FF;
|
||||
@domInteractiveColor: #FFE433;
|
||||
@domInteractiveBg: #FFFCCC;
|
||||
|
||||
|
||||
.timeline {
|
||||
margin: 2em 0 5em;
|
||||
}
|
||||
.timeline .chart {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #000;
|
||||
}
|
||||
.timeline .startTime, .timeline .endTime {
|
||||
position: absolute;
|
||||
bottom: 0.5em;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.timeline .startTime {
|
||||
left: 0em;
|
||||
}
|
||||
.timeline .endTime {
|
||||
right: 0em;
|
||||
}
|
||||
.timeline .chartPoints {
|
||||
display: table;
|
||||
height: 100px;
|
||||
width: 99%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.timeline .interval {
|
||||
display: table-cell;
|
||||
position: relative;
|
||||
height: 100px;
|
||||
width: 0.5%;
|
||||
}
|
||||
.timeline .interval .color {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.timeline div.interval:hover {
|
||||
background: #9C4274;
|
||||
}
|
||||
.timeline .interval:hover .color {
|
||||
background: #F04DA7;
|
||||
}
|
||||
.timeline .domComplete.interval {
|
||||
background: @domCompleteBg;
|
||||
}
|
||||
.timeline .domComplete .color {
|
||||
background: @domCompleteColor;
|
||||
}
|
||||
.timeline .domContentLoadedEnd.interval {
|
||||
background: @domContentLoadedEndBg;
|
||||
}
|
||||
.timeline .domContentLoadedEnd .color {
|
||||
background: @domContentLoadedEndColor;
|
||||
}
|
||||
.timeline .domContentLoaded.interval {
|
||||
background: @domContentLoadedBg;
|
||||
}
|
||||
.timeline .domContentLoaded .color {
|
||||
background: @domContentLoadedColor;
|
||||
}
|
||||
.timeline .domInteractive.interval {
|
||||
background: @domInteractiveBg;
|
||||
}
|
||||
.timeline .domInteractive .color {
|
||||
background: @domInteractiveColor;
|
||||
}
|
||||
.timeline .domCreation.interval {
|
||||
background: @domCreationBg;
|
||||
}
|
||||
.timeline .domCreation .color {
|
||||
background: @domCreationColor;
|
||||
}
|
||||
.timeline .tooltip.detailsOverlay {
|
||||
position: absolute;
|
||||
display: none;
|
||||
width: auto;
|
||||
padding: 0.5em 1em;
|
||||
top: -1.5em;
|
||||
right: 1em;
|
||||
}
|
||||
.timeline .interval:hover .tooltip {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.timeline .legend {
|
||||
display: table;
|
||||
width: 100%;
|
||||
margin-top: 1em;
|
||||
}
|
||||
.timeline .legend > div {
|
||||
display: table-row;
|
||||
}
|
||||
.timeline .legend > div > div {
|
||||
position: relative;
|
||||
display: table-cell;
|
||||
margin-top: 1em;
|
||||
}
|
||||
.timeline .titles {
|
||||
font-weight: bold;
|
||||
}
|
||||
.timeline .titles > div {
|
||||
padding: 0 1em 0 2em;
|
||||
}
|
||||
.timeline .tips {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
.timeline .tips > div {
|
||||
padding: 1em 1em 0 0;
|
||||
}
|
||||
.timeline .legend .color {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
height: 1.5em;
|
||||
width: 1.5em;
|
||||
border-radius: 0.2em;
|
||||
}
|
||||
|
||||
.filters {
|
||||
margin: 1em 0;
|
||||
padding: 0.5em;
|
||||
border: 1px dotted #aaa;
|
||||
}
|
||||
|
||||
.slowRequestsLimit {
|
||||
width: 3em;
|
||||
font-size: 1em;
|
||||
text-align: right;
|
||||
border: 1px solid #aaa;
|
||||
}
|
||||
|
||||
input.textFilter {
|
||||
box-shadow: none;
|
||||
font-size: 1em;
|
||||
padding: 0 0.2em;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: none;
|
||||
width: 15em;
|
||||
}
|
||||
|
||||
.table {
|
||||
display: table;
|
||||
width: 100%;
|
||||
border-spacing: 0.25em;
|
||||
}
|
||||
|
||||
.table > div {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.table > .headers > div {
|
||||
font-weight: bold;
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
|
||||
.table > div > div {
|
||||
padding: 0.1em 1em;
|
||||
background: #f2f2f2;
|
||||
display: table-cell;
|
||||
text-align: left;
|
||||
}
|
||||
.table > div.jsError > .type, .table > div.jsError > .value {
|
||||
color: #e74c3c;
|
||||
font-weight: bold;
|
||||
}
|
||||
.table > div.windowPerformance > div, .table > div.windowPerformance > div.startTime {
|
||||
background: #EBD8E2;
|
||||
}
|
||||
.table > div.showingDetails > div {
|
||||
background: #f1c40f;
|
||||
}
|
||||
|
||||
.table > div > .index {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.table > div > .type {
|
||||
white-space:nowrap;
|
||||
}
|
||||
|
||||
.table > div > .value {
|
||||
width: 70%;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.table > div > .details {
|
||||
position: relative;
|
||||
}
|
||||
.table .details .icon-question {
|
||||
color: #f1c40f;
|
||||
cursor: pointer;
|
||||
}
|
||||
.table .icon-warning {
|
||||
display: inline-block;
|
||||
width: 0.8em;
|
||||
}
|
||||
|
||||
.detailsOverlay {
|
||||
position: absolute;
|
||||
right: 3em;
|
||||
top: -3em;
|
||||
width: 45em;
|
||||
min-height: 1em;
|
||||
padding: 0 1em 1em;
|
||||
background: #fff;
|
||||
border: 2px solid #f1c40f;
|
||||
border-radius: 0.5em;
|
||||
z-index: 1;
|
||||
}
|
||||
@media screen and (max-width: 1024px) {
|
||||
.detailsOverlay {
|
||||
width: 25em;
|
||||
}
|
||||
}
|
||||
.detailsOverlay .closeBtn {
|
||||
position: absolute;
|
||||
top: 0.5em;
|
||||
right: 0.5em;
|
||||
color: #f1c40f;
|
||||
cursor: pointer;
|
||||
}
|
||||
.detailsOverlay .advice {
|
||||
color: #e74c3c;
|
||||
font-weight: bold;
|
||||
}
|
||||
.detailsOverlay .trace {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.table > div > .duration, .table > div > .startTime {
|
||||
text-align: center;
|
||||
white-space:nowrap;
|
||||
}
|
||||
.table > div > .startTime.domComplete {
|
||||
background: @domCompleteBg;
|
||||
}
|
||||
.table > div > .startTime.domContentLoadedEnd {
|
||||
background: @domContentLoadedEndBg;
|
||||
}
|
||||
.table > div > .startTime.domContentLoaded {
|
||||
background: @domContentLoadedBg;
|
||||
}
|
||||
.table > div > .startTime.domInteractive {
|
||||
background: @domInteractiveBg;
|
||||
}
|
||||
.table > div > .startTime.domCreation {
|
||||
background: @domCreationBg;
|
||||
}
|
||||
.execution .icon-warning {
|
||||
color: #e74c3c;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -9,6 +9,8 @@
|
||||
<link rel="stylesheet" type="text/css" href="/front/css/index.css">
|
||||
<link rel="stylesheet" type="text/css" href="/front/css/dashboard.css">
|
||||
<link rel="stylesheet" type="text/css" href="/front/css/queue.css">
|
||||
<link rel="stylesheet" type="text/css" href="/front/css/rule.css">
|
||||
<link rel="stylesheet" type="text/css" href="/front/css/timeline.css">
|
||||
|
||||
<script src="/bower_components/angular/angular.min.js"></script>
|
||||
<script src="/bower_components/angular-route/angular-route.min.js"></script>
|
||||
@@ -18,6 +20,8 @@
|
||||
<script src="/front/js/controllers/aboutCtrl.js"></script>
|
||||
<script src="/front/js/controllers/dashboardCtrl.js"></script>
|
||||
<script src="/front/js/controllers/queueCtrl.js"></script>
|
||||
<script src="/front/js/controllers/ruleCtrl.js"></script>
|
||||
<script src="/front/js/controllers/timelineCtrl.js"></script>
|
||||
<script src="/front/js/models/resultsFactory.js"></script>
|
||||
<script src="/front/js/models/runsFactory.js"></script>
|
||||
<script src="/front/js/services/menuService.js"></script>
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
<div ng-include="'/front/views/resultSubHeader.html'"></div>
|
||||
<div class="execution">
|
||||
<h2>Javascript Timeline</h2>
|
||||
<p>This graph gives a quick view of when the Javascript interactions with the DOM occur during the loading of the page.</p>
|
||||
|
||||
<div class="timeline">
|
||||
<div class="chart">
|
||||
<div class="chartPoints">
|
||||
<div ng-repeat="duration in timeline track by $index"
|
||||
class="interval"
|
||||
ng-class="{
|
||||
domCreation: $index * timelineIntervalDuration < result.toolsResults.phantomas.metrics.domInteractive,
|
||||
domInteractive: $index * timelineIntervalDuration >= result.toolsResults.phantomas.metrics.domInteractive
|
||||
&& $index * timelineIntervalDuration < result.toolsResults.phantomas.metrics.domContentLoaded,
|
||||
domContentLoaded: $index * timelineIntervalDuration >= result.toolsResults.phantomas.metrics.domContentLoaded
|
||||
&& $index * timelineIntervalDuration < result.toolsResults.phantomas.metrics.domContentLoadedEnd,
|
||||
domContentLoadedEnd: $index * timelineIntervalDuration >= result.toolsResults.phantomas.metrics.domContentLoadedEnd
|
||||
&& $index * timelineIntervalDuration < result.toolsResults.phantomas.metrics.domComplete,
|
||||
domComplete: $index * timelineIntervalDuration >= result.toolsResults.phantomas.metrics.domComplete
|
||||
}">
|
||||
<div style="height: {{100 * duration / timelineMax | number: 0}}px" class="color"></div>
|
||||
<div class="tooltip detailsOverlay">
|
||||
<div>Timestamp: {{$index * timelineIntervalDuration | number: 0}} ms</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="startTime">0 ms</div>
|
||||
<div class="endTime">{{endTime | number: 0}} ms</div>
|
||||
</div>
|
||||
<div class="legend">
|
||||
<div class="titles">
|
||||
<div class="domCreation"><div class="color"></div>DOM creation</div>
|
||||
<div class="domInteractive"><div class="color"></div>DOM interactive</div>
|
||||
<div class="domContentLoaded"><div class="color"></div>DOM content loaded event</div>
|
||||
<div class="domContentLoadedEnd"><div class="color"></div>Page completion</div>
|
||||
<div class="domComplete"><div class="color"></div>Page is complete</div>
|
||||
</div>
|
||||
<div class="tips">
|
||||
<div>Executing Javascript and DOM queries here is a <b>bad practice</b> and slows down the DOM construction.</div>
|
||||
<div>Some frameworks do things here, but it's not reliable and should be avoided.</div>
|
||||
<div>Also known as "document ready". This is where you should execute <b>top-priority</b> scripts, like binding action buttons or launch a video player.</div>
|
||||
<div>Here you can execute <b>mid-priority</b> tasks. Loading a script with createElement('script') is one way to do so.</div>
|
||||
<div>The page is considered loaded, it's time for low <b>priority things</b> : trackers, social plugins, easter egg...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Javascript Profiler</h2>
|
||||
<p>
|
||||
The table below shows the interactions between Javascript and the DOM. It is useful to understand what happens while the page loads.
|
||||
</p>
|
||||
<div class="filters">
|
||||
<div>
|
||||
<input type="checkbox" ng-model="warningsFilterOn" id="warningsFilterOn" />
|
||||
<label for="warningsFilterOn">Show warnings only</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" ng-model="textFilterOn" />
|
||||
Filter by
|
||||
<input type="text" ng-model="textFilter" placeholder="search..." class="textFilter" ng-change="textFilterOn = true" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="table">
|
||||
|
||||
<toto data-info="mavariable"></toto>
|
||||
<div class="headers">
|
||||
<div><!-- index --></div>
|
||||
<div>Type</div>
|
||||
<div>Params</div>
|
||||
<div><!-- details --></div>
|
||||
<div>Timestamp</div>
|
||||
</div>
|
||||
<div ng-repeat="node in profilerData"
|
||||
ng-if="(!warningsFilterOn || node.warning || node.error)
|
||||
&& (!textFilterOn || !textFilter.length || node.searchIndex.indexOf(textFilter) >= 0)"
|
||||
ng-class="{
|
||||
showingDetails: node.showDetails,
|
||||
jsError: node.error,
|
||||
windowPerformance: node.windowPerformance
|
||||
}">
|
||||
|
||||
<div class="index">{{$index + 1}}</div>
|
||||
<div class="type">{{node.data.type}}</div>
|
||||
|
||||
<div class="value">
|
||||
{{node.data.callDetails.arguments[0]}}
|
||||
<span ng-if="node.data.callDetails.arguments.length > 1"> : {{node.data.callDetails.arguments[1]}}</span>
|
||||
<span ng-if="node.data.callDetails.arguments.length > 2"> : {{node.data.callDetails.arguments[2]}}</span>
|
||||
<span ng-if="node.data.callDetails.arguments.length > 3"> : {{node.data.callDetails.arguments[3]}}</span>
|
||||
</div>
|
||||
|
||||
<div class="details">
|
||||
<div ng-class="{
|
||||
'icon-question': !node.warning && !node.error,
|
||||
'icon-warning': node.warning || node.error
|
||||
}"
|
||||
ng-click="onNodeDetailsClick(node)"
|
||||
ng-if="node.data.type != 'jQuery loaded' && node.data.type != 'jQuery version change' && !node.windowPerformance"></div>
|
||||
|
||||
<div class="detailsOverlay" ng-if="node.showDetails">
|
||||
<div class="closeBtn" ng-click="onNodeDetailsClick(node)">✖</div>
|
||||
|
||||
<div ng-if="node.data.callDetails.context.domElement">
|
||||
<h4>Called on DOM element</h4>
|
||||
<div>{{node.data.callDetails.context.domElement}}</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="node.data.callDetails.context.length === 0">
|
||||
<h4>Called on 0 jQuery element</h4>
|
||||
<p class="advice">Useless function call, as the jQuery object is empty.</p>
|
||||
</div>
|
||||
|
||||
<div ng-if="node.data.callDetails.context.length == 1 && node.data.callDetails.context.firstElementPath">
|
||||
<h4>Called on 1 jQuery element</h4>
|
||||
<div>{{node.data.callDetails.context.firstElementPath}}</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="node.data.callDetails.context.length > 1">
|
||||
<h4>Called on {{node.data.callDetails.context.length}} jQuery elements</h4>
|
||||
<p class="advice" ng-if="node.data.type == 'jQuery - bind' && node.data.callDetails.context.length > 5">
|
||||
The .bind() method attaches the event listener to each jQuery element one by one. Using the .on() method is preferable if available (from v1.7).
|
||||
</p>
|
||||
<p ng-if="node.data.callDetails.context.firstElementPath"><b>First one is:</b> {{node.data.callDetails.context.firstElementPath}}</p>
|
||||
</div>
|
||||
|
||||
<p class="advice" ng-if="node.data.resultsNumber === 0">
|
||||
The query returned 0 results. Could it be unused or dead code?
|
||||
</p>
|
||||
|
||||
<div ng-if="node.parsedBacktrace">
|
||||
<h4>Backtrace</h4>
|
||||
<div class="table">
|
||||
<div ng-repeat="trace in node.parsedBacktrace track by $index">
|
||||
<div>{{trace.fnName || '(anonymous)'}}</div>
|
||||
<div class="trace"><a href="{{trace.filePath}}" title="{{trace.filePath}}" target="_blank">{{trace.fileName || 'HTML'}}</a>:{{trace.line}}</div>
|
||||
</div>
|
||||
<div ng-if="node.parsedBacktrace.length == 0 && node.data.type != 'script loaded'">
|
||||
<div>can't find any backtrace :/</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="startTime" ng-class="node.data.loadingStep">{{node.data.timestamp | number: 0}} ms</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,4 +1,3 @@
|
||||
var Q = require('q');
|
||||
var debug = require('debug')('ylt:ruleschecker');
|
||||
|
||||
var RulesChecker = function() {
|
||||
|
||||
+8
-11
@@ -1,9 +1,10 @@
|
||||
var Q = require('q');
|
||||
var debug = require('debug')('ylt:runner');
|
||||
var Q = require('q');
|
||||
var debug = require('debug')('ylt:runner');
|
||||
|
||||
var phantomasWrapper = require('./tools/phantomasWrapper');
|
||||
var rulesChecker = require('./rulesChecker');
|
||||
var scoreCalculator = require('./scoreCalculator');
|
||||
var phantomasWrapper = require('./tools/phantomasWrapper');
|
||||
var jsExecutionTransformer = require('./tools/jsExecutionTransformer');
|
||||
var rulesChecker = require('./rulesChecker');
|
||||
var scoreCalculator = require('./scoreCalculator');
|
||||
|
||||
|
||||
var Runner = function(params) {
|
||||
@@ -21,12 +22,8 @@ var Runner = function(params) {
|
||||
phantomasWrapper.execute(data).then(function(phantomasResults) {
|
||||
data.toolsResults.phantomas = phantomasResults;
|
||||
|
||||
// Get the JS Execution Tree from offenders and put in the main object
|
||||
try {
|
||||
data.javascriptExecutionTree = JSON.parse(data.toolsResults.phantomas.offenders.javascriptExecutionTree[0]);
|
||||
} catch(e) {
|
||||
debug('Could not find nor parse phantomas.offenders.javascriptExecutionTree');
|
||||
}
|
||||
// Treat the JS Execution Tree from offenders
|
||||
data.javascriptExecutionTree = jsExecutionTransformer.transform(data);
|
||||
|
||||
// Other tools go here
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
var debug = require('debug')('ylt:jsExecutionTransformer');
|
||||
|
||||
var jsExecutionTransformer = function() {
|
||||
|
||||
this.transform = function(data) {
|
||||
var javascriptExecutionTree = {};
|
||||
|
||||
debug('Starting JS execution transformation');
|
||||
|
||||
try {
|
||||
javascriptExecutionTree = JSON.parse(data.toolsResults.phantomas.offenders.javascriptExecutionTree[0]);
|
||||
|
||||
if (javascriptExecutionTree.children) {
|
||||
javascriptExecutionTree.children.forEach(function(node) {
|
||||
|
||||
// Mark abnormal things with a warning flag
|
||||
var contextLenght = (node.data.callDetails && node.data.callDetails.context) ? node.data.callDetails.context.length : null;
|
||||
if ((node.data.type === 'jQuery - bind' && contextLenght > 5) ||
|
||||
node.data.resultsNumber === 0 ||
|
||||
contextLenght === 0) {
|
||||
node.warning = true;
|
||||
}
|
||||
|
||||
// Mark errors with an error flag
|
||||
if (node.data.type === 'error' || node.data.type === 'jQuery version change') {
|
||||
node.error = true;
|
||||
}
|
||||
|
||||
// Mark a performance flag
|
||||
if (['domInteractive', 'domContentLoaded', 'domContentLoadedEnd', 'domComplete'].indexOf(node.data.type) >= 0) {
|
||||
node.windowPerformance = true;
|
||||
}
|
||||
|
||||
// Read the execution tree and adjust the navigation timings (cause their not very well synchronised)
|
||||
switch(node.data.type) {
|
||||
case 'domInteractive':
|
||||
data.toolsResults.phantomas.metrics.domInteractive = node.data.timestamp;
|
||||
break;
|
||||
case 'domContentLoaded':
|
||||
data.toolsResults.phantomas.metrics.domContentLoaded = node.data.timestamp;
|
||||
break;
|
||||
case 'domContentLoadedEnd':
|
||||
data.toolsResults.phantomas.metrics.domContentLoadedEnd = node.data.timestamp;
|
||||
break;
|
||||
case 'domComplete':
|
||||
data.toolsResults.phantomas.metrics.domComplete = node.data.timestamp;
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
debug('JS execution transformation complete');
|
||||
|
||||
} catch(err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
return javascriptExecutionTree;
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = new jsExecutionTransformer();
|
||||
@@ -26,6 +26,9 @@ var YellowLabTools = function(url, options) {
|
||||
var runner = new Runner(params)
|
||||
.then(function(data) {
|
||||
deferred.resolve(data);
|
||||
})
|
||||
.fail(function(err) {
|
||||
deferred.reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user