Files
YellowLabTools/app/public/scripts/resultsCtrl.js
T
2014-09-11 18:59:34 +02:00

320 lines
12 KiB
JavaScript

var app = angular.module('Results', []);
app.controller('ResultsCtrl', function ($scope) {
// Grab results from nodeJS served page
$scope.phantomasResults = window._phantomas_results;
$scope.phantomasMetadata = window._phantomas_metadata.metrics;
$scope.view = 'summary';
if ($scope.phantomasResults.metrics && $scope.phantomasResults.offenders && $scope.phantomasResults.offenders.javascriptExecutionTree) {
// Get the execution tree from the offenders
$scope.javascript = JSON.parse($scope.phantomasResults.offenders.javascriptExecutionTree);
initSummaryView();
initExecutionView();
initMetricsView();
}
$scope.setView = function(viewName) {
$scope.view = viewName;
};
$scope.onNodeDetailsClick = function(node) {
var isOpen = node.data.showDetails;
if (!isOpen) {
// Close all other nodes
$scope.javascript.children.forEach(function(currentNode) {
currentNode.data.showDetails = false;
});
// Parse the backtrace
if (!node.data.parsedBacktrace) {
node.data.parsedBacktrace = parseBacktrace(node.data.backtrace);
}
}
node.data.showDetails = !isOpen;
};
function initSummaryView() {
// Read the main elements of the tree and sum the total time
$scope.totalJSTime = 0;
treeRunner($scope.javascript, function(node) {
if (node.data.time) {
$scope.totalJSTime += node.data.time;
}
if (node.data.type !== 'main') {
// Don't check the children
return false;
}
});
// Read all the duplicated queries and calculate a more appropriated score
$scope.duplicatedQueriesCountAll = 0;
if ($scope.phantomasResults.offenders.DOMqueriesDuplicated) {
var regex = /\): *(\d+) queries$/;
$scope.phantomasResults.offenders.DOMqueriesDuplicated.forEach(function(query) {
var regexResult = regex.exec(query);
if (regexResult) {
$scope.duplicatedQueriesCountAll += parseInt(regexResult[1], 10) - 1;
}
});
}
$scope.notations = {
domComplexity: 'A',
domManipulations: 'A',
duplicatedDomQueries: 'A',
eventsBound: 'A',
badPractices: 'A',
scripts: 'A',
jQueryLoading: 'A'
};
var domComplexityScore = $scope.phantomasResults.metrics.DOMelementsCount +
Math.pow($scope.phantomasResults.metrics.DOMelementMaxDepth, 2) +
$scope.phantomasResults.metrics.iframesCount * 50;
if (domComplexityScore > 1000) {
$scope.notations.domComplexity = 'B';
}
if (domComplexityScore > 1500) {
$scope.notations.domComplexity = 'C';
}
if (domComplexityScore > 2000) {
$scope.notations.domComplexity = 'D';
}
if (domComplexityScore > 3000) {
$scope.notations.domComplexity = 'E';
}
if (domComplexityScore > 4000) {
$scope.notations.domComplexity = 'F';
}
var domManipulationsScore = $scope.phantomasResults.metrics.DOMinserts +
$scope.phantomasResults.metrics.DOMqueries * 0.5 +
$scope.totalJSTime;
if (domManipulationsScore > 100) {
$scope.notations.domManipulations = 'B';
}
if (domManipulationsScore > 200) {
$scope.notations.domManipulations = 'C';
}
if (domManipulationsScore > 300) {
$scope.notations.domManipulations = 'D';
}
if (domManipulationsScore > 500) {
$scope.notations.domManipulations = 'E';
}
if (domManipulationsScore > 800) {
$scope.notations.domManipulations = 'F';
}
var duplicatedDomQueries = $scope.duplicatedQueriesCountAll;
if (duplicatedDomQueries > 10) {
$scope.notations.duplicatedDomQueries = 'B';
}
if (duplicatedDomQueries > 50) {
$scope.notations.duplicatedDomQueries = 'C';
}
if (duplicatedDomQueries > 100) {
$scope.notations.duplicatedDomQueries = 'D';
}
if (duplicatedDomQueries > 200) {
$scope.notations.duplicatedDomQueries = 'E';
}
if (duplicatedDomQueries > 500) {
$scope.notations.duplicatedDomQueries = 'F';
}
var eventsBoundScore = $scope.phantomasResults.metrics.eventsBound;
if (eventsBoundScore > 50) {
$scope.notations.eventsBound = 'B';
}
if (eventsBoundScore > 100) {
$scope.notations.eventsBound = 'C';
}
if (eventsBoundScore > 200) {
$scope.notations.eventsBound = 'D';
}
if (eventsBoundScore > 500) {
$scope.notations.eventsBound = 'E';
}
if (eventsBoundScore > 1000) {
$scope.notations.eventsBound = 'F';
}
var badPracticesScore = $scope.phantomasResults.metrics.documentWriteCalls * 3 +
$scope.phantomasResults.metrics.evalCalls * 3 +
$scope.phantomasResults.metrics.jsErrors * 10 +
$scope.phantomasResults.metrics.consoleMessages;
if (badPracticesScore > 5) {
$scope.notations.badPractices = 'B';
}
if (badPracticesScore > 10) {
$scope.notations.badPractices = 'C';
}
if (badPracticesScore > 15) {
$scope.notations.badPractices = 'D';
}
if (badPracticesScore > 25) {
$scope.notations.badPractices = 'E';
}
if (badPracticesScore > 40) {
$scope.notations.badPractices = 'F';
}
var scriptsScore = $scope.phantomasResults.metrics.jsCount;
if (scriptsScore > 4) {
$scope.notations.scripts = 'B';
}
if (scriptsScore > 8) {
$scope.notations.scripts = 'C';
}
if (scriptsScore > 12) {
$scope.notations.scripts = 'D';
}
if (scriptsScore > 16) {
$scope.notations.scripts = 'E';
}
if (scriptsScore > 20) {
$scope.notations.scripts = 'F';
}
$scope.notations.jQueryLoading = 'NA';
if ($scope.phantomasResults.metrics.jQueryDifferentVersions > 1) {
$scope.notations.jQueryLoading = 'F';
} else if ($scope.phantomasResults.metrics.jQueryVersion) {
if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.10.') === 0 ||
$scope.phantomasResults.metrics.jQueryVersion.indexOf('1.11.') === 0 ||
$scope.phantomasResults.metrics.jQueryVersion.indexOf('1.12.') === 0 ||
$scope.phantomasResults.metrics.jQueryVersion.indexOf('2.0.') === 0 ||
$scope.phantomasResults.metrics.jQueryVersion.indexOf('2.1.') === 0 ||
$scope.phantomasResults.metrics.jQueryVersion.indexOf('2.2.') === 0) {
$scope.notations.jQueryLoading = 'A';
} else if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.8.') === 0 ||
$scope.phantomasResults.metrics.jQueryVersion.indexOf('1.9.') === 0) {
$scope.notations.jQueryLoading = 'B';
} else if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.6.') === 0 ||
$scope.phantomasResults.metrics.jQueryVersion.indexOf('1.7.') === 0) {
$scope.notations.jQueryLoading = 'C';
} else if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.4.') === 0 ||
$scope.phantomasResults.metrics.jQueryVersion.indexOf('1.5.') === 0) {
$scope.notations.jQueryLoading = 'D';
} else if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.2.') === 0 ||
$scope.phantomasResults.metrics.jQueryVersion.indexOf('1.3.') === 0) {
$scope.notations.jQueryLoading = 'E';
}
}
}
function initExecutionView() {
$scope.slowRequestsOn = false;
$scope.slowRequestsLimit = 5;
if (!$scope.javascript.children) {
return;
}
// Now read the tree and display it on a timeline
// Split the timeline into 200 intervals
var numberOfIntervals = 200;
var lastEvent = $scope.javascript.children[$scope.javascript.children.length - 1];
$scope.endTime = lastEvent.data.timestamp + (lastEvent.data.time || 0);
$scope.timelineIntervalDuration = $scope.endTime / numberOfIntervals;
// Pre-filled array of 100 elements
$scope.timeline = Array.apply(null, new Array(numberOfIntervals)).map(Number.prototype.valueOf,0);
treeRunner($scope.javascript, function(node) {
if (node.data.time) {
// If a node is between two intervals, split it. That's the meaning of the following dirty algorithm.
var startInterval = Math.floor(node.data.timestamp / $scope.timelineIntervalDuration);
var endInterval = Math.floor((node.data.timestamp + node.data.time) / $scope.timelineIntervalDuration);
if (startInterval === endInterval) {
$scope.timeline[startInterval] += node.data.time;
} else {
var timeToDispatch = node.data.time;
var startIntervalPart = ((startInterval + 1) * $scope.timelineIntervalDuration) - node.data.timestamp;
$scope.timeline[startInterval] += startIntervalPart;
timeToDispatch -= startIntervalPart;
var currentInterval = startInterval;
while(currentInterval < endInterval && currentInterval + 1 < numberOfIntervals) {
currentInterval ++;
var currentIntervalPart = Math.min(timeToDispatch, $scope.timelineIntervalDuration);
$scope.timeline[currentInterval] = currentIntervalPart;
timeToDispatch -= currentIntervalPart;
}
}
}
if (node.data.type !== 'main') {
// Don't check the children
return false;
}
});
$scope.timelineMax = Math.max.apply(Math, $scope.timeline);
}
function initMetricsView() {
// Get the Phantomas modules from metadata
$scope.metricsModule = {};
for (var metricName in $scope.phantomasMetadata) {
var metric = $scope.phantomasMetadata[metricName];
if (!$scope.metricsModule[metric.module]) {
$scope.metricsModule[metric.module] = {};
}
$scope.metricsModule[metric.module][metricName] = metric;
}
}
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;
}
// Goes on every node of the tree and calls the function fn. If fn returns false on a node, its children won't be checked.
function treeRunner(node, fn) {
if (fn(node) !== false && node.children) {
node.children.forEach(function(child) {
treeRunner(child, fn);
});
}
}
});