From b72e74e0d3c33a6a15823098579be8bdf7002692 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Tue, 18 Nov 2014 22:27:10 +0100 Subject: [PATCH 01/98] Init --- bin/cli.js | 2 ++ html/1416302062861.html | 1 + lib/yellowlabtools.js | 10 ++++++++++ 3 files changed, 13 insertions(+) create mode 100644 bin/cli.js create mode 100644 html/1416302062861.html create mode 100644 lib/yellowlabtools.js diff --git a/bin/cli.js b/bin/cli.js new file mode 100644 index 0000000..eb109ab --- /dev/null +++ b/bin/cli.js @@ -0,0 +1,2 @@ +'use strict'; + diff --git a/html/1416302062861.html b/html/1416302062861.html new file mode 100644 index 0000000..78915d3 --- /dev/null +++ b/html/1416302062861.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/yellowlabtools.js b/lib/yellowlabtools.js new file mode 100644 index 0000000..f15a41e --- /dev/null +++ b/lib/yellowlabtools.js @@ -0,0 +1,10 @@ + + + +var YellowLabTools = function() { + 'use strict'; + + +}; + +module.exports = YellowLabTools; \ No newline at end of file From 1f83fe1b48df20fac0026163d97e501d2bed58ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Fri, 28 Nov 2014 10:45:30 +0100 Subject: [PATCH 02/98] CLI is working, API not ready, Front broken --- .travis.yml | 2 +- Gruntfile.js | 48 ++- app/node_controllers/launchTestController.js | 6 - bin/cli.js | 35 +- html/1416302062861.html | 1 - lib/metadata/policies.json | 370 +++++++++++++++++++ lib/metadata/scoreProfileGeneric.json | 3 + lib/rulesChecker.js | 52 +++ lib/runner.js | 38 ++ {app/lib => lib/tools}/phantomasWrapper.js | 31 +- lib/yellowlabtools.js | 37 +- package.json | 20 +- test/api/phantomasWrapperTest.js | 78 ++++ test/api/rulesCheckerTest.js | 30 ++ test/api/yellowlabtoolsTest.js | 52 +++ test/fixtures/rulesCheckerInput.json | 25 ++ test/fixtures/rulesCheckerOutput.json | 124 +++++++ test/fixtures/rulesCheckerPolicies.json | 82 ++++ test/server/phantomasWrapperTest.js | 53 --- test/www/simple-page.html | 8 + 20 files changed, 1001 insertions(+), 94 deletions(-) delete mode 100644 html/1416302062861.html create mode 100644 lib/metadata/policies.json create mode 100644 lib/metadata/scoreProfileGeneric.json create mode 100644 lib/rulesChecker.js create mode 100644 lib/runner.js rename {app/lib => lib/tools}/phantomasWrapper.js (81%) create mode 100644 test/api/phantomasWrapperTest.js create mode 100644 test/api/rulesCheckerTest.js create mode 100644 test/api/yellowlabtoolsTest.js create mode 100644 test/fixtures/rulesCheckerInput.json create mode 100644 test/fixtures/rulesCheckerOutput.json create mode 100644 test/fixtures/rulesCheckerPolicies.json delete mode 100644 test/server/phantomasWrapperTest.js create mode 100644 test/www/simple-page.html diff --git a/.travis.yml b/.travis.yml index fd3aacd..ed73ffb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,4 +5,4 @@ before_install: - "npm install -g grunt-cli" - "npm install -g phantomjs" install: npm install -before_script: grunt build \ No newline at end of file +before_script: grunt test \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js index 70f9c5c..25bf0fb 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -31,7 +31,9 @@ module.exports = function(grunt) { jshint: { all: [ '*.js', - 'app/lib/*', + 'app/lib/*.js', + 'bin/*.js', + 'lib/**/*.js', 'app/nodeControllers/*.js', 'app/public/scripts/*.js', 'phantomas_custom/**/*.js' @@ -52,9 +54,13 @@ module.exports = function(grunt) { } }, blanket: { - coverage: { + coverageApp: { src: ['app/'], dest: 'coverage/app/' + }, + coverageLib: { + src: ['lib/'], + dest: 'coverage/lib/' } }, mochaTest: { @@ -62,18 +68,29 @@ module.exports = function(grunt) { options: { reporter: 'spec', }, - src: ['coverage/test/server/*.js'] + src: ['coverage/test/api/*.js'] + }, + 'test-current-work': { + options: { + reporter: 'spec', + }, + src: ['coverage/test/api/rulesCheckerTest.js'] }, coverage: { options: { reporter: 'html-cov', - // use the quiet flag to suppress the mocha console output quiet: true, - // specify a destination file to capture the mocha - // output (the quiet option does not suppress this) captureFile: 'coverage/coverage.html' }, - src: ['coverage/test/server/*.js'] + src: ['coverage/test/api/*.js'] + } + }, + express: { + test: { + options: { + port: 8388, + bases: 'test/www' + } } } }); @@ -87,7 +104,6 @@ module.exports = function(grunt) { ]); grunt.registerTask('build', [ - 'jshint', 'less' ]); @@ -96,10 +112,24 @@ module.exports = function(grunt) { ]); grunt.registerTask('test', [ + 'build', + 'jshint', + 'express:test', 'clean:coverage', 'blanket', 'copy:coverage', - 'mochaTest' + 'mochaTest:test', + 'mochaTest:coverage' + ]); + + grunt.registerTask('test-current-work', [ + 'build', + 'jshint', + 'express:test', + 'clean:coverage', + 'blanket', + 'copy:coverage', + 'mochaTest:test-current-work' ]); }; \ No newline at end of file diff --git a/app/node_controllers/launchTestController.js b/app/node_controllers/launchTestController.js index 5ea78d8..a3ee5fc 100644 --- a/app/node_controllers/launchTestController.js +++ b/app/node_controllers/launchTestController.js @@ -9,16 +9,10 @@ var strReplace = require('../lib/strReplace'); var launchTestController = function(req, res, testQueue, googleAnalyticsId) { 'use strict'; - // Generate test id - var testId = (Date.now()*1000 + Math.round(Math.random()*1000)).toString(36); - var resultsPath = 'results/' + testId; var phantomasResultsPath = resultsPath + '/results.json'; var url = req.body.url; - if (url.indexOf('http://') !== 0 && url.indexOf('https://') !== 0) { - url = 'http://' + url; - } var options = {}; if (req.body.timeout) { diff --git a/bin/cli.js b/bin/cli.js index eb109ab..e862f88 100644 --- a/bin/cli.js +++ b/bin/cli.js @@ -1,2 +1,35 @@ -'use strict'; +#!/usr/bin/env node +//var color = require('colors'); + +var YellowLabTools = require('../lib/yellowlabtools'); + +// Check parameters +if (process.argv.length !== 3) { + console.error('Incorrect parameters'); + console.error('\nUsage: ylt \n'); + process.exit(1); +} + +var url = process.argv[2]; + +(function execute(url) { + 'use strict'; + + var ylt = new YellowLabTools(url); + console.log('Test launched...'); + + ylt. + then(function(data) { + + console.log('Success'); + console.log(JSON.stringify(data.rules, null, 2)); + + }).fail(function(err) { + + console.error('Test failed for %s', url); + console.error(err); + + }); + +})(url); \ No newline at end of file diff --git a/html/1416302062861.html b/html/1416302062861.html deleted file mode 100644 index 78915d3..0000000 --- a/html/1416302062861.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lib/metadata/policies.json b/lib/metadata/policies.json new file mode 100644 index 0000000..30c3402 --- /dev/null +++ b/lib/metadata/policies.json @@ -0,0 +1,370 @@ +{ + "DOMelementsCount": { + "tool": "phantomas", + "label": "DOM elements count", + "message": "

A high number of DOM elements means a lot of work for the browser to render the page.

It also slows down Javascript DOM queries, as there are more elements to search through.

", + "isOkThreshold": 1000, + "isBadThreshold": 3000, + "isAbnormalThreshold": 5000 + }, + "DOMelementMaxDepth": { + "tool": "phantomas", + "label": "DOM max depth", + "message": "

A deep DOM makes the CSS matching with DOM elements difficult.

It also slows down Javascript modifications to the DOM because changing the dimensions of an element makes the browser re-calculate the dimensions of it's parents. Same thing for Javascript events, that bubble up to the document root.

", + "isOkThreshold": 10, + "isBadThreshold": 20, + "isAbnormalThreshold": 30 + }, + "iframesCount": { + "tool": "phantomas", + "label": "Number of iframes", + "message": "

iFrames are the most complex HTML elements. They are pages, just like the main page, and the browser needs to create a new page context, which has a cost.

", + "isOkThreshold": 2, + "isBadThreshold": 15, + "isAbnormalThreshold": 30 + }, + "DOMidDuplicated": { + "tool": "phantomas", + "label": "IDs duplicated", + "message": "

IDs of HTML elements must be document-wide unique. This can cause problems with getElementById returning the wrong element.

", + "isOkThreshold": 0, + "isBadThreshold": 5, + "isAbnormalThreshold": 10 + }, + "DOMinserts": { + "tool": "phantomas", + "label": "DOM inserts", + "message": "

Working with the DOM in Javascript triggers layout calculations and slows down the page.

Try, as much as possible, to have an HTML page fully generated by the server instead of making changes with JS.

", + "isOkThreshold": 10, + "isBadThreshold": 400, + "isAbnormalThreshold": 1000 + }, + "DOMqueries": { + "tool": "phantomas", + "label": "DOM queries", + "message": "

DOM queries are like looking in a large catalog of items. Even if the browsers made progress on the performances of queries, websites often make hundreds of them.

Try to reduce the number of queries by refactoring your Javascript code.

Avoid also to have a read query between two write queries. To be able to reduce the number repaints and optimize performances, browsers buffer the DOM writing operations and treat them in bulk. But each time a DOM reading is asked, the browser needs to empty the buffer. This can be particularly slow inside a loop.

", + "isOkThreshold": 50, + "isBadThreshold": 1000, + "isAbnormalThreshold": 2000 + }, + "DOMqueriesAvoidable": { + "tool": "phantomas", + "label": "Duplicated DOM queries", + "message": "

This is the number of queries that could be avoided by removing all duplicated queries.

Simply save the result of a query in a variable. Ok it is not always simple, especially with third-party scripts, but at least do it with your own code.

", + "isOkThreshold": 0, + "isBadThreshold": 200, + "isAbnormalThreshold": 500 + }, + "eventsBound": { + "tool": "phantomas", + "label": "Events bound", + "message": "

Binding too many events has a cost.

It can be avoided by using \"event delegation\". Instead of binding events on each element one by one, events delegation binds them on the top level document element and uses the bubbling principle. It will imperceptibly slow down the event when it occurs, but the loading of the page will speed-up.

", + "isOkThreshold": 100, + "isBadThreshold": 1500, + "isAbnormalThreshold": 2000 + }, + "jsErrors": { + "tool": "phantomas", + "label": "Javascript errors", + "message": "

Just to let you know there are some errors on the page.

Please note that some errors only occur in the PhantomJS browser, so you might need to double check on other browsers.

", + "isOkThreshold": 0, + "isBadThreshold": 1, + "isAbnormalThreshold": 1 + }, + "evalCalls": { + "tool": "phantomas", + "label": "eval calls", + "message": "

The 'eval' function is slow and is a bad coding practice. Try to get rid of it.

", + "isOkThreshold": 0, + "isBadThreshold": 10, + "isAbnormalThreshold": 20 + }, + "documentWriteCalls": { + "tool": "phantomas", + "label": "document.write calls", + "message": "

They slow down the page construction, especially if they are used to insert scripts in the page. Remove them ASAP.

If you cannot remove them because they come from a third-party script (such as ads), have a look at PostScribe.

", + "isOkThreshold": 0, + "isBadThreshold": 10, + "isAbnormalThreshold": 20 + }, + "consoleMessages": { + "tool": "phantomas", + "label": "Console messages", + "message": "

Try to keep your console clean when in production. Debugging is good for development only.

Writing in the console has a cost, especially when dumping large object variables.

There is also a problem with Internet Explorer 8, not knowing the console object.

", + "isOkThreshold": 0, + "isBadThreshold": 10, + "isAbnormalThreshold": 25 + }, + "globalVariables": { + "tool": "phantomas", + "label": "Global variables", + "message": "

It is a bad practice because they clutter up the global namespace. If two scripts use the same variable name in the global scope, it can cause conflicts and it is generally hard to debug.

Global variables also take a (very) little bit longer to be accessed than variables in the local scope of a function.

", + "isOkThreshold": 10, + "isBadThreshold": 50, + "isAbnormalThreshold": 200 + }, + "inBodyDomManipulations": { + "tool": "ylt", + "label": "DOM manipulations in body", + "message": "

This metric counts the number of DOM queries, DOM inserts, binds, etc. made by the Javascript before the DOMContentLoaded event.

Wait for this event before manipulating the DOM. Do not execute Javascript in the middle of the BODY as it slows down the construction of the DOM and makes a poor maintainability. This is what i call spaghetti code.

The JS Timeline tab can help you identify what's happening.

", + "isOkThreshold": 10, + "isBadThreshold": 50, + "isAbnormalThreshold": 100 + }, + "jQueryVersion": { + "tool": "ylt", + "label": "jQuery version", + "message": "

Current latest versions of jQuery are 1.11 (with support for old IE versions) and 2.1 (without).

Each new version of jQuery optimizes performances. Do not keep an old version of jQuery. Updating can sometimes break a few things, but it is generally quite easy to fix them up. So don't hesitate.

", + "isOkThreshold": 1, + "isBadThreshold": 2, + "isAbnormalThreshold": 3 + }, + "jQueryDifferentVersions": { + "tool": "ylt", + "label": "Several versions loaded", + "message": "

jQuery is a heavy library. You should never load jQuery more than one on the same page.

", + "isOkThreshold": 1, + "isBadThreshold": 2, + "isAbnormalThreshold": 3 + }, + "cssRules": { + "tool": "phantomas", + "label": "Rules count", + "message": "

Having a huge number of CSS rules hurts performances. If the number of CSS rules is higher than the number of DOM elements, there is clearly a problem.

Huge stylesheets generally occur when the different pages of a website load all the CSS, concatenated in a single stylesheet, even if a large part of the rules are page-specific. Solution is to create one main CSS file with global rules and one custom files per page.

", + "isOkThreshold": 500, + "isBadThreshold": 2500, + "isAbnormalThreshold": 4000 + }, + "cssComplexSelectors": { + "tool": "phantomas", + "label": "Complex selectors", + "message": "

Complex selectors are CSS selectors with 4 or more expressions, like \"#header ul li .foo\".

They are adding more work for the browser, and this could be avoided by simplifying selectors.

", + "isOkThreshold": 0, + "isBadThreshold": 500, + "isAbnormalThreshold": 2000 + }, + "cssComplexSelectorsByAttribute": { + "tool": "phantomas", + "label": "Complex attributes selector", + "message": "

Complex attributes selectors are one of these:

  • .foo[type*=bar] (contains bar)
  • .foo[type^=bar] (starts with bar)
  • .foo[type|=bar] (starts with bar or bar-)
  • .foo[type$=bar] (ends with bar)
  • .foo[type~=bar baz] (bar or baz)

Their matching process needs more CPU and it has a cost on performances.

", + "isOkThreshold": 0, + "isBadThreshold": 50, + "isAbnormalThreshold": 100 + }, + "cssParsingErrors": { + "tool": "phantomas", + "label": "CSS syntax error", + "message": "

Yellow Lab Tools failed to parse a CSS file. I doubt the problem comes from the css parser.

Maybe a CSS validator can help you.

", + "isOkThreshold": 1, + "isBadThreshold": 2, + "isAbnormalThreshold": 3 + }, + "cssImports": { + "tool": "phantomas", + "label": "Uses of @import", + "message": "

It’s bad for performance to use @import because CSS files don't get downloaded in parallel.

You should use <link rel='stylesheet' href='a.css'> instead.

", + "isOkThreshold": 0, + "isBadThreshold": 1, + "isAbnormalThreshold": 1 + }, + "cssDuplicatedSelectors": { + "tool": "phantomas", + "label": "Duplicated selectors", + "message": "

This is when two or more selectors are strictly identical and should be merged.

", + "isOkThreshold": 0, + "isBadThreshold": 40, + "isAbnormalThreshold": 80 + }, + "cssDuplicatedProperties": { + "tool": "phantomas", + "label": "Duplicated properties", + "message": "

This is the number of property definitions duplicated within a selector.

", + "isOkThreshold": 0, + "isBadThreshold": 50, + "isAbnormalThreshold": 100 + }, + "cssEmptyRules": { + "tool": "phantomas", + "label": "Empty rules", + "message": "

Very easy to fix: remove all empty rules.

", + "isOkThreshold": 0, + "isBadThreshold": 40, + "isAbnormalThreshold": 100 + }, + "cssExpressions": { + "tool": "phantomas", + "label": "CSS expressions", + "message": "

Such as: expression( document.body.clientWidth > 600 ? \"600px\" : \"auto\" )

This is a bad practice as it slows down browsers. There are some simpler CSS3 methods for doing this.

", + "isOkThreshold": 0, + "isBadThreshold": 1, + "isAbnormalThreshold": 20 + }, + "cssImportants": { + "tool": "phantomas", + "label": "Uses of !important", + "message": "

It can be useful, but only as a last resort. It is a bad practice because it overrides the normal cascading logic. The more you use !important, the more you need it again to over-override. This conducts to a poor maintainability.

", + "isOkThreshold": 0, + "isBadThreshold": 50, + "isAbnormalThreshold": 150 + }, + "cssOldIEFixes": { + "tool": "phantomas", + "label": "Old IE fixes", + "message": "

What browser do you need to support? Once you've got the answer, take a look at these old rules that pollute your CSS code and remove them.

IE6:

  • * html
  • html > body (everything but IE6)

IE7:

  • *height: 123px;
  • height: 123px !ie;

IE9:

  • -ms-filter
  • progid:DXImageTransform.Microsoft

", + "isOkThreshold": 0, + "isBadThreshold": 50, + "isAbnormalThreshold": 300 + }, + "cssOldPropertyPrefixes": { + "tool": "phantomas", + "label": "Old prefixes", + "message": "

Many property prefixes such as -moz- or -webkit- are not needed anymore, or by very few people. You can remove them or replace them with the non-prefixed version. This will help reducing your stylesheets weight.

", + "isOkThreshold": 0, + "isBadThreshold": 50, + "isAbnormalThreshold": 300 + }, + "cssUniversalSelectors": { + "tool": "phantomas", + "label": "Universal selectors", + "message": "

Universal selectors are the most expensive CSS selectors.

More informations here.

", + "isOkThreshold": 0, + "isBadThreshold": 40, + "isAbnormalThreshold": 80 + }, + "cssRedundantBodySelectors": { + "tool": "phantomas", + "label": "Redundant body selectors", + "message": "

This is one way to remove complexity from a CSS rule. Generally, when \"body\" is specified in a rule it can be removed, because an element is necessarily inside the body.

", + "isOkThreshold": 0, + "isBadThreshold": 50, + "isAbnormalThreshold": 150 + }, + "cssRedundantChildNodesSelectors": { + "tool": "phantomas", + "label": "Redundant tags selectors", + "message": "

Some tags included inside other tags are obvious. For example, when \"ul li\" is specified in a rule, \"ul\" can be removed because the \"li\" element is always inside a \"ul\". Same thing for \"tr td\", \"select option\", ...

Lowering compexity in CSS selectors can make the page load a little faster.

", + "isOkThreshold": 0, + "isBadThreshold": 50, + "isAbnormalThreshold": 150 + }, + "requests": { + "tool": "phantomas", + "label": "Total requests number", + "message": "

This is one of the most important performance rule. Every request is slowing down the page loading.

There are several technics to reduce their number:

  • Concatenate JS files
  • Concatenate CSS files
  • Embed or inline small JS or CSS files in the HTML
  • Create sprites or icon fonts
  • Base64 encode small images in HTML or stylesheets
  • Use lazyloading for images

", + "isOkThreshold": 15, + "isBadThreshold": 100, + "isAbnormalThreshold": 200 + }, + "htmlCount": { + "tool": "phantomas", + "label": "Document count", + "message": "

The number of HTML pages requests, HTML fragments or iframes.

", + "isOkThreshold": 10, + "isBadThreshold": 20, + "isAbnormalThreshold": 30 + }, + "jsCount": { + "tool": "phantomas", + "label": "Script count", + "message": "

Reduce the number of scripts by concatenating them.

", + "isOkThreshold": 5, + "isBadThreshold": 15, + "isAbnormalThreshold": 30 + }, + "cssCount": { + "tool": "phantomas", + "label": "CSS count", + "message": "

Reduce the number of stylesheets by concatenating them.

", + "isOkThreshold": 3, + "isBadThreshold": 10, + "isAbnormalThreshold": 22 + }, + "imageCount": { + "tool": "phantomas", + "label": "Image count", + "message": "

Reduce the number of images by lazyloading them, by spriting them or by creating an icons font.

", + "isOkThreshold": 15, + "isBadThreshold": 40, + "isAbnormalThreshold": 70 + }, + "webfontCount": { + "tool": "phantomas", + "label": "Font count", + "message": "

Fonts are loaded on the critical path of the head. Load as many as possible.

", + "isOkThreshold": 0, + "isBadThreshold": 3, + "isAbnormalThreshold": 5 + }, + "videoCount": { + "tool": "phantomas", + "label": "Videos count", + "message": "

The number of videos loaded.

", + "isOkThreshold": 1, + "isBadThreshold": 5, + "isAbnormalThreshold": 15 + }, + "jsonCount": { + "tool": "phantomas", + "label": "JSON count", + "message": "

The number of AJAX requests to JSON files or webservices.

", + "isOkThreshold": 2, + "isBadThreshold": 10, + "isAbnormalThreshold": 25 + }, + "otherCount": { + "tool": "phantomas", + "label": "Other type of requests", + "message": "

They can be Flash, XML, music or any unknown format.

", + "isOkThreshold": 5, + "isBadThreshold": 20, + "isAbnormalThreshold": 40 + }, + "notFound": { + "tool": "phantomas", + "label": "404 not found", + "message": "

404 errors are never cached, so each time a page ask for it, it hits se server. Even if it is behind a CDN or a reverse-proxy cache.

", + "isOkThreshold": 0, + "isBadThreshold": 1, + "isAbnormalThreshold": 1 + }, + "closedConnections": { + "tool": "phantomas", + "label": "Connections closed", + "message": "

This counts the number of requests not keeping the connection alive (specifying \"Connection: close\" in the response headers). It is only counting a request if it is followed by another request on the same domain.

This is slowing down the next request, because the brower needs to open a new connection to the server, which means a additional round-trip.

Correct the problem by setting a Keep-Alive header on the guilty server.

", + "isOkThreshold": 0, + "isBadThreshold": 8, + "isAbnormalThreshold": 20 + }, + "multipleRequests": { + "tool": "phantomas", + "label": "Duplicated requests", + "message": "

This only happens when the asset has no cache and is requested more than once on the same page. Be very careful about it.

", + "isOkThreshold": 0, + "isBadThreshold": 5, + "isAbnormalThreshold": 10 + }, + "cachingDisabled": { + "tool": "phantomas", + "label": "Caching disabled", + "message": "

Counts responses with caching disabled (max-age=0)

Fix immediatly if on static assets.

", + "isOkThreshold": 0, + "isBadThreshold": 12, + "isAbnormalThreshold": 25 + }, + "cachingTooShort": { + "tool": "phantomas", + "label": "Caching too short", + "message": "

Responses with too short caching time (less than a week).

The longer you cache, the better. Add versionning to your static assets, if it's not already done, and set their cache time to one year.

", + "isOkThreshold": 5, + "isBadThreshold": 20, + "isAbnormalThreshold": 40 + }, + "domains": { + "tool": "phantomas", + "label": "Different domains", + "message": "

For each domain met, the browser needs to make a DNS look-up, which is slow. Avoid having to many different domains and the page should render faster.

By the way, domain sharding is not a good practice anymore.

", + "isOkThreshold": 10, + "isBadThreshold": 25, + "isAbnormalThreshold": 40 + } +} \ No newline at end of file diff --git a/lib/metadata/scoreProfileGeneric.json b/lib/metadata/scoreProfileGeneric.json new file mode 100644 index 0000000..544b7b4 --- /dev/null +++ b/lib/metadata/scoreProfileGeneric.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/lib/rulesChecker.js b/lib/rulesChecker.js new file mode 100644 index 0000000..d2659ac --- /dev/null +++ b/lib/rulesChecker.js @@ -0,0 +1,52 @@ +var Q = require('q'); + +var RulesChecker = function() { + 'use strict'; + + this.check = function(data, policies) { + var deferred = Q.defer(); + + var results = {}; + var err = null; + + for (var metricName in policies) { + var policy = policies[metricName]; + + if (data.toolsResults[policy.tool] && + data.toolsResults[policy.tool].metrics && + (data.toolsResults[policy.tool].metrics[metricName] || data.toolsResults[policy.tool].metrics[metricName] === 0)) { + + var rule = { + value: data.toolsResults[policy.tool].metrics[metricName], + policy: policy + }; + + if (data.toolsResults[policy.tool].offenders && + data.toolsResults[policy.tool].offenders[metricName] && + data.toolsResults[policy.tool].offenders[metricName].length > 0) { + //rule.offenders = data.toolsResults[policy.tool].offenders[metricName]; + } + + rule.bad = rule.value > policy.isOkThreshold; + rule.abnormal = policy.isAbnormalThreshold && rule.value >= policy.isAbnormalThreshold; + + // A value between 0 (bad) and 100 (very good). + var score = (policy.isBadThreshold - rule.value) * 100 / (policy.isBadThreshold - policy.isOkThreshold); + rule.score = Math.min(Math.max(Math.round(score), 0), 100); + + // A value between 0 (abnormal) and negative-infinity (your website is a blackhole) + var abnormalityScore = (policy.isAbnormalThreshold - rule.value) * 100 / (policy.isAbnormalThreshold - policy.isOkThreshold); + rule.abnormalityScore = Math.min(Math.round(abnormalityScore), 0); + + results[metricName] = rule; + } + } + + data.rules = results; + deferred.resolve(data); + + return deferred.promise; + }; +}; + +module.exports = new RulesChecker(); \ No newline at end of file diff --git a/lib/runner.js b/lib/runner.js new file mode 100644 index 0000000..6270e09 --- /dev/null +++ b/lib/runner.js @@ -0,0 +1,38 @@ +var Q = require('q'); + +var phantomasWrapper = require('./tools/phantomasWrapper'); +var rulesChecker = require('./rulesChecker'); + +var Runner = function(params) { + 'use strict'; + + // The pivot format + var data = { + params: params, + toolsResults: {} + }; + + // Execute Phantomas first + var run = phantomasWrapper.execute(data); + + // Other tools go here + + + // Read each policy and save the results + run.then(function() { + var policies = require('./metadata/policies.json'); + return rulesChecker.check(data, policies); + }); + + // TODO : error handler + /*run.catch(function(err) { + console.log('The run failed'); + console.log(err); + });*/ + + run.done(data); + + return run; +}; + +module.exports = Runner; \ No newline at end of file diff --git a/app/lib/phantomasWrapper.js b/lib/tools/phantomasWrapper.js similarity index 81% rename from app/lib/phantomasWrapper.js rename to lib/tools/phantomasWrapper.js index 98ebeaf..b1e89ef 100644 --- a/app/lib/phantomasWrapper.js +++ b/lib/tools/phantomasWrapper.js @@ -1,8 +1,5 @@ -/** - * Yellow Lab Tools main file - */ - var async = require('async'); +var Q = require('q'); var phantomas = require('phantomas'); var PhantomasWrapper = function() { @@ -13,10 +10,14 @@ var PhantomasWrapper = function() { * Available options : * * - timeout : in seconds (default 60) - * - jsDeepAnalysis : should we inspect subrequests in the javascript execution tree (reported durations of main tasks will be slower than usual) + * - jsDeepAnalysis : should we inspect subrequests in the javascript execution tree? * */ - this.execute = function(task, callback) { + this.execute = function(data) { + + var deferred = Q.defer(); + + var task = data.params; var options = { // Cusomizable options @@ -55,8 +56,8 @@ var PhantomasWrapper = function() { // Kill the application if nothing happens for 10 minutes var killer = setTimeout(function() { - console.log('Killing the server because the test ' + task.testId + ' on ' + task.url + ' was launched 10 minutes ago'); - // Forever will restart the server + console.log('Killing the app because the test on ' + task.url + ' was launched 10 minutes ago'); + // If in server mode, forever will restart the server process.exit(1); }, 600000); @@ -83,21 +84,25 @@ var PhantomasWrapper = function() { } if (err) { - console.log('Attempt failed for test id ' + task.testId + '. Error code ' + err); + console.log('Attempt failed. Error code ' + err); } - cb(err, {json: json, results: results}); + cb(err, json); }); - }, function(err, data) { + }, function(err, json) { clearTimeout(killer); if (err) { - console.log('All ' + triesNumber + ' attemps failed for test id ' + task.testId); + console.log('All ' + triesNumber + ' attemps failed for the test'); + deferred.reject(err); + } else { + data.toolsResults.phantomas = json; + deferred.resolve(data); } - callback(err, data.json, data.results); }); + return deferred.promise; }; }; diff --git a/lib/yellowlabtools.js b/lib/yellowlabtools.js index f15a41e..929339f 100644 --- a/lib/yellowlabtools.js +++ b/lib/yellowlabtools.js @@ -1,10 +1,41 @@ +var Q = require('q'); + +var Runner = require('./runner'); - -var YellowLabTools = function() { +var YellowLabTools = function(url, options) { 'use strict'; - + var deferred = Q.defer(); + + if (!url) { + + deferred.reject('URL missing'); + + } else { + + // Generate a test id + var testId = (Date.now()*1000 + Math.round(Math.random()*1000)).toString(36); + + if (url.indexOf('http://') !== 0 && url.indexOf('https://') !== 0) { + url = 'http://' + url; + } + + var params = { + testId: testId, + url: url, + options: options || {} + }; + + var runner = new Runner(params); + + runner.then(function(data) { + console.log(data); + deferred.resolve(data); + }); + } + + return deferred.promise; }; module.exports = YellowLabTools; \ No newline at end of file diff --git a/package.json b/package.json index c7204c4..896a474 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,10 @@ "type": "git", "url": "git://github.com/gmetais/YellowLabTools.git" }, + "bin": { + "ylt": "./bin/cli.js" + }, + "main": "./lib/yellowlabtools.js", "dependencies": { "phantomas": "1.7.0", "express": "~4.10.1", @@ -14,18 +18,20 @@ "compression": "~1.2.0" }, "devDependencies": { + "chai": "^1.9.2", "grunt": "^0.4.5", - "grunt-contrib-jshint": "^0.10.0", - "matchdep": "^0.3.0", - "grunt-mocha-test": "^0.12.2", + "grunt-blanket": "^0.0.8", "grunt-contrib-clean": "^0.6.0", "grunt-contrib-copy": "^0.7.0", - "grunt-blanket": "^0.0.8", - "chai": "^1.9.2", + "grunt-contrib-jshint": "^0.10.0", + "grunt-contrib-less": "^0.12.0", + "grunt-express": "^1.4.1", + "grunt-fontsmith": "^0.9.1", + "grunt-mocha-test": "^0.12.2", + "matchdep": "^0.3.0", "mocha": "^2.0.1", "phantomjs": "^1.9.10", - "grunt-fontsmith": "^0.9.1", - "grunt-contrib-less": "^0.12.0" + "q": "^1.1.2" }, "scripts": { "test": "grunt test" diff --git a/test/api/phantomasWrapperTest.js b/test/api/phantomasWrapperTest.js new file mode 100644 index 0000000..a800abd --- /dev/null +++ b/test/api/phantomasWrapperTest.js @@ -0,0 +1,78 @@ +var should = require('chai').should(); +var phantomasWrapper = require('../../lib/tools/phantomasWrapper'); + +describe('phantomasWrapper', function() { + + it('should have a method execute', function() { + phantomasWrapper.should.have.property('execute').that.is.a('function'); + }); + + it('should execute', function(done) { + var url = 'http://localhost:8388/simple-page.html'; + + this.timeout(15000); + + phantomasWrapper.execute({ + url: url, + options: {} + }).then(function(json) { + json.should.be.an('object'); + json.should.have.a.property('generator'); + json.generator.should.contain('phantomas'); + json.should.have.a.property('url').that.equals(url); + json.should.have.a.property('metrics').that.is.an('object').not.empty; + json.should.have.a.property('offenders').that.is.an('object').not.empty; + json.offenders.should.have.a.property('javascriptExecutionTree').that.is.a('array').not.empty; + + done(); + }).fail(function(err) { + done(err); + }); + }); + + it('should fail with error 254', function(done) { + var url = 'http://localhost:8389/not-existing.html'; + + this.timeout(15000); + + phantomasWrapper.execute({ + url: url, + options: {} + }).then(function(json) { + + done('Error: unwanted success'); + + }).fail(function(err) { + + should.exist(err); + err.should.equal(254); + + done(); + }); + }); + + it('should timeout but return some results', function(done) { + var url = 'http://localhost:8388/simple-page.html'; + + this.timeout(5000); + phantomasWrapper.execute({ + url: url, + options: { + timeout: 1 + } + }).then(function(json) { + + json.should.be.an('object'); + json.should.have.a.property('generator'); + json.generator.should.contain('phantomas'); + json.should.have.a.property('url').that.equals(url); + json.should.have.a.property('metrics').that.is.an('object').not.empty; + json.should.have.a.property('offenders').that.is.an('object').not.empty; + json.offenders.should.have.a.property('javascriptExecutionTree').that.is.a('array').not.empty; + + done(); + }).fail(function(err) { + done(err); + }); + }); +}); \ No newline at end of file diff --git a/test/api/rulesCheckerTest.js b/test/api/rulesCheckerTest.js new file mode 100644 index 0000000..d4e3ad7 --- /dev/null +++ b/test/api/rulesCheckerTest.js @@ -0,0 +1,30 @@ +var should = require('chai').should(); +var rulesChecker = require('../../lib/rulesChecker'); + +describe('rulesChecker', function() { + + it('should have a method check', function() { + rulesChecker.should.have.property('check').that.is.a('function'); + }); + + it('should produce a nice rules object', function(done) { + var data = require('../fixtures/rulesCheckerInput.json'); + var policies = require('../fixtures/rulesCheckerPolicies.json'); + var expected = require('../fixtures/rulesCheckerOutput.json'); + + var checker = rulesChecker.check(data, policies); + + checker.then(function(results) { + try { + results.should.deep.equals(expected); + done(); + } catch(e) { + done(e); + } + }); + + checker.fail(function(err) { + done(err); + }); + }); +}); diff --git a/test/api/yellowlabtoolsTest.js b/test/api/yellowlabtoolsTest.js new file mode 100644 index 0000000..1e3eeff --- /dev/null +++ b/test/api/yellowlabtoolsTest.js @@ -0,0 +1,52 @@ +var should = require('chai').should(); +var YellowLabTools = require('../../lib/yellowlabtools.js'); + + +describe('yellowlabtools', function() { + + it('returns a promise', function() { + var ylt = new YellowLabTools(); + + ylt.should.have.property('then').that.is.a('function'); + ylt.should.have.property('fail').that.is.a('function'); + }); + + it('fails an undefined url', function(done) { + var ylt = new YellowLabTools().fail(function(err) { + err.should.be.a('string').that.equals('URL missing'); + done(); + }); + }); + + it('fails with an empty url string', function(done) { + var ylt = new YellowLabTools('').fail(function(err) { + err.should.be.a('string').that.equals('URL missing'); + done(); + }); + }); + + it('succeeds on simple-page.html', function(done) { + this.timeout(15000); + + var url = 'http://localhost:8388/simple-page.html'; + + var ylt = new YellowLabTools(url) + .then(function(data) { + data.should.be.an('object'); + data.should.have.a.property('url').that.equals(url); + + data.should.have.a.property('metrics').that.is.an('object'); + data.metrics.should.have.a.property('requests').that.equals(1); + + data.should.have.a.property('offenders').that.is.an('object'); + data.offenders.should.have.a.property('DOMelementMaxDepth'); + data.offenders.DOMelementMaxDepth.should.have.length(1); + data.offenders.DOMelementMaxDepth[0].should.equal('body > h1[1]'); + + done(); + }).fail(function(err) { + done(err); + }); + }); + +}); \ No newline at end of file diff --git a/test/fixtures/rulesCheckerInput.json b/test/fixtures/rulesCheckerInput.json new file mode 100644 index 0000000..96b949c --- /dev/null +++ b/test/fixtures/rulesCheckerInput.json @@ -0,0 +1,25 @@ +{ + "tool1": { + "metrics": { + "metric1": 1236, + "metric2": 222, + "metric3": 6666, + "metric4": 1000, + "metric5": 3000, + "metric6": 0, + "metric7": 5000 + }, + "offenders": { + "metric1": [], + "metric2": [], + "metric3": ["offender1", "offender2"], + "metric5": [] + } + }, + "tool2": { + "metrics": { + "metric1": 22, + "metric10": 22 + } + } +} \ No newline at end of file diff --git a/test/fixtures/rulesCheckerOutput.json b/test/fixtures/rulesCheckerOutput.json new file mode 100644 index 0000000..d482344 --- /dev/null +++ b/test/fixtures/rulesCheckerOutput.json @@ -0,0 +1,124 @@ +{ + "metric1": { + "policy": { + "tool": "tool1", + "label": "The metric 1", + "message": "A great message", + "isOkThreshold": 1000, + "isBadThreshold": 3000, + "isAbnormalThreshold": 5000 + }, + "value": 1236, + "bad": true, + "abnormal": false, + "score": 88, + "abnormalityScore": 0 + }, + "metric2": { + "policy": { + "tool": "tool1", + "label": "The metric 2", + "message": "A great message", + "isOkThreshold": 1000, + "isBadThreshold": 3000, + "isAbnormalThreshold": 5000 + }, + "value": 222, + "bad": false, + "abnormal": false, + "score": 100, + "abnormalityScore": 0 + }, + "metric3": { + "policy": { + "tool": "tool1", + "label": "The metric 3", + "message": "A great message", + "isOkThreshold": 1000, + "isBadThreshold": 3000, + "isAbnormalThreshold": 5000 + }, + "value": 6666, + "offenders": ["offender1", "offender2"], + "bad": true, + "abnormal": true, + "score": 0, + "abnormalityScore": -42 + }, + "metric4": { + "policy": { + "tool": "tool1", + "label": "The metric 4", + "message": "A great message", + "isOkThreshold": 1000, + "isBadThreshold": 3000, + "isAbnormalThreshold": 5000 + }, + "value": 1000, + "bad": false, + "abnormal": false, + "score": 100, + "abnormalityScore": 0 + }, + "metric5": { + "policy": { + "tool": "tool1", + "label": "The metric 5", + "message": "A great message", + "isOkThreshold": 1000, + "isBadThreshold": 3000, + "isAbnormalThreshold": 5000 + }, + "value": 3000, + "bad": true, + "abnormal": false, + "score": 0, + "abnormalityScore": 0 + }, + "metric6": { + "policy": { + "tool": "tool1", + "label": "The metric 6", + "message": "A great message", + "isOkThreshold": 1000, + "isBadThreshold": 3000, + "isAbnormalThreshold": 5000 + }, + "value": 0, + "bad": false, + "abnormal": false, + "score": 100, + "abnormalityScore": 0 + }, + "metric7": { + "policy": { + "tool": "tool1", + "label": "The metric 7", + "message": "A great message", + "isOkThreshold": 1000, + "isBadThreshold": 3000, + "isAbnormalThreshold": 5000 + }, + "value": 5000, + "bad": true, + "abnormal": true, + "score": 0, + "abnormalityScore": 0 + }, + + "metric10": { + "policy": { + "tool": "tool2", + "label": "The metric 10", + "message": "

This is from another tool!

", + "isOkThreshold": 0, + "isBadThreshold": 3, + "isAbnormalThreshold": 11 + }, + "value": 22, + "bad": true, + "abnormal": true, + "score": 0, + "abnormalityScore": -100 + } +} \ No newline at end of file diff --git a/test/fixtures/rulesCheckerPolicies.json b/test/fixtures/rulesCheckerPolicies.json new file mode 100644 index 0000000..3cadee3 --- /dev/null +++ b/test/fixtures/rulesCheckerPolicies.json @@ -0,0 +1,82 @@ +{ + "metric1": { + "tool": "tool1", + "label": "The metric 1", + "message": "A great message", + "isOkThreshold": 1000, + "isBadThreshold": 3000, + "isAbnormalThreshold": 5000 + }, + "metric2": { + "tool": "tool1", + "label": "The metric 2", + "message": "A great message", + "isOkThreshold": 1000, + "isBadThreshold": 3000, + "isAbnormalThreshold": 5000 + }, + "metric3": { + "tool": "tool1", + "label": "The metric 3", + "message": "A great message", + "isOkThreshold": 1000, + "isBadThreshold": 3000, + "isAbnormalThreshold": 5000 + }, + "metric4": { + "tool": "tool1", + "label": "The metric 4", + "message": "A great message", + "isOkThreshold": 1000, + "isBadThreshold": 3000, + "isAbnormalThreshold": 5000 + }, + "metric5": { + "tool": "tool1", + "label": "The metric 5", + "message": "A great message", + "isOkThreshold": 1000, + "isBadThreshold": 3000, + "isAbnormalThreshold": 5000 + }, + "metric6": { + "tool": "tool1", + "label": "The metric 6", + "message": "A great message", + "isOkThreshold": 1000, + "isBadThreshold": 3000, + "isAbnormalThreshold": 5000 + }, + "metric7": { + "tool": "tool1", + "label": "The metric 7", + "message": "A great message", + "isOkThreshold": 1000, + "isBadThreshold": 3000, + "isAbnormalThreshold": 5000 + }, + + "metric10": { + "tool": "tool2", + "label": "The metric 10", + "message": "

This is from another tool!

", + "isOkThreshold": 0, + "isBadThreshold": 3, + "isAbnormalThreshold": 11 + }, + + "unexistantMetric": { + "tool": "tool1", + "label": "", + "message": "", + "isOkThreshold": 1000, + "isBadThreshold": 3000, + "isAbnormalThreshold": 5000 + }, + "unexistantTool": { + "tool": "unexistant", + "isOkThreshold": 1000, + "isBadThreshold": 3000, + "isAbnormalThreshold": 5000 + } +} \ No newline at end of file diff --git a/test/server/phantomasWrapperTest.js b/test/server/phantomasWrapperTest.js deleted file mode 100644 index b585210..0000000 --- a/test/server/phantomasWrapperTest.js +++ /dev/null @@ -1,53 +0,0 @@ -var should = require('chai').should(); -var phantomasWrapper = require('../../app/lib/phantomasWrapper.js'); - -describe('phantomasWrapper', function() { - - it('should have a method execute', function() { - phantomasWrapper.should.have.property('execute').that.is.a('function'); - }); - - it('should execute', function(done) { - var url = 'http://www.google.fr/'; - - this.timeout(20000); - phantomasWrapper.execute({ - url: url, - testId: '123abc', - options:{ - - } - }, function(err, json, results) { - should.not.exist(err); - - json.should.be.an('object'); - json.should.have.a.property('generator'); - json.generator.should.contain('phantomas'); - json.should.have.a.property('url').that.equals(url); - json.should.have.a.property('metrics').that.is.an('object').not.empty; - json.should.have.a.property('offenders').that.is.an('object').not.empty; - json.offenders.should.have.a.property('javascriptExecutionTree').that.is.a('array').not.empty; - - done(); - }); - }); - - it('should fail with error 254', function(done) { - var url = 'http://www.not.existing'; - - this.timeout(15000); - phantomasWrapper.execute({ - url: url, - testId: '123abc', - options:{ - - } - }, function(err, json, results) { - should.exist(err); - err.should.equal(254); - should.not.exist(json); - - done(); - }); - }); -}); \ No newline at end of file diff --git a/test/www/simple-page.html b/test/www/simple-page.html new file mode 100644 index 0000000..83c658d --- /dev/null +++ b/test/www/simple-page.html @@ -0,0 +1,8 @@ + + + Simple page + + +

Simple page

+ + \ No newline at end of file From 2a316ebb1e8a17fb4fb1b0961c7ee549de22e220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Fri, 28 Nov 2014 17:22:32 +0100 Subject: [PATCH 03/98] Fix tests --- Gruntfile.js | 8 ++-- bin/cli.js | 2 +- lib/rulesChecker.js | 8 +--- lib/runner.js | 30 ++++++++------- lib/tools/phantomasWrapper.js | 5 ++- lib/yellowlabtools.js | 1 - test/api/phantomasWrapperTest.js | 55 ++++++++++++++++------------ test/api/rulesCheckerTest.js | 18 ++------- test/api/yellowlabtoolsTest.js | 42 ++++++++++++++++----- test/fixtures/rulesCheckerInput.json | 42 +++++++++++---------- 10 files changed, 117 insertions(+), 94 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 25bf0fb..1644902 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -49,8 +49,10 @@ module.exports = function(grunt) { }, copy: { coverage: { - src: ['test/**'], - dest: 'coverage/' + files: [ + {src: ['test/**'], dest: 'coverage/'}, + {src: ['lib/metadata/**'], dest: 'coverage/'} + ] } }, blanket: { @@ -74,7 +76,7 @@ module.exports = function(grunt) { options: { reporter: 'spec', }, - src: ['coverage/test/api/rulesCheckerTest.js'] + src: ['coverage/test/api/yellowlabtoolsTest.js'] }, coverage: { options: { diff --git a/bin/cli.js b/bin/cli.js index e862f88..3937df3 100644 --- a/bin/cli.js +++ b/bin/cli.js @@ -23,7 +23,7 @@ var url = process.argv[2]; then(function(data) { console.log('Success'); - console.log(JSON.stringify(data.rules, null, 2)); + console.log(JSON.stringify(data, null, 2)); }).fail(function(err) { diff --git a/lib/rulesChecker.js b/lib/rulesChecker.js index d2659ac..042d73e 100644 --- a/lib/rulesChecker.js +++ b/lib/rulesChecker.js @@ -4,7 +4,6 @@ var RulesChecker = function() { 'use strict'; this.check = function(data, policies) { - var deferred = Q.defer(); var results = {}; var err = null; @@ -24,7 +23,7 @@ var RulesChecker = function() { if (data.toolsResults[policy.tool].offenders && data.toolsResults[policy.tool].offenders[metricName] && data.toolsResults[policy.tool].offenders[metricName].length > 0) { - //rule.offenders = data.toolsResults[policy.tool].offenders[metricName]; + rule.offenders = data.toolsResults[policy.tool].offenders[metricName]; } rule.bad = rule.value > policy.isOkThreshold; @@ -42,10 +41,7 @@ var RulesChecker = function() { } } - data.rules = results; - deferred.resolve(data); - - return deferred.promise; + return results; }; }; diff --git a/lib/runner.js b/lib/runner.js index 6270e09..67e641c 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -6,6 +6,8 @@ var rulesChecker = require('./rulesChecker'); var Runner = function(params) { 'use strict'; + var deferred = Q.defer(); + // The pivot format var data = { params: params, @@ -13,26 +15,28 @@ var Runner = function(params) { }; // Execute Phantomas first - var run = phantomasWrapper.execute(data); + phantomasWrapper.execute(data).then(function(phantomasResults) { + data.toolsResults.phantomas = phantomasResults; - // Other tools go here + // Other tools go there - // Read each policy and save the results - run.then(function() { + // Rules checker var policies = require('./metadata/policies.json'); - return rulesChecker.check(data, policies); + data.rules = rulesChecker.check(data, policies); + + + deferred.resolve(data); + + }).fail(function(err) { + console.log('Run failed'); + console.log(err); + + deferred.reject(err); }); - // TODO : error handler - /*run.catch(function(err) { - console.log('The run failed'); - console.log(err); - });*/ - run.done(data); - - return run; + return deferred.promise; }; module.exports = Runner; \ No newline at end of file diff --git a/lib/tools/phantomasWrapper.js b/lib/tools/phantomasWrapper.js index b1e89ef..7186941 100644 --- a/lib/tools/phantomasWrapper.js +++ b/lib/tools/phantomasWrapper.js @@ -97,8 +97,9 @@ var PhantomasWrapper = function() { console.log('All ' + triesNumber + ' attemps failed for the test'); deferred.reject(err); } else { - data.toolsResults.phantomas = json; - deferred.resolve(data); + + // Success!!! + deferred.resolve(json); } }); diff --git a/lib/yellowlabtools.js b/lib/yellowlabtools.js index 929339f..a0f4e38 100644 --- a/lib/yellowlabtools.js +++ b/lib/yellowlabtools.js @@ -30,7 +30,6 @@ var YellowLabTools = function(url, options) { var runner = new Runner(params); runner.then(function(data) { - console.log(data); deferred.resolve(data); }); } diff --git a/test/api/phantomasWrapperTest.js b/test/api/phantomasWrapperTest.js index a800abd..785bdbe 100644 --- a/test/api/phantomasWrapperTest.js +++ b/test/api/phantomasWrapperTest.js @@ -13,16 +13,19 @@ describe('phantomasWrapper', function() { this.timeout(15000); phantomasWrapper.execute({ - url: url, - options: {} - }).then(function(json) { - json.should.be.an('object'); - json.should.have.a.property('generator'); - json.generator.should.contain('phantomas'); - json.should.have.a.property('url').that.equals(url); - json.should.have.a.property('metrics').that.is.an('object').not.empty; - json.should.have.a.property('offenders').that.is.an('object').not.empty; - json.offenders.should.have.a.property('javascriptExecutionTree').that.is.a('array').not.empty; + params: { + url: url, + options: {} + } + }).then(function(data) { + + data.should.be.an('object'); + data.should.have.a.property('generator'); + data.generator.should.contain('phantomas'); + data.should.have.a.property('url').that.equals(url); + data.should.have.a.property('metrics').that.is.an('object').not.empty; + data.should.have.a.property('offenders').that.is.an('object').not.empty; + data.offenders.should.have.a.property('javascriptExecutionTree').that.is.a('array').not.empty; done(); }).fail(function(err) { @@ -36,9 +39,11 @@ describe('phantomasWrapper', function() { this.timeout(15000); phantomasWrapper.execute({ - url: url, - options: {} - }).then(function(json) { + params: { + url: url, + options: {} + } + }).then(function(data) { done('Error: unwanted success'); @@ -56,19 +61,21 @@ describe('phantomasWrapper', function() { this.timeout(5000); phantomasWrapper.execute({ - url: url, - options: { - timeout: 1 + params: { + url: url, + options: { + timeout: 1 + } } - }).then(function(json) { + }).then(function(data) { - json.should.be.an('object'); - json.should.have.a.property('generator'); - json.generator.should.contain('phantomas'); - json.should.have.a.property('url').that.equals(url); - json.should.have.a.property('metrics').that.is.an('object').not.empty; - json.should.have.a.property('offenders').that.is.an('object').not.empty; - json.offenders.should.have.a.property('javascriptExecutionTree').that.is.a('array').not.empty; + data.should.be.an('object'); + data.should.have.a.property('generator'); + data.generator.should.contain('phantomas'); + data.should.have.a.property('url').that.equals(url); + data.should.have.a.property('metrics').that.is.an('object').not.empty; + data.should.have.a.property('offenders').that.is.an('object').not.empty; + data.offenders.should.have.a.property('javascriptExecutionTree').that.is.a('array').not.empty; done(); }).fail(function(err) { diff --git a/test/api/rulesCheckerTest.js b/test/api/rulesCheckerTest.js index d4e3ad7..841c1ab 100644 --- a/test/api/rulesCheckerTest.js +++ b/test/api/rulesCheckerTest.js @@ -7,24 +7,12 @@ describe('rulesChecker', function() { rulesChecker.should.have.property('check').that.is.a('function'); }); - it('should produce a nice rules object', function(done) { + it('should produce a nice rules object', function() { var data = require('../fixtures/rulesCheckerInput.json'); var policies = require('../fixtures/rulesCheckerPolicies.json'); var expected = require('../fixtures/rulesCheckerOutput.json'); - var checker = rulesChecker.check(data, policies); - - checker.then(function(results) { - try { - results.should.deep.equals(expected); - done(); - } catch(e) { - done(e); - } - }); - - checker.fail(function(err) { - done(err); - }); + var results = rulesChecker.check(data, policies); + results.should.deep.equals(expected); }); }); diff --git a/test/api/yellowlabtoolsTest.js b/test/api/yellowlabtoolsTest.js index 1e3eeff..ddc285e 100644 --- a/test/api/yellowlabtoolsTest.js +++ b/test/api/yellowlabtoolsTest.js @@ -32,16 +32,40 @@ describe('yellowlabtools', function() { var ylt = new YellowLabTools(url) .then(function(data) { - data.should.be.an('object'); - data.should.have.a.property('url').that.equals(url); - - data.should.have.a.property('metrics').that.is.an('object'); - data.metrics.should.have.a.property('requests').that.equals(1); - data.should.have.a.property('offenders').that.is.an('object'); - data.offenders.should.have.a.property('DOMelementMaxDepth'); - data.offenders.DOMelementMaxDepth.should.have.length(1); - data.offenders.DOMelementMaxDepth[0].should.equal('body > h1[1]'); + data.should.be.an('object'); + data.toolsResults.should.be.an('object'); + + // Test Phantomas + data.toolsResults.phantomas.should.be.an('object'); + data.toolsResults.phantomas.should.have.a.property('url').that.equals(url); + data.toolsResults.phantomas.should.have.a.property('metrics').that.is.an('object'); + data.toolsResults.phantomas.metrics.should.have.a.property('requests').that.equals(1); + data.toolsResults.phantomas.should.have.a.property('offenders').that.is.an('object'); + data.toolsResults.phantomas.offenders.should.have.a.property('DOMelementMaxDepth'); + data.toolsResults.phantomas.offenders.DOMelementMaxDepth.should.have.length(1); + data.toolsResults.phantomas.offenders.DOMelementMaxDepth[0].should.equal('body > h1[1]'); + + // Test rules + data.should.have.a.property('rules').that.is.an('object'); + + data.rules.should.have.a.property('DOMelementMaxDepth').that.is.an('object'); + data.rules.DOMelementMaxDepth.should.deep.equal({ + policy: { + "tool": "phantomas", + "label": "DOM max depth", + "message": "

A deep DOM makes the CSS matching with DOM elements difficult.

It also slows down Javascript modifications to the DOM because changing the dimensions of an element makes the browser re-calculate the dimensions of it's parents. Same thing for Javascript events, that bubble up to the document root.

", + "isOkThreshold": 10, + "isBadThreshold": 20, + "isAbnormalThreshold": 30 + }, + "value": 1, + "bad": false, + "abnormal": false, + "score": 100, + "abnormalityScore": 0, + "offenders": ["body > h1[1]"] + }); done(); }).fail(function(err) { diff --git a/test/fixtures/rulesCheckerInput.json b/test/fixtures/rulesCheckerInput.json index 96b949c..fd05a45 100644 --- a/test/fixtures/rulesCheckerInput.json +++ b/test/fixtures/rulesCheckerInput.json @@ -1,25 +1,27 @@ { - "tool1": { - "metrics": { - "metric1": 1236, - "metric2": 222, - "metric3": 6666, - "metric4": 1000, - "metric5": 3000, - "metric6": 0, - "metric7": 5000 + "toolsResults": { + "tool1": { + "metrics": { + "metric1": 1236, + "metric2": 222, + "metric3": 6666, + "metric4": 1000, + "metric5": 3000, + "metric6": 0, + "metric7": 5000 + }, + "offenders": { + "metric1": [], + "metric2": [], + "metric3": ["offender1", "offender2"], + "metric5": [] + } }, - "offenders": { - "metric1": [], - "metric2": [], - "metric3": ["offender1", "offender2"], - "metric5": [] - } - }, - "tool2": { - "metrics": { - "metric1": 22, - "metric10": 22 + "tool2": { + "metrics": { + "metric1": 22, + "metric10": 22 + } } } } \ No newline at end of file From 6089e5fe20adfb8d7eca0458d4b891328c7ab034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Fri, 28 Nov 2014 17:53:39 +0100 Subject: [PATCH 04/98] Introduce debug instead of console.log --- bin/cli.js | 8 ++++---- lib/rulesChecker.js | 10 ++++++++++ lib/runner.js | 5 +++-- lib/tools/phantomasWrapper.js | 16 +++++++++------- package.json | 9 +++++---- 5 files changed, 31 insertions(+), 17 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index 3937df3..88b615a 100644 --- a/bin/cli.js +++ b/bin/cli.js @@ -1,6 +1,6 @@ #!/usr/bin/env node -//var color = require('colors'); +var debug = require('debug')('ylt:cli'); var YellowLabTools = require('../lib/yellowlabtools'); @@ -17,17 +17,17 @@ var url = process.argv[2]; 'use strict'; var ylt = new YellowLabTools(url); - console.log('Test launched...'); + debug('Test launched...'); ylt. then(function(data) { - console.log('Success'); + debug('Success'); console.log(JSON.stringify(data, null, 2)); }).fail(function(err) { - console.error('Test failed for %s', url); + debug('Test failed for %s', url); console.error(err); }); diff --git a/lib/rulesChecker.js b/lib/rulesChecker.js index 042d73e..c576c72 100644 --- a/lib/rulesChecker.js +++ b/lib/rulesChecker.js @@ -1,4 +1,5 @@ var Q = require('q'); +var debug = require('debug')('ylt:ruleschecker'); var RulesChecker = function() { 'use strict'; @@ -8,6 +9,8 @@ var RulesChecker = function() { var results = {}; var err = null; + debug('Starting checking rules'); + for (var metricName in policies) { var policy = policies[metricName]; @@ -38,9 +41,16 @@ var RulesChecker = function() { rule.abnormalityScore = Math.min(Math.round(abnormalityScore), 0); results[metricName] = rule; + + } else { + + debug('Metric %s not found for tool %s', metricName, policy.tool); + } } + debug('Rules checking finished'); + return results; }; }; diff --git a/lib/runner.js b/lib/runner.js index 67e641c..c9083c1 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -1,4 +1,5 @@ var Q = require('q'); +var debug = require('debug')('ylt:yellowlabtools'); var phantomasWrapper = require('./tools/phantomasWrapper'); var rulesChecker = require('./rulesChecker'); @@ -29,8 +30,8 @@ var Runner = function(params) { deferred.resolve(data); }).fail(function(err) { - console.log('Run failed'); - console.log(err); + debug('Run failed'); + debug(err); deferred.reject(err); }); diff --git a/lib/tools/phantomasWrapper.js b/lib/tools/phantomasWrapper.js index 7186941..e1df01d 100644 --- a/lib/tools/phantomasWrapper.js +++ b/lib/tools/phantomasWrapper.js @@ -1,7 +1,9 @@ var async = require('async'); var Q = require('q'); +var debug = require('debug')('ylt:phantomaswrapper'); var phantomas = require('phantomas'); + var PhantomasWrapper = function() { 'use strict'; @@ -47,16 +49,16 @@ var PhantomasWrapper = function() { }; // Output the command line for debugging purpose - console.log('If you want to reproduce the phantomas task only, copy the following command line:'); + debug('If you want to reproduce the phantomas task only, copy the following command line:'); var optionsString = ''; for (var opt in options) { optionsString += ' ' + '--' + opt + '=' + options[opt]; } - console.log('node node_modules/phantomas/bin/phantomas.js --url=' + task.url + optionsString + ' --verbose'); + debug('node node_modules/phantomas/bin/phantomas.js --url=' + task.url + optionsString + ' --verbose'); // Kill the application if nothing happens for 10 minutes var killer = setTimeout(function() { - console.log('Killing the app because the test on ' + task.url + ' was launched 10 minutes ago'); + debug('Killing the app because the test on ' + task.url + ' was launched 10 minutes ago'); // If in server mode, forever will restart the server process.exit(1); }, 600000); @@ -66,7 +68,7 @@ var PhantomasWrapper = function() { async.retry(triesNumber, function(cb) { phantomas(task.url, options, function(err, json, results) { - console.log('Returning from Phantomas'); + debug('Returning from Phantomas'); // Adding some YellowLabTools errors here if (json && json.metrics && !json.metrics.javascriptExecutionTree) { @@ -79,12 +81,12 @@ var PhantomasWrapper = function() { // Don't cancel test if it is a timeout and we've got some results if (err === 252 && json) { - console.log('Timeout after ' + options.timeout + ' seconds. But it\'s not a problem, the test is valid.'); + debug('Timeout after ' + options.timeout + ' seconds. But it\'s not a problem, the test is valid.'); err = null; } if (err) { - console.log('Attempt failed. Error code ' + err); + debug('Attempt failed. Error code ' + err); } cb(err, json); @@ -94,7 +96,7 @@ var PhantomasWrapper = function() { clearTimeout(killer); if (err) { - console.log('All ' + triesNumber + ' attemps failed for the test'); + debug('All ' + triesNumber + ' attemps failed for the test'); deferred.reject(err); } else { diff --git a/package.json b/package.json index 896a474..eed79c0 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,13 @@ }, "main": "./lib/yellowlabtools.js", "dependencies": { - "phantomas": "1.7.0", - "express": "~4.10.1", "async": "~0.9.0", - "socket.io": "~1.2.0", "body-parser": "~1.9.2", - "compression": "~1.2.0" + "compression": "~1.2.0", + "debug": "^2.1.0", + "express": "~4.10.1", + "phantomas": "1.7.0", + "socket.io": "~1.2.0" }, "devDependencies": { "chai": "^1.9.2", From 35aed5fb552179de6c81ff95477e7eb3b9dd6e62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Fri, 28 Nov 2014 18:31:14 +0100 Subject: [PATCH 05/98] Test that console.log is never called --- package.json | 4 +++- test/api/yellowlabtoolsTest.js | 12 +++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index eed79c0..5bb8f24 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,9 @@ "matchdep": "^0.3.0", "mocha": "^2.0.1", "phantomjs": "^1.9.10", - "q": "^1.1.2" + "q": "^1.1.2", + "sinon": "^1.12.1", + "sinon-chai": "^2.6.0" }, "scripts": { "test": "grunt test" diff --git a/test/api/yellowlabtoolsTest.js b/test/api/yellowlabtoolsTest.js index ddc285e..89c36ab 100644 --- a/test/api/yellowlabtoolsTest.js +++ b/test/api/yellowlabtoolsTest.js @@ -1,6 +1,11 @@ -var should = require('chai').should(); +var chai = require('chai'); +var sinon = require('sinon'); +var sinonChai = require('sinon-chai'); +var should = chai.should(); var YellowLabTools = require('../../lib/yellowlabtools.js'); +chai.use(sinonChai); + describe('yellowlabtools', function() { @@ -28,6 +33,9 @@ describe('yellowlabtools', function() { it('succeeds on simple-page.html', function(done) { this.timeout(15000); + // Check if console.log is called + sinon.spy(console, 'log') + var url = 'http://localhost:8388/simple-page.html'; var ylt = new YellowLabTools(url) @@ -67,6 +75,8 @@ describe('yellowlabtools', function() { "offenders": ["body > h1[1]"] }); + console.log.should.not.have.been.called; + done(); }).fail(function(err) { done(err); From e18cd87a07fd0a3f7a1bd3600e1b8bc3879a8ad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Tue, 2 Dec 2014 00:08:07 +0100 Subject: [PATCH 06/98] Writing the API (workin in progress) --- Gruntfile.js | 7 +- app/lib/testQueue.js | 66 ----------------- bin/server.js | 22 ++++++ lib/server/controllers/apiController.js | 86 +++++++++++++++++++++++ lib/server/controllers/uiController.js | 0 lib/server/datastores/resultsDatastore.js | 9 +++ lib/server/datastores/runsDatastore.js | 36 ++++++++++ lib/server/datastores/runsQueue.js | 73 +++++++++++++++++++ package.json | 2 +- test/api/phantomasWrapperTest.js | 12 ++-- test/api/yellowlabtoolsTest.js | 3 +- test/server/runsDatastoreTest.js | 41 +++++++++++ test/server/runsQueueTest.js | 64 +++++++++++++++++ test/server/testQueueTest.js | 23 ------ 14 files changed, 344 insertions(+), 100 deletions(-) delete mode 100644 app/lib/testQueue.js create mode 100644 bin/server.js create mode 100644 lib/server/controllers/apiController.js create mode 100644 lib/server/controllers/uiController.js create mode 100644 lib/server/datastores/resultsDatastore.js create mode 100644 lib/server/datastores/runsDatastore.js create mode 100644 lib/server/datastores/runsQueue.js create mode 100644 test/server/runsDatastoreTest.js create mode 100644 test/server/runsQueueTest.js delete mode 100644 test/server/testQueueTest.js diff --git a/Gruntfile.js b/Gruntfile.js index 1644902..41a5eef 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -36,7 +36,8 @@ module.exports = function(grunt) { 'lib/**/*.js', 'app/nodeControllers/*.js', 'app/public/scripts/*.js', - 'phantomas_custom/**/*.js' + 'phantomas_custom/**/*.js', + 'test/**/*.js' ] }, clean: { @@ -70,13 +71,13 @@ module.exports = function(grunt) { options: { reporter: 'spec', }, - src: ['coverage/test/api/*.js'] + src: ['coverage/test/api/*.js', 'coverage/test/server/*.js'] }, 'test-current-work': { options: { reporter: 'spec', }, - src: ['coverage/test/api/yellowlabtoolsTest.js'] + src: ['coverage/test/server/runsQueueTest.js'] }, coverage: { options: { diff --git a/app/lib/testQueue.js b/app/lib/testQueue.js deleted file mode 100644 index f271ccb..0000000 --- a/app/lib/testQueue.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Creation of a queue and it's worker function - */ - -var util = require('util'); -var EventEmitter = require('events').EventEmitter; -var async = require('async'); -var phantomasWrapper = require('./phantomasWrapper'); - - -var testQueue = function() { - 'use strict'; - - var currentTask = null; - var self = this; - - var queue = async.queue(function(task, callback) { - currentTask = task; - - console.log('Starting test ' + task.testId); - - phantomasWrapper.execute(task, function(err, json, results) { - console.log('Test ' + task.testId + ' complete'); - currentTask = null; - callback(err, json, results); - self.emit('queueMoving'); - }); - }); - - - // Use this method to add a test to the queue - this.push = queue.push; - - - // Gives the position of a task in the queue - // Returns 0 if it is the current running task - // Returns -1 if not found - this.indexOf = function(testId) { - if (currentTask && currentTask.testId === testId) { - return 0; - } - - var position = -1; - if (queue.length() > 0) { - queue.tasks.forEach(function(task, index) { - if (task.data.testId === testId) { - position = index + 1; - } - }); - } - return position; - }; - - this.testComplete = function(testId) { - self.emit('testComplete', testId); - }; - - this.testFailed = function(testId) { - self.emit('testFailed', testId); - }; -}; - -// extend the EventEmitter class -util.inherits(testQueue, EventEmitter); - -module.exports = new testQueue(); \ No newline at end of file diff --git a/bin/server.js b/bin/server.js new file mode 100644 index 0000000..3f72983 --- /dev/null +++ b/bin/server.js @@ -0,0 +1,22 @@ +// Config file +var settings = require('../server_config/settings.json'); + +var express = require('express'); +var app = express(); +var server = require('http').createServer(app); +var bodyParser = require('body-parser'); +var compress = require('compression'); + +app.use(compress()); +app.use(bodyParser.urlencoded({ extended: false })); + + +// Initialize the controllers +var apiController = require('../lib/server/controllers/apiController')(app); +var uiController = require('../lib/server/controllers/uiController')(app); + + +// Launch the server +server.listen(settings.serverPort, function() { + console.log('Listening on port %d', server.address().port); +}); \ No newline at end of file diff --git a/lib/server/controllers/apiController.js b/lib/server/controllers/apiController.js new file mode 100644 index 0000000..fb9d7c6 --- /dev/null +++ b/lib/server/controllers/apiController.js @@ -0,0 +1,86 @@ +var debug = require('debug')('ylt:server'); + +var runsQueue = require('../datastores/runsQueue'); +var runsDatastore = require('../datastores/runsDatastore'); + + +function ApiController(app) { + 'use strict'; + + // Retrieve the list of all runs + /*app.get('/runs', function(req, res) { + // NOT YET + });*/ + + // Create a new run + app.post('/runs', function(req, res) { + + // Grab the test parameters + var run = { + // Generate a random run ID + _id: (Date.now()*1000 + Math.round(Math.random()*1000)).toString(36), + params: { + url: req.body.url, + waitForResponse: req.body.waitForResponse || true + } + }; + + // Add test to the testQueue + debug('Adding test %s to the queue', run._id); + var queuing = runsQueue.push(run._id); + + + // Save the run to the datastore + var position = runsQueue.getPosition(run._id); + run.status = { + statusCode: (position === 0) ? STATUS_RUNNING : STATUS_AWAITING, + position: position + }; + runsDatastore.add(run); + + + // Listening for position updates + queuing.progress(function(position) { + var savedRun = runsDatastore.get(run._id); + savedRun.status = { + statusCode: STATUS_AWAITING, + position: position + }; + runsDatastore.update(savedRun); + }); + + + queuing.then(function() { + + }); + + // The user doesn't not want to wait for the response + if (!params.waitForResponse) { + + // Sending just the test id + res.setHeader('Content-Type', 'application/javascript'); + res.send(JSON.stringify({ + testId: testId + })); + } + }); + + // Retrive one run by id + app.get('/run/:id', function(req, res) { + + }); + + // Delete one run by id + /*app.delete('/run/:id', function(req, res) { + // NOT YET + });*/ + + + var STATUS_AWAITING = 'awaiting'; + var STATUS_RUNNING = 'running'; + var STATUS_DONE = 'done'; + var STATUS_FAILED = 'failed'; + +} + +module.exports = ApiController; \ No newline at end of file diff --git a/lib/server/controllers/uiController.js b/lib/server/controllers/uiController.js new file mode 100644 index 0000000..e69de29 diff --git a/lib/server/datastores/resultsDatastore.js b/lib/server/datastores/resultsDatastore.js new file mode 100644 index 0000000..954f9b7 --- /dev/null +++ b/lib/server/datastores/resultsDatastore.js @@ -0,0 +1,9 @@ + + +function ResultsDatastore() { + 'use strict'; + + +} + +module.exports = ResultsDatastore; \ No newline at end of file diff --git a/lib/server/datastores/runsDatastore.js b/lib/server/datastores/runsDatastore.js new file mode 100644 index 0000000..c84ac4e --- /dev/null +++ b/lib/server/datastores/runsDatastore.js @@ -0,0 +1,36 @@ + + +function RunsDatastore() { + 'use strict'; + + // NOT PERSISTING RUNS + // For the moment, maybe one day + var runs = {}; + + + this.add = function(run) { + runs[run._id] = run; + }; + + this.get = function(runId) { + return runs[runId]; + }; + + this.update = function(run) { + runs[run._id] = run; + }; + + this.delete = function(runId) { + delete runs[runId]; + }; + + this.list = function() { + var runsArray = []; + Object.keys(runs).forEach(function(key) { + runsArray.push(runs[key]); + }); + return runsArray; + }; +} + +module.exports = RunsDatastore; \ No newline at end of file diff --git a/lib/server/datastores/runsQueue.js b/lib/server/datastores/runsQueue.js new file mode 100644 index 0000000..ad1a6b8 --- /dev/null +++ b/lib/server/datastores/runsQueue.js @@ -0,0 +1,73 @@ +var Q = require('q'); + + +function RunsQueue() { + 'use strict'; + + var queue = []; + + + this.push = function(runId) { + var deferred = Q.defer(); + + if (queue.length === 0) { + + // The queue is empty, let's run immediatly + queue.push({ + runId: runId + }); + + deferred.resolve(); + + } else { + + queue.push({ + runId: runId, + positionChangedCallback: function(position) { + deferred.notify(position); + }, + itIsTimeCallback: function() { + deferred.resolve(); + } + }); + } + + return deferred.promise; + }; + + + this.getPosition = function(runId) { + // Position 0 means it's a work in progress (a run is removed AFTER it is finished, not before) + var position = -1; + + queue.some(function(run, index) { + if (run.runId === runId) { + position = index; + return true; + } + return false; + }); + + return position; + }; + + + this.remove = function(runId) { + var position = this.getPosition(runId); + if (position >= 0) { + queue.splice(position, 1); + } + + // Update other runs' positions + queue.forEach(function(run, index) { + if (index === 0 && run.itIsTimeCallback) { + run.itIsTimeCallback(); + } else if (index > 0 && run.positionChangedCallback) { + run.positionChangedCallback(index); + } + }); + + }; +} + +module.exports = RunsQueue; \ No newline at end of file diff --git a/package.json b/package.json index 5bb8f24..29bca24 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "body-parser": "~1.9.2", "compression": "~1.2.0", "debug": "^2.1.0", - "express": "~4.10.1", + "express": "~4.10.4", "phantomas": "1.7.0", "socket.io": "~1.2.0" }, diff --git a/test/api/phantomasWrapperTest.js b/test/api/phantomasWrapperTest.js index 785bdbe..904915e 100644 --- a/test/api/phantomasWrapperTest.js +++ b/test/api/phantomasWrapperTest.js @@ -23,9 +23,9 @@ describe('phantomasWrapper', function() { data.should.have.a.property('generator'); data.generator.should.contain('phantomas'); data.should.have.a.property('url').that.equals(url); - data.should.have.a.property('metrics').that.is.an('object').not.empty; - data.should.have.a.property('offenders').that.is.an('object').not.empty; - data.offenders.should.have.a.property('javascriptExecutionTree').that.is.a('array').not.empty; + data.should.have.a.property('metrics').that.is.an('object').not.empty(); + data.should.have.a.property('offenders').that.is.an('object').not.empty(); + data.offenders.should.have.a.property('javascriptExecutionTree').that.is.a('array').not.empty(); done(); }).fail(function(err) { @@ -73,9 +73,9 @@ describe('phantomasWrapper', function() { data.should.have.a.property('generator'); data.generator.should.contain('phantomas'); data.should.have.a.property('url').that.equals(url); - data.should.have.a.property('metrics').that.is.an('object').not.empty; - data.should.have.a.property('offenders').that.is.an('object').not.empty; - data.offenders.should.have.a.property('javascriptExecutionTree').that.is.a('array').not.empty; + data.should.have.a.property('metrics').that.is.an('object').not.empty(); + data.should.have.a.property('offenders').that.is.an('object').not.empty(); + data.offenders.should.have.a.property('javascriptExecutionTree').that.is.a('array').not.empty(); done(); }).fail(function(err) { diff --git a/test/api/yellowlabtoolsTest.js b/test/api/yellowlabtoolsTest.js index 89c36ab..b1dd85e 100644 --- a/test/api/yellowlabtoolsTest.js +++ b/test/api/yellowlabtoolsTest.js @@ -34,7 +34,7 @@ describe('yellowlabtools', function() { this.timeout(15000); // Check if console.log is called - sinon.spy(console, 'log') + sinon.spy(console, 'log'); var url = 'http://localhost:8388/simple-page.html'; @@ -75,6 +75,7 @@ describe('yellowlabtools', function() { "offenders": ["body > h1[1]"] }); + /*jshint expr: true*/ console.log.should.not.have.been.called; done(); diff --git a/test/server/runsDatastoreTest.js b/test/server/runsDatastoreTest.js new file mode 100644 index 0000000..750956d --- /dev/null +++ b/test/server/runsDatastoreTest.js @@ -0,0 +1,41 @@ +var should = require('chai').should(); +var runsDatastore = require('../../lib/server/datastores/runsDatastore'); + +describe('runsDatastore', function() { + + var datastore = new runsDatastore(); + + var randomId = Math.round(Math.random() * 100000); + + it('should accept a new run', function() { + datastore.should.have.a.property('add').that.is.a('function'); + + datastore.add({ + _id: randomId, + otherData: 123456789 + }); + }); + + it('should have stored the run', function() { + datastore.should.have.a.property('get').that.is.a('function'); + + var run = datastore.get(randomId); + + run.should.have.a.property('_id').that.equals(randomId); + }); + + it('should have exactly 1 run in the store', function() { + var runs = datastore.list(); + runs.should.be.a('array'); + runs.should.have.length(1); + runs[0].should.have.a.property('_id').that.equals(randomId); + }); + + it('should delete the run', function() { + datastore.delete(randomId); + + var runs = datastore.list(); + runs.should.be.a('array'); + runs.should.have.length(0); + }); +}); diff --git a/test/server/runsQueueTest.js b/test/server/runsQueueTest.js new file mode 100644 index 0000000..45a2a23 --- /dev/null +++ b/test/server/runsQueueTest.js @@ -0,0 +1,64 @@ +var should = require('chai').should(); +var runsQueue = require('../../lib/server/datastores/runsQueue.js'); + +describe('runsQueue', function() { + + var queue = new runsQueue(); + var cccRun = null; + + it('should accept a new runId', function(done) { + queue.should.have.a.property('push').that.is.a('function'); + + var aaaRun = queue.push('aaa'); + queue.push('bbb'); + + aaaRun.then(function() { + done(); + }); + }); + + it('should return the right positions', function() { + var aaaPosition = queue.getPosition('aaa'); + aaaPosition.should.equal(0); + + var bbbPosition = queue.getPosition('bbb'); + bbbPosition.should.equal(1); + + var cccPosition = queue.getPosition('ccc'); + cccPosition.should.equal(-1); + }); + + it('should refresh runs\' positions', function(done) { + cccRun = queue.push('ccc'); + + cccRun.progress(function(position) { + position.should.equal(1); + + var positionDoubleCheck = queue.getPosition('ccc'); + positionDoubleCheck.should.equal(1); + + done(); + }); + + queue.remove('aaa'); + }); + + it('should fulfill the promise when first in the line', function(done) { + cccRun.then(function() { + done(); + }); + + queue.remove('bbb'); + }); + + it('should not keep removed runs', function() { + var aaaPosition = queue.getPosition('aaa'); + aaaPosition.should.equal(-1); + + var bbbPosition = queue.getPosition('bbb'); + bbbPosition.should.equal(-1); + + var cccPosition = queue.getPosition('ccc'); + cccPosition.should.equal(0); + }); +}); \ No newline at end of file diff --git a/test/server/testQueueTest.js b/test/server/testQueueTest.js deleted file mode 100644 index 6ae1be6..0000000 --- a/test/server/testQueueTest.js +++ /dev/null @@ -1,23 +0,0 @@ -var should = require('chai').should(); -var testQueue = require('../../app/lib/testQueue.js'); - -describe('testQueue', function() { - - var url = 'http://www.not.existing'; - - it('should accept a new test with method push', function() { - testQueue.should.have.property('push').that.is.a('function'); - - var task = { - testId: 'aaaaa', - url: url, - options: {} - }; - - testQueue.push(task, function(err, json, results) { - done(); - }); - - }); - -}); \ No newline at end of file From 906f0876a038bf300bffd333871946baa544e7fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Fri, 5 Dec 2014 16:14:08 +0100 Subject: [PATCH 07/98] API is starting to run --- Gruntfile.js | 4 +- bin/server.js | 2 +- lib/metadata/policies.json | 2 +- lib/server/controllers/apiController.js | 154 +++++++++++++++------ lib/server/controllers/uiController.js | 13 ++ lib/server/datastores/resultsDatastore.js | 72 +++++++++- lib/server/datastores/runsDatastore.js | 53 ++++++- lib/server/datastores/runsQueue.js | 11 +- lib/tools/phantomasWrapper.js | 2 +- lib/yellowlabtools.js | 13 +- package.json | 1 + test/api/resultsDatastoreTest.js | 74 ++++++++++ test/api/runsDatastoreTest.js | 81 +++++++++++ test/{server => api}/runsQueueTest.js | 8 +- test/{api => core}/phantomasWrapperTest.js | 0 test/{api => core}/rulesCheckerTest.js | 0 test/{api => core}/yellowlabtoolsTest.js | 0 test/server/runsDatastoreTest.js | 41 ------ 18 files changed, 423 insertions(+), 108 deletions(-) create mode 100644 test/api/resultsDatastoreTest.js create mode 100644 test/api/runsDatastoreTest.js rename test/{server => api}/runsQueueTest.js (88%) rename test/{api => core}/phantomasWrapperTest.js (100%) rename test/{api => core}/rulesCheckerTest.js (100%) rename test/{api => core}/yellowlabtoolsTest.js (100%) delete mode 100644 test/server/runsDatastoreTest.js diff --git a/Gruntfile.js b/Gruntfile.js index 41a5eef..240d1fe 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -71,13 +71,13 @@ module.exports = function(grunt) { options: { reporter: 'spec', }, - src: ['coverage/test/api/*.js', 'coverage/test/server/*.js'] + src: ['coverage/test/core/*.js', 'coverage/test/api/*.js'] }, 'test-current-work': { options: { reporter: 'spec', }, - src: ['coverage/test/server/runsQueueTest.js'] + src: ['coverage/test/server/runsDatastoreTest.js'] }, coverage: { options: { diff --git a/bin/server.js b/bin/server.js index 3f72983..3cd54f7 100644 --- a/bin/server.js +++ b/bin/server.js @@ -8,7 +8,7 @@ var bodyParser = require('body-parser'); var compress = require('compression'); app.use(compress()); -app.use(bodyParser.urlencoded({ extended: false })); +app.use(bodyParser.json()); // Initialize the controllers diff --git a/lib/metadata/policies.json b/lib/metadata/policies.json index 30c3402..40df848 100644 --- a/lib/metadata/policies.json +++ b/lib/metadata/policies.json @@ -365,6 +365,6 @@ "message": "

For each domain met, the browser needs to make a DNS look-up, which is slow. Avoid having to many different domains and the page should render faster.

By the way, domain sharding is not a good practice anymore.

", "isOkThreshold": 10, "isBadThreshold": 25, - "isAbnormalThreshold": 40 + "isAbnormalThreshold": 50 } } \ No newline at end of file diff --git a/lib/server/controllers/apiController.js b/lib/server/controllers/apiController.js index fb9d7c6..ee4e0de 100644 --- a/lib/server/controllers/apiController.js +++ b/lib/server/controllers/apiController.js @@ -1,86 +1,152 @@ -var debug = require('debug')('ylt:server'); +var debug = require('debug')('ylt:server'); -var runsQueue = require('../datastores/runsQueue'); -var runsDatastore = require('../datastores/runsDatastore'); +var YellowLabTools = require('../../yellowlabtools'); +var RunsQueue = require('../datastores/runsQueue'); +var RunsDatastore = require('../datastores/runsDatastore'); +var ResultsDatastore = require('../datastores/resultsDatastore'); -function ApiController(app) { +var ApiController = function(app) { 'use strict'; + var queue = new RunsQueue(); + var runsDatastore = new RunsDatastore(); + var resultsDatastore = new ResultsDatastore(); + // Retrieve the list of all runs - /*app.get('/runs', function(req, res) { + /*app.get('/api/runs', function(req, res) { // NOT YET });*/ // Create a new run - app.post('/runs', function(req, res) { + app.post('/api/runs', function(req, res) { - // Grab the test parameters + // Grab the test parameters and generate a random run ID var run = { - // Generate a random run ID - _id: (Date.now()*1000 + Math.round(Math.random()*1000)).toString(36), + runId: (Date.now()*1000 + Math.round(Math.random()*1000)).toString(36), params: { url: req.body.url, - waitForResponse: req.body.waitForResponse || true + waitForResponse: req.body.waitForResponse !== false } }; // Add test to the testQueue - debug('Adding test %s to the queue', run._id); - var queuing = runsQueue.push(run._id); + debug('Adding test %s to the queue', run.runId); + var queuePromise = queue.push(run.runId); + - // Save the run to the datastore - var position = runsQueue.getPosition(run._id); - run.status = { - statusCode: (position === 0) ? STATUS_RUNNING : STATUS_AWAITING, - position: position - }; - runsDatastore.add(run); + runsDatastore.add(run, queuePromise.startingPosition); // Listening for position updates - queuing.progress(function(position) { - var savedRun = runsDatastore.get(run._id); - savedRun.status = { - statusCode: STATUS_AWAITING, - position: position - }; - runsDatastore.update(savedRun); + queuePromise.progress(function(position) { + runsDatastore.updatePosition(run.runId, position); }); + // Let's start the run + queuePromise.then(function() { - queuing.then(function() { - + runsDatastore.updatePosition(run.runId, 0); + + debug('Launching test %s on %s', run.runId, run.params.url); + + new YellowLabTools(run.params.url) + .then(function(data) { + + debug('Success'); + runsDatastore.markAsComplete(run.runId); + + // Save result in datastore + data.runId = run.runId; + resultsDatastore.saveResult(data); + + // Send result if the user was waiting + if (run.params.waitForResponse) { + + res.redirect(302, '/api/results/' + run.runId); + } + + }).fail(function(err) { + + console.error('Test failed for %s', run.params.url); + console.error(err); + console.error(err.stack); + + runsDatastore.markAsFailed(run.runId); + + }).finally(function() { + queue.remove(run.runId); + }); + + }).fail(function(err) { + console.error('Error or YLT\'s core instanciation'); + console.error(err); + console.error(err.stack); }); - // The user doesn't not want to wait for the response - if (!params.waitForResponse) { - - // Sending just the test id - res.setHeader('Content-Type', 'application/javascript'); - res.send(JSON.stringify({ - testId: testId - })); + // The user doesn't not want to wait for the response, sending the run ID only + if (!run.params.waitForResponse) { + console.log('Sending response without waiting.'); + res.setHeader('Content-Type', 'application/json'); + res.send(JSON.stringify({runId: run.runId})); } }); // Retrive one run by id - app.get('/run/:id', function(req, res) { + app.get('/api/runs/:id', function(req, res) { + var runId = req.params.id; + var run = runsDatastore.get(runId); + + if (run) { + res.setHeader('Content-Type', 'application/json'); + res.send(JSON.stringify(run, null, 2)); + } else { + res.status(404).send('Not found'); + } }); // Delete one run by id - /*app.delete('/run/:id', function(req, res) { - // NOT YET + /*app.delete('/api/runs/:id', function(req, res) { + deleteRun() });*/ + // Delete all + /*app.delete('/api/runs', function(req, res) { + purgeRuns() + }); - var STATUS_AWAITING = 'awaiting'; - var STATUS_RUNNING = 'running'; - var STATUS_DONE = 'done'; - var STATUS_FAILED = 'failed'; + // List all + app.get('/api/runs', function(req, res) { + listRuns() + }); -} + // Exists + app.head('/api/runs/:id', function(req, res) { + existsX(); + // Retourne 200 si existe ou 404 si n'existe pas + }); + */ + + // Retrive one result by id + app.get('/api/results/:id', function(req, res) { + var runId = req.params.id; + + resultsDatastore.getResult(runId) + .then(function(data) { + // This is the pivot format, we might need to clean it first? + + // Hide phantomas results + data.toolsResults.phantomas = {}; + + res.setHeader('Content-Type', 'application/json'); + res.send(JSON.stringify(data, null, 2)); + }).fail(function() { + res.status(404).send('Not found'); + }); + }); + +}; module.exports = ApiController; \ No newline at end of file diff --git a/lib/server/controllers/uiController.js b/lib/server/controllers/uiController.js index e69de29..a6d906d 100644 --- a/lib/server/controllers/uiController.js +++ b/lib/server/controllers/uiController.js @@ -0,0 +1,13 @@ +var UiController = function(app) { + 'use strict'; + + + // Create a new run + app.get('/', function(req, res) { + + res.setHeader('Content-Type', 'text/html'); + res.send('Test'); + }); +}; + +module.exports = UiController; \ No newline at end of file diff --git a/lib/server/datastores/resultsDatastore.js b/lib/server/datastores/resultsDatastore.js index 954f9b7..3d2ce8a 100644 --- a/lib/server/datastores/resultsDatastore.js +++ b/lib/server/datastores/resultsDatastore.js @@ -1,9 +1,79 @@ +var fs = require('fs'); +var rimraf = require('rimraf'); +var path = require('path'); +var Q = require('q'); function ResultsDatastore() { 'use strict'; - + var resultFileName = 'results.json'; + var resultsFolderName = 'results'; + var resultsDir = path.join(__dirname, '..', '..', '..', resultsFolderName); + + + this.saveResult = function(testResults) { + + var promise = createResultFolder(testResults.runId); + + promise.then(function() { + + var resultFilePath = path.join(resultsDir, testResults.runId, resultFileName); + + return Q.nfcall(fs.writeFile, resultFilePath, JSON.stringify(testResults, null, 2)); + }); + + return promise; + }; + + + this.getResult = function(runId) { + + var resultFilePath = path.join(resultsDir, runId, resultFileName); + + return Q.nfcall(fs.readFile, resultFilePath, {encoding: 'utf8'}).then(function(data) { + return JSON.parse(data); + }); + }; + + + this.deleteResult = function(runId) { + var folder = path.join(resultsDir, runId); + + return Q.nfcall(rimraf, folder); + }; + + + // The folder /results/folderName/ + function createResultFolder(folderName) { + var folder = path.join(resultsDir, folderName); + + return createGlobalFolder().then(function() { + return Q.nfcall(fs.mkdir, folder); + }); + } + + // The folder /results/ + function createGlobalFolder() { + var deferred = Q.defer(); + + // Create the results folder if it doesn't exist + fs.exists(resultsDir, function(exists) { + if (exists) { + deferred.resolve(); + } else { + fs.mkdir(resultsDir, function(err) { + if (err) { + deferred.reject(err); + } else { + deferred.resolve(); + } + }); + } + }); + + return deferred.promise; + } } module.exports = ResultsDatastore; \ No newline at end of file diff --git a/lib/server/datastores/runsDatastore.js b/lib/server/datastores/runsDatastore.js index c84ac4e..cf0af10 100644 --- a/lib/server/datastores/runsDatastore.js +++ b/lib/server/datastores/runsDatastore.js @@ -7,23 +7,68 @@ function RunsDatastore() { // For the moment, maybe one day var runs = {}; + var STATUS_AWAITING = 'awaiting'; + var STATUS_RUNNING = 'running'; + var STATUS_COMPLETE = 'complete'; + var STATUS_FAILED = 'failed'; - this.add = function(run) { - runs[run._id] = run; + + this.add = function(run, position) { + runs[run.runId] = run; + this.updatePosition(run.runId, position); }; + this.get = function(runId) { return runs[runId]; }; + - this.update = function(run) { - runs[run._id] = run; + this.updatePosition = function(runId, position) { + var run = runs[runId]; + + if (position > 0) { + run.status = { + statusCode: STATUS_AWAITING, + position: position + }; + } else { + run.status = { + statusCode: STATUS_RUNNING + }; + } + + runs[runId] = run; }; + + this.markAsComplete = function(runId) { + var run = runs[runId]; + + run.status = { + statusCode: STATUS_COMPLETE + }; + + runs[runId] = run; + }; + + + this.markAsFailed = function(runId) { + var run = runs[runId]; + + run.status = { + statusCode: STATUS_FAILED + }; + + runs[runId] = run; + }; + + this.delete = function(runId) { delete runs[runId]; }; + this.list = function() { var runsArray = []; Object.keys(runs).forEach(function(key) { diff --git a/lib/server/datastores/runsQueue.js b/lib/server/datastores/runsQueue.js index ad1a6b8..26a6717 100644 --- a/lib/server/datastores/runsQueue.js +++ b/lib/server/datastores/runsQueue.js @@ -9,8 +9,9 @@ function RunsQueue() { this.push = function(runId) { var deferred = Q.defer(); + var startingPosition = queue.length; - if (queue.length === 0) { + if (startingPosition === 0) { // The queue is empty, let's run immediatly queue.push({ @@ -32,7 +33,9 @@ function RunsQueue() { }); } - return deferred.promise; + var promise = deferred.promise; + promise.startingPosition = startingPosition; + return promise; }; @@ -68,6 +71,10 @@ function RunsQueue() { }); }; + + this.length = function() { + return queue.length; + }; } module.exports = RunsQueue; \ No newline at end of file diff --git a/lib/tools/phantomasWrapper.js b/lib/tools/phantomasWrapper.js index e1df01d..adb3dd7 100644 --- a/lib/tools/phantomasWrapper.js +++ b/lib/tools/phantomasWrapper.js @@ -64,7 +64,7 @@ var PhantomasWrapper = function() { }, 600000); // It's time to launch the test!!! - var triesNumber = 3; + var triesNumber = 2; async.retry(triesNumber, function(cb) { phantomas(task.url, options, function(err, json, results) { diff --git a/lib/yellowlabtools.js b/lib/yellowlabtools.js index a0f4e38..a56e49d 100644 --- a/lib/yellowlabtools.js +++ b/lib/yellowlabtools.js @@ -14,24 +14,19 @@ var YellowLabTools = function(url, options) { } else { - // Generate a test id - var testId = (Date.now()*1000 + Math.round(Math.random()*1000)).toString(36); - if (url.indexOf('http://') !== 0 && url.indexOf('https://') !== 0) { url = 'http://' + url; } var params = { - testId: testId, url: url, options: options || {} }; - var runner = new Runner(params); - - runner.then(function(data) { - deferred.resolve(data); - }); + var runner = new Runner(params) + .then(function(data) { + deferred.resolve(data); + }); } return deferred.promise; diff --git a/package.json b/package.json index 29bca24..9ceae90 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "debug": "^2.1.0", "express": "~4.10.4", "phantomas": "1.7.0", + "rimraf": "^2.2.8", "socket.io": "~1.2.0" }, "devDependencies": { diff --git a/test/api/resultsDatastoreTest.js b/test/api/resultsDatastoreTest.js new file mode 100644 index 0000000..4e8bdaf --- /dev/null +++ b/test/api/resultsDatastoreTest.js @@ -0,0 +1,74 @@ +var should = require('chai').should(); +var resultsDatastore = require('../../lib/server/datastores/resultsDatastore'); + +describe('resultsDatastore', function() { + + var datastore = new resultsDatastore(); + + var testId1 = '123456789'; + var testData1 = { + runId: testId1, + other: { + foo: 'foo', + bar: 1 + } + }; + + + it('should store a result', function(done) { + datastore.should.have.a.property('saveResult').that.is.a('function'); + + datastore.saveResult(testData1).then(function() { + done(); + }).fail(function(err) { + done(err); + }); + }); + + it('should store another result', function(done) { + var testData2 = { + runId: '987654321', + other: { + foo: 'foo', + bar: 2 + } + }; + + datastore.saveResult(testData2).then(function() { + done(); + }).fail(function(err) { + done(err); + }); + }); + + it('should retrieve a result', function(done) { + datastore.getResult(testId1) + .then(function(results) { + + // Compare results with testData + results.should.deep.equal(testData1); + + done(); + }).fail(function(err) { + done(err); + }); + }); + + it('should delete a result', function(done) { + datastore.deleteResult(testId1) + .then(function() { + done(); + }).fail(function(err) { + done(err); + }); + }); + + it('should not find the result anymore', function(done) { + datastore.getResult(testId1) + .then(function(results) { + done('Error, the result is still in the datastore'); + }).fail(function(err) { + done(); + }); + }); +}); diff --git a/test/api/runsDatastoreTest.js b/test/api/runsDatastoreTest.js new file mode 100644 index 0000000..1ac4d03 --- /dev/null +++ b/test/api/runsDatastoreTest.js @@ -0,0 +1,81 @@ +var should = require('chai').should(); +var runsDatastore = require('../../lib/server/datastores/runsDatastore'); + +describe('runsDatastore', function() { + + var datastore = new runsDatastore(); + + var firstRunId = 333; + var secondRunId = 999; + + it('should accept new runs', function() { + datastore.should.have.a.property('add').that.is.a('function'); + + datastore.add({ + runId: firstRunId, + otherData: 123456789 + }, 0); + + datastore.add({ + runId: secondRunId, + otherData: 'whatever' + }, 1); + }); + + it('should have stored the runs with a status "runnung"', function() { + datastore.should.have.a.property('get').that.is.a('function'); + + var firstRun = datastore.get(firstRunId); + firstRun.should.have.a.property('runId').that.equals(firstRunId); + firstRun.should.have.a.property('status').that.deep.equals({ + statusCode: 'running' + }); + + var secondRun = datastore.get(secondRunId); + secondRun.should.have.a.property('runId').that.equals(secondRunId); + secondRun.should.have.a.property('status').that.deep.equals({ + statusCode: 'awaiting', + position: 1 + }); + + }); + + it('should have exactly 2 runs in the store', function() { + var runs = datastore.list(); + runs.should.be.a('array'); + runs.should.have.length(2); + runs[0].should.have.a.property('runId').that.equals(firstRunId); + }); + + it('shoud update statuses correctly', function() { + + datastore.markAsComplete(firstRunId); + var firstRun = datastore.get(firstRunId); + firstRun.should.have.a.property('status').that.deep.equals({ + statusCode: 'complete' + }); + + datastore.updatePosition(secondRunId, 0); + var secondRun = datastore.get(secondRunId); + secondRun.should.have.a.property('status').that.deep.equals({ + statusCode: 'running' + }); + + datastore.markAsFailed(secondRunId); + secondRun = datastore.get(secondRunId); + secondRun.should.have.a.property('status').that.deep.equals({ + statusCode: 'failed' + }); + + }); + + it('should delete a run', function() { + datastore.delete(firstRunId); + + var runs = datastore.list(); + runs.should.be.a('array'); + runs.should.have.length(1); + + runs[0].should.have.a.property('runId').that.equals(secondRunId); + }); +}); diff --git a/test/server/runsQueueTest.js b/test/api/runsQueueTest.js similarity index 88% rename from test/server/runsQueueTest.js rename to test/api/runsQueueTest.js index 45a2a23..1e8798d 100644 --- a/test/server/runsQueueTest.js +++ b/test/api/runsQueueTest.js @@ -4,13 +4,15 @@ var runsQueue = require('../../lib/server/datastores/runsQueue.js'); describe('runsQueue', function() { var queue = new runsQueue(); + var aaaRun = null; + var bbbRun = null; var cccRun = null; it('should accept a new runId', function(done) { queue.should.have.a.property('push').that.is.a('function'); - var aaaRun = queue.push('aaa'); - queue.push('bbb'); + aaaRun = queue.push('aaa'); + bbbRun = queue.push('bbb'); aaaRun.then(function() { done(); @@ -20,9 +22,11 @@ describe('runsQueue', function() { it('should return the right positions', function() { var aaaPosition = queue.getPosition('aaa'); aaaPosition.should.equal(0); + aaaRun.startingPosition.should.equal(0); var bbbPosition = queue.getPosition('bbb'); bbbPosition.should.equal(1); + bbbRun.startingPosition.should.equal(1); var cccPosition = queue.getPosition('ccc'); cccPosition.should.equal(-1); diff --git a/test/api/phantomasWrapperTest.js b/test/core/phantomasWrapperTest.js similarity index 100% rename from test/api/phantomasWrapperTest.js rename to test/core/phantomasWrapperTest.js diff --git a/test/api/rulesCheckerTest.js b/test/core/rulesCheckerTest.js similarity index 100% rename from test/api/rulesCheckerTest.js rename to test/core/rulesCheckerTest.js diff --git a/test/api/yellowlabtoolsTest.js b/test/core/yellowlabtoolsTest.js similarity index 100% rename from test/api/yellowlabtoolsTest.js rename to test/core/yellowlabtoolsTest.js diff --git a/test/server/runsDatastoreTest.js b/test/server/runsDatastoreTest.js deleted file mode 100644 index 750956d..0000000 --- a/test/server/runsDatastoreTest.js +++ /dev/null @@ -1,41 +0,0 @@ -var should = require('chai').should(); -var runsDatastore = require('../../lib/server/datastores/runsDatastore'); - -describe('runsDatastore', function() { - - var datastore = new runsDatastore(); - - var randomId = Math.round(Math.random() * 100000); - - it('should accept a new run', function() { - datastore.should.have.a.property('add').that.is.a('function'); - - datastore.add({ - _id: randomId, - otherData: 123456789 - }); - }); - - it('should have stored the run', function() { - datastore.should.have.a.property('get').that.is.a('function'); - - var run = datastore.get(randomId); - - run.should.have.a.property('_id').that.equals(randomId); - }); - - it('should have exactly 1 run in the store', function() { - var runs = datastore.list(); - runs.should.be.a('array'); - runs.should.have.length(1); - runs[0].should.have.a.property('_id').that.equals(randomId); - }); - - it('should delete the run', function() { - datastore.delete(randomId); - - var runs = datastore.list(); - runs.should.be.a('array'); - runs.should.have.length(0); - }); -}); From 49015e3422723d2bd8f43c6c01799cc2c4ddd96d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Tue, 9 Dec 2014 09:04:28 +0100 Subject: [PATCH 08/98] API authentification (tbc...) --- Gruntfile.js | 36 ++++- bin/server.js | 17 ++- bower.json | 2 +- lib/server/authMiddleware.js | 52 +++++++ lib/server/controllers/apiController.js | 12 +- package.json | 12 +- server_config/settings-prod.json | 10 +- server_config/settings.json | 10 +- test/api/apiTest.js | 171 ++++++++++++++++++++++++ 9 files changed, 300 insertions(+), 22 deletions(-) create mode 100644 lib/server/authMiddleware.js create mode 100644 test/api/apiTest.js diff --git a/Gruntfile.js b/Gruntfile.js index 240d1fe..b9e7296 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,5 +1,11 @@ module.exports = function(grunt) { + var DEV_SERVER_PORT = 8383; + var TEST_SERVER_PORT = 8387; + + // Tell our Express server that Grunt launched it + process.env.GRUNTED = true; + // Project configuration. grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), @@ -77,7 +83,7 @@ module.exports = function(grunt) { options: { reporter: 'spec', }, - src: ['coverage/test/server/runsDatastoreTest.js'] + src: ['coverage/test/api/apiTest.js'] }, coverage: { options: { @@ -85,11 +91,25 @@ module.exports = function(grunt) { quiet: true, captureFile: 'coverage/coverage.html' }, - src: ['coverage/test/api/*.js'] + src: ['coverage/test/core/*.js', 'coverage/test/api/*.js'] } }, express: { - test: { + dev: { + options: { + port: 8383, + server: './bin/server.js', + serverreload: true, + showStack: true + } + }, + testServer: { + options: { + port: 8387, + server: './bin/server.js' + } + }, + testSuite: { options: { port: 8388, bases: 'test/www' @@ -114,10 +134,15 @@ module.exports = function(grunt) { 'jshint' ]); + grunt.registerTask('dev', [ + 'express:dev' + ]); + grunt.registerTask('test', [ 'build', 'jshint', - 'express:test', + 'express:testServer', + 'express:testSuite', 'clean:coverage', 'blanket', 'copy:coverage', @@ -128,7 +153,8 @@ module.exports = function(grunt) { grunt.registerTask('test-current-work', [ 'build', 'jshint', - 'express:test', + 'express:testServer', + 'express:testSuite', 'clean:coverage', 'blanket', 'copy:coverage', diff --git a/bin/server.js b/bin/server.js index 3cd54f7..8fda8f9 100644 --- a/bin/server.js +++ b/bin/server.js @@ -7,8 +7,11 @@ var server = require('http').createServer(app); var bodyParser = require('body-parser'); var compress = require('compression'); +var authMiddleware = require('../lib/server/authMiddleware'); + app.use(compress()); app.use(bodyParser.json()); +app.use(authMiddleware); // Initialize the controllers @@ -16,7 +19,13 @@ var apiController = require('../lib/server/controllers/apiController') var uiController = require('../lib/server/controllers/uiController')(app); -// Launch the server -server.listen(settings.serverPort, function() { - console.log('Listening on port %d', server.address().port); -}); \ No newline at end of file +// Let's start the server! +if (!process.env.GRUNTED) { + // The server is not launched by Grunt + server.listen(settings.serverPort, function() { + console.log('Listening on port %d', server.address().port); + }); +} + +// For Grunt +module.exports = app; \ No newline at end of file diff --git a/bower.json b/bower.json index f248d80..b308e91 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,7 @@ { "name": "yellowlabtools", "dependencies": { - "angular": "~1.3.1", + "angular": "~1.3.5", "ngModal": "git://github.com/gmetais/ngModal.git#1.2.3" } } diff --git a/lib/server/authMiddleware.js b/lib/server/authMiddleware.js new file mode 100644 index 0000000..e9f68c7 --- /dev/null +++ b/lib/server/authMiddleware.js @@ -0,0 +1,52 @@ +var config = require('../../server_config/settings.json'); + +var jwt = require('jwt-simple'); +var debug = require('debug')('authMiddleware'); + + +var authMiddleware = function(req, res, next) { + 'use strict'; + + if (req.path.indexOf('/api/') === 0) { + + // Test if it's an authorized key + if (req.headers && req.headers['x-api-key'] && isApiKeyValid(req.headers['x-api-key'])) { + next(); + return; + } + + // Test if it's an authorized token + if (req.headers && req.headers['x-api-token'] && isTokenValid(req.headers['x-api-token'])) { + next(); + return; + } + + res.status(401).send('Unauthorized'); + } +}; + + +function isApiKeyValid(apiKey) { + return (config.authorizedKeys[apiKey]) ? true : false; +} + + +function isTokenValid(token) { + + var data = null; + + try { + jwt.decode(token, config.tokenSalt); + } catch(err) { + debug('Error while decoding token'); + debug(err); + return false; + } + + return data.expire && + data.expire > Date.now() && + data.application && + config.authorizedApplications.indexOf(data.application) >= 0; +} + +module.exports = authMiddleware; \ No newline at end of file diff --git a/lib/server/controllers/apiController.js b/lib/server/controllers/apiController.js index ee4e0de..a1c0952 100644 --- a/lib/server/controllers/apiController.js +++ b/lib/server/controllers/apiController.js @@ -13,10 +13,7 @@ var ApiController = function(app) { var runsDatastore = new RunsDatastore(); var resultsDatastore = new ResultsDatastore(); - // Retrieve the list of all runs - /*app.get('/api/runs', function(req, res) { - // NOT YET - });*/ + // Create a new run app.post('/api/runs', function(req, res) { @@ -107,6 +104,11 @@ var ApiController = function(app) { } }); + // Retrieve the list of all runs + /*app.get('/api/runs', function(req, res) { + // NOT YET + });*/ + // Delete one run by id /*app.delete('/api/runs/:id', function(req, res) { deleteRun() @@ -125,7 +127,7 @@ var ApiController = function(app) { // Exists app.head('/api/runs/:id', function(req, res) { existsX(); - // Retourne 200 si existe ou 404 si n'existe pas + // Returns 200 if the result exists or 404 if not }); */ diff --git a/package.json b/package.json index 9ceae90..0e8fe6c 100644 --- a/package.json +++ b/package.json @@ -11,16 +11,17 @@ "main": "./lib/yellowlabtools.js", "dependencies": { "async": "~0.9.0", - "body-parser": "~1.9.2", - "compression": "~1.2.0", + "body-parser": "~1.10.0", + "compression": "~1.2.1", "debug": "^2.1.0", "express": "~4.10.4", + "jwt-simple": "^0.2.0", "phantomas": "1.7.0", "rimraf": "^2.2.8", "socket.io": "~1.2.0" }, "devDependencies": { - "chai": "^1.9.2", + "chai": "^1.10.0", "grunt": "^0.4.5", "grunt-blanket": "^0.0.8", "grunt-contrib-clean": "^0.6.0", @@ -29,11 +30,12 @@ "grunt-contrib-less": "^0.12.0", "grunt-express": "^1.4.1", "grunt-fontsmith": "^0.9.1", - "grunt-mocha-test": "^0.12.2", + "grunt-mocha-test": "^0.12.4", "matchdep": "^0.3.0", "mocha": "^2.0.1", - "phantomjs": "^1.9.10", + "phantomjs": "^1.9.12", "q": "^1.1.2", + "request": "^2.49.0", "sinon": "^1.12.1", "sinon-chai": "^2.6.0" }, diff --git a/server_config/settings-prod.json b/server_config/settings-prod.json index f7b4cb5..8d06c6a 100644 --- a/server_config/settings-prod.json +++ b/server_config/settings-prod.json @@ -1,4 +1,12 @@ { "serverPort": 80, - "googleAnalyticsId": "UA-54493828-1" + "googleAnalyticsId": "UA-54493828-1", + + "authorized-keys": { + + }, + "tokenSalt": "", + "authorizedApplications": [ + + ] } \ No newline at end of file diff --git a/server_config/settings.json b/server_config/settings.json index fb400fe..70f03bf 100644 --- a/server_config/settings.json +++ b/server_config/settings.json @@ -1,4 +1,12 @@ { "serverPort": 8383, - "googleAnalyticsId": "" + "googleAnalyticsId": "", + + "authorizedKeys": { + "1234567890": "contact@gaelmetais.com" + }, + "tokenSalt": "lake-city", + "authorizedApplications": [ + "frontend" + ] } \ No newline at end of file diff --git a/test/api/apiTest.js b/test/api/apiTest.js new file mode 100644 index 0000000..e2469a0 --- /dev/null +++ b/test/api/apiTest.js @@ -0,0 +1,171 @@ +var should = require('chai').should(); +var request = require('request'); +var jwt = require('jwt-simple'); + +var config = { + "authorizedKeys": { + "1234567890": "test@test.com" + }, + "tokenSalt": "test-salt", + "authorizedApplications": ["wooot"] +}; + +var apiUrl = 'http://localhost:8387/api'; +var wwwUrl = 'http://localhost:8388'; + +describe('api', function() { + + var runId; + + it('should not accept a query if there is no key in headers', function(done) { + this.timeout(5000); + + request({ + method: 'POST', + url: apiUrl + '/runs', + body: { + url: wwwUrl + '/simple-page.html', + waitForResponse: false + }, + json: true + }, function(error, response, body) { + if (!error && response.statusCode === 401) { + done(); + } else { + done(error || response.statusCode); + } + }); + }); + + it('should refuse a query with an invalid key', function(done) { + this.timeout(5000); + + request({ + method: 'POST', + url: apiUrl + '/runs', + body: { + url: wwwUrl + '/simple-page.html', + waitForResponse: false + }, + json: true, + headers: { + 'X-Api-Key': 'invalid' + } + }, function(error, response, body) { + if (!error && response.statusCode === 401) { + done(); + } else { + done(error || response.statusCode); + } + }); + }); + + it('should accept a query with a valid key', function(done) { + this.timeout(5000); + + request({ + method: 'POST', + url: apiUrl + '/runs', + body: { + url: wwwUrl + '/simple-page.html', + waitForResponse: false + }, + json: true, + headers: { + 'X-Api-Key': Object.keys(config.authorizedKeys)[0] + } + }, function(error, response, body) { + if (!error && response.statusCode === 200) { + + runId = body.runId; + runId.should.be.a('string'); + done(); + + } else { + done(error || response.statusCode); + } + }); + }); + + it('should refuse an expired token', function(done) { + this.timeout(5000); + + request({ + method: 'POST', + url: apiUrl + '/runs', + body: { + url: wwwUrl + '/simple-page.html', + waitForResponse: false + }, + json: true, + headers: { + 'X-Api-Token': jwt.encode({ + application: config.authorizedApplications[0], + expire: Date.now() - 60000 + }, config.tokenSalt) + } + }, function(error, response, body) { + if (!error && response.statusCode === 401) { + done(); + } else { + done(error || response.statusCode); + } + }); + }); + + it('should refuse a token from an unknown app', function(done) { + this.timeout(5000); + + request({ + method: 'POST', + url: apiUrl + '/runs', + body: { + url: wwwUrl + '/simple-page.html', + waitForResponse: false + }, + json: true, + headers: { + 'X-Api-Token': jwt.encode({ + application: 'unknown-app', + expire: Date.now() + 60000 + }, config.tokenSalt) + } + }, function(error, response, body) { + if (!error && response.statusCode === 401) { + done(); + } else { + done(error || response.statusCode); + } + }); + }); + + it('should accept a good token', function(done) { + this.timeout(5000); + + request({ + method: 'POST', + url: apiUrl + '/runs', + body: { + url: wwwUrl + '/simple-page.html', + waitForResponse: false + }, + json: true, + headers: { + 'X-Api-Token': jwt.encode({ + application: config.authorizedApplications[0], + expire: Date.now() + 60000 + }, config.tokenSalt) + } + }, function(error, response, body) { + if (!error && response.statusCode === 200) { + + runId = body.runId; + runId.should.be.a('string'); + done(); + + } else { + done(error || response.statusCode); + } + }); + }); +}); From 84faabca868591fe497d1677145f11fc1ed00047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Wed, 10 Dec 2014 08:16:11 +0100 Subject: [PATCH 09/98] Fix Auth token --- lib/server/authMiddleware.js | 2 +- lib/tools/phantomasWrapper.js | 4 ++-- test/api/apiTest.js | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/server/authMiddleware.js b/lib/server/authMiddleware.js index e9f68c7..fe70125 100644 --- a/lib/server/authMiddleware.js +++ b/lib/server/authMiddleware.js @@ -36,7 +36,7 @@ function isTokenValid(token) { var data = null; try { - jwt.decode(token, config.tokenSalt); + data = jwt.decode(token, config.tokenSalt); } catch(err) { debug('Error while decoding token'); debug(err); diff --git a/lib/tools/phantomasWrapper.js b/lib/tools/phantomasWrapper.js index adb3dd7..8dbd614 100644 --- a/lib/tools/phantomasWrapper.js +++ b/lib/tools/phantomasWrapper.js @@ -58,10 +58,10 @@ var PhantomasWrapper = function() { // Kill the application if nothing happens for 10 minutes var killer = setTimeout(function() { - debug('Killing the app because the test on ' + task.url + ' was launched 10 minutes ago'); + debug('Killing the app because the test on %s was launched %d seconds ago', task.url, 3*options.timeout); // If in server mode, forever will restart the server process.exit(1); - }, 600000); + }, 3*options.timeout*60*1000); // It's time to launch the test!!! var triesNumber = 2; diff --git a/test/api/apiTest.js b/test/api/apiTest.js index e2469a0..dacc553 100644 --- a/test/api/apiTest.js +++ b/test/api/apiTest.js @@ -4,10 +4,10 @@ var jwt = require('jwt-simple'); var config = { "authorizedKeys": { - "1234567890": "test@test.com" + "1234567890": "contact@gaelmetais.com" }, - "tokenSalt": "test-salt", - "authorizedApplications": ["wooot"] + "tokenSalt": "lake-city", + "authorizedApplications": ["frontend"] }; var apiUrl = 'http://localhost:8387/api'; From 0da2ad0c1044568ae78fe8ee2d81b42f64ccecb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Fri, 12 Dec 2014 20:26:19 +0100 Subject: [PATCH 10/98] Add API limits configurable in settings.json --- bin/server.js | 5 +- lib/server/authMiddleware.js | 52 ------ lib/server/middlewares/apiLimitsMiddleware.js | 85 +++++++++ lib/server/middlewares/authMiddleware.js | 42 +++++ package.json | 1 - server_config/settings.json | 6 +- test/api/apiTest.js | 163 ++++++++---------- 7 files changed, 202 insertions(+), 152 deletions(-) delete mode 100644 lib/server/authMiddleware.js create mode 100644 lib/server/middlewares/apiLimitsMiddleware.js create mode 100644 lib/server/middlewares/authMiddleware.js diff --git a/bin/server.js b/bin/server.js index 8fda8f9..f8b76aa 100644 --- a/bin/server.js +++ b/bin/server.js @@ -7,11 +7,14 @@ var server = require('http').createServer(app); var bodyParser = require('body-parser'); var compress = require('compression'); -var authMiddleware = require('../lib/server/authMiddleware'); +var authMiddleware = require('../lib/server/middlewares/authMiddleware'); +var apiLimitsMiddleware = require('../lib/server/middlewares/apiLimitsMiddleware'); + app.use(compress()); app.use(bodyParser.json()); app.use(authMiddleware); +app.use(apiLimitsMiddleware); // Initialize the controllers diff --git a/lib/server/authMiddleware.js b/lib/server/authMiddleware.js deleted file mode 100644 index fe70125..0000000 --- a/lib/server/authMiddleware.js +++ /dev/null @@ -1,52 +0,0 @@ -var config = require('../../server_config/settings.json'); - -var jwt = require('jwt-simple'); -var debug = require('debug')('authMiddleware'); - - -var authMiddleware = function(req, res, next) { - 'use strict'; - - if (req.path.indexOf('/api/') === 0) { - - // Test if it's an authorized key - if (req.headers && req.headers['x-api-key'] && isApiKeyValid(req.headers['x-api-key'])) { - next(); - return; - } - - // Test if it's an authorized token - if (req.headers && req.headers['x-api-token'] && isTokenValid(req.headers['x-api-token'])) { - next(); - return; - } - - res.status(401).send('Unauthorized'); - } -}; - - -function isApiKeyValid(apiKey) { - return (config.authorizedKeys[apiKey]) ? true : false; -} - - -function isTokenValid(token) { - - var data = null; - - try { - data = jwt.decode(token, config.tokenSalt); - } catch(err) { - debug('Error while decoding token'); - debug(err); - return false; - } - - return data.expire && - data.expire > Date.now() && - data.application && - config.authorizedApplications.indexOf(data.application) >= 0; -} - -module.exports = authMiddleware; \ No newline at end of file diff --git a/lib/server/middlewares/apiLimitsMiddleware.js b/lib/server/middlewares/apiLimitsMiddleware.js new file mode 100644 index 0000000..7c17015 --- /dev/null +++ b/lib/server/middlewares/apiLimitsMiddleware.js @@ -0,0 +1,85 @@ +var config = require('../../../server_config/settings.json'); + +var debug = require('debug')('apiLimitsMiddleware'); + + +var apiLimitsMiddleware = function(req, res, next) { + 'use strict'; + + debug('Entering API Limits Middleware with IP address %s', req.connection.remoteAddress); + + if (req.path.indexOf('/api/') === 0 && !res.locals.hasApiKey) { + + + if (req.path === '/api/runs') { + + if (!runsTable.accepts(req.connection.remoteAddress)) { + // Sorry :/ + debug('Too many tests launched from IP address %s', req.connection.remoteAddress); + res.status(429).send('Too Many Requests'); + return; + } + + } + + if (!callsTable.accepts(req.connection.remoteAddress)) { + // Sorry :/ + debug('Too many API requests from IP address %s', req.connection.remoteAddress); + res.status(429).send('Too Many Requests'); + return; + } + + debug('Not blocked by the API limits'); + // It's ok for the moment + } + + next(); +}; + + +var RecordTable = function(maxPerDay) { + var table = {}; + + // Check if the user overpassed the limit and save its visit + this.accepts = function(ipAddress) { + if (table[ipAddress]) { + + this.cleanEntry(ipAddress); + + debug('%d visits in the last 24 hours', table[ipAddress].length); + + if (table[ipAddress].length >= maxPerDay) { + return false; + } else { + table[ipAddress].push(Date.now()); + } + + } else { + table[ipAddress] = []; + table[ipAddress].push(Date.now()); + } + + return true; + }; + + // Clean the table for this guy + this.cleanEntry = function(ipAddress) { + table[ipAddress] = table[ipAddress].filter(function(date) { + return date > Date.now() - 1000*60*60*24; + }); + }; + + // Clean the entire table once in a while + this.removeOld = function() { + for (var ipAddress in table) { + this.cleanEntry(ipAddress); + } + }; + +}; + +// Init the records tables +var runsTable = new RecordTable(config.maxAnonymousRunsPerDay); +var callsTable = new RecordTable(config.maxAnonymousCallsPerDay); + +module.exports = apiLimitsMiddleware; \ No newline at end of file diff --git a/lib/server/middlewares/authMiddleware.js b/lib/server/middlewares/authMiddleware.js new file mode 100644 index 0000000..e5335e6 --- /dev/null +++ b/lib/server/middlewares/authMiddleware.js @@ -0,0 +1,42 @@ +var config = require('../../../server_config/settings.json'); + +var debug = require('debug')('authMiddleware'); + + +var authMiddleware = function(req, res, next) { + 'use strict'; + + if (req.path.indexOf('/api/') === 0) { + + + if (req.headers && req.headers['x-api-key']) { + + // Test if it's an authorized key + if (isApiKeyValid(req.headers['x-api-key'])) { + + // Come in! + debug('Authorized key: %s', req.headers['x-api-key']); + res.locals.hasApiKey = true; + + } else { + + // Sorry :/ + debug('Unauthorized key %s', req.headers['x-api-key']); + res.status(401).send('Unauthorized'); + return; + } + } else { + debug('No authorization key'); + // It's ok for the moment but you might be blocked by the apiLimitsMiddleware, dude + } + } + + next(); +}; + + +function isApiKeyValid(apiKey) { + return (config.authorizedKeys[apiKey]) ? true : false; +} + +module.exports = authMiddleware; \ No newline at end of file diff --git a/package.json b/package.json index 0e8fe6c..1c0eb59 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,6 @@ "compression": "~1.2.1", "debug": "^2.1.0", "express": "~4.10.4", - "jwt-simple": "^0.2.0", "phantomas": "1.7.0", "rimraf": "^2.2.8", "socket.io": "~1.2.0" diff --git a/server_config/settings.json b/server_config/settings.json index 70f03bf..2b05df1 100644 --- a/server_config/settings.json +++ b/server_config/settings.json @@ -5,8 +5,6 @@ "authorizedKeys": { "1234567890": "contact@gaelmetais.com" }, - "tokenSalt": "lake-city", - "authorizedApplications": [ - "frontend" - ] + "maxAnonymousRunsPerDay": 24, + "maxAnonymousCallsPerDay": 1000 } \ No newline at end of file diff --git a/test/api/apiTest.js b/test/api/apiTest.js index dacc553..3183bf2 100644 --- a/test/api/apiTest.js +++ b/test/api/apiTest.js @@ -1,13 +1,11 @@ var should = require('chai').should(); var request = require('request'); -var jwt = require('jwt-simple'); +var Q = require('q'); var config = { "authorizedKeys": { "1234567890": "contact@gaelmetais.com" - }, - "tokenSalt": "lake-city", - "authorizedApplications": ["frontend"] + } }; var apiUrl = 'http://localhost:8387/api'; @@ -16,26 +14,7 @@ var wwwUrl = 'http://localhost:8388'; describe('api', function() { var runId; - - it('should not accept a query if there is no key in headers', function(done) { - this.timeout(5000); - request({ - method: 'POST', - url: apiUrl + '/runs', - body: { - url: wwwUrl + '/simple-page.html', - waitForResponse: false - }, - json: true - }, function(error, response, body) { - if (!error && response.statusCode === 401) { - done(); - } else { - done(error || response.statusCode); - } - }); - }); it('should refuse a query with an invalid key', function(done) { this.timeout(5000); @@ -87,85 +66,81 @@ describe('api', function() { }); }); - it('should refuse an expired token', function(done) { - this.timeout(5000); + it('should accept up to 24 anonymous runs to the API', function(done) { + this.timeout(15000); - request({ - method: 'POST', - url: apiUrl + '/runs', - body: { - url: wwwUrl + '/simple-page.html', - waitForResponse: false - }, - json: true, - headers: { - 'X-Api-Token': jwt.encode({ - application: config.authorizedApplications[0], - expire: Date.now() - 60000 - }, config.tokenSalt) - } - }, function(error, response, body) { - if (!error && response.statusCode === 401) { - done(); - } else { - done(error || response.statusCode); - } - }); - }); + function launchRun() { + var deferred = Q.defer(); - it('should refuse a token from an unknown app', function(done) { - this.timeout(5000); + request({ + method: 'POST', + url: apiUrl + '/runs', + body: { + url: wwwUrl + '/simple-page.html', + waitForResponse: false + }, + json: true + }, function(error, response, body) { + if (error) { + deferred.reject(error); + } else { + deferred.resolve(response, body); + } + }); - request({ - method: 'POST', - url: apiUrl + '/runs', - body: { - url: wwwUrl + '/simple-page.html', - waitForResponse: false - }, - json: true, - headers: { - 'X-Api-Token': jwt.encode({ - application: 'unknown-app', - expire: Date.now() + 60000 - }, config.tokenSalt) - } - }, function(error, response, body) { - if (!error && response.statusCode === 401) { - done(); - } else { - done(error || response.statusCode); - } - }); - }); + return deferred.promise; + } - it('should accept a good token', function(done) { - this.timeout(5000); + launchRun() + .then(launchRun) + .then(launchRun) + .then(launchRun) + .then(launchRun) + .then(launchRun) + .then(launchRun) + .then(launchRun) + .then(launchRun) + .then(launchRun) + .then(launchRun) + .then(launchRun) - request({ - method: 'POST', - url: apiUrl + '/runs', - body: { - url: wwwUrl + '/simple-page.html', - waitForResponse: false - }, - json: true, - headers: { - 'X-Api-Token': jwt.encode({ - application: config.authorizedApplications[0], - expire: Date.now() + 60000 - }, config.tokenSalt) - } - }, function(error, response, body) { - if (!error && response.statusCode === 200) { + .then(function(response, body) { + + // Here should still be ok + response.statusCode.should.equal(200); - runId = body.runId; - runId.should.be.a('string'); + launchRun() + .then(launchRun) + .then(launchRun) + .then(launchRun) + .then(launchRun) + .then(launchRun) + .then(launchRun) + .then(launchRun) + .then(launchRun) + .then(launchRun) + .then(launchRun) + .then(launchRun) + .then(launchRun) + .then(launchRun) + .then(launchRun) + + .then(function(response, body) { + + // It should fail now + response.statusCode.should.equal(429); done(); - } else { - done(error || response.statusCode); - } + }) + .fail(function(error) { + done(error); + }); + + }).fail(function(error) { + done(error); }); + }); + + }); From 3ad6cdef51b074e1a465483acb40f0a54b0d044d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sat, 13 Dec 2014 12:11:33 +0100 Subject: [PATCH 11/98] Launch the test server with coverage --- Gruntfile.js | 42 +++++++++++++++++++++++++++++++++++-- bin/cli.js | 2 -- bin/server.js | 4 +--- server_config/settings.json | 2 +- test/api/apiTest.js | 18 +--------------- test/fixtures/settings.json | 10 +++++++++ 6 files changed, 53 insertions(+), 25 deletions(-) create mode 100644 test/fixtures/settings.json diff --git a/Gruntfile.js b/Gruntfile.js index b9e7296..9cf19b1 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -58,7 +58,8 @@ module.exports = function(grunt) { coverage: { files: [ {src: ['test/**'], dest: 'coverage/'}, - {src: ['lib/metadata/**'], dest: 'coverage/'} + {src: ['lib/metadata/**'], dest: 'coverage/'}, + {src: ['bin/**'], dest: 'coverage/'} ] } }, @@ -70,6 +71,10 @@ module.exports = function(grunt) { coverageLib: { src: ['lib/'], dest: 'coverage/lib/' + }, + coverageBin: { + src: ['bin/'], + dest: 'coverage/bin/' } }, mochaTest: { @@ -106,7 +111,7 @@ module.exports = function(grunt) { testServer: { options: { port: 8387, - server: './bin/server.js' + server: './coverage/bin/server.js' } }, testSuite: { @@ -118,6 +123,37 @@ module.exports = function(grunt) { } }); + + // Custom task: copies the test settings.json file to the coverage folder, and checks if there's no missing fields + grunt.registerTask('copy-test-server-settings', function() { + var mainSettingsFile = './server_config/settings.json'; + var testSettingsFile = './test/fixtures/settings.json'; + + var mainSettings = grunt.file.readJSON(mainSettingsFile); + var testSettings = grunt.file.readJSON(testSettingsFile); + + // Recursively compare keys of two objects (not the values) + function compareKeys(original, copy, context) { + for (var key in original) { + if (!copy[key] && copy[key] !== '' && copy[key] !== 0) { + grunt.fail.warn('Settings file ' + testSettingsFile + ' doesn\'t contain key ' + context + '.' + key); + } + if (original[key] !== null && typeof original[key] === 'object') { + compareKeys(original[key], copy[key], context + '.' + key); + } + } + } + + compareKeys(mainSettings, testSettings, 'settings'); + + var outputFile = './coverage/server_config/settings.json'; + grunt.file.write(outputFile, JSON.stringify(testSettings, null, 4)); + grunt.verbose.ok('File ' + outputFile + ' created'); + }); + + + + require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); grunt.registerTask('icons', [ @@ -141,6 +177,7 @@ module.exports = function(grunt) { grunt.registerTask('test', [ 'build', 'jshint', + 'copy-test-server-settings', 'express:testServer', 'express:testSuite', 'clean:coverage', @@ -153,6 +190,7 @@ module.exports = function(grunt) { grunt.registerTask('test-current-work', [ 'build', 'jshint', + 'copy-test-server-settings', 'express:testServer', 'express:testSuite', 'clean:coverage', diff --git a/bin/cli.js b/bin/cli.js index 88b615a..0524668 100644 --- a/bin/cli.js +++ b/bin/cli.js @@ -1,5 +1,3 @@ -#!/usr/bin/env node - var debug = require('debug')('ylt:cli'); var YellowLabTools = require('../lib/yellowlabtools'); diff --git a/bin/server.js b/bin/server.js index f8b76aa..95bb8a5 100644 --- a/bin/server.js +++ b/bin/server.js @@ -1,6 +1,3 @@ -// Config file -var settings = require('../server_config/settings.json'); - var express = require('express'); var app = express(); var server = require('http').createServer(app); @@ -25,6 +22,7 @@ var uiController = require('../lib/server/controllers/uiController')( // Let's start the server! if (!process.env.GRUNTED) { // The server is not launched by Grunt + var settings = require('../server_config/settings.json'); server.listen(settings.serverPort, function() { console.log('Listening on port %d', server.address().port); }); diff --git a/server_config/settings.json b/server_config/settings.json index 2b05df1..5c69980 100644 --- a/server_config/settings.json +++ b/server_config/settings.json @@ -5,6 +5,6 @@ "authorizedKeys": { "1234567890": "contact@gaelmetais.com" }, - "maxAnonymousRunsPerDay": 24, + "maxAnonymousRunsPerDay": 50, "maxAnonymousCallsPerDay": 1000 } \ No newline at end of file diff --git a/test/api/apiTest.js b/test/api/apiTest.js index 3183bf2..e23f3d5 100644 --- a/test/api/apiTest.js +++ b/test/api/apiTest.js @@ -66,7 +66,7 @@ describe('api', function() { }); }); - it('should accept up to 24 anonymous runs to the API', function(done) { + it('should accept up to 10 anonymous runs to the API', function(done) { this.timeout(15000); function launchRun() { @@ -96,13 +96,6 @@ describe('api', function() { .then(launchRun) .then(launchRun) .then(launchRun) - .then(launchRun) - .then(launchRun) - .then(launchRun) - .then(launchRun) - .then(launchRun) - .then(launchRun) - .then(launchRun) .then(function(response, body) { @@ -115,15 +108,6 @@ describe('api', function() { .then(launchRun) .then(launchRun) .then(launchRun) - .then(launchRun) - .then(launchRun) - .then(launchRun) - .then(launchRun) - .then(launchRun) - .then(launchRun) - .then(launchRun) - .then(launchRun) - .then(launchRun) .then(function(response, body) { diff --git a/test/fixtures/settings.json b/test/fixtures/settings.json new file mode 100644 index 0000000..619190f --- /dev/null +++ b/test/fixtures/settings.json @@ -0,0 +1,10 @@ +{ + "serverPort": "auto", + "googleAnalyticsId": "", + + "authorizedKeys": { + "1234567890": "contact@gaelmetais.com" + }, + "maxAnonymousRunsPerDay": 10, + "maxAnonymousCallsPerDay": 1000 +} \ No newline at end of file From 8abfe93df74084b62be1a6ccdc53bc9b2c934894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sat, 13 Dec 2014 13:56:21 +0100 Subject: [PATCH 12/98] The test itself starts the server in a before hook --- Gruntfile.js | 14 +++----------- bin/server.js | 19 ++++++++++--------- test/api/apiTest.js | 12 ++++++++++-- test/fixtures/settings.json | 2 +- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 9cf19b1..ac5dc6a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -108,12 +108,6 @@ module.exports = function(grunt) { showStack: true } }, - testServer: { - options: { - port: 8387, - server: './coverage/bin/server.js' - } - }, testSuite: { options: { port: 8388, @@ -148,7 +142,7 @@ module.exports = function(grunt) { var outputFile = './coverage/server_config/settings.json'; grunt.file.write(outputFile, JSON.stringify(testSettings, null, 4)); - grunt.verbose.ok('File ' + outputFile + ' created'); + grunt.log.ok('File ' + outputFile + ' created'); }); @@ -177,10 +171,9 @@ module.exports = function(grunt) { grunt.registerTask('test', [ 'build', 'jshint', - 'copy-test-server-settings', - 'express:testServer', 'express:testSuite', 'clean:coverage', + 'copy-test-server-settings', 'blanket', 'copy:coverage', 'mochaTest:test', @@ -190,10 +183,9 @@ module.exports = function(grunt) { grunt.registerTask('test-current-work', [ 'build', 'jshint', - 'copy-test-server-settings', - 'express:testServer', 'express:testSuite', 'clean:coverage', + 'copy-test-server-settings', 'blanket', 'copy:coverage', 'mochaTest:test-current-work' diff --git a/bin/server.js b/bin/server.js index 95bb8a5..cc7199c 100644 --- a/bin/server.js +++ b/bin/server.js @@ -20,13 +20,14 @@ var uiController = require('../lib/server/controllers/uiController')( // Let's start the server! -if (!process.env.GRUNTED) { - // The server is not launched by Grunt - var settings = require('../server_config/settings.json'); - server.listen(settings.serverPort, function() { - console.log('Listening on port %d', server.address().port); - }); -} +var settings = require('../server_config/settings.json'); +server.listen(settings.serverPort, function() { + console.log('Listening on port %d', server.address().port); -// For Grunt -module.exports = app; \ No newline at end of file + // For the tests + if (server.startTests) { + server.startTests(); + } +}); + +module.exports = server; \ No newline at end of file diff --git a/test/api/apiTest.js b/test/api/apiTest.js index e23f3d5..da46653 100644 --- a/test/api/apiTest.js +++ b/test/api/apiTest.js @@ -14,7 +14,12 @@ var wwwUrl = 'http://localhost:8388'; describe('api', function() { var runId; + var apiServer; + before(function(done) { + apiServer = require('../../bin/server.js'); + apiServer.startTests = done; + }); it('should refuse a query with an invalid key', function(done) { this.timeout(5000); @@ -67,7 +72,7 @@ describe('api', function() { }); it('should accept up to 10 anonymous runs to the API', function(done) { - this.timeout(15000); + this.timeout(5000); function launchRun() { var deferred = Q.defer(); @@ -126,5 +131,8 @@ describe('api', function() { }); - + after(function() { + console.log('Closing the server'); + apiServer.close(); + }); }); diff --git a/test/fixtures/settings.json b/test/fixtures/settings.json index 619190f..75b2365 100644 --- a/test/fixtures/settings.json +++ b/test/fixtures/settings.json @@ -1,5 +1,5 @@ { - "serverPort": "auto", + "serverPort": "8387", "googleAnalyticsId": "", "authorizedKeys": { From 792f7d8e21ef31827fa5833a229683031046f110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sun, 14 Dec 2014 15:47:58 +0100 Subject: [PATCH 13/98] Implements a general score calculator --- Gruntfile.js | 2 +- lib/rulesChecker.js | 1 - lib/runner.js | 28 ++- lib/scoreCalculator.js | 83 +++++++ lib/server/middlewares/apiLimitsMiddleware.js | 4 +- test/api/apiTest.js | 210 +++++++++++++++++- test/core/scoreCalculatorTest.js | 18 ++ test/core/yellowlabtoolsTest.js | 15 +- test/fixtures/scoreInput.json | 68 ++++++ test/fixtures/scoreOutput.json | 34 +++ test/fixtures/scoreProfile.json | 35 +++ 11 files changed, 478 insertions(+), 20 deletions(-) create mode 100644 lib/scoreCalculator.js create mode 100644 test/core/scoreCalculatorTest.js create mode 100644 test/fixtures/scoreInput.json create mode 100644 test/fixtures/scoreOutput.json create mode 100644 test/fixtures/scoreProfile.json diff --git a/Gruntfile.js b/Gruntfile.js index ac5dc6a..81702ea 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -88,7 +88,7 @@ module.exports = function(grunt) { options: { reporter: 'spec', }, - src: ['coverage/test/api/apiTest.js'] + src: ['coverage/test/core/scoreCalculatorTest.js'] }, coverage: { options: { diff --git a/lib/rulesChecker.js b/lib/rulesChecker.js index c576c72..9033f0e 100644 --- a/lib/rulesChecker.js +++ b/lib/rulesChecker.js @@ -7,7 +7,6 @@ var RulesChecker = function() { this.check = function(data, policies) { var results = {}; - var err = null; debug('Starting checking rules'); diff --git a/lib/runner.js b/lib/runner.js index c9083c1..caa81a4 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -1,8 +1,10 @@ -var Q = require('q'); -var debug = require('debug')('ylt:yellowlabtools'); +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 rulesChecker = require('./rulesChecker'); var Runner = function(params) { 'use strict'; @@ -27,6 +29,24 @@ var Runner = function(params) { data.rules = rulesChecker.check(data, policies); + // Scores calculator + var scoreProfileGeneric = require('./metadata/scoreProfileGeneric.json'); + data.scoreProfiles = { + generic : scoreCalculator.calculate(data, scoreProfileGeneric) + }; + + + // 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'); + } + + delete data.toolsResults.phantomas.metrics.javascriptExecutionTree; + delete data.toolsResults.phantomas.offenders.javascriptExecutionTree; + + //Finished! deferred.resolve(data); }).fail(function(err) { diff --git a/lib/scoreCalculator.js b/lib/scoreCalculator.js new file mode 100644 index 0000000..8a0a399 --- /dev/null +++ b/lib/scoreCalculator.js @@ -0,0 +1,83 @@ +var Q = require('q'); +var debug = require('debug')('ylt:scoreCalculator'); + +var ScoreCalculator = function() { + 'use strict'; + + this.calculate = function(data, profile) { + + var results = { + categories: {} + }; + var weight; + var categoryName; + + debug('Starting calculating scores'); + + // Calculate categories + for (categoryName in profile.categories) { + var categoryResult = { + label: profile.categories[categoryName].label + }; + + var sum = 0; + var totalWeight = 0; + var rules = []; + + for (var policyName in profile.categories[categoryName].policies) { + weight = profile.categories[categoryName].policies[policyName]; + + if (data.rules[policyName]) { + sum += data.rules[policyName].score * weight; + } else { + // Max value if rule is not here + sum += 100 * weight; + debug('Warning: could not find rule %s', policyName); + } + + totalWeight += weight; + rules.push(policyName); + } + + if (totalWeight === 0) { + categoryResult.categoryScore = 100; + } else { + categoryResult.categoryScore = Math.round(sum / totalWeight); + } + + categoryResult.rules = rules; + results.categories[categoryName] = categoryResult; + } + + + // Calculate general score + var globalSum = 0; + var globalTotalWeight = 0; + + for (categoryName in profile.globalScore) { + weight = profile.globalScore[categoryName]; + + if (results.categories[categoryName]) { + globalSum += results.categories[categoryName].categoryScore * weight; + } else { + globalSum += 100 * weight; + } + globalTotalWeight += profile.globalScore[categoryName]; + } + + if (globalTotalWeight === 0) { + results.globalScore = 100; + } else { + results.globalScore = Math.round(globalSum / globalTotalWeight); + } + + + + debug('Score calculation finished:'); + debug(results); + + return results; + }; +}; + +module.exports = new ScoreCalculator(); \ No newline at end of file diff --git a/lib/server/middlewares/apiLimitsMiddleware.js b/lib/server/middlewares/apiLimitsMiddleware.js index 7c17015..cce5df2 100644 --- a/lib/server/middlewares/apiLimitsMiddleware.js +++ b/lib/server/middlewares/apiLimitsMiddleware.js @@ -16,7 +16,7 @@ var apiLimitsMiddleware = function(req, res, next) { if (!runsTable.accepts(req.connection.remoteAddress)) { // Sorry :/ debug('Too many tests launched from IP address %s', req.connection.remoteAddress); - res.status(429).send('Too Many Requests'); + res.status(429).send('Too many requests'); return; } @@ -25,7 +25,7 @@ var apiLimitsMiddleware = function(req, res, next) { if (!callsTable.accepts(req.connection.remoteAddress)) { // Sorry :/ debug('Too many API requests from IP address %s', req.connection.remoteAddress); - res.status(429).send('Too Many Requests'); + res.status(429).send('Too many requests'); return; } diff --git a/test/api/apiTest.js b/test/api/apiTest.js index da46653..44c599f 100644 --- a/test/api/apiTest.js +++ b/test/api/apiTest.js @@ -8,25 +8,30 @@ var config = { } }; -var apiUrl = 'http://localhost:8387/api'; +var serverUrl = 'http://localhost:8387'; var wwwUrl = 'http://localhost:8388'; describe('api', function() { - var runId; + + var syncRunResultUrl; + var asyncRunId; var apiServer; + + // Start the server before(function(done) { apiServer = require('../../bin/server.js'); apiServer.startTests = done; }); + it('should refuse a query with an invalid key', function(done) { this.timeout(5000); request({ method: 'POST', - url: apiUrl + '/runs', + url: serverUrl + '/api/runs', body: { url: wwwUrl + '/simple-page.html', waitForResponse: false @@ -44,12 +49,67 @@ describe('api', function() { }); }); - it('should accept a query with a valid key', function(done) { + + it('should launch a synchronous run', function(done) { + this.timeout(15000); + + request({ + method: 'POST', + url: serverUrl + '/api/runs', + body: { + url: wwwUrl + '/simple-page.html', + waitForResponse: true + }, + json: true, + headers: { + 'X-Api-Key': Object.keys(config.authorizedKeys)[0] + } + }, function(error, response, body) { + if (!error && response.statusCode === 302) { + + response.headers.should.have.a.property('location').that.is.a('string'); + syncRunResultUrl = response.headers.location; + + done(); + } else { + done(error || response.statusCode); + } + }); + }); + + + it('should retrieve the results for the synchronous run', function(done) { + this.timeout(15000); + + request({ + method: 'GET', + url: serverUrl + syncRunResultUrl, + json: true, + }, function(error, response, body) { + if (!error && response.statusCode === 200) { + + body.should.have.a.property('runId').that.is.a('string'); + body.should.have.a.property('params').that.is.an('object'); + body.should.have.a.property('scoreProfiles').that.is.an('object'); + body.should.have.a.property('rules').that.is.an('object'); + body.should.have.a.property('toolsResults').that.is.an('object'); + body.should.have.a.property('javascriptExecutionTree').that.is.an('object'); + + done(); + + } else { + done(error || response.statusCode); + } + }); + }); + + + it('should launch a run without waiting for the response', function(done) { this.timeout(5000); request({ method: 'POST', - url: apiUrl + '/runs', + url: serverUrl + '/api/runs', body: { url: wwwUrl + '/simple-page.html', waitForResponse: false @@ -61,8 +121,35 @@ describe('api', function() { }, function(error, response, body) { if (!error && response.statusCode === 200) { - runId = body.runId; - runId.should.be.a('string'); + asyncRunId = body.runId; + asyncRunId.should.be.a('string'); + done(); + + } else { + done(error || response.statusCode); + } + }); + }); + + + it('should respond run status: running', function(done) { + this.timeout(5000); + + request({ + method: 'GET', + url: serverUrl + '/api/runs/' + asyncRunId, + json: true, + headers: { + 'X-Api-Key': Object.keys(config.authorizedKeys)[0] + } + }, function(error, response, body) { + if (!error && response.statusCode === 200) { + + body.runId.should.equal(asyncRunId); + body.status.should.deep.equal({ + statusCode: 'running' + }); + done(); } else { @@ -79,13 +166,16 @@ describe('api', function() { request({ method: 'POST', - url: apiUrl + '/runs', + url: serverUrl + '/api/runs', body: { url: wwwUrl + '/simple-page.html', waitForResponse: false }, json: true }, function(error, response, body) { + + lastRunId = body.runId; + if (error) { deferred.reject(error); } else { @@ -131,6 +221,110 @@ describe('api', function() { }); + + it('should respond 404 to unknown runId', function(done) { + this.timeout(5000); + + request({ + method: 'GET', + url: serverUrl + '/api/runs/unknown', + json: true + }, function(error, response, body) { + if (!error && response.statusCode === 404) { + done(); + } else { + done(error || response.statusCode); + } + }); + }); + + + it('should respond 404 to unknown result', function(done) { + this.timeout(5000); + + request({ + method: 'GET', + url: serverUrl + '/api/results/unknown', + json: true + }, function(error, response, body) { + if (!error && response.statusCode === 404) { + done(); + } else { + done(error || response.statusCode); + } + }); + }); + + + it('should respond status complete to the first run', function(done) { + this.timeout(12000); + + function checkStatus() { + request({ + method: 'GET', + url: serverUrl + '/api/runs/' + asyncRunId, + json: true + }, function(error, response, body) { + if (!error && response.statusCode === 200) { + + body.runId.should.equal(asyncRunId); + + if (body.status.statusCode === 'running') { + setTimeout(checkStatus, 250); + } else if (body.status.statusCode === 'complete') { + done(); + } else { + done(body.status.statusCode); + } + + } else { + done(error || response.statusCode); + } + }); + } + + checkStatus(); + }); + + + it('should find the result of the async run', function(done) { + this.timeout(5000); + + request({ + method: 'GET', + url: serverUrl + '/api/results/' + asyncRunId, + json: true, + }, function(error, response, body) { + if (!error && response.statusCode === 200) { + + body.should.have.a.property('runId').that.equals(asyncRunId); + body.should.have.a.property('params').that.is.an('object'); + body.params.url.should.equal(wwwUrl + '/simple-page.html'); + + body.should.have.a.property('scoreProfiles').that.is.an('object'); + body.scoreProfiles.should.have.a.property('generic').that.is.an('object'); + body.scoreProfiles.generic.should.have.a.property('globalScore').that.is.a('number'); + body.scoreProfiles.generic.should.have.a.property('categories').that.is.an('object'); + + body.should.have.a.property('rules').that.is.an('object'); + + body.should.have.a.property('toolsResults').that.is.an('object'); + body.toolsResults.should.have.a.property('phantomas').that.is.an('object'); + + body.should.have.a.property('javascriptExecutionTree').that.is.an('object'); + body.javascriptExecutionTree.should.have.a.property('data').that.is.an('object'); + body.javascriptExecutionTree.data.should.have.a.property('type').that.equals('main'); + + done(); + + } else { + done(error || response.statusCode); + } + }); + }); + + + // Stop the server after(function() { console.log('Closing the server'); apiServer.close(); diff --git a/test/core/scoreCalculatorTest.js b/test/core/scoreCalculatorTest.js new file mode 100644 index 0000000..171fe93 --- /dev/null +++ b/test/core/scoreCalculatorTest.js @@ -0,0 +1,18 @@ +var should = require('chai').should(); +var scoreCalculator = require('../../lib/scoreCalculator'); + +describe('scoreCalculator', function() { + + it('should have a method calculate', function() { + scoreCalculator.should.have.property('calculate').that.is.a('function'); + }); + + it('should produce a nice rules object', function() { + var data = require('../fixtures/scoreInput.json'); + var profile = require('../fixtures/scoreProfile.json'); + var expected = require('../fixtures/scoreOutput.json'); + + var results = scoreCalculator.calculate(data, profile); + results.should.deep.equals(expected); + }); +}); diff --git a/test/core/yellowlabtoolsTest.js b/test/core/yellowlabtoolsTest.js index b1dd85e..eea43e5 100644 --- a/test/core/yellowlabtoolsTest.js +++ b/test/core/yellowlabtoolsTest.js @@ -9,28 +9,28 @@ chai.use(sinonChai); describe('yellowlabtools', function() { - it('returns a promise', function() { + it('should return a promise', function() { var ylt = new YellowLabTools(); ylt.should.have.property('then').that.is.a('function'); ylt.should.have.property('fail').that.is.a('function'); }); - it('fails an undefined url', function(done) { + it('should fail an undefined url', function(done) { var ylt = new YellowLabTools().fail(function(err) { err.should.be.a('string').that.equals('URL missing'); done(); }); }); - it('fails with an empty url string', function(done) { + it('should fail with an empty url string', function(done) { var ylt = new YellowLabTools('').fail(function(err) { err.should.be.a('string').that.equals('URL missing'); done(); }); }); - it('succeeds on simple-page.html', function(done) { + it('should succeeds on simple-page.html', function(done) { this.timeout(15000); // Check if console.log is called @@ -75,6 +75,13 @@ describe('yellowlabtools', function() { "offenders": ["body > h1[1]"] }); + // Test javascriptExecutionTree + data.toolsResults.phantomas.metrics.should.not.have.a.property('javascriptExecutionTree'); + data.toolsResults.phantomas.offenders.should.not.have.a.property('javascriptExecutionTree'); + data.should.have.a.property('javascriptExecutionTree').that.is.an('object'); + data.javascriptExecutionTree.should.have.a.property('data'); + data.javascriptExecutionTree.data.should.have.a.property('type').that.equals('main'); + /*jshint expr: true*/ console.log.should.not.have.been.called; diff --git a/test/fixtures/scoreInput.json b/test/fixtures/scoreInput.json new file mode 100644 index 0000000..81e6a4f --- /dev/null +++ b/test/fixtures/scoreInput.json @@ -0,0 +1,68 @@ +{ + "rules": { + "metric1": { + "policy": { + "tool": "tool1", + "label": "The metric 1", + "message": "A great message", + "isOkThreshold": 1000, + "isBadThreshold": 3000, + "isAbnormalThreshold": 5000 + }, + "value": 1236, + "bad": true, + "abnormal": false, + "score": 88, + "abnormalityScore": 0 + }, + "metric2": { + "value": 222, + "bad": false, + "abnormal": false, + "score": 100, + "abnormalityScore": 0 + }, + "metric3": { + "value": 6666, + "bad": true, + "abnormal": true, + "score": 0, + "abnormalityScore": -42 + }, + "metric4": { + "value": 1000, + "bad": false, + "abnormal": false, + "score": 100, + "abnormalityScore": 0 + }, + "metric5": { + "value": 3000, + "bad": true, + "abnormal": false, + "score": 0, + "abnormalityScore": 0 + }, + "metric6": { + "value": 0, + "bad": false, + "abnormal": false, + "score": 100, + "abnormalityScore": 0 + }, + "metric7": { + "value": 5000, + "bad": true, + "abnormal": true, + "score": 0, + "abnormalityScore": 0 + }, + "metric8": { + "value": 22, + "bad": true, + "abnormal": true, + "score": 0, + "abnormalityScore": -100 + } + } +} \ No newline at end of file diff --git a/test/fixtures/scoreOutput.json b/test/fixtures/scoreOutput.json new file mode 100644 index 0000000..4630bbf --- /dev/null +++ b/test/fixtures/scoreOutput.json @@ -0,0 +1,34 @@ +{ + "globalScore": 69, + "categories": { + "category1": { + "label": "Category 1", + "categoryScore": 87, + "rules": [ + "metric1", + "metric2", + "metric3", + "metric4" + ] + }, + "category2": { + "label": "Category 2", + "categoryScore": 31, + "rules": [ + "metric5", + "metric6", + "metric7", + "metric8", + "unexistantMetric1" + ] + }, + "category3": { + "label": "Category 3", + "categoryScore": 100, + "rules": [ + "unexistantMetric1", + "unexistantMetric2" + ] + } + } +} \ No newline at end of file diff --git a/test/fixtures/scoreProfile.json b/test/fixtures/scoreProfile.json new file mode 100644 index 0000000..8856126 --- /dev/null +++ b/test/fixtures/scoreProfile.json @@ -0,0 +1,35 @@ +{ + "categories": { + "category1": { + "label": "Category 1", + "policies": { + "metric1": 2, + "metric2": 1, + "metric3": 0.5, + "metric4": 2 + } + }, + "category2": { + "label": "Category 2", + "policies": { + "metric5": 2, + "metric6": 1, + "metric7": 0.5, + "metric8": 2, + "unexistantMetric1": 1 + } + }, + "category3": { + "label": "Category 3", + "policies": { + "unexistantMetric1": 2, + "unexistantMetric2": 1 + } + } + }, + "globalScore": { + "category1": 2, + "category2": 1, + "category3": 0.1 + } +} \ No newline at end of file From 90dac7dd19efef1d04335bafad90385577e6dcb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sun, 14 Dec 2014 21:35:46 +0100 Subject: [PATCH 14/98] Created the generic score profile --- lib/metadata/policies.json | 28 +++---- lib/metadata/scoreProfileGeneric.json | 105 +++++++++++++++++++++++++- 2 files changed, 118 insertions(+), 15 deletions(-) diff --git a/lib/metadata/policies.json b/lib/metadata/policies.json index 40df848..c858342 100644 --- a/lib/metadata/policies.json +++ b/lib/metadata/policies.json @@ -2,7 +2,7 @@ "DOMelementsCount": { "tool": "phantomas", "label": "DOM elements count", - "message": "

A high number of DOM elements means a lot of work for the browser to render the page.

It also slows down Javascript DOM queries, as there are more elements to search through.

", + "message": "

A high number of DOM elements means a lot of work for the browser to render the page.

It also slows down JavaScript DOM queries, as there are more elements to search through.

", "isOkThreshold": 1000, "isBadThreshold": 3000, "isAbnormalThreshold": 5000 @@ -10,7 +10,7 @@ "DOMelementMaxDepth": { "tool": "phantomas", "label": "DOM max depth", - "message": "

A deep DOM makes the CSS matching with DOM elements difficult.

It also slows down Javascript modifications to the DOM because changing the dimensions of an element makes the browser re-calculate the dimensions of it's parents. Same thing for Javascript events, that bubble up to the document root.

", + "message": "

A deep DOM makes the CSS matching with DOM elements difficult.

It also slows down JavaScript modifications to the DOM because changing the dimensions of an element makes the browser re-calculate the dimensions of it's parents. Same thing for JavaScript events, that bubble up to the document root.

", "isOkThreshold": 10, "isBadThreshold": 20, "isAbnormalThreshold": 30 @@ -34,7 +34,7 @@ "DOMinserts": { "tool": "phantomas", "label": "DOM inserts", - "message": "

Working with the DOM in Javascript triggers layout calculations and slows down the page.

Try, as much as possible, to have an HTML page fully generated by the server instead of making changes with JS.

", + "message": "

Working with the DOM in JavaScript triggers layout calculations and slows down the page.

Try, as much as possible, to have an HTML page fully generated by the server instead of making changes with JS.

", "isOkThreshold": 10, "isBadThreshold": 400, "isAbnormalThreshold": 1000 @@ -42,7 +42,7 @@ "DOMqueries": { "tool": "phantomas", "label": "DOM queries", - "message": "

DOM queries are like looking in a large catalog of items. Even if the browsers made progress on the performances of queries, websites often make hundreds of them.

Try to reduce the number of queries by refactoring your Javascript code.

Avoid also to have a read query between two write queries. To be able to reduce the number repaints and optimize performances, browsers buffer the DOM writing operations and treat them in bulk. But each time a DOM reading is asked, the browser needs to empty the buffer. This can be particularly slow inside a loop.

", + "message": "

DOM queries are like looking in a large catalog of items. Even if the browsers made progress on the performances of queries, websites often make hundreds of them.

Try to reduce the number of queries by refactoring your JavaScript code.

Avoid also to have a read query between two write queries. To be able to reduce the number repaints and optimize performances, browsers buffer the DOM writing operations and treat them in bulk. But each time a DOM reading is asked, the browser needs to empty the buffer. This can be particularly slow inside a loop.

", "isOkThreshold": 50, "isBadThreshold": 1000, "isAbnormalThreshold": 2000 @@ -65,7 +65,7 @@ }, "jsErrors": { "tool": "phantomas", - "label": "Javascript errors", + "label": "JavaScript errors", "message": "

Just to let you know there are some errors on the page.

Please note that some errors only occur in the PhantomJS browser, so you might need to double check on other browsers.

", "isOkThreshold": 0, "isBadThreshold": 1, @@ -106,7 +106,7 @@ "inBodyDomManipulations": { "tool": "ylt", "label": "DOM manipulations in body", - "message": "

This metric counts the number of DOM queries, DOM inserts, binds, etc. made by the Javascript before the DOMContentLoaded event.

Wait for this event before manipulating the DOM. Do not execute Javascript in the middle of the BODY as it slows down the construction of the DOM and makes a poor maintainability. This is what i call spaghetti code.

The JS Timeline tab can help you identify what's happening.

", + "message": "

This metric counts the number of DOM queries, DOM inserts, binds, etc. made by the JavaScript before the DOMContentLoaded event.

Wait for this event before manipulating the DOM. Do not execute JavaScript in the middle of the BODY as it slows down the construction of the DOM and makes a poor maintainability. This is what i call spaghetti code.

The JS Timeline tab can help you identify what's happening.

", "isOkThreshold": 10, "isBadThreshold": 50, "isAbnormalThreshold": 100 @@ -125,6 +125,14 @@ "message": "

jQuery is a heavy library. You should never load jQuery more than one on the same page.

", "isOkThreshold": 1, "isBadThreshold": 2, + "isAbnormalThreshold": 2 + }, + "cssParsingErrors": { + "tool": "phantomas", + "label": "CSS syntax error", + "message": "

Yellow Lab Tools failed to parse a CSS file. I doubt the problem comes from the css parser.

Maybe a CSS validator can help you.

", + "isOkThreshold": 1, + "isBadThreshold": 2, "isAbnormalThreshold": 3 }, "cssRules": { @@ -151,14 +159,6 @@ "isBadThreshold": 50, "isAbnormalThreshold": 100 }, - "cssParsingErrors": { - "tool": "phantomas", - "label": "CSS syntax error", - "message": "

Yellow Lab Tools failed to parse a CSS file. I doubt the problem comes from the css parser.

Maybe a CSS validator can help you.

", - "isOkThreshold": 1, - "isBadThreshold": 2, - "isAbnormalThreshold": 3 - }, "cssImports": { "tool": "phantomas", "label": "Uses of @import", diff --git a/lib/metadata/scoreProfileGeneric.json b/lib/metadata/scoreProfileGeneric.json index 544b7b4..15bf1e0 100644 --- a/lib/metadata/scoreProfileGeneric.json +++ b/lib/metadata/scoreProfileGeneric.json @@ -1,3 +1,106 @@ { - + "categories": { + "domComplexity": { + "label": "DOM complexity", + "policies": { + "DOMelementsCount": 1, + "DOMelementMaxDepth": 1, + "iframesCount": 1, + "DOMidDuplicated": 1 + } + }, + "domManipulations": { + "label": "DOM manipulations", + "policies": { + "DOMinserts": 2, + "DOMqueries": 1, + "DOMqueriesAvoidable": 2, + "eventsBound": 1 + } + }, + "badJavascript": { + "label": "Bad JavaScript", + "policies": { + "jsErrors": 2, + "evalCalls": 1, + "documentWriteCalls": 2, + "consoleMessages": 0.5, + "globalVariables": 0.5, + "inBodyDomManipulations": 1 + } + }, + "jQueryVersion": { + "label": "jQuery version", + "policies": { + "jQueryVersion": 1, + "jQueryDifferentVersions": 5 + } + }, + "cssSyntaxError": { + "label": "CSS syntax errors", + "policies": { + "cssParsingErrors": 1 + } + }, + "cssComplexity": { + "label": "CSS complexity", + "policies": { + "cssRules": 2, + "cssComplexSelectors": 2, + "cssComplexSelectorsByAttribute": 1.5 + } + }, + "badCSS": { + "label": "Bad CSS", + "policies": { + "cssImports": 3, + "cssDuplicatedSelectors": 2, + "cssDuplicatedProperties": 1, + "cssEmptyRules": 2, + "cssExpressions": 1, + "cssImportants": 3, + "cssOldIEFixes": 1, + "cssOldPropertyPrefixes": 1, + "cssUniversalSelectors": 1, + "cssRedundantBodySelectors": 1, + "cssRedundantChildNodesSelectors": 1 + } + }, + "requests": { + "label": "Requests number", + "policies": { + "request": 5, + "htmlCount": 0, + "jsCount": 1, + "cssCount": 1, + "imageCount": 0, + "webfontCount": 2, + "videoCount": 0, + "jsonCount": 0, + "otherCount": 0 + } + }, + "network": { + "label": "Network", + "policies": { + "notFound": 3, + "closedConnections": 3, + "multipleRequests": 3, + "cachingDisabled": 1, + "cachingTooShort": 1, + "domains": 1 + } + } + }, + "globalScore": { + "domComplexity": 1, + "domManipulations": 2, + "badJavascript": 1, + "jQueryVersion": 1, + "cssSyntaxError": 1, + "cssComplexity": 1, + "badCSS": 1, + "requests": 3, + "network": 2 + } } \ No newline at end of file From 2ab843efdbc55ebb67b92d987fed224e6fada82b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sun, 14 Dec 2014 22:36:25 +0100 Subject: [PATCH 15/98] Added some sub-requests to the API --- Gruntfile.js | 2 +- lib/server/controllers/apiController.js | 53 ++++++++-- test/api/apiTest.js | 128 ++++++++++++++++++++++++ 3 files changed, 174 insertions(+), 9 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 81702ea..ac5dc6a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -88,7 +88,7 @@ module.exports = function(grunt) { options: { reporter: 'spec', }, - src: ['coverage/test/core/scoreCalculatorTest.js'] + src: ['coverage/test/api/apiTest.js'] }, coverage: { options: { diff --git a/lib/server/controllers/apiController.js b/lib/server/controllers/apiController.js index a1c0952..0f976d7 100644 --- a/lib/server/controllers/apiController.js +++ b/lib/server/controllers/apiController.js @@ -60,7 +60,6 @@ var ApiController = function(app) { // Send result if the user was waiting if (run.params.waitForResponse) { - res.redirect(302, '/api/results/' + run.runId); } @@ -133,21 +132,59 @@ var ApiController = function(app) { // Retrive one result by id app.get('/api/results/:id', function(req, res) { - var runId = req.params.id; + getPartialResults(req.params.id, res, function(data) { + return data; + }); + }); + // Retrieve one result and return only the generalScores part of the response + app.get('/api/results/:id/generalScores', function(req, res) { + getPartialResults(req.params.id, res, function(data) { + return data.scoreProfiles.generic; + }); + }); + + app.get('/api/results/:id/generalScores/:scoreProfile', function(req, res) { + getPartialResults(req.params.id, res, function(data) { + return data.scoreProfiles[req.params.scoreProfile]; + }); + }); + + app.get('/api/results/:id/rules', function(req, res) { + getPartialResults(req.params.id, res, function(data) { + return data.rules; + }); + }); + + app.get('/api/results/:id/javascriptExecutionTree', function(req, res) { + getPartialResults(req.params.id, res, function(data) { + return data.javascriptExecutionTree; + }); + }); + + app.get('/api/results/:id/toolsResults/phantomas', function(req, res) { + getPartialResults(req.params.id, res, function(data) { + return data.toolsResults.phantomas; + }); + }); + + function getPartialResults(runId, res, partialGetterFn) { resultsDatastore.getResult(runId) .then(function(data) { - // This is the pivot format, we might need to clean it first? - - // Hide phantomas results - data.toolsResults.phantomas = {}; + var results = partialGetterFn(data); + if (typeof results === 'undefined') { + res.status(404).send('Not found'); + return; + } + res.setHeader('Content-Type', 'application/json'); - res.send(JSON.stringify(data, null, 2)); + res.send(JSON.stringify(results, null, 2)); + }).fail(function() { res.status(404).send('Not found'); }); - }); + } }; diff --git a/test/api/apiTest.js b/test/api/apiTest.js index 44c599f..f7ff020 100644 --- a/test/api/apiTest.js +++ b/test/api/apiTest.js @@ -324,6 +324,134 @@ describe('api', function() { }); + it('should return the generic score object', function(done) { + this.timeout(5000); + + request({ + method: 'GET', + url: serverUrl + '/api/results/' + asyncRunId + '/generalScores', + json: true, + }, function(error, response, body) { + if (!error && response.statusCode === 200) { + body.should.have.a.property('globalScore').that.is.a('number'); + body.should.have.a.property('categories').that.is.an('object'); + done(); + + } else { + done(error || response.statusCode); + } + }); + }); + + + it('should return the generic score object also', function(done) { + this.timeout(5000); + + request({ + method: 'GET', + url: serverUrl + '/api/results/' + asyncRunId + '/generalScores/generic', + json: true, + }, function(error, response, body) { + if (!error && response.statusCode === 200) { + body.should.have.a.property('globalScore').that.is.a('number'); + body.should.have.a.property('categories').that.is.an('object'); + done(); + + } else { + done(error || response.statusCode); + } + }); + }); + + + it('should not find an unknown score object', function(done) { + this.timeout(5000); + + request({ + method: 'GET', + url: serverUrl + '/api/results/' + asyncRunId + '/generalScores/unknown', + json: true, + }, function(error, response, body) { + if (!error && response.statusCode === 404) { + done(); + } else { + done(error || response.statusCode); + } + }); + }); + + + it('should return the rules', function(done) { + this.timeout(5000); + + request({ + method: 'GET', + url: serverUrl + '/api/results/' + asyncRunId + '/rules', + json: true, + }, function(error, response, body) { + if (!error && response.statusCode === 200) { + + var firstRule = body[Object.keys(body)[0]]; + firstRule.should.have.a.property('policy').that.is.an('object'); + firstRule.should.have.a.property('value').that.is.a('number'); + firstRule.should.have.a.property('bad').that.is.a('boolean'); + firstRule.should.have.a.property('abnormal').that.is.a('boolean'); + firstRule.should.have.a.property('score').that.is.a('number'); + firstRule.should.have.a.property('abnormalityScore').that.is.a('number'); + + done(); + + } else { + done(error || response.statusCode); + } + }); + }); + + + it('should return the javascript execution tree', function(done) { + this.timeout(5000); + + request({ + method: 'GET', + url: serverUrl + '/api/results/' + asyncRunId + '/javascriptExecutionTree', + json: true, + }, function(error, response, body) { + if (!error && response.statusCode === 200) { + + body.should.have.a.property('data').that.is.an('object'); + body.data.should.have.a.property('type').that.equals('main'); + + done(); + + } else { + done(error || response.statusCode); + } + }); + }); + + + it('should return the phantomas results', function(done) { + this.timeout(5000); + + request({ + method: 'GET', + url: serverUrl + '/api/results/' + asyncRunId + '/toolsResults/phantomas', + json: true, + }, function(error, response, body) { + if (!error && response.statusCode === 200) { + + body.should.have.a.property('metrics').that.is.an('object'); + body.should.have.a.property('offenders').that.is.an('object'); + + done(); + + } else { + done(error || response.statusCode); + } + }); + }); + + // Stop the server after(function() { console.log('Closing the server'); From 1385e28911fb871f77edc389faa0f1696dea9219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sun, 14 Dec 2014 22:50:56 +0100 Subject: [PATCH 16/98] Fix tests --- test/core/yellowlabtoolsTest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/core/yellowlabtoolsTest.js b/test/core/yellowlabtoolsTest.js index eea43e5..952abb2 100644 --- a/test/core/yellowlabtoolsTest.js +++ b/test/core/yellowlabtoolsTest.js @@ -62,7 +62,7 @@ describe('yellowlabtools', function() { policy: { "tool": "phantomas", "label": "DOM max depth", - "message": "

A deep DOM makes the CSS matching with DOM elements difficult.

It also slows down Javascript modifications to the DOM because changing the dimensions of an element makes the browser re-calculate the dimensions of it's parents. Same thing for Javascript events, that bubble up to the document root.

", + "message": "

A deep DOM makes the CSS matching with DOM elements difficult.

It also slows down JavaScript modifications to the DOM because changing the dimensions of an element makes the browser re-calculate the dimensions of it's parents. Same thing for JavaScript events, that bubble up to the document root.

", "isOkThreshold": 10, "isBadThreshold": 20, "isAbnormalThreshold": 30 From ffbedc1e02b75f78c39f4560c9fdeb288cd86346 Mon Sep 17 00:00:00 2001 From: achrafbenyounes Date: Mon, 15 Dec 2014 18:43:22 +0100 Subject: [PATCH 17/98] Starting Angular FrontEnd --- app/public/scripts/app.js | 3 ++- bin/server.js | 2 +- bower.json | 4 ++- front/src/js/app.js | 30 +++++++++++++++++++++++ front/src/js/controllers/aboutCtrl.js | 5 ++++ front/src/js/controllers/dashboardCtrl.js | 10 ++++++++ front/src/js/controllers/indexCtrl.js | 5 ++++ front/src/js/models/resultsFactory.js | 7 ++++++ front/src/main.html | 19 ++++++++++++++ front/src/views/about.html | 1 + front/src/views/dashboard.html | 2 ++ front/src/views/index.html | 1 + lib/server/controllers/frontController.js | 23 +++++++++++++++++ lib/server/controllers/uiController.js | 13 ---------- 14 files changed, 109 insertions(+), 16 deletions(-) create mode 100644 front/src/js/app.js create mode 100644 front/src/js/controllers/aboutCtrl.js create mode 100644 front/src/js/controllers/dashboardCtrl.js create mode 100644 front/src/js/controllers/indexCtrl.js create mode 100644 front/src/js/models/resultsFactory.js create mode 100644 front/src/main.html create mode 100644 front/src/views/about.html create mode 100644 front/src/views/dashboard.html create mode 100644 front/src/views/index.html create mode 100644 lib/server/controllers/frontController.js delete mode 100644 lib/server/controllers/uiController.js diff --git a/app/public/scripts/app.js b/app/public/scripts/app.js index f8b9dd7..3b54686 100644 --- a/app/public/scripts/app.js +++ b/app/public/scripts/app.js @@ -2,4 +2,5 @@ angular.module('YellowLabTools', [ 'Results', 'ngModal', 'ShowOffendersDirective' -]); \ No newline at end of file +]); + diff --git a/bin/server.js b/bin/server.js index cc7199c..ea6ca38 100644 --- a/bin/server.js +++ b/bin/server.js @@ -16,7 +16,7 @@ app.use(apiLimitsMiddleware); // Initialize the controllers var apiController = require('../lib/server/controllers/apiController')(app); -var uiController = require('../lib/server/controllers/uiController')(app); +var frontController = require('../lib/server/controllers/frontController')(app); // Let's start the server! diff --git a/bower.json b/bower.json index b308e91..3a28910 100644 --- a/bower.json +++ b/bower.json @@ -2,6 +2,8 @@ "name": "yellowlabtools", "dependencies": { "angular": "~1.3.5", - "ngModal": "git://github.com/gmetais/ngModal.git#1.2.3" + "ngModal": "git://github.com/gmetais/ngModal.git#1.2.3", + "angular-route": "~1.3.6", + "angular-resource": "~1.3.6" } } diff --git a/front/src/js/app.js b/front/src/js/app.js new file mode 100644 index 0000000..aaea1b7 --- /dev/null +++ b/front/src/js/app.js @@ -0,0 +1,30 @@ +var yltApp = angular.module('YellowLabTools', [ + 'ngRoute', + 'indexCtrl', + 'aboutCtrl', + 'dashboardCtrl' +]); + +yltApp.config(['$routeProvider', '$locationProvider', + function($routeProvider, $locationProvider) { + $routeProvider. + when('/', { + templateUrl: 'front/views/index.html', + controller: 'IndexCtrl' + }). + when('/about', { + templateUrl: 'front/views/about.html', + controller: 'AboutCtrl' + }). + when('/results/:runId', { + templateUrl: 'front/views/dashboard.html', + controller: 'DashboardCtrl' + }). + otherwise({ + redirectTo: '/' + }); + + $locationProvider.html5Mode(true); + } +]); + diff --git a/front/src/js/controllers/aboutCtrl.js b/front/src/js/controllers/aboutCtrl.js new file mode 100644 index 0000000..11957f9 --- /dev/null +++ b/front/src/js/controllers/aboutCtrl.js @@ -0,0 +1,5 @@ +var aboutCtrl = angular.module('aboutCtrl', []); + +aboutCtrl.controller('AboutCtrl', ['$scope', function($scope) { + $scope.about = "this is about YLT"; +}]); \ No newline at end of file diff --git a/front/src/js/controllers/dashboardCtrl.js b/front/src/js/controllers/dashboardCtrl.js new file mode 100644 index 0000000..fcc5d95 --- /dev/null +++ b/front/src/js/controllers/dashboardCtrl.js @@ -0,0 +1,10 @@ +var dashboardCtrl = angular.module('dashboardCtrl', ['resultsFactory']); + +dashboardCtrl.controller('DashboardCtrl', ['$scope', '$routeParams', 'Results', function($scope, $routeParams, Results) { + $scope.dashboard = "this is a dashboard"; + $scope.runId = $routeParams.runId; + Results.get({runId: $routeParams.runId}, function(result) { + $scope.result = result; + console.log(result); + }); +}]); \ No newline at end of file diff --git a/front/src/js/controllers/indexCtrl.js b/front/src/js/controllers/indexCtrl.js new file mode 100644 index 0000000..d308850 --- /dev/null +++ b/front/src/js/controllers/indexCtrl.js @@ -0,0 +1,5 @@ +var indexCtrl = angular.module('indexCtrl', []); + +indexCtrl.controller('IndexCtrl', ['$scope', function($scope) { + $scope.toto = "Achraf"; +}]); \ No newline at end of file diff --git a/front/src/js/models/resultsFactory.js b/front/src/js/models/resultsFactory.js new file mode 100644 index 0000000..fab26d0 --- /dev/null +++ b/front/src/js/models/resultsFactory.js @@ -0,0 +1,7 @@ +var resultsFactory = angular.module('resultsFactory', ['ngResource']); + +resultsFactory.factory('Results', ['$resource', function($resource) { + return $resource('/api/results/:runId', { + 'get': {method: 'GET', params: {runId: '@runId'}} + }); +}]); \ No newline at end of file diff --git a/front/src/main.html b/front/src/main.html new file mode 100644 index 0000000..5142b30 --- /dev/null +++ b/front/src/main.html @@ -0,0 +1,19 @@ + + + index template + + + + + + + + + + + + +
hello yellowlab, body
+ + + \ No newline at end of file diff --git a/front/src/views/about.html b/front/src/views/about.html new file mode 100644 index 0000000..d0f7199 --- /dev/null +++ b/front/src/views/about.html @@ -0,0 +1 @@ +{{about}} \ No newline at end of file diff --git a/front/src/views/dashboard.html b/front/src/views/dashboard.html new file mode 100644 index 0000000..29fb6e9 --- /dev/null +++ b/front/src/views/dashboard.html @@ -0,0 +1,2 @@ +
{{dashboard}}
+
{{runId}}
\ No newline at end of file diff --git a/front/src/views/index.html b/front/src/views/index.html new file mode 100644 index 0000000..b475959 --- /dev/null +++ b/front/src/views/index.html @@ -0,0 +1 @@ +this is a html index by {{toto}} \ No newline at end of file diff --git a/lib/server/controllers/frontController.js b/lib/server/controllers/frontController.js new file mode 100644 index 0000000..d13ea6d --- /dev/null +++ b/lib/server/controllers/frontController.js @@ -0,0 +1,23 @@ +var path = require('path'); +var express = require('express'); + +var FrontController = function(app) { + 'use strict'; + + app.get('/', function(req, res) { + res.sendFile(path.join(__dirname, '../../../front/src/main.html')); + }); + + app.get('/about', function(req, res) { + res.sendFile(path.join(__dirname, '../../../front/src/main.html')); + }); + + app.get('/results/:runId', function(req, res) { + res.sendFile(path.join(__dirname, '../../../front/src/main.html')); + }); + + app.use('/front', express.static(path.join(__dirname, '../../../front/src'))); + app.use('/bower_components', express.static(path.join(__dirname, '../../../bower_components'))); +}; + +module.exports = FrontController; \ No newline at end of file diff --git a/lib/server/controllers/uiController.js b/lib/server/controllers/uiController.js deleted file mode 100644 index a6d906d..0000000 --- a/lib/server/controllers/uiController.js +++ /dev/null @@ -1,13 +0,0 @@ -var UiController = function(app) { - 'use strict'; - - - // Create a new run - app.get('/', function(req, res) { - - res.setHeader('Content-Type', 'text/html'); - res.send('Test'); - }); -}; - -module.exports = UiController; \ No newline at end of file From b1a0dfa7bd41248a286b9c394fdd51a60c76fad1 Mon Sep 17 00:00:00 2001 From: achrafbenyounes Date: Mon, 15 Dec 2014 18:58:38 +0100 Subject: [PATCH 18/98] Update jshint config --- Gruntfile.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index ac5dc6a..d21c2cb 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -43,7 +43,8 @@ module.exports = function(grunt) { 'app/nodeControllers/*.js', 'app/public/scripts/*.js', 'phantomas_custom/**/*.js', - 'test/**/*.js' + 'test/**/*.js', + 'front/src/js/**/*.js' ] }, clean: { From d712eb3a5947bdc967285ab9a5c62074d49c0017 Mon Sep 17 00:00:00 2001 From: achrafbenyounes Date: Thu, 18 Dec 2014 18:02:04 +0100 Subject: [PATCH 19/98] Enhancing index, view and dashboard ui --- Gruntfile.js | 29 ++++++++++++++--------- app/public/scripts/app.js | 1 + front/src/js/app.js | 12 ++++++++-- front/src/js/controllers/indexCtrl.js | 15 ++++++++++-- front/src/main.html | 26 ++++++++++++++++---- front/src/views/about.html | 9 ++++++- front/src/views/dashboard.html | 21 ++++++++++++++-- front/src/views/index.html | 5 +++- lib/server/controllers/frontController.js | 14 ++++------- 9 files changed, 99 insertions(+), 33 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index d21c2cb..1f0ad14 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -12,26 +12,29 @@ module.exports = function(grunt) { font: { icons: { - src: ['app/public/fonts/svg-icons/*.svg'], - destCss: 'app/public/styles/less/icons.less', - destFonts: 'app/public/fonts/icons.woff', + src: ['front/src/fonts/svg-icons/*.svg'], + destCss: 'front/src/less/icons.less', + destFonts: 'front/src/fonts/icons.woff', // Optional: Custom routing of font filepaths for CSS cssRouter: function (fontpath) { var pathArray = fontpath.split('/'); var fileName = pathArray[pathArray.length - 1]; - return '/public/fonts/' + fileName; + return '/front/fonts/' + fileName; } } }, less: { all: { - files: { - 'app/public/styles/main.css': [ 'app/public/styles/less/main.less' ], - 'app/public/styles/index.css': [ 'app/public/styles/less/index.less' ], - 'app/public/styles/launchTest.css': [ 'app/public/styles/less/launchTest.less' ], - 'app/public/styles/results.css': [ 'app/public/styles/less/results.less' ] - } + files: [ + { + expand: true, + cwd: 'front/src/less/', + src: ['**/*.less'], + dest: 'front/src/css/', + ext: '.css' + } + ] } }, jshint: { @@ -51,6 +54,9 @@ module.exports = function(grunt) { icons: { src: ['tmp'] }, + dev: { + src: ['front/src/css'] + }, coverage: { src: ['coverage/'] } @@ -166,7 +172,8 @@ module.exports = function(grunt) { ]); grunt.registerTask('dev', [ - 'express:dev' + 'clean:dev', + 'less' ]); grunt.registerTask('test', [ diff --git a/app/public/scripts/app.js b/app/public/scripts/app.js index 3b54686..ec4f9b9 100644 --- a/app/public/scripts/app.js +++ b/app/public/scripts/app.js @@ -1,5 +1,6 @@ angular.module('YellowLabTools', [ 'Results', + 'Runs', 'ngModal', 'ShowOffendersDirective' ]); diff --git a/front/src/js/app.js b/front/src/js/app.js index aaea1b7..47c3210 100644 --- a/front/src/js/app.js +++ b/front/src/js/app.js @@ -2,7 +2,11 @@ var yltApp = angular.module('YellowLabTools', [ 'ngRoute', 'indexCtrl', 'aboutCtrl', - 'dashboardCtrl' + 'dashboardCtrl', + 'queueCtrl', + 'runsFactory', + 'resultsFactory', + 'gradeDirective' ]); yltApp.config(['$routeProvider', '$locationProvider', @@ -12,11 +16,15 @@ yltApp.config(['$routeProvider', '$locationProvider', templateUrl: 'front/views/index.html', controller: 'IndexCtrl' }). + when('/queue/:runId', { + templateUrl: 'front/views/queue.html', + controller: 'QueueCtrl' + }). when('/about', { templateUrl: 'front/views/about.html', controller: 'AboutCtrl' }). - when('/results/:runId', { + when('/result/:runId', { templateUrl: 'front/views/dashboard.html', controller: 'DashboardCtrl' }). diff --git a/front/src/js/controllers/indexCtrl.js b/front/src/js/controllers/indexCtrl.js index d308850..6b74e1c 100644 --- a/front/src/js/controllers/indexCtrl.js +++ b/front/src/js/controllers/indexCtrl.js @@ -1,5 +1,16 @@ var indexCtrl = angular.module('indexCtrl', []); -indexCtrl.controller('IndexCtrl', ['$scope', function($scope) { - $scope.toto = "Achraf"; +indexCtrl.controller('IndexCtrl', ['$scope', '$location', 'Runs', function($scope, $location, Runs) { + $scope.launchTest = function() { + if ($scope.url) { + Runs.save({ + url: $scope.url, + waitForResponse: false + }, function(data) { + console.log(data); + $location.path('/queue/' + data.runId); + }); + + } + } }]); \ No newline at end of file diff --git a/front/src/main.html b/front/src/main.html index 5142b30..d6cddde 100644 --- a/front/src/main.html +++ b/front/src/main.html @@ -1,7 +1,12 @@ - index template + + Yellow Lab Tools + + + + @@ -9,11 +14,22 @@ + + + + + - -
hello yellowlab, body
- + + +
+ - \ No newline at end of file + + + + diff --git a/front/src/views/about.html b/front/src/views/about.html index d0f7199..3016791 100644 --- a/front/src/views/about.html +++ b/front/src/views/about.html @@ -1 +1,8 @@ -{{about}} \ No newline at end of file + + + + + + \ No newline at end of file diff --git a/front/src/views/dashboard.html b/front/src/views/dashboard.html index 29fb6e9..c128f64 100644 --- a/front/src/views/dashboard.html +++ b/front/src/views/dashboard.html @@ -1,2 +1,19 @@ -
{{dashboard}}
-
{{runId}}
\ No newline at end of file +
+

Grades

+ +
+
+ +
{{category.label}}
+
+
+
+
+
{{rule.policy.label}}
+
{{rule.value}}
+
+
+
+
+
+
\ No newline at end of file diff --git a/front/src/views/index.html b/front/src/views/index.html index b475959..4a6df0c 100644 --- a/front/src/views/index.html +++ b/front/src/views/index.html @@ -1 +1,4 @@ -this is a html index by {{toto}} \ No newline at end of file +

Free online test to help speeding up heavy web pages

+ + +

(This is a BETA, your feedback is more than welcome)

\ No newline at end of file diff --git a/lib/server/controllers/frontController.js b/lib/server/controllers/frontController.js index d13ea6d..25b84cc 100644 --- a/lib/server/controllers/frontController.js +++ b/lib/server/controllers/frontController.js @@ -3,17 +3,13 @@ var express = require('express'); var FrontController = function(app) { 'use strict'; - - app.get('/', function(req, res) { - res.sendFile(path.join(__dirname, '../../../front/src/main.html')); - }); - app.get('/about', function(req, res) { - res.sendFile(path.join(__dirname, '../../../front/src/main.html')); - }); + var routes = ['/', '/about', '/result/:runId', '/queue/:runId']; - app.get('/results/:runId', function(req, res) { - res.sendFile(path.join(__dirname, '../../../front/src/main.html')); + routes.forEach(function(route) { + app.get(route, function(req, res) { + res.sendFile(path.join(__dirname, '../../../front/src/main.html')); + }); }); app.use('/front', express.static(path.join(__dirname, '../../../front/src'))); From c4a7b1f129b4856597c2aa7bfd473ae32f5d6050 Mon Sep 17 00:00:00 2001 From: achrafbenyounes Date: Thu, 18 Dec 2014 18:02:39 +0100 Subject: [PATCH 20/98] Enhancing index, view and dashboard ui --- front/src/css/dashboard.css | 479 +++++++++++++++ front/src/css/icons.css | 6 + front/src/css/index.css | 36 ++ front/src/css/main.css | 139 +++++ front/src/css/queue.css | 25 + front/src/fonts/icons.woff | Bin 0 -> 2140 bytes front/src/fonts/svg-icons/arrow-left3.svg | 1 + front/src/fonts/svg-icons/bars.svg | 1 + front/src/fonts/svg-icons/eye.svg | 1 + front/src/fonts/svg-icons/lab.svg | 1 + front/src/fonts/svg-icons/list.svg | 1 + front/src/fonts/svg-icons/question.svg | 1 + front/src/fonts/svg-icons/warning.svg | 1 + front/src/js/controllers/queueCtrl.js | 23 + front/src/js/directives/gradeDirective.js | 34 ++ front/src/js/models/runsFactory.js | 7 + front/src/less/dashboard.less | 507 ++++++++++++++++ front/src/less/icons.less | 56 ++ front/src/less/index.less | 38 ++ front/src/less/main.less | 66 +++ front/src/less/queue.less | 19 + front/src/views/queue.html | 8 + src | 672 ++++++++++++++++++++++ 23 files changed, 2122 insertions(+) create mode 100644 front/src/css/dashboard.css create mode 100644 front/src/css/icons.css create mode 100644 front/src/css/index.css create mode 100644 front/src/css/main.css create mode 100644 front/src/css/queue.css create mode 100644 front/src/fonts/icons.woff create mode 100644 front/src/fonts/svg-icons/arrow-left3.svg create mode 100644 front/src/fonts/svg-icons/bars.svg create mode 100644 front/src/fonts/svg-icons/eye.svg create mode 100644 front/src/fonts/svg-icons/lab.svg create mode 100644 front/src/fonts/svg-icons/list.svg create mode 100644 front/src/fonts/svg-icons/question.svg create mode 100644 front/src/fonts/svg-icons/warning.svg create mode 100644 front/src/js/controllers/queueCtrl.js create mode 100644 front/src/js/directives/gradeDirective.js create mode 100644 front/src/js/models/runsFactory.js create mode 100644 front/src/less/dashboard.less create mode 100644 front/src/less/icons.less create mode 100644 front/src/less/index.less create mode 100644 front/src/less/main.less create mode 100644 front/src/less/queue.less create mode 100644 front/src/views/queue.html create mode 100644 src diff --git a/front/src/css/dashboard.css b/front/src/css/dashboard.css new file mode 100644 index 0000000..8992c43 --- /dev/null +++ b/front/src/css/dashboard.css @@ -0,0 +1,479 @@ +/* Timeline colors, related to Window Performances */ +.resultsMenu { + margin-top: 2em; +} +.resultsMenu .menuItem { + display: inline-block; + margin: 1em; + width: 8em; + height: 7em; + color: #fff; + border: 3px solid #fff; + border-radius: 0.5em; + cursor: pointer; + text-decoration: none; +} +.resultsMenu .back { + color: #5e2846; + border-color: #5e2846; +} +.resultsMenu .menuItem div { + padding-top: 0.5em; + font-size: 3em; +} +.resultsMenu .active, +.resultsMenu .menuItem.active:hover { + color: #ffa319; + border-color: #ffa319; +} +.resultsMenu .menuItem:hover { + color: #ffa319; +} +.resultsMenu span { + position: relative; + top: 0.5em; +} +.testedUrl { + color: inherit; +} +h4 { + margin-bottom: 0.5em; +} +.summary, +.metrics, +.execution { + margin-top: 2em; + padding: 1em; + background: #fff; + color: #000; + border-radius: 0.5em; + text-align: left; +} +.notations { + display: table; + width: 90%; + margin: 0 10%; + border-spacing: 1em; +} +.notations > div { + display: table-row; +} +.notations > div > div { + display: table-cell; + height: 2.5em; + vertical-align: middle; +} +.notations .notation { + font-weight: bold; + text-align: center; +} +.notations .criteria { + font-weight: normal; +} +.notations .A, +.notations .B, +.notations .C, +.notations .D, +.notations .E, +.notations .F, +.notations .NA { + width: 2.5em; + font-size: 2em; + text-align: center; + border-radius: 0.5em; + font-weight: bold; +} +.notations .grade .A, +.notations .grade .B, +.notations .grade .C, +.notations .grade .D, +.notations .grade .E, +.notations .grade .F, +.notations .grade .NA { + width: 1em; + height: 1em; + font-size: 1em; + color: transparent; + position: relative; + top: 0.15em; +} +.notations .A { + /* green */ + background: #00DB61; +} +.notations .B { + /* green */ + background: #CAD600; +} +.notations .C { + /* yellow */ + background: #FFD119; +} +.notations .D { + /* orange */ + background: #FFA319; +} +.notations .E { + /* red */ + background: #FF6600; +} +.notations .F { + /* red */ + background: #FF1919; +} +.notations .NA { + /* Non applicable */ + background: #CCC; +} +.notations .icon-eye { + color: #9c4274; + cursor: pointer; +} +.notations .criteria .table { + width: 75%; +} +.notations .criteria .label { + width: 70%; +} +.notations .criteria .result { + width: 20%; + font-weight: bold; + white-space: nowrap; + text-align: center; +} +.notations .warning .label, +.notations .warning .result { + color: #FF1919; +} +.notations .criteria .info { + width: 10%; + text-align: center; +} +.notations .criteria .icon-question { + color: #f1c40f; + cursor: pointer; +} +.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 .details .icon-warning { + cursor: pointer; +} +.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; +} +.table .icon-warning { + color: #e74c3c; +} +/**** NgModal popin (have a look inside bower_components) ****/ +.ng-modal { + position: fixed; + z-index: 9999; + top: 0; + left: 0; + width: 100%; + height: 100%; + text-align: left; +} +.ng-modal-overlay { + position: absolute; + z-index: 9999; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: #000; + opacity: 0.5; +} +.ng-modal-dialog { + z-index: 10000; + position: absolute; + top: 50%; + left: 50%; + width: 50%; + transform: translate(-50%, -50%); + -webkit-transform: translate(-50%, -50%); + background-color: #fff; + padding: 10px; + border: 3px solid #f1c40f; + border-radius: 0.5em; + color: #000; +} +.ng-modal-dialog-content { + overflow-x: hidden; + overflow-y: scroll; + word-wrap: break-word; + max-height: 20em; + font-weight: normal; + white-space: normal; +} +.ng-modal-close { + position: absolute; + top: 3px; + right: 5px; + cursor: pointer; + font-size: 120%; + padding: 5px; + display: inline-block; +} +.ng-modal-close-x { + font-weight: bold; + font-family: Arial, sans-serif; +} +.ng-modal-title { + font-weight: bold; + font-size: 1.5em; + display: block; + margin-bottom: 10px; + padding-bottom: 7px; + border-bottom: solid 1px #999; +} diff --git a/front/src/css/icons.css b/front/src/css/icons.css new file mode 100644 index 0000000..6ebf26b --- /dev/null +++ b/front/src/css/icons.css @@ -0,0 +1,6 @@ +@font-face { + font-family: "fontsmith-icons"; + src: url("/front/fonts/icons.woff") format("woff"); + font-weight: normal; + font-style: normal; +} diff --git a/front/src/css/index.css b/front/src/css/index.css new file mode 100644 index 0000000..d22b867 --- /dev/null +++ b/front/src/css/index.css @@ -0,0 +1,36 @@ +.promess { + padding: 0em 2em 3em; + font-weight: normal; + font-size: 1.2em; +} +.url { + width: 50%; +} +.launchBtn { + background: #e74c3c; + color: #fff; +} +.launchBtn.disabled { + background: #deaca6; +} +input[type=submit], +input.url { + padding: 0 0.5em; + margin: 0.5em; + font-size: 1.2em; + height: 2em; + border: 0 solid; + border-radius: 0.5em; + box-shadow: 0.1em 0.2em 0 0 #5e2846; + outline: none; +} +input[type=submit]:hover { + color: #ddd; +} +input[type=submit].clicked { + color: #ddd; + position: relative; + left: 0.1em; + top: 0.2em; + box-shadow: none; +} diff --git a/front/src/css/main.css b/front/src/css/main.css new file mode 100644 index 0000000..1ef1db3 --- /dev/null +++ b/front/src/css/main.css @@ -0,0 +1,139 @@ +@font-face { + font-family: "fontsmith-icons"; + src: url("/front/fonts/icons.woff") format("woff"); + font-weight: normal; + font-style: normal; +} +html { + margin: 100px 50px; +} +body { + margin: 0 auto; + max-width: 1280px; + background: #9c4274; + color: #fff; + font-size: 16px; + text-align: center; +} +body, +input[type=submit], +input[type=text], +input[type=url], +input[type=number], +button { + font-family: 'Century Gothic', helvetica, arial, sans-serif; +} +input[type=submit] { + cursor: pointer; +} +h1 span { + display: inline-block; + height: 1em; + width: 1em; + color: #ffa319; +} +.footer { + padding: 3em; + color: #4e1836; +} +.footer a { + color: inherit; +} +.footer .star { + font-weight: bold; +} +.footer .star span { + font-size: 1.2em; +} +/* Icons */ +.icon-lab { + font-family: "fontsmith-icons"; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; +} +.icon-lab:before { + content: "\e003"; +} +.icon-question { + font-family: "fontsmith-icons"; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; +} +.icon-question:before { + content: "\e001"; +} +.icon-warning { + font-family: "fontsmith-icons"; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; +} +.icon-warning:before { + content: "\e000"; +} +.icon-back { + font-family: "fontsmith-icons"; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; +} +.icon-back:before { + content: "\e006"; +} +.icon-summary { + font-family: "fontsmith-icons"; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; +} +.icon-summary:before { + content: "\e002"; +} +.icon-spaghetti { + font-family: "fontsmith-icons"; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; +} +.icon-spaghetti:before { + content: "\e005"; +} +.icon-eye { + font-family: "fontsmith-icons"; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; +} +.icon-eye:before { + content: "\e004"; +} diff --git a/front/src/css/queue.css b/front/src/css/queue.css new file mode 100644 index 0000000..76161b3 --- /dev/null +++ b/front/src/css/queue.css @@ -0,0 +1,25 @@ +.status { + margin-top: 2em; + font-size: 2.5em; +} +@-webkit-keyframes rotating { + from { + -webkit-transform: rotate(0deg); + } + to { + -webkit-transform: rotate(360deg); + } +} +@keyframes rotating { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} +.waiting .icon-lab { + -webkit-animation: rotating 3s linear 0s infinite; + -webkit-transform: translateZ(0); + animation: rotating 3s linear 0s infinite; +} diff --git a/front/src/fonts/icons.woff b/front/src/fonts/icons.woff new file mode 100644 index 0000000000000000000000000000000000000000..dd8c0bd30e55a48a472a6f7085916d3ea822f6b3 GIT binary patch literal 2140 zcmZWrc{r478-K=(nL!w#rb!GZ>)4H@>@}Qn+_4mrQnZ003zOMdKjIFlvI+Y>tu0kmd&QLlE%5 z03aM)wR9mZ65(n29V4b?~=&Cg5? zyAT2Of&P)Z2Lc|z0f;G%a`z2&bH|i4j++-oB{rNpuIkhG9yeG2jjtZ+t?cmdp zq=R@Rr!ql;Jm!v+!t+vd@lgb9$=kATucU=DhVa;h(nPh}0a*L42HbHq+d04Ck?{G6 z)|)sTM!TNg>o=*HV3De-`Wa zESI9B(fUSvh7*AiyxNsUOvnxHGf)#93uY3!r)ErxwFP8T!gFA-=ZTe0;5D}~WV?#0 zZTr$bf^|>p$WGpUE%`WBnO1)G$X-Q(v`+!4F^(UlTt4pAzq{{RI9cQ&DUsdTV74SZ z>OBhkHYr=_;f?AD8Jc$PP+2zjp0YM-nAs!tzU>kMC2s1j$SAT@6!<26a{%L9O(D4{ zEMPuotwbvX)r>QfBTmMLSnsxJXT-#fTtJsB(4PgOF7_9a%f zUQ*KuUg%kGR}N#uS*F&PKHD?;e5u995zWlF<(JSH%A=5&JG6ww6c0G@9cTJbUfp%J znQL5HPu5pM!fs;Rx+5l>#)OrX8zR~ZMB4_gIt%H=3}qT|Z7SU!wa?H8B;3{Fz6f?5 zzxPU3rSOdNFoiEJF1%~|&aslh-gmV;E-NM?93y}3lI+Z!qYNG_{r)J7=ht7gV&ms-hHc>c6klsBGbSl+M>Si{%oN z2p_OZyCX*@=1T7qA4_YjOA~yZpII2muKiWJZ285tT+V-Dc<(ZqJdv3;so(FsW%OCz zZ;Zk?vi)6lDJyz7DxGC6p`_{4IpKeGw<~gm8vj&Zido(5ww~ddE1Fm9a9(q~mnjk% zc+nc&%M^pl96KDk|ILk@aZakyon&ikBt4{~upL(KAEfG@_n%L=nPOK7IpNoBhujAK zWPDqUsyb-5Jvniu)iW=6uqD^vGDU5&f9=VU&Vr_`Uz*0Q<7tC_q}@J`j3KZ3&Kz7t zj&Z<6H*r=ht9WYJnVVOpzpnTs-9E0U_ ziUq9g$y0xDSn#I2=aGInt=pu!;<6lL!RmsKo9$+oFDp}O@J)x}^5iq$y)4%d@mI6c zUZEHMZbt8XLo<~%nVerQ$wpsaoyBnqFuQf9UuP#T49}0)hh1CVZr<|k^trt;T_3WR z*~8wPXc@8XTr#`b&vLj%44MtOEy#^FwNWQM|A-~$mnjR%hpq1KY$XOvEqyl6&mt)L z{E8XV$REnTMtU)1eL~FK;TuwUweT65xo{B|lEGcvTfxM-RJ9Zi%V>rpn`zOm#!Z|y zy92f5{W|3ygKr6yHGhb3>jl0b2P!8-!-s1DHr#IN!O zJznB^fet=LXVwfCL11omWcZ)w>-B~ftv1$l9vY4-w=K))Hwb6U_!R|ho;8&J9#vlC zcVZRHSVT)uC?DV^2{{E59VHqq#t#h_^h~TdTh0BC0tqW`?{z6FjADW!1fO}g=RM^N zAlYI?JCj1k&%7qYTH;C(26_~;v#QI)#t-5*E_1sJ(_dGGL|q=JJGryj;=A#i2~OSW zRRybM9)9#$k>Xb*T@8K7pPY-6U7uK&1^H;b;I;@5_lVye7F+wDat|<1^2`OZFk{M|GQi4 zDj$|CD+z}`F-PKsq%2{t0Am5Z`Hvrv0F+dE@aR$iCc!?a1_02fM(BqZ#WX!Hpo2-_ zrlg=HNR@XBG*#Yw3}LV>WB~hw<@KBg-a>V>JKdIV=FzGVSu%K==Zb5d<#vhe1Myd` zti#9FPCVY08VkE%f-yfJ&iWE`>NuY$t1~wmqyE>)-n+%N%`5kNArB}1uTmXNc0Bfa z%+yQBOJ$0t-&UwkJ)eI`Rmd-$Q#{n5v~pnd?IjJ*&Gx|fvquz|7S+Mbl$m&L<~$3U zc60cO1+VNk-yhmHXKM~*`xyPEzPA1%RgLIZb8e-YXcW=MKa=(OBvpF7)K&7d;mLD+ TP(eTH^7DNL@t_}xIDq&Y7`uu9 literal 0 HcmV?d00001 diff --git a/front/src/fonts/svg-icons/arrow-left3.svg b/front/src/fonts/svg-icons/arrow-left3.svg new file mode 100644 index 0000000..d96cc70 --- /dev/null +++ b/front/src/fonts/svg-icons/arrow-left3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/fonts/svg-icons/bars.svg b/front/src/fonts/svg-icons/bars.svg new file mode 100644 index 0000000..2e7adca --- /dev/null +++ b/front/src/fonts/svg-icons/bars.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/fonts/svg-icons/eye.svg b/front/src/fonts/svg-icons/eye.svg new file mode 100644 index 0000000..59ff3c2 --- /dev/null +++ b/front/src/fonts/svg-icons/eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/fonts/svg-icons/lab.svg b/front/src/fonts/svg-icons/lab.svg new file mode 100644 index 0000000..28b3aa8 --- /dev/null +++ b/front/src/fonts/svg-icons/lab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/fonts/svg-icons/list.svg b/front/src/fonts/svg-icons/list.svg new file mode 100644 index 0000000..403a80a --- /dev/null +++ b/front/src/fonts/svg-icons/list.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/fonts/svg-icons/question.svg b/front/src/fonts/svg-icons/question.svg new file mode 100644 index 0000000..49e9fc3 --- /dev/null +++ b/front/src/fonts/svg-icons/question.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/fonts/svg-icons/warning.svg b/front/src/fonts/svg-icons/warning.svg new file mode 100644 index 0000000..85f8300 --- /dev/null +++ b/front/src/fonts/svg-icons/warning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/js/controllers/queueCtrl.js b/front/src/js/controllers/queueCtrl.js new file mode 100644 index 0000000..45b8bd4 --- /dev/null +++ b/front/src/js/controllers/queueCtrl.js @@ -0,0 +1,23 @@ +var queueCtrl = angular.module('queueCtrl', ['runsFactory']); + +queueCtrl.controller('QueueCtrl', ['$scope', '$routeParams', '$location', 'Runs', function($scope, $routeParams, $location, Runs) { + $scope.runId = $routeParams.runId; + + function getRunStatus () { + Runs.get({runId: $scope.runId}, function(data) { + $scope.status = data.status; + if (data.status.statusCode === 'running' || data.status.statusCode === 'awaiting') { + // Retrying in 2 seconds + setTimeout(getRunStatus, 2000); + } else if (data.status.statusCode === 'complete') { + $location.path('/result/' + $scope.runId); + } else { + // Handled by the view + } + }); + } + + getRunStatus(); +}]); + + \ No newline at end of file diff --git a/front/src/js/directives/gradeDirective.js b/front/src/js/directives/gradeDirective.js new file mode 100644 index 0000000..f79f2cc --- /dev/null +++ b/front/src/js/directives/gradeDirective.js @@ -0,0 +1,34 @@ +var gradeDirective = angular.module('gradeDirective', []); + +gradeDirective.directive('grade', function() { + + return { + restrict: 'E', + scope: { + score: '=score' + }, + template: '
{{getGrade(score)}}
', + replace: true, + controller : function($scope) { + $scope.getGrade = function(score) { + console.log(score); + if (score >= 85) { + return 'A'; + } + if (score >= 65) { + return 'B'; + } + if (score >= 45) { + return 'C'; + } + if (score >= 30) { + return 'D'; + } + if (score >= 15) { + return 'E'; + } + return 'F'; + } + } + }; +}); \ No newline at end of file diff --git a/front/src/js/models/runsFactory.js b/front/src/js/models/runsFactory.js new file mode 100644 index 0000000..8f4bb8e --- /dev/null +++ b/front/src/js/models/runsFactory.js @@ -0,0 +1,7 @@ +var runsFactory = angular.module('runsFactory', ['ngResource']); + +runsFactory.factory('Runs', ['$resource', function($resource) { + return $resource('/api/runs/:runId', { + + }); +}]); \ No newline at end of file diff --git a/front/src/less/dashboard.less b/front/src/less/dashboard.less new file mode 100644 index 0000000..74a9931 --- /dev/null +++ b/front/src/less/dashboard.less @@ -0,0 +1,507 @@ + +/* 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; +} + +.resultsMenu .menuItem { + display: inline-block; + margin: 1em; + width: 8em; + height: 7em; + color: #fff; + border: 3px solid #fff; + border-radius: 0.5em; + cursor: pointer; + text-decoration: none; +} +.resultsMenu .back { + color: #5e2846; + border-color: #5e2846; +} +.resultsMenu .menuItem div { + padding-top: 0.5em; + font-size: 3em; +} +.resultsMenu .active, .resultsMenu .menuItem.active:hover { + color: #ffa319; + border-color: #ffa319; +} +.resultsMenu .menuItem:hover { + color: #ffa319; +} + +.resultsMenu span { + position: relative; + top: 0.5em; +} + +.testedUrl { + color: inherit; +} + +h4 { + margin-bottom: 0.5em; +} + +.summary, .metrics, .execution { + margin-top: 2em; + padding: 1em; + background: #fff; + color: #000; + border-radius: 0.5em; + text-align: left; +} + +.notations { + display: table; + width: 90%; + margin: 0 10%; + border-spacing: 1em; +} +.notations > div { + display: table-row; +} +.notations > div > div { + display: table-cell; + height: 2.5em; + vertical-align: middle; +} +.notations .notation { + font-weight: bold; + text-align: center; +} +.notations .criteria { + font-weight: normal; +} +.A, .B, .C, .D, .E, .F, .NA { + .notations & { + width: 2.5em; + font-size: 2em; + text-align: center; + border-radius: 0.5em; + font-weight: bold; + } + .notations .grade & { + width: 1em; + height: 1em; + font-size: 1em; + color: transparent; + position: relative; + top: 0.15em; + } +} +.notations .A { + /* green */ + background: #00DB61; +} +.notations .B { + /* green */ + background: #CAD600; +} +.notations .C { + /* yellow */ + background: #FFD119; +} +.notations .D { + /* orange */ + background: #FFA319; +} +.notations .E { + /* red */ + background: #FF6600; +} +.notations .F { + /* red */ + background: #FF1919; +} +.notations .NA { + /* Non applicable */ + background: #CCC; +} +.notations .icon-eye { + color: #9c4274; + cursor: pointer; +} + +.notations .criteria .table { + width: 75%; +} +.notations .criteria .label { + width: 70%; +} +.notations .criteria .result { + width: 20%; + font-weight: bold; + white-space: nowrap; + text-align: center; +} +.notations .warning .label, .notations .warning .result { + color: #FF1919; +} +.notations .criteria .info { + width: 10%; + text-align: center; +} +.notations .criteria .icon-question { + color: #f1c40f; + cursor: pointer; +} + + +.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 .details .icon-warning { + cursor: pointer; +} + +.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; +} + +.table .icon-warning { + color: #e74c3c; +} + + +/**** NgModal popin (have a look inside bower_components) ****/ +.ng-modal { + position: fixed; + z-index: 9999; + top: 0; + left: 0; + width: 100%; + height: 100%; + text-align: left; +} +.ng-modal-overlay { + position: absolute; + z-index: 9999; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: #000; + opacity: 0.5; +} +.ng-modal-dialog { + z-index: 10000; + position: absolute; + top: 50%; + left: 50%; + width: 50%; + transform: translate(-50%, -50%); + -webkit-transform: translate(-50%, -50%); + background-color: #fff; + padding: 10px; + border: 3px solid #f1c40f; + border-radius: 0.5em; + color: #000; +} +.ng-modal-dialog-content { + overflow-x: hidden; + overflow-y: scroll; + word-wrap: break-word; + max-height: 20em; + font-weight: normal; + white-space: normal; +} +.ng-modal-close { + position: absolute; + top: 3px; + right: 5px; + cursor: pointer; + font-size: 120%; + padding: 5px; + display: inline-block; +} +.ng-modal-close-x { + font-weight: bold; + font-family: Arial, sans-serif; +} +.ng-modal-title { + font-weight: bold; + font-size: 1.5em; + display: block; + margin-bottom: 10px; + padding-bottom: 7px; + border-bottom: solid 1px #999; +} diff --git a/front/src/less/icons.less b/front/src/less/icons.less new file mode 100644 index 0000000..8a2c12a --- /dev/null +++ b/front/src/less/icons.less @@ -0,0 +1,56 @@ +@arrow-left3-font-family: "fontsmith-icons"; +@arrow-left3-value: "\e006"; +@arrow-left3: '"fontsmith-icons"' '"\\e006"'; +@lab-font-family: "fontsmith-icons"; +@lab-value: "\e003"; +@lab: '"fontsmith-icons"' '"\\e003"'; +@warning-font-family: "fontsmith-icons"; +@warning-value: "\e000"; +@warning: '"fontsmith-icons"' '"\\e000"'; +@list-font-family: "fontsmith-icons"; +@list-value: "\e002"; +@list: '"fontsmith-icons"' '"\\e002"'; +@eye-font-family: "fontsmith-icons"; +@eye-value: "\e004"; +@eye: '"fontsmith-icons"' '"\\e004"'; +@bars-font-family: "fontsmith-icons"; +@bars-value: "\e005"; +@bars: '"fontsmith-icons"' '"\\e005"'; +@question-font-family: "fontsmith-icons"; +@question-value: "\e001"; +@question: '"fontsmith-icons"' '"\\e001"'; + +.icon-font-family(@char) { + font-family: ~`@{char}[0]`; +} + +.icon-font() { + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; +} + +.icon-content(@char) { + content: ~`@{char}[1]`; +} + +.icon(@char) { + .icon-font-family(@char); + .icon-font(); + + &:before { + .icon-content(@char); + } +} + +@font-face { + font-family: "fontsmith-icons"; + src:url("/front/fonts/icons.woff") format("woff"), + ; + font-weight: normal; + font-style: normal; +} \ No newline at end of file diff --git a/front/src/less/index.less b/front/src/less/index.less new file mode 100644 index 0000000..5bbd573 --- /dev/null +++ b/front/src/less/index.less @@ -0,0 +1,38 @@ +.promess { + padding: 0em 2em 3em; + font-weight: normal; + font-size: 1.2em; +} + +.url { + width: 50%; +} + +.launchBtn { + background: #e74c3c; + color: #fff; + &.disabled { + background: #deaca6; + } +} + +input[type=submit], input.url { + padding: 0 0.5em; + margin: 0.5em; + font-size: 1.2em; + height: 2em; + border: 0 solid; + border-radius: 0.5em; + box-shadow: 0.1em 0.2em 0 0 #5e2846; + outline: none; +} +input[type=submit]:hover { + color: #ddd; +} +input[type=submit].clicked { + color: #ddd; + position: relative; + left: 0.1em; + top: 0.2em; + box-shadow: none; +} \ No newline at end of file diff --git a/front/src/less/main.less b/front/src/less/main.less new file mode 100644 index 0000000..79f6dd2 --- /dev/null +++ b/front/src/less/main.less @@ -0,0 +1,66 @@ +@import "icons.less"; + +html { + margin: 100px 50px; +} + +body { + margin: 0 auto; + max-width: 1280px; + background: #9c4274; + color: #fff; + font-size: 16px; + text-align: center; +} + +body, input[type=submit], input[type=text], input[type=url], input[type=number], button { + font-family: 'Century Gothic', helvetica, arial, sans-serif; +} + +input[type=submit] { + cursor: pointer; +} + +h1 span { + display: inline-block; + height: 1em; + width: 1em; + color: #ffa319; +} + +.footer { + padding: 3em; + color: #4e1836; + a { + color: inherit; + } + .star { + font-weight: bold; + span { + font-size: 1.2em; + } + } +} + +/* Icons */ +.icon-lab { + .icon(@lab); +} +.icon-question { + .icon(@question); +} +.icon-warning { + .icon(@warning); +} +.icon-back { + .icon(@arrow-left3); +} +.icon-summary { + .icon(@list); +} +.icon-spaghetti { + .icon(@bars); +} +.icon-eye { + .icon(@eye); +} \ No newline at end of file diff --git a/front/src/less/queue.less b/front/src/less/queue.less new file mode 100644 index 0000000..57a52f6 --- /dev/null +++ b/front/src/less/queue.less @@ -0,0 +1,19 @@ +.status { + margin-top: 2em; + font-size: 2.5em; +} + +@-webkit-keyframes rotating { + from { -webkit-transform: rotate(0deg); } + to { -webkit-transform: rotate(360deg); } +} +@keyframes rotating { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +.waiting .icon-lab { + -webkit-animation: rotating 3s linear 0s infinite; + -webkit-transform: translateZ(0); + animation: rotating 3s linear 0s infinite; +} \ No newline at end of file diff --git a/front/src/views/queue.html b/front/src/views/queue.html new file mode 100644 index 0000000..dcd631b --- /dev/null +++ b/front/src/views/queue.html @@ -0,0 +1,8 @@ +
Test failed
+
+ +
+
Test is running...
+
Test complete
\ No newline at end of file diff --git a/src b/src new file mode 100644 index 0000000..36a8a55 --- /dev/null +++ b/src @@ -0,0 +1,672 @@ +/* Timeline colors, related to Window Performances */ +.resultsMenu { + margin-top: 2em; +} +.resultsMenu .menuItem { + display: inline-block; + margin: 1em; + width: 8em; + height: 7em; + color: #fff; + border: 3px solid #fff; + border-radius: 0.5em; + cursor: pointer; + text-decoration: none; +} +.resultsMenu .back { + color: #5e2846; + border-color: #5e2846; +} +.resultsMenu .menuItem div { + padding-top: 0.5em; + font-size: 3em; +} +.resultsMenu .active, +.resultsMenu .menuItem.active:hover { + color: #ffa319; + border-color: #ffa319; +} +.resultsMenu .menuItem:hover { + color: #ffa319; +} +.resultsMenu span { + position: relative; + top: 0.5em; +} +.testedUrl { + color: inherit; +} +h4 { + margin-bottom: 0.5em; +} +.summary, +.metrics, +.execution { + margin-top: 2em; + padding: 1em; + background: #fff; + color: #000; + border-radius: 0.5em; + text-align: left; +} +.notations { + display: table; + width: 90%; + margin: 0 10%; + border-spacing: 1em; +} +.notations > div { + display: table-row; +} +.notations > div > div { + display: table-cell; + height: 2.5em; + vertical-align: middle; +} +.notations .notation { + font-weight: bold; + text-align: center; +} +.notations .criteria { + font-weight: normal; +} +.notations .A, +.notations .B, +.notations .C, +.notations .D, +.notations .E, +.notations .F, +.notations .NA { + width: 2.5em; + font-size: 2em; + text-align: center; + border-radius: 0.5em; + font-weight: bold; +} +.notations .A { + /* green */ + background: #00DB61; +} +.notations .B { + /* green */ + background: #CAD63D; +} +.notations .C { + /* yellow */ + background: #FFD119; +} +.notations .D { + /* orange */ + background: #FFA319; +} +.notations .E { + /* red */ + background: #FF6600; +} +.notations .F { + /* red */ + background: #FF1919; +} +.notations .NA { + /* Non applicable */ + background: #CCC; +} +.notations .icon-eye { + color: #9c4274; + cursor: pointer; +} +.notations .criteria .table { + width: 75%; +} +.notations .criteria .label { + width: 70%; +} +.notations .criteria .result { + width: 20%; + font-weight: bold; + white-space: nowrap; + text-align: center; +} +.notations .warning .label, +.notations .warning .result { + color: #FF1919; +} +.notations .criteria .info { + width: 10%; + text-align: center; +} +.notations .criteria .icon-question { + color: #f1c40f; + cursor: pointer; +} +.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 .details .icon-warning { + cursor: pointer; +} +.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; +} +.table .icon-warning { + color: #e74c3c; +} +/**** NgModal popin (have a look inside bower_components) ****/ +.ng-modal { + position: fixed; + z-index: 9999; + top: 0; + left: 0; + width: 100%; + height: 100%; + text-align: left; +} +.ng-modal-overlay { + position: absolute; + z-index: 9999; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: #000; + opacity: 0.5; +} +.ng-modal-dialog { + z-index: 10000; + position: absolute; + top: 50%; + left: 50%; + width: 50%; + transform: translate(-50%, -50%); + -webkit-transform: translate(-50%, -50%); + background-color: #fff; + padding: 10px; + border: 3px solid #f1c40f; + border-radius: 0.5em; + color: #000; +} +.ng-modal-dialog-content { + overflow-x: hidden; + overflow-y: scroll; + word-wrap: break-word; + max-height: 20em; + font-weight: normal; + white-space: normal; +} +.ng-modal-close { + position: absolute; + top: 3px; + right: 5px; + cursor: pointer; + font-size: 120%; + padding: 5px; + display: inline-block; +} +.ng-modal-close-x { + font-weight: bold; + font-family: Arial, sans-serif; +} +.ng-modal-title { + font-weight: bold; + font-size: 1.5em; + display: block; + margin-bottom: 10px; + padding-bottom: 7px; + border-bottom: solid 1px #999; +} + +@font-face { + font-family: "fontsmith-icons"; + src: url("/public/fonts/icons.woff") format("woff"); + font-weight: normal; + font-style: normal; +} + +.promess { + padding: 0em 2em 3em; + font-weight: normal; + font-size: 1.2em; +} +.url { + width: 50%; +} +.launchBtn { + background: #e74c3c; + color: #fff; +} +input[type=submit], +input.url { + padding: 0 0.5em; + margin: 0.5em; + font-size: 1.2em; + height: 2em; + border: 0 solid; + border-radius: 0.5em; + box-shadow: 0.1em 0.2em 0 0 #5e2846; + outline: none; +} +input[type=submit]:hover { + color: #ddd; +} +input[type=submit].clicked { + color: #ddd; + position: relative; + left: 0.1em; + top: 0.2em; + box-shadow: none; +} + +#status { + margin-top: 2em; + font-size: 2.5em; +} +@-webkit-keyframes rotating { + from { + -webkit-transform: rotate(0deg); + } + to { + -webkit-transform: rotate(360deg); + } +} +@keyframes rotating { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} +.waiting .icon-lab { + -webkit-animation: rotating 3s linear 0s infinite; + -webkit-transform: translateZ(0); + animation: rotating 3s linear 0s infinite; +} + +@font-face { + font-family: "fontsmith-icons"; + src: url("/public/fonts/icons.woff") format("woff"); + font-weight: normal; + font-style: normal; +} +html { + margin: 100px 50px; +} +body { + margin: 0 auto; + max-width: 1280px; + background: #9c4274; + color: #fff; + font-size: 16px; + text-align: center; +} +body, +input[type=submit], +input[type=text], +input[type=url], +input[type=number], +button { + font-family: 'Century Gothic', helvetica, arial, sans-serif; +} +input[type=submit] { + cursor: pointer; +} +h1 span { + display: inline-block; + height: 1em; + width: 1em; + color: #ffa319; +} +.footer { + padding: 3em; + color: #4e1836; +} +.footer a { + color: inherit; +} +.footer .star { + font-weight: bold; +} +.footer .star span { + font-size: 1.2em; +} +/* Icons */ +.icon-lab { + font-family: "fontsmith-icons"; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; +} +.icon-lab:before { + content: "\e003"; +} +.icon-question { + font-family: "fontsmith-icons"; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; +} +.icon-question:before { + content: "\e001"; +} +.icon-warning { + font-family: "fontsmith-icons"; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; +} +.icon-warning:before { + content: "\e000"; +} +.icon-back { + font-family: "fontsmith-icons"; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; +} +.icon-back:before { + content: "\e006"; +} +.icon-summary { + font-family: "fontsmith-icons"; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; +} +.icon-summary:before { + content: "\e002"; +} +.icon-spaghetti { + font-family: "fontsmith-icons"; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; +} +.icon-spaghetti:before { + content: "\e005"; +} +.icon-eye { + font-family: "fontsmith-icons"; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; +} +.icon-eye:before { + content: "\e004"; +} From 521a81ca1dc618761b0d61ce7ad6b8ac71f6c599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Fri, 19 Dec 2014 14:20:20 +0100 Subject: [PATCH 21/98] Introducing custom score calculations (jQuery version) --- lib/metadata/{policies.json => policies.js} | 71 +++++++++++++---- lib/metadata/scoreProfileGeneric.json | 3 +- lib/rulesChecker.js | 30 ++++++- lib/runner.js | 18 ++--- test/core/customPoliciesTest.js | 87 +++++++++++++++++++++ 5 files changed, 180 insertions(+), 29 deletions(-) rename lib/metadata/{policies.json => policies.js} (90%) create mode 100644 test/core/customPoliciesTest.js diff --git a/lib/metadata/policies.json b/lib/metadata/policies.js similarity index 90% rename from lib/metadata/policies.json rename to lib/metadata/policies.js index c858342..378b89c 100644 --- a/lib/metadata/policies.json +++ b/lib/metadata/policies.js @@ -1,4 +1,6 @@ -{ +var debug = require('debug')('ylt:policies'); + +var policies = { "DOMelementsCount": { "tool": "phantomas", "label": "DOM elements count", @@ -103,24 +105,61 @@ "isBadThreshold": 50, "isAbnormalThreshold": 200 }, - "inBodyDomManipulations": { - "tool": "ylt", - "label": "DOM manipulations in body", - "message": "

This metric counts the number of DOM queries, DOM inserts, binds, etc. made by the JavaScript before the DOMContentLoaded event.

Wait for this event before manipulating the DOM. Do not execute JavaScript in the middle of the BODY as it slows down the construction of the DOM and makes a poor maintainability. This is what i call spaghetti code.

The JS Timeline tab can help you identify what's happening.

", - "isOkThreshold": 10, - "isBadThreshold": 50, - "isAbnormalThreshold": 100 - }, "jQueryVersion": { - "tool": "ylt", "label": "jQuery version", "message": "

Current latest versions of jQuery are 1.11 (with support for old IE versions) and 2.1 (without).

Each new version of jQuery optimizes performances. Do not keep an old version of jQuery. Updating can sometimes break a few things, but it is generally quite easy to fix them up. So don't hesitate.

", - "isOkThreshold": 1, - "isBadThreshold": 2, - "isAbnormalThreshold": 3 + "scoreFn": function(data) { + var differentVersions = data.toolsResults.phantomas.metrics.jQueryDifferentVersions; + + if (differentVersions === 0 || differentVersions > 1) { + // Not applicable + return null; + } else { + var value = data.toolsResults.phantomas.metrics.jQueryVersion; + var score; + + if (value.indexOf('1.11.') === 0 || + value.indexOf('1.12.') === 0 || + value.indexOf('2.1.') === 0 || + value.indexOf('2.2.') === 0 || + value.indexOf('3.0.') === 0) { + score = 100; + } else if (value.indexOf('1.10.') === 0 || + value.indexOf('2.0.') === 0) { + score = 90; + } else if (value.indexOf('1.9.') === 0) { + score = 70; + } else if (value.indexOf('1.8.') === 0) { + score = 50; + } else if (value.indexOf('1.7.') === 0) { + score = 40; + } else if (value.indexOf('1.6.') === 0) { + score = 30; + } else if (value.indexOf('1.5.') === 0) { + score = 20; + } else if (value.indexOf('1.4.') === 0) { + score = 10; + } else if (value.indexOf('1.3.') === 0) { + score = 0; + } else if (value.indexOf('1.2.') === 0) { + score = 0; + } else { + debug('Unknown jQuery version "%s"', value); + return null; + } + + return { + value: value, + score: score, + bad: value < 100, + abnormal: false, + abnormalityScore: 0 + }; + } + } }, "jQueryDifferentVersions": { - "tool": "ylt", + "tool": "phantomas", "label": "Several versions loaded", "message": "

jQuery is a heavy library. You should never load jQuery more than one on the same page.

", "isOkThreshold": 1, @@ -367,4 +406,6 @@ "isBadThreshold": 25, "isAbnormalThreshold": 50 } -} \ No newline at end of file +}; + +module.exports = policies; \ No newline at end of file diff --git a/lib/metadata/scoreProfileGeneric.json b/lib/metadata/scoreProfileGeneric.json index 15bf1e0..02e9d3b 100644 --- a/lib/metadata/scoreProfileGeneric.json +++ b/lib/metadata/scoreProfileGeneric.json @@ -25,8 +25,7 @@ "evalCalls": 1, "documentWriteCalls": 2, "consoleMessages": 0.5, - "globalVariables": 0.5, - "inBodyDomManipulations": 1 + "globalVariables": 0.5 } }, "jQueryVersion": { diff --git a/lib/rulesChecker.js b/lib/rulesChecker.js index 9033f0e..f6f6d65 100644 --- a/lib/rulesChecker.js +++ b/lib/rulesChecker.js @@ -12,12 +12,14 @@ var RulesChecker = function() { for (var metricName in policies) { var policy = policies[metricName]; + var rule; - if (data.toolsResults[policy.tool] && + if (policy.tool && + data.toolsResults[policy.tool] && data.toolsResults[policy.tool].metrics && (data.toolsResults[policy.tool].metrics[metricName] || data.toolsResults[policy.tool].metrics[metricName] === 0)) { - - var rule = { + + rule = { value: data.toolsResults[policy.tool].metrics[metricName], policy: policy }; @@ -40,7 +42,29 @@ var RulesChecker = function() { rule.abnormalityScore = Math.min(Math.round(abnormalityScore), 0); results[metricName] = rule; + debug('Metric %s calculated. Score: %d', metricName, rule.score); + + } else if (policy.scoreFn) { + + debug('Custom score function for %s', metricName); + + // Custom score function + rule = policy.scoreFn(data); + + // Check returned values (if the result is null, just don't save) + if (rule) { + rule.policy = { + label: policy.label, + message: policy.message + }; + + results[metricName] = rule; + debug('Metric %s calculated. Score: %d', metricName, rule.score); + } else { + debug('Metric %s is null. Ignored.', metricName); + } + } else { debug('Metric %s not found for tool %s', metricName, policy.tool); diff --git a/lib/runner.js b/lib/runner.js index caa81a4..e48a306 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -21,11 +21,18 @@ var Runner = function(params) { phantomasWrapper.execute(data).then(function(phantomasResults) { data.toolsResults.phantomas = phantomasResults; - // Other tools go there + // 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'); + } + + // Other tools go here // Rules checker - var policies = require('./metadata/policies.json'); + var policies = require('./metadata/policies'); data.rules = rulesChecker.check(data, policies); @@ -35,13 +42,6 @@ var Runner = function(params) { generic : scoreCalculator.calculate(data, scoreProfileGeneric) }; - - // 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'); - } delete data.toolsResults.phantomas.metrics.javascriptExecutionTree; delete data.toolsResults.phantomas.offenders.javascriptExecutionTree; diff --git a/test/core/customPoliciesTest.js b/test/core/customPoliciesTest.js new file mode 100644 index 0000000..5593f2f --- /dev/null +++ b/test/core/customPoliciesTest.js @@ -0,0 +1,87 @@ +var should = require('chai').should(); +var rulesChecker = require('../../lib/rulesChecker'); + +describe('rulesChecker', function() { + + var policies = require('../../lib/metadata/policies.js'); + + + it('should grade correctly jQuery versions', function() { + + + var versions = { + '1.2.9': 0, + '1.3.9': 0, + '1.4.4': 10, + '1.5.0': 20, + '1.6.3': 30, + '1.7.0': 40, + '1.8.3a': 50, + '1.9.2': 70, + '1.10.1': 90, + '2.0.0-rc1': 90, + '1.11.1': 100, + '2.1.1-beta1': 100, + '3.0.0': 100 + }; + + for (var version in versions) { + results = rulesChecker.check({ + "toolsResults": { + "phantomas": { + "metrics": { + "jQueryVersion": version + } + } + } + }, policies); + results.jQueryVersion.score.should.equal(versions[version]); + } + + + // Unknown jQuery version + results = rulesChecker.check({ + "toolsResults": { + "phantomas": { + "metrics": { + "jQueryVersion": "wooot" + } + } + } + }, policies); + results.should.deep.equals({}); + + + // If jQueryDifferentVersions is 0 + results = rulesChecker.check({ + "toolsResults": { + "phantomas": { + "metrics": { + "jQueryVersion": "1.6.0", + "jQueryDifferentVersions": 0 + } + } + } + }, policies); + results.should.not.have.a.property('jQueryVersion'); + results.should.have.a.property('jQueryDifferentVersions'); + results.jQueryDifferentVersions.should.have.a.property('score').that.equals(100); + + + // If there are more than 1 jQuery version + results = rulesChecker.check({ + "toolsResults": { + "phantomas": { + "metrics": { + "jQueryVersion": "1.6.0", + "jQueryDifferentVersions": 2 + } + } + } + }, policies); + results.should.not.have.a.property('jQueryVersion'); + results.should.have.a.property('jQueryDifferentVersions'); + results.jQueryDifferentVersions.should.have.a.property('score').that.equals(0); + results.jQueryDifferentVersions.should.have.a.property('abnormal').that.equals(true); + }); +}); From bc8ebd8db3e1a6a39b439331f80350962c314abd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Fri, 19 Dec 2014 14:26:46 +0100 Subject: [PATCH 22/98] Fix CERT_UNTRUSTED error on Travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ed73ffb..9577d83 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "0.10" + - "0.10.33" before_install: - "npm install -g grunt-cli" - "npm install -g phantomjs" From 482a890447f7ea91df41dcaa5d9627c5f718b54e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Fri, 19 Dec 2014 14:27:19 +0100 Subject: [PATCH 23/98] Add debugging logs to resultsDatastore --- lib/server/datastores/resultsDatastore.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/server/datastores/resultsDatastore.js b/lib/server/datastores/resultsDatastore.js index 3d2ce8a..3392337 100644 --- a/lib/server/datastores/resultsDatastore.js +++ b/lib/server/datastores/resultsDatastore.js @@ -2,6 +2,7 @@ var fs = require('fs'); var rimraf = require('rimraf'); var path = require('path'); var Q = require('q'); +var debug = require('debug')('ylt:resultsDatastore'); function ResultsDatastore() { @@ -13,12 +14,14 @@ function ResultsDatastore() { this.saveResult = function(testResults) { - var promise = createResultFolder(testResults.runId); + debug('Saving results to disk...'); + promise.then(function() { var resultFilePath = path.join(resultsDir, testResults.runId, resultFileName); + debug('Destination file is %s', resultFilePath); return Q.nfcall(fs.writeFile, resultFilePath, JSON.stringify(testResults, null, 2)); }); @@ -30,6 +33,8 @@ function ResultsDatastore() { this.getResult = function(runId) { var resultFilePath = path.join(resultsDir, runId, resultFileName); + + debug('Reading results (runID = %s) from disk...', runId); return Q.nfcall(fs.readFile, resultFilePath, {encoding: 'utf8'}).then(function(data) { return JSON.parse(data); @@ -40,6 +45,8 @@ function ResultsDatastore() { this.deleteResult = function(runId) { var folder = path.join(resultsDir, runId); + debug('Deleting results (runID = %s) from disk...', runId); + return Q.nfcall(rimraf, folder); }; @@ -48,6 +55,8 @@ function ResultsDatastore() { function createResultFolder(folderName) { var folder = path.join(resultsDir, folderName); + debug('Creating the folder %s', folderName); + return createGlobalFolder().then(function() { return Q.nfcall(fs.mkdir, folder); }); @@ -62,6 +71,7 @@ function ResultsDatastore() { if (exists) { deferred.resolve(); } else { + debug('Creating the global results folder', resultsDir); fs.mkdir(resultsDir, function(err) { if (err) { deferred.reject(err); From 0776551aa7ff4d1355c0e2b1e41906a6fb663c44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Fri, 19 Dec 2014 14:34:47 +0100 Subject: [PATCH 24/98] Fix #26 API redirects to the response before it's written to disk --- lib/server/controllers/apiController.js | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/server/controllers/apiController.js b/lib/server/controllers/apiController.js index 0f976d7..d76d458 100644 --- a/lib/server/controllers/apiController.js +++ b/lib/server/controllers/apiController.js @@ -52,16 +52,25 @@ var ApiController = function(app) { .then(function(data) { debug('Success'); - runsDatastore.markAsComplete(run.runId); + // Save result in datastore data.runId = run.runId; - resultsDatastore.saveResult(data); + resultsDatastore.saveResult(data) + .then(function() { - // Send result if the user was waiting - if (run.params.waitForResponse) { - res.redirect(302, '/api/results/' + run.runId); - } + runsDatastore.markAsComplete(run.runId); + + // Send result if the user was waiting + if (run.params.waitForResponse) { + res.redirect(302, '/api/results/' + run.runId); + } + + }) + .fail(function(err) { + debug('Saving results to resultsDatastore failed:'); + debug(err); + }); }).fail(function(err) { From f66f4641991fc45a0e2a1be99113a6c593b2c85f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Fri, 19 Dec 2014 14:44:03 +0100 Subject: [PATCH 25/98] Fixes for jsHint --- front/src/js/controllers/indexCtrl.js | 2 +- front/src/js/directives/gradeDirective.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/front/src/js/controllers/indexCtrl.js b/front/src/js/controllers/indexCtrl.js index 6b74e1c..2d8f650 100644 --- a/front/src/js/controllers/indexCtrl.js +++ b/front/src/js/controllers/indexCtrl.js @@ -12,5 +12,5 @@ indexCtrl.controller('IndexCtrl', ['$scope', '$location', 'Runs', function($scop }); } - } + }; }]); \ No newline at end of file diff --git a/front/src/js/directives/gradeDirective.js b/front/src/js/directives/gradeDirective.js index f79f2cc..785ba17 100644 --- a/front/src/js/directives/gradeDirective.js +++ b/front/src/js/directives/gradeDirective.js @@ -28,7 +28,7 @@ gradeDirective.directive('grade', function() { return 'E'; } return 'F'; - } + }; } }; }); \ No newline at end of file From e2dc5e070539504b4d3bd358bf429737297afb89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Fri, 19 Dec 2014 14:48:13 +0100 Subject: [PATCH 26/98] Fix typo --- lib/metadata/policies.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/metadata/policies.json b/lib/metadata/policies.json index c858342..34bfe28 100644 --- a/lib/metadata/policies.json +++ b/lib/metadata/policies.json @@ -313,7 +313,7 @@ }, "otherCount": { "tool": "phantomas", - "label": "Other type of requests", + "label": "Other types of requests", "message": "

They can be Flash, XML, music or any unknown format.

", "isOkThreshold": 5, "isBadThreshold": 20, From ebb9dbc7f1ad5bd769ece39c870df1033f195b26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Fri, 19 Dec 2014 14:48:54 +0100 Subject: [PATCH 27/98] Fix mispelled 'requests' rule in API --- lib/metadata/scoreProfileGeneric.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/metadata/scoreProfileGeneric.json b/lib/metadata/scoreProfileGeneric.json index 15bf1e0..3b11c0d 100644 --- a/lib/metadata/scoreProfileGeneric.json +++ b/lib/metadata/scoreProfileGeneric.json @@ -69,7 +69,7 @@ "requests": { "label": "Requests number", "policies": { - "request": 5, + "requests": 5, "htmlCount": 0, "jsCount": 1, "cssCount": 1, From 239672116b68b5a964e94b0819cf5132ee147844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sat, 20 Dec 2014 09:25:26 +0100 Subject: [PATCH 28/98] Removing the old server --- app/lib/strReplace.js | 8 - app/node_controllers/indexController.js | 27 - app/node_controllers/launchTestController.js | 79 -- app/node_controllers/resultsController.js | 47 - app/node_controllers/waitingQueueSocket.js | 60 -- app/node_views/index.html | 35 - app/node_views/launchTest.html | 97 -- app/node_views/results.html | 933 ------------------- app/public/fonts/icons.woff | Bin 2136 -> 0 bytes app/public/fonts/svg-icons/arrow-left3.svg | 1 - app/public/fonts/svg-icons/bars.svg | 1 - app/public/fonts/svg-icons/eye.svg | 1 - app/public/fonts/svg-icons/lab.svg | 1 - app/public/fonts/svg-icons/list.svg | 1 - app/public/fonts/svg-icons/question.svg | 1 - app/public/fonts/svg-icons/warning.svg | 1 - app/public/scripts/app.js | 7 - app/public/scripts/resultsCtrl.js | 406 -------- app/public/scripts/showOffenders.js | 17 - app/public/styles/index.css | 33 - app/public/styles/launchTest.css | 25 - app/public/styles/less/icons.less | 56 -- app/public/styles/less/index.less | 35 - app/public/styles/less/launchTest.less | 19 - app/public/styles/less/main.less | 66 -- app/public/styles/less/results.less | 497 ---------- app/public/styles/main.css | 139 --- app/public/styles/results.css | 465 --------- {app/public => front/src}/img/favicon.png | Bin front/src/main.html | 18 +- package.json | 5 +- server.js | 59 -- src | 672 ------------- 33 files changed, 10 insertions(+), 3802 deletions(-) delete mode 100644 app/lib/strReplace.js delete mode 100644 app/node_controllers/indexController.js delete mode 100644 app/node_controllers/launchTestController.js delete mode 100644 app/node_controllers/resultsController.js delete mode 100644 app/node_controllers/waitingQueueSocket.js delete mode 100644 app/node_views/index.html delete mode 100644 app/node_views/launchTest.html delete mode 100644 app/node_views/results.html delete mode 100644 app/public/fonts/icons.woff delete mode 100755 app/public/fonts/svg-icons/arrow-left3.svg delete mode 100755 app/public/fonts/svg-icons/bars.svg delete mode 100755 app/public/fonts/svg-icons/eye.svg delete mode 100755 app/public/fonts/svg-icons/lab.svg delete mode 100755 app/public/fonts/svg-icons/list.svg delete mode 100755 app/public/fonts/svg-icons/question.svg delete mode 100755 app/public/fonts/svg-icons/warning.svg delete mode 100644 app/public/scripts/app.js delete mode 100644 app/public/scripts/resultsCtrl.js delete mode 100644 app/public/scripts/showOffenders.js delete mode 100644 app/public/styles/index.css delete mode 100644 app/public/styles/launchTest.css delete mode 100644 app/public/styles/less/icons.less delete mode 100644 app/public/styles/less/index.less delete mode 100644 app/public/styles/less/launchTest.less delete mode 100644 app/public/styles/less/main.less delete mode 100644 app/public/styles/less/results.less delete mode 100644 app/public/styles/main.css delete mode 100644 app/public/styles/results.css rename {app/public => front/src}/img/favicon.png (100%) delete mode 100644 server.js delete mode 100644 src diff --git a/app/lib/strReplace.js b/app/lib/strReplace.js deleted file mode 100644 index 4b36abb..0000000 --- a/app/lib/strReplace.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Alternative to the standard String.prototype.replace function - * Avoids problems with $$, $1, $2, ... - */ - -module.exports = function(str, searched, replacement) { - return str.split(searched).join(replacement); -}; \ No newline at end of file diff --git a/app/node_controllers/indexController.js b/app/node_controllers/indexController.js deleted file mode 100644 index b5c488c..0000000 --- a/app/node_controllers/indexController.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Yellow Lab Tools home page controller - */ - -var async = require('async'); -var fs = require ('fs'); -var strReplace = require('../lib/strReplace'); - -var indexController = function(req, res, googleAnalyticsId) { - 'use strict'; - - async.parallel({ - - htmlTemplate: function(callback) { - fs.readFile('./app/node_views/index.html', {encoding: 'utf8'}, callback); - } - - }, function(err, results) { - var html = results.htmlTemplate; - html = strReplace(html, '%%GA_ID%%', googleAnalyticsId); - - res.setHeader('Content-Type', 'text/html'); - res.send(html); - }); - }; - - module.exports = indexController; \ No newline at end of file diff --git a/app/node_controllers/launchTestController.js b/app/node_controllers/launchTestController.js deleted file mode 100644 index a3ee5fc..0000000 --- a/app/node_controllers/launchTestController.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Controller for the test launching page (the waiting page, after the user submited a test on the index page) - */ - -var async = require('async'); -var fs = require ('fs'); -var strReplace = require('../lib/strReplace'); - -var launchTestController = function(req, res, testQueue, googleAnalyticsId) { - 'use strict'; - - var resultsPath = 'results/' + testId; - var phantomasResultsPath = resultsPath + '/results.json'; - - var url = req.body.url; - - var options = {}; - if (req.body.timeout) { - options.timeout = req.body.timeout; - } - - async.waterfall([ - - function htmlTemplate(callback) { - fs.readFile('./app/node_views/launchTest.html', {encoding: 'utf8'}, callback); - }, - - function sendResponse(html, callback) { - - html = strReplace(html, '%%TEST_URL%%', url); - html = strReplace(html, '%%TEST_ID%%', testId); - html = strReplace(html, '%%GA_ID%%', googleAnalyticsId); - - res.setHeader('Content-Type', 'text/html'); - res.send(html); - - callback(); - }, - - function createFolder(callback) { - // Create results folder - fs.mkdir(resultsPath, callback); - }, - - function executePhantomas(callback) { - console.log('Adding test ' + testId + ' on ' + url + ' to the queue'); - - var task = { - testId: testId, - url: url, - options: options - }; - - testQueue.push(task, callback); - }, - - function writeResults(json, resultsObject, callback) { - console.log('Saving Phantomas results file to ' + phantomasResultsPath); - fs.writeFile(phantomasResultsPath, JSON.stringify(json, null, 4), callback); - } - - ], function(err) { - if (err) { - console.log('An error occured in the phantomas test: ', err); - - fs.writeFile(phantomasResultsPath, JSON.stringify({url: url, error: err}, null, 4), function(err) { - if (err) { - console.log('Could not even write an error message on file ' + phantomasResultsPath); - console.log(err); - } - }); - testQueue.testFailed(testId); - } else { - testQueue.testComplete(testId); - } - }); - }; - - module.exports = launchTestController; \ No newline at end of file diff --git a/app/node_controllers/resultsController.js b/app/node_controllers/resultsController.js deleted file mode 100644 index d1cdb97..0000000 --- a/app/node_controllers/resultsController.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * The page that dispays the results - */ - -var async = require('async'); -var fs = require('fs'); -var strReplace = require('../lib/strReplace'); - -var resultsController = function(req, res, googleAnalyticsId) { - 'use strict'; - - var testId = req.params.testId; - var resultsPath = 'results/' + testId; - var phantomasResultsPath = resultsPath + '/results.json'; - - console.log('Opening test ' + testId + ' results as HTML'); - - async.parallel({ - - htmlTemplate: function(callback) { - fs.readFile('./app/node_views/results.html', {encoding: 'utf8'}, callback); - }, - - phantomasResults: function(callback) { - fs.readFile(phantomasResultsPath, {encoding: 'utf8'}, callback); - } - - }, function(err, results) { - if (err) { - console.log(err); - return res.status(404).send('Sorry, test not found...'); - } - - // Escape "" because it can interfer with the HTML parser - var phantomasResults = results.phantomasResults; - phantomasResults = phantomasResults.replace(/<\/script>/g, '\\u003c/script>'); - - var html = results.htmlTemplate; - html = strReplace(html, '%%RESULTS%%', phantomasResults); - html = strReplace(html, '%%GA_ID%%', googleAnalyticsId); - - res.setHeader('Content-Type', 'text/html'); - res.send(html); - }); -}; - -module.exports = resultsController; \ No newline at end of file diff --git a/app/node_controllers/waitingQueueSocket.js b/app/node_controllers/waitingQueueSocket.js deleted file mode 100644 index b8fd30f..0000000 --- a/app/node_controllers/waitingQueueSocket.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Socket.io handler - */ - -var fs = require('fs'); - -var waitingQueueSocket = function(socket, testQueue) { - - socket.on('waiting', function(testId) { - console.log('User waiting for test id ' + testId); - - sendTestStatus(testId); - - testQueue.on('testComplete', function(id) { - if (testId === id) { - socket.emit('complete'); - console.log('Sending complete event to test id ' + testId); - } - }); - - testQueue.on('testFailed', function(id) { - if (testId === id) { - socket.emit('failed'); - console.log('Sending failed event to test id ' + testId); - } - }); - - testQueue.on('queueMoving', function() { - var positionInQueue = testQueue.indexOf(testId); - if (positionInQueue >= 0) { - socket.emit('position', positionInQueue); - console.log('Sending position to test id ' + testId); - } - }); - }); - - // Finds the status of a test and send it to the client - function sendTestStatus(testId) { - // Check task position in queue - var positionInQueue = testQueue.indexOf(testId); - - if (positionInQueue >= 0) { - socket.emit('position', positionInQueue); - } else { - // Find in results files - var exists = fs.exists('results/' + testId + '/results.json', function(exists) { - if (exists) { - // TODO : find a way to make sure the file is completely written - setTimeout(function() { - socket.emit('complete'); - }, 4000); - } else { - socket.emit('404'); - } - }); - } - } -}; - -module.exports = waitingQueueSocket; \ No newline at end of file diff --git a/app/node_views/index.html b/app/node_views/index.html deleted file mode 100644 index 5cd772b..0000000 --- a/app/node_views/index.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - Yellow Lab Tools - - - - - -

Yellow Lab Tools

-

Free online test to help speeding up heavy web pages

-
- - -
-

(This is a BETA, your feedback is more than welcome)

- - - - - - \ No newline at end of file diff --git a/app/node_views/launchTest.html b/app/node_views/launchTest.html deleted file mode 100644 index 9d31ffc..0000000 --- a/app/node_views/launchTest.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - Yellow Lab Tools - Awaiting - - - - - - -

Yellow Lab Tools

- -
- -
%%TEST_URL%%
- - - - - - \ No newline at end of file diff --git a/app/node_views/results.html b/app/node_views/results.html deleted file mode 100644 index 6d2c686..0000000 --- a/app/node_views/results.html +++ /dev/null @@ -1,933 +0,0 @@ - - - - Yellow Lab Tools - Results page - - - - - - - - - - -

Yellow Lab Tools

- -
Untangling and counting the spaghettis...
- -
- - -
-
New test
- - -
- -
-

Error: {{phantomasResults.error}}

-
Phantomas timed out
-
Phantomas config error
-
Phantomas failed to load page
-
Phantomas internal error
-
Javascript execution tree error
-
JSON undefined error
-
- -
-

Grades

- -
-
-
{{notations.domComplexity}}
-
DOM complexity
-
-
-
-
DOM elements count
-
- {{phantomasResults.metrics.DOMelementsCount}} -
-
-
- -

A high number of DOM elements means a lot of work for the browser to render the page.

-

It also slows down Javascript DOM queries, as there are more elements to search through.

-
-
-
-
-
DOM max depth
-
- {{phantomasResults.metrics.DOMelementMaxDepth}} - -
-
-
- -

A deep DOM makes the CSS matching with DOM elements difficult.

-

It also slows down Javascript modifications to the DOM because changing the dimensions of an element makes the browser re-calculate the dimensions of it's parents. Same thing for Javascript events, that bubble up to the document root.

-
-
-
-
-
Number of iframes
-
- {{phantomasResults.metrics.iframesCount}} -
-
-
- -

iFrames are the most complex HTML elements. They are pages, just like the main page, and the browser needs to create a new page context, which has a cost.

-
-
-
-
-
IDs duplicated
-
- {{phantomasResults.metrics.DOMidDuplicated}} - -
-
-
- -

IDs of HTML elements must be document-wide unique. This can cause problems with getElementById returning the wrong element.

-
-
-
-
-
-
-
-
{{notations.jsDomManipulations}}
-
DOM manipulations
-
-
-
-
DOM inserts
-
- {{phantomasResults.metrics.DOMinserts}} - -
-
-
- -

Working with the DOM in Javascript triggers layout calculations and slows down the page.

-

Try, as much as possible, to have an HTML page fully generated by the server instead of making changes with JS.

-
-
-
-
-
DOM queries
-
- {{phantomasResults.metrics.DOMqueries}} -
-
-
- -

DOM queries are like looking in a large catalog of items. Even if the browsers made progress on the performances of queries, websites often make hundreds of them.

-

Try to reduce the number of queries by refactoring your Javascript code.

-

Avoid also to have a read query between two write queries. To be able to reduce the number repaints and optimize performances, browsers buffer the DOM writing operations and treat them in bulk. But each time a DOM reading is asked, the browser needs to empty the buffer. This can be particularly slow inside a loop.

-
-
-
-
-
Avoidable queries
-
- {{phantomasResults.metrics.DOMqueriesAvoidable}} - -
-
-
- -

This is the number of queries that could be avoided by removing all duplicated queries.

-

Simply save the result of a query in a variable. Ok it is not always simple, especially with third-party scripts, but at least do it with your own code.

-
-
-
-
-
Events bound
-
- {{phantomasResults.metrics.eventsBound}} - -
-
-
- -

Binding too many events has a cost.

-

It can be avoided by using "event delegation". Instead of binding events on each element one by one, events delegation binds them on the top level document element and uses the bubbling principle. It will imperceptibly slow down the event when it occurs, but the loading of the page will speed-up.

-
-
-
-
-
-
-
-
{{notations.jsBadPractices}}
-
Bad Javascript
-
-
-
-
Javascript errors
-
- {{phantomasResults.metrics.jsErrors}} - -
-
-
- -

Just to let you know there are some errors on the page.

-

Please note that some errors only occur in the PhantomJS browser, so you might need to double check on other browsers.

-
-
-
-
-
eval calls
-
{{phantomasResults.metrics.evalCalls}}
-
-
- -

The 'eval' function is slow and a bad coding practice. Try to get rid of it.

-
-
-
-
-
document.write calls
-
{{phantomasResults.metrics.documentWriteCalls}}
-
-
- -

They slow down the page construction, especially if they are used to insert scripts in the page. Remove them ASAP.

-

If you cannot remove them because they come from a third-party script (such as ads), have a look at PostScribe.

-
-
-
-
-
Console messages
-
{{phantomasResults.metrics.consoleMessages}}
-
-
- -

Try to keep your console clean when in production. Debugging is good for development only.

-

Writing in the console has a cost, especially when dumping large object variables.

-

There is also a problem with Internet Explorer 8, not knowing the console object.

-
-
-
-
-
Global variables
-
- {{phantomasResults.metrics.globalVariables}} - -
-
-
- -

It is a bad practice because they clutter up the global namespace. If two scripts use the same variable name in the global scope, it can cause conflicts and it is generally hard to debug.

-

Global variables also take a (very) little bit longer to be accessed than variables in the local scope of a function.

-
-
-
-
-
DOM manipulations in body
-
- {{inBodyDomManipulations}} -
-
-
- -

This metric counts the number of DOM queries, DOM inserts, binds, etc. made by the Javascript before the DOMContentLoaded event.

-

Wait for this event before manipulating the DOM. Do not execute Javascript in the middle of the BODY as it slows down the construction of the DOM and makes a poor maintainability. This is what i call spaghetti code.

-

The JS Timeline tab can help you identify what's happening.

-
-
-
-
-
-
-
-
{{notations.jQueryLoading}}
-
jQuery version
-
-
-
-
jQuery version
-
{{phantomasResults.metrics.jQueryVersion}}
-
-
- -

Current latest versions of jQuery are 1.11 (with support for old IE versions) and 2.1 (without).

-

Each new version of jQuery optimizes performances. Do not keep an old version of jQuery. Updating can sometimes break a few things, but it is generally quite easy to fix them up. So don't hesitate.

-
-
-
-
-
{{phantomasResults.metrics.jQueryDifferentVersions}} versions loaded
-
- {{version}} & -
-
-
- -

jQuery is a heavy library. You should **never** load jQuery more than one on the same page.

-
-
-
-
-
-
-
-
{{notations.cssComplexity}}
-
CSS complexity
-
-
-
-
Rules count
-
- {{phantomasResults.metrics.cssRules}} -
-
-
- -

Having a huge number of CSS rules hurts performances. If the number of CSS rules is higher than the number of DOM elements, there is clearly a problem.

-

Huge stylesheets generally occur when the different pages of a website load all the CSS, concatenated in a single stylesheet, even if a large part of the rules are page-specific. Solution is to create one main CSS file with global rules and one custom files per page.

-
-
-
-
-
Complex selectors
-
- {{phantomasResults.metrics.cssComplexSelectors}} - -
-
-
- -

Complex selectors are CSS selectors with 4 or more expressions, like "#header ul li .foo".

-

They are adding more work for the browser, and this could be avoided by simplifying selectors.

-
-
-
-
-
Complex attributes selector
-
- {{phantomasResults.metrics.cssComplexSelectorsByAttribute}} - -
-
-
- -

Complex attributes selectors are one of these: -

    -
  • .foo[type*=bar] (contains bar)
  • -
  • .foo[type^=bar] (starts with bar)
  • -
  • .foo[type|=bar] (starts with bar or bar-)
  • -
  • .foo[type$=bar] (ends with bar)
  • -
  • .foo[type~=bar baz] (bar or baz)
  • -
-

-

Their matching process needs more CPU and it has a cost on performances.

-
-
-
-
-
-
-
-
{{notations.badCss}}
-
Bad CSS
-
-
-
-
CSS syntax error
-
- {{phantomasResults.metrics.cssParsingErrors}} - -
-
-
- -

Yellow Lab Tools failed to parse a CSS file. I doubt the problem comes from the css parser.

-

Maybe a CSS validator can help you.

-
-
-
-
-
Uses of @import
-
- {{phantomasResults.metrics.cssImports}} - -
-
-
- -

It’s bad for performance to use @import because CSS files don't get downloaded in parallel.

-

You should use <link rel='stylesheet' href='a.css'> instead.

-
-
-
-
-
Duplicated selectors
-
- {{phantomasResults.metrics.cssDuplicatedSelectors}} - -
-
-
- -

This is when two or more selectors are strictly identical and should be merged.

-
-
-
-
-
Duplicated properties
-
- {{phantomasResults.metrics.cssDuplicatedProperties}} - -
-
-
- -

This is the number of property definitions duplicated within a selector.

-
-
-
-
-
Empty rules
-
- {{phantomasResults.metrics.cssEmptyRules}} - -
-
-
- -

Very easy to fix.

-
-
-
-
-
CSS expressions
-
- {{phantomasResults.metrics.cssExpressions}} - -
-
-
- -

Such as: expression( document.body.clientWidth > 600 ? "600px" : "auto" )

-

This is a bad practice as it slows down browsers. There are some simpler CSS3 methods for doing this.

-
-
-
-
-
Uses of !important
-
- {{phantomasResults.metrics.cssImportants}} - -
-
-
- -

It can be useful, but only as a last resort. It is a bad practice because it overrides the normal cascading logic. The more you use !important, the more you need it again to over-override. This conducts to a poor maintainability.

-
-
-
-
-
Old IE fixes
-
- {{phantomasResults.metrics.cssOldIEFixes}} - -
-
-
- -

What browser do you need to support? Once you've got the answer, take a look at these old rules that pollute your CSS code and remove them.

-

IE6: -

    -
  • * html
  • -
  • html > body (everything but IE6)
  • -
-

-

IE7: -

    -
  • *height: 123px;
  • -
  • height: 123px !ie;
  • -
-

-

IE9: -

    -
  • -ms-filter
  • -
  • progid:DXImageTransform.Microsoft
  • -
-

-
-
-
-
-
Old prefixes
-
- {{phantomasResults.metrics.cssOldPropertyPrefixes}} - -
-
-
- -

Many property prefixes such as -moz- or -webkit- are not needed anymore, or by very few people. You can remove them or replace them with the non-prefixed version. This will help reducing your stylesheets weight.

-
-
-
-
-
Universal selectors
-
- {{phantomasResults.metrics.cssUniversalSelectors}} - -
-
-
- -

Universal selectors are the most expensive CSS selectors.

-

More informations here.

-
-
-
-
-
Redundant body selectors
-
- {{phantomasResults.metrics.cssRedundantBodySelectors}} - -
-
-
- -

This is one way to remove complexity from a CSS rule. Generally, when "body" is specified in a rule it can be removed, because an element is necessarily inside the body.

-
-
-
-
-
Redundant tags selectors
-
- {{phantomasResults.metrics.cssRedundantChildNodesSelectors}} - -
-
-
- -

Some tags included inside other tags are obvious. For example, when "ul li" is specified in a rule, "ul" can be removed because the "li" element is always inside a "ul". Same thing for "tr td", "select option", ...

-

Lowering compexity in CSS selectors can make the page load a little faster.

-
-
-
-
-
-
-
-
{{notations.requests}}
-
Requests number
-
-
-
-
Total requests
-
- {{phantomasResults.metrics.requests}} -
-
-
- -

This is one of the most important performance rule. Every request is slowing down the page loading.

-

There are several technics to reduce their number: -

    -
  • Concatenate JS files
  • -
  • Concatenate CSS files
  • -
  • Embed or inline small JS or CSS files in the HTML
  • -
  • Create sprites or icon fonts
  • -
  • Base64 encode small images in HTML or stylesheets
  • -
  • Use lazyloading for images
  • -
-

-
-
-
-
-
Documents
-
- {{phantomasResults.metrics.htmlCount}} - -
-
-
-
-
Scripts
-
- {{phantomasResults.metrics.jsCount}} - -
-
-
-
-
Stylesheets
-
- {{phantomasResults.metrics.cssCount}} - -
-
-
-
-
Images
-
- {{phantomasResults.metrics.imageCount}} - -
-
-
-
-
Fonts
-
- {{phantomasResults.metrics.webfontCount}} - -
-
-
-
-
Videos
-
- {{phantomasResults.metrics.videoCount}} - -
-
-
-
-
JSON
-
- {{phantomasResults.metrics.jsonCount}} - -
-
-
-
-
Other
-
- {{phantomasResults.metrics.otherCount}} - -
-
-
-
-
-
-
-
{{notations.network}}
-
Network
-
-
-
-
404 not found
-
- {{phantomasResults.metrics.notFound}} - -
-
-
- -

404 errors are never cached, so each time a page ask for it, it hits se server. Even if it is behind a CDN or a reverse-proxy cache.

-
-
-
-
-
Connections closed
-
- {{phantomasResults.metrics.closedConnections}} - -
-
-
- -

This counts the number of requests not keeping the connection alive (specifying "Connection: close" in the response headers). It is only counting a request if it is followed by another request on the same domain.

-

This is slowing down the next request, because the brower needs to open a new connection to the server, which means a additional round-trip.

-

Correct the problem by setting a Keep-Alive header on the guilty server.

-
-
-
-
-
Duplicated requests
-
- {{phantomasResults.metrics.multipleRequests}} - -
-
-
- -

This only happens when the asset has no cache and is requested more than once on the same page. Be very careful about it.

-
-
-
-
-
Caching disabled
-
- {{phantomasResults.metrics.cachingDisabled}} - -
-
-
- -

Counts responses with caching disabled (max-age=0)

-

Fix immediatly if on static assets.

-
-
-
-
-
Caching not specified
-
- {{phantomasResults.metrics.cachingNotSpecified}} - -
-
-
- -

Responses with no caching header sent (either Cache-Control or Expires).

-

Every request should have a cache time specified. If you really don't want cache, specify "max-age=0", otherwise some browsers will try to cache.

-
-
-
-
-
Caching too short
-
- {{phantomasResults.metrics.cachingTooShort}} - -
-
-
- -

Responses with too short caching time (less than a week).

-

The longer you cache, the better. Add versionning to your static assets, if it's not already done, and set their cache time to one year.

-
-
-
-
-
Different domains
-
- {{phantomasResults.metrics.domains}} - -
-
-
- -

For each domain met, the browser needs to make a DNS look-up, which is slow. Avoid having to many different domains and the page should render faster.

-

By the way, domain sharding is not a good practice anymore.

-
-
-
-
-
-
-
-
- -
-

Javascript Timeline

-

- This graph gives a quick view of when the Javascript interactions with the DOM occur during the loading of the page. -

-
-
-
-
-
-
-
Timestamp: {{$index * timelineIntervalDuration | number: 0}} ms
-
-
-
-
0 ms
-
{{endTime | number: 0}} ms
-
-
-
-
DOM creation
-
DOM interactive
-
DOM content loaded event
-
Page completion
-
Page is complete
-
-
-
Executing Javascript and DOM queries here is a bad practice and slows down the DOM construction.
-
Some frameworks do things here, but it's not reliable and should be avoided.
-
Also known as "document ready". This is where you should execute top-priority scripts, like binding action buttons or launch a video player.
-
Here you can execute mid-priority tasks. Loading a script with createElement('script') is one way to do so.
-
The page is considered loaded, it's time for low priority things : trackers, social plugins, easter egg...
-
-
-
- -

Javascript Profiler

-

- The table below shows the interactions between Javascript and the DOM. It is useful to understand what happens while the page loads. -

-
-
- - -
-
- - Filter by - -
-
-
- - -
-
-
Type
-
Params
-
-
Timestamp
-
-
-
{{$index + 1}}
-
{{node.data.type}}
- -
- {{node.data.callDetails.arguments[0]}} - : {{node.data.callDetails.arguments[1]}} - : {{node.data.callDetails.arguments[2]}} - : {{node.data.callDetails.arguments[3]}} -
- -
-
-
-
✖
- -
-

Called on DOM element

-
{{node.data.callDetails.context.domElement}}
-
- -
-

Called on 0 jQuery element

-

Useless function call, as the jQuery object is empty.

-
- -
-

Called on 1 jQuery element

-
{{node.data.callDetails.context.firstElementPath}}
-
- -
-

Called on {{node.data.callDetails.context.length}} jQuery elements

-

- 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). -

-

First one is: {{node.data.callDetails.context.firstElementPath}}

-
- -

- The query returned 0 results. Could it be unused or dead code? -

- -
-

Backtrace

-
-
-
{{trace.fnName || '(anonymous)'}}
- -
-
-
can't find any backtrace :/
-
-
-
- -
-

Sub processes

-
-
-
-
Type
-
Params
-
Duration
-
-
-
{{$index}}
-
{{node.data.type}}
-
- {{node.data.callDetails.arguments[0]}} - : {{node.data.callDetails.arguments[1]}} - : {{node.data.callDetails.arguments[2]}} - : {{node.data.callDetails.arguments[3]}} -
-
{{node.data.time}} ms
-
-
-
-
-
-
-
{{node.data.timestamp | number: 0}} ms
-
-
-
-
- - - - - - \ No newline at end of file diff --git a/app/public/fonts/icons.woff b/app/public/fonts/icons.woff deleted file mode 100644 index 832a8a8809b188082bbe7d8602d50a98e77f7f37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2136 zcmZXWc{H2Z8^>=XlAwqd&15vfG_^#nvF}@|4JEc(hKaSBkq$-Ww2ZAail&5W`KhVa zS`?+GqC_=R7h0`-4QflS4taBET~`5ir6(*TAQ%7?gaA|n7>J;< z62c$|F>T1>Ass|ucHf3EqgfDhfxHYPECPBm16a^{C}=)Fev_CBI32(Yfmk@?1yGGk zW1RGh5x+!1eV`p7wg(aczyV+t#(4OJyL(`Y8YV3BVv_Ftd`!*v)gW%DgxX@2jT{qK zmE9twA??2>9C9fUB`INU$|`pkTS|{1U`syMulDY%Buh25y;#6rV#pjo3``@QK|tyYd3&W9GY9<+6hWr(gF^YzS|s+OxNU6BjRf!pQ@T z(+4He^7Cahn^ zLp}bJXy>tt7kJgY5ZVZJS6W(p$6>&^l3FT%p)5X=6%~n5`uQw=W^UgHTjEb{DuInL zGWQ?0Dwj=5PTZ)j6&~MBUM(Vi=3Z@TDKVv`tCi)Jx45aHh}ZN7D-Tz4d7j1d zwarr5dyd75S0e;Z-@3AMJ^i5q{VBVW%_t9X+8-UzT0-)O|JGEnAXP>vURk zqL(cZ9dyPP-OHAOE7%RNu;DV}5Sa#t%*7R|vsF{`NbsJky80 z>-kx@@@S#PATN%l+j}*6)l=+>q?xw6sx$Vs)w3u|}jZ~D#Cdh*SqFQWY3 z$k@TM_)>u@dpH{R4~kw>@-psI)Vg6`S6Gr|Dq55Ke!b1&+&LBMT|(1=c#cxW!1J=h z5&`N*wcmy1{caJ)e;sU&H=CMYExL@pvOJ4x%*E`~{q*v3%EHL}sAEK`aI<;CkMDb9 zZMvSZlF?JTKFJ+*;4fKR=*xFXC5O&3ZiwqUGK z96l+m_ICati+CAxB&vP?21TwmgA3aI&vwraBVW9(=Xt z{8`DM$6H!2$jR4aX2oz31ZCGmNB()bUT=8OW^G01f#HNot58Azo_P9hyp_Isj`uY0(#vW_%(?!$6I<(CzqS9E z;WTVsl;?Bj;U-TCh@X*R>hS9bGmyig+vW)B)}|wCZ4%hiyTA$IyY}_G45HKN!5~?$ zDWf-KQGzIx4j=<+Crb9ev0yq~01p&HV(5W%gpCv-6t;>5h+DuEI&i!91Ht*xz7r>obV1d^X(vgQjq1j~ujaQJM>t~7QHtEjC?hB7+j zRDseD0AsU9@!vi|6Y1X}DM-vRYz6x$P96y-^uR7S5-y>_6|rA-Q4LsvhUg&}r9xAi z9voyf&vpRdl^MG6-(H|z!Hi_(wgrE6EG|S48VHiU?nh>A7v;DoQ#$cSDP$cPc(_Rn z{%>z_%e$}?{60ARu_clqCTk6Q0hkN$^>6XOL~uy8hkz~yU<$}XH2{FFDY37y7`6^W z#9B6$H!wg*%~CHZ>`?B})T3H01CGhX*6uou3ixdUV~0@%r4V*D{4KiK?$@EAk_ds$ z)Xpkz!%~nJE8bf!NIgHB^w(2Xcc$U2>sI-vG_|R&pe5cJ`8m61ep4~?Nq_aRx+Nwb zW> z-BnGW$A#$xXX5&;laj}_XLaWcxMl07r!~C$UsMUIx=_t`YyNPJn7_G8bzD3_ms>4< YljX8q)tXRRX>$DAcWsA$BH{t!AH^=4T>t<8 diff --git a/app/public/fonts/svg-icons/arrow-left3.svg b/app/public/fonts/svg-icons/arrow-left3.svg deleted file mode 100755 index d96cc70..0000000 --- a/app/public/fonts/svg-icons/arrow-left3.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/public/fonts/svg-icons/bars.svg b/app/public/fonts/svg-icons/bars.svg deleted file mode 100755 index 2e7adca..0000000 --- a/app/public/fonts/svg-icons/bars.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/public/fonts/svg-icons/eye.svg b/app/public/fonts/svg-icons/eye.svg deleted file mode 100755 index 59ff3c2..0000000 --- a/app/public/fonts/svg-icons/eye.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/public/fonts/svg-icons/lab.svg b/app/public/fonts/svg-icons/lab.svg deleted file mode 100755 index 28b3aa8..0000000 --- a/app/public/fonts/svg-icons/lab.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/public/fonts/svg-icons/list.svg b/app/public/fonts/svg-icons/list.svg deleted file mode 100755 index 403a80a..0000000 --- a/app/public/fonts/svg-icons/list.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/public/fonts/svg-icons/question.svg b/app/public/fonts/svg-icons/question.svg deleted file mode 100755 index 49e9fc3..0000000 --- a/app/public/fonts/svg-icons/question.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/public/fonts/svg-icons/warning.svg b/app/public/fonts/svg-icons/warning.svg deleted file mode 100755 index 85f8300..0000000 --- a/app/public/fonts/svg-icons/warning.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/public/scripts/app.js b/app/public/scripts/app.js deleted file mode 100644 index ec4f9b9..0000000 --- a/app/public/scripts/app.js +++ /dev/null @@ -1,7 +0,0 @@ -angular.module('YellowLabTools', [ - 'Results', - 'Runs', - 'ngModal', - 'ShowOffendersDirective' -]); - diff --git a/app/public/scripts/resultsCtrl.js b/app/public/scripts/resultsCtrl.js deleted file mode 100644 index 0325b0e..0000000 --- a/app/public/scripts/resultsCtrl.js +++ /dev/null @@ -1,406 +0,0 @@ -var app = angular.module('Results', []); - -app.controller('ResultsCtrl', function ($scope) { - // Grab results from nodeJS served page - $scope.phantomasResults = window._phantomas_results; - - $scope.view = 'execution'; - - 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); - - // Sort globalVariables offenders alphabetically - if ($scope.phantomasResults.offenders.globalVariables) { - $scope.phantomasResults.offenders.globalVariables.sort(); - } - - - initSummaryView(); - initJSTimelineView(); - - } - - $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; - $scope.inBodyDomManipulations = 0; - treeRunner($scope.javascript, function(node) { - if (node.data.time) { - $scope.totalJSTime += node.data.time; - } - - if (node.data.timestamp < $scope.phantomasResults.metrics.domInteractive && - node.data.type !== 'jQuery - onDOMReady') { - $scope.inBodyDomManipulations ++; - } - - if (node.data.type !== 'main') { - // Don't check the children - return false; - } - }); - - // Grab the notes - $scope.notations = { - domComplexity: getDomComplexityScore(), - jsDomManipulations: getJsDomManipulationsScore(), - jsBadPractices: getJSBadPracticesScore(), - jQueryLoading: getJQueryLoadingScore(), - cssComplexity: getCSSComplexityScore(), - badCss: getBadCssScore(), - requests: requestsScore(), - network: networkScore() - }; - } - - function initJSTimelineView() { - - if (!$scope.javascript.children) { - return; - } - - // Read the execution tree and adjust the navigation timings (cause their not very well synchronised) - treeRunner($scope.javascript, function(node) { - switch(node.data.type) { - case 'domInteractive': - $scope.phantomasResults.metrics.domInteractive = node.data.timestamp; - break; - case 'domContentLoaded': - $scope.phantomasResults.metrics.domContentLoaded = node.data.timestamp; - break; - case 'domContentLoadedEnd': - $scope.phantomasResults.metrics.domContentLoadedEnd = node.data.timestamp; - break; - case 'domComplete': - $scope.phantomasResults.metrics.domComplete = node.data.timestamp; - break; - } - - if (node.data.type !== 'main') { - // Don't check the children - return false; - } - }); - - - // Now read the tree and display it on a timeline - - // Split the timeline into 200 intervals - var numberOfIntervals = 199; - 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-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 - treeRunner($scope.javascript, 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 1000) { - note = 'B'; - } - if (score > 1500) { - note = 'C'; - } - if (score > 2000) { - note = 'D'; - } - if (score > 3000) { - note = 'E'; - } - if (score > 4000) { - note = 'F'; - } - return note; - } - - function getJsDomManipulationsScore() { - var note = 'A'; - var score = $scope.phantomasResults.metrics.DOMinserts * 2 + - $scope.phantomasResults.metrics.DOMqueries + - $scope.phantomasResults.metrics.DOMqueriesAvoidable * 2 + - $scope.phantomasResults.metrics.eventsBound; - if (score > 300) { - note = 'B'; - } - if (score > 500) { - note = 'C'; - } - if (score > 700) { - note = 'D'; - } - if (score > 1000) { - note = 'E'; - } - if (score > 1400) { - note = 'F'; - } - return note; - } - - function getJSBadPracticesScore() { - var note = 'A'; - var score = $scope.phantomasResults.metrics.documentWriteCalls * 3 + - $scope.phantomasResults.metrics.evalCalls * 2 + - $scope.phantomasResults.metrics.jsErrors * 10 + - $scope.phantomasResults.metrics.consoleMessages / 2 + - $scope.phantomasResults.metrics.globalVariables / 20 + - Math.sqrt($scope.inBodyDomManipulations); - if (score > 10) { - note = 'B'; - } - if (score > 15) { - note = 'C'; - } - if (score > 20) { - note = 'D'; - } - if (score > 30) { - note = 'E'; - } - if (score > 45) { - note = 'F'; - } - return note; - } - - function getJQueryLoadingScore() { - var note = 'NA'; - if ($scope.phantomasResults.metrics.jQueryDifferentVersions > 1) { - note = 'F'; - } else if ($scope.phantomasResults.metrics.jQueryVersion) { - if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.11.') === 0 || - $scope.phantomasResults.metrics.jQueryVersion.indexOf('1.12.') === 0 || - $scope.phantomasResults.metrics.jQueryVersion.indexOf('2.1.') === 0 || - $scope.phantomasResults.metrics.jQueryVersion.indexOf('2.2.') === 0) { - note = 'A'; - } else if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.9.') === 0 || - $scope.phantomasResults.metrics.jQueryVersion.indexOf('1.10.') === 0 || - $scope.phantomasResults.metrics.jQueryVersion.indexOf('2.0.') === 0) { - note = 'B'; - } else if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.7.') === 0 || - $scope.phantomasResults.metrics.jQueryVersion.indexOf('1.8.') === 0) { - note = 'C'; - } else if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.5.') === 0 || - $scope.phantomasResults.metrics.jQueryVersion.indexOf('1.6.') === 0) { - note = 'D'; - } else if ($scope.phantomasResults.metrics.jQueryVersion.indexOf('1.2.') === 0 || - $scope.phantomasResults.metrics.jQueryVersion.indexOf('1.3.') === 0 || - $scope.phantomasResults.metrics.jQueryVersion.indexOf('1.4.') === 0) { - note = 'E'; - } - } - return note; - } - - function getCSSComplexityScore() { - if (!$scope.phantomasResults.metrics.cssRules) { - return 'NA'; - } - - var note = 'A'; - var score = $scope.phantomasResults.metrics.cssRules + - $scope.phantomasResults.metrics.cssComplexSelectors * 5 + - $scope.phantomasResults.metrics.cssComplexSelectorsByAttribute * 10; - if (score > 800) { - note = 'B'; - } - if (score > 1200) { - note = 'C'; - } - if (score > 2500) { - note = 'D'; - } - if (score > 4000) { - note = 'E'; - } - if (score > 6000) { - note = 'F'; - } - return note; - } - - function getBadCssScore() { - if ($scope.phantomasResults.metrics.cssParsingErrors) { - return 'F'; - } else if (!$scope.phantomasResults.metrics.cssRules) { - return 'NA'; - } - - var note = 'A'; - var score = $scope.phantomasResults.metrics.cssDuplicatedSelectors + - $scope.phantomasResults.metrics.cssDuplicatedProperties + - $scope.phantomasResults.metrics.cssEmptyRules + - $scope.phantomasResults.metrics.cssExpressions * 10 + - $scope.phantomasResults.metrics.cssImportants * 2 + - $scope.phantomasResults.metrics.cssOldIEFixes * 10 + - $scope.phantomasResults.metrics.cssOldPropertyPrefixes + - $scope.phantomasResults.metrics.cssUniversalSelectors * 5 + - $scope.phantomasResults.metrics.cssRedundantBodySelectors * 0.5 + - $scope.phantomasResults.metrics.cssRedundantChildNodesSelectors * 0.5 + - $scope.phantomasResults.metrics.cssImports * 50; - if (score > 50) { - note = 'B'; - } - if (score > 100) { - note = 'C'; - } - if (score > 200) { - note = 'D'; - } - if (score > 500) { - note = 'E'; - } - if (score > 1000) { - note = 'F'; - } - return note; - } - - function requestsScore() { - var note = 'A'; - var score = $scope.phantomasResults.metrics.requests; - if (score > 30) { - note = 'B'; - } - if (score > 45) { - note = 'C'; - } - if (score > 60) { - note = 'D'; - } - if (score > 80) { - note = 'E'; - } - if (score > 100) { - note = 'F'; - } - return note; - } - - function networkScore() { - var note = 'A'; - var score = $scope.phantomasResults.metrics.notFound * 25 + - $scope.phantomasResults.metrics.closedConnections * 10 + - $scope.phantomasResults.metrics.multipleRequests * 10 + - $scope.phantomasResults.metrics.cachingDisabled * 2 + - $scope.phantomasResults.metrics.cachingNotSpecified + - $scope.phantomasResults.metrics.cachingTooShort / 2 + - $scope.phantomasResults.metrics.domains; - if (score > 20) { - note = 'B'; - } - if (score > 40) { - note = 'C'; - } - if (score > 60) { - note = 'D'; - } - if (score > 80) { - note = 'E'; - } - if (score > 100) { - note = 'F'; - } - return note; - } - - - - 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); - }); - } - } - -}); \ No newline at end of file diff --git a/app/public/scripts/showOffenders.js b/app/public/scripts/showOffenders.js deleted file mode 100644 index b4e16c8..0000000 --- a/app/public/scripts/showOffenders.js +++ /dev/null @@ -1,17 +0,0 @@ -var app = angular.module("ShowOffendersDirective", []); - -app.directive('showOffenders', function() { - return { - restrict: 'E', - transclude: true, - scope: { - modalTitle: "@", - metricName: "@", - phantomasResults: "=" - }, - controller: function($scope, $element, $attrs, $location) { - $scope.dialogShown = false; - }, - template: ' 
  • {{offender}}
' - }; -}); \ No newline at end of file diff --git a/app/public/styles/index.css b/app/public/styles/index.css deleted file mode 100644 index a555cef..0000000 --- a/app/public/styles/index.css +++ /dev/null @@ -1,33 +0,0 @@ -.promess { - padding: 0em 2em 3em; - font-weight: normal; - font-size: 1.2em; -} -.url { - width: 50%; -} -.launchBtn { - background: #e74c3c; - color: #fff; -} -input[type=submit], -input.url { - padding: 0 0.5em; - margin: 0.5em; - font-size: 1.2em; - height: 2em; - border: 0 solid; - border-radius: 0.5em; - box-shadow: 0.1em 0.2em 0 0 #5e2846; - outline: none; -} -input[type=submit]:hover { - color: #ddd; -} -input[type=submit].clicked { - color: #ddd; - position: relative; - left: 0.1em; - top: 0.2em; - box-shadow: none; -} diff --git a/app/public/styles/launchTest.css b/app/public/styles/launchTest.css deleted file mode 100644 index 47bcde3..0000000 --- a/app/public/styles/launchTest.css +++ /dev/null @@ -1,25 +0,0 @@ -#status { - margin-top: 2em; - font-size: 2.5em; -} -@-webkit-keyframes rotating { - from { - -webkit-transform: rotate(0deg); - } - to { - -webkit-transform: rotate(360deg); - } -} -@keyframes rotating { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} -.waiting .icon-lab { - -webkit-animation: rotating 3s linear 0s infinite; - -webkit-transform: translateZ(0); - animation: rotating 3s linear 0s infinite; -} diff --git a/app/public/styles/less/icons.less b/app/public/styles/less/icons.less deleted file mode 100644 index dbdc9f3..0000000 --- a/app/public/styles/less/icons.less +++ /dev/null @@ -1,56 +0,0 @@ -@eye-font-family: "fontsmith-icons"; -@eye-value: "\e004"; -@eye: '"fontsmith-icons"' '"\\e004"'; -@lab-font-family: "fontsmith-icons"; -@lab-value: "\e003"; -@lab: '"fontsmith-icons"' '"\\e003"'; -@bars-font-family: "fontsmith-icons"; -@bars-value: "\e005"; -@bars: '"fontsmith-icons"' '"\\e005"'; -@warning-font-family: "fontsmith-icons"; -@warning-value: "\e000"; -@warning: '"fontsmith-icons"' '"\\e000"'; -@arrow-left3-font-family: "fontsmith-icons"; -@arrow-left3-value: "\e006"; -@arrow-left3: '"fontsmith-icons"' '"\\e006"'; -@question-font-family: "fontsmith-icons"; -@question-value: "\e001"; -@question: '"fontsmith-icons"' '"\\e001"'; -@list-font-family: "fontsmith-icons"; -@list-value: "\e002"; -@list: '"fontsmith-icons"' '"\\e002"'; - -.icon-font-family(@char) { - font-family: ~`@{char}[0]`; -} - -.icon-font() { - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - -webkit-font-smoothing: antialiased; -} - -.icon-content(@char) { - content: ~`@{char}[1]`; -} - -.icon(@char) { - .icon-font-family(@char); - .icon-font(); - - &:before { - .icon-content(@char); - } -} - -@font-face { - font-family: "fontsmith-icons"; - src:url("/public/fonts/icons.woff") format("woff"), - ; - font-weight: normal; - font-style: normal; -} \ No newline at end of file diff --git a/app/public/styles/less/index.less b/app/public/styles/less/index.less deleted file mode 100644 index ff487ad..0000000 --- a/app/public/styles/less/index.less +++ /dev/null @@ -1,35 +0,0 @@ -.promess { - padding: 0em 2em 3em; - font-weight: normal; - font-size: 1.2em; -} - -.url { - width: 50%; -} - -.launchBtn { - background: #e74c3c; - color: #fff; -} - -input[type=submit], input.url { - padding: 0 0.5em; - margin: 0.5em; - font-size: 1.2em; - height: 2em; - border: 0 solid; - border-radius: 0.5em; - box-shadow: 0.1em 0.2em 0 0 #5e2846; - outline: none; -} -input[type=submit]:hover { - color: #ddd; -} -input[type=submit].clicked { - color: #ddd; - position: relative; - left: 0.1em; - top: 0.2em; - box-shadow: none; -} \ No newline at end of file diff --git a/app/public/styles/less/launchTest.less b/app/public/styles/less/launchTest.less deleted file mode 100644 index 92788fc..0000000 --- a/app/public/styles/less/launchTest.less +++ /dev/null @@ -1,19 +0,0 @@ -#status { - margin-top: 2em; - font-size: 2.5em; -} - -@-webkit-keyframes rotating { - from { -webkit-transform: rotate(0deg); } - to { -webkit-transform: rotate(360deg); } -} -@keyframes rotating { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } -} - -.waiting .icon-lab { - -webkit-animation: rotating 3s linear 0s infinite; - -webkit-transform: translateZ(0); - animation: rotating 3s linear 0s infinite; -} \ No newline at end of file diff --git a/app/public/styles/less/main.less b/app/public/styles/less/main.less deleted file mode 100644 index 79f6dd2..0000000 --- a/app/public/styles/less/main.less +++ /dev/null @@ -1,66 +0,0 @@ -@import "icons.less"; - -html { - margin: 100px 50px; -} - -body { - margin: 0 auto; - max-width: 1280px; - background: #9c4274; - color: #fff; - font-size: 16px; - text-align: center; -} - -body, input[type=submit], input[type=text], input[type=url], input[type=number], button { - font-family: 'Century Gothic', helvetica, arial, sans-serif; -} - -input[type=submit] { - cursor: pointer; -} - -h1 span { - display: inline-block; - height: 1em; - width: 1em; - color: #ffa319; -} - -.footer { - padding: 3em; - color: #4e1836; - a { - color: inherit; - } - .star { - font-weight: bold; - span { - font-size: 1.2em; - } - } -} - -/* Icons */ -.icon-lab { - .icon(@lab); -} -.icon-question { - .icon(@question); -} -.icon-warning { - .icon(@warning); -} -.icon-back { - .icon(@arrow-left3); -} -.icon-summary { - .icon(@list); -} -.icon-spaghetti { - .icon(@bars); -} -.icon-eye { - .icon(@eye); -} \ No newline at end of file diff --git a/app/public/styles/less/results.less b/app/public/styles/less/results.less deleted file mode 100644 index fbd9d64..0000000 --- a/app/public/styles/less/results.less +++ /dev/null @@ -1,497 +0,0 @@ - -/* 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; -} - -.resultsMenu .menuItem { - display: inline-block; - margin: 1em; - width: 8em; - height: 7em; - color: #fff; - border: 3px solid #fff; - border-radius: 0.5em; - cursor: pointer; - text-decoration: none; -} -.resultsMenu .back { - color: #5e2846; - border-color: #5e2846; -} -.resultsMenu .menuItem div { - padding-top: 0.5em; - font-size: 3em; -} -.resultsMenu .active, .resultsMenu .menuItem.active:hover { - color: #ffa319; - border-color: #ffa319; -} -.resultsMenu .menuItem:hover { - color: #ffa319; -} - -.resultsMenu span { - position: relative; - top: 0.5em; -} - -.testedUrl { - color: inherit; -} - -h4 { - margin-bottom: 0.5em; -} - -.summary, .metrics, .execution { - margin-top: 2em; - padding: 1em; - background: #fff; - color: #000; - border-radius: 0.5em; - text-align: left; -} - -.notations { - display: table; - width: 90%; - margin: 0 10%; - border-spacing: 1em; -} -.notations > div { - display: table-row; -} -.notations > div > div { - display: table-cell; - height: 2.5em; - vertical-align: middle; -} -.notations .notation { - font-weight: bold; - text-align: center; -} -.notations .criteria { - font-weight: normal; -} -.notations .A, .notations .B, .notations .C, .notations .D, .notations .E, .notations .F, .notations .NA { - width: 2.5em; - font-size: 2em; - text-align: center; - border-radius: 0.5em; - font-weight: bold; -} -.notations .A { - /* green */ - background: #00DB61; -} -.notations .B { - /* green */ - background: #CAD63D; -} -.notations .C { - /* yellow */ - background: #FFD119; -} -.notations .D { - /* orange */ - background: #FFA319; -} -.notations .E { - /* red */ - background: #FF6600; -} -.notations .F { - /* red */ - background: #FF1919; -} -.notations .NA { - /* Non applicable */ - background: #CCC; -} -.notations .icon-eye { - color: #9c4274; - cursor: pointer; -} - -.notations .criteria .table { - width: 75%; -} -.notations .criteria .label { - width: 70%; -} -.notations .criteria .result { - width: 20%; - font-weight: bold; - white-space: nowrap; - text-align: center; -} -.notations .warning .label, .notations .warning .result { - color: #FF1919; -} -.notations .criteria .info { - width: 10%; - text-align: center; -} -.notations .criteria .icon-question { - color: #f1c40f; - cursor: pointer; -} - - -.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 .details .icon-warning { - cursor: pointer; -} - -.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; -} - -.table .icon-warning { - color: #e74c3c; -} - - -/**** NgModal popin (have a look inside bower_components) ****/ -.ng-modal { - position: fixed; - z-index: 9999; - top: 0; - left: 0; - width: 100%; - height: 100%; - text-align: left; -} -.ng-modal-overlay { - position: absolute; - z-index: 9999; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: #000; - opacity: 0.5; -} -.ng-modal-dialog { - z-index: 10000; - position: absolute; - top: 50%; - left: 50%; - width: 50%; - transform: translate(-50%, -50%); - -webkit-transform: translate(-50%, -50%); - background-color: #fff; - padding: 10px; - border: 3px solid #f1c40f; - border-radius: 0.5em; - color: #000; -} -.ng-modal-dialog-content { - overflow-x: hidden; - overflow-y: scroll; - word-wrap: break-word; - max-height: 20em; - font-weight: normal; - white-space: normal; -} -.ng-modal-close { - position: absolute; - top: 3px; - right: 5px; - cursor: pointer; - font-size: 120%; - padding: 5px; - display: inline-block; -} -.ng-modal-close-x { - font-weight: bold; - font-family: Arial, sans-serif; -} -.ng-modal-title { - font-weight: bold; - font-size: 1.5em; - display: block; - margin-bottom: 10px; - padding-bottom: 7px; - border-bottom: solid 1px #999; -} diff --git a/app/public/styles/main.css b/app/public/styles/main.css deleted file mode 100644 index 5a8669b..0000000 --- a/app/public/styles/main.css +++ /dev/null @@ -1,139 +0,0 @@ -@font-face { - font-family: "fontsmith-icons"; - src: url("/public/fonts/icons.woff") format("woff"); - font-weight: normal; - font-style: normal; -} -html { - margin: 100px 50px; -} -body { - margin: 0 auto; - max-width: 1280px; - background: #9c4274; - color: #fff; - font-size: 16px; - text-align: center; -} -body, -input[type=submit], -input[type=text], -input[type=url], -input[type=number], -button { - font-family: 'Century Gothic', helvetica, arial, sans-serif; -} -input[type=submit] { - cursor: pointer; -} -h1 span { - display: inline-block; - height: 1em; - width: 1em; - color: #ffa319; -} -.footer { - padding: 3em; - color: #4e1836; -} -.footer a { - color: inherit; -} -.footer .star { - font-weight: bold; -} -.footer .star span { - font-size: 1.2em; -} -/* Icons */ -.icon-lab { - font-family: "fontsmith-icons"; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - -webkit-font-smoothing: antialiased; -} -.icon-lab:before { - content: "\e003"; -} -.icon-question { - font-family: "fontsmith-icons"; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - -webkit-font-smoothing: antialiased; -} -.icon-question:before { - content: "\e001"; -} -.icon-warning { - font-family: "fontsmith-icons"; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - -webkit-font-smoothing: antialiased; -} -.icon-warning:before { - content: "\e000"; -} -.icon-back { - font-family: "fontsmith-icons"; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - -webkit-font-smoothing: antialiased; -} -.icon-back:before { - content: "\e006"; -} -.icon-summary { - font-family: "fontsmith-icons"; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - -webkit-font-smoothing: antialiased; -} -.icon-summary:before { - content: "\e002"; -} -.icon-spaghetti { - font-family: "fontsmith-icons"; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - -webkit-font-smoothing: antialiased; -} -.icon-spaghetti:before { - content: "\e005"; -} -.icon-eye { - font-family: "fontsmith-icons"; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - -webkit-font-smoothing: antialiased; -} -.icon-eye:before { - content: "\e004"; -} diff --git a/app/public/styles/results.css b/app/public/styles/results.css deleted file mode 100644 index 05a0ac7..0000000 --- a/app/public/styles/results.css +++ /dev/null @@ -1,465 +0,0 @@ -/* Timeline colors, related to Window Performances */ -.resultsMenu { - margin-top: 2em; -} -.resultsMenu .menuItem { - display: inline-block; - margin: 1em; - width: 8em; - height: 7em; - color: #fff; - border: 3px solid #fff; - border-radius: 0.5em; - cursor: pointer; - text-decoration: none; -} -.resultsMenu .back { - color: #5e2846; - border-color: #5e2846; -} -.resultsMenu .menuItem div { - padding-top: 0.5em; - font-size: 3em; -} -.resultsMenu .active, -.resultsMenu .menuItem.active:hover { - color: #ffa319; - border-color: #ffa319; -} -.resultsMenu .menuItem:hover { - color: #ffa319; -} -.resultsMenu span { - position: relative; - top: 0.5em; -} -.testedUrl { - color: inherit; -} -h4 { - margin-bottom: 0.5em; -} -.summary, -.metrics, -.execution { - margin-top: 2em; - padding: 1em; - background: #fff; - color: #000; - border-radius: 0.5em; - text-align: left; -} -.notations { - display: table; - width: 90%; - margin: 0 10%; - border-spacing: 1em; -} -.notations > div { - display: table-row; -} -.notations > div > div { - display: table-cell; - height: 2.5em; - vertical-align: middle; -} -.notations .notation { - font-weight: bold; - text-align: center; -} -.notations .criteria { - font-weight: normal; -} -.notations .A, -.notations .B, -.notations .C, -.notations .D, -.notations .E, -.notations .F, -.notations .NA { - width: 2.5em; - font-size: 2em; - text-align: center; - border-radius: 0.5em; - font-weight: bold; -} -.notations .A { - /* green */ - background: #00DB61; -} -.notations .B { - /* green */ - background: #CAD63D; -} -.notations .C { - /* yellow */ - background: #FFD119; -} -.notations .D { - /* orange */ - background: #FFA319; -} -.notations .E { - /* red */ - background: #FF6600; -} -.notations .F { - /* red */ - background: #FF1919; -} -.notations .NA { - /* Non applicable */ - background: #CCC; -} -.notations .icon-eye { - color: #9c4274; - cursor: pointer; -} -.notations .criteria .table { - width: 75%; -} -.notations .criteria .label { - width: 70%; -} -.notations .criteria .result { - width: 20%; - font-weight: bold; - white-space: nowrap; - text-align: center; -} -.notations .warning .label, -.notations .warning .result { - color: #FF1919; -} -.notations .criteria .info { - width: 10%; - text-align: center; -} -.notations .criteria .icon-question { - color: #f1c40f; - cursor: pointer; -} -.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 .details .icon-warning { - cursor: pointer; -} -.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; -} -.table .icon-warning { - color: #e74c3c; -} -/**** NgModal popin (have a look inside bower_components) ****/ -.ng-modal { - position: fixed; - z-index: 9999; - top: 0; - left: 0; - width: 100%; - height: 100%; - text-align: left; -} -.ng-modal-overlay { - position: absolute; - z-index: 9999; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: #000; - opacity: 0.5; -} -.ng-modal-dialog { - z-index: 10000; - position: absolute; - top: 50%; - left: 50%; - width: 50%; - transform: translate(-50%, -50%); - -webkit-transform: translate(-50%, -50%); - background-color: #fff; - padding: 10px; - border: 3px solid #f1c40f; - border-radius: 0.5em; - color: #000; -} -.ng-modal-dialog-content { - overflow-x: hidden; - overflow-y: scroll; - word-wrap: break-word; - max-height: 20em; - font-weight: normal; - white-space: normal; -} -.ng-modal-close { - position: absolute; - top: 3px; - right: 5px; - cursor: pointer; - font-size: 120%; - padding: 5px; - display: inline-block; -} -.ng-modal-close-x { - font-weight: bold; - font-family: Arial, sans-serif; -} -.ng-modal-title { - font-weight: bold; - font-size: 1.5em; - display: block; - margin-bottom: 10px; - padding-bottom: 7px; - border-bottom: solid 1px #999; -} diff --git a/app/public/img/favicon.png b/front/src/img/favicon.png similarity index 100% rename from app/public/img/favicon.png rename to front/src/img/favicon.png diff --git a/front/src/main.html b/front/src/main.html index d6cddde..1d55d31 100644 --- a/front/src/main.html +++ b/front/src/main.html @@ -3,10 +3,13 @@ Yellow Lab Tools - - - - + + + + + + + @@ -18,18 +21,13 @@ - -
- - - - + \ No newline at end of file diff --git a/package.json b/package.json index 1c0eb59..c7cfe8c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yellowlabtools", - "version": "1.0.0", + "version": "1.1.0", "repository": { "type": "git", "url": "git://github.com/gmetais/YellowLabTools.git" @@ -17,7 +17,7 @@ "express": "~4.10.4", "phantomas": "1.7.0", "rimraf": "^2.2.8", - "socket.io": "~1.2.0" + "q": "^1.1.2" }, "devDependencies": { "chai": "^1.10.0", @@ -33,7 +33,6 @@ "matchdep": "^0.3.0", "mocha": "^2.0.1", "phantomjs": "^1.9.12", - "q": "^1.1.2", "request": "^2.49.0", "sinon": "^1.12.1", "sinon-chai": "^2.6.0" diff --git a/server.js b/server.js deleted file mode 100644 index dcb4b1a..0000000 --- a/server.js +++ /dev/null @@ -1,59 +0,0 @@ -// Config file -var settings = require('./server_config/settings.json'); - -// Libraries -var fs = require('fs'); -var async = require('async'); -var express = require('express'); -var app = express(); -var server = require('http').createServer(app); -var io = require('socket.io').listen(server); -var bodyParser = require('body-parser'); -var compress = require('compression'); - -// Internals -var indexController = require('./app/node_controllers/indexController'); -var launchTestController = require('./app/node_controllers/launchTestController'); -var resultsController = require('./app/node_controllers/resultsController'); -var waitingQueueSocket = require('./app/node_controllers/waitingQueueSocket'); -var testQueue = require('./app/lib/testQueue'); - -app.use(compress()); -app.use(bodyParser.urlencoded({ extended: false })); - - -// Redirect www.yellowlab.tools to yellowlab.tools (for SEO) -app.all('*', function(req, res, next) { - if (req.hostname && req.hostname.match(/^www\.yellowlab\.tools/) !== null) { - res.redirect('http://' + req.hostname.replace(/^www\.yellowlab\.tools/, 'yellowlab.tools') + req.url); - } else { - next(); - } -}); - -// Routes definition -app.get('/', function(req, res) { indexController(req, res, settings.googleAnalyticsId); }); -app.post('/launchTest', function(req, res) { launchTestController(req, res, testQueue, settings.googleAnalyticsId); }); -app.get('/results/:testId', function(req, res) { resultsController(req, res, settings.googleAnalyticsId); }); - - -// Static files -app.use('/public', express.static(__dirname + '/app/public')); -app.use('/bower_components', express.static(__dirname + '/bower_components')); - - -// Socket.io -io.on('connection', function(socket){ - waitingQueueSocket(socket, testQueue); -}); - -// Create the results folder if it doesn't exist -var resultsPath = 'results'; -if (!fs.existsSync(resultsPath)) { - fs.mkdirSync(resultsPath); -} - -// Launch the server -server.listen(settings.serverPort, function() { - console.log('Listening on port %d', server.address().port); -}); \ No newline at end of file diff --git a/src b/src deleted file mode 100644 index 36a8a55..0000000 --- a/src +++ /dev/null @@ -1,672 +0,0 @@ -/* Timeline colors, related to Window Performances */ -.resultsMenu { - margin-top: 2em; -} -.resultsMenu .menuItem { - display: inline-block; - margin: 1em; - width: 8em; - height: 7em; - color: #fff; - border: 3px solid #fff; - border-radius: 0.5em; - cursor: pointer; - text-decoration: none; -} -.resultsMenu .back { - color: #5e2846; - border-color: #5e2846; -} -.resultsMenu .menuItem div { - padding-top: 0.5em; - font-size: 3em; -} -.resultsMenu .active, -.resultsMenu .menuItem.active:hover { - color: #ffa319; - border-color: #ffa319; -} -.resultsMenu .menuItem:hover { - color: #ffa319; -} -.resultsMenu span { - position: relative; - top: 0.5em; -} -.testedUrl { - color: inherit; -} -h4 { - margin-bottom: 0.5em; -} -.summary, -.metrics, -.execution { - margin-top: 2em; - padding: 1em; - background: #fff; - color: #000; - border-radius: 0.5em; - text-align: left; -} -.notations { - display: table; - width: 90%; - margin: 0 10%; - border-spacing: 1em; -} -.notations > div { - display: table-row; -} -.notations > div > div { - display: table-cell; - height: 2.5em; - vertical-align: middle; -} -.notations .notation { - font-weight: bold; - text-align: center; -} -.notations .criteria { - font-weight: normal; -} -.notations .A, -.notations .B, -.notations .C, -.notations .D, -.notations .E, -.notations .F, -.notations .NA { - width: 2.5em; - font-size: 2em; - text-align: center; - border-radius: 0.5em; - font-weight: bold; -} -.notations .A { - /* green */ - background: #00DB61; -} -.notations .B { - /* green */ - background: #CAD63D; -} -.notations .C { - /* yellow */ - background: #FFD119; -} -.notations .D { - /* orange */ - background: #FFA319; -} -.notations .E { - /* red */ - background: #FF6600; -} -.notations .F { - /* red */ - background: #FF1919; -} -.notations .NA { - /* Non applicable */ - background: #CCC; -} -.notations .icon-eye { - color: #9c4274; - cursor: pointer; -} -.notations .criteria .table { - width: 75%; -} -.notations .criteria .label { - width: 70%; -} -.notations .criteria .result { - width: 20%; - font-weight: bold; - white-space: nowrap; - text-align: center; -} -.notations .warning .label, -.notations .warning .result { - color: #FF1919; -} -.notations .criteria .info { - width: 10%; - text-align: center; -} -.notations .criteria .icon-question { - color: #f1c40f; - cursor: pointer; -} -.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 .details .icon-warning { - cursor: pointer; -} -.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; -} -.table .icon-warning { - color: #e74c3c; -} -/**** NgModal popin (have a look inside bower_components) ****/ -.ng-modal { - position: fixed; - z-index: 9999; - top: 0; - left: 0; - width: 100%; - height: 100%; - text-align: left; -} -.ng-modal-overlay { - position: absolute; - z-index: 9999; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: #000; - opacity: 0.5; -} -.ng-modal-dialog { - z-index: 10000; - position: absolute; - top: 50%; - left: 50%; - width: 50%; - transform: translate(-50%, -50%); - -webkit-transform: translate(-50%, -50%); - background-color: #fff; - padding: 10px; - border: 3px solid #f1c40f; - border-radius: 0.5em; - color: #000; -} -.ng-modal-dialog-content { - overflow-x: hidden; - overflow-y: scroll; - word-wrap: break-word; - max-height: 20em; - font-weight: normal; - white-space: normal; -} -.ng-modal-close { - position: absolute; - top: 3px; - right: 5px; - cursor: pointer; - font-size: 120%; - padding: 5px; - display: inline-block; -} -.ng-modal-close-x { - font-weight: bold; - font-family: Arial, sans-serif; -} -.ng-modal-title { - font-weight: bold; - font-size: 1.5em; - display: block; - margin-bottom: 10px; - padding-bottom: 7px; - border-bottom: solid 1px #999; -} - -@font-face { - font-family: "fontsmith-icons"; - src: url("/public/fonts/icons.woff") format("woff"); - font-weight: normal; - font-style: normal; -} - -.promess { - padding: 0em 2em 3em; - font-weight: normal; - font-size: 1.2em; -} -.url { - width: 50%; -} -.launchBtn { - background: #e74c3c; - color: #fff; -} -input[type=submit], -input.url { - padding: 0 0.5em; - margin: 0.5em; - font-size: 1.2em; - height: 2em; - border: 0 solid; - border-radius: 0.5em; - box-shadow: 0.1em 0.2em 0 0 #5e2846; - outline: none; -} -input[type=submit]:hover { - color: #ddd; -} -input[type=submit].clicked { - color: #ddd; - position: relative; - left: 0.1em; - top: 0.2em; - box-shadow: none; -} - -#status { - margin-top: 2em; - font-size: 2.5em; -} -@-webkit-keyframes rotating { - from { - -webkit-transform: rotate(0deg); - } - to { - -webkit-transform: rotate(360deg); - } -} -@keyframes rotating { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} -.waiting .icon-lab { - -webkit-animation: rotating 3s linear 0s infinite; - -webkit-transform: translateZ(0); - animation: rotating 3s linear 0s infinite; -} - -@font-face { - font-family: "fontsmith-icons"; - src: url("/public/fonts/icons.woff") format("woff"); - font-weight: normal; - font-style: normal; -} -html { - margin: 100px 50px; -} -body { - margin: 0 auto; - max-width: 1280px; - background: #9c4274; - color: #fff; - font-size: 16px; - text-align: center; -} -body, -input[type=submit], -input[type=text], -input[type=url], -input[type=number], -button { - font-family: 'Century Gothic', helvetica, arial, sans-serif; -} -input[type=submit] { - cursor: pointer; -} -h1 span { - display: inline-block; - height: 1em; - width: 1em; - color: #ffa319; -} -.footer { - padding: 3em; - color: #4e1836; -} -.footer a { - color: inherit; -} -.footer .star { - font-weight: bold; -} -.footer .star span { - font-size: 1.2em; -} -/* Icons */ -.icon-lab { - font-family: "fontsmith-icons"; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - -webkit-font-smoothing: antialiased; -} -.icon-lab:before { - content: "\e003"; -} -.icon-question { - font-family: "fontsmith-icons"; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - -webkit-font-smoothing: antialiased; -} -.icon-question:before { - content: "\e001"; -} -.icon-warning { - font-family: "fontsmith-icons"; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - -webkit-font-smoothing: antialiased; -} -.icon-warning:before { - content: "\e000"; -} -.icon-back { - font-family: "fontsmith-icons"; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - -webkit-font-smoothing: antialiased; -} -.icon-back:before { - content: "\e006"; -} -.icon-summary { - font-family: "fontsmith-icons"; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - -webkit-font-smoothing: antialiased; -} -.icon-summary:before { - content: "\e002"; -} -.icon-spaghetti { - font-family: "fontsmith-icons"; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - -webkit-font-smoothing: antialiased; -} -.icon-spaghetti:before { - content: "\e005"; -} -.icon-eye { - font-family: "fontsmith-icons"; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - -webkit-font-smoothing: antialiased; -} -.icon-eye:before { - content: "\e004"; -} From 3320e993e7e039d837d7a0834fe6d306defb726f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sat, 20 Dec 2014 17:42:49 +0100 Subject: [PATCH 29/98] Improve colors --- front/src/css/dashboard.css | 15 +++++++++------ front/src/less/dashboard.less | 15 +++++++++------ front/src/less/main.less | 2 +- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/front/src/css/dashboard.css b/front/src/css/dashboard.css index 8992c43..112110f 100644 --- a/front/src/css/dashboard.css +++ b/front/src/css/dashboard.css @@ -99,27 +99,27 @@ h4 { } .notations .A { /* green */ - background: #00DB61; + background: #0C4; } .notations .B { /* green */ - background: #CAD600; + background: #CD0; } .notations .C { /* yellow */ - background: #FFD119; + background: #FD2; } .notations .D { /* orange */ - background: #FFA319; + background: #FA2; } .notations .E { /* red */ - background: #FF6600; + background: #F60; } .notations .F { /* red */ - background: #FF1919; + background: #F22; } .notations .NA { /* Non applicable */ @@ -132,6 +132,9 @@ h4 { .notations .criteria .table { width: 75%; } +.notations .criteria .grade { + width: 10%; +} .notations .criteria .label { width: 70%; } diff --git a/front/src/less/dashboard.less b/front/src/less/dashboard.less index 74a9931..0f9d068 100644 --- a/front/src/less/dashboard.less +++ b/front/src/less/dashboard.less @@ -105,27 +105,27 @@ h4 { } .notations .A { /* green */ - background: #00DB61; + background: #0C4; } .notations .B { /* green */ - background: #CAD600; + background: #CD0; } .notations .C { /* yellow */ - background: #FFD119; + background: #FD2; } .notations .D { /* orange */ - background: #FFA319; + background: #FA2; } .notations .E { /* red */ - background: #FF6600; + background: #F60; } .notations .F { /* red */ - background: #FF1919; + background: #F22; } .notations .NA { /* Non applicable */ @@ -139,6 +139,9 @@ h4 { .notations .criteria .table { width: 75%; } +.notations .criteria .grade { + width: 10%; +} .notations .criteria .label { width: 70%; } diff --git a/front/src/less/main.less b/front/src/less/main.less index 79f6dd2..f471fd0 100644 --- a/front/src/less/main.less +++ b/front/src/less/main.less @@ -30,7 +30,7 @@ h1 span { .footer { padding: 3em; - color: #4e1836; + color: #000; a { color: inherit; } From b6a9f5109c2ca1179ed571298959917b9356833c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sat, 20 Dec 2014 17:43:21 +0100 Subject: [PATCH 30/98] Improve colors --- front/src/css/main.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/css/main.css b/front/src/css/main.css index 1ef1db3..b807d79 100644 --- a/front/src/css/main.css +++ b/front/src/css/main.css @@ -34,7 +34,7 @@ h1 span { } .footer { padding: 3em; - color: #4e1836; + color: #000; } .footer a { color: inherit; From 01fe0ca38158efa3200dbc34577f20b27746df78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sat, 20 Dec 2014 17:55:42 +0100 Subject: [PATCH 31/98] Optimized category score bubble position --- front/src/css/dashboard.css | 7 +++++-- front/src/less/dashboard.less | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/front/src/css/dashboard.css b/front/src/css/dashboard.css index 112110f..9690c0b 100644 --- a/front/src/css/dashboard.css +++ b/front/src/css/dashboard.css @@ -94,8 +94,7 @@ h4 { height: 1em; font-size: 1em; color: transparent; - position: relative; - top: 0.15em; + margin: 0 auto; } .notations .A { /* green */ @@ -134,6 +133,9 @@ h4 { } .notations .criteria .grade { width: 10%; + padding-left: 0.5em; + padding-right: 0.5em; + vertical-align: middle; } .notations .criteria .label { width: 70%; @@ -143,6 +145,7 @@ h4 { font-weight: bold; white-space: nowrap; text-align: center; + vertical-align: middle; } .notations .warning .label, .notations .warning .result { diff --git a/front/src/less/dashboard.less b/front/src/less/dashboard.less index 0f9d068..f5b1a05 100644 --- a/front/src/less/dashboard.less +++ b/front/src/less/dashboard.less @@ -99,8 +99,7 @@ h4 { height: 1em; font-size: 1em; color: transparent; - position: relative; - top: 0.15em; + margin: 0 auto; } } .notations .A { @@ -141,6 +140,9 @@ h4 { } .notations .criteria .grade { width: 10%; + padding-left: 0.5em; + padding-right: 0.5em; + vertical-align: middle; } .notations .criteria .label { width: 70%; @@ -150,6 +152,7 @@ h4 { font-weight: bold; white-space: nowrap; text-align: center; + vertical-align: middle; } .notations .warning .label, .notations .warning .result { color: #FF1919; From 301ebd9b6fe040dd58029565fc2a70c8bed34f36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sat, 20 Dec 2014 18:37:31 +0100 Subject: [PATCH 32/98] Re-enable warnings on dashboard --- front/src/views/dashboard.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/front/src/views/dashboard.html b/front/src/views/dashboard.html index c128f64..d43ee3d 100644 --- a/front/src/views/dashboard.html +++ b/front/src/views/dashboard.html @@ -7,8 +7,10 @@
{{category.label}}
-
-
+
+
+ +
{{rule.policy.label}}
{{rule.value}}
From 4cca167fc1cb421598ac17b38d5ff7d4f528ef97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sat, 20 Dec 2014 18:38:48 +0100 Subject: [PATCH 33/98] Change warning limit for not so important metrics --- lib/metadata/policies.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/metadata/policies.js b/lib/metadata/policies.js index eedebe6..fda35a0 100644 --- a/lib/metadata/policies.js +++ b/lib/metadata/policies.js @@ -268,7 +268,7 @@ var policies = { "message": "

Universal selectors are the most expensive CSS selectors.

More informations here.

", "isOkThreshold": 0, "isBadThreshold": 40, - "isAbnormalThreshold": 80 + "isAbnormalThreshold": 150 }, "cssRedundantBodySelectors": { "tool": "phantomas", @@ -276,7 +276,7 @@ var policies = { "message": "

This is one way to remove complexity from a CSS rule. Generally, when \"body\" is specified in a rule it can be removed, because an element is necessarily inside the body.

", "isOkThreshold": 0, "isBadThreshold": 50, - "isAbnormalThreshold": 150 + "isAbnormalThreshold": 200 }, "cssRedundantChildNodesSelectors": { "tool": "phantomas", @@ -284,7 +284,7 @@ var policies = { "message": "

Some tags included inside other tags are obvious. For example, when \"ul li\" is specified in a rule, \"ul\" can be removed because the \"li\" element is always inside a \"ul\". Same thing for \"tr td\", \"select option\", ...

Lowering compexity in CSS selectors can make the page load a little faster.

", "isOkThreshold": 0, "isBadThreshold": 50, - "isAbnormalThreshold": 150 + "isAbnormalThreshold": 200 }, "requests": { "tool": "phantomas", From 95a5f5063ec8b61d0ea8fb1b8c4676bbc5f278d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sat, 20 Dec 2014 18:43:11 +0100 Subject: [PATCH 34/98] Repair test submission on Enter key --- front/src/views/index.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/front/src/views/index.html b/front/src/views/index.html index 4a6df0c..f676eee 100644 --- a/front/src/views/index.html +++ b/front/src/views/index.html @@ -1,4 +1,5 @@

Free online test to help speeding up heavy web pages

- - -

(This is a BETA, your feedback is more than welcome)

\ No newline at end of file +
+ + +
\ No newline at end of file From 3dc4f4641cbf75f1badc0963056990690aef5fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sun, 21 Dec 2014 10:55:19 +0100 Subject: [PATCH 35/98] Remove console.log --- front/src/js/directives/gradeDirective.js | 1 - 1 file changed, 1 deletion(-) diff --git a/front/src/js/directives/gradeDirective.js b/front/src/js/directives/gradeDirective.js index 785ba17..bbe82c8 100644 --- a/front/src/js/directives/gradeDirective.js +++ b/front/src/js/directives/gradeDirective.js @@ -11,7 +11,6 @@ gradeDirective.directive('grade', function() { replace: true, controller : function($scope) { $scope.getGrade = function(score) { - console.log(score); if (score >= 85) { return 'A'; } From ed4f4483707f3ac6eefe749de9ffc36e900daaae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sun, 21 Dec 2014 11:42:09 +0100 Subject: [PATCH 36/98] Re-introduce menu on the result page --- front/src/js/app.js | 3 +- front/src/js/controllers/dashboardCtrl.js | 7 +++-- front/src/js/services/menuService.js | 34 +++++++++++++++++++++++ front/src/main.html | 1 + front/src/views/dashboard.html | 1 + front/src/views/resultSubHeader.html | 7 +++++ server_config/settings.json | 2 +- 7 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 front/src/js/services/menuService.js create mode 100644 front/src/views/resultSubHeader.html diff --git a/front/src/js/app.js b/front/src/js/app.js index 47c3210..0c48a13 100644 --- a/front/src/js/app.js +++ b/front/src/js/app.js @@ -6,7 +6,8 @@ var yltApp = angular.module('YellowLabTools', [ 'queueCtrl', 'runsFactory', 'resultsFactory', - 'gradeDirective' + 'menuService', + 'gradeDirective', ]); yltApp.config(['$routeProvider', '$locationProvider', diff --git a/front/src/js/controllers/dashboardCtrl.js b/front/src/js/controllers/dashboardCtrl.js index fcc5d95..2d8e22b 100644 --- a/front/src/js/controllers/dashboardCtrl.js +++ b/front/src/js/controllers/dashboardCtrl.js @@ -1,8 +1,9 @@ -var dashboardCtrl = angular.module('dashboardCtrl', ['resultsFactory']); +var dashboardCtrl = angular.module('dashboardCtrl', ['resultsFactory', 'menuService']); -dashboardCtrl.controller('DashboardCtrl', ['$scope', '$routeParams', 'Results', function($scope, $routeParams, Results) { - $scope.dashboard = "this is a dashboard"; +dashboardCtrl.controller('DashboardCtrl', ['$scope', '$routeParams', 'Results', 'Menu', function($scope, $routeParams, 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); diff --git a/front/src/js/services/menuService.js b/front/src/js/services/menuService.js new file mode 100644 index 0000000..c6ec6f4 --- /dev/null +++ b/front/src/js/services/menuService.js @@ -0,0 +1,34 @@ +var menuService = angular.module('menuService', []); + +menuService.factory('Menu', ['$location', function($location) { + + var currentPage, currentRunId; + + return { + getCurrentPage: function() { + return currentPage; + }, + setCurrentPage: function(page, runId) { + currentPage = page; + currentRunId = runId; + + return this; + }, + changePage: function(page) { + switch (page) { + case 'index': + $location.path('/'); + break; + case 'dashboard': + $location.path('/result/' + currentRunId); + break; + case 'timeline': + $location.path('/result/' + currentRunId + '/timeline'); + break; + default: + console.err('Undefined Menu.changePage() destination'); + } + } + } + +}]); \ No newline at end of file diff --git a/front/src/main.html b/front/src/main.html index 1d55d31..f062e71 100644 --- a/front/src/main.html +++ b/front/src/main.html @@ -20,6 +20,7 @@ + diff --git a/front/src/views/dashboard.html b/front/src/views/dashboard.html index d43ee3d..ee492c7 100644 --- a/front/src/views/dashboard.html +++ b/front/src/views/dashboard.html @@ -1,3 +1,4 @@ +

Grades

diff --git a/front/src/views/resultSubHeader.html b/front/src/views/resultSubHeader.html new file mode 100644 index 0000000..e583b36 --- /dev/null +++ b/front/src/views/resultSubHeader.html @@ -0,0 +1,7 @@ +
Tested url:   {{result.params.url}}
+ +
+
New test
+ + +
\ No newline at end of file diff --git a/server_config/settings.json b/server_config/settings.json index 5c69980..e03ad0f 100644 --- a/server_config/settings.json +++ b/server_config/settings.json @@ -6,5 +6,5 @@ "1234567890": "contact@gaelmetais.com" }, "maxAnonymousRunsPerDay": 50, - "maxAnonymousCallsPerDay": 1000 + "maxAnonymousCallsPerDay": 2000 } \ No newline at end of file From 6eefc0a6238a89723ddfa7e6e7123d423ffc09d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sun, 21 Dec 2014 11:48:35 +0100 Subject: [PATCH 37/98] jshint --- front/src/js/services/menuService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/js/services/menuService.js b/front/src/js/services/menuService.js index c6ec6f4..7b56c74 100644 --- a/front/src/js/services/menuService.js +++ b/front/src/js/services/menuService.js @@ -29,6 +29,6 @@ menuService.factory('Menu', ['$location', function($location) { console.err('Undefined Menu.changePage() destination'); } } - } + }; }]); \ No newline at end of file From 0862116a40586ba06585400b00c6d3ebabb38234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sun, 21 Dec 2014 14:19:04 +0100 Subject: [PATCH 38/98] Cooler abnormality score for JS errors for the moment --- lib/metadata/policies.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/metadata/policies.js b/lib/metadata/policies.js index fda35a0..c5a21aa 100644 --- a/lib/metadata/policies.js +++ b/lib/metadata/policies.js @@ -71,7 +71,7 @@ var policies = { "message": "

Just to let you know there are some errors on the page.

Please note that some errors only occur in the PhantomJS browser, so you might need to double check on other browsers.

", "isOkThreshold": 0, "isBadThreshold": 1, - "isAbnormalThreshold": 1 + "isAbnormalThreshold": 2 }, "evalCalls": { "tool": "phantomas", From c1257afc4c40a623246ffd0528088674bcd641fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sun, 21 Dec 2014 14:19:56 +0100 Subject: [PATCH 39/98] Add onclick property on the rules on dashboard --- front/src/css/dashboard.css | 59 ++++++++++++++--------- front/src/js/app.js | 8 +++ front/src/js/controllers/dashboardCtrl.js | 6 ++- front/src/less/dashboard.less | 49 +++++++++++-------- front/src/views/dashboard.html | 19 ++++++-- lib/server/controllers/frontController.js | 2 +- 6 files changed, 94 insertions(+), 49 deletions(-) diff --git a/front/src/css/dashboard.css b/front/src/css/dashboard.css index 9690c0b..9d64baf 100644 --- a/front/src/css/dashboard.css +++ b/front/src/css/dashboard.css @@ -51,7 +51,7 @@ h4 { } .notations { display: table; - width: 90%; + width: 80%; margin: 0 10%; border-spacing: 1em; } @@ -63,21 +63,25 @@ h4 { height: 2.5em; vertical-align: middle; } -.notations .notation { +.notations .category { font-weight: bold; text-align: center; + width: 20%; } .notations .criteria { font-weight: normal; + width: 75%; } -.notations .A, -.notations .B, -.notations .C, -.notations .D, -.notations .E, -.notations .F, -.notations .NA { +.notations .A.categoryScore, +.notations .B.categoryScore, +.notations .C.categoryScore, +.notations .D.categoryScore, +.notations .E.categoryScore, +.notations .F.categoryScore, +.notations .NA.categoryScore { width: 2.5em; + max-width: 2.5em; + min-width: 2.5em; font-size: 2em; text-align: center; border-radius: 0.5em; @@ -95,6 +99,7 @@ h4 { font-size: 1em; color: transparent; margin: 0 auto; + border-radius: 0.5em; } .notations .A { /* green */ @@ -124,12 +129,18 @@ h4 { /* Non applicable */ background: #CCC; } -.notations .icon-eye { - color: #9c4274; +.notations .criteria .table { + width: 100%; +} +.notations .criteria .table > div:hover > div { + background: #F1C40F; cursor: pointer; } -.notations .criteria .table { - width: 75%; +.notations .criteria .table > div:hover > div.info { + background: #FFF; +} +.notations .criteria .table > div:hover > div.info .icon-question { + color: #F1C40F; } .notations .criteria .grade { width: 10%; @@ -141,23 +152,27 @@ h4 { width: 70%; } .notations .criteria .result { - width: 20%; + width: 18%; font-weight: bold; white-space: nowrap; text-align: center; vertical-align: middle; } .notations .warning .label, -.notations .warning .result { +.notations .warning .result, +.notations .icon-warning { color: #FF1919; } .notations .criteria .info { - width: 10%; + width: 2%; text-align: center; + vertical-align: middle; + background: #FFF; + padding-left: 0.1em; + padding-right: 0.1em; } .notations .criteria .icon-question { - color: #f1c40f; - cursor: pointer; + color: transparent; } .timeline { margin: 2em 0 5em; @@ -363,8 +378,9 @@ input.textFilter { color: #f1c40f; cursor: pointer; } -.table .details .icon-warning { - cursor: pointer; +.table .icon-warning { + display: inline-block; + width: 0.8em; } .detailsOverlay { position: absolute; @@ -417,9 +433,6 @@ input.textFilter { .table > div > .startTime.domCreation { background: #ffe0cc; } -.table .icon-warning { - color: #e74c3c; -} /**** NgModal popin (have a look inside bower_components) ****/ .ng-modal { position: fixed; diff --git a/front/src/js/app.js b/front/src/js/app.js index 0c48a13..6d44767 100644 --- a/front/src/js/app.js +++ b/front/src/js/app.js @@ -29,6 +29,14 @@ yltApp.config(['$routeProvider', '$locationProvider', templateUrl: 'front/views/dashboard.html', controller: 'DashboardCtrl' }). + when('/result/:runId/timeline', { + templateUrl: 'front/views/timeline.html', + controller: 'TimelineCtrl' + }). + when('/result/:runId/rule/:policy', { + templateUrl: 'front/views/rule.html', + controller: 'RuleCtrl' + }). otherwise({ redirectTo: '/' }); diff --git a/front/src/js/controllers/dashboardCtrl.js b/front/src/js/controllers/dashboardCtrl.js index 2d8e22b..3598b70 100644 --- a/front/src/js/controllers/dashboardCtrl.js +++ b/front/src/js/controllers/dashboardCtrl.js @@ -1,6 +1,6 @@ var dashboardCtrl = angular.module('dashboardCtrl', ['resultsFactory', 'menuService']); -dashboardCtrl.controller('DashboardCtrl', ['$scope', '$routeParams', 'Results', 'Menu', function($scope, $routeParams, Results, Menu) { +dashboardCtrl.controller('DashboardCtrl', ['$scope', '$routeParams', '$location', 'Results', 'Menu', function($scope, $routeParams, $location, Results, Menu) { $scope.runId = $routeParams.runId; $scope.Menu = Menu.setCurrentPage('dashboard', $scope.runId); @@ -8,4 +8,8 @@ dashboardCtrl.controller('DashboardCtrl', ['$scope', '$routeParams', 'Results', $scope.result = result; console.log(result); }); + + $scope.showRulePage = function(ruleName) { + $location.path('/result/' + $scope.runId + '/rule/' + ruleName); + }; }]); \ No newline at end of file diff --git a/front/src/less/dashboard.less b/front/src/less/dashboard.less index f5b1a05..629df3b 100644 --- a/front/src/less/dashboard.less +++ b/front/src/less/dashboard.less @@ -67,7 +67,7 @@ h4 { .notations { display: table; - width: 90%; + width: 80%; margin: 0 10%; border-spacing: 1em; } @@ -79,16 +79,20 @@ h4 { height: 2.5em; vertical-align: middle; } -.notations .notation { +.notations .category { font-weight: bold; text-align: center; + width: 20%; } .notations .criteria { font-weight: normal; + width: 75%; } .A, .B, .C, .D, .E, .F, .NA { - .notations & { + .notations &.categoryScore { width: 2.5em; + max-width: 2.5em; + min-width: 2.5em; font-size: 2em; text-align: center; border-radius: 0.5em; @@ -100,6 +104,7 @@ h4 { font-size: 1em; color: transparent; margin: 0 auto; + border-radius: 0.5em; } } .notations .A { @@ -130,13 +135,19 @@ h4 { /* Non applicable */ background: #CCC; } -.notations .icon-eye { - color: #9c4274; - cursor: pointer; -} .notations .criteria .table { - width: 75%; + width: 100%; + > div:hover > div { + background: #F1C40F; + cursor: pointer; + &.info { + background: #FFF; + .icon-question { + color: #F1C40F; + } + } + } } .notations .criteria .grade { width: 10%; @@ -148,22 +159,25 @@ h4 { width: 70%; } .notations .criteria .result { - width: 20%; + width: 18%; font-weight: bold; white-space: nowrap; text-align: center; vertical-align: middle; } -.notations .warning .label, .notations .warning .result { +.notations .warning .label, .notations .warning .result, .notations .icon-warning { color: #FF1919; } .notations .criteria .info { - width: 10%; + width: 2%; text-align: center; + vertical-align: middle; + background: #FFF; + padding-left: 0.1em; + padding-right: 0.1em; } .notations .criteria .icon-question { - color: #f1c40f; - cursor: pointer; + color: transparent; } @@ -387,8 +401,9 @@ input.textFilter { color: #f1c40f; cursor: pointer; } -.table .details .icon-warning { - cursor: pointer; +.table .icon-warning { + display: inline-block; + width: 0.8em; } .detailsOverlay { @@ -443,10 +458,6 @@ input.textFilter { background: @domCreationBg; } -.table .icon-warning { - color: #e74c3c; -} - /**** NgModal popin (have a look inside bower_components) ****/ .ng-modal { diff --git a/front/src/views/dashboard.html b/front/src/views/dashboard.html index ee492c7..cfb3f01 100644 --- a/front/src/views/dashboard.html +++ b/front/src/views/dashboard.html @@ -4,16 +4,25 @@
- -
{{category.label}}
+ +
{{category.label}}
-
-
+
+
{{rule.policy.label}}
-
{{rule.value}}
+
+ {{rule.value}} + + + +
+
diff --git a/lib/server/controllers/frontController.js b/lib/server/controllers/frontController.js index 25b84cc..ebcf139 100644 --- a/lib/server/controllers/frontController.js +++ b/lib/server/controllers/frontController.js @@ -4,7 +4,7 @@ var express = require('express'); var FrontController = function(app) { 'use strict'; - var routes = ['/', '/about', '/result/:runId', '/queue/:runId']; + var routes = ['/', '/about', '/result/:runId', '/result/:runId/timeline', '/result/:runId/rule/:policy', '/queue/:runId']; routes.forEach(function(route) { app.get(route, function(req, res) { From cb200755993132f5d7c7cc05e7a64e9ae1cd431e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 22 Dec 2014 14:07:16 +0100 Subject: [PATCH 40/98] Re-construct the JS Timeline page --- bower.json | 1 - front/src/css/dashboard.css | 260 ------------------- front/src/css/rule.css | 0 front/src/css/timeline.css | 243 ++++++++++++++++++ front/src/js/app.js | 6 + front/src/js/controllers/dashboardCtrl.js | 19 +- front/src/js/controllers/ruleCtrl.js | 5 + front/src/js/controllers/timelineCtrl.js | 132 ++++++++++ front/src/js/models/resultsFactory.js | 2 +- front/src/less/dashboard.less | 291 ---------------------- front/src/less/rule.less | 0 front/src/less/timeline.less | 265 ++++++++++++++++++++ front/src/main.html | 4 + front/src/views/rule.html | 0 front/src/views/timeline.html | 147 +++++++++++ lib/rulesChecker.js | 1 - lib/runner.js | 19 +- lib/tools/jsExecutionTransformer.js | 62 +++++ lib/yellowlabtools.js | 3 + 19 files changed, 890 insertions(+), 570 deletions(-) create mode 100644 front/src/css/rule.css create mode 100644 front/src/css/timeline.css create mode 100644 front/src/js/controllers/ruleCtrl.js create mode 100644 front/src/js/controllers/timelineCtrl.js create mode 100644 front/src/less/rule.less create mode 100644 front/src/less/timeline.less create mode 100644 front/src/views/rule.html create mode 100644 front/src/views/timeline.html create mode 100644 lib/tools/jsExecutionTransformer.js diff --git a/bower.json b/bower.json index 3a28910..9d7da0f 100644 --- a/bower.json +++ b/bower.json @@ -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" } diff --git a/front/src/css/dashboard.css b/front/src/css/dashboard.css index 9d64baf..5a6147d 100644 --- a/front/src/css/dashboard.css +++ b/front/src/css/dashboard.css @@ -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; diff --git a/front/src/css/rule.css b/front/src/css/rule.css new file mode 100644 index 0000000..e69de29 diff --git a/front/src/css/timeline.css b/front/src/css/timeline.css new file mode 100644 index 0000000..c2eb686 --- /dev/null +++ b/front/src/css/timeline.css @@ -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; +} diff --git a/front/src/js/app.js b/front/src/js/app.js index 6d44767..612ad18 100644 --- a/front/src/js/app.js +++ b/front/src/js/app.js @@ -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. diff --git a/front/src/js/controllers/dashboardCtrl.js b/front/src/js/controllers/dashboardCtrl.js index 3598b70..f28a4ad 100644 --- a/front/src/js/controllers/dashboardCtrl.js +++ b/front/src/js/controllers/dashboardCtrl.js @@ -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(); }]); \ No newline at end of file diff --git a/front/src/js/controllers/ruleCtrl.js b/front/src/js/controllers/ruleCtrl.js new file mode 100644 index 0000000..7ad7a97 --- /dev/null +++ b/front/src/js/controllers/ruleCtrl.js @@ -0,0 +1,5 @@ +var ruleCtrl = angular.module('ruleCtrl', []); + +ruleCtrl.controller('RuleCtrl', ['$scope', function($scope) { + +}]); \ No newline at end of file diff --git a/front/src/js/controllers/timelineCtrl.js b/front/src/js/controllers/timelineCtrl.js new file mode 100644 index 0000000..8d4b18c --- /dev/null +++ b/front/src/js/controllers/timelineCtrl.js @@ -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 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(); + +}]); \ No newline at end of file diff --git a/front/src/js/models/resultsFactory.js b/front/src/js/models/resultsFactory.js index fab26d0..33b286e 100644 --- a/front/src/js/models/resultsFactory.js +++ b/front/src/js/models/resultsFactory.js @@ -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'}} + }); }]); \ No newline at end of file diff --git a/front/src/less/dashboard.less b/front/src/less/dashboard.less index 629df3b..bdaa76b 100644 --- a/front/src/less/dashboard.less +++ b/front/src/less/dashboard.less @@ -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; diff --git a/front/src/less/rule.less b/front/src/less/rule.less new file mode 100644 index 0000000..e69de29 diff --git a/front/src/less/timeline.less b/front/src/less/timeline.less new file mode 100644 index 0000000..a15465a --- /dev/null +++ b/front/src/less/timeline.less @@ -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; +} \ No newline at end of file diff --git a/front/src/main.html b/front/src/main.html index f062e71..72b719d 100644 --- a/front/src/main.html +++ b/front/src/main.html @@ -9,6 +9,8 @@ + + @@ -18,6 +20,8 @@ + + diff --git a/front/src/views/rule.html b/front/src/views/rule.html new file mode 100644 index 0000000..e69de29 diff --git a/front/src/views/timeline.html b/front/src/views/timeline.html new file mode 100644 index 0000000..f3fcb25 --- /dev/null +++ b/front/src/views/timeline.html @@ -0,0 +1,147 @@ +
+
+

Javascript Timeline

+

This graph gives a quick view of when the Javascript interactions with the DOM occur during the loading of the page.

+ +
+
+
+
+
+
+
Timestamp: {{$index * timelineIntervalDuration | number: 0}} ms
+
+
+
+
0 ms
+
{{endTime | number: 0}} ms
+
+
+
+
DOM creation
+
DOM interactive
+
DOM content loaded event
+
Page completion
+
Page is complete
+
+
+
Executing Javascript and DOM queries here is a bad practice and slows down the DOM construction.
+
Some frameworks do things here, but it's not reliable and should be avoided.
+
Also known as "document ready". This is where you should execute top-priority scripts, like binding action buttons or launch a video player.
+
Here you can execute mid-priority tasks. Loading a script with createElement('script') is one way to do so.
+
The page is considered loaded, it's time for low priority things : trackers, social plugins, easter egg...
+
+
+
+ +

Javascript Profiler

+

+ The table below shows the interactions between Javascript and the DOM. It is useful to understand what happens while the page loads. +

+
+
+ + +
+
+ + Filter by + +
+
+
+ + +
+
+
Type
+
Params
+
+
Timestamp
+
+
+ +
{{$index + 1}}
+
{{node.data.type}}
+ +
+ {{node.data.callDetails.arguments[0]}} + : {{node.data.callDetails.arguments[1]}} + : {{node.data.callDetails.arguments[2]}} + : {{node.data.callDetails.arguments[3]}} +
+ +
+
+ +
+
✖
+ +
+

Called on DOM element

+
{{node.data.callDetails.context.domElement}}
+
+ +
+

Called on 0 jQuery element

+

Useless function call, as the jQuery object is empty.

+
+ +
+

Called on 1 jQuery element

+
{{node.data.callDetails.context.firstElementPath}}
+
+ +
+

Called on {{node.data.callDetails.context.length}} jQuery elements

+

+ 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). +

+

First one is: {{node.data.callDetails.context.firstElementPath}}

+
+ +

+ The query returned 0 results. Could it be unused or dead code? +

+ +
+

Backtrace

+
+
+
{{trace.fnName || '(anonymous)'}}
+ +
+
+
can't find any backtrace :/
+
+
+
+
+
+
{{node.data.timestamp | number: 0}} ms
+
+
+
\ No newline at end of file diff --git a/lib/rulesChecker.js b/lib/rulesChecker.js index f6f6d65..1fab084 100644 --- a/lib/rulesChecker.js +++ b/lib/rulesChecker.js @@ -1,4 +1,3 @@ -var Q = require('q'); var debug = require('debug')('ylt:ruleschecker'); var RulesChecker = function() { diff --git a/lib/runner.js b/lib/runner.js index e48a306..0cd4dcb 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -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 diff --git a/lib/tools/jsExecutionTransformer.js b/lib/tools/jsExecutionTransformer.js new file mode 100644 index 0000000..c7801e3 --- /dev/null +++ b/lib/tools/jsExecutionTransformer.js @@ -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(); \ No newline at end of file diff --git a/lib/yellowlabtools.js b/lib/yellowlabtools.js index a56e49d..bca7a61 100644 --- a/lib/yellowlabtools.js +++ b/lib/yellowlabtools.js @@ -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); }); } From 3c22cd216cdd6601a10647ea73ceb51970b0d4d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 22 Dec 2014 16:25:16 +0100 Subject: [PATCH 41/98] Add the Policy page for the details of a policy score --- front/src/css/dashboard.css | 72 -------------------- front/src/css/main.css | 82 +++++++++++++++++++++- front/src/css/rule.css | 58 ++++++++++++++++ front/src/js/controllers/ruleCtrl.js | 32 ++++++++- front/src/js/controllers/timelineCtrl.js | 6 +- front/src/less/dashboard.less | 74 -------------------- front/src/less/main.less | 86 +++++++++++++++++++++++- front/src/less/rule.less | 68 +++++++++++++++++++ front/src/main.html | 2 +- front/src/views/dashboard.html | 2 +- front/src/views/resultSubHeader.html | 2 +- front/src/views/rule.html | 39 +++++++++++ front/src/views/timeline.html | 4 +- 13 files changed, 372 insertions(+), 155 deletions(-) diff --git a/front/src/css/dashboard.css b/front/src/css/dashboard.css index 5a6147d..0dd2187 100644 --- a/front/src/css/dashboard.css +++ b/front/src/css/dashboard.css @@ -1,53 +1,9 @@ -.resultsMenu { - margin-top: 2em; -} -.resultsMenu .menuItem { - display: inline-block; - margin: 1em; - width: 8em; - height: 7em; - color: #fff; - border: 3px solid #fff; - border-radius: 0.5em; - cursor: pointer; - text-decoration: none; -} -.resultsMenu .back { - color: #5e2846; - border-color: #5e2846; -} -.resultsMenu .menuItem div { - padding-top: 0.5em; - font-size: 3em; -} -.resultsMenu .active, -.resultsMenu .menuItem.active:hover { - color: #ffa319; - border-color: #ffa319; -} -.resultsMenu .menuItem:hover { - color: #ffa319; -} -.resultsMenu span { - position: relative; - top: 0.5em; -} .testedUrl { color: inherit; } h4 { margin-bottom: 0.5em; } -.summary, -.metrics, -.execution { - margin-top: 2em; - padding: 1em; - background: #fff; - color: #000; - border-radius: 0.5em; - text-align: left; -} .notations { display: table; width: 80%; @@ -100,34 +56,6 @@ h4 { margin: 0 auto; border-radius: 0.5em; } -.notations .A { - /* green */ - background: #0C4; -} -.notations .B { - /* green */ - background: #CD0; -} -.notations .C { - /* yellow */ - background: #FD2; -} -.notations .D { - /* orange */ - background: #FA2; -} -.notations .E { - /* red */ - background: #F60; -} -.notations .F { - /* red */ - background: #F22; -} -.notations .NA { - /* Non applicable */ - background: #CCC; -} .notations .criteria .table { width: 100%; } diff --git a/front/src/css/main.css b/front/src/css/main.css index b807d79..5a8e4a7 100644 --- a/front/src/css/main.css +++ b/front/src/css/main.css @@ -32,9 +32,89 @@ h1 span { width: 1em; color: #ffa319; } +.resultsMenu { + margin-top: 2em; +} +.resultsMenu .menuItem { + display: inline-block; + margin: 1em; + width: 8em; + height: 7em; + color: #fff; + border: 3px solid #fff; + border-radius: 0.5em; + cursor: pointer; + text-decoration: none; +} +.resultsMenu .back { + color: #413; + border-color: #413; +} +.resultsMenu .menuItem div { + padding-top: 0.5em; + font-size: 3em; +} +.resultsMenu .active, +.resultsMenu .menuItem.active:hover { + color: #ffa319; + border-color: #ffa319; +} +.resultsMenu .menuItem:hover { + color: #ffa319; +} +.resultsMenu span { + position: relative; + top: 0.5em; +} +/* Grade colors */ +.A { + /* green */ + background: #0C4; +} +.B { + /* green */ + background: #CD0; +} +.C { + /* yellow */ + background: #FD2; +} +.D { + /* orange */ + background: #FA2; +} +.E { + /* red */ + background: #F60; +} +.F { + /* red */ + background: #F22; +} +.NA { + /* Non applicable */ + background: #CCC; +} +.board { + margin-top: 2em; + padding: 1em; + background: #fff; + color: #000; + border-radius: 0.5em; + text-align: left; +} +.backToDashboard { + text-align: center; +} +.backToDashboard a { + font-size: 0.9em; + display: block; + margin-top: 4em; + color: black; +} .footer { padding: 3em; - color: #000; + color: #413; } .footer a { color: inherit; diff --git a/front/src/css/rule.css b/front/src/css/rule.css index e69de29..f9636e7 100644 --- a/front/src/css/rule.css +++ b/front/src/css/rule.css @@ -0,0 +1,58 @@ +.rule.board { + text-align: center; +} +.rule h2 span { + font-size: 1.5em; +} +.rule .scoreDisplay { + display: table; + border-spacing: 1em; + margin: 0 auto; +} +.rule .scoreDisplay > div { + display: table-cell; + vertical-align: middle; + font-weight: bold; + font-size: 1.5em; +} +.rule .scoreDisplay > div.score { + font-size: 2.5em; + height: 2em; + width: 2em; + border-radius: 0.5em; +} +.rule h3 { + margin-top: 2.5em; +} +.rule .icon-warning { + font-size: 2em; + color: #FF1919; +} +.rule .message { + width: 80%; + margin: 0 auto; +} +.rule .message p { + margin: 0.5em; +} +.rule ul { + list-style-type: none; + padding-left: 0; +} +.rule li:before { + content: '\25e6'; + margin-right: 0.3em; + font-size: 1.2em; + position: relative; + top: 0.1em; +} +.rule .offenders li { + font-size: 0.8em; +} +.rule .notFound { + font-size: 1em; +} +.rule .notFound h2 { + font-size: 3em; + margin-bottom: 1em; +} diff --git a/front/src/js/controllers/ruleCtrl.js b/front/src/js/controllers/ruleCtrl.js index 7ad7a97..f41ae8d 100644 --- a/front/src/js/controllers/ruleCtrl.js +++ b/front/src/js/controllers/ruleCtrl.js @@ -1,5 +1,33 @@ var ruleCtrl = angular.module('ruleCtrl', []); -ruleCtrl.controller('RuleCtrl', ['$scope', function($scope) { - +ruleCtrl.controller('RuleCtrl', ['$scope', '$rootScope', '$routeParams', '$location', '$sce', 'Menu', 'Results', function($scope, $rootScope, $routeParams, $location, $sce, Menu, Results) { + $scope.runId = $routeParams.runId; + $scope.policyName = $routeParams.policy; + $scope.Menu = Menu.setCurrentPage(null, $scope.runId); + $scope.rule = null; + + 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; + init(); + }); + } else { + $scope.result = $rootScope.loadedResult; + init(); + } + } + + function init() { + $scope.rule = $scope.result.rules[$scope.policyName]; + $scope.message = $sce.trustAsHtml($scope.rule.policy.message); + } + + $scope.backToDashboard = function() { + $location.path('/result/' + $scope.runId); + }; + + loadResults(); }]); \ No newline at end of file diff --git a/front/src/js/controllers/timelineCtrl.js b/front/src/js/controllers/timelineCtrl.js index 8d4b18c..66c568a 100644 --- a/front/src/js/controllers/timelineCtrl.js +++ b/front/src/js/controllers/timelineCtrl.js @@ -1,6 +1,6 @@ var timelineCtrl = angular.module('timelineCtrl', []); -timelineCtrl.controller('TimelineCtrl', ['$scope', '$rootScope', '$routeParams', '$timeout', 'Menu', 'Results', function($scope, $rootScope, $routeParams, $timeout, Menu, Results) { +timelineCtrl.controller('TimelineCtrl', ['$scope', '$rootScope', '$routeParams', '$location', '$timeout', 'Menu', 'Results', function($scope, $rootScope, $routeParams, $location, $timeout, Menu, Results) { $scope.runId = $routeParams.runId; $scope.Menu = Menu.setCurrentPage('timeline', $scope.runId); @@ -127,6 +127,10 @@ timelineCtrl.controller('TimelineCtrl', ['$scope', '$rootScope', '$routeParams', node.showDetails = !isOpen; }; + $scope.backToDashboard = function() { + $location.path('/result/' + $scope.runId); + }; + loadResults(); }]); \ No newline at end of file diff --git a/front/src/less/dashboard.less b/front/src/less/dashboard.less index bdaa76b..74d6d05 100644 --- a/front/src/less/dashboard.less +++ b/front/src/less/dashboard.less @@ -1,40 +1,3 @@ - -.resultsMenu { - margin-top: 2em; -} - -.resultsMenu .menuItem { - display: inline-block; - margin: 1em; - width: 8em; - height: 7em; - color: #fff; - border: 3px solid #fff; - border-radius: 0.5em; - cursor: pointer; - text-decoration: none; -} -.resultsMenu .back { - color: #5e2846; - border-color: #5e2846; -} -.resultsMenu .menuItem div { - padding-top: 0.5em; - font-size: 3em; -} -.resultsMenu .active, .resultsMenu .menuItem.active:hover { - color: #ffa319; - border-color: #ffa319; -} -.resultsMenu .menuItem:hover { - color: #ffa319; -} - -.resultsMenu span { - position: relative; - top: 0.5em; -} - .testedUrl { color: inherit; } @@ -43,15 +6,6 @@ h4 { margin-bottom: 0.5em; } -.summary, .metrics, .execution { - margin-top: 2em; - padding: 1em; - background: #fff; - color: #000; - border-radius: 0.5em; - text-align: left; -} - .notations { display: table; width: 80%; @@ -94,34 +48,6 @@ h4 { border-radius: 0.5em; } } -.notations .A { - /* green */ - background: #0C4; -} -.notations .B { - /* green */ - background: #CD0; -} -.notations .C { - /* yellow */ - background: #FD2; -} -.notations .D { - /* orange */ - background: #FA2; -} -.notations .E { - /* red */ - background: #F60; -} -.notations .F { - /* red */ - background: #F22; -} -.notations .NA { - /* Non applicable */ - background: #CCC; -} .notations .criteria .table { width: 100%; diff --git a/front/src/less/main.less b/front/src/less/main.less index f471fd0..5d4abdf 100644 --- a/front/src/less/main.less +++ b/front/src/less/main.less @@ -28,9 +28,93 @@ h1 span { color: #ffa319; } +.resultsMenu { + margin-top: 2em; +} +.resultsMenu .menuItem { + display: inline-block; + margin: 1em; + width: 8em; + height: 7em; + color: #fff; + border: 3px solid #fff; + border-radius: 0.5em; + cursor: pointer; + text-decoration: none; +} +.resultsMenu .back { + color: #413; + border-color: #413; +} +.resultsMenu .menuItem div { + padding-top: 0.5em; + font-size: 3em; +} +.resultsMenu .active, .resultsMenu .menuItem.active:hover { + color: #ffa319; + border-color: #ffa319; +} +.resultsMenu .menuItem:hover { + color: #ffa319; +} +.resultsMenu span { + position: relative; + top: 0.5em; +} + +/* Grade colors */ +.A { + /* green */ + background: #0C4; +} +.B { + /* green */ + background: #CD0; +} +.C { + /* yellow */ + background: #FD2; +} +.D { + /* orange */ + background: #FA2; +} +.E { + /* red */ + background: #F60; +} +.F { + /* red */ + background: #F22; +} +.NA { + /* Non applicable */ + background: #CCC; +} + +.board { + margin-top: 2em; + padding: 1em; + background: #fff; + color: #000; + border-radius: 0.5em; + text-align: left; +} + +.backToDashboard { + text-align: center; + + a { + font-size: 0.9em; + display: block; + margin-top: 4em; + color: black; + } +} + .footer { padding: 3em; - color: #000; + color: #413; a { color: inherit; } diff --git a/front/src/less/rule.less b/front/src/less/rule.less index e69de29..4ed2958 100644 --- a/front/src/less/rule.less +++ b/front/src/less/rule.less @@ -0,0 +1,68 @@ +.rule.board { + text-align: center; +} + +.rule h2 span { + font-size: 1.5em; +} + +.rule .scoreDisplay { + display: table; + border-spacing: 1em; + margin: 0 auto; + + > div { + display: table-cell; + vertical-align: middle; + font-weight: bold; + font-size: 1.5em; + + &.score { + font-size: 2.5em; + height: 2em; + width: 2em; + border-radius: 0.5em; + } + } +} + +.rule h3 { + margin-top: 2.5em; +} + +.rule .icon-warning { + font-size: 2em; + color: #FF1919; +} + +.rule .message { + width: 80%; + margin: 0 auto; + p { + margin: 0.5em; + } +} + +.rule ul { + list-style-type: none; + padding-left: 0; +} +.rule li:before { + content:'\25e6'; + margin-right: 0.3em; + font-size: 1.2em; + position: relative; + top: 0.1em; +} + +.rule .offenders li { + font-size: 0.8em; +} + +.rule .notFound { + font-size: 1em; + h2 { + font-size: 3em; + margin-bottom: 1em; + } +} \ No newline at end of file diff --git a/front/src/main.html b/front/src/main.html index 72b719d..51277bb 100644 --- a/front/src/main.html +++ b/front/src/main.html @@ -30,7 +30,7 @@ -
+
diff --git a/front/src/views/dashboard.html b/front/src/views/dashboard.html index cfb3f01..9aa74e8 100644 --- a/front/src/views/dashboard.html +++ b/front/src/views/dashboard.html @@ -1,5 +1,5 @@
-
+

Grades

diff --git a/front/src/views/resultSubHeader.html b/front/src/views/resultSubHeader.html index e583b36..e429366 100644 --- a/front/src/views/resultSubHeader.html +++ b/front/src/views/resultSubHeader.html @@ -2,6 +2,6 @@
New test
- +
\ No newline at end of file diff --git a/front/src/views/rule.html b/front/src/views/rule.html index e69de29..06c5f4b 100644 --- a/front/src/views/rule.html +++ b/front/src/views/rule.html @@ -0,0 +1,39 @@ +
+
+
+

Score for rule:
{{rule.policy.label}}

+ +
+ +
{{rule.score}}/100
+
+
+

The page has a real problem with this rule

+ + + +

(Abnormality score: {{rule.abnormalityScore}})

+
+ +

Value

+ {{rule.value}} + +

Why this rule?

+
+ +
+

List of offenders

+
    +
  • + {{offender}} +
  • +
+
+
+
+

404

+ Rule "{{policyName}}"" not found +
+ + +
\ No newline at end of file diff --git a/front/src/views/timeline.html b/front/src/views/timeline.html index f3fcb25..de7894f 100644 --- a/front/src/views/timeline.html +++ b/front/src/views/timeline.html @@ -1,5 +1,5 @@
-
+

Javascript Timeline

This graph gives a quick view of when the Javascript interactions with the DOM occur during the loading of the page.

@@ -144,4 +144,6 @@
{{node.data.timestamp | number: 0}} ms
+ +
\ No newline at end of file From cf79e95c451f106592a51c1fb1b47a4d86a4a407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 22 Dec 2014 17:09:29 +0100 Subject: [PATCH 42/98] Add contributors to README --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d19b146..c2b12eb 100644 --- a/README.md +++ b/README.md @@ -34,4 +34,8 @@ Icons are the property of [IcoMoon.io](https://icomoon.io/) ## Author -Gaël Métais. I'm a webperf freelance based in Paris. If you understand french, you can visit [my website](http://www.gaelmetais.com). \ No newline at end of file +Gaël Métais. I'm a webperf freelance based in Paris. If you understand french, you can visit [my website](http://www.gaelmetais.com). + + +## Contributors +- [achrafbenyounes](https://github.com/achrafbenyounes) \ No newline at end of file From 3cd60e9a8da7cd5972da50b9e5889a19a854d261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 22 Dec 2014 17:45:11 +0100 Subject: [PATCH 43/98] About page --- front/src/css/about.css | 10 ++++++++++ front/src/css/main.css | 12 ++++++------ front/src/less/about.less | 12 ++++++++++++ front/src/less/main.less | 14 ++++++++------ front/src/main.html | 3 ++- front/src/views/about.html | 17 +++++++++-------- front/src/views/index.html | 1 + 7 files changed, 48 insertions(+), 21 deletions(-) create mode 100644 front/src/css/about.css create mode 100644 front/src/less/about.less diff --git a/front/src/css/about.css b/front/src/css/about.css new file mode 100644 index 0000000..0436d82 --- /dev/null +++ b/front/src/css/about.css @@ -0,0 +1,10 @@ +.about { + margin: 3em auto; + width: 50%; +} +.about p { + margin: 2em; +} +.about a { + color: #FFF; +} diff --git a/front/src/css/main.css b/front/src/css/main.css index 5a8e4a7..d4631b7 100644 --- a/front/src/css/main.css +++ b/front/src/css/main.css @@ -112,6 +112,12 @@ h1 span { margin-top: 4em; color: black; } +.star { + font-weight: bold; +} +.star span { + font-size: 1.2em; +} .footer { padding: 3em; color: #413; @@ -119,12 +125,6 @@ h1 span { .footer a { color: inherit; } -.footer .star { - font-weight: bold; -} -.footer .star span { - font-size: 1.2em; -} /* Icons */ .icon-lab { font-family: "fontsmith-icons"; diff --git a/front/src/less/about.less b/front/src/less/about.less new file mode 100644 index 0000000..67de419 --- /dev/null +++ b/front/src/less/about.less @@ -0,0 +1,12 @@ +.about { + margin: 3em auto; + width: 50%; +} + +.about p { + margin: 2em; +} + +.about a { + color: #FFF; +} \ No newline at end of file diff --git a/front/src/less/main.less b/front/src/less/main.less index 5d4abdf..9d8065f 100644 --- a/front/src/less/main.less +++ b/front/src/less/main.less @@ -112,18 +112,20 @@ h1 span { } } + +.star { + font-weight: bold; + span { + font-size: 1.2em; + } +} + .footer { padding: 3em; color: #413; a { color: inherit; } - .star { - font-weight: bold; - span { - font-size: 1.2em; - } - } } /* Icons */ diff --git a/front/src/main.html b/front/src/main.html index 51277bb..6015d59 100644 --- a/front/src/main.html +++ b/front/src/main.html @@ -11,6 +11,7 @@ + @@ -32,7 +33,7 @@
\ No newline at end of file diff --git a/front/src/views/about.html b/front/src/views/about.html index 3016791..7824c61 100644 --- a/front/src/views/about.html +++ b/front/src/views/about.html @@ -1,8 +1,9 @@ - - - - - - \ No newline at end of file +
+

Yellow Lab Tools is an open source project by Gaël Métais. It lets you test a webpage (via an URL) and detects performance and front-end code quality issues.

+ +

This is done by loading the webpage via PhantomJS and collecting various metrics and statistics with the help of Phantomas. These metrics are categorized and transformed into scores. It also gives in-depth details so developpers can correct the detected issues.

+ +

By the way, it's free because we are geeks, not businessmen. All we want is a on GitHub. It will boost our motivation to add more awesome features!!!

+ +


Back to index

+
\ No newline at end of file diff --git a/front/src/views/index.html b/front/src/views/index.html index f676eee..7bc52c3 100644 --- a/front/src/views/index.html +++ b/front/src/views/index.html @@ -1,4 +1,5 @@

Free online test to help speeding up heavy web pages

+
From 0e5c7e1215891cda01e629229939e27007c5247d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 22 Dec 2014 19:35:02 +0100 Subject: [PATCH 44/98] Progressively increment polling interval on queue page --- front/src/js/controllers/queueCtrl.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/front/src/js/controllers/queueCtrl.js b/front/src/js/controllers/queueCtrl.js index 45b8bd4..3fa96b6 100644 --- a/front/src/js/controllers/queueCtrl.js +++ b/front/src/js/controllers/queueCtrl.js @@ -2,13 +2,18 @@ var queueCtrl = angular.module('queueCtrl', ['runsFactory']); queueCtrl.controller('QueueCtrl', ['$scope', '$routeParams', '$location', 'Runs', function($scope, $routeParams, $location, Runs) { $scope.runId = $routeParams.runId; + + var numberOfTries = 0; function getRunStatus () { Runs.get({runId: $scope.runId}, function(data) { $scope.status = data.status; if (data.status.statusCode === 'running' || data.status.statusCode === 'awaiting') { - // Retrying in 2 seconds - setTimeout(getRunStatus, 2000); + numberOfTries ++; + + // Retrying in 2 seconds (and increasing the delay a bit more each time) + setTimeout(getRunStatus, 2000 + (numberOfTries * 100)); + } else if (data.status.statusCode === 'complete') { $location.path('/result/' + $scope.runId); } else { From 7aede2081936f25ea0e3b1dd34dbf0e864dcd459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 22 Dec 2014 19:36:14 +0100 Subject: [PATCH 45/98] Remove eye icon from font --- front/src/css/main.css | 15 +-------------- front/src/fonts/icons.woff | Bin 2140 -> 1784 bytes front/src/fonts/svg-icons/eye.svg | 1 - front/src/less/icons.less | 21 +++++++++------------ front/src/less/main.less | 3 --- 5 files changed, 10 insertions(+), 30 deletions(-) delete mode 100644 front/src/fonts/svg-icons/eye.svg diff --git a/front/src/css/main.css b/front/src/css/main.css index d4631b7..8c66887 100644 --- a/front/src/css/main.css +++ b/front/src/css/main.css @@ -176,7 +176,7 @@ h1 span { -webkit-font-smoothing: antialiased; } .icon-back:before { - content: "\e006"; + content: "\e005"; } .icon-summary { font-family: "fontsmith-icons"; @@ -202,18 +202,5 @@ h1 span { -webkit-font-smoothing: antialiased; } .icon-spaghetti:before { - content: "\e005"; -} -.icon-eye { - font-family: "fontsmith-icons"; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - -webkit-font-smoothing: antialiased; -} -.icon-eye:before { content: "\e004"; } diff --git a/front/src/fonts/icons.woff b/front/src/fonts/icons.woff index dd8c0bd30e55a48a472a6f7085916d3ea822f6b3..9410fd7527866bd261e99775bbc5dfc00e41dde4 100644 GIT binary patch delta 1604 zcmXYy4LlQQ9LFDR&5r7Zp(!lM<-7}phBn*fZClKp^0qMyv50MYTi!Y@>f*An9F>z& z=Nf5QxJ-1NE!{;Jq0_B5O>ZYO_tf3<`Fx+x=l6YH{-4kD`8>~~!pyD2@@W(bjlrM+ zfSLpP05(0%39OY6aTgjhk08S^3WX5@0P+DGGJ~v5llC3?%AZVu?hVZ55Ik@PNLrYa zD*(%}Fb6@HPAZjOd>U&HbWE5D0O*8Hn7-=gB(T`f)k5b8@mN-!xR(PZbg(9{fLJ4i zn5DlsX;}g&HBcBpOpvfoV+r7fC?w3NPoU`_ot2gV;|iF^VUgm|(vrMA+`U=QJ%{e2 z#{&ca0&zAYdN(#4>%-^slA>%#k-Xqbqv<_oiWjK}h*j{Xz zN-46AsQ;yJ`JHZ$9XlkA_`o$5&W-fub-RN5vH1a(!7D~EmyU#7@Mlx_e>{@%c@6G^ zE$(@GxU;r&+AQ=hMH+FP+8Uid(5y_xMhbn-ez^~bJ4@&03% zEdsm|R2xk0WWB-R@6b_AC5J6O6N)e8waam4_J!?rCT4il#YtAg(!Fs-$gDv8J4M-) z*`G4xcHL&O%6&sXLIP+yZl1`!vc5!e_W5egl$oV^_YH3KOXHFQk9 z@Swq$E4a2gg#)<{k`CZ$B;k4chF>xE7w+SDw^mi#-U)T^jyD+z+XW15M_Hu9Gg8jBPs3SWdt7n%oHS>w}G}iRW%>8qX8Sg{u{t9}!jAl|m9OxFd9t}L$R~vra ziCy3S10kWYXZJdmmdn8}u*l{&2c51CxS3Ve_%CLDul>_EnUCr8+!im@DB7d!f|p0r z7+U{`*7oRT3HtV`5wE%0NiWy8bmQdlO;z4qJtJ-&_juHp`qJcJJg2;T1HF;1h%Ox| zt`*UWjsy%;ZOmP9M=Dh7$a71*0|LTcX3HJUdKy(`{Ow%4ul*mwKJx`Twbpdu5G^Cn zd@R<$*?z+``^ni+4k7OA7;~%0zPn}TNiGu1)#uG$np}2H7!I47_QXki7^;xSoF)s5 zZi7x;{?-SZqft1=vT}A?|FgH5JLHO_GV~@Xsbx$kGr?l;Cx!2WB?Ru$bFgeU1t7&9$oHxI13k;ggeXj_mrzeBW$i2T!7 zE)d5vnM^jw+jRA)B44e)P>|lu0n|#}%#U|Ip2=o1ClGO;p)kxiCX&d*C+fUj4Ui(> zRi1%L*J&!BK;hh#rf3O#C;p!f;*7uf_U-JVM=b7IN*Im0FVF^xE2690O(K8%rS2&!UaoKT9@OIL`Km)vR)Og)Zv0X{>)F? zEy;tNbJi8&bF6hO_PJ#V&7h*o=2}J!$skhc)6ww~p?XE_Ne++7;BGba#T~DlO^ga! za;w?X-8RdZ|5+S<&|&&A`?SFNkekJ8Vp7zhpe5%TO_vA`evU{rj3meDc6+6T8OjWw zcwS+V2RFUz?GJ3;*g=v?)*5_O8@{ z$qg}bN!_xQY-1hS>t>6G+&sVT^PJb~b6&5{`+Ls!oY(o|bN(Ybfsu@JvbJ_|b8`X! z^d&$9fQG1So}?m()PM*f%KIkQ+S<(l08j={2@6SQ93j3PKG4)ISn(1|s~a!4M0F{48`x29J@I7I`fi zx(NEm-b0Y&0XP8AieueAL%^23Ed~xBF(+YgFIe3QCAgdtTEL4up& zBIZl>YMf$7%_Kb~`XU%+wcn*c*#Zt-zK(=#9VB{qh45DE0Xpv0wA4>hDh?wD0F_)=GrUj5o_Gt)IsG{4p%LSwRzE)o-X z-3r}D7lX^5KNWuL9e6Kyl9OieD8bIrXs^fOM(^I!vJ@Z3HTJrSbiK^3s8-Eyf6*TSqt7+n{e9(v=VSm6QbJVN8D;HZ4 zv!XW4y-7OTcQ3px-TCoPFEM^2WxHWopw9m1cZR*qt34&PA9YlS^%`r>tLSSEMra0Ol3rK+WHOjaWb)f^P(YShGO zvy-W14{EbLM&9EpYkn2s)(fnoMk-)D^oo}*`non+7$hcYTGXxVoUiT@-zvO{SnDd3dp@e+Mt}xY$g?9$zlO5s&@Lv`5 zdcDMT0v&wL@i+9BfWW-!n5e%Z>UBn!EVnkapXpC3x2?$PHV9|({fa`iujng$k7XD6 zIj#YG&^%6pNc;#lPRuQs>MT)jF?y!IsAFu!*=Zhp9*A3g|F~OOk%tM1M)r8O=fC6( zp;%%?d(%P}g8OkdEwH5sJsqOy6_piy<45s(@!Z#i8U0mZvGGH77x%VXe7Al!#;RGq ztzfn+z|Zv*DSbszRN)U0#sGsxwqnfGZOz(5)mnn|*#p1<;j{H4Is?gMauATL)0EYh zvLs@ZA4tXn4#x$B|5pnllV{)oLMRM5fQ+ydlMjJyN&|>8z=$D%jDUqR5|J{x1IB0$ z6vaPULCXPtEM1(y-=(&njnvUj>_|QFS{uE006&d-DQK=C2=~MSTJFN18GT4~uoM8M zfuqn~0DuBr=$|B~n?wj`V^X=Psb~p8Wm$p7nRlPUs4R0?faPd$H}|QxP+icY*B0*< z&??c{vhp^slr}usc1bK5@YbzO_{4_ei(Scy$ZN(JGZ}H_ddQ^<2Y5N{c^*ye?~8q9 z#kS3>Px_!5R{Y@*G=%N`973Hq4ZQf$jw?{LV+?bU$N+w^X~gI`{o>tp&TEBpVc-t|4dWG`_)`st;QQf m4;%z%f4NAO+AMWFa#{c4)dQe{a?a)d4;9h@eaY|y0P$bEzg7wW diff --git a/front/src/fonts/svg-icons/eye.svg b/front/src/fonts/svg-icons/eye.svg deleted file mode 100644 index 59ff3c2..0000000 --- a/front/src/fonts/svg-icons/eye.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/front/src/less/icons.less b/front/src/less/icons.less index 8a2c12a..90c82b5 100644 --- a/front/src/less/icons.less +++ b/front/src/less/icons.less @@ -1,21 +1,18 @@ -@arrow-left3-font-family: "fontsmith-icons"; -@arrow-left3-value: "\e006"; -@arrow-left3: '"fontsmith-icons"' '"\\e006"'; +@list-font-family: "fontsmith-icons"; +@list-value: "\e002"; +@list: '"fontsmith-icons"' '"\\e002"'; +@bars-font-family: "fontsmith-icons"; +@bars-value: "\e004"; +@bars: '"fontsmith-icons"' '"\\e004"'; @lab-font-family: "fontsmith-icons"; @lab-value: "\e003"; @lab: '"fontsmith-icons"' '"\\e003"'; @warning-font-family: "fontsmith-icons"; @warning-value: "\e000"; @warning: '"fontsmith-icons"' '"\\e000"'; -@list-font-family: "fontsmith-icons"; -@list-value: "\e002"; -@list: '"fontsmith-icons"' '"\\e002"'; -@eye-font-family: "fontsmith-icons"; -@eye-value: "\e004"; -@eye: '"fontsmith-icons"' '"\\e004"'; -@bars-font-family: "fontsmith-icons"; -@bars-value: "\e005"; -@bars: '"fontsmith-icons"' '"\\e005"'; +@arrow-left3-font-family: "fontsmith-icons"; +@arrow-left3-value: "\e005"; +@arrow-left3: '"fontsmith-icons"' '"\\e005"'; @question-font-family: "fontsmith-icons"; @question-value: "\e001"; @question: '"fontsmith-icons"' '"\\e001"'; diff --git a/front/src/less/main.less b/front/src/less/main.less index 9d8065f..91a0097 100644 --- a/front/src/less/main.less +++ b/front/src/less/main.less @@ -146,7 +146,4 @@ h1 span { } .icon-spaghetti { .icon(@bars); -} -.icon-eye { - .icon(@eye); } \ No newline at end of file From d6f76de349ae76d9e7a0deaedbd0fef57bfdd991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 22 Dec 2014 22:08:24 +0100 Subject: [PATCH 46/98] Repair grunt express task --- Gruntfile.js | 8 ++++++++ bin/server.js | 20 +++++++++++--------- test/api/apiTest.js | 14 -------------- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 1f0ad14..f18a304 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -115,6 +115,13 @@ module.exports = function(grunt) { showStack: true } }, + test: { + options: { + port: 8387, + server: './coverage/bin/server.js', + showStack: true + } + }, testSuite: { options: { port: 8388, @@ -184,6 +191,7 @@ module.exports = function(grunt) { 'copy-test-server-settings', 'blanket', 'copy:coverage', + 'express:test', 'mochaTest:test', 'mochaTest:coverage' ]); diff --git a/bin/server.js b/bin/server.js index ea6ca38..5263db7 100644 --- a/bin/server.js +++ b/bin/server.js @@ -20,14 +20,16 @@ var frontController = require('../lib/server/controllers/frontController // Let's start the server! -var settings = require('../server_config/settings.json'); -server.listen(settings.serverPort, function() { - console.log('Listening on port %d', server.address().port); +if (!process.env.GRUNTED) { + var settings = require('../server_config/settings.json'); + server.listen(settings.serverPort, function() { + console.log('Listening on port %d', server.address().port); - // For the tests - if (server.startTests) { - server.startTests(); - } -}); + // For the tests + if (server.startTests) { + server.startTests(); + } + }); +} -module.exports = server; \ No newline at end of file +module.exports = app; \ No newline at end of file diff --git a/test/api/apiTest.js b/test/api/apiTest.js index f7ff020..d4695e6 100644 --- a/test/api/apiTest.js +++ b/test/api/apiTest.js @@ -16,14 +16,6 @@ describe('api', function() { var syncRunResultUrl; var asyncRunId; - var apiServer; - - - // Start the server - before(function(done) { - apiServer = require('../../bin/server.js'); - apiServer.startTests = done; - }); it('should refuse a query with an invalid key', function(done) { @@ -451,10 +443,4 @@ describe('api', function() { }); }); - - // Stop the server - after(function() { - console.log('Closing the server'); - apiServer.close(); - }); }); From 983671f5c369d1e4aa50c3d928f8b71f304570d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 22 Dec 2014 22:13:17 +0100 Subject: [PATCH 47/98] 'grunt dev' task with server autoreload --- Gruntfile.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index f18a304..8a4fa1d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -179,8 +179,7 @@ module.exports = function(grunt) { ]); grunt.registerTask('dev', [ - 'clean:dev', - 'less' + 'express:dev' ]); grunt.registerTask('test', [ From 1c0936d4c1b5b604a53371b8e0dc38232cacb689 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 22 Dec 2014 22:16:04 +0100 Subject: [PATCH 48/98] Change hover color on dashboard --- front/src/css/dashboard.css | 4 ++-- front/src/less/dashboard.less | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/front/src/css/dashboard.css b/front/src/css/dashboard.css index 0dd2187..4720f58 100644 --- a/front/src/css/dashboard.css +++ b/front/src/css/dashboard.css @@ -60,14 +60,14 @@ h4 { width: 100%; } .notations .criteria .table > div:hover > div { - background: #F1C40F; + background: #EBD8E2; cursor: pointer; } .notations .criteria .table > div:hover > div.info { background: #FFF; } .notations .criteria .table > div:hover > div.info .icon-question { - color: #F1C40F; + color: #EBD8E2; } .notations .criteria .grade { width: 10%; diff --git a/front/src/less/dashboard.less b/front/src/less/dashboard.less index 74d6d05..42639ca 100644 --- a/front/src/less/dashboard.less +++ b/front/src/less/dashboard.less @@ -52,12 +52,12 @@ h4 { .notations .criteria .table { width: 100%; > div:hover > div { - background: #F1C40F; + background: #EBD8E2; cursor: pointer; &.info { background: #FFF; .icon-question { - color: #F1C40F; + color: #EBD8E2; } } } From b6df64337dff164e2f143d594ba3d19de90a08ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 22 Dec 2014 22:22:58 +0100 Subject: [PATCH 49/98] Browser back button from result page goes to index ignoring queue --- front/src/js/controllers/queueCtrl.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/js/controllers/queueCtrl.js b/front/src/js/controllers/queueCtrl.js index 3fa96b6..a18d601 100644 --- a/front/src/js/controllers/queueCtrl.js +++ b/front/src/js/controllers/queueCtrl.js @@ -15,7 +15,7 @@ queueCtrl.controller('QueueCtrl', ['$scope', '$routeParams', '$location', 'Runs' setTimeout(getRunStatus, 2000 + (numberOfTries * 100)); } else if (data.status.statusCode === 'complete') { - $location.path('/result/' + $scope.runId); + $location.path('/result/' + $scope.runId).replace(); } else { // Handled by the view } From c99463816887bfa5700706647718146806863fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 22 Dec 2014 22:40:59 +0100 Subject: [PATCH 50/98] Remove console.log --- front/src/js/controllers/indexCtrl.js | 1 - 1 file changed, 1 deletion(-) diff --git a/front/src/js/controllers/indexCtrl.js b/front/src/js/controllers/indexCtrl.js index 2d8f650..bec1da3 100644 --- a/front/src/js/controllers/indexCtrl.js +++ b/front/src/js/controllers/indexCtrl.js @@ -7,7 +7,6 @@ indexCtrl.controller('IndexCtrl', ['$scope', '$location', 'Runs', function($scop url: $scope.url, waitForResponse: false }, function(data) { - console.log(data); $location.path('/queue/' + data.runId); }); From c8ba9ef99a1bf92977c5a657e364a21ada1f6cbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 22 Dec 2014 23:11:25 +0100 Subject: [PATCH 51/98] Take in account abnormalityScore to calculate category score --- lib/metadata/scoreProfileGeneric.json | 4 ++-- lib/scoreCalculator.js | 5 +++-- test/fixtures/scoreOutput.json | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/metadata/scoreProfileGeneric.json b/lib/metadata/scoreProfileGeneric.json index 2260afb..8feb3ff 100644 --- a/lib/metadata/scoreProfileGeneric.json +++ b/lib/metadata/scoreProfileGeneric.json @@ -31,8 +31,8 @@ "jQueryVersion": { "label": "jQuery version", "policies": { - "jQueryVersion": 1, - "jQueryDifferentVersions": 5 + "jQueryVersion": 5, + "jQueryDifferentVersions": 0.1 } }, "cssSyntaxError": { diff --git a/lib/scoreCalculator.js b/lib/scoreCalculator.js index 8a0a399..25a147c 100644 --- a/lib/scoreCalculator.js +++ b/lib/scoreCalculator.js @@ -28,7 +28,8 @@ var ScoreCalculator = function() { weight = profile.categories[categoryName].policies[policyName]; if (data.rules[policyName]) { - sum += data.rules[policyName].score * weight; + var policyScore = data.rules[policyName].score + data.rules[policyName].abnormalityScore; + sum += policyScore * weight; } else { // Max value if rule is not here sum += 100 * weight; @@ -42,7 +43,7 @@ var ScoreCalculator = function() { if (totalWeight === 0) { categoryResult.categoryScore = 100; } else { - categoryResult.categoryScore = Math.round(sum / totalWeight); + categoryResult.categoryScore = Math.round(Math.max(sum, 0) / totalWeight); } categoryResult.rules = rules; diff --git a/test/fixtures/scoreOutput.json b/test/fixtures/scoreOutput.json index 4630bbf..7c0259f 100644 --- a/test/fixtures/scoreOutput.json +++ b/test/fixtures/scoreOutput.json @@ -1,9 +1,9 @@ { - "globalScore": 69, + "globalScore": 57, "categories": { "category1": { "label": "Category 1", - "categoryScore": 87, + "categoryScore": 83, "rules": [ "metric1", "metric2", @@ -13,7 +13,7 @@ }, "category2": { "label": "Category 2", - "categoryScore": 31, + "categoryScore": 0, "rules": [ "metric5", "metric6", From b186677a530b037c8362f3ade8e845c584e8e852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Tue, 23 Dec 2014 16:07:48 +0100 Subject: [PATCH 52/98] Better scoring algorithm --- lib/metadata/policies.js | 14 ++++++++--- lib/scoreCalculator.js | 53 +++++++++++++++++++++------------------- 2 files changed, 39 insertions(+), 28 deletions(-) diff --git a/lib/metadata/policies.js b/lib/metadata/policies.js index c5a21aa..d88da7a 100644 --- a/lib/metadata/policies.js +++ b/lib/metadata/policies.js @@ -170,9 +170,9 @@ var policies = { "tool": "phantomas", "label": "CSS syntax error", "message": "

Yellow Lab Tools failed to parse a CSS file. I doubt the problem comes from the css parser.

Maybe a CSS validator can help you.

", - "isOkThreshold": 1, - "isBadThreshold": 2, - "isAbnormalThreshold": 3 + "isOkThreshold": 0, + "isBadThreshold": 1, + "isAbnormalThreshold": 1 }, "cssRules": { "tool": "phantomas", @@ -390,6 +390,14 @@ var policies = { "isBadThreshold": 12, "isAbnormalThreshold": 25 }, + "cachingNotSpecified": { + "tool": "phantomas", + "label": "Caching not specified", + "message": "

When no caching is specified, each browser will handle it differently. Most of the time, it will automatically add a cache for you, but a poor one. You'd better handle it yourself.

", + "isOkThreshold": 5, + "isBadThreshold": 20, + "isAbnormalThreshold": 40 + }, "cachingTooShort": { "tool": "phantomas", "label": "Caching too short", diff --git a/lib/scoreCalculator.js b/lib/scoreCalculator.js index 25a147c..2d8dfaf 100644 --- a/lib/scoreCalculator.js +++ b/lib/scoreCalculator.js @@ -9,8 +9,9 @@ var ScoreCalculator = function() { var results = { categories: {} }; - var weight; + var categoryScore; var categoryName; + var weight; debug('Starting calculating scores'); @@ -20,31 +21,24 @@ var ScoreCalculator = function() { label: profile.categories[categoryName].label }; - var sum = 0; - var totalWeight = 0; + categoryScore = new ScoreMerger(); var rules = []; + var policyScore; for (var policyName in profile.categories[categoryName].policies) { weight = profile.categories[categoryName].policies[policyName]; if (data.rules[policyName]) { - var policyScore = data.rules[policyName].score + data.rules[policyName].abnormalityScore; - sum += policyScore * weight; + policyScore = data.rules[policyName].score + (data.rules[policyName].abnormalityScore * 2); + categoryScore.push(policyScore, weight); } else { - // Max value if rule is not here - sum += 100 * weight; debug('Warning: could not find rule %s', policyName); } - totalWeight += weight; rules.push(policyName); } - if (totalWeight === 0) { - categoryResult.categoryScore = 100; - } else { - categoryResult.categoryScore = Math.round(Math.max(sum, 0) / totalWeight); - } + categoryResult.categoryScore = categoryScore.getScore(); categoryResult.rules = rules; results.categories[categoryName] = categoryResult; @@ -52,26 +46,17 @@ var ScoreCalculator = function() { // Calculate general score - var globalSum = 0; - var globalTotalWeight = 0; + var globalScore = new ScoreMerger(); for (categoryName in profile.globalScore) { weight = profile.globalScore[categoryName]; if (results.categories[categoryName]) { - globalSum += results.categories[categoryName].categoryScore * weight; - } else { - globalSum += 100 * weight; + globalScore.push(results.categories[categoryName].categoryScore, weight); } - globalTotalWeight += profile.globalScore[categoryName]; - } - - if (globalTotalWeight === 0) { - results.globalScore = 100; - } else { - results.globalScore = Math.round(globalSum / globalTotalWeight); } + results.globalScore = Math.round(globalScore.getScore()); debug('Score calculation finished:'); @@ -79,6 +64,24 @@ var ScoreCalculator = function() { return results; }; + + + var ScoreMerger = function() { + var sum = 0; + var totalWeight = 0; + + this.push = function(score, weight) { + sum += (100 - score) * weight; + totalWeight += weight; + }; + + this.getScore = function() { + if (totalWeight === 0) { + return 100; + } + return Math.round(100 - (sum / totalWeight)); + }; + }; }; module.exports = new ScoreCalculator(); \ No newline at end of file From a141122fbd12e5fe0f038bc7029d7b7f8e9ccb07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Tue, 23 Dec 2014 16:08:39 +0100 Subject: [PATCH 53/98] Share score button + Restart test button --- front/src/css/dashboard.css | 117 ++++++++++---------- front/src/css/main.css | 24 ++++- front/src/css/timeline.css | 8 +- front/src/fonts/icons.woff | Bin 1784 -> 2036 bytes front/src/fonts/svg-icons/loop.svg | 1 + front/src/js/controllers/dashboardCtrl.js | 41 +++++++- front/src/js/controllers/ruleCtrl.js | 11 +- front/src/js/controllers/timelineCtrl.js | 11 +- front/src/less/dashboard.less | 123 ++++++++++------------ front/src/less/icons.less | 25 +++-- front/src/less/main.less | 11 +- front/src/less/timeline.less | 8 +- front/src/views/dashboard.html | 28 ++++- front/src/views/index.html | 2 +- front/src/views/resultSubHeader.html | 1 + test/fixtures/scoreOutput.json | 6 +- 16 files changed, 257 insertions(+), 160 deletions(-) create mode 100755 front/src/fonts/svg-icons/loop.svg diff --git a/front/src/css/dashboard.css b/front/src/css/dashboard.css index 4720f58..8ed25ac 100644 --- a/front/src/css/dashboard.css +++ b/front/src/css/dashboard.css @@ -1,8 +1,49 @@ .testedUrl { color: inherit; } -h4 { - margin-bottom: 0.5em; +.summary { + text-align: center; +} +.globalScore { + margin-bottom: 3em; +} +.globalScore .globalGrade { + margin: 0.5 auto; + width: 2.5em; + height: 2.5em; + line-height: 2.5em; + border-radius: 0.5em; + font-size: 3em; + font-weight: bold; + vertical-align: middle; +} +.globalScore .on100 { + font-size: 1.2em; + font-weight: bold; + margin: 0.5em 0 1em; +} +.globalScore .tweetText { + color: #333; + background: #F2F2F2; + border: none; + width: 25em; + padding: 0.4em; + border-radius: 0.5em; + box-shadow: 0.05em 0.1em 0 0 #999; +} +.globalScore .tweetButton, +.globalScore .linkedinButton { + color: #333; + background: #F2F2F2; + margin-right: 0; +} +.globalScore .tweetButton:hover, +.globalScore .linkedinButton:hover { + color: #F2F2F2; + background: #e74c3c; +} +.globalScore input { + font-size: 0.9em; } .notations { display: table; @@ -101,66 +142,18 @@ h4 { .notations .criteria .icon-question { color: transparent; } -/**** NgModal popin (have a look inside bower_components) ****/ -.ng-modal { - position: fixed; - z-index: 9999; - top: 0; - left: 0; - width: 100%; - height: 100%; - text-align: left; +.fromShare { + margin-bottom: 3em; } -.ng-modal-overlay { - position: absolute; - z-index: 9999; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: #000; - opacity: 0.5; -} -.ng-modal-dialog { - z-index: 10000; - position: absolute; - top: 50%; - left: 50%; - width: 50%; - transform: translate(-50%, -50%); - -webkit-transform: translate(-50%, -50%); - background-color: #fff; - padding: 10px; - border: 3px solid #f1c40f; +.fromShare a { + font-size: 1em; + padding: 0.3em 0.5em; + margin: 0.5em; + line-height: 2em; + border: 0 solid; border-radius: 0.5em; - color: #000; -} -.ng-modal-dialog-content { - overflow-x: hidden; - overflow-y: scroll; - word-wrap: break-word; - max-height: 20em; - font-weight: normal; - white-space: normal; -} -.ng-modal-close { - position: absolute; - top: 3px; - right: 5px; - cursor: pointer; - font-size: 120%; - padding: 5px; - display: inline-block; -} -.ng-modal-close-x { - font-weight: bold; - font-family: Arial, sans-serif; -} -.ng-modal-title { - font-weight: bold; - font-size: 1.5em; - display: block; - margin-bottom: 10px; - padding-bottom: 7px; - border-bottom: solid 1px #999; + box-shadow: 0.1em 0.2em 0 0 #5e2846; + background: #e74c3c; + color: #fff; + text-decoration: none; } diff --git a/front/src/css/main.css b/front/src/css/main.css index 8c66887..6022ca3 100644 --- a/front/src/css/main.css +++ b/front/src/css/main.css @@ -46,7 +46,8 @@ h1 span { cursor: pointer; text-decoration: none; } -.resultsMenu .back { +.resultsMenu .menuItem.back, +.resultsMenu .menuItem.restart { color: #413; border-color: #413; } @@ -137,7 +138,7 @@ h1 span { -webkit-font-smoothing: antialiased; } .icon-lab:before { - content: "\e003"; + content: "\e004"; } .icon-question { font-family: "fontsmith-icons"; @@ -176,7 +177,7 @@ h1 span { -webkit-font-smoothing: antialiased; } .icon-back:before { - content: "\e005"; + content: "\e006"; } .icon-summary { font-family: "fontsmith-icons"; @@ -189,7 +190,7 @@ h1 span { -webkit-font-smoothing: antialiased; } .icon-summary:before { - content: "\e002"; + content: "\e003"; } .icon-spaghetti { font-family: "fontsmith-icons"; @@ -202,5 +203,18 @@ h1 span { -webkit-font-smoothing: antialiased; } .icon-spaghetti:before { - content: "\e004"; + content: "\e005"; +} +.icon-loop { + font-family: "fontsmith-icons"; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; +} +.icon-loop:before { + content: "\e002"; } diff --git a/front/src/css/timeline.css b/front/src/css/timeline.css index c2eb686..43304d1 100644 --- a/front/src/css/timeline.css +++ b/front/src/css/timeline.css @@ -1,4 +1,7 @@ /* Timeline colors, related to Window Performances */ +.execution { + text-align: center; +} .timeline { margin: 2em 0 5em; } @@ -117,9 +120,12 @@ border-radius: 0.2em; } .filters { - margin: 1em 0; + margin: 1em auto; padding: 0.5em; + min-width: 30em; + width: 30%; border: 1px dotted #aaa; + text-align: left; } .slowRequestsLimit { width: 3em; diff --git a/front/src/fonts/icons.woff b/front/src/fonts/icons.woff index 9410fd7527866bd261e99775bbc5dfc00e41dde4..196e5567b1958b74d86b0e6b4276079f13bd267a 100644 GIT binary patch delta 1863 zcmXYyc{J2}AIEi>nJb0EUz-R7HzxoPo`RO-Ar$FvQl;(AUm!qk6Us^uDu6KnBf07s z000~XWg-MsZ+_l7fKH2ooG+Bm0e}EyN%q@FXb>$BaxBQ5hA%&lBLO zz><#n!h$pg=DvAZ@EJmx`b+`*l==%Z#n_Oyc~+lf`@0_p+E#E9Z*;%nZn5L_T0@_Y zZ5`|;S&i)^v&TfMKcuN3{QHO96B`<~)2among#EM30_2}UeYzZpwBe!LEOa*s01`n z4RCI7Z^4KP$qpOt2!Many z3ak2T;Y#M#Sm#Z@OosVDrfDTlB<-xWByXq19OZHDW={6Wn2Nc^wV{w)^F*{iagKqv z#CMu_ECi!&HOUiWlD5JzxCn|^T1^7wgo?+gcUB;Ot%Y-X;$PMt?g|U)qV=zFjSa+^ zsrd}4?Bh`#?U{-db9ig8p-X*NI=r-2<9y}_^)s16)F(y^`s;4)N>~okB|F5_Lh3AE zr?~IUU=H*u3N7#(Cb)jGI7_Zdb>S58!lf1?CEi+PPk+J7WW>apZe20bgEaJ`T-teh zt*9F8L0&gSUi2#L#c0#xZAELP72LEgi)x+!a zKXaPU5oP5zEb!zLyGuSBgw6>qishQ&qQ0loRGZasc@=l&?$YM_dM~dZioM0DqUaMV z&Z^5*TxZnqJ*INsba!0ewr2qJ&N+ z^as@^p75?u3&>@CZ?M!$0zc=+QTm!jyUi=!tyHL-CO)2t%^1+6XSj8 zS*Lv1Y$i~%`9{^uo?`Z5pIt%VJYpQ@)xVFac*fex&+eghss1*01@Sem_**CvA1vpj z-KvpSOEs0ZrO$kri_7Bu7Ht~ZaPTo~QqdVU!9}K~D$?d+-*Bd$#%Yo^x7g!pYV{`xhoKggnZuiB#-LC4kwVh5q8!RR0E8VnC7y*o$;n&lT?zQhZWug!N zslTiJsM3a|G`vbV>(gzBKyO$8f3erd%;U6Kbg{VWs|oPS4=UTdGa6Yoo{K?8 z6YMye=LNhDxO4bc@m)!GifGK$B1b>BhJ*O$sV%6fYT{>_`b#lLj_d+VV6?;gW8 zSbs!6|I@~PdzrWEf})_;D$pQw)H+sY#wB(Wx8GcSq1NhcGYI70A9iA}0;p7KAdqI* zmNS~ZF228@-5Uy!Ux>8-uj2(!1F6&{A%9Ugit0~=;iXi91-4`XSS?`AL<>g~4a`wt zEf8*sf3pfg$2LKlU5dGC?@0^JcJcGKh*o)7|s&ykf9K(dgy)=Ge3OH1xDW3>` zDNP-;W~M(JK~p}XG6jiPEAX(?s#}+fVUc_|J(c_2e?{>7G-`*V$%W5ir+66v%Yv%Clp@O@i-%=%KO9LHa6&5r&ILsM9i%lR)98rp1^f7@a%<=@6I#3Huo&n=lg zyAL#fQat5P|NZ>E%9V~~7AK!k@(52Kx0s$@xvFt*`Mh}kZ)(e(Y~R6Cw=k+-cNK{D zAG>N1;EkZ#U~(tw3=Vydj%qAEWbuVieC2SP9A{=<&{k_=hDTkVU_~rG8dHSK2*kfo zl#Q8vDT8jeY$ht**9RmdfX3tIiQMb!iY4b?t>R3YS*my6=2pEiUR<%b`u=eg-!;(X zB|E%JVdbr5RTVkQ{%}=%@Ff)Qt+)JhLZ@QS#Y3&HF``2C`$7D$)UL2Kg_|lYw2PVU zl71Pp*~o7pEw4au{?iU*{HU2cmipR1LfaW~b*(plPFjQZd46v5_w>NN9*1i6yPu<|fP+NY)+Gj6b)LzoFQe?0Y`$?w=|dn<;$W@{$ATtCo_lgBnzdUtjYyLmj~QDf>#l7sP_va!;W+&W~^iao@z4TSfLhEWJo_kzj7TYWm9Ls&m3n*yNNaPU6E*g+%5w zT3~eRb!zjrJlPbD!a0_fv0M9Ie#qP|S0t69H%dv(qe7Vp7K1+}{1hZX&;8tCJ1r&V zy~-^op8n`QtKKT_aO|K;iq87mQ(Mto)3ZHgXs!A*^O!X2VQh$=ApibEPkE714_Otg z%&{Ik+O%`S=1sh6(OBLAJ;z>62REP19o1}hYB5C0B1sRV-tpasnFWtn+|7zre&ct1YDds>iwGOkU0pv% zT||T#u;l37E4z=f6>jLmqpbjt028<$06GBZe_PElZz{qCOIBK!;gLi};UltMC9cHb zl0E+1kJ~KC1Dp%i<>Cvhwaxaqr3p=-ywm1pMhwXyQt8v){sy6XOYKe$kILX~G4#b9 zub4@U3R-ll-qY1O!#yE03Lymho&<$G?(YukSpmDmaY3&cTy{0CzI BvWox! diff --git a/front/src/fonts/svg-icons/loop.svg b/front/src/fonts/svg-icons/loop.svg new file mode 100755 index 0000000..11e65f9 --- /dev/null +++ b/front/src/fonts/svg-icons/loop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/js/controllers/dashboardCtrl.js b/front/src/js/controllers/dashboardCtrl.js index f28a4ad..db1c4dd 100644 --- a/front/src/js/controllers/dashboardCtrl.js +++ b/front/src/js/controllers/dashboardCtrl.js @@ -1,8 +1,9 @@ var dashboardCtrl = angular.module('dashboardCtrl', ['resultsFactory', 'menuService']); -dashboardCtrl.controller('DashboardCtrl', ['$scope', '$rootScope', '$routeParams', '$location', 'Results', 'Menu', function($scope, $rootScope, $routeParams, $location, Results, Menu) { +dashboardCtrl.controller('DashboardCtrl', ['$scope', '$rootScope', '$routeParams', '$location', 'Results', 'Runs', 'Menu', function($scope, $rootScope, $routeParams, $location, Results, Runs, Menu) { $scope.runId = $routeParams.runId; $scope.Menu = Menu.setCurrentPage('dashboard', $scope.runId); + $scope.fromSocialShare = $location.search().share; function loadResults() { // Load result if needed @@ -10,15 +11,53 @@ dashboardCtrl.controller('DashboardCtrl', ['$scope', '$rootScope', '$routeParams Results.get({runId: $routeParams.runId}, function(result) { $rootScope.loadedResult = result; $scope.result = result; + init(); }); } else { $scope.result = $rootScope.loadedResult; + init(); } } + function init() { + $scope.globalScore = Math.max($scope.result.scoreProfiles.generic.globalScore, 0); + $scope.tweetText = 'My website\'s score is ' + $scope.globalScore + '/100 on #YellowLabTools!'; + } + $scope.showRulePage = function(ruleName) { $location.path('/result/' + $scope.runId + '/rule/' + ruleName); }; + $scope.testAgain = function() { + Runs.save({ + url: $scope.result.params.url, + waitForResponse: false + }, function(data) { + $location.path('/queue/' + data.runId); + }); + }; + + /// When comming from a social shared link, the user needs to click on "See full report" button to display the full dashboard. + $scope.seeFullReport = function() { + $scope.fromSocialShare = false; + $location.search({}); + }; + + $scope.shareOnTwitter = function(message) { + openSocialPopup('https://twitter.com/intent/tweet?url=' + document.URL + '%3Fshare&text=' + encodeURIComponent(message)); + }; + + $scope.shareOnLinkedin = function(message) { + openSocialPopup('https://www.linkedin.com/shareArticle?mini=true&url=' + document.URL + '%3Fshare&title=' + encodeURIComponent(message) + '&summary=' + encodeURIComponent('YellowLabTools is a free online tool that analyzes performance and front-end quality of a webpage.')); + }; + + function openSocialPopup(url) { + var winHeight = 400; + var winWidth = 600; + var winTop = (screen.height / 2) - (winHeight / 2); + var winLeft = (screen.width / 2) - (winWidth / 2); + window.open(url, 'sharer', 'top=' + winTop + ',left=' + winLeft + ',toolbar=0,status=0,width=' + winWidth + ',height=' + winHeight); + } + loadResults(); }]); \ No newline at end of file diff --git a/front/src/js/controllers/ruleCtrl.js b/front/src/js/controllers/ruleCtrl.js index f41ae8d..ae7d58c 100644 --- a/front/src/js/controllers/ruleCtrl.js +++ b/front/src/js/controllers/ruleCtrl.js @@ -1,6 +1,6 @@ var ruleCtrl = angular.module('ruleCtrl', []); -ruleCtrl.controller('RuleCtrl', ['$scope', '$rootScope', '$routeParams', '$location', '$sce', 'Menu', 'Results', function($scope, $rootScope, $routeParams, $location, $sce, Menu, Results) { +ruleCtrl.controller('RuleCtrl', ['$scope', '$rootScope', '$routeParams', '$location', '$sce', 'Menu', 'Results', 'Runs', function($scope, $rootScope, $routeParams, $location, $sce, Menu, Results, Run) { $scope.runId = $routeParams.runId; $scope.policyName = $routeParams.policy; $scope.Menu = Menu.setCurrentPage(null, $scope.runId); @@ -29,5 +29,14 @@ ruleCtrl.controller('RuleCtrl', ['$scope', '$rootScope', '$routeParams', '$locat $location.path('/result/' + $scope.runId); }; + $scope.testAgain = function() { + Runs.save({ + url: $scope.result.params.url, + waitForResponse: false + }, function(data) { + $location.path('/queue/' + data.runId); + }); + }; + loadResults(); }]); \ No newline at end of file diff --git a/front/src/js/controllers/timelineCtrl.js b/front/src/js/controllers/timelineCtrl.js index 66c568a..da813f9 100644 --- a/front/src/js/controllers/timelineCtrl.js +++ b/front/src/js/controllers/timelineCtrl.js @@ -1,6 +1,6 @@ var timelineCtrl = angular.module('timelineCtrl', []); -timelineCtrl.controller('TimelineCtrl', ['$scope', '$rootScope', '$routeParams', '$location', '$timeout', 'Menu', 'Results', function($scope, $rootScope, $routeParams, $location, $timeout, Menu, Results) { +timelineCtrl.controller('TimelineCtrl', ['$scope', '$rootScope', '$routeParams', '$location', '$timeout', 'Menu', 'Results', 'Runs', function($scope, $rootScope, $routeParams, $location, $timeout, Menu, Results, Runs) { $scope.runId = $routeParams.runId; $scope.Menu = Menu.setCurrentPage('timeline', $scope.runId); @@ -131,6 +131,15 @@ timelineCtrl.controller('TimelineCtrl', ['$scope', '$rootScope', '$routeParams', $location.path('/result/' + $scope.runId); }; + $scope.testAgain = function() { + Runs.save({ + url: $scope.result.params.url, + waitForResponse: false + }, function(data) { + $location.path('/queue/' + data.runId); + }); + }; + loadResults(); }]); \ No newline at end of file diff --git a/front/src/less/dashboard.less b/front/src/less/dashboard.less index 42639ca..6340fca 100644 --- a/front/src/less/dashboard.less +++ b/front/src/less/dashboard.less @@ -2,8 +2,48 @@ color: inherit; } -h4 { - margin-bottom: 0.5em; +.summary { + text-align: center; +} + +.globalScore { + margin-bottom: 3em; + .globalGrade { + margin: 0.5 auto; + width: 2.5em; + height: 2.5em; + line-height: 2.5em; + border-radius: 0.5em; + font-size: 3em; + font-weight: bold; + vertical-align: middle; + } + .on100 { + font-size: 1.2em; + font-weight: bold; + margin: 0.5em 0 1em; + } + .tweetText { + color: #333; + background: #F2F2F2; + border: none; + width: 25em; + padding: 0.4em; + border-radius: 0.5em; + box-shadow: 0.05em 0.1em 0 0 #999; + } + .tweetButton, .linkedinButton { + color: #333; + background: #F2F2F2; + margin-right: 0; + &:hover { + color: #F2F2F2; + background: #e74c3c; + } + } + input { + font-size: 0.9em; + } } .notations { @@ -93,67 +133,18 @@ h4 { color: transparent; } - -/**** NgModal popin (have a look inside bower_components) ****/ -.ng-modal { - position: fixed; - z-index: 9999; - top: 0; - left: 0; - width: 100%; - height: 100%; - text-align: left; -} -.ng-modal-overlay { - position: absolute; - z-index: 9999; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: #000; - opacity: 0.5; -} -.ng-modal-dialog { - z-index: 10000; - position: absolute; - top: 50%; - left: 50%; - width: 50%; - transform: translate(-50%, -50%); - -webkit-transform: translate(-50%, -50%); - background-color: #fff; - padding: 10px; - border: 3px solid #f1c40f; - border-radius: 0.5em; - color: #000; -} -.ng-modal-dialog-content { - overflow-x: hidden; - overflow-y: scroll; - word-wrap: break-word; - max-height: 20em; - font-weight: normal; - white-space: normal; -} -.ng-modal-close { - position: absolute; - top: 3px; - right: 5px; - cursor: pointer; - font-size: 120%; - padding: 5px; - display: inline-block; -} -.ng-modal-close-x { - font-weight: bold; - font-family: Arial, sans-serif; -} -.ng-modal-title { - font-weight: bold; - font-size: 1.5em; - display: block; - margin-bottom: 10px; - padding-bottom: 7px; - border-bottom: solid 1px #999; -} +.fromShare { + margin-bottom: 3em; + a { + font-size: 1em; + padding: 0.3em 0.5em; + margin: 0.5em; + line-height: 2em; + border: 0 solid; + border-radius: 0.5em; + box-shadow: 0.1em 0.2em 0 0 #5e2846; + background: #e74c3c; + color: #fff; + text-decoration: none; + } +} \ No newline at end of file diff --git a/front/src/less/icons.less b/front/src/less/icons.less index 90c82b5..f02dcbc 100644 --- a/front/src/less/icons.less +++ b/front/src/less/icons.less @@ -1,21 +1,24 @@ -@list-font-family: "fontsmith-icons"; -@list-value: "\e002"; -@list: '"fontsmith-icons"' '"\\e002"'; -@bars-font-family: "fontsmith-icons"; -@bars-value: "\e004"; -@bars: '"fontsmith-icons"' '"\\e004"'; @lab-font-family: "fontsmith-icons"; -@lab-value: "\e003"; -@lab: '"fontsmith-icons"' '"\\e003"'; +@lab-value: "\e004"; +@lab: '"fontsmith-icons"' '"\\e004"'; +@list-font-family: "fontsmith-icons"; +@list-value: "\e003"; +@list: '"fontsmith-icons"' '"\\e003"'; +@loop-font-family: "fontsmith-icons"; +@loop-value: "\e002"; +@loop: '"fontsmith-icons"' '"\\e002"'; @warning-font-family: "fontsmith-icons"; @warning-value: "\e000"; @warning: '"fontsmith-icons"' '"\\e000"'; -@arrow-left3-font-family: "fontsmith-icons"; -@arrow-left3-value: "\e005"; -@arrow-left3: '"fontsmith-icons"' '"\\e005"'; @question-font-family: "fontsmith-icons"; @question-value: "\e001"; @question: '"fontsmith-icons"' '"\\e001"'; +@bars-font-family: "fontsmith-icons"; +@bars-value: "\e005"; +@bars: '"fontsmith-icons"' '"\\e005"'; +@arrow-left3-font-family: "fontsmith-icons"; +@arrow-left3-value: "\e006"; +@arrow-left3: '"fontsmith-icons"' '"\\e006"'; .icon-font-family(@char) { font-family: ~`@{char}[0]`; diff --git a/front/src/less/main.less b/front/src/less/main.less index 91a0097..a2c6c0f 100644 --- a/front/src/less/main.less +++ b/front/src/less/main.less @@ -41,10 +41,10 @@ h1 span { border-radius: 0.5em; cursor: pointer; text-decoration: none; -} -.resultsMenu .back { - color: #413; - border-color: #413; + &.back, &.restart { + color: #413; + border-color: #413; + } } .resultsMenu .menuItem div { padding-top: 0.5em; @@ -146,4 +146,7 @@ h1 span { } .icon-spaghetti { .icon(@bars); +} +.icon-loop { + .icon(@loop); } \ No newline at end of file diff --git a/front/src/less/timeline.less b/front/src/less/timeline.less index a15465a..3c7a541 100644 --- a/front/src/less/timeline.less +++ b/front/src/less/timeline.less @@ -10,6 +10,9 @@ @domInteractiveColor: #FFE433; @domInteractiveBg: #FFFCCC; +.execution { + text-align: center; +} .timeline { margin: 2em 0 5em; @@ -130,9 +133,12 @@ } .filters { - margin: 1em 0; + margin: 1em auto; padding: 0.5em; + min-width: 30em; + width: 30%; border: 1px dotted #aaa; + text-align: left; } .slowRequestsLimit { diff --git a/front/src/views/dashboard.html b/front/src/views/dashboard.html index 9aa74e8..7599530 100644 --- a/front/src/views/dashboard.html +++ b/front/src/views/dashboard.html @@ -1,14 +1,30 @@
-

Grades

+ +

Global score

+
+
+ +
{{globalScore}}/100
+
+
+ + + + + +
+
-
+

Score details

+
{{category.label}}
-
@@ -28,4 +44,10 @@
+ +
+

Yellow Lab Tools is a free online tool that analyzes performance and front-end quality.

+ See the full report for this page + Test another webpage +
\ No newline at end of file diff --git a/front/src/views/index.html b/front/src/views/index.html index 7bc52c3..84ba965 100644 --- a/front/src/views/index.html +++ b/front/src/views/index.html @@ -1,6 +1,6 @@

Free online test to help speeding up heavy web pages

- +
\ No newline at end of file diff --git a/front/src/views/resultSubHeader.html b/front/src/views/resultSubHeader.html index e429366..af20dd5 100644 --- a/front/src/views/resultSubHeader.html +++ b/front/src/views/resultSubHeader.html @@ -2,6 +2,7 @@
New test
+
Test again
\ No newline at end of file diff --git a/test/fixtures/scoreOutput.json b/test/fixtures/scoreOutput.json index 7c0259f..98c7756 100644 --- a/test/fixtures/scoreOutput.json +++ b/test/fixtures/scoreOutput.json @@ -1,9 +1,9 @@ { - "globalScore": 57, + "globalScore": 36, "categories": { "category1": { "label": "Category 1", - "categoryScore": 83, + "categoryScore": 79, "rules": [ "metric1", "metric2", @@ -13,7 +13,7 @@ }, "category2": { "label": "Category 2", - "categoryScore": 0, + "categoryScore": -55, "rules": [ "metric5", "metric6", From 5d2e647f10f5d2b4d4ad9fe33e7f878b46007278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Tue, 23 Dec 2014 16:27:18 +0100 Subject: [PATCH 54/98] Put the tweet block at the bottom of the dashboard --- front/src/css/dashboard.css | 18 +++++++++--------- front/src/less/dashboard.less | 9 ++++++--- front/src/views/dashboard.html | 15 ++++++++------- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/front/src/css/dashboard.css b/front/src/css/dashboard.css index 8ed25ac..8447b98 100644 --- a/front/src/css/dashboard.css +++ b/front/src/css/dashboard.css @@ -22,8 +22,8 @@ font-weight: bold; margin: 0.5em 0 1em; } -.globalScore .tweetText { - color: #333; +.tweet .tweetText { + color: #413; background: #F2F2F2; border: none; width: 25em; @@ -31,24 +31,24 @@ border-radius: 0.5em; box-shadow: 0.05em 0.1em 0 0 #999; } -.globalScore .tweetButton, -.globalScore .linkedinButton { - color: #333; +.tweet .tweetButton, +.tweet .linkedinButton { + color: #413; background: #F2F2F2; margin-right: 0; } -.globalScore .tweetButton:hover, -.globalScore .linkedinButton:hover { +.tweet .tweetButton:hover, +.tweet .linkedinButton:hover { color: #F2F2F2; background: #e74c3c; } -.globalScore input { +.tweet input { font-size: 0.9em; } .notations { display: table; width: 80%; - margin: 0 10%; + margin: 0 10% 3em; border-spacing: 1em; } .notations > div { diff --git a/front/src/less/dashboard.less b/front/src/less/dashboard.less index 6340fca..e41a435 100644 --- a/front/src/less/dashboard.less +++ b/front/src/less/dashboard.less @@ -23,8 +23,11 @@ font-weight: bold; margin: 0.5em 0 1em; } +} + +.tweet { .tweetText { - color: #333; + color: #413; background: #F2F2F2; border: none; width: 25em; @@ -33,7 +36,7 @@ box-shadow: 0.05em 0.1em 0 0 #999; } .tweetButton, .linkedinButton { - color: #333; + color: #413; background: #F2F2F2; margin-right: 0; &:hover { @@ -49,7 +52,7 @@ .notations { display: table; width: 80%; - margin: 0 10%; + margin: 0 10% 3em; border-spacing: 1em; } .notations > div { diff --git a/front/src/views/dashboard.html b/front/src/views/dashboard.html index 7599530..5f504d0 100644 --- a/front/src/views/dashboard.html +++ b/front/src/views/dashboard.html @@ -7,13 +7,6 @@
{{globalScore}}/100
-
-
- - - -
-

Score details

@@ -45,6 +38,14 @@
+
+
+ + + +
+
+

Yellow Lab Tools is a free online tool that analyzes performance and front-end quality.

See the full report for this page From 2ddf2e8341a5b04f2adb7adffd8e1629d2f28ecf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Tue, 23 Dec 2014 16:54:24 +0100 Subject: [PATCH 55/98] Fix Test again button on Rule page --- front/src/js/controllers/ruleCtrl.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/js/controllers/ruleCtrl.js b/front/src/js/controllers/ruleCtrl.js index ae7d58c..63fa2ef 100644 --- a/front/src/js/controllers/ruleCtrl.js +++ b/front/src/js/controllers/ruleCtrl.js @@ -1,6 +1,6 @@ var ruleCtrl = angular.module('ruleCtrl', []); -ruleCtrl.controller('RuleCtrl', ['$scope', '$rootScope', '$routeParams', '$location', '$sce', 'Menu', 'Results', 'Runs', function($scope, $rootScope, $routeParams, $location, $sce, Menu, Results, Run) { +ruleCtrl.controller('RuleCtrl', ['$scope', '$rootScope', '$routeParams', '$location', '$sce', 'Menu', 'Results', 'Runs', function($scope, $rootScope, $routeParams, $location, $sce, Menu, Results, Runs) { $scope.runId = $routeParams.runId; $scope.policyName = $routeParams.policy; $scope.Menu = Menu.setCurrentPage(null, $scope.runId); From c0e02726be4058dd349d54d26ce4fd609de96ba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Tue, 23 Dec 2014 17:33:13 +0100 Subject: [PATCH 56/98] Fix categories order on dashboard --- front/src/js/controllers/dashboardCtrl.js | 3 +++ front/src/views/dashboard.html | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/front/src/js/controllers/dashboardCtrl.js b/front/src/js/controllers/dashboardCtrl.js index db1c4dd..12c269c 100644 --- a/front/src/js/controllers/dashboardCtrl.js +++ b/front/src/js/controllers/dashboardCtrl.js @@ -20,6 +20,9 @@ dashboardCtrl.controller('DashboardCtrl', ['$scope', '$rootScope', '$routeParams } function init() { + // By default, Angular sorts object's attributes alphabetically. Countering this problem by retrieving the keys order here. + $scope.categoriesOrder = Object.keys($scope.result.scoreProfiles.generic.categories); + $scope.globalScore = Math.max($scope.result.scoreProfiles.generic.globalScore, 0); $scope.tweetText = 'My website\'s score is ' + $scope.globalScore + '/100 on #YellowLabTools!'; } diff --git a/front/src/views/dashboard.html b/front/src/views/dashboard.html index 5f504d0..896ee77 100644 --- a/front/src/views/dashboard.html +++ b/front/src/views/dashboard.html @@ -11,7 +11,7 @@

Score details

-
+
{{category.label}}
From 606c2c6f95e6fc4bc872d0bea5da4d44847e4fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Tue, 23 Dec 2014 18:06:33 +0100 Subject: [PATCH 57/98] DOMQueriesAvoidable taking offenders from DOMQueriesDuplicated --- lib/metadata/policies.js | 3 ++- lib/rulesChecker.js | 5 +++++ test/fixtures/rulesCheckerOutput.json | 4 +++- test/fixtures/rulesCheckerPolicies.json | 3 ++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/metadata/policies.js b/lib/metadata/policies.js index d88da7a..b9cb910 100644 --- a/lib/metadata/policies.js +++ b/lib/metadata/policies.js @@ -55,7 +55,8 @@ var policies = { "message": "

This is the number of queries that could be avoided by removing all duplicated queries.

Simply save the result of a query in a variable. Ok it is not always simple, especially with third-party scripts, but at least do it with your own code.

", "isOkThreshold": 0, "isBadThreshold": 200, - "isAbnormalThreshold": 500 + "isAbnormalThreshold": 500, + "takeOffendersFrom": "DOMqueriesDuplicated" }, "eventsBound": { "tool": "phantomas", diff --git a/lib/rulesChecker.js b/lib/rulesChecker.js index 1fab084..bbbae49 100644 --- a/lib/rulesChecker.js +++ b/lib/rulesChecker.js @@ -23,6 +23,11 @@ var RulesChecker = function() { policy: policy }; + // Take DOMqueriesAvoidable's offenders from DOMqueriesDuplicated. + if (policy.takeOffendersFrom) { + data.toolsResults[policy.tool].offenders[metricName] = data.toolsResults[policy.tool].offenders[policy.takeOffendersFrom]; + } + if (data.toolsResults[policy.tool].offenders && data.toolsResults[policy.tool].offenders[metricName] && data.toolsResults[policy.tool].offenders[metricName].length > 0) { diff --git a/test/fixtures/rulesCheckerOutput.json b/test/fixtures/rulesCheckerOutput.json index d482344..ef696af 100644 --- a/test/fixtures/rulesCheckerOutput.json +++ b/test/fixtures/rulesCheckerOutput.json @@ -21,9 +21,11 @@ "message": "A great message", "isOkThreshold": 1000, "isBadThreshold": 3000, - "isAbnormalThreshold": 5000 + "isAbnormalThreshold": 5000, + "takeOffendersFrom": "metric3" }, "value": 222, + "offenders": ["offender1", "offender2"], "bad": false, "abnormal": false, "score": 100, diff --git a/test/fixtures/rulesCheckerPolicies.json b/test/fixtures/rulesCheckerPolicies.json index 3cadee3..9c2f064 100644 --- a/test/fixtures/rulesCheckerPolicies.json +++ b/test/fixtures/rulesCheckerPolicies.json @@ -13,7 +13,8 @@ "message": "A great message", "isOkThreshold": 1000, "isBadThreshold": 3000, - "isAbnormalThreshold": 5000 + "isAbnormalThreshold": 5000, + "takeOffendersFrom": "metric3" }, "metric3": { "tool": "tool1", From b064e73ef4a98819d1b6f5236c1bb9e81af60fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sun, 28 Dec 2014 15:47:12 +0100 Subject: [PATCH 58/98] Improve UI on rule pages --- front/src/css/rule.css | 66 +++++++++++++++++++++++----------- front/src/less/rule.less | 75 +++++++++++++++++++++++++-------------- front/src/views/rule.html | 49 ++++++++++++------------- 3 files changed, 120 insertions(+), 70 deletions(-) diff --git a/front/src/css/rule.css b/front/src/css/rule.css index f9636e7..9fea690 100644 --- a/front/src/css/rule.css +++ b/front/src/css/rule.css @@ -1,32 +1,33 @@ .rule.board { text-align: center; } -.rule h2 span { - font-size: 1.5em; -} -.rule .scoreDisplay { +.rule .ruleTable { display: table; border-spacing: 1em; - margin: 0 auto; + width: 90%; + margin: 2em auto; + background: #f2f2f2; + border: 1px dashed #666; + border-radius: 0.5em; } -.rule .scoreDisplay > div { +.rule .ruleTable > div { display: table-cell; vertical-align: middle; - font-weight: bold; - font-size: 1.5em; } -.rule .scoreDisplay > div.score { +.rule .ruleTable .left { + width: 33%; + font-weight: bold; +} +.rule .ruleTable .right { + width: 67%; +} +.rule .score { font-size: 2.5em; + line-height: 2em; height: 2em; width: 2em; border-radius: 0.5em; -} -.rule h3 { - margin-top: 2.5em; -} -.rule .icon-warning { - font-size: 2em; - color: #FF1919; + margin: 0 auto 0.5em; } .rule .message { width: 80%; @@ -35,19 +36,44 @@ .rule .message p { margin: 0.5em; } -.rule ul { +.rule .message ul { list-style-type: none; padding-left: 0; } -.rule li:before { +.rule .message li:before { content: '\25e6'; margin-right: 0.3em; font-size: 1.2em; position: relative; top: 0.1em; } -.rule .offenders li { - font-size: 0.8em; +.rule .warning { + width: 90%; + margin: -1em auto 2em; + background: #FEE; + border: 1px dashed #e74c3c; + color: #e74c3c; + border-radius: 0.5em; +} +.rule .offendersTable { + display: table; + border-spacing: 0 0.25em; + margin: 0 auto; + min-width: 10%; + max-width: 90%; +} +.rule .offendersTable > div { + display: table-row; +} +.rule .offendersTable > div > div { + display: table-cell; + background: #f2f2f2; + padding: 0 1em; + word-wrap: break-word; + word-break: break-all; +} +.rule .offendersTable > div > div:hover { + background: #EBD8E2; } .rule .notFound { font-size: 1em; diff --git a/front/src/less/rule.less b/front/src/less/rule.less index 4ed2958..a12463a 100644 --- a/front/src/less/rule.less +++ b/front/src/less/rule.less @@ -2,37 +2,34 @@ text-align: center; } -.rule h2 span { - font-size: 1.5em; -} - -.rule .scoreDisplay { +.rule .ruleTable { display: table; border-spacing: 1em; - margin: 0 auto; - + width: 90%; + margin: 2em auto; + background: #f2f2f2; + border: 1px dashed #666; + border-radius: 0.5em; > div { display: table-cell; vertical-align: middle; + } + .left { + width: 33%; font-weight: bold; - font-size: 1.5em; - - &.score { - font-size: 2.5em; - height: 2em; - width: 2em; - border-radius: 0.5em; - } + } + .right { + width: 67%; } } -.rule h3 { - margin-top: 2.5em; -} - -.rule .icon-warning { - font-size: 2em; - color: #FF1919; +.rule .score { + font-size: 2.5em; + line-height: 2em; + height: 2em; + width: 2em; + border-radius: 0.5em; + margin: 0 auto 0.5em; } .rule .message { @@ -43,11 +40,11 @@ } } -.rule ul { +.rule .message ul { list-style-type: none; padding-left: 0; } -.rule li:before { +.rule .message li:before { content:'\25e6'; margin-right: 0.3em; font-size: 1.2em; @@ -55,8 +52,34 @@ top: 0.1em; } -.rule .offenders li { - font-size: 0.8em; +.rule .warning { + width: 90%; + margin: -1em auto 2em; + background: #FEE; + border: 1px dashed #e74c3c; + color: #e74c3c; + border-radius: 0.5em; +} + +.rule .offendersTable { + display: table; + border-spacing: 0 0.25em; + margin: 0 auto; + min-width: 10%; + max-width: 90%; + > div { + display: table-row; + > div { + display: table-cell; + background: #f2f2f2; + padding: 0 1em; + word-wrap: break-word; + word-break: break-all; + &:hover { + background: #EBD8E2; + } + } + } } .rule .notFound { diff --git a/front/src/views/rule.html b/front/src/views/rule.html index 06c5f4b..36b6cfc 100644 --- a/front/src/views/rule.html +++ b/front/src/views/rule.html @@ -1,33 +1,34 @@
-
-

Score for rule:
{{rule.policy.label}}

- -
+ + +
+
+

{{rule.policy.label}}

{{rule.score}}/100
-
-

The page has a real problem with this rule

- - - -

(Abnormality score: {{rule.abnormalityScore}})

+
+

Value: {{rule.value}}

+
- -

Value

- {{rule.value}} - -

Why this rule?

-
- -
-

List of offenders

-
    -
  • - {{offender}} -
  • -
+
+
+

Warning

+

This rule reached the abnormality threshold, which means there is a real problem you should care about.

+
+
+

+ + +

+
+
+
{{offender}}
+
From c31e880f63eab9d7fb3816e0194f5bc7827ad5eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sun, 28 Dec 2014 16:32:24 +0100 Subject: [PATCH 59/98] Add a tip for API advertising on the dashboard #20 --- front/src/css/dashboard.css | 128 +++++++------ front/src/js/controllers/dashboardCtrl.js | 5 + front/src/less/dashboard.less | 219 +++++++++++----------- front/src/views/dashboard.html | 4 + 4 files changed, 191 insertions(+), 165 deletions(-) diff --git a/front/src/css/dashboard.css b/front/src/css/dashboard.css index 8447b98..407887b 100644 --- a/front/src/css/dashboard.css +++ b/front/src/css/dashboard.css @@ -4,10 +4,10 @@ .summary { text-align: center; } -.globalScore { +.summary .globalScore { margin-bottom: 3em; } -.globalScore .globalGrade { +.summary .globalScore .globalGrade { margin: 0.5 auto; width: 2.5em; height: 2.5em; @@ -17,64 +17,41 @@ font-weight: bold; vertical-align: middle; } -.globalScore .on100 { +.summary .globalScore .on100 { font-size: 1.2em; font-weight: bold; margin: 0.5em 0 1em; } -.tweet .tweetText { - color: #413; - background: #F2F2F2; - border: none; - width: 25em; - padding: 0.4em; - border-radius: 0.5em; - box-shadow: 0.05em 0.1em 0 0 #999; -} -.tweet .tweetButton, -.tweet .linkedinButton { - color: #413; - background: #F2F2F2; - margin-right: 0; -} -.tweet .tweetButton:hover, -.tweet .linkedinButton:hover { - color: #F2F2F2; - background: #e74c3c; -} -.tweet input { - font-size: 0.9em; -} -.notations { +.summary .notations { display: table; width: 80%; - margin: 0 10% 3em; + margin: 0 10% 1.5em; border-spacing: 1em; } -.notations > div { +.summary .notations > div { display: table-row; } -.notations > div > div { +.summary .notations > div > div { display: table-cell; height: 2.5em; vertical-align: middle; } -.notations .category { +.summary .notations .category { font-weight: bold; text-align: center; width: 20%; } -.notations .criteria { +.summary .notations .criteria { font-weight: normal; width: 75%; } -.notations .A.categoryScore, -.notations .B.categoryScore, -.notations .C.categoryScore, -.notations .D.categoryScore, -.notations .E.categoryScore, -.notations .F.categoryScore, -.notations .NA.categoryScore { +.summary .notations .A.categoryScore, +.summary .notations .B.categoryScore, +.summary .notations .C.categoryScore, +.summary .notations .D.categoryScore, +.summary .notations .E.categoryScore, +.summary .notations .F.categoryScore, +.summary .notations .NA.categoryScore { width: 2.5em; max-width: 2.5em; min-width: 2.5em; @@ -83,13 +60,13 @@ border-radius: 0.5em; font-weight: bold; } -.notations .grade .A, -.notations .grade .B, -.notations .grade .C, -.notations .grade .D, -.notations .grade .E, -.notations .grade .F, -.notations .grade .NA { +.summary .notations .grade .A, +.summary .notations .grade .B, +.summary .notations .grade .C, +.summary .notations .grade .D, +.summary .notations .grade .E, +.summary .notations .grade .F, +.summary .notations .grade .NA { width: 1em; height: 1em; font-size: 1em; @@ -97,41 +74,41 @@ margin: 0 auto; border-radius: 0.5em; } -.notations .criteria .table { +.summary .notations .criteria .table { width: 100%; } -.notations .criteria .table > div:hover > div { +.summary .notations .criteria .table > div:hover > div { background: #EBD8E2; cursor: pointer; } -.notations .criteria .table > div:hover > div.info { +.summary .notations .criteria .table > div:hover > div.info { background: #FFF; } -.notations .criteria .table > div:hover > div.info .icon-question { +.summary .notations .criteria .table > div:hover > div.info .icon-question { color: #EBD8E2; } -.notations .criteria .grade { +.summary .notations .criteria .grade { width: 10%; padding-left: 0.5em; padding-right: 0.5em; vertical-align: middle; } -.notations .criteria .label { +.summary .notations .criteria .label { width: 70%; } -.notations .criteria .result { +.summary .notations .criteria .result { width: 18%; font-weight: bold; white-space: nowrap; text-align: center; vertical-align: middle; } -.notations .warning .label, -.notations .warning .result, -.notations .icon-warning { +.summary .notations .warning .label, +.summary .notations .warning .result, +.summary .notations .icon-warning { color: #FF1919; } -.notations .criteria .info { +.summary .notations .criteria .info { width: 2%; text-align: center; vertical-align: middle; @@ -139,13 +116,13 @@ padding-left: 0.1em; padding-right: 0.1em; } -.notations .criteria .icon-question { +.summary .notations .criteria .icon-question { color: transparent; } -.fromShare { +.summary .fromShare { margin-bottom: 3em; } -.fromShare a { +.summary .fromShare a { font-size: 1em; padding: 0.3em 0.5em; margin: 0.5em; @@ -157,3 +134,34 @@ color: #fff; text-decoration: none; } +.summary .apiTip { + font-size: 0.8em; + margin-bottom: 4em; + color: #413; +} +.summary .apiTip a { + color: inherit; +} +.summary .tweet .tweetText { + color: #413; + background: #F2F2F2; + border: none; + width: 25em; + padding: 0.4em; + border-radius: 0.5em; + box-shadow: 0.05em 0.1em 0 0 #999; +} +.summary .tweet .tweetButton, +.summary .tweet .linkedinButton { + color: #413; + background: #F2F2F2; + margin-right: 0; +} +.summary .tweet .tweetButton:hover, +.summary .tweet .linkedinButton:hover { + color: #F2F2F2; + background: #e74c3c; +} +.summary .tweet input { + font-size: 0.9em; +} diff --git a/front/src/js/controllers/dashboardCtrl.js b/front/src/js/controllers/dashboardCtrl.js index 12c269c..2dac82b 100644 --- a/front/src/js/controllers/dashboardCtrl.js +++ b/front/src/js/controllers/dashboardCtrl.js @@ -62,5 +62,10 @@ dashboardCtrl.controller('DashboardCtrl', ['$scope', '$rootScope', '$routeParams window.open(url, 'sharer', 'top=' + winTop + ',left=' + winLeft + ',toolbar=0,status=0,width=' + winWidth + ',height=' + winHeight); } + // Returns the URL of the JSON result + $scope.getAPIUrl = function() { + return '/api/results/' + $scope.runId; + }; + loadResults(); }]); \ No newline at end of file diff --git a/front/src/less/dashboard.less b/front/src/less/dashboard.less index e41a435..9813f0e 100644 --- a/front/src/less/dashboard.less +++ b/front/src/less/dashboard.less @@ -6,7 +6,7 @@ text-align: center; } -.globalScore { +.summary .globalScore { margin-bottom: 3em; .globalGrade { margin: 0.5 auto; @@ -25,7 +25,119 @@ } } -.tweet { +.summary .notations { + display: table; + width: 80%; + margin: 0 10% 1.5em; + border-spacing: 1em; +} +.summary .notations > div { + display: table-row; +} +.summary .notations > div > div { + display: table-cell; + height: 2.5em; + vertical-align: middle; +} +.summary .notations .category { + font-weight: bold; + text-align: center; + width: 20%; +} +.summary .notations .criteria { + font-weight: normal; + width: 75%; +} +.A, .B, .C, .D, .E, .F, .NA { + .summary .notations &.categoryScore { + width: 2.5em; + max-width: 2.5em; + min-width: 2.5em; + font-size: 2em; + text-align: center; + border-radius: 0.5em; + font-weight: bold; + } + .summary .notations .grade & { + width: 1em; + height: 1em; + font-size: 1em; + color: transparent; + margin: 0 auto; + border-radius: 0.5em; + } +} + +.summary .notations .criteria .table { + width: 100%; + > div:hover > div { + background: #EBD8E2; + cursor: pointer; + &.info { + background: #FFF; + .icon-question { + color: #EBD8E2; + } + } + } +} +.summary .notations .criteria .grade { + width: 10%; + padding-left: 0.5em; + padding-right: 0.5em; + vertical-align: middle; +} +.summary .notations .criteria .label { + width: 70%; +} +.summary .notations .criteria .result { + width: 18%; + font-weight: bold; + white-space: nowrap; + text-align: center; + vertical-align: middle; +} +.summary .notations .warning .label, .summary .notations .warning .result, .summary .notations .icon-warning { + color: #FF1919; +} +.summary .notations .criteria .info { + width: 2%; + text-align: center; + vertical-align: middle; + background: #FFF; + padding-left: 0.1em; + padding-right: 0.1em; +} +.summary .notations .criteria .icon-question { + color: transparent; +} + +.summary .fromShare { + margin-bottom: 3em; + a { + font-size: 1em; + padding: 0.3em 0.5em; + margin: 0.5em; + line-height: 2em; + border: 0 solid; + border-radius: 0.5em; + box-shadow: 0.1em 0.2em 0 0 #5e2846; + background: #e74c3c; + color: #fff; + text-decoration: none; + } +} + +.summary .apiTip { + font-size: 0.8em; + margin-bottom: 4em; + color: #413; + a { + color: inherit; + } +} + +.summary .tweet { .tweetText { color: #413; background: #F2F2F2; @@ -47,107 +159,4 @@ input { font-size: 0.9em; } -} - -.notations { - display: table; - width: 80%; - margin: 0 10% 3em; - border-spacing: 1em; -} -.notations > div { - display: table-row; -} -.notations > div > div { - display: table-cell; - height: 2.5em; - vertical-align: middle; -} -.notations .category { - font-weight: bold; - text-align: center; - width: 20%; -} -.notations .criteria { - font-weight: normal; - width: 75%; -} -.A, .B, .C, .D, .E, .F, .NA { - .notations &.categoryScore { - width: 2.5em; - max-width: 2.5em; - min-width: 2.5em; - font-size: 2em; - text-align: center; - border-radius: 0.5em; - font-weight: bold; - } - .notations .grade & { - width: 1em; - height: 1em; - font-size: 1em; - color: transparent; - margin: 0 auto; - border-radius: 0.5em; - } -} - -.notations .criteria .table { - width: 100%; - > div:hover > div { - background: #EBD8E2; - cursor: pointer; - &.info { - background: #FFF; - .icon-question { - color: #EBD8E2; - } - } - } -} -.notations .criteria .grade { - width: 10%; - padding-left: 0.5em; - padding-right: 0.5em; - vertical-align: middle; -} -.notations .criteria .label { - width: 70%; -} -.notations .criteria .result { - width: 18%; - font-weight: bold; - white-space: nowrap; - text-align: center; - vertical-align: middle; -} -.notations .warning .label, .notations .warning .result, .notations .icon-warning { - color: #FF1919; -} -.notations .criteria .info { - width: 2%; - text-align: center; - vertical-align: middle; - background: #FFF; - padding-left: 0.1em; - padding-right: 0.1em; -} -.notations .criteria .icon-question { - color: transparent; -} - -.fromShare { - margin-bottom: 3em; - a { - font-size: 1em; - padding: 0.3em 0.5em; - margin: 0.5em; - line-height: 2em; - border: 0 solid; - border-radius: 0.5em; - box-shadow: 0.1em 0.2em 0 0 #5e2846; - background: #e74c3c; - color: #fff; - text-decoration: none; - } } \ No newline at end of file diff --git a/front/src/views/dashboard.html b/front/src/views/dashboard.html index 896ee77..41a72ac 100644 --- a/front/src/views/dashboard.html +++ b/front/src/views/dashboard.html @@ -38,6 +38,10 @@
+
+ Did you know? Yellow Lab Tools now has an API! Here is the JSON output for this run. Checkout the API doc. +
+
From e9505f31f76c8a67054704cb70c51a3dfa2b7b17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sun, 28 Dec 2014 16:57:45 +0100 Subject: [PATCH 60/98] Update phantomas and other dependencies --- bower.json | 6 +++--- package.json | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/bower.json b/bower.json index 9d7da0f..8609ca8 100644 --- a/bower.json +++ b/bower.json @@ -1,8 +1,8 @@ { "name": "yellowlabtools", "dependencies": { - "angular": "~1.3.5", - "angular-route": "~1.3.6", - "angular-resource": "~1.3.6" + "angular": "~1.3.8", + "angular-route": "~1.3.8", + "angular-resource": "~1.3.7" } } diff --git a/package.json b/package.json index c7cfe8c..f77e320 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,12 @@ "dependencies": { "async": "~0.9.0", "body-parser": "~1.10.0", - "compression": "~1.2.1", - "debug": "^2.1.0", - "express": "~4.10.4", - "phantomas": "1.7.0", - "rimraf": "^2.2.8", - "q": "^1.1.2" + "compression": "~1.2.2", + "debug": "~2.1.0", + "express": "~4.10.6", + "phantomas": "1.8.0", + "rimraf": "~2.2.8", + "q": "~1.1.2" }, "devDependencies": { "chai": "^1.10.0", @@ -31,9 +31,9 @@ "grunt-fontsmith": "^0.9.1", "grunt-mocha-test": "^0.12.4", "matchdep": "^0.3.0", - "mocha": "^2.0.1", - "phantomjs": "^1.9.12", - "request": "^2.49.0", + "mocha": "^2.1.0", + "phantomjs": "^1.9.13", + "request": "^2.51.0", "sinon": "^1.12.1", "sinon-chai": "^2.6.0" }, From dccb9bb49cea5ada648b0bcac0ac2ed6976e2150 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sun, 28 Dec 2014 18:05:10 +0100 Subject: [PATCH 61/98] Move phantomas_custom to the phantomas tool directory --- lib/runner.js | 2 +- .../tools/phantomas/custom_modules}/core/scopeYLT/scopeYLT.js | 0 .../custom_modules}/modules/domComplexYLT/domComplexYLT.js | 2 +- .../phantomas/custom_modules}/modules/domQYLT/domQYLT.js | 2 +- .../custom_modules}/modules/eventListYLT/eventListYLT.js | 0 .../tools/phantomas/custom_modules}/modules/jQYLT/jQYLT.js | 2 +- .../phantomas/custom_modules}/modules/jsErrYLT/jsErrYLT.js | 0 .../custom_modules}/modules/jsFileLoadYLT/jsFileLoadYLT.js | 0 .../phantomas/custom_modules}/modules/jsTreeYLT/jsTreeYLT.js | 0 .../phantomas/custom_modules}/modules/keepAlive/keepAlive.js | 0 .../custom_modules}/modules/windowPerfYLT/windowPerfYLT.js | 0 lib/tools/{ => phantomas}/phantomasWrapper.js | 4 ++-- test/core/phantomasWrapperTest.js | 2 +- 13 files changed, 7 insertions(+), 7 deletions(-) rename {phantomas_custom => lib/tools/phantomas/custom_modules}/core/scopeYLT/scopeYLT.js (100%) rename {phantomas_custom => lib/tools/phantomas/custom_modules}/modules/domComplexYLT/domComplexYLT.js (98%) rename {phantomas_custom => lib/tools/phantomas/custom_modules}/modules/domQYLT/domQYLT.js (99%) rename {phantomas_custom => lib/tools/phantomas/custom_modules}/modules/eventListYLT/eventListYLT.js (100%) rename {phantomas_custom => lib/tools/phantomas/custom_modules}/modules/jQYLT/jQYLT.js (99%) rename {phantomas_custom => lib/tools/phantomas/custom_modules}/modules/jsErrYLT/jsErrYLT.js (100%) rename {phantomas_custom => lib/tools/phantomas/custom_modules}/modules/jsFileLoadYLT/jsFileLoadYLT.js (100%) rename {phantomas_custom => lib/tools/phantomas/custom_modules}/modules/jsTreeYLT/jsTreeYLT.js (100%) rename {phantomas_custom => lib/tools/phantomas/custom_modules}/modules/keepAlive/keepAlive.js (100%) rename {phantomas_custom => lib/tools/phantomas/custom_modules}/modules/windowPerfYLT/windowPerfYLT.js (100%) rename lib/tools/{ => phantomas}/phantomasWrapper.js (95%) diff --git a/lib/runner.js b/lib/runner.js index 0cd4dcb..ab6ea6d 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -1,7 +1,7 @@ var Q = require('q'); var debug = require('debug')('ylt:runner'); -var phantomasWrapper = require('./tools/phantomasWrapper'); +var phantomasWrapper = require('./tools/phantomas/phantomasWrapper'); var jsExecutionTransformer = require('./tools/jsExecutionTransformer'); var rulesChecker = require('./rulesChecker'); var scoreCalculator = require('./scoreCalculator'); diff --git a/phantomas_custom/core/scopeYLT/scopeYLT.js b/lib/tools/phantomas/custom_modules/core/scopeYLT/scopeYLT.js similarity index 100% rename from phantomas_custom/core/scopeYLT/scopeYLT.js rename to lib/tools/phantomas/custom_modules/core/scopeYLT/scopeYLT.js diff --git a/phantomas_custom/modules/domComplexYLT/domComplexYLT.js b/lib/tools/phantomas/custom_modules/modules/domComplexYLT/domComplexYLT.js similarity index 98% rename from phantomas_custom/modules/domComplexYLT/domComplexYLT.js rename to lib/tools/phantomas/custom_modules/modules/domComplexYLT/domComplexYLT.js index a64f3c6..f03bf84 100644 --- a/phantomas_custom/modules/domComplexYLT/domComplexYLT.js +++ b/lib/tools/phantomas/custom_modules/modules/domComplexYLT/domComplexYLT.js @@ -28,7 +28,7 @@ exports.module = function(phantomas) { // duplicated ID (issue #392) phantomas.setMetric('DOMidDuplicated'); // @desc number of duplicated IDs found in DOM - var Collection = require('../../../node_modules/phantomas/lib/collection'), + var Collection = require('../../../../../../node_modules/phantomas/lib/collection'), DOMids = new Collection(); phantomas.on('domId', function(id) { diff --git a/phantomas_custom/modules/domQYLT/domQYLT.js b/lib/tools/phantomas/custom_modules/modules/domQYLT/domQYLT.js similarity index 99% rename from phantomas_custom/modules/domQYLT/domQYLT.js rename to lib/tools/phantomas/custom_modules/modules/domQYLT/domQYLT.js index 8f0e4c9..78260d2 100644 --- a/phantomas_custom/modules/domQYLT/domQYLT.js +++ b/lib/tools/phantomas/custom_modules/modules/domQYLT/domQYLT.js @@ -246,7 +246,7 @@ exports.module = function(phantomas) { // count DOM queries by either ID, tag name, class name and selector query // @see https://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-doctype - var Collection = require('../../../node_modules/phantomas/lib/collection'), + var Collection = require('../../../../../../node_modules/phantomas/lib/collection'), DOMqueries = new Collection(); phantomas.on('domQuery', function(type, query, fnName, context) { diff --git a/phantomas_custom/modules/eventListYLT/eventListYLT.js b/lib/tools/phantomas/custom_modules/modules/eventListYLT/eventListYLT.js similarity index 100% rename from phantomas_custom/modules/eventListYLT/eventListYLT.js rename to lib/tools/phantomas/custom_modules/modules/eventListYLT/eventListYLT.js diff --git a/phantomas_custom/modules/jQYLT/jQYLT.js b/lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js similarity index 99% rename from phantomas_custom/modules/jQYLT/jQYLT.js rename to lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js index 149fce0..b1014c3 100644 --- a/phantomas_custom/modules/jQYLT/jQYLT.js +++ b/lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js @@ -298,7 +298,7 @@ exports.module = function(phantomas) { // count Sizzle calls to detect duplicated queries - var Collection = require('../../../node_modules/phantomas/lib/collection'), + var Collection = require('../../../../../../node_modules/phantomas/lib/collection'), sizzleCalls = new Collection(), jQueryLoading = new Collection(); diff --git a/phantomas_custom/modules/jsErrYLT/jsErrYLT.js b/lib/tools/phantomas/custom_modules/modules/jsErrYLT/jsErrYLT.js similarity index 100% rename from phantomas_custom/modules/jsErrYLT/jsErrYLT.js rename to lib/tools/phantomas/custom_modules/modules/jsErrYLT/jsErrYLT.js diff --git a/phantomas_custom/modules/jsFileLoadYLT/jsFileLoadYLT.js b/lib/tools/phantomas/custom_modules/modules/jsFileLoadYLT/jsFileLoadYLT.js similarity index 100% rename from phantomas_custom/modules/jsFileLoadYLT/jsFileLoadYLT.js rename to lib/tools/phantomas/custom_modules/modules/jsFileLoadYLT/jsFileLoadYLT.js diff --git a/phantomas_custom/modules/jsTreeYLT/jsTreeYLT.js b/lib/tools/phantomas/custom_modules/modules/jsTreeYLT/jsTreeYLT.js similarity index 100% rename from phantomas_custom/modules/jsTreeYLT/jsTreeYLT.js rename to lib/tools/phantomas/custom_modules/modules/jsTreeYLT/jsTreeYLT.js diff --git a/phantomas_custom/modules/keepAlive/keepAlive.js b/lib/tools/phantomas/custom_modules/modules/keepAlive/keepAlive.js similarity index 100% rename from phantomas_custom/modules/keepAlive/keepAlive.js rename to lib/tools/phantomas/custom_modules/modules/keepAlive/keepAlive.js diff --git a/phantomas_custom/modules/windowPerfYLT/windowPerfYLT.js b/lib/tools/phantomas/custom_modules/modules/windowPerfYLT/windowPerfYLT.js similarity index 100% rename from phantomas_custom/modules/windowPerfYLT/windowPerfYLT.js rename to lib/tools/phantomas/custom_modules/modules/windowPerfYLT/windowPerfYLT.js diff --git a/lib/tools/phantomasWrapper.js b/lib/tools/phantomas/phantomasWrapper.js similarity index 95% rename from lib/tools/phantomasWrapper.js rename to lib/tools/phantomas/phantomasWrapper.js index 8dbd614..803fb00 100644 --- a/lib/tools/phantomasWrapper.js +++ b/lib/tools/phantomas/phantomasWrapper.js @@ -43,8 +43,8 @@ var PhantomasWrapper = function() { 'windowPerformance' // overriden ].join(','), 'include-dirs': [ - 'phantomas_custom/core', - 'phantomas_custom/modules' + 'lib/tools/phantomas/custom_modules/core', + 'lib/tools/phantomas/custom_modules/modules' ].join(',') }; diff --git a/test/core/phantomasWrapperTest.js b/test/core/phantomasWrapperTest.js index 904915e..62ddd08 100644 --- a/test/core/phantomasWrapperTest.js +++ b/test/core/phantomasWrapperTest.js @@ -1,5 +1,5 @@ var should = require('chai').should(); -var phantomasWrapper = require('../../lib/tools/phantomasWrapper'); +var phantomasWrapper = require('../../lib/tools/phantomas/phantomasWrapper'); describe('phantomasWrapper', function() { From fb323c2086d400dcba2b2cf05cb4e5b6828a49b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 29 Dec 2014 08:20:46 +0100 Subject: [PATCH 62/98] Import DOMqueriesWithoutResults fonctionnality --- .../custom_modules/core/scopeYLT/scopeYLT.js | 4 +- .../custom_modules/modules/domQYLT/domQYLT.js | 69 +++++++++++++++---- 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/lib/tools/phantomas/custom_modules/core/scopeYLT/scopeYLT.js b/lib/tools/phantomas/custom_modules/core/scopeYLT/scopeYLT.js index 9ed515a..c7478bb 100644 --- a/lib/tools/phantomas/custom_modules/core/scopeYLT/scopeYLT.js +++ b/lib/tools/phantomas/custom_modules/core/scopeYLT/scopeYLT.js @@ -7,7 +7,7 @@ */ /* global document: true, window: true */ -exports.version = '0.1'; +exports.version = '0.2'; exports.module = function(phantomas) { 'use strict'; @@ -62,7 +62,7 @@ exports.module = function(phantomas) { // After if (enabled && callbackAfter) { - callbackAfter.call(this, result); + callbackAfter.call(this, result, arguments); } } diff --git a/lib/tools/phantomas/custom_modules/modules/domQYLT/domQYLT.js b/lib/tools/phantomas/custom_modules/modules/domQYLT/domQYLT.js index 78260d2..81985f4 100644 --- a/lib/tools/phantomas/custom_modules/modules/domQYLT/domQYLT.js +++ b/lib/tools/phantomas/custom_modules/modules/domQYLT/domQYLT.js @@ -3,12 +3,13 @@ */ /* global Element: true, Document: true, Node: true, window: true */ -exports.version = '0.9.a'; +exports.version = '0.10.a'; exports.module = function(phantomas) { 'use strict'; phantomas.setMetric('DOMqueries'); // @desc number of all DOM queries @offenders + phantomas.setMetric('DOMqueriesWithoutResults'); // @desc number of DOM queries that retutned nothing @offenders phantomas.setMetric('DOMqueriesById'); // @desc number of document.getElementById calls phantomas.setMetric('DOMqueriesByClassName'); // @desc number of document.getElementsByClassName calls phantomas.setMetric('DOMqueriesByTagName'); // @desc number of document.getElementsByTagName calls @@ -21,14 +22,13 @@ exports.module = function(phantomas) { phantomas.once('init', function() { phantomas.evaluate(function() { (function(phantomas) { - function querySpy(type, query, fnName, context) { - phantomas.emit('domQuery', type, query, fnName, context); // @desc DOM query has been made + function querySpy(type, query, fnName, context, hasNoResults) { + phantomas.emit('domQuery', type, query, fnName, context, hasNoResults); // @desc DOM query has been made } phantomas.spy(Document.prototype, 'getElementById', function(id) { phantomas.incrMetric('DOMqueriesById'); phantomas.addOffender('DOMqueriesById', '#%s (in %s)', id, '#document'); - querySpy('id', '#' + id, 'getElementById', '#document'); phantomas.enterContext({ type: 'getElementById', @@ -41,9 +41,13 @@ exports.module = function(phantomas) { backtrace: phantomas.getBacktrace() }); - }, function(result) { + }, function(result, args) { + var id = args[0]; + + querySpy('id', '#' + id, 'getElementById', '#document', (result === null)); + var moreData = { - resultsNumber : result ? 1 : 0 + resultsNumber : (result === null) ? 0 : 1 }; phantomas.leaveContext(moreData); }); @@ -56,7 +60,6 @@ exports.module = function(phantomas) { phantomas.incrMetric('DOMqueriesByClassName'); phantomas.addOffender('DOMqueriesByClassName', '.%s (in %s)', className, context); - querySpy('class', '.' + className, 'getElementsByClassName', context); phantomas.enterContext({ type: 'getElementsByClassName', @@ -70,7 +73,14 @@ exports.module = function(phantomas) { }); } - function selectorClassNameAfter(result) { + function selectorClassNameAfter(result, args) { + /*jshint validthis: true */ + + var className = args[0]; + var context = phantomas.getDOMPath(this); + + querySpy('class', '.' + className, 'getElementsByClassName', context, (result.length === 0)); + var moreData = { resultsNumber : (result && result.length > 0) ? result.length : 0 }; @@ -88,7 +98,6 @@ exports.module = function(phantomas) { phantomas.incrMetric('DOMqueriesByTagName'); phantomas.addOffender('DOMqueriesByTagName', '%s (in %s)', tagName, context); - querySpy('tag name', tagName.toLowerCase(), 'getElementsByTagName', context); phantomas.enterContext({ type: 'getElementsByTagName', @@ -102,7 +111,14 @@ exports.module = function(phantomas) { }); } - function selectorTagNameSpyAfter(result) { + function selectorTagNameSpyAfter(result, args) { + /*jshint validthis: true */ + + var tagName = args[0]; + var context = phantomas.getDOMPath(this); + + querySpy('tag name', tagName.toLowerCase(), 'getElementsByTagName', context, (result.length === 0)); + var moreData = { resultsNumber : (result && result.length > 0) ? result.length : 0 }; @@ -112,11 +128,11 @@ exports.module = function(phantomas) { phantomas.spy(Document.prototype, 'getElementsByTagName', selectorTagNameSpyBefore, selectorTagNameSpyAfter); phantomas.spy(Element.prototype, 'getElementsByTagName', selectorTagNameSpyBefore, selectorTagNameSpyAfter); + // selector queries function selectorQuerySpy(selector, context) { phantomas.incrMetric('DOMqueriesByQuerySelectorAll'); phantomas.addOffender('DOMqueriesByQuerySelectorAll', '%s (in %s)', selector, context); - querySpy('selector', selector, 'querySelectorAll', context); } function selectorQuerySpyBefore(selector) { @@ -137,7 +153,14 @@ exports.module = function(phantomas) { }); } - function selectorQuerySpyAfter(result) { + function selectorQuerySpyAfter(result, args) { + /*jshint validthis: true */ + + var selector = args[0]; + var context = phantomas.getDOMPath(this); + + querySpy('selector', selector, 'querySelectorAll', context, (!result || result.length === 0)); + var moreData = { resultsNumber : result ? 1 : 0 }; @@ -162,7 +185,14 @@ exports.module = function(phantomas) { }); } - function selectorAllQuerySpryAfter(result) { + function selectorAllQuerySpryAfter(result, args) { + /*jshint validthis: true */ + + var selector = args[0]; + var context = phantomas.getDOMPath(this); + + querySpy('selector', selector, 'querySelectorAll', context, (!result || result.length === 0)); + var moreData = { resultsNumber : (result && result.length > 0) ? result.length : 0 }; @@ -244,6 +274,19 @@ exports.module = function(phantomas) { }); }); + // report DOM queries that return no results (issue #420) + phantomas.on('domQuery', function(type, query, fnName, context, hasNoResults) { + // ignore DOM queries within DOM fragments (used internally by jQuery) + if (context.indexOf('body') !== 0 && context.indexOf('#document') !== 0) { + return; + } + + if (hasNoResults === true) { + phantomas.incrMetric('DOMqueriesWithoutResults'); + phantomas.addOffender('DOMqueriesWithoutResults', '%s (in %s) using %s', query, context, fnName); + } + }); + // count DOM queries by either ID, tag name, class name and selector query // @see https://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-doctype var Collection = require('../../../../../../node_modules/phantomas/lib/collection'), From 6fe61a041eb94bad8dd073aa772012a28e2d33ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 29 Dec 2014 10:26:08 +0100 Subject: [PATCH 63/98] Add DOMqueriesWithoutResults as a rule --- lib/metadata/policies.js | 8 ++++++++ lib/metadata/scoreProfileGeneric.json | 1 + 2 files changed, 9 insertions(+) diff --git a/lib/metadata/policies.js b/lib/metadata/policies.js index b9cb910..c01566d 100644 --- a/lib/metadata/policies.js +++ b/lib/metadata/policies.js @@ -58,6 +58,14 @@ var policies = { "isAbnormalThreshold": 500, "takeOffendersFrom": "DOMqueriesDuplicated" }, + "DOMqueriesWithoutResults": { + "tool": "phantomas", + "label": "DOM queries without result", + "message": "

Number of queries that return no result.

It suggests the query is not used on the page, probably because it is some dead code.

Or maybe the code is trying to find an HTML block that is not always here. Look at the JS Timeline to see if the scripts correctly figures out the HTML block is not here and immediatly stops interacting further with the DOM.

", + "isOkThreshold": 0, + "isBadThreshold": 100, + "isAbnormalThreshold": 200 + }, "eventsBound": { "tool": "phantomas", "label": "Events bound", diff --git a/lib/metadata/scoreProfileGeneric.json b/lib/metadata/scoreProfileGeneric.json index 8feb3ff..d104e51 100644 --- a/lib/metadata/scoreProfileGeneric.json +++ b/lib/metadata/scoreProfileGeneric.json @@ -14,6 +14,7 @@ "policies": { "DOMinserts": 2, "DOMqueries": 1, + "DOMqueriesWithoutResults": 2, "DOMqueriesAvoidable": 2, "eventsBound": 1 } From 0b23fad9093ab33f31f3a03ea605a8dba5c7849f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 29 Dec 2014 10:35:18 +0100 Subject: [PATCH 64/98] Remove custom closedConnections module implemented in Phantomas --- .../modules/keepAlive/keepAlive.js | 37 ------------------- 1 file changed, 37 deletions(-) delete mode 100644 lib/tools/phantomas/custom_modules/modules/keepAlive/keepAlive.js diff --git a/lib/tools/phantomas/custom_modules/modules/keepAlive/keepAlive.js b/lib/tools/phantomas/custom_modules/modules/keepAlive/keepAlive.js deleted file mode 100644 index 4550e77..0000000 --- a/lib/tools/phantomas/custom_modules/modules/keepAlive/keepAlive.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Analyzes if HTTP responses keep the connections alive. - */ - -exports.version = '0.1'; - -exports.module = function(phantomas) { - 'use strict'; - - phantomas.setMetric('closedConnections'); // @desc requests not keeping the connection alive and slowing down the next request @offenders - - var closedConnectionHosts = {}; - - phantomas.on('recv', function(entry, res) { - var connectionHeader = (entry.headers.Connection || '').toLowerCase(); - // Taking the protocol in account, in case the same domain is called with two different protocols. - var host = entry.protocol + '://' + entry.domain; - - if (connectionHeader.indexOf('close') >= 0) { - // Don't blame it immediatly, wait to see if the connection is needed a second time. - closedConnectionHosts[host] = entry.url; - } - }); - - phantomas.on('send', function(entry, res) { - var host = entry.protocol + '://' + entry.domain; - var previousClosedConnection = closedConnectionHosts[host]; - - if (previousClosedConnection) { - // There was a closed connection. We can blame it safely now! - phantomas.incrMetric('closedConnections'); - phantomas.addOffender('closedConnections', previousClosedConnection); - - closedConnectionHosts[host] = null; - } - }); -}; From 519f2ba61739f7767cb0a0cf2b29f0485ffcdd5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 29 Dec 2014 12:17:52 +0100 Subject: [PATCH 65/98] Remove custom domComplexity module, maxDepth offenders added to phantomas --- .../modules/domComplexYLT/domComplexYLT.js | 135 ------------------ lib/tools/phantomas/phantomasWrapper.js | 1 - 2 files changed, 136 deletions(-) delete mode 100644 lib/tools/phantomas/custom_modules/modules/domComplexYLT/domComplexYLT.js diff --git a/lib/tools/phantomas/custom_modules/modules/domComplexYLT/domComplexYLT.js b/lib/tools/phantomas/custom_modules/modules/domComplexYLT/domComplexYLT.js deleted file mode 100644 index f03bf84..0000000 --- a/lib/tools/phantomas/custom_modules/modules/domComplexYLT/domComplexYLT.js +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Analyzes DOM complexity - */ -/* global document: true, Node: true, window: true */ - -exports.version = '1.0.a'; - -exports.module = function(phantomas) { - 'use strict'; - - // total length of HTML comments (including brackets) - phantomas.setMetric('commentsSize'); // @desc the size of HTML comments on the page @offenders - - // total length of text nodes with whitespaces only (i.e. pretty formatting of HTML) - phantomas.setMetric('whiteSpacesSize'); // @desc the size of text nodes with whitespaces only - - // count all tags - phantomas.setMetric('DOMelementsCount'); // @desc total number of HTML element nodes - phantomas.setMetric('DOMelementMaxDepth'); // @desc maximum level on nesting of HTML element node - - // nodes with inlines CSS (style attribute) - phantomas.setMetric('nodesWithInlineCSS'); // @desc number of nodes with inline CSS styling (with style attribute) @offenders - - // images - phantomas.setMetric('imagesScaledDown'); // @desc number of nodes that have images scaled down in HTML @offenders - phantomas.setMetric('imagesWithoutDimensions'); // @desc number of nodes without both width and height attribute @offenders - - // duplicated ID (issue #392) - phantomas.setMetric('DOMidDuplicated'); // @desc number of duplicated IDs found in DOM - - var Collection = require('../../../../../../node_modules/phantomas/lib/collection'), - DOMids = new Collection(); - - phantomas.on('domId', function(id) { - DOMids.push(id); - }); - - // HTML size - phantomas.on('report', function() { - phantomas.setMetricEvaluate('bodyHTMLSize', function() { // @desc the size of body tag content (document.body.innerHTML.length) - return document.body && document.body.innerHTML.length || 0; - }); - - phantomas.evaluate(function() { - (function(phantomas) { - var runner = new phantomas.nodeRunner(), - whitespacesRegExp = /^\s+$/, - DOMelementMaxDepth = 0, - DOMelementMaxDepthElts = [], - size = 0; - - runner.walk(document.body, function(node, depth) { - switch (node.nodeType) { - case Node.COMMENT_NODE: - size = node.textContent.length + 7; // ''.length - phantomas.incrMetric('commentsSize', size); - - // log HTML comments bigger than 64 characters - if (size > 64) { - phantomas.addOffender('commentsSize', phantomas.getDOMPath(node) + ' (' + size + ' characters)'); - } - break; - - case Node.ELEMENT_NODE: - phantomas.incrMetric('DOMelementsCount'); - - if (depth > DOMelementMaxDepth) { - DOMelementMaxDepth = depth; - DOMelementMaxDepthElts = [phantomas.getDOMPath(node)]; - } else if (depth === DOMelementMaxDepth) { - DOMelementMaxDepthElts.push(phantomas.getDOMPath(node)); - } - - // report duplicated ID (issue #392) - if (node.id) { - phantomas.emit('domId', node.id); - } - - // ignore inline \ No newline at end of file diff --git a/package.json b/package.json index 6b34d28..80f6fd9 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "grunt-express": "^1.4.1", "grunt-fontsmith": "^0.9.1", "grunt-mocha-test": "^0.12.4", + "grunt-replace": "^0.8.0", "matchdep": "^0.3.0", "mocha": "^2.1.0", "phantomjs": "^1.9.13", diff --git a/server_config/settings-prod.json b/server_config/settings-prod.json index 8d06c6a..6b383c4 100644 --- a/server_config/settings-prod.json +++ b/server_config/settings-prod.json @@ -1,12 +1,10 @@ { "serverPort": 80, - "googleAnalyticsId": "UA-54493828-1", + "googleAnalyticsId": "", - "authorized-keys": { + "authorizedKeys": { }, - "tokenSalt": "", - "authorizedApplications": [ - - ] + "maxAnonymousRunsPerDay": 50, + "maxAnonymousCallsPerDay": 2000 } \ No newline at end of file From 5c80f3197a6610d14d5c3ffd178ae6d6a7ad3952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Tue, 30 Dec 2014 09:20:05 +0100 Subject: [PATCH 74/98] Add version number in the footer --- Gruntfile.js | 4 ++++ front/src/css/main.css | 3 +++ front/src/less/main.less | 3 +++ front/src/main.html | 3 ++- 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index f2c3ae6..2ef5149 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -42,6 +42,10 @@ module.exports = function(grunt) { { match: 'googleAnalyticsId', replacement: '<%= settings.googleAnalyticsId %>' + }, + { + match: 'version', + replacement: 'v<%= pkg.version %>' } ] }, diff --git a/front/src/css/main.css b/front/src/css/main.css index 6022ca3..9ff35ad 100644 --- a/front/src/css/main.css +++ b/front/src/css/main.css @@ -126,6 +126,9 @@ h1 span { .footer a { color: inherit; } +.footer .version { + font-size: 0.7em; +} /* Icons */ .icon-lab { font-family: "fontsmith-icons"; diff --git a/front/src/less/main.less b/front/src/less/main.less index a2c6c0f..ae2cc21 100644 --- a/front/src/less/main.less +++ b/front/src/less/main.less @@ -126,6 +126,9 @@ h1 span { a { color: inherit; } + .version { + font-size: 0.7em; + } } /* Icons */ diff --git a/front/src/main.html b/front/src/main.html index 8522dcd..53ccfc5 100644 --- a/front/src/main.html +++ b/front/src/main.html @@ -33,7 +33,8 @@
- - - - - - - - - - - + + + + + + + + + + + + @@ -38,13 +42,7 @@
\ No newline at end of file diff --git a/front/src/views/dashboard.html b/front/src/views/dashboard.html index 0fba7bc..e95421d 100644 --- a/front/src/views/dashboard.html +++ b/front/src/views/dashboard.html @@ -1,4 +1,4 @@ -
+
@@ -16,11 +16,8 @@
{{category.label}}
-
+
diff --git a/front/src/views/queue.html b/front/src/views/queue.html index 219d29f..c34d3b4 100644 --- a/front/src/views/queue.html +++ b/front/src/views/queue.html @@ -8,9 +8,7 @@
- +

(auto-refresh activated)

diff --git a/front/src/views/rule.html b/front/src/views/rule.html index 36b6cfc..ee540ce 100644 --- a/front/src/views/rule.html +++ b/front/src/views/rule.html @@ -1,4 +1,4 @@ -
+
@@ -19,10 +19,7 @@

- +

diff --git a/front/src/views/timeline.html b/front/src/views/timeline.html index de7894f..71210ab 100644 --- a/front/src/views/timeline.html +++ b/front/src/views/timeline.html @@ -1,4 +1,4 @@ -
+

Javascript Timeline

This graph gives a quick view of when the Javascript interactions with the DOM occur during the loading of the page.

@@ -9,14 +9,14 @@
@@ -70,14 +70,12 @@
Timestamp
-
+
{{$index + 1}}
{{node.data.type}}
@@ -90,10 +88,7 @@
-
diff --git a/lib/server/controllers/frontController.js b/lib/server/controllers/frontController.js index ebcf139..9a19a67 100644 --- a/lib/server/controllers/frontController.js +++ b/lib/server/controllers/frontController.js @@ -1,19 +1,26 @@ -var path = require('path'); -var express = require('express'); +var path = require('path'); +var express = require('express'); var FrontController = function(app) { 'use strict'; + + var cacheDuration = 365 * 24 * 60 * 60 * 1000; // One year + var assetsPath = (app.get('env') === 'development') ? '../../../front/src' : '../../../front/build'; var routes = ['/', '/about', '/result/:runId', '/result/:runId/timeline', '/result/:runId/rule/:policy', '/queue/:runId']; - routes.forEach(function(route) { app.get(route, function(req, res) { - res.sendFile(path.join(__dirname, '../../../front/src/main.html')); + res.setHeader('Cache-Control', 'public, max-age=20'); + res.sendFile(path.join(__dirname, assetsPath, 'main.html')); }); }); - app.use('/front', express.static(path.join(__dirname, '../../../front/src'))); - app.use('/bower_components', express.static(path.join(__dirname, '../../../bower_components'))); + app.use('/css', express.static(path.join(__dirname, assetsPath, 'css'), { maxAge: cacheDuration })); + app.use('/fonts', express.static(path.join(__dirname, assetsPath, 'fonts'), { maxAge: cacheDuration })); + app.use('/img', express.static(path.join(__dirname, assetsPath, 'img'), { maxAge: cacheDuration })); + app.use('/js', express.static(path.join(__dirname, assetsPath, 'js'), { maxAge: cacheDuration })); + app.use('/views', express.static(path.join(__dirname, assetsPath, 'views'), { maxAge: cacheDuration })); + app.use('/bower_components', express.static(path.join(__dirname, '../../../bower_components'), { maxAge: cacheDuration })); }; module.exports = FrontController; \ No newline at end of file diff --git a/package.json b/package.json index 39a277d..fa04850 100644 --- a/package.json +++ b/package.json @@ -26,14 +26,22 @@ "grunt": "^0.4.5", "grunt-blanket": "^0.0.8", "grunt-contrib-clean": "^0.6.0", + "grunt-contrib-concat": "^0.5.0", "grunt-contrib-copy": "^0.7.0", + "grunt-contrib-cssmin": "^0.11.0", + "grunt-contrib-htmlmin": "^0.3.0", "grunt-contrib-jshint": "^0.10.0", "grunt-contrib-less": "^0.12.0", + "grunt-contrib-uglify": "^0.7.0", + "grunt-env": "^0.4.2", "grunt-express": "^1.4.1", + "grunt-filerev": "^2.1.2", "grunt-fontsmith": "^0.9.1", + "grunt-inline-angular-templates": "^0.1.5", "grunt-line-remover": "^0.0.2", "grunt-mocha-test": "^0.12.4", "grunt-replace": "^0.8.0", + "grunt-usemin": "^3.0.0", "matchdep": "^0.3.0", "mocha": "^2.1.0", "phantomjs": "^1.9.13", diff --git a/server_config/server_install.sh b/server_config/server_install.sh index cbdecae..f63294e 100644 --- a/server_config/server_install.sh +++ b/server_config/server_install.sh @@ -21,7 +21,7 @@ sudo chown $USER /space cd /space git clone https://github.com/gmetais/YellowLabTools.git --branch master cd YellowLabTools -npm install --production +npm install bower install --config.interactive=false --allow-root # Front-end compilation @@ -31,4 +31,4 @@ grunt build # Start the server rm server_config/settings.json cp server_config/settings-prod.json server_config/settings.json -forever start -c "node --stack-size=65500" bin/server.js \ No newline at end of file +NODE_ENV=production forever start -c "node --stack-size=65500" bin/server.js \ No newline at end of file diff --git a/server_config/server_update.sh b/server_config/server_update.sh index a294ffb..0cf1ee3 100755 --- a/server_config/server_update.sh +++ b/server_config/server_update.sh @@ -12,9 +12,14 @@ git stash pop # In case something was added in package.json or bower.json rm -rf node_modules -npm install --production +npm install rm -rf bower_components bower install --config.interactive=false --allow-root +# Front-end compilation +rm -rf front/build +npm install -g grunt +grunt build + # Restart the server -forever start -c "node --stack-size=65500" server.js \ No newline at end of file +NODE_ENV=production forever start -c "node --stack-size=65500" bin/server.js \ No newline at end of file From 7485f00f2fc9ea75d6e0da87e25faebfd152517c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Fri, 9 Jan 2015 15:14:49 +0100 Subject: [PATCH 97/98] Fix install & update scripts --- server_config/server_install.sh | 5 ++--- server_config/server_update.sh | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server_config/server_install.sh b/server_config/server_install.sh index f63294e..66a1716 100644 --- a/server_config/server_install.sh +++ b/server_config/server_install.sh @@ -12,8 +12,8 @@ sudo apt-get install -y nodejs source ~/.profile # Installation of some packages globally -npm install bower -g -npm install forever -g +npm install bower forever grunt-cli phantomjs -g +source ~/.profile # Installation of YellowLabTools sudo mkdir /space @@ -25,7 +25,6 @@ npm install bower install --config.interactive=false --allow-root # Front-end compilation -npm install grunt -g grunt build # Start the server diff --git a/server_config/server_update.sh b/server_config/server_update.sh index 0cf1ee3..58529e7 100755 --- a/server_config/server_update.sh +++ b/server_config/server_update.sh @@ -18,7 +18,8 @@ bower install --config.interactive=false --allow-root # Front-end compilation rm -rf front/build -npm install -g grunt +npm install grunt-cli -g +npm install phantomjs -g grunt build # Restart the server From 95daa4b02e4915e4b789810fa5d3ea18cccf9617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Fri, 9 Jan 2015 18:18:35 +0100 Subject: [PATCH 98/98] Fix tests --- Gruntfile.js | 1 + test/server/phantomasWrapperTest.js | 53 ----------------------------- 2 files changed, 1 insertion(+), 53 deletions(-) delete mode 100644 test/server/phantomasWrapperTest.js diff --git a/Gruntfile.js b/Gruntfile.js index 2584078..197fe33 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -288,6 +288,7 @@ module.exports = function(grunt) { ]); grunt.registerTask('build', [ + 'jshint', 'clean:build', 'copy:build', 'less', diff --git a/test/server/phantomasWrapperTest.js b/test/server/phantomasWrapperTest.js deleted file mode 100644 index 4710774..0000000 --- a/test/server/phantomasWrapperTest.js +++ /dev/null @@ -1,53 +0,0 @@ -var should = require('chai').should(); -var phantomasWrapper = require('../../app/lib/phantomasWrapper.js'); - -describe('phantomasWrapper', function() { - - it('should have a method execute', function() { - phantomasWrapper.should.have.property('execute').that.is.a('function'); - }); - - it('should execute', function(done) { - var url = 'http://example.com/'; - - this.timeout(20000); - phantomasWrapper.execute({ - url: url, - testId: '123abc', - options:{ - - } - }, function(err, json, results) { - should.not.exist(err); - - json.should.be.an('object'); - json.should.have.a.property('generator'); - json.generator.should.contain('phantomas'); - json.should.have.a.property('url').that.equals(url); - json.should.have.a.property('metrics').that.is.an('object').not.empty; - json.should.have.a.property('offenders').that.is.an('object').not.empty; - json.offenders.should.have.a.property('javascriptExecutionTree').that.is.a('array').not.empty; - - done(); - }); - }); - - it('should fail with error 254', function(done) { - var url = 'http://www.not.existing'; - - this.timeout(15000); - phantomasWrapper.execute({ - url: url, - testId: '123abc', - options:{ - - } - }, function(err, json, results) { - should.exist(err); - err.should.equal(254); - should.not.exist(json); - - done(); - }); - }); -}); \ No newline at end of file