From d44fa327d871794f08327f771659ab84e179085f Mon Sep 17 00:00:00 2001 From: Jonathan Olson Date: Wed, 15 Apr 2020 18:00:46 -0600 Subject: [PATCH] Restructuring experimental CT interface to using modules, see https://github.com/phetsims/aqua/issues/88 --- html/local-report.html | 5 +- js/aqua-main.js | 3 + js/{local-report.js => report/report.js} | 0 js/report/reportModules.js | 162 ++++++++++++++++++ js/{ => server}/Snapshot.js | 12 +- js/{ => server}/Test.js | 0 js/{ => server}/TestResult.js | 0 .../continuous-server.js} | 18 +- 8 files changed, 183 insertions(+), 17 deletions(-) create mode 100644 js/aqua-main.js rename js/{local-report.js => report/report.js} (100%) create mode 100644 js/report/reportModules.js rename js/{ => server}/Snapshot.js (85%) rename js/{ => server}/Test.js (100%) rename js/{ => server}/TestResult.js (100%) rename js/{local-server.js => server/continuous-server.js} (94%) diff --git a/html/local-report.html b/html/local-report.html index 4240927..72b7f12 100644 --- a/html/local-report.html +++ b/html/local-report.html @@ -19,7 +19,8 @@ - - + + + diff --git a/js/aqua-main.js b/js/aqua-main.js new file mode 100644 index 0000000..7998ea1 --- /dev/null +++ b/js/aqua-main.js @@ -0,0 +1,3 @@ +// Copyright 2020, University of Colorado Boulder + +import './report/reportModules.js'; diff --git a/js/local-report.js b/js/report/report.js similarity index 100% rename from js/local-report.js rename to js/report/report.js diff --git a/js/report/reportModules.js b/js/report/reportModules.js new file mode 100644 index 0000000..e71c966 --- /dev/null +++ b/js/report/reportModules.js @@ -0,0 +1,162 @@ +// Copyright 2020, University of Colorado Boulder + +/** + * Displays a self-updating report of continuous test results + * + * @author Jonathan Olson + */ + +import Property from '../../../axon/js/Property.js'; +import Display from '../../../scenery/js/display/Display.js'; +import Node from '../../../scenery/js/nodes/Node.js'; +import Rectangle from '../../../scenery/js/nodes/Rectangle.js'; +import Text from '../../../scenery/js/nodes/Text.js'; +import VBox from '../../../scenery/js/nodes/VBox.js'; +import Color from '../../../scenery/js/util/Color.js'; + +window.assertions.enableAssert(); + +const options = QueryStringMachine.getAll( { + server: { + type: 'string', + + // Origin for our server (ignoring current port), so that we don't require localhost + defaultValue: window.location.protocol + '//' + window.location.hostname + } +} ); + +const passColor = new Color( 60, 255, 60 ); +const failColor = new Color( 255, 90, 90 ); +const mixedColor = new Color( 255,210,80 ); +const unincludedColor = new Color( 128, 128, 128 ); +const untestedColor = new Color( 240, 240, 240 ); + +// Property. +const snapshotStatusProperty = new Property( 'unknown status' ); + +snapshotStatusProperty.lazyLink( status => console.log( `Status: ${status}` ) ); + +(function snapshotStatusLoop() { + const req = new XMLHttpRequest(); + req.onload = function() { + setTimeout( snapshotStatusLoop, 1000 ); + snapshotStatusProperty.value = JSON.parse( req.responseText ).status; + }; + req.onerror = function() { + setTimeout( snapshotStatusLoop, 1000 ); + snapshotStatusProperty.value = 'Could not contact server'; + }; + req.open( 'get', options.server + '/aquaserver/snapshot-status', true ); + req.send(); +})(); + +// Property. +const reportProperty = new Property( { + snapshots: [], + testNames: [] +} ); + +(function reportLoop() { + const req = new XMLHttpRequest(); + req.onload = function() { + setTimeout( reportLoop, 20000 ); + reportProperty.value = JSON.parse( req.responseText ); + }; + req.onerror = function() { + setTimeout( reportLoop, 20000 ); + reportProperty.reset(); + }; + req.open( 'get', options.server + '/aquaserver/report', true ); + req.send(); +})(); + +const rootNode = new Node(); +const display = new Display( rootNode, { + passiveEvents: true +} ); + +document.body.appendChild( display.domElement ); + +const statusNode = new Text( '', { fontSize: 14 } ); +snapshotStatusProperty.link( status => { + statusNode.text = status; +} ); + +const reportNode = new Node(); + +rootNode.addChild( new VBox( { + spacing: 10, + align: 'left', + children: [ statusNode, reportNode ] +} ) ); + +reportProperty.link( report => { + const testLabels = report.testNames.map( names => new Text( names.join( ' : ' ), { fontSize: 12 } ) ); + + const padding = 3; + + const snapshotLabels = report.snapshots.map( snapshot => new VBox( { + spacing: 2, + children: [ + ...new Date( snapshot.timestamp ).toLocaleString().replace( ',', '' ).replace( ' AM', 'am' ).replace( ' PM', 'pm' ).split( ' ' ).map( str => new Text( str, { fontSize: 10 } ) ) + ], + cursor: 'pointer' + } ) ); + + const maxTestLabelWidth = _.max( testLabels.map( node => node.width ) ); + const maxTestLabelHeight = _.max( testLabels.map( node => node.height ) ); + const maxSnapshotLabelWidth = _.max( snapshotLabels.map( node => node.width ) ); + const maxSnapshotLabelHeight = _.max( snapshotLabels.map( node => node.height ) ); + + const snapshotsTestNodes = _.flatten( report.snapshots.map( ( snapshot, i ) => { + return report.testNames.map( ( names, j ) => { + const test = _.find( snapshot.tests, test => _.isEqual( names, test.names ) ); + + const background = new Rectangle( 0, 0, maxSnapshotLabelWidth, maxTestLabelHeight, { + x: maxTestLabelWidth + padding + i * ( maxSnapshotLabelWidth + padding ), + y: maxSnapshotLabelHeight + padding + j * ( maxTestLabelHeight + padding ) + } ); + + if ( test ) { + if ( test.passCount > 0 && test.failCount === 0 ) { + background.fill = passColor; + } + else if ( test.passCount === 0 && test.failCount > 0 ) { + background.fill = failColor; + } + else if ( test.passCount === 0 && test.failCount === 0 ) { + background.fill = untestedColor; + } + else { + background.fill = mixedColor; + } + } + else { + background.fill = unincludedColor; + } + + return background; + } ); + } ) ); + + testLabels.forEach( ( label, i ) => { + label.left = 0; + label.top = i * ( maxTestLabelHeight + padding ) + maxSnapshotLabelHeight + padding; + } ); + snapshotLabels.forEach( ( label, i ) => { + label.top = 0; + label.left = ( maxTestLabelWidth + padding ) + i * ( maxSnapshotLabelWidth + padding ); + } ); + + reportNode.children = [ + ...testLabels, + ...snapshotLabels, + ...snapshotsTestNodes + ]; +} ); + +display.initializeEvents(); +display.updateOnRequestAnimationFrame( dt => { + display.width = Math.ceil( rootNode.width ); + display.height = Math.ceil( rootNode.height ); +} ); diff --git a/js/Snapshot.js b/js/server/Snapshot.js similarity index 85% rename from js/Snapshot.js rename to js/server/Snapshot.js index b680158..717f35b 100644 --- a/js/Snapshot.js +++ b/js/server/Snapshot.js @@ -8,12 +8,12 @@ 'use strict'; -const copyDirectory = require( '../../perennial/js/common/copyDirectory' ); -const createDirectory = require( '../../perennial/js/common/createDirectory' ); -const deleteDirectory = require( '../../perennial/js/common/deleteDirectory' ); -const execute = require( '../../perennial/js/common/execute' ); -const getRepoList = require( '../../perennial/js/common/getRepoList' ); -const gitRevParse = require( '../../perennial/js/common/gitRevParse' ); +const copyDirectory = require( '../../../perennial/js/common/copyDirectory' ); +const createDirectory = require( '../../../perennial/js/common/createDirectory' ); +const deleteDirectory = require( '../../../perennial/js/common/deleteDirectory' ); +const execute = require( '../../../perennial/js/common/execute' ); +const getRepoList = require( '../../../perennial/js/common/getRepoList' ); +const gitRevParse = require( '../../../perennial/js/common/gitRevParse' ); const Test = require( './Test' ); const fs = require( 'fs' ); const _ = require( 'lodash' ); // eslint-disable-line diff --git a/js/Test.js b/js/server/Test.js similarity index 100% rename from js/Test.js rename to js/server/Test.js diff --git a/js/TestResult.js b/js/server/TestResult.js similarity index 100% rename from js/TestResult.js rename to js/server/TestResult.js diff --git a/js/local-server.js b/js/server/continuous-server.js similarity index 94% rename from js/local-server.js rename to js/server/continuous-server.js index 56e2b72..ea8d3fb 100644 --- a/js/local-server.js +++ b/js/server/continuous-server.js @@ -6,15 +6,15 @@ 'use strict'; -const asyncFilter = require( '../../perennial/js/common/asyncFilter' ); -const cloneMissingRepos = require( '../../perennial/js/common/cloneMissingRepos' ); -const execute = require( '../../perennial/js/common/execute' ); -const getRepoList = require( '../../perennial/js/common/getRepoList' ); -const gitPull = require( '../../perennial/js/common/gitPull' ); -const gruntCommand = require( '../../perennial/js/common/gruntCommand' ); -const isStale = require( '../../perennial/js/common/isStale' ); -const npmUpdate = require( '../../perennial/js/common/npmUpdate' ); -const sleep = require( '../../perennial/js/common/sleep' ); +const asyncFilter = require( '../../../perennial/js/common/asyncFilter' ); +const cloneMissingRepos = require( '../../../perennial/js/common/cloneMissingRepos' ); +const execute = require( '../../../perennial/js/common/execute' ); +const getRepoList = require( '../../../perennial/js/common/getRepoList' ); +const gitPull = require( '../../../perennial/js/common/gitPull' ); +const gruntCommand = require( '../../../perennial/js/common/gruntCommand' ); +const isStale = require( '../../../perennial/js/common/isStale' ); +const npmUpdate = require( '../../../perennial/js/common/npmUpdate' ); +const sleep = require( '../../../perennial/js/common/sleep' ); const Snapshot = require( './Snapshot' ); const fs = require( 'fs' ); const http = require( 'http' );