├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── LovefieldService.js ├── README.md ├── angular.min.js ├── angular_scripts.js ├── bin └── test_travis.sh ├── edit_icon.svg ├── index.html ├── lf_scripts ├── lovefield.js └── lovefield.min.js ├── logcruncher.js ├── spinner ├── angular-spinner.min.js └── spin.min.js ├── warning.svg ├── workerApi.js └── wptview.css /.eslintignore: -------------------------------------------------------------------------------- 1 | *.min.js 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 6 4 | }, 5 | "root": true, 6 | "env": { 7 | "browser": true 8 | }, 9 | "extends": "eslint:recommended", 10 | "rules": { 11 | // Default rules we've disabled until the failures are fixed. 12 | // Remove a line to enable the rule. 13 | "comma-dangle": "off", 14 | "no-console": "off", 15 | "no-redeclare": "off", 16 | "no-undef": "off", 17 | "no-unused-vars": "off", 18 | "strict": "off", 19 | 20 | // Non-default rules we've chosen to enable. 21 | "accessor-pairs": "error", 22 | "comma-style": "error", 23 | "eol-last": "error", 24 | "eqeqeq": "error", 25 | "indent": ["error", 2, {"SwitchCase": 1}], 26 | "linebreak-style": "error", 27 | "new-cap": "error", 28 | "new-parens": "error", 29 | "no-array-constructor": "error", 30 | "no-bitwise": "error", 31 | "no-caller": "error", 32 | "no-div-regex": "error", 33 | "no-empty-pattern": "error", 34 | "no-eval": "error", 35 | "no-extend-native": "error", 36 | "no-extra-bind": "error", 37 | "no-floating-decimal": "error", 38 | "no-implied-eval": "error", 39 | "no-iterator": "error", 40 | "no-label-var": "error", 41 | "no-labels": "error", 42 | "no-lone-blocks": "error", 43 | "no-multi-str": "error", 44 | "no-native-reassign": "error", 45 | "no-new": "error", 46 | "no-new-func": "error", 47 | "no-new-object": "error", 48 | "no-new-wrappers": "error", 49 | "no-octal-escape": "error", 50 | "no-proto": "error", 51 | "no-return-assign": "error", 52 | "no-script-url": "error", 53 | "no-self-compare": "error", 54 | "no-sequences": "error", 55 | "no-shadow-restricted-names": "error", 56 | "no-spaced-func": "error", 57 | "no-trailing-spaces": "error", 58 | "no-undef-init": "error", 59 | "no-unexpected-multiline": "error", 60 | "no-unused-expressions": "error", 61 | "no-useless-call": "error", 62 | "no-void": "error", 63 | "no-with": "error", 64 | "keyword-spacing": "error", 65 | "semi": "error", 66 | "yoda": "error" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | 59 | # Temp files 60 | *~ 61 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | 4 | node_js: 5 | - "5.10.0" 6 | install: 7 | - npm install eslint csslint 8 | script: 9 | - bin/test_travis.sh 10 | - eslint *.js 11 | - csslint --format=compact *.css 12 | env: 13 | global: 14 | - GH_REF: github.com/mozilla/wptview.git 15 | - secure: "XJNGVyYe2/TfvPfT2LaLpenNhI2Dd2cVUYdpTwKhaB/FabdfPBl1gU1iheNBtct3nG33iv9H+6HCeqXEkxI+vZM1KNj2dJMwVkdRxIGypCKd5WOJDNceAeVGc3wF1KUD4pSfSaA7oGLd/zprifTrgs3DQ5Gwdjd1oD89YoG20fXBe9p9AcFgD7cdKG8RZ6BfzZxjKwUHaoEP18oQhAg4w2dbO0uqUAzqN4UprhPWZc4su4cd2nBPIBpL7PFy0Diqz4CE5vdf5cQsFn4Kgep4035YhR00V9uqg06ZcQHx9u/XWnzDqvtw5GYcKwhWeHsG/cA7dlcshQ6w5YxLbaq+JzeVky5bHAwW4gHUuJvypNqNExQR8laYvAG0zTNXR2UAeRRVe+j5NVsrbWhLYWvzO18GAi7stuuMtXwB7bNGTd25FsMv016lQAqOyVep/HwVh6dREh7ig4bxfRvWhFJO2odvgT2eru+PEwccPTuoaKzwIXw/98vQaERbTxdpALq8JPzk71YobO4wPl4/Q8jFbSlA7OAxhyv1I18wM5PDbVmOWyFtxgaAFkNavRzimSToj6cpQAtTtSwrzw19zuVCMrPlOuGHJLsXg2bhAIoGwBTIyUh/gD/q5/MqbAvPmoAvhuNKIbIjgUTczCeDcmjCMMegctEihn7iLxnGLTZZpVo=" 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to WPT Viewer 2 | 3 | Hello! 4 | It's glad to see you are interested in making a contribution here. Please follow the [README.md](https://github.com/jgraham/wptview/blob/master/README.md) file to get wptview up and running. 5 | We also urge you to look at 6 | 7 | We have our issue tracker on Github. Please feel free to pick any of the issues you find interesting :smile:. In the case you find a bug or have an idea to improve wptview, you may file it in the issues section. 8 | 9 | We mantain a list of easy issues [here](https://github.com/jgraham/wptview/labels/easy). These issues are great for newcomers. 10 | 11 | Once you have expressed your desire to solve an issue, you may open a Pull Request for us to review. Our reviews would be done using Reviewable. 12 | 13 | We are active on the irc.mozilla.org under **#ateam**. Hope to see you there! -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2016, jgraham 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of wptview nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /LovefieldService.js: -------------------------------------------------------------------------------- 1 | importScripts("workerApi.js"); 2 | importScripts("lf_scripts/lovefield.js"); 3 | 4 | onmessage = messageAdapter(new LovefieldService()); 5 | 6 | // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex/6969486#6969486 7 | function escapeRegExp(str) { 8 | return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 9 | } 10 | 11 | function LovefieldService() { 12 | // Following member variables are initialized within getDbConnection(). 13 | this.db_ = null; 14 | this.test_runs = null; 15 | this.tests = null; 16 | this.test_results = null; 17 | } 18 | 19 | /** 20 | * Initializes member variables that can't be initialized before getting a 21 | * connection to the database. 22 | * @private 23 | */ 24 | LovefieldService.prototype.onConnected_ = function() { 25 | this.test_runs = this.db_.getSchema().table('test_runs'); 26 | this.tests = this.db_.getSchema().table('tests'); 27 | this.test_results = this.db_.getSchema().table('test_results'); 28 | this.comments = this.db_.getSchema().table('comments'); 29 | }; 30 | 31 | 32 | /** 33 | * Instantiates the DB connection (re-entrant). 34 | * @return {!IThenable} 35 | */ 36 | LovefieldService.prototype.getDbConnection = function() { 37 | if (this.db_ !== null) { 38 | return new Promise((resolve) => resolve(this.db_)); 39 | } 40 | var connectOptions = {storeType: lf.schema.DataStoreType.INDEXED_DB}; 41 | return this.buildSchema_().connect(connectOptions).then((db) => { 42 | this.db_ = db; 43 | this.onConnected_(); 44 | return db; 45 | }); 46 | }; 47 | 48 | 49 | /** 50 | * Builds the database schema. 51 | * @return {!lf.schema.Builder} 52 | * @private 53 | */ 54 | LovefieldService.prototype.buildSchema_ = function() { 55 | var schemaBuilder = lf.schema.create('wptview', 2); 56 | schemaBuilder.createTable('test_runs'). 57 | addColumn('run_id', lf.Type.INTEGER). 58 | addColumn('name', lf.Type.STRING). 59 | addColumn('enabled', lf.Type.BOOLEAN). 60 | addColumn('url', lf.Type.STRING). 61 | addColumn('warnings', lf.Type.STRING). 62 | addNullable(['url']). 63 | addPrimaryKey(['run_id'],true); 64 | schemaBuilder.createTable('tests'). 65 | addColumn('id', lf.Type.INTEGER). 66 | addColumn('test', lf.Type.STRING). 67 | addColumn('parent_id',lf.Type.INTEGER). 68 | addColumn('title',lf.Type.STRING). 69 | addNullable(['parent_id','title']). 70 | addPrimaryKey(['id'], true); 71 | schemaBuilder.createTable('test_results'). 72 | addColumn('result_id', lf.Type.INTEGER). 73 | addColumn('expected', lf.Type.STRING). 74 | addColumn('status', lf.Type.STRING). 75 | addColumn('message', lf.Type.STRING). 76 | addColumn('test_id', lf.Type.INTEGER). 77 | addColumn('run_id', lf.Type.INTEGER). 78 | addPrimaryKey(['result_id'], true). 79 | addNullable(['message']). 80 | addUnique("unique_fk", ['run_id', 'test_id']). 81 | addForeignKey('fk_test_id', { 82 | local: 'test_id', 83 | ref: 'tests.id' 84 | }). 85 | addForeignKey('fk_run_id', { 86 | local: 'run_id', 87 | ref: 'test_runs.run_id' 88 | }); 89 | schemaBuilder.createTable('comments'). 90 | addColumn('comment_id', lf.Type.INTEGER). 91 | addColumn('comment', lf.Type.STRING). 92 | addColumn('result_id', lf.Type.INTEGER). 93 | addUnique("unique_fk", ['result_id']). 94 | addForeignKey('fk_result_id', { 95 | local: 'result_id', 96 | ref: 'test_results.result_id' 97 | }). 98 | addNullable(['comment']). 99 | addPrimaryKey(['comment_id'], true); 100 | 101 | return schemaBuilder; 102 | }; 103 | 104 | 105 | var testLogsRaw; 106 | 107 | LovefieldService.prototype.insertTestRuns = function(runType, runName, testRuns) { 108 | if (testRuns.length !== 0) { 109 | return new Promise(function(resolve, reject) { 110 | resolve(testRuns); 111 | }); 112 | } 113 | var testRunRows = []; 114 | var test_runs = this.test_runs; 115 | testRunRows.push(test_runs.createRow({ 116 | 'name': runName, 117 | 'enabled': true, 118 | 'url': runType.url, 119 | 'warnings': "" 120 | })); 121 | var q1 = this.db_. 122 | insert(). 123 | into(test_runs). 124 | values(testRunRows); 125 | return q1.exec(); 126 | }; 127 | 128 | LovefieldService.prototype.switchRuns = function(run_ids, enabled) { 129 | var test_runs = this.test_runs; 130 | var q1 = this.db_. 131 | update(test_runs). 132 | set(test_runs.enabled, enabled). 133 | where(test_runs.run_id.in(run_ids)); 134 | return q1.exec(); 135 | }; 136 | 137 | 138 | LovefieldService.prototype.editRunName = function(run_id, newRunName) { 139 | var test_runs = this.test_runs; 140 | var q1 = this.db_. 141 | update(test_runs). 142 | set(test_runs.name, newRunName). 143 | where(lf.op.and(test_runs.run_id.eq(run_id))); 144 | return q1.exec(); 145 | }; 146 | 147 | LovefieldService.prototype.insertTests = function(testLogsRaw, currentTests) { 148 | var testRows = []; 149 | var tests = this.tests; 150 | var currentTestMap = {}; 151 | var duplicates = []; 152 | // We create an associative array whose keys are tests that have been added 153 | // in previous insert queries. 154 | currentTests.forEach(function(currentTest) { 155 | currentTestMap[currentTest.test] = 1; 156 | }); 157 | // This is a list of the tests currently being added with keys being tests. 158 | // It differs from testRows as testRows is a special lovefield object. 159 | var testsBeingAdded = {}; 160 | testLogsRaw.forEach(function(testLog) { 161 | // First set of checks to ensure log has "action" set to "test_start" 162 | // and does not exist in table. (We don't want to add duplicates!) 163 | if (testLog.action === "test_start" && !(testLog.test in currentTestMap)) { 164 | // Checks whether this test is already present in the insert query array. 165 | if (testsBeingAdded.hasOwnProperty(testLog.test)) { 166 | // Notify UI as this is an anomaly. 167 | duplicates.push({test: testLog.test, subtest: null}); 168 | } else { 169 | // Add it to the set of keys 170 | testsBeingAdded[testLog.test] = 1; 171 | var row = tests.createRow({ 172 | 'test': testLog.test, 173 | }); 174 | testRows.push(row); 175 | } 176 | } 177 | }); 178 | var q1 = this.db_. 179 | insert(). 180 | into(tests). 181 | values(testRows); 182 | return q1.exec().then((rows) => [rows, duplicates]); 183 | }; 184 | 185 | LovefieldService.prototype.insertTestResults = function(testLogsRaw, tests, testRuns) { 186 | // Let's first create a test to id mapping 187 | var testRunId = testRuns[0].run_id; 188 | testIds = {}; 189 | tests.forEach(function(test) { 190 | testIds[test.test] = test.id; 191 | }); 192 | var testResultsRows = []; 193 | var test_results = this.test_results; 194 | // As in insertTests(), support added to ensure no duplicate entries are added 195 | // in same select query. 196 | var testResultsBeingAdded = {}; 197 | testLogsRaw.forEach(function(testLog) { 198 | if (testLog.action === "test_end") { 199 | // Duplicate found in same insert query array. 200 | if (!testResultsBeingAdded.hasOwnProperty(testLog.test)) { 201 | // Add it to set of keys 202 | testResultsBeingAdded[testLog.test] = 1; 203 | var resultId = testIds[testLog.test]; 204 | var row = test_results.createRow({ 205 | 'status': testLog.status, 206 | 'message': testLog.message, 207 | 'test_id': resultId, 208 | 'run_id': testRunId, 209 | 'expected': testLog.hasOwnProperty("expected") ? testLog.expected : testLog.status 210 | }); 211 | testResultsRows.push(row); 212 | } 213 | } 214 | }); 215 | var q1 = this.db_. 216 | insert(). 217 | into(test_results). 218 | values(testResultsRows); 219 | return q1.exec(); 220 | }; 221 | 222 | LovefieldService.prototype.insertSubtests = function(testLogsRaw, tests, currentSubtests) { 223 | testIds = {}; 224 | tests.forEach(function(test) { 225 | testIds[test.test] = test.id; 226 | }); 227 | var subtestRows = []; 228 | var tests = this.tests; 229 | var duplicates = []; 230 | // Creating a 2-D hash map to store existing subtests in the table inserted 231 | // via previous insert queries. The first dimension corresponds to test, 232 | // and the second dimension corresponds to subtest. 233 | var currentSubtestMap = {}; 234 | currentSubtests.forEach(function(currentSubtest) { 235 | if (!currentSubtestMap.hasOwnProperty(currentSubtest.test)) { 236 | currentSubtestMap[currentSubtest.test] = {}; 237 | } 238 | currentSubtestMap[currentSubtest.test][currentSubtest.title] = 1; 239 | }); 240 | 241 | // Similarly, creating a 2-D hash map for tests being added in this insert query. 242 | var subtestsBeingAdded = {}; 243 | testLogsRaw.forEach(function(testLog) { 244 | // Checking whether subtest hasn't been inserted previously to our test table. 245 | if (testLog.action === "test_status" && !(currentSubtestMap.hasOwnProperty(testLog.test) && currentSubtestMap[testLog.test].hasOwnProperty(testLog.subtest))) { 246 | // Checking whether this subtest has been added previously in the same insert query. 247 | if (subtestsBeingAdded.hasOwnProperty(testLog.test) && subtestsBeingAdded[testLog.test].hasOwnProperty(testLog.subtest)) { 248 | duplicates.push({test: testLog.test, subtest: testLog.subtest}); 249 | } else { 250 | // Adding test-subtest pair to hash map subtestsBeingAdded 251 | if (!subtestsBeingAdded.hasOwnProperty(testLog.test)) { 252 | subtestsBeingAdded[testLog.test] = {}; 253 | } 254 | subtestsBeingAdded[testLog.test][testLog.subtest] = 1; 255 | var row = tests.createRow({ 256 | 'test': testLog.test, 257 | 'parent_id': testIds[testLog.test], 258 | 'title': testLog.subtest 259 | }); 260 | subtestRows.push(row); 261 | } 262 | } 263 | }); 264 | var q1 = this.db_. 265 | insert(). 266 | into(tests). 267 | values(subtestRows); 268 | return q1.exec().then((rows) => [rows, duplicates]); 269 | }; 270 | 271 | LovefieldService.prototype.insertSubtestResults = function(testLogsRaw, subtests, testRuns) { 272 | subtestIds = {}; 273 | var testRunId = testRuns[0].run_id; 274 | subtests.forEach(function(subtest) { 275 | if (!(subtest.test in subtestIds)) 276 | subtestIds[subtest.test] = {}; 277 | subtestIds[subtest.test][subtest.title] = subtest.id; 278 | }); 279 | var subtestResultsRows = []; 280 | var test_results = this.test_results; 281 | var subtestResultsBeingAdded = {}; 282 | testLogsRaw.forEach(function(testLog) { 283 | if (testLog.action === "test_status") { 284 | if (!(subtestResultsBeingAdded.hasOwnProperty(testLog.test) && subtestResultsBeingAdded[testLog.test].hasOwnProperty(testLog.subtest))) { 285 | if (!subtestResultsBeingAdded.hasOwnProperty(testLog.test)) { 286 | subtestResultsBeingAdded[testLog.test] = {}; 287 | } 288 | subtestResultsBeingAdded[testLog.test][testLog.subtest] = 1; 289 | var resultId = subtestIds[testLog.test][testLog.subtest]; 290 | var row = test_results.createRow({ 291 | 'status': testLog.status, 292 | 'message': testLog.message, 293 | 'test_id': resultId, 294 | 'run_id': testRunId, 295 | 'expected': testLog.hasOwnProperty("expected") ? testLog.expected : testLog.status 296 | }); 297 | subtestResultsRows.push(row); 298 | } 299 | } 300 | }); 301 | var q1 = this.db_. 302 | insert(). 303 | into(test_results). 304 | values(subtestResultsRows); 305 | return q1.exec(); 306 | }; 307 | 308 | LovefieldService.prototype.updateWarnings = function(runName, duplicates) { 309 | var test_runs = this.test_runs; 310 | var q1 = this.db_. 311 | update(test_runs). 312 | set(test_runs.warnings, JSON.stringify(duplicates)). 313 | where(lf.op.and(test_runs.name.eq(runName))); 314 | return q1.exec(); 315 | }; 316 | 317 | LovefieldService.prototype.selectAllParentTests = function() { 318 | var tests = this.tests; 319 | return this.db_. 320 | select(). 321 | from(tests). 322 | where(tests.parent_id.eq(null)). 323 | exec(); 324 | }; 325 | 326 | LovefieldService.prototype.selectAllSubtests = function() { 327 | var tests = this.tests; 328 | return this.db_. 329 | select(). 330 | from(tests). 331 | where(tests.parent_id.neq(null)). 332 | exec(); 333 | }; 334 | 335 | LovefieldService.prototype.selectFilteredResults = function(filter, runs, minTestId, maxTestId, limit) { 336 | var lovefield = this; 337 | var tests = this.tests; 338 | var test_results = this.test_results; 339 | var test_runs = this.test_runs; 340 | 341 | var query = lovefield.db_. 342 | select(tests.id.as("test_id")). 343 | from(tests); 344 | 345 | var whereConditions = []; 346 | 347 | var joinRuns = {}; 348 | filter.statusFilter.forEach((x) => { 349 | if (x.run === "ALL") { 350 | runs.forEach((run) => { 351 | if (run.enabled) { 352 | joinRuns[run.name] = 1; 353 | } 354 | }); 355 | } else { 356 | joinRuns[x.run] = 1; 357 | } 358 | x.isRun = []; 359 | x.targets = []; 360 | x.status.forEach((status) => { 361 | if (status.startsWith("result_")) { 362 | x.isRun.push(true); 363 | var target = status.slice("result_".length); 364 | joinRuns[target] = 1; 365 | x.targets.push(target); 366 | } else { 367 | x.isRun.push(false); 368 | var target = status; 369 | x.targets.push(target); 370 | } 371 | }); 372 | }); 373 | 374 | // JOINs with results and runs table 375 | var aliases = {}; 376 | Object.keys(joinRuns).forEach((run) => { 377 | var resultAlias = this.test_results.as('results ' + run); 378 | query = query.leftOuterJoin(resultAlias, tests.id.eq(resultAlias.test_id)); 379 | var runAlias = this.test_runs.as('run ' + run); 380 | query = query.innerJoin(runAlias, resultAlias.run_id.eq(runAlias.run_id)); 381 | aliases[run] = { 382 | "runAlias": runAlias, 383 | "resultAlias": resultAlias 384 | }; 385 | whereConditions.push(runAlias.name.eq(run)); 386 | }); 387 | 388 | // WHERE clause 389 | filter.statusFilter.forEach((constraint, i) => { 390 | var runConditions = []; 391 | var op = constraint.equality === "is" ? "eq" : "neq"; 392 | var booleanOp = constraint.equality === "is" ? "or" : "and"; 393 | if (constraint.run === "ALL") { 394 | runs.forEach((run) => { 395 | if (run.enabled) { 396 | runConditions = constraint.targets.map((x) => aliases[run.name].resultAlias.status[op](x)); 397 | var condition = lf.op[booleanOp].apply(lf.op[booleanOp], runConditions); 398 | whereConditions.push(condition); 399 | } 400 | }); 401 | } else { 402 | constraint.targets.forEach((x, j) => { 403 | var target = constraint.isRun[j] ? aliases[x].resultAlias.status : x; 404 | runConditions.push(aliases[constraint.run].resultAlias.status[op](target)); 405 | }); 406 | var condition = lf.op[booleanOp].apply(lf.op[booleanOp], runConditions); 407 | whereConditions.push(condition); 408 | } 409 | }); 410 | 411 | // Account for the case where no status filters exist 412 | // This is slightly hacky and we need to find a proper solution here. 413 | if (filter.statusFilter.length === 0) { 414 | query = query.innerJoin(test_results, tests.id.eq(test_results.test_id)); 415 | query = query.innerJoin(test_runs, test_results.run_id.eq(test_runs.run_id)); 416 | var possibleRuns = []; 417 | runs.forEach((run) => { 418 | if (run.enabled) { 419 | possibleRuns.push(test_runs.name.eq(run.name)); 420 | } 421 | }); 422 | var condition = lf.op.or.apply(lf.op.or, possibleRuns); 423 | whereConditions.push(condition); 424 | } 425 | 426 | // working on path filter 427 | var pathOrConditions = { 428 | include: [], 429 | exclude: [] 430 | }; 431 | filter.pathFilter.forEach((pathFilter) => { 432 | pathFilter.path = pathFilter.path.replace("\\", "/"); 433 | var path_regex = escapeRegExp(pathFilter.path); 434 | var choice = pathFilter.choice.split(":"); 435 | if (choice[1] === "start") { 436 | if (pathFilter.path.charAt(0) !== "/") { 437 | path_regex = "\/?" + path_regex; 438 | } 439 | path_regex = "^" + path_regex; 440 | } else if (choice[1] === "end") { 441 | path_regex = path_regex + "$"; 442 | } 443 | path_regex = new RegExp(path_regex, 'i'); 444 | if (choice[0] === "include") { 445 | pathOrConditions.include.push(tests.test.match(path_regex)); 446 | } else { 447 | pathOrConditions.exclude.push(tests.test.match(path_regex)); 448 | } 449 | }); 450 | 451 | if (pathOrConditions.include.length) { 452 | whereConditions.push(lf.op.or.apply(lf.op.or, pathOrConditions.include)); 453 | } 454 | if (pathOrConditions.exclude.length) { 455 | whereConditions.push(lf.op.not(lf.op.or.apply(lf.op.or, pathOrConditions.exclude))); 456 | } 457 | 458 | // Working test type filter 459 | if (filter.testTypeFilter.type === "parent") { 460 | whereConditions.push(tests.parent_id.eq(null)); 461 | } else if (filter.testTypeFilter.type === "child") { 462 | whereConditions.push(tests.parent_id.neq(null)); 463 | } 464 | 465 | orderByDir = lf.Order.ASC; 466 | if (limit) { 467 | if (minTestId) { 468 | whereConditions.push(tests.id.gt(minTestId)); 469 | } else if (maxTestId) { 470 | whereConditions.push(tests.id.lt(maxTestId)); 471 | // The final results are always in ascending order because they come from a second query 472 | // with its own order 473 | orderByDir = lf.Order.DESC; 474 | } 475 | } 476 | 477 | if (whereConditions.length) { 478 | var whereClause = lf.op.and.apply(lf.op.and, whereConditions); 479 | query = query.where(whereClause); 480 | } 481 | query = query.orderBy(tests.id, orderByDir); 482 | if (limit) { 483 | query = query.limit(limit); 484 | } 485 | 486 | return query.exec() 487 | .then((test_ids) => { 488 | var test_list = test_ids.map((test) => test.test_id); 489 | 490 | // We need an additional query to select test results for ALL runs 491 | // for the tests filtered by q1. We need this unusual approach as 492 | // lovefield doesn't support subqueries. 493 | return lovefield.db_. 494 | select( 495 | tests.id.as("test_id"), 496 | tests.test.as("test"), 497 | test_results.message.as("message"), 498 | test_results.status.as("status"), 499 | test_results.expected.as("expected"), 500 | test_results.result_id.as("result_id"), 501 | tests.title.as("title"), 502 | test_runs.run_id.as("run_id"), 503 | test_runs.name.as("run_name") 504 | ) 505 | .from(tests) 506 | .innerJoin(test_results, tests.id.eq(test_results.test_id)) 507 | .innerJoin(test_runs, test_results.run_id.eq(test_runs.run_id)) 508 | .where(lf.op.and(tests.id.in(test_list), test_runs.enabled.eq(true))) 509 | .orderBy(tests.id) 510 | .orderBy(test_runs.run_id) 511 | .exec(); 512 | }); 513 | }; 514 | 515 | LovefieldService.prototype.deleteEntries = function(run_id) { 516 | return this.getDbConnection().then((db) => { 517 | var comments = this.comments; 518 | var test_results = this.test_results; 519 | var deleteCommentsQuery, rv; 520 | if (run_id) { 521 | deleteCommentsQuery = db.select(comments.result_id.as("result_id")) 522 | .from(comments) 523 | .innerJoin(test_results, test_results.result_id.eq(comments.result_id)) 524 | .where(test_results.run_id.eq(run_id)) 525 | .exec(); 526 | } else { 527 | deleteCommentsQuery = db.select(test_results.result_id.as("result_id")) 528 | .from(test_results) 529 | .exec(); 530 | } 531 | rv = deleteCommentsQuery 532 | .then((ids) => { 533 | result_ids = ids.map((id) => id.result_id); 534 | return db.delete() 535 | .from(comments) 536 | .where(comments.result_id.in(result_ids)) 537 | .exec(); 538 | }); 539 | return rv; 540 | }) 541 | .then(() => { 542 | var db = this.db_; 543 | var test_results = this.test_results; 544 | var tests = this.tests; 545 | var test_runs = this.test_runs; 546 | var q1 = db.delete().from(test_results); 547 | var q2 = db.delete().from(test_runs); 548 | if (run_id) { 549 | q1 = q1.where(test_results.run_id.eq(run_id)); 550 | q2 = q2.where(test_runs.run_id.eq(run_id)); 551 | } 552 | var queries = [q1, q2]; 553 | if (!run_id) { 554 | queries.push(db.delete().from(tests)); 555 | } 556 | var tx = db.createTransaction(); 557 | var rv = tx.exec(queries); 558 | if (run_id) { 559 | var keepIds = {}; 560 | rv = rv 561 | .then(() => { 562 | // Can't do a leftOuterJoin in a delete 563 | // and doing 564 | // SELECT id FROM tests 565 | // LEFT OUTER JOIN test_results ON tests.id = test_results.test_id 566 | // WHERE test_results.test_id = null 567 | // returned all rows in tests, not just those with no matching row in test_results. 568 | // So we select all the tests with a matching result in one query, all the tests 569 | // in another query and take the difference in application code. This is silly. 570 | return db.select(tests.id) 571 | .from(tests) 572 | .innerJoin(test_results, tests.id.eq(test_results.test_id)) 573 | .exec(); 574 | }) 575 | .then((ids) => { 576 | ids.forEach((id) => {keepIds[id] = true;}); 577 | return db.select(tests.id) 578 | .from(tests) 579 | .exec(); 580 | }) 581 | .then((allIds) => { 582 | var removeIds = allIds.filter((x) => !keepIds.hasOwnProperty(x)); 583 | return db.delete() 584 | .from(tests) 585 | .where(tests.id.in(removeIds)) 586 | .exec(); 587 | }); 588 | } 589 | return rv; 590 | }); 591 | }; 592 | 593 | LovefieldService.prototype.selectParticularRun = function(runName) { 594 | var test_runs = this.test_runs; 595 | return this.getDbConnection() 596 | .then(db => { 597 | return db 598 | .select() 599 | .from(test_runs) 600 | .where(test_runs.name.eq(runName)) 601 | .exec(); 602 | }); 603 | }; 604 | 605 | LovefieldService.prototype.getRunURLs = function() { 606 | var test_runs = this.test_runs; 607 | return this.db_. 608 | select(test_runs.url). 609 | from(test_runs). 610 | where(test_runs.url.neq(null)). 611 | exec(); 612 | }; 613 | 614 | LovefieldService.prototype.getRuns = function() { 615 | var db = null; 616 | var service = this; 617 | var test_runs = null; 618 | var test_results = null; 619 | var counts = {}; 620 | return this.getDbConnection() 621 | .then((db_conn) => { 622 | db = db_conn; 623 | test_runs = this.test_runs; 624 | test_results = this.test_results; 625 | return db.select(test_runs.run_id, lf.fn.count(test_results.result_id).as('count')) 626 | .from(test_runs) 627 | .innerJoin(test_results, test_results.run_id.eq(test_runs.run_id)) 628 | .groupBy(test_runs.run_id) 629 | .exec(); 630 | }) 631 | .then((count_data) => { 632 | // Lovefield doesn't allow us to grab all the data from test_runs and the counts 633 | // in a single query 634 | count_data.forEach((x) => {counts[x.test_runs.run_id] = x.count;}); 635 | return db.select().from(test_runs).exec(); 636 | }) 637 | .then((data) => { 638 | data.forEach((x) => {x.count = counts[x.run_id];}); 639 | return data; 640 | }); 641 | }; 642 | 643 | LovefieldService.prototype.selectComment = function(result_id) { 644 | var comments = this.comments; 645 | return this.db_.select(comments.comment). 646 | from(comments). 647 | where(comments.result_id.eq(result_id)). 648 | exec(); 649 | }; 650 | 651 | LovefieldService.prototype.insertComment = function(result_id, comment) { 652 | var comments = this.comments; 653 | return this.db_.insert(). 654 | into(comments). 655 | values([comments.createRow({'result_id':result_id, 'comment': comment})]). 656 | exec(); 657 | }; 658 | 659 | LovefieldService.prototype.updateComment = function(result_id, comment) { 660 | var comments = this.comments; 661 | return this.db_.update(comments). 662 | set(comments.comment, comment). 663 | where(comments.result_id.eq(result_id)). 664 | exec(); 665 | }; 666 | 667 | LovefieldService.prototype.deleteComment = function(result_id) { 668 | var comments = this.comments; 669 | return this.db_.delete(). 670 | from(comments). 671 | where(comments.result_id.eq(result_id)). 672 | exec(); 673 | }; 674 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wptview 2 | [![Build Status](https://travis-ci.org/mozilla/wptview.png?branch=master)](https://travis-ci.org/mozilla/wptview) 3 | 4 | 5 | Webapp for displaying the results of web-platform-tests. 6 | 7 | ## Running wptview 8 | This app runs on a local server. You may set up a local server by using Python's SimpleHTTPServer. 9 | 10 | `python -m SimpleHTTPServer 8000` 11 | 12 | Go to a browser and visit `localhost:8000`. WPT Viewer should start. 13 | 14 | WPT Viewer needs to ingest some web-platform-tests mozlog files to get up and running. A good place to find mozlog files is Mozilla's [Treeherder](https://treeherder.mozilla.org/#/jobs?repo=mozilla-inbound) for mozilla-inbound. 15 | These files are generally found under "Job Details" after clicking a job having structured logs (such as WPT) as an artifact. The typical name of these files are `testsuite_raw.log`, such as `wpt_raw.log` for the Web Platform Tests. 16 | We have added an option in Treeherder present adjacent to these files. This option ingests the log in wptview along with an appropriate run name fetched from Treeherder. 17 | You can try to press **open in tests results viewer** [here](https://treeherder.mozilla.org/#/jobs?repo=mozilla-inbound&revision=4391e6b0e891&selectedJob=21919829). 18 | 19 | A good log file to testing purposes may be found [here](http://mozilla-releng-blobs.s3.amazonaws.com/blobs/mozilla-inbound/sha512/05a68f7a1acdd9f9c800e587f576083678588c9271d9221878f3cf959063473bc6783ca09c75eacd39990ac037b0b9ec3c806a8e910c74388609fff1be8fe570). 20 | If you wish to test comparisons, you may use the log files for a recent [w-2](http://mozilla-releng-blobs.s3.amazonaws.com/blobs/mozilla-inbound/sha512/27983329167951b69f6a451846d0c7e422bb0e589c407141737b40ef21f7a8aaf9347bc57928450fff6e64cc86c72970bb0325dbab258b43284a8008cf3e1885) with the [w-e10s-2](http://mozilla-releng-blobs.s3.amazonaws.com/blobs/mozilla-inbound/sha512/d4372303775a5dfeb2d1d7c44d89a2b1dcb78d9ee2e7859fd6469b9a7518594a905a313f0bc947196bebc9a95f153804cd5344471126fd833a56d5498aa9cc5b) run. 21 | 22 | ## Contributing 23 | You may refer to the [CONTRIBUTING.md](https://github.com/jgraham/wptview/blob/dcc68d8ffffafc7eca8b080becd825fab5a5d61d/CONTRIBUTING.md) file for further details. 24 | 25 | ## Contact Us 26 | We are active on the irc.mozilla.org under **#ateam**. Hope to see you there! -------------------------------------------------------------------------------- /angular.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.3.14 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(P,X,u){'use strict';function M(b){return function(){var a=arguments[0],c;c="["+(b?b+":":"")+a+"] http://errors.angularjs.org/1.3.14/"+(b?b+"/":"")+a;for(a=1;a").append(b).html();try{return b[0].nodeType===qb?R(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+R(b)})}catch(d){return R(c)}}function qc(b){try{return decodeURIComponent(b)}catch(a){}}function rc(b){var a= 15 | {},c,d;s((b||"").split("&"),function(b){b&&(c=b.replace(/\+/g,"%20").split("="),d=qc(c[0]),y(d)&&(b=y(c[1])?qc(c[1]):!0,sc.call(a,d)?E(a[d])?a[d].push(b):a[d]=[a[d],b]:a[d]=b))});return a}function Qb(b){var a=[];s(b,function(b,d){E(b)?s(b,function(b){a.push(Fa(d,!0)+(!0===b?"":"="+Fa(b,!0)))}):a.push(Fa(d,!0)+(!0===b?"":"="+Fa(b,!0)))});return a.length?a.join("&"):""}function rb(b){return Fa(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function Fa(b,a){return encodeURIComponent(b).replace(/%40/gi, 16 | "@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,a?"%20":"+")}function Id(b,a){var c,d,e=sb.length;b=C(b);for(d=0;d/,">"));}a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);c.debugInfoEnabled&&a.push(["$compileProvider",function(a){a.debugInfoEnabled(!0)}]);a.unshift("ng");d=ab(a,c.strictDi);d.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector", 18 | d);c(b)(a)})}]);return d},e=/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;P&&e.test(P.name)&&(c.debugInfoEnabled=!0,P.name=P.name.replace(e,""));if(P&&!f.test(P.name))return d();P.name=P.name.replace(f,"");aa.resumeBootstrap=function(b){s(b,function(b){a.push(b)});return d()};G(aa.resumeDeferredBootstrap)&&aa.resumeDeferredBootstrap()}function Kd(){P.name="NG_ENABLE_DEBUG_INFO!"+P.name;P.location.reload()}function Ld(b){b=aa.element(b).injector();if(!b)throw Ka("test");return b.get("$$testability")} 19 | function uc(b,a){a=a||"_";return b.replace(Md,function(b,d){return(d?a:"")+b.toLowerCase()})}function Nd(){var b;vc||((ra=P.jQuery)&&ra.fn.on?(C=ra,w(ra.fn,{scope:La.scope,isolateScope:La.isolateScope,controller:La.controller,injector:La.injector,inheritedData:La.inheritedData}),b=ra.cleanData,ra.cleanData=function(a){var c;if(Rb)Rb=!1;else for(var d=0,e;null!=(e=a[d]);d++)(c=ra._data(e,"events"))&&c.$destroy&&ra(e).triggerHandler("$destroy");b(a)}):C=Q,aa.element=C,vc=!0)}function Sb(b,a,c){if(!b)throw Ka("areq", 20 | a||"?",c||"required");return b}function tb(b,a,c){c&&E(b)&&(b=b[b.length-1]);Sb(G(b),a,"not a function, got "+(b&&"object"===typeof b?b.constructor.name||"Object":typeof b));return b}function Ma(b,a){if("hasOwnProperty"===b)throw Ka("badname",a);}function wc(b,a,c){if(!a)return b;a=a.split(".");for(var d,e=b,f=a.length,g=0;g")+d[2];for(d=d[0];d--;)c=c.lastChild;f=Ya(f,c.childNodes);c=e.firstChild;c.textContent=""}else f.push(a.createTextNode(b));e.textContent="";e.innerHTML="";s(f,function(a){e.appendChild(a)}); 27 | return e}function Q(b){if(b instanceof Q)return b;var a;x(b)&&(b=T(b),a=!0);if(!(this instanceof Q)){if(a&&"<"!=b.charAt(0))throw Ub("nosel");return new Q(b)}if(a){a=X;var c;b=(c=gf.exec(b))?[a.createElement(c[1])]:(c=Gc(b,a))?c.childNodes:[]}Hc(this,b)}function Vb(b){return b.cloneNode(!0)}function xb(b,a){a||yb(b);if(b.querySelectorAll)for(var c=b.querySelectorAll("*"),d=0,e=c.length;d 4096 bytes)!"));else{if(p.cookie!==y)for(y=p.cookie,d=y.split("; "),wa={},f=0;fk&&this.remove(q.key),b},get:function(a){if(k").parent()[0])});var f=ba(a,b,a,c,d,e);F.$$addScopeClass(a);var g=null;return function(b, 50 | c,d){Sb(b,"scope");d=d||{};var e=d.parentBoundTranscludeFn,h=d.transcludeControllers;d=d.futureParentElement;e&&e.$$boundTransclude&&(e=e.$$boundTransclude);g||(g=(d=d&&d[0])?"foreignobject"!==ta(d)&&d.toString().match(/SVG/)?"svg":"html":"html");d="html"!==g?C(Xb(g,C("
").append(a).html())):c?La.clone.call(a):a;if(h)for(var l in h)d.data("$"+l+"Controller",h[l].instance);F.$$addScopeInfo(d,b);c&&c(d,b);f&&f(b,d,d,e);return d}}function ba(a,b,c,d,e,f){function g(a,c,d,e){var f,l,k,q,p,r,D;if(n)for(D= 51 | Array(c.length),q=0;qL.priority)break;if(U=L.scope)L.templateUrl||(J(U)?(Oa("new/isolated scope",I||N,L,w),I=L):Oa("new/isolated scope",I,L,w)),N=N||L;ca=L.name;!L.templateUrl&&L.controller&&(U=L.controller,H=H||{},Oa("'"+ca+"' controller",H[ca],L,w),H[ca]=L);if(U=L.transclude)ha=!0,L.$$tlb||(Oa("transclusion",wa,L,w),wa=L),"element"==U?(B=!0,v=L.priority,U=w,w=e.$$element=C(X.createComment(" "+ca+": "+e[ca]+" ")),d=w[0],V(g,Za.call(U, 61 | 0),d),R=F(U,f,v,l&&l.name,{nonTlbTranscludeDirective:wa})):(U=C(Vb(d)).contents(),w.empty(),R=F(U,f));if(L.template)if(fb=!0,Oa("template",ja,L,w),ja=L,U=G(L.template)?L.template(w,e):L.template,U=Tc(U),L.replace){l=L;U=Tb.test(U)?Uc(Xb(L.templateNamespace,T(U))):[];d=U[0];if(1!=U.length||d.nodeType!==na)throw ia("tplrt",ca,"");V(g,w,d);Aa={$attr:{}};U=W(d,[],Aa);var of=a.splice(Q+1,a.length-(Q+1));I&&z(U);a=a.concat(U).concat(of);Rc(e,Aa);Aa=a.length}else w.html(U);if(L.templateUrl)fb=!0,Oa("template", 62 | ja,L,w),ja=L,L.replace&&(l=L),A=M(a.splice(Q,a.length-Q),w,e,g,ha&&R,k,p,{controllerDirectives:H,newIsolateScopeDirective:I,templateDirective:ja,nonTlbTranscludeDirective:wa}),Aa=a.length;else if(L.compile)try{P=L.compile(w,e,R),G(P)?r(null,P,Pa,$):P&&r(P.pre,P.post,Pa,$)}catch(aa){c(aa,ua(w))}L.terminal&&(A.terminal=!0,v=Math.max(v,L.priority))}A.scope=N&&!0===N.scope;A.transcludeOnThisElement=ha;A.elementTranscludeOnThisElement=B;A.templateOnThisElement=fb;A.transclude=R;n.hasElementTranscludeDirective= 63 | B;return A}function z(a){for(var b=0,c=a.length;bq.priority)&&-1!=q.restrict.indexOf(f)&&(l&&(q=Pb(q,{$$start:l,$$end:k})),b.push(q),h=q)}catch(D){c(D)}}return h}function fb(b){if(d.hasOwnProperty(b))for(var c=a.get(b+"Directive"),e=0,f=c.length;e"+b+"";return c.childNodes[0].childNodes;default:return b}}function Q(a,b){if("srcdoc"== 68 | b)return H.HTML;var c=ta(a);if("xlinkHref"==b||"form"==c&&"action"==b||"img"!=c&&("src"==b||"ngSrc"==b))return H.RESOURCE_URL}function Aa(a,c,d,e,f){var h=Q(a,e);f=g[e]||f;var k=b(d,!0,h,f);if(k){if("multiple"===e&&"select"===ta(a))throw ia("selmulti",ua(a));c.push({priority:100,compile:function(){return{pre:function(a,c,g){c=g.$$observers||(g.$$observers={});if(l.test(e))throw ia("nodomevents");var n=g[e];n!==d&&(k=n&&b(n,!0,h,f),d=n);k&&(g[e]=k(a),(c[e]||(c[e]=[])).$$inter=!0,(g.$$observers&&g.$$observers[e].$$scope|| 69 | a).$watch(k,function(a,b){"class"===e&&a!=b?g.$updateClass(a,b):g.$set(e,a)}))}}}})}}function V(a,b,c){var d=b[0],e=b.length,f=d.parentNode,g,h;if(a)for(g=0,h=a.length;g=a)return b;for(;a--;)8===b[a].nodeType&&pf.call(b,a,1);return b}function Fe(){var b={},a=!1,c=/^(\S+)(\s+as\s+(\w+))?$/;this.register=function(a,c){Ma(a,"controller");J(a)?w(b,a):b[a]=c};this.allowGlobals=function(){a= 75 | !0};this.$get=["$injector","$window",function(d,e){function f(a,b,c,d){if(!a||!J(a.$scope))throw M("$controller")("noscp",d,b);a.$scope[b]=c}return function(g,h,l,k){var m,p,q;l=!0===l;k&&x(k)&&(q=k);if(x(g)){k=g.match(c);if(!k)throw qf("ctrlfmt",g);p=k[1];q=q||k[3];g=b.hasOwnProperty(p)?b[p]:wc(h.$scope,p,!0)||(a?wc(e,p,!0):u);tb(g,p,!0)}if(l)return l=(E(g)?g[g.length-1]:g).prototype,m=Object.create(l||null),q&&f(h,q,m,p||g.name),w(function(){d.invoke(g,m,h,p);return m},{instance:m,identifier:q}); 76 | m=d.instantiate(g,h,p);q&&f(h,q,m,p||g.name);return m}}]}function Ge(){this.$get=["$window",function(b){return C(b.document)}]}function He(){this.$get=["$log",function(b){return function(a,c){b.error.apply(b,arguments)}}]}function Zb(b,a){if(x(b)){var c=b.replace(rf,"").trim();if(c){var d=a("Content-Type");(d=d&&0===d.indexOf(Wc))||(d=(d=c.match(sf))&&tf[d[0]].test(c));d&&(b=pc(c))}}return b}function Xc(b){var a=fa(),c,d,e;if(!b)return a;s(b.split("\n"),function(b){e=b.indexOf(":");c=R(T(b.substr(0, 77 | e)));d=T(b.substr(e+1));c&&(a[c]=a[c]?a[c]+", "+d:d)});return a}function Yc(b){var a=J(b)?b:u;return function(c){a||(a=Xc(b));return c?(c=a[R(c)],void 0===c&&(c=null),c):a}}function Zc(b,a,c,d){if(G(d))return d(b,a,c);s(d,function(d){b=d(b,a,c)});return b}function Ke(){var b=this.defaults={transformResponse:[Zb],transformRequest:[function(a){return J(a)&&"[object File]"!==Da.call(a)&&"[object Blob]"!==Da.call(a)&&"[object FormData]"!==Da.call(a)?$a(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"}, 78 | post:qa($b),put:qa($b),patch:qa($b)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN"},a=!1;this.useApplyAsync=function(b){return y(b)?(a=!!b,this):a};var c=this.interceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(d,e,f,g,h,l){function k(a){function c(a){var b=w({},a);b.data=a.data?Zc(a.data,a.headers,a.status,e.transformResponse):a.data;a=a.status;return 200<=a&&300>a?b:h.reject(b)}function d(a){var b,c={};s(a,function(a,d){G(a)?(b= 79 | a(),null!=b&&(c[d]=b)):c[d]=a});return c}if(!aa.isObject(a))throw M("$http")("badreq",a);var e=w({method:"get",transformRequest:b.transformRequest,transformResponse:b.transformResponse},a);e.headers=function(a){var c=b.headers,e=w({},a.headers),f,g,c=w({},c.common,c[R(a.method)]);a:for(f in c){a=R(f);for(g in e)if(R(g)===a)continue a;e[f]=c[f]}return d(e)}(a);e.method=vb(e.method);var f=[function(a){var d=a.headers,e=Zc(a.data,Yc(d),u,a.transformRequest);z(e)&&s(d,function(a,b){"content-type"===R(b)&& 80 | delete d[b]});z(a.withCredentials)&&!z(b.withCredentials)&&(a.withCredentials=b.withCredentials);return m(a,e).then(c,c)},u],g=h.when(e);for(s(t,function(a){(a.request||a.requestError)&&f.unshift(a.request,a.requestError);(a.response||a.responseError)&&f.push(a.response,a.responseError)});f.length;){a=f.shift();var l=f.shift(),g=g.then(a,l)}g.success=function(a){g.then(function(b){a(b.data,b.status,b.headers,e)});return g};g.error=function(a){g.then(null,function(b){a(b.data,b.status,b.headers,e)}); 81 | return g};return g}function m(c,f){function l(b,c,d,e){function f(){n(c,b,d,e)}N&&(200<=b&&300>b?N.put(I,[b,c,Xc(d),e]):N.remove(I));a?g.$applyAsync(f):(f(),g.$$phase||g.$apply())}function n(a,b,d,e){b=Math.max(b,0);(200<=b&&300>b?v.resolve:v.reject)({data:a,status:b,headers:Yc(d),config:c,statusText:e})}function m(a){n(a.data,a.status,qa(a.headers()),a.statusText)}function t(){var a=k.pendingRequests.indexOf(c);-1!==a&&k.pendingRequests.splice(a,1)}var v=h.defer(),A=v.promise,N,F,s=c.headers,I=p(c.url, 82 | c.params);k.pendingRequests.push(c);A.then(t,t);!c.cache&&!b.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(N=J(c.cache)?c.cache:J(b.cache)?b.cache:q);N&&(F=N.get(I),y(F)?F&&G(F.then)?F.then(m,m):E(F)?n(F[1],F[0],qa(F[2]),F[3]):n(F,200,{},"OK"):N.put(I,A));z(F)&&((F=$c(c.url)?e.cookies()[c.xsrfCookieName||b.xsrfCookieName]:u)&&(s[c.xsrfHeaderName||b.xsrfHeaderName]=F),d(c.method,I,f,l,s,c.timeout,c.withCredentials,c.responseType));return A}function p(a,b){if(!b)return a;var c=[];Ed(b, 83 | function(a,b){null===a||z(a)||(E(a)||(a=[a]),s(a,function(a){J(a)&&(a=pa(a)?a.toISOString():$a(a));c.push(Fa(b)+"="+Fa(a))}))});0=l&&(r.resolve(q),p(S.$$intervalId),delete f[S.$$intervalId]);t||b.$apply()},h);f[S.$$intervalId]=r;return S}var f={};e.cancel=function(b){return b&&b.$$intervalId in f?(f[b.$$intervalId].reject("canceled"),a.clearInterval(b.$$intervalId),delete f[b.$$intervalId],!0):!1};return e}]} 91 | function Rd(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January February March April May June July August September October November December".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "), 92 | DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return 1===b?"one":"other"}}}}function bc(b){b=b.split("/");for(var a=b.length;a--;)b[a]=rb(b[a]);return b.join("/")}function ad(b,a){var c=Ba(b);a.$$protocol= 93 | c.protocol;a.$$host=c.hostname;a.$$port=$(c.port)||wf[c.protocol]||null}function bd(b,a){var c="/"!==b.charAt(0);c&&(b="/"+b);var d=Ba(b);a.$$path=decodeURIComponent(c&&"/"===d.pathname.charAt(0)?d.pathname.substring(1):d.pathname);a.$$search=rc(d.search);a.$$hash=decodeURIComponent(d.hash);a.$$path&&"/"!=a.$$path.charAt(0)&&(a.$$path="/"+a.$$path)}function za(b,a){if(0===a.indexOf(b))return a.substr(b.length)}function Ha(b){var a=b.indexOf("#");return-1==a?b:b.substr(0,a)}function Gb(b){return b.replace(/(#.+)|#$/, 94 | "$1")}function cc(b){return b.substr(0,Ha(b).lastIndexOf("/")+1)}function dc(b,a){this.$$html5=!0;a=a||"";var c=cc(b);ad(b,this);this.$$parse=function(a){var b=za(c,a);if(!x(b))throw Hb("ipthprfx",a,c);bd(b,this);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Qb(this.$$search),b=this.$$hash?"#"+rb(this.$$hash):"";this.$$url=bc(this.$$path)+(a?"?"+a:"")+b;this.$$absUrl=c+this.$$url.substr(1)};this.$$parseLinkUrl=function(d,e){if(e&&"#"===e[0])return this.hash(e.slice(1)), 95 | !0;var f,g;(f=za(b,d))!==u?(g=f,g=(f=za(a,f))!==u?c+(za("/",f)||f):b+g):(f=za(c,d))!==u?g=c+f:c==d+"/"&&(g=c);g&&this.$$parse(g);return!!g}}function ec(b,a){var c=cc(b);ad(b,this);this.$$parse=function(d){d=za(b,d)||za(c,d);var e;"#"===d.charAt(0)?(e=za(a,d),z(e)&&(e=d)):e=this.$$html5?d:"";bd(e,this);d=this.$$path;var f=/^\/[A-Z]:(\/.*)/;0===e.indexOf(b)&&(e=e.replace(b,""));f.exec(e)||(d=(e=f.exec(d))?e[1]:d);this.$$path=d;this.$$compose()};this.$$compose=function(){var c=Qb(this.$$search),e=this.$$hash? 96 | "#"+rb(this.$$hash):"";this.$$url=bc(this.$$path)+(c?"?"+c:"")+e;this.$$absUrl=b+(this.$$url?a+this.$$url:"")};this.$$parseLinkUrl=function(a,c){return Ha(b)==Ha(a)?(this.$$parse(a),!0):!1}}function cd(b,a){this.$$html5=!0;ec.apply(this,arguments);var c=cc(b);this.$$parseLinkUrl=function(d,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;b==Ha(d)?f=d:(g=za(c,d))?f=b+a+g:c===d+"/"&&(f=c);f&&this.$$parse(f);return!!f};this.$$compose=function(){var c=Qb(this.$$search),e=this.$$hash?"#"+rb(this.$$hash): 97 | "";this.$$url=bc(this.$$path)+(c?"?"+c:"")+e;this.$$absUrl=b+a+this.$$url}}function Ib(b){return function(){return this[b]}}function dd(b,a){return function(c){if(z(c))return this[b];this[b]=a(c);this.$$compose();return this}}function Me(){var b="",a={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(a){return y(a)?(b=a,this):b};this.html5Mode=function(b){return Wa(b)?(a.enabled=b,this):J(b)?(Wa(b.enabled)&&(a.enabled=b.enabled),Wa(b.requireBase)&&(a.requireBase=b.requireBase),Wa(b.rewriteLinks)&& 98 | (a.rewriteLinks=b.rewriteLinks),this):a};this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window",function(c,d,e,f,g){function h(a,b,c){var e=k.url(),f=k.$$state;try{d.url(a,b,c),k.$$state=d.state()}catch(g){throw k.url(e),k.$$state=f,g;}}function l(a,b){c.$broadcast("$locationChangeSuccess",k.absUrl(),a,k.$$state,b)}var k,m;m=d.baseHref();var p=d.url(),q;if(a.enabled){if(!m&&a.requireBase)throw Hb("nobase");q=p.substring(0,p.indexOf("/",p.indexOf("//")+2))+(m||"/");m=e.history?dc:cd}else q= 99 | Ha(p),m=ec;k=new m(q,"#"+b);k.$$parseLinkUrl(p,p);k.$$state=d.state();var t=/^\s*(javascript|mailto):/i;f.on("click",function(b){if(a.rewriteLinks&&!b.ctrlKey&&!b.metaKey&&!b.shiftKey&&2!=b.which&&2!=b.button){for(var e=C(b.target);"a"!==ta(e[0]);)if(e[0]===f[0]||!(e=e.parent())[0])return;var h=e.prop("href"),l=e.attr("href")||e.attr("xlink:href");J(h)&&"[object SVGAnimatedString]"===h.toString()&&(h=Ba(h.animVal).href);t.test(h)||!h||e.attr("target")||b.isDefaultPrevented()||!k.$$parseLinkUrl(h, 100 | l)||(b.preventDefault(),k.absUrl()!=d.url()&&(c.$apply(),g.angular["ff-684208-preventDefault"]=!0))}});Gb(k.absUrl())!=Gb(p)&&d.url(k.absUrl(),!0);var r=!0;d.onUrlChange(function(a,b){c.$evalAsync(function(){var d=k.absUrl(),e=k.$$state,f;k.$$parse(a);k.$$state=b;f=c.$broadcast("$locationChangeStart",a,d,b,e).defaultPrevented;k.absUrl()===a&&(f?(k.$$parse(d),k.$$state=e,h(d,!1,e)):(r=!1,l(d,e)))});c.$$phase||c.$digest()});c.$watch(function(){var a=Gb(d.url()),b=Gb(k.absUrl()),f=d.state(),g=k.$$replace, 101 | q=a!==b||k.$$html5&&e.history&&f!==k.$$state;if(r||q)r=!1,c.$evalAsync(function(){var b=k.absUrl(),d=c.$broadcast("$locationChangeStart",b,a,k.$$state,f).defaultPrevented;k.absUrl()===b&&(d?(k.$$parse(a),k.$$state=f):(q&&h(b,g,f===k.$$state?null:k.$$state),l(a,f)))});k.$$replace=!1});return k}]}function Ne(){var b=!0,a=this;this.debugEnabled=function(a){return y(a)?(b=a,this):b};this.$get=["$window",function(c){function d(a){a instanceof Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)? 102 | "Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=c.console||{},e=b[a]||b.log||B;a=!1;try{a=!!e.apply}catch(l){}return a?function(){var a=[];s(arguments,function(b){a.push(d(b))});return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){b&&c.apply(a,arguments)}}()}}]}function sa(b,a){if("__defineGetter__"=== 103 | b||"__defineSetter__"===b||"__lookupGetter__"===b||"__lookupSetter__"===b||"__proto__"===b)throw ka("isecfld",a);return b}function la(b,a){if(b){if(b.constructor===b)throw ka("isecfn",a);if(b.window===b)throw ka("isecwindow",a);if(b.children&&(b.nodeName||b.prop&&b.attr&&b.find))throw ka("isecdom",a);if(b===Object)throw ka("isecobj",a);}return b}function fc(b){return b.constant}function hb(b,a,c,d,e){la(b,e);la(a,e);c=c.split(".");for(var f,g=0;1h?ed(g[0],g[1],g[2],g[3],g[4],c,d):function(a,b){var e=0,f;do f=ed(g[e++],g[e++],g[e++],g[e++],g[e++],c,d)(a,b),b=u,a=f;while(e=this.promise.$$state.status&&d&&d.length&&b(function(){for(var b,e,f=0,g=d.length;fa)for(b in k++,f)e.hasOwnProperty(b)||(t--,delete f[b])}else f!==e&&(f=e,k++);return k}}c.$stateful=!0;var d=this,e,f,h,l=1s&&(y=4-s,W[y]||(W[y]=[]),W[y].push({msg:G(e.exp)?"fn: "+(e.exp.name||e.exp.toString()):e.exp,newVal:g,oldVal:l}));else if(e===c){t=!1;break a}}catch(C){f(C)}if(!(m=I.$$childHead||I!==this&&I.$$nextSibling))for(;I!==this&&!(m=I.$$nextSibling);)I=I.$parent}while(I=m);if((t||S.length)&&!s--)throw r.$$phase=null,a("infdig",b,W);}while(t||S.length); 124 | for(r.$$phase=null;u.length;)try{u.shift()()}catch(B){f(B)}},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;if(this!==r){for(var b in this.$$listenerCount)m(this,this.$$listenerCount[b],b);a.$$childHead==this&&(a.$$childHead=this.$$nextSibling);a.$$childTail==this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling);this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling); 125 | this.$destroy=this.$digest=this.$apply=this.$evalAsync=this.$applyAsync=B;this.$on=this.$watch=this.$watchGroup=function(){return B};this.$$listeners={};this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=this.$root=this.$$watchers=null}}},$eval:function(a,b){return g(a)(this,b)},$evalAsync:function(a,b){r.$$phase||S.length||h.defer(function(){S.length&&r.$digest()});S.push({scope:this,expression:a,locals:b})},$$postDigest:function(a){u.push(a)},$apply:function(a){try{return k("$apply"), 126 | this.$eval(a)}catch(b){f(b)}finally{r.$$phase=null;try{r.$digest()}catch(c){throw f(c),c;}}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&n.push(b);t()},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]||(d.$$listenerCount[a]=0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){var d=c.indexOf(b);-1!==d&&(c[d]=null,m(e,1,a))}},$emit:function(a,b){var c=[],d,e=this,g=!1,h={name:a,targetScope:e, 127 | stopPropagation:function(){g=!0},preventDefault:function(){h.defaultPrevented=!0},defaultPrevented:!1},l=Ya([h],arguments,1),k,m;do{d=e.$$listeners[a]||c;h.currentScope=e;k=0;for(m=d.length;kRa)throw Ca("iequirks");var d=qa(ma);d.isEnabled=function(){return b};d.trustAs=c.trustAs;d.getTrusted=c.getTrusted;d.valueOf=c.valueOf;b||(d.trustAs=d.getTrusted=function(a,b){return b},d.valueOf=oa);d.parseAs=function(b,c){var e=a(c);return e.literal&&e.constant?e:a(c,function(a){return d.getTrusted(b,a)})};var e=d.parseAs,f=d.getTrusted,g=d.trustAs;s(ma,function(a,b){var c=R(b);d[db("parse_as_"+c)]=function(b){return e(a, 134 | b)};d[db("get_trusted_"+c)]=function(b){return f(a,b)};d[db("trust_as_"+c)]=function(b){return g(a,b)}});return d}]}function Ue(){this.$get=["$window","$document",function(b,a){var c={},d=$((/android (\d+)/.exec(R((b.navigator||{}).userAgent))||[])[1]),e=/Boxee/i.test((b.navigator||{}).userAgent),f=a[0]||{},g,h=/^(Moz|webkit|ms)(?=[A-Z])/,l=f.body&&f.body.style,k=!1,m=!1;if(l){for(var p in l)if(k=h.exec(p)){g=k[0];g=g.substr(0,1).toUpperCase()+g.substr(1);break}g||(g="WebkitOpacity"in l&&"webkit"); 135 | k=!!("transition"in l||g+"Transition"in l);m=!!("animation"in l||g+"Animation"in l);!d||k&&m||(k=x(f.body.style.webkitTransition),m=x(f.body.style.webkitAnimation))}return{history:!(!b.history||!b.history.pushState||4>d||e),hasEvent:function(a){if("input"===a&&11>=Ra)return!1;if(z(c[a])){var b=f.createElement("div");c[a]="on"+a in b}return c[a]},csp:bb(),vendorPrefix:g,transitions:k,animations:m,android:d}}]}function We(){this.$get=["$templateCache","$http","$q",function(b,a,c){function d(e,f){d.totalPendingRequests++; 136 | var g=a.defaults&&a.defaults.transformResponse;E(g)?g=g.filter(function(a){return a!==Zb}):g===Zb&&(g=null);return a.get(e,{cache:b,transformResponse:g}).finally(function(){d.totalPendingRequests--}).then(function(a){return a.data},function(a){if(!f)throw ia("tpload",e);return c.reject(a)})}d.totalPendingRequests=0;return d}]}function Xe(){this.$get=["$rootScope","$browser","$location",function(b,a,c){return{findBindings:function(a,b,c){a=a.getElementsByClassName("ng-binding");var g=[];s(a,function(a){var d= 137 | aa.element(a).data("$binding");d&&s(d,function(d){c?(new RegExp("(^|\\s)"+gd(b)+"(\\s|\\||$)")).test(d)&&g.push(a):-1!=d.indexOf(b)&&g.push(a)})});return g},findModels:function(a,b,c){for(var g=["ng-","data-ng-","ng\\:"],h=0;hb;b=Math.abs(b);var g=b+"",h="",l=[],k=!1;if(-1!==g.indexOf("e")){var m=g.match(/([\d\.]+)e(-?)(\d+)/);m&&"-"==m[2]&&m[3]>e+1?b=0:(h=g,k=!0)}if(k)0b&&(h=b.toFixed(e),b=parseFloat(h));else{g=(g.split(od)[1]||"").length;z(e)&&(e=Math.min(Math.max(a.minFrac,g),a.maxFrac));b=+(Math.round(+(b.toString()+"e"+e)).toString()+"e"+-e);var g=(""+b).split(od),k=g[0],g=g[1]||"",p=0,q=a.lgSize,t=a.gSize;if(k.length>=q+t)for(p=k.length-q,m=0;mb&&(d="-",b=-b);for(b=""+b;b.length-c)e+=c;0===e&&-12==c&&(e=12);return Jb(e,a,d)}}function Kb(b,a){return function(c,d){var e=c["get"+b](),f=vb(a?"SHORT"+b:b);return d[f][e]}} 145 | function pd(b){var a=(new Date(b,0,1)).getDay();return new Date(b,0,(4>=a?5:12)-a)}function qd(b){return function(a){var c=pd(a.getFullYear());a=+new Date(a.getFullYear(),a.getMonth(),a.getDate()+(4-a.getDay()))-+c;a=1+Math.round(a/6048E5);return Jb(a,b)}}function kd(b){function a(a){var b;if(b=a.match(c)){a=new Date(0);var f=0,g=0,h=b[8]?a.setUTCFullYear:a.setFullYear,l=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=$(b[9]+b[10]),g=$(b[9]+b[11]));h.call(a,$(b[1]),$(b[2])-1,$(b[3]));f=$(b[4]||0)-f;g=$(b[5]|| 146 | 0)-g;h=$(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));l.call(a,f,g,h,b)}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,e,f){var g="",h=[],l,k;e=e||"mediumDate";e=b.DATETIME_FORMATS[e]||e;x(c)&&(c=Jf.test(c)?$(c):a(c));V(c)&&(c=new Date(c));if(!pa(c))return c;for(;e;)(k=Kf.exec(e))?(h=Ya(h,k,1),e=h.pop()):(h.push(e),e=null);f&&"UTC"===f&&(c=new Date(c.getTime()),c.setMinutes(c.getMinutes()+c.getTimezoneOffset())); 147 | s(h,function(a){l=Lf[a];g+=l?l(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function Ef(){return function(b,a){z(a)&&(a=2);return $a(b,a)}}function Ff(){return function(b,a){V(b)&&(b=b.toString());return E(b)||x(b)?(a=Infinity===Math.abs(Number(a))?Number(a):$(a))?0b||37<=b&&40>=b||m(a,this,this.value)});if(e.hasEvent("paste"))a.on("paste cut",m)}a.on("change",l);d.$render=function(){a.val(d.$isEmpty(d.$viewValue)?"":d.$viewValue)}} 154 | function Nb(b,a){return function(c,d){var e,f;if(pa(c))return c;if(x(c)){'"'==c.charAt(0)&&'"'==c.charAt(c.length-1)&&(c=c.substring(1,c.length-1));if(Mf.test(c))return new Date(c);b.lastIndex=0;if(e=b.exec(c))return e.shift(),f=d?{yyyy:d.getFullYear(),MM:d.getMonth()+1,dd:d.getDate(),HH:d.getHours(),mm:d.getMinutes(),ss:d.getSeconds(),sss:d.getMilliseconds()/1E3}:{yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0,sss:0},s(e,function(b,c){c=s};g.$observe("min",function(a){s=q(a);h.$validate()})}if(y(g.max)||g.ngMax){var K;h.$validators.max=function(a){return!p(a)||z(K)||c(a)<=K};g.$observe("max",function(a){K=q(a);h.$validate()})}}}function td(b,a,c,d){(d.$$hasNativeValidators=J(a[0].validity))&&d.$parsers.push(function(b){var c=a.prop("validity")||{}; 157 | return c.badInput&&!c.typeMismatch?u:b})}function ud(b,a,c,d,e){if(y(d)){b=b(d);if(!b.constant)throw M("ngModel")("constexpr",c,d);return b(a)}return e}function jc(b,a){b="ngClass"+b;return["$animate",function(c){function d(a,b){var c=[],d=0;a:for(;d(?:<\/\1>|)$/,Tb=/<|&#?\w+;/,ef=/<([\w:]+)/,ff=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, 163 | ga={option:[1,'"],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ga.optgroup=ga.option;ga.tbody=ga.tfoot=ga.colgroup=ga.caption=ga.thead;ga.th=ga.td;var La=Q.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;"complete"===X.readyState?setTimeout(a):(this.on("DOMContentLoaded",a),Q(P).on("load",a))}, 164 | toString:function(){var b=[];s(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return 0<=b?C(this[b]):C(this[this.length+b])},length:0,push:Of,sort:[].sort,splice:[].splice},Fb={};s("multiple selected checked disabled readOnly required open".split(" "),function(b){Fb[R(b)]=b});var Nc={};s("input select option textarea button form details".split(" "),function(b){Nc[b]=!0});var Oc={ngMinlength:"minlength",ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern"}; 165 | s({data:Wb,removeData:yb},function(b,a){Q[a]=b});s({data:Wb,inheritedData:Eb,scope:function(b){return C.data(b,"$scope")||Eb(b.parentNode||b,["$isolateScope","$scope"])},isolateScope:function(b){return C.data(b,"$isolateScope")||C.data(b,"$isolateScopeNoTemplate")},controller:Jc,injector:function(b){return Eb(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Bb,css:function(b,a,c){a=db(a);if(y(c))b.style[a]=c;else return b.style[a]},attr:function(b,a,c){var d=R(a);if(Fb[d])if(y(c))c? 166 | (b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||B).specified?d:u;else if(y(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),null===b?u:b},prop:function(b,a,c){if(y(c))b[a]=c;else return b[a]},text:function(){function b(a,b){if(z(b)){var d=a.nodeType;return d===na||d===qb?a.textContent:""}a.textContent=b}b.$dv="";return b}(),val:function(b,a){if(z(a)){if(b.multiple&&"select"===ta(b)){var c=[];s(b.options,function(a){a.selected&& 167 | c.push(a.value||a.text)});return 0===c.length?null:c}return b.value}b.value=a},html:function(b,a){if(z(a))return b.innerHTML;xb(b,!0);b.innerHTML=a},empty:Kc},function(b,a){Q.prototype[a]=function(a,d){var e,f,g=this.length;if(b!==Kc&&(2==b.length&&b!==Bb&&b!==Jc?a:d)===u){if(J(a)){for(e=0;e":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a, 183 | c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"!":function(a,c,d){return!d(a,c)},"=":!0,"|":!0}),Yf={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},hc=function(a){this.options=a};hc.prototype={constructor:hc,lex:function(a){this.text=a;this.index=0;for(this.tokens=[];this.index=a&&"string"===typeof a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdent:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},throwError:function(a,c,d){d=d||this.index;c=y(c)?"s "+c+"-"+this.index+" ["+this.text.substring(c,d)+"]":" "+d;throw ka("lexerr",a,c,this.text); 186 | },readNumber:function(){for(var a="",c=this.index;this.indexa){a=this.tokens[a];var g=a.text;if(g===c||g===d||g===e||g=== 191 | f||!(c||d||e||f))return a}return!1},expect:function(a,c,d,e){return(a=this.peek(a,c,d,e))?(this.tokens.shift(),a):!1},consume:function(a){if(0===this.tokens.length)throw ka("ueoe",this.text);var c=this.expect(a);c||this.throwError("is unexpected, expecting ["+a+"]",this.peek());return c},unaryFn:function(a,c){var d=nb[a];return w(function(a,f){return d(a,f,c)},{constant:c.constant,inputs:[c]})},binaryFn:function(a,c,d,e){var f=nb[c];return w(function(c,e){return f(c,e,a,d)},{constant:a.constant&& 192 | d.constant,inputs:!e&&[a,d]})},identifier:function(){for(var a=this.consume().text;this.peek(".")&&this.peekAhead(1).identifier&&!this.peekAhead(2,"(");)a+=this.consume().text+this.consume().text;return yf(a,this.options,this.text)},constant:function(){var a=this.consume().value;return w(function(){return a},{constant:!0,literal:!0})},statements:function(){for(var a=[];;)if(0","<=",">=");)a=this.binaryFn(a,c.text, 196 | this.additive());return a},additive:function(){for(var a=this.multiplicative(),c;c=this.expect("+","-");)a=this.binaryFn(a,c.text,this.multiplicative());return a},multiplicative:function(){for(var a=this.unary(),c;c=this.expect("*","/","%");)a=this.binaryFn(a,c.text,this.unary());return a},unary:function(){var a;return this.expect("+")?this.primary():(a=this.expect("-"))?this.binaryFn(ib.ZERO,a.text,this.unary()):(a=this.expect("!"))?this.unaryFn(a.text,this.unary()):this.primary()},fieldAccess:function(a){var c= 197 | this.identifier();return w(function(d,e,f){d=f||a(d,e);return null==d?u:c(d)},{assign:function(d,e,f){var g=a(d,f);g||a.assign(d,g={},f);return c.assign(g,e)}})},objectIndex:function(a){var c=this.text,d=this.expression();this.consume("]");return w(function(e,f){var g=a(e,f),h=d(e,f);sa(h,c);return g?la(g[h],c):u},{assign:function(e,f,g){var h=sa(d(e,g),c),l=la(a(e,g),c);l||a.assign(e,l={},g);return l[h]=f}})},functionCall:function(a,c){var d=[];if(")"!==this.peekToken().text){do d.push(this.expression()); 198 | while(this.expect(","))}this.consume(")");var e=this.text,f=d.length?[]:null;return function(g,h){var l=c?c(g,h):y(c)?u:g,k=a(g,h,l)||B;if(f)for(var m=d.length;m--;)f[m]=la(d[m](g,h),e);la(l,e);if(k){if(k.constructor===k)throw ka("isecfn",e);if(k===Vf||k===Wf||k===Xf)throw ka("isecff",e);}l=k.apply?k.apply(l,f):k(f[0],f[1],f[2],f[3],f[4]);f&&(f.length=0);return la(l,e)}},arrayDeclaration:function(){var a=[];if("]"!==this.peekToken().text){do{if(this.peek("]"))break;a.push(this.expression())}while(this.expect(",")) 199 | }this.consume("]");return w(function(c,d){for(var e=[],f=0,g=a.length;fa.getHours()?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){a=-1*a.getTimezoneOffset();return a=(0<=a?"+":"")+(Jb(Math[0=h};d.$observe("min",function(a){y(a)&&!V(a)&&(a=parseFloat(a,10));h=V(a)&&!isNaN(a)?a:u;e.$validate()})}if(y(d.max)|| 209 | d.ngMax){var l;e.$validators.max=function(a){return e.$isEmpty(a)||z(l)||a<=l};d.$observe("max",function(a){y(a)&&!V(a)&&(a=parseFloat(a,10));l=V(a)&&!isNaN(a)?a:u;e.$validate()})}},url:function(a,c,d,e,f,g){jb(a,c,d,e,f,g);ic(e);e.$$parserName="url";e.$validators.url=function(a,c){var d=a||c;return e.$isEmpty(d)||Zf.test(d)}},email:function(a,c,d,e,f,g){jb(a,c,d,e,f,g);ic(e);e.$$parserName="email";e.$validators.email=function(a,c){var d=a||c;return e.$isEmpty(d)||$f.test(d)}},radio:function(a,c, 210 | d,e){z(d.name)&&c.attr("name",++ob);c.on("click",function(a){c[0].checked&&e.$setViewValue(d.value,a&&a.type)});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e,f,g,h,l){var k=ud(l,a,"ngTrueValue",d.ngTrueValue,!0),m=ud(l,a,"ngFalseValue",d.ngFalseValue,!1);c.on("click",function(a){e.$setViewValue(c[0].checked,a&&a.type)});e.$render=function(){c[0].checked=e.$viewValue};e.$isEmpty=function(a){return!1===a};e.$formatters.push(function(a){return ea(a, 211 | k)});e.$parsers.push(function(a){return a?k:m})},hidden:B,button:B,submit:B,reset:B,file:B},yc=["$browser","$sniffer","$filter","$parse",function(a,c,d,e){return{restrict:"E",require:["?ngModel"],link:{pre:function(f,g,h,l){l[0]&&(Dd[R(h.type)]||Dd.text)(f,g,h,l[0],c,a,d,e)}}}}],bg=/^(true|false|\d+)$/,ye=function(){return{restrict:"A",priority:100,compile:function(a,c){return bg.test(c.ngValue)?function(a,c,f){f.$set("value",a.$eval(f.ngValue))}:function(a,c,f){a.$watch(f.ngValue,function(a){f.$set("value", 212 | a)})}}}},Zd=["$compile",function(a){return{restrict:"AC",compile:function(c){a.$$addBindingClass(c);return function(c,e,f){a.$$addBindingInfo(e,f.ngBind);e=e[0];c.$watch(f.ngBind,function(a){e.textContent=a===u?"":a})}}}}],ae=["$interpolate","$compile",function(a,c){return{compile:function(d){c.$$addBindingClass(d);return function(d,f,g){d=a(f.attr(g.$attr.ngBindTemplate));c.$$addBindingInfo(f,d.expressions);f=f[0];g.$observe("ngBindTemplate",function(a){f.textContent=a===u?"":a})}}}}],$d=["$sce", 213 | "$parse","$compile",function(a,c,d){return{restrict:"A",compile:function(e,f){var g=c(f.ngBindHtml),h=c(f.ngBindHtml,function(a){return(a||"").toString()});d.$$addBindingClass(e);return function(c,e,f){d.$$addBindingInfo(e,f.ngBindHtml);c.$watch(h,function(){e.html(a.getTrustedHtml(g(c))||"")})}}}}],xe=da({restrict:"A",require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),be=jc("",!0),de=jc("Odd",0),ce=jc("Even",1),ee=Ja({compile:function(a,c){c.$set("ngCloak", 214 | u);a.removeClass("ng-cloak")}}),fe=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}],Dc={},cg={blur:!0,focus:!0};s("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var c=ya("ng-"+a);Dc[c]=["$parse","$rootScope",function(d,e){return{restrict:"A",compile:function(f,g){var h=d(g[c],null,!0);return function(c,d){d.on(a,function(d){var f=function(){h(c,{$event:d})}; 215 | cg[a]&&e.$$phase?c.$evalAsync(f):c.$apply(f)})}}}}]});var ie=["$animate",function(a){return{multiElement:!0,transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(c,d,e,f,g){var h,l,k;c.$watch(e.ngIf,function(c){c?l||g(function(c,f){l=f;c[c.length++]=X.createComment(" end ngIf: "+e.ngIf+" ");h={clone:c};a.enter(c,d.parent(),d)}):(k&&(k.remove(),k=null),l&&(l.$destroy(),l=null),h&&(k=ub(h.clone),a.leave(k).then(function(){k=null}),h=null))})}}}],je=["$templateRequest","$anchorScroll", 216 | "$animate","$sce",function(a,c,d,e){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:aa.noop,compile:function(f,g){var h=g.ngInclude||g.src,l=g.onload||"",k=g.autoscroll;return function(f,g,q,s,r){var u=0,w,n,D,H=function(){n&&(n.remove(),n=null);w&&(w.$destroy(),w=null);D&&(d.leave(D).then(function(){n=null}),n=D,D=null)};f.$watch(e.parseAsResourceUrl(h),function(e){var h=function(){!y(k)||k&&!f.$eval(k)||c()},n=++u;e?(a(e,!0).then(function(a){if(n===u){var c=f.$new(); 217 | s.template=a;a=r(c,function(a){H();d.enter(a,null,g).then(h)});w=c;D=a;w.$emit("$includeContentLoaded",e);f.$eval(l)}},function(){n===u&&(H(),f.$emit("$includeContentError",e))}),f.$emit("$includeContentRequested",e)):(H(),s.template=null)})}}}}],Ae=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(c,d,e,f){/SVG/.test(d[0].toString())?(d.empty(),a(Gc(f.template,X).childNodes)(c,function(a){d.append(a)},{futureParentElement:d})):(d.html(f.template),a(d.contents())(c))}}}], 218 | ke=Ja({priority:450,compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),we=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,c,d,e){var f=c.attr(d.$attr.ngList)||", ",g="false"!==d.ngTrim,h=g?T(f):f;e.$parsers.push(function(a){if(!z(a)){var c=[];a&&s(a.split(h),function(a){a&&c.push(g?T(a):a)});return c}});e.$formatters.push(function(a){return E(a)?a.join(f):u});e.$isEmpty=function(a){return!a||!a.length}}}},lb="ng-valid",vd="ng-invalid",Sa="ng-pristine", 219 | Mb="ng-dirty",xd="ng-pending",Ob=new M("ngModel"),dg=["$scope","$exceptionHandler","$attrs","$element","$parse","$animate","$timeout","$rootScope","$q","$interpolate",function(a,c,d,e,f,g,h,l,k,m){this.$modelValue=this.$viewValue=Number.NaN;this.$$rawModelValue=u;this.$validators={};this.$asyncValidators={};this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$untouched=!0;this.$touched=!1;this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$error={};this.$$success= 220 | {};this.$pending=u;this.$name=m(d.name||"",!1)(a);var p=f(d.ngModel),q=p.assign,t=p,r=q,w=null,C,n=this;this.$$setOptions=function(a){if((n.$options=a)&&a.getterSetter){var c=f(d.ngModel+"()"),g=f(d.ngModel+"($$$p)");t=function(a){var d=p(a);G(d)&&(d=c(a));return d};r=function(a,c){G(p(a))?g(a,{$$$p:n.$modelValue}):q(a,n.$modelValue)}}else if(!p.assign)throw Ob("nonassign",d.ngModel,ua(e));};this.$render=B;this.$isEmpty=function(a){return z(a)||""===a||null===a||a!==a};var D=e.inheritedData("$formController")|| 221 | Lb,H=0;sd({ctrl:this,$element:e,set:function(a,c){a[c]=!0},unset:function(a,c){delete a[c]},parentForm:D,$animate:g});this.$setPristine=function(){n.$dirty=!1;n.$pristine=!0;g.removeClass(e,Mb);g.addClass(e,Sa)};this.$setDirty=function(){n.$dirty=!0;n.$pristine=!1;g.removeClass(e,Sa);g.addClass(e,Mb);D.$setDirty()};this.$setUntouched=function(){n.$touched=!1;n.$untouched=!0;g.setClass(e,"ng-untouched","ng-touched")};this.$setTouched=function(){n.$touched=!0;n.$untouched=!1;g.setClass(e,"ng-touched", 222 | "ng-untouched")};this.$rollbackViewValue=function(){h.cancel(w);n.$viewValue=n.$$lastCommittedViewValue;n.$render()};this.$validate=function(){if(!V(n.$modelValue)||!isNaN(n.$modelValue)){var a=n.$$rawModelValue,c=n.$valid,d=n.$modelValue,e=n.$options&&n.$options.allowInvalid;n.$$runValidators(a,n.$$lastCommittedViewValue,function(f){e||c===f||(n.$modelValue=f?a:u,n.$modelValue!==d&&n.$$writeModelToScope())})}};this.$$runValidators=function(a,c,d){function e(){var d=!0;s(n.$validators,function(e, 223 | f){var h=e(a,c);d=d&&h;g(f,h)});return d?!0:(s(n.$asyncValidators,function(a,c){g(c,null)}),!1)}function f(){var d=[],e=!0;s(n.$asyncValidators,function(f,h){var k=f(a,c);if(!k||!G(k.then))throw Ob("$asyncValidators",k);g(h,u);d.push(k.then(function(){g(h,!0)},function(a){e=!1;g(h,!1)}))});d.length?k.all(d).then(function(){h(e)},B):h(!0)}function g(a,c){l===H&&n.$setValidity(a,c)}function h(a){l===H&&d(a)}H++;var l=H;(function(){var a=n.$$parserName||"parse";if(C===u)g(a,null);else return C||(s(n.$validators, 224 | function(a,c){g(c,null)}),s(n.$asyncValidators,function(a,c){g(c,null)})),g(a,C),C;return!0})()?e()?f():h(!1):h(!1)};this.$commitViewValue=function(){var a=n.$viewValue;h.cancel(w);if(n.$$lastCommittedViewValue!==a||""===a&&n.$$hasNativeValidators)n.$$lastCommittedViewValue=a,n.$pristine&&this.$setDirty(),this.$$parseAndValidate()};this.$$parseAndValidate=function(){var c=n.$$lastCommittedViewValue;if(C=z(c)?u:!0)for(var d=0;dA;)d=u.pop(),m(O,d.label,!1),d.element.remove()}for(;P.length> 244 | x;){l=P.pop();for(A=1;Aa&&q.removeOption(c)})}var v;if(!(v=r.match(d)))throw fg("iexp",r,ua(f));var C=c(v[2]||v[1]),B=v[4]||v[6],x=/ as /.test(v[0])&&v[1],z=x?c(x):null,G=v[5],J=c(v[3]||""),A=c(v[2]?v[1]:B),N=c(v[7]),K=v[8]?c(v[8]):null,Q={},P=[[{element:f,label:""}]],R={};w&&(a(w)(e),w.removeClass("ng-scope"),w.remove());f.empty();f.on("change",function(){e.$apply(function(){var a=N(e)||[],c;if(t)c=[],s(f.val(), 245 | function(d){d=K?Q[d]:d;c.push("?"===d?u:""===d?null:h(z?z:A,d,a[d]))});else{var d=K?Q[f.val()]:f.val();c="?"===d?u:""===d?null:h(z?z:A,d,a[d])}g.$setViewValue(c);p()})});g.$render=p;e.$watchCollection(N,l);e.$watchCollection(function(){var a=N(e),c;if(a&&E(a)){c=Array(a.length);for(var d=0,f=a.length;df||e.$isEmpty(c)||c.length<=f}}}}},Bc=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){if(e){var f=0;d.$observe("minlength",function(a){f=$(a)||0;e.$validate()});e.$validators.minlength=function(a,c){return e.$isEmpty(c)||c.length>=f}}}}};P.angular.bootstrap?console.log("WARNING: Tried to load angular more than once."):(Nd(),Pd(aa),C(X).ready(function(){Jd(X,tc)}))})(window,document);!window.angular.$$csp()&&window.angular.element(document).find("head").prepend(''); 250 | //# sourceMappingURL=angular.min.js.map 251 | -------------------------------------------------------------------------------- /angular_scripts.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('wptview', ['angularSpinner']); 2 | 3 | app.directive('customOnChange', function() { 4 | return { 5 | restrict: 'A', 6 | link: function (scope, element, attrs) { 7 | var onChangeHandler = scope.$eval(attrs.customOnChange); 8 | element.bind('change', onChangeHandler); 9 | } 10 | }; 11 | }); 12 | 13 | app.directive('editName', function() { 14 | return { 15 | require: 'ngModel', 16 | link: function(scope, element, attrs, ctrl) { 17 | var runNames = scope.runs.map((run) => run.name); 18 | var initialName = attrs.value; 19 | ctrl.$validators.editName = function(modelValue, viewValue) { 20 | var curName = viewValue; 21 | if (!(curName === null || curName.match(/^\s*$/) !== null)) { 22 | if (runNames.indexOf(curName) === -1 || curName === initialName) { 23 | element.css('border-color', 'green'); 24 | return true; 25 | } else { 26 | element.css('border-color', 'red'); 27 | return false; 28 | } 29 | } 30 | }; 31 | } 32 | }; 33 | }); 34 | 35 | app.filter('arrFilter', function() { 36 | return function(collection, currentRun) { 37 | return collection.filter((item) => currentRun !== item.name && currentRun !== "ALL" && item.enabled); 38 | }; 39 | }); 40 | 41 | app.filter('enabledFilter', function() { 42 | return function(collection) { 43 | return collection.filter((item) => item.enabled); 44 | }; 45 | }); 46 | 47 | function WorkerService(workerScript) { 48 | this.msg_id = 0; 49 | this.resolvers = {}; 50 | 51 | this.worker = new Worker(workerScript); 52 | this.worker.onmessage = function(event) { 53 | var msg_id = event.data[0]; 54 | var data = event.data[1]; 55 | if (!this.resolvers.hasOwnProperty(msg_id)) { 56 | throw Error("Unexpected message " + msg_id); 57 | } 58 | resolve = this.resolvers[msg_id]; 59 | delete this.resolvers[msg_id]; 60 | resolve(data); 61 | }.bind(this); 62 | } 63 | 64 | WorkerService.prototype.run = function(command, data) { 65 | var data = data || []; 66 | var msg = [this.msg_id++, command, data]; 67 | this.worker.postMessage(msg); 68 | return new Promise((resolve) => { 69 | this.resolvers[msg[0]] = resolve; 70 | }); 71 | }; 72 | 73 | app.factory('ResultsModel',function() { 74 | var ResultsModel = function() { 75 | this.service = new WorkerService("LovefieldService.js"); 76 | this.logReader = new WorkerService("logcruncher.js"); 77 | }; 78 | 79 | ResultsModel.prototype.addResultsFromLogs = function (source, runName, fetchFunc, updateProgress) { 80 | var lovefield = this.service; 81 | var resultData = null; 82 | var testData = null; 83 | var testRunData = null; 84 | var duplicates = null; 85 | var runType = null; 86 | if (fetchFunc === "readURL") { 87 | runType = { 88 | "type": "url", 89 | "url": source 90 | }; 91 | } else if (fetchFunc === "read") { 92 | runType = { 93 | "type": "file", 94 | "url": null 95 | }; 96 | } 97 | var totalTasks = 12; 98 | return this.logReader.run(fetchFunc, [source]) 99 | .then((data) => { 100 | resultData = data; 101 | updateProgress(1, totalTasks); 102 | }) 103 | // Filling the test_runs table 104 | .then(() => { 105 | updateProgress(2, totalTasks); 106 | return lovefield.run("selectParticularRun", [runName]); 107 | }) 108 | .then((testRuns) => { 109 | updateProgress(3, totalTasks); 110 | return lovefield.run("insertTestRuns", [runType, runName, testRuns]); 111 | }) 112 | // Selecting current tests table, adding extra entries only 113 | .then((testRuns) => { 114 | testRunData = testRuns; 115 | updateProgress(4, totalTasks); 116 | return lovefield.run("selectAllParentTests"); 117 | }) 118 | .then((parentTests) => { 119 | updateProgress(5, totalTasks); 120 | return lovefield.run("insertTests", [resultData, parentTests]); 121 | }) 122 | .then((insertData) => { 123 | updateProgress(6, totalTasks); 124 | duplicates = insertData[1]; 125 | return lovefield.run("selectAllParentTests"); 126 | }) 127 | // populating results table with parent test results 128 | .then((tests) => { 129 | testData = tests; 130 | updateProgress(7, totalTasks); 131 | return lovefield.run("insertTestResults",[resultData, testData, testRunData]); 132 | }) 133 | // add subtests to tests table 134 | .then(() => { 135 | updateProgress(8, totalTasks); 136 | return lovefield.run("selectAllSubtests"); 137 | }) 138 | .then((subtests) => { 139 | updateProgress(9, totalTasks); 140 | return lovefield.run("insertSubtests", 141 | [resultData, testData, subtests]); 142 | }) 143 | .then((subtestData) => { 144 | updateProgress(10, totalTasks); 145 | duplicates = duplicates.concat(subtestData[1]); 146 | return lovefield.run("selectAllSubtests"); 147 | }) 148 | // adding subtest results 149 | .then((subtests) => { 150 | updateProgress(11, totalTasks); 151 | return lovefield.run("insertSubtestResults",[resultData, subtests, testRunData]); 152 | }) 153 | .then(() => { 154 | updateProgress(12, totalTasks); 155 | return duplicates; 156 | }); 157 | }; 158 | 159 | ResultsModel.prototype.updateWarnings = function(runName, duplicates) { 160 | return this.service.run("updateWarnings", [runName, duplicates]); 161 | }; 162 | 163 | /* 164 | Load the results of a specified number of tests, ordered by test id, either taking all 165 | results above a lower limit test id, all results below an upper limit id, or all results 166 | starting from the first test. 167 | Results may be filtered by various filters. 168 | @param {Object[]} filter - Array of filter definitions for the allowed test results. 169 | @param {} pathFilter - Array if filter definitions for the allowed test names. 170 | @param {(number|null)} minTestId - Exclusive lower bound on the test ID to load, or null if 171 | there is no lower limit. 172 | @param {(number|null)} maxTestId - Exclusive upper bound on the test ID to load, or null if 173 | there is no upper limit. 174 | @param {(number)} limit - Number of tests to load. 175 | */ 176 | ResultsModel.prototype.switchRuns = function(run_ids, enabled) { 177 | return this.service.run("switchRuns", [run_ids, enabled]); 178 | }; 179 | ResultsModel.prototype.getResults = function(filter, runs, minTestId, maxTestId, limit) { 180 | return this.service.run("selectFilteredResults", 181 | [filter, runs, minTestId, maxTestId, limit]); 182 | }; 183 | 184 | ResultsModel.prototype.getComment = function(result_id) { 185 | return this.service.run("selectComment", [result_id]); 186 | }; 187 | 188 | ResultsModel.prototype.saveComment = function(result_id, comment, update) { 189 | if (update) { 190 | return this.service.run("updateComment", [result_id, comment]); 191 | } else { 192 | return this.service.run("insertComment", [result_id, comment]); 193 | } 194 | }; 195 | 196 | ResultsModel.prototype.deleteComment = function(result_id) { 197 | return this.service.run("deleteComment", [result_id]); 198 | }; 199 | 200 | ResultsModel.prototype.removeResults = function(run_id) { 201 | return this.service.run("deleteEntries", [run_id]); 202 | }; 203 | 204 | ResultsModel.prototype.getRuns = function() { 205 | return this.service.run("getRuns"); 206 | }; 207 | 208 | ResultsModel.prototype.getRunURLs = function() { 209 | return this.service.run("getRunURLs"); 210 | }; 211 | 212 | ResultsModel.prototype.editRunName = function(run_id, newRunName) { 213 | return this.service.run("editRunName", [run_id, newRunName]); 214 | }; 215 | 216 | return ResultsModel; 217 | }); 218 | 219 | app.controller('wptviewController', function($scope, $location, $interval, ResultsModel) { 220 | $scope.results = null; 221 | $scope.showImport = false; 222 | $scope.busy = true; 223 | $scope.runs = null; 224 | $scope.upload = {}; 225 | $scope.displayWarnings = { 226 | runName: "", 227 | visible: false, 228 | warnings: [] 229 | }; 230 | $scope.displayError = { 231 | test: "", 232 | subtest: "", 233 | expected: "", 234 | status: "", 235 | error: "", 236 | visible: false 237 | }; 238 | $scope.progressBar = { 239 | "visibility": false 240 | }; 241 | $scope.resultsView = { 242 | limit: 50, 243 | firstPage: true, 244 | lastPage: false, 245 | minTestId: null, 246 | maxTestId: null, 247 | firstTestId: null 248 | }; 249 | $scope.filter = { 250 | "statusFilter": [], 251 | "pathFilter": [], 252 | "testTypeFilter": { 253 | type:"both" 254 | } 255 | }; 256 | var runIndex = {}; 257 | var resultsModel = new ResultsModel(); 258 | 259 | function updateRuns() { 260 | var runs; 261 | return resultsModel.getRuns() 262 | .then((runsData) => { 263 | runs = runsData; 264 | }) 265 | .then(() => { 266 | $scope.runs = runs; 267 | $scope.runs.forEach((run, i) => { 268 | runIndex[run.run_id] = i; 269 | run.edit = false; 270 | run.newName = ""; 271 | run.warnings = JSON.parse(run.warnings); 272 | }); 273 | }); 274 | } 275 | 276 | function addRun(source, name, type) { 277 | $scope.updateProgress(0,12); 278 | $scope.progressBar.visibility = true; 279 | return resultsModel.addResultsFromLogs(source, name, type, $scope.updateProgress) 280 | .then((duplicates) => { 281 | return resultsModel.updateWarnings(name, duplicates); 282 | }); 283 | } 284 | 285 | function getRunURLs() { 286 | return resultsModel.getRunURLs(); 287 | } 288 | 289 | function checkQuery(urls) { 290 | urls = urls.map((result) => result.url); 291 | var run_strings = []; 292 | if ($location.search() && $location.search().hasOwnProperty("urls")) { 293 | run_strings = $location.search().urls.split(";"); 294 | } 295 | var runs = []; 296 | var run_names = {}; 297 | $scope.runs.forEach((run) => { 298 | run_names[run.name] = 1; 299 | }); 300 | run_strings.forEach((run) => { 301 | var parameters = run.split(","); 302 | if (urls.indexOf(parameters[0]) === -1) { 303 | if (run_names.hasOwnProperty(parameters[1])) { 304 | var original_name = parameters[1]; 305 | while (run_names.hasOwnProperty(parameters[1])) { 306 | parameters[1] = original_name + " (" + run_names[original_name] + ")"; 307 | run_names[original_name] += 1; 308 | } 309 | } else { 310 | run_names[parameters[1]] = 1; 311 | } 312 | runs.push({ 313 | "url": parameters[0], 314 | "name": parameters[1] 315 | }); 316 | } 317 | }); 318 | var disabledRuns = []; 319 | if (runs.length) { 320 | disabledRuns = $scope.runs.map((run) => run.run_id); 321 | } 322 | var add_runs = runs.map((run) => addRun(run.url, run.name, "readURL")); 323 | return resultsModel.switchRuns(disabledRuns, false) 324 | .then(() => {return Promise.all(add_runs);}); 325 | } 326 | 327 | // first updateRuns() helps initialize the database 328 | updateRuns() 329 | .then(() => getRunURLs()) 330 | .then((urls) => checkQuery(urls)) 331 | .then(() => updateRuns()) 332 | .then(() => { 333 | $scope.busy = false; 334 | $scope.$apply(); 335 | }); 336 | 337 | $scope.updateProgress = function updateProgress(current, total) { 338 | $scope.progressValue = Math.floor(current*100/total); 339 | if (current === total) { 340 | setTimeout(function() { 341 | $scope.progressBar.visibility = false; 342 | $scope.$apply(); 343 | }, 100); 344 | } 345 | if (current > 0) { 346 | $scope.$apply(); 347 | } 348 | }; 349 | 350 | $scope.range = function(min, max, step) { 351 | step = step || 1; 352 | var input = []; 353 | for (var i = min; i < max; i += step) { 354 | input.push(i); 355 | } 356 | return input; 357 | }; 358 | 359 | $scope.duplicate_run_name = function() { 360 | var run_names = $scope.runs.map((run) => run.name); 361 | //Check if run having same run_name exists 362 | if (run_names.indexOf($scope.upload.runName) !== -1) { 363 | console.log("A run with name " + $scope.upload.runName + " already exists."); 364 | return true; 365 | } else { 366 | return false; 367 | } 368 | }; 369 | 370 | $scope.fetchLog = function () { 371 | if ($scope.upload.logSrc === 'file') { 372 | $scope.uploadFile(); 373 | } else if ($scope.upload.logSrc === 'url') { 374 | $scope.fetchFromUrl(); 375 | } 376 | }; 377 | 378 | $scope.uploadFile = function () { 379 | $scope.busy = true; 380 | var evt = $scope.fileEvent; 381 | var file = evt.target.files[0]; 382 | addRun(file, $scope.upload.runName, "read") 383 | .then(updateRuns) 384 | .then(() => { 385 | $scope.upload.runName = ""; 386 | // Clears target for "Upload File" after import is complete. 387 | $scope.fileEvent.target.value = null; 388 | $scope.busy = false; 389 | $scope.$apply(); 390 | }); 391 | }; 392 | 393 | $scope.fetchFromUrl = function () { 394 | $scope.busy = true; 395 | addRun($scope.upload.logUrl, $scope.upload.runName, "readURL") 396 | .then(updateRuns) 397 | .then(() => { 398 | $scope.upload.runName = ""; 399 | $scope.busy = false; 400 | $scope.$apply(); 401 | }); 402 | }; 403 | 404 | $scope.switchRun = function(run) { 405 | $scope.busy = true; 406 | resultsModel.switchRuns([run.run_id], run.enabled) 407 | .then(() => { 408 | $scope.busy = false; 409 | $scope.results = null; 410 | $scope.$apply(); 411 | }); 412 | }; 413 | 414 | $scope.editNameSubmit = function(index, isValid) { 415 | if (isValid) { 416 | resultsModel.editRunName($scope.runs[index].run_id, $scope.runs[index].newName) 417 | .then(() => { 418 | updateRuns() 419 | .then(() => { 420 | $scope.runs[index].edit = false; 421 | $scope.$apply(); 422 | }); 423 | }); 424 | } 425 | }; 426 | 427 | $scope.clearTable = function(run_id) { 428 | $scope.busy = true; 429 | resultsModel.removeResults(run_id) 430 | .then(() => { 431 | $scope.results = null; 432 | $scope.warnings = [];}) 433 | .then(updateRuns) 434 | .then(() => { 435 | $scope.busy = false; 436 | $scope.$apply(); 437 | }); 438 | }; 439 | 440 | $scope.export = function() { 441 | $scope.busy = true; 442 | resultsModel.getResults($scope.filter, $scope.runs) 443 | .then((results) => { 444 | var finaljson = {}; 445 | finaljson.runs = $scope.runs.map((run) => run.name); 446 | finaljson.results = {}; 447 | var organizedResults = organizeResults(results); 448 | organizedResults.forEach((result) => { 449 | if (!finaljson.results.hasOwnProperty(result.test)) { 450 | finaljson.results[result.test] = []; 451 | } 452 | var run_results = result.runs.map((run) => [run.expected, run.status, run.message]); 453 | finaljson.results[result.test].push([result.subtest].concat(run_results)); 454 | }); 455 | saveData(finaljson, "result.json"); 456 | $scope.busy = false; 457 | $scope.$apply(); 458 | }); 459 | }; 460 | 461 | // http://jsfiddle.net/koldev/cw7w5/ 462 | function saveData(data, fileName) { 463 | var a = document.createElement("a"); 464 | document.body.appendChild(a); 465 | a.style = "display: none"; 466 | var json = JSON.stringify(data), 467 | blob = new Blob([json], {type: "octet/stream"}), 468 | url = window.URL.createObjectURL(blob); 469 | a.href = url; 470 | a.download = fileName; 471 | a.click(); 472 | window.URL.revokeObjectURL(url); 473 | } 474 | 475 | $scope.fillTable = function(page) { 476 | var minTestId = null; 477 | var maxTestId = null; 478 | 479 | $scope.busy = true; 480 | 481 | if (page === "next") { 482 | var minTestId = $scope.resultsView.maxTestId; 483 | } else if (page === "prev") { 484 | var maxTestId = $scope.resultsView.minTestId; 485 | } 486 | 487 | resultsModel.getResults($scope.filter, $scope.runs, minTestId, maxTestId, $scope.resultsView.limit) 488 | .then((results) => { 489 | if (results.length) { 490 | if (!page) { 491 | $scope.resultsView.firstTestId = results[0].test_id; 492 | } 493 | $scope.resultsView.lastPage = results.length < $scope.resultsView.limit; 494 | $scope.resultsView.firstPage = results[0].test_id === $scope.resultsView.firstTestId; 495 | $scope.resultsView.minTestId = results[0].test_id; 496 | $scope.resultsView.maxTestId = results[results.length - 1].test_id; 497 | } else { 498 | // We want to disable NEXT when we are on the last page 499 | $scope.resultsView.lastPage = true; 500 | } 501 | var finalResults = organizeResults(results); 502 | $scope.results = finalResults; 503 | $scope.busy = false; 504 | $scope.$apply(); 505 | }); 506 | }; 507 | 508 | $scope.newFile = function(evt) { 509 | $scope.fileEvent = evt; 510 | $scope.$apply(); 511 | }; 512 | 513 | $scope.addConstraint = function() { 514 | $scope.filter.statusFilter.push({ 515 | run : $scope.runs[0].name, 516 | equality : "is", 517 | status : ["PASS"] 518 | }); 519 | }; 520 | 521 | $scope.deleteConstraint = function() { 522 | $scope.filter.statusFilter.pop(); 523 | }; 524 | 525 | $scope.addOrConstraint = function(index) { 526 | $scope.filter.statusFilter[index].status.push("PASS"); 527 | }; 528 | 529 | $scope.deleteOrConstraint = function(index) { 530 | $scope.filter.statusFilter[index].status.pop(); 531 | }; 532 | 533 | $scope.addPath = function() { 534 | $scope.filter.pathFilter.push({ 535 | choice: "include:start", 536 | path: "" 537 | }); 538 | }; 539 | 540 | $scope.deletePath = function() { 541 | $scope.filter.pathFilter.pop(); 542 | }; 543 | 544 | $scope.showWarnings = function(run) { 545 | $scope.displayWarnings.runName = run.name; 546 | $scope.displayWarnings.warnings = run.warnings; 547 | $scope.displayWarnings.visible = true; 548 | }; 549 | 550 | $scope.showError = function(runResult, resultTest) { 551 | saveComment(); 552 | 553 | $scope.displayError.test = resultTest.test; 554 | $scope.displayError.subtest = resultTest.subtest; 555 | $scope.displayError.expected = runResult.expected; 556 | $scope.displayError.status = runResult.status; 557 | $scope.displayError.error = runResult.message; 558 | $scope.displayError.result_id = runResult.result_id; 559 | 560 | $scope.busy = true; 561 | 562 | resultsModel.getComment(runResult.result_id) 563 | .then((comment) => { 564 | $scope.displayError.comment = comment.length ? comment[0].comment : ""; 565 | $scope.displayError.commentBox = $scope.displayError.comment; 566 | $scope.displayError.visible = true; 567 | 568 | $scope.busy = false; 569 | $scope.$apply(); 570 | }); 571 | }; 572 | 573 | $scope.startCommentSaveTrigger = function() { 574 | $scope.displayError.commentSaveTrigger = $interval(saveComment, 5000); 575 | }; 576 | 577 | $scope.stopCommentSaveTrigger = function() { 578 | saveComment(); 579 | 580 | // Stop periodic calls of the save function 581 | if ($scope.displayError.commentSaveTrigger) { 582 | $interval.cancel($scope.displayError.commentSaveTrigger); 583 | $scope.displayError.commentSaveTrigger = undefined; 584 | } 585 | }; 586 | 587 | function saveComment() { 588 | var comment = $scope.displayError.comment; 589 | var commentNew = $scope.displayError.commentBox; 590 | var resultId = $scope.displayError.result_id; 591 | if ($scope.displayError.visible && comment !== commentNew) { 592 | if (commentNew === "") { 593 | resultsModel.deleteComment(resultId); 594 | } else { 595 | resultsModel.saveComment(resultId, commentNew, comment !== ""); 596 | } 597 | $scope.displayError.comment = commentNew; 598 | } 599 | } 600 | 601 | function organizeResults(results) { 602 | var testMap = {}; 603 | results.forEach(function(result) { 604 | if (result.title === undefined) { 605 | result.title = ""; 606 | } 607 | if (!testMap.hasOwnProperty(result.test)) { 608 | testMap[result.test] = {}; 609 | } 610 | if (!testMap[result.test].hasOwnProperty(result.title)) { 611 | testMap[result.test][result.title] = []; 612 | for (var i = 0; i < $scope.runs.length; i++) { 613 | testMap[result.test][result.title].push({ 614 | 'run_id': $scope.runs[i].run_id, 615 | 'run_name': $scope.runs[i].run_name, 616 | 'enabled': $scope.runs[i].enabled, 617 | 'status': "", 618 | 'expected': "", 619 | 'message': "" 620 | }); 621 | } 622 | } 623 | var x = testMap[result.test][result.title][runIndex[result.run_id]]; 624 | x.status = result.status; 625 | x.expected = result.expected; 626 | x.message = result.message; 627 | x.result_id = result.result_id; 628 | }); 629 | var finalResults = []; 630 | for (var test in testMap) { 631 | for (var subtest in testMap[test]) { 632 | finalResults.push({ 633 | 'test': test, 634 | 'subtest': subtest, 635 | 'runs': testMap[test][subtest] 636 | }); 637 | } 638 | } 639 | return finalResults; 640 | } 641 | }); 642 | -------------------------------------------------------------------------------- /bin/test_travis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e # exit with nonzero exit code if anything fails 3 | echo $TRAVIS_PULL_REQUEST 4 | if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then 5 | exit 0 6 | fi 7 | echo "Pushing to gh-pages.." 8 | git push --quiet "https://${GH_TOKEN}@${GH_REF}" master:gh-pages > /dev/null 2>&1 9 | echo "Finished pushing to gh-pages.." -------------------------------------------------------------------------------- /edit_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test Results Viewer 4 | 5 |
6 | 7 | 8 | 9 |
10 |

Test Results Viewer

11 | 12 | 13 |
14 |

Log Files

15 |

16 | Loading... 17 |

18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 39 | 40 | 41 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
Run NameResults Loaded
33 |
34 | 35 | 36 | {{ run.name }} 37 |
38 |
{{ run.count }} 42 | 43 |
52 |
53 | 54 |
55 |
56 | 57 | 58 | Import 59 |
60 |
61 |
62 | Upload File
63 |
64 | Fetch from URL
65 | 66 |

67 | 68 | 69 |
70 |
71 |
72 |
73 |
74 | 75 |
76 |

Filter

77 |
78 | Parent 79 | Child 80 | Both 81 |
82 |

83 |

86 | 87 | 88 |
89 |
90 | 98 | 99 |
100 |

101 |

102 |

105 | 106 | 107 |
108 |
109 | 113 | 117 | 118 | 119 | 130 | 131 | 132 |
133 |
134 | 135 |
136 |

Results

137 |
138 | Show results. 139 | 140 |
141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 |
TestSubtest{{ run.name }}
ExpectedActual
{{ result.test }}{{ result.subtest }}{{ run.expected }}{{ run.status }}
158 | 159 | 160 |
161 |
162 |
163 |

Duplicate tests in {{displayWarnings.runName}}

164 |
    165 |
  1. 166 |
    Test : {{ warning.test }}
    167 |
    Subtest : {{ warning.subtest }}
    168 |
  2. 169 |
170 |
171 |
172 |
173 |
Test : {{ displayError.test }}
174 |
Subtest : {{ displayError.subtest }}
175 |
Expected : {{ displayError.expected }} 176 | Got : {{ displayError.status }}
177 |

Message : {{ displayError.error }}

178 |
179 |
180 | Comments : 181 | 184 |
185 |
186 |
187 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /logcruncher.js: -------------------------------------------------------------------------------- 1 | importScripts("workerApi.js"); 2 | 3 | onmessage = messageAdapter(new LogReader()); 4 | 5 | function LogReader() {} 6 | 7 | LogReader.prototype.read = read(readFile); 8 | LogReader.prototype.readURL = read(readURL); 9 | 10 | function read(reader) { 11 | return (data) => { 12 | return reader(data) 13 | .then((logData) => {return getLogType(logData)();}); 14 | }; 15 | } 16 | 17 | function getLogType(logData) { 18 | var parsed = null; 19 | var mozlogParser = () => parseMozlog(logData, testsFilter); 20 | try { 21 | parsed = JSON.parse(logData); 22 | } catch (e) { 23 | return mozlogParser; 24 | } 25 | if (!parsed.hasOwnProperty("results")) { 26 | return mozlogParser; 27 | } 28 | return () => parseRunnerJSON(parsed); 29 | } 30 | 31 | function parseMozlog(rawtext, filter) { 32 | return new Promise(function (resolve, reject) { 33 | var JSONArray = []; 34 | var lines = rawtext.split('\n'); 35 | for (var i = 0; i < lines.length; i++) { 36 | if (lines[i] === "") { 37 | continue; 38 | } 39 | var json = JSON.parse(lines[i]); 40 | if (filter(json)) { 41 | JSONArray.push(json); 42 | } 43 | } 44 | resolve(JSONArray); 45 | }); 46 | } 47 | 48 | function parseRunnerJSON(parsedJson) { 49 | return new Promise((resolve, reject) => { 50 | var JSONArray = []; 51 | parsedJson.results.forEach((result) => { 52 | JSONArray.push({"action": "test_start", 53 | "test": result.test}); 54 | result.subtests.forEach((subtest) => { 55 | JSONArray.push({"action": "test_status", 56 | "test": result.test, 57 | "subtest": subtest.name, 58 | "status": subtest.status, 59 | "message": subtest.message}); 60 | }); 61 | JSONArray.push({"action": "test_end", 62 | "test": result.test, 63 | "status": result.status, 64 | "message": result.message}); 65 | }); 66 | resolve(JSONArray); 67 | }); 68 | } 69 | 70 | function testsFilter(parsedLine) { 71 | var pattr = /^test_/; 72 | var pattr2 = /^(?:error)|(?:critical)/i; 73 | return (pattr.test(parsedLine.action) || (parsedLine.action === "log" && pattr2.test(parsedLine.level))); 74 | } 75 | 76 | function readFile(file) { 77 | return new Promise(function(resolve, reject) { 78 | var reader = new FileReaderSync(); 79 | resolve(reader.readAsText(file, "UTF-8")); 80 | }); 81 | } 82 | 83 | function readURL(url) { 84 | return new Promise(function(resolve, reject) { 85 | var xhttp = new XMLHttpRequest(); 86 | xhttp.onreadystatechange = function() { 87 | if (xhttp.readyState === 4) { 88 | if (xhttp.status === 200) { 89 | resolve(xhttp.responseText); 90 | } else { 91 | reject("HTTP request failed!"); 92 | } 93 | } 94 | }; 95 | xhttp.open('GET', url, false); 96 | xhttp.send(); 97 | }); 98 | } 99 | -------------------------------------------------------------------------------- /spinner/angular-spinner.min.js: -------------------------------------------------------------------------------- 1 | "format amd";!function(a){"use strict";function b(a,b){return a.module("angularSpinner",[]).constant("SpinJSSpinner",b).provider("usSpinnerConfig",function(){var a={},b={};return{setDefaults:function(b){a=b||a},setTheme:function(a,c){b[a]=c},$get:function(){return{config:a,themes:b}}}}).factory("usSpinnerService",["$rootScope",function(a){var b={};return b.spin=function(b){a.$broadcast("us-spinner:spin",b)},b.stop=function(b){a.$broadcast("us-spinner:stop",b)},b}]).directive("usSpinner",["SpinJSSpinner","usSpinnerConfig",function(b,c){return{scope:!0,link:function(d,e,f){function g(){d.spinner&&d.spinner.stop()}d.spinner=null,d.key=a.isDefined(f.spinnerKey)?f.spinnerKey:!1,d.startActive=a.isDefined(f.spinnerStartActive)?d.$eval(f.spinnerStartActive):d.key?!1:!0,d.spin=function(){d.spinner&&d.spinner.spin(e[0])},d.stop=function(){d.startActive=!1,g()},d.$watch(f.usSpinner,function(h){g(),h=a.extend(c.config,c.themes[f.spinnerTheme],h),d.spinner=new b(h),d.key&&!d.startActive||f.spinnerOn||d.spinner.spin(e[0])},!0),f.spinnerOn&&d.$watch(f.spinnerOn,function(a){a?d.spin():d.stop()}),d.$on("us-spinner:spin",function(a,b){b===d.key&&d.spin()}),d.$on("us-spinner:stop",function(a,b){b===d.key&&d.stop()}),d.$on("$destroy",function(){d.stop(),d.spinner=null})}}}])}"object"==typeof module&&module.exports?module.exports=b(require("angular"),require("spin.js")):"function"==typeof define&&define.amd?define(["angular","spin"],b):b(a.angular,a.Spinner)}(this); 2 | //# sourceMappingURL=angular-spinner.min.js.map -------------------------------------------------------------------------------- /spinner/spin.min.js: -------------------------------------------------------------------------------- 1 | // http://spin.js.org/#v2.3.2 2 | !function(a,b){"object"==typeof module&&module.exports?module.exports=b():"function"==typeof define&&define.amd?define(b):a.Spinner=b()}(this,function(){"use strict";function a(a,b){var c,d=document.createElement(a||"div");for(c in b)d[c]=b[c];return d}function b(a){for(var b=1,c=arguments.length;c>b;b++)a.appendChild(arguments[b]);return a}function c(a,b,c,d){var e=["opacity",b,~~(100*a),c,d].join("-"),f=.01+c/d*100,g=Math.max(1-(1-a)/b*(100-f),a),h=j.substring(0,j.indexOf("Animation")).toLowerCase(),i=h&&"-"+h+"-"||"";return m[e]||(k.insertRule("@"+i+"keyframes "+e+"{0%{opacity:"+g+"}"+f+"%{opacity:"+a+"}"+(f+.01)+"%{opacity:1}"+(f+b)%100+"%{opacity:"+a+"}100%{opacity:"+g+"}}",k.cssRules.length),m[e]=1),e}function d(a,b){var c,d,e=a.style;if(b=b.charAt(0).toUpperCase()+b.slice(1),void 0!==e[b])return b;for(d=0;d',c)}k.addRule(".spin-vml","behavior:url(#default#VML)"),h.prototype.lines=function(a,d){function f(){return e(c("group",{coordsize:k+" "+k,coordorigin:-j+" "+-j}),{width:k,height:k})}function h(a,h,i){b(m,b(e(f(),{rotation:360/d.lines*a+"deg",left:~~h}),b(e(c("roundrect",{arcsize:d.corners}),{width:j,height:d.scale*d.width,left:d.scale*d.radius,top:-d.scale*d.width>>1,filter:i}),c("fill",{color:g(d.color,a),opacity:d.opacity}),c("stroke",{opacity:0}))))}var i,j=d.scale*(d.length+d.width),k=2*d.scale*j,l=-(d.width+d.length)*d.scale*2+"px",m=e(f(),{position:"absolute",top:l,left:l});if(d.shadow)for(i=1;i<=d.lines;i++)h(i,-2,"progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)");for(i=1;i<=d.lines;i++)h(i);return b(a,m)},h.prototype.opacity=function(a,b,c,d){var e=a.firstChild;d=d.shadow&&d.lines||0,e&&b+d>1)+"px"})}for(var i,k=0,l=(f.lines-1)*(1-f.direction)/2;k 2 | -------------------------------------------------------------------------------- /workerApi.js: -------------------------------------------------------------------------------- 1 | function messageAdapter(service) { 2 | return function(event) { 3 | var msg_id = event.data[0]; 4 | var cmd = event.data[1]; 5 | var data = event.data[2]; 6 | 7 | console.log(service.constructor.name + " got command " + cmd); 8 | 9 | service[cmd].apply(service, data) 10 | .then((resp) => {console.log("Got result for command " + cmd); return resp;}) 11 | .then((resp) => postMessage([msg_id, resp])); 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /wptview.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Fira Sans", "DejaVu Sans", "Bitstream Vera Sans", "Sans"; 3 | font-size: 14px; 4 | padding: 0; 5 | margin: 0; 6 | background-color: #f8f8f8; 7 | height: 100vh; 8 | } 9 | 10 | #wptview { 11 | display: flex; 12 | flex-direction: column; 13 | height: 100vh; 14 | overflow: hidden; 15 | } 16 | 17 | .page { 18 | position: relative; 19 | display: inline-block; 20 | width: 97vw; 21 | flex: 1; 22 | overflow-y: scroll; 23 | 24 | padding-left: 1.5vw; 25 | padding-right: 1.5vw; 26 | } 27 | 28 | h1 { 29 | margin-top:0; 30 | box-sizing: border-box; 31 | width: 100%; 32 | padding: 0.5em; 33 | background-color: #e7ebf1; 34 | } 35 | 36 | #message { 37 | font-family: "Fira Sans Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Monospace"; 38 | } 39 | 40 | .detailsPanel { 41 | position: relative; 42 | height: 15vh; 43 | background-color: #FFFFFF; 44 | border-top: solid black; 45 | padding: 1%; 46 | overflow: auto; 47 | 48 | flex: none; 49 | } 50 | 51 | .detailsPanel li { 52 | margin-top: 0.8em; 53 | } 54 | 55 | #runsData { 56 | display: flex; 57 | } 58 | 59 | #runsData > div { 60 | padding-right: 16px; 61 | } 62 | 63 | #progressBar { 64 | width: 40%; 65 | height: 20px; 66 | margin-left: 30%; 67 | margin-right: 30%; 68 | top: 60%; 69 | position: absolute; 70 | } 71 | 72 | table { 73 | border-collapse: collapse; 74 | } 75 | 76 | th, td { 77 | padding: 5px; 78 | text-align: center; 79 | } 80 | 81 | 82 | th { 83 | background-color: #e7ebf1; 84 | } 85 | 86 | tbody > tr > td { 87 | border-bottom: thin solid black; 88 | } 89 | 90 | .details-legend { 91 | margin-bottom:5px; 92 | cursor: pointer; 93 | } 94 | 95 | #resultsTable > tbody > tr:hover { 96 | background-color: #e7ebf1; 97 | } 98 | 99 | #resultsTable > tbody > tr > td:first-child { 100 | font-family: "Fira Sans Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", Monospace; 101 | font-size: 10px; 102 | text-align:left; 103 | } 104 | 105 | #resultsTable > tbody > tr > td:nth-child(2) { 106 | text-align:left; 107 | } 108 | 109 | .expected, .result { 110 | width: 5em; 111 | } 112 | 113 | .result { 114 | cursor: pointer; 115 | } 116 | 117 | .PASS, .OK { 118 | background-color: green; 119 | color: white; 120 | } 121 | 122 | .FAIL { 123 | background-color: red; 124 | color: white; 125 | } 126 | 127 | .SKIP { 128 | background-color: blue; 129 | color: white; 130 | } 131 | 132 | .ERROR { 133 | background-color: orange; 134 | color: white; 135 | } 136 | 137 | .TIMEOUT, .NOTRUN { 138 | background-color: purple; 139 | color: white; 140 | } 141 | 142 | .CRASH { 143 | background-color: black; 144 | color: white; 145 | } 146 | --------------------------------------------------------------------------------