Compare commits

..

28 Commits

Author SHA1 Message Date
Andrew Henry
5e1d558cf7 Finished observers 2022-07-25 10:43:49 -07:00
Jesse Mazzella
488cd82ae1 Uppercase-X checkboxes are also valid (#5527) 2022-07-18 18:38:12 +00:00
Jesse Mazzella
d85be3b88e Remove exampleImagery suite from CI (#5529) 2022-07-18 11:01:49 -07:00
John Hill
4d48cf3180 Add TBD Readme (#5514)
* Add TBD Readme

Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
2022-07-16 12:21:40 -07:00
Jesse Mazzella
413cb13edf [e2e] Fix flaky exampleImagery zoom tests (#5517)
* extract zoom logic to functions and update test

* Refactor other tests to use new button zoom fns

* e2e:ci ++exampleImagery

* try polling

* try wait for animations

* code review comments

* fix syntax
2022-07-15 16:39:55 -07:00
John Hill
70115be727 Removing flaky test from CI runs (#5511)
* Removing flaky test from CI runs

* Update package.json

* Update package.json

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2022-07-15 11:49:58 -07:00
John Hill
97f5528dfc Last of the post-2.0.6 band-aid removal (#5512)
* back out changes

* Ensure that coverage is generated in all tests
2022-07-15 08:58:03 -07:00
Jesse Mazzella
0e1cc5dc30 Upgrade to karma-jasmine 5.1.0 and fix unit tests (#5503) 2022-07-14 17:16:50 -07:00
John Hill
0062191416 Update moment (#5509)
Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2022-07-14 22:36:42 +00:00
Jesse Mazzella
eedc523078 Use webpack.dev.js if NODE_ENV is not set (#5498) 2022-07-14 15:21:17 -07:00
John Hill
db97acb61e 2.0.5 Backmerge (#5505)
* Bump d3-selection from 1.3.2 to 3.0.0

Bumps [d3-selection](https://github.com/d3/d3-selection) from 1.3.2 to 3.0.0.
- [Release notes](https://github.com/d3/d3-selection/releases)
- [Commits](https://github.com/d3/d3-selection/compare/v1.3.2...v3.0.0)

---
updated-dependencies:
- dependency-name: d3-selection
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Remove snapshot

* Fix imagery filter slider drag in flexible layouts (#5326) (#5350)

* Dont' mutate a stacked plot unless its user initiated (#5357)

* Port grid icons and imagery test to release 2.0.5 from master (#5360)

* Port grid icons to release 2.0.5 from master

* Port imagery test to release/2.0.5

* Restrict timestrip composition to time based plots, plans and imagery (#5161)

* Restrict timestrip composition to time based plots, plans and imagery

* Adds unit tests for timeline composition policy

* Addresses review comments
Improves tests

* Reuse test objects

Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>

* Include objectStyles reference to conditionSetIdentifier in imports (#5354)

* Include objectStyles reference to conditionSetIdentifier in imports

* Add tests for export

* Refactored some code and removed console log

* Remove workarounds for chrome 'scrollTop' issue (#5375)

* Fix naming of method (#5368)

* Imagery View does not discard old images when they fall out of bounds (#5351)

* change to using telemetry collection

* fix tests

* added more unit tests

* Cherrypicked commits (#5390)

Co-authored-by: unlikelyzero <jchill2@gmail.com>

* [Timer] Update 3dot menu actions appropriately (#5387)

* Call `removeAllListeners()` after emit

* Manually show/hide actions if within a view

* remove sneaky `console.log()`

* Add Timer e2e test

* Add to comments

* Avoid hard waits in Timer e2e test

- Assert against timer view state instead of menu options

* Let's also test actions from the Timer view

* 5391 Add preview and drag support to Grand Search (#5394)

* add preview and drag actions

* added unit test, simplified remove action

* do not hide search results in preview mode when clicking outside search results

* add semantic aria labels to enable e2e tests

* readd preview

* add e2e test

* remove commented out url

* add percy snapshot and add search to ci

* make percy stuff work

* linting

* fix percy again

* move percy snapshots to a visual test

* added separate visual test and changed test to fixtures

* fix fixtures path

* addressing review comments

* 5361 tags not persisting locally (#5408)

* fixed typo

* remove unneeded lookup

* fix tags adding and deleting

* more reliable way to remove tags

* break tests up for parallel execution

* fixed notebook tagging test

* enable e2e tests

* made schedule index comment more clear and fix uppercase/lowercase issue

* address e2e changes

* add unit test to bump coverage

* fix typo

* need to check on annotation creation if provider exists or not

* added fixtures

* undo silly couchdb commit

* Plot progress bar fix for 2.0.5 (#5386)

* Add .bind(this) to stopLoading() in loadMoreData()

* Replace load spinner with progress bar for plots

* Add loading delay prop to swg

* fix linting errors

* match load order

* Update accessibility

* Add Math.max to timeout to handle negative inputs

* Moved math.max to load delay variable

* Add loading fix for stacked plots

* Move loadingUpdate func into plot item for update

* Merge conflict resolve

* Check if delay is 0 and send, put post in a func

* Put obj directly to model, removed computed prop

* Lint fix

* Fix template where legend was not displayed

* Remove commented out template

* Fixed failing test

Co-authored-by: unlikelyzero <jchill2@gmail.com>

* Make plans non editable. (#5377)

* Make plans non editable.

* Add unit test for fix

* [CouchDB] Better determination of indicator status (#5415)

* Add unknown state, remove maintenance state

* Handle all CouchDB status codes

- Set unknown status if we receive an unhandled code

* Include status code in error messages

* SharedWorker can send unknown status

* Add test for unknown status

* Gauge fixes for Firefox and units display (#5369)

* Closes #5323, #5325. Parent branch is release/2.0.5.
- Significant work refactoring SVG markup and CSS for dial gauge;
- Fixed missing `v-if` to control display of units for #5325;
- Fixed bad `.length` test for limit properties;

* Closes #5323, #5325
- Add 'value out of range' indicator

* Closes #5323, #5325
- More accurate element naming;
- Fix cross-browser problems with current value display in dial gauge;
- Refinements to "out of range" indicator approach;
- Fixed size of "Amplitude" input in Sine Wave Generator;

* Closes #5323, #5325
- Styles and stubbed in code to support needle meter type;

* Closes #5323, #5325
- Stubbed in markup and CSS for needle-style meter;

* Closes #5323, #5325
- Fixed missing `js-*` classes that were failing npm run test;

* Closes #5323, #5325
- Fix to not display meter value bar unless a data value is expected;

* Addressing PR comments
- Renamed method for clarity;
- Added null value check in method `valueExpected`;

* [Static Root] Return leafValue if null/undefined/false (#5416)

* Return leafValue if null/undefined/false

* Added a null to the test json

* Show a better default poll question (#5425)

* 5361 Tags not persisting when several notebook entries are created at once (#5428)

* add end to end test to catch multiple entry errors

* click expansion triangle instead

* fix race condition between annotation creation and mutation

* make sure notebook tags run in e2e

* address PR comments

* Handle missing objects gracefully  (#5399)

* Handle missing object errors for display layouts
* Handle missing object errors for Overlay Plots
* Add check for this.config
* Add try/catch statement & check if obj is missing
* Changed console.error to console.warn
* Lint fix
* Fix for this.metadata.value is undefined
* Add e2e test
* Update comment text
* Add reload check and @private, verify console.warn
* Redid assignment and metadata check
* Fix typo
* Changed assignment and metadata check
* Redid checks for isMissing(object)
* Lint fix

* Backmerge e2e code coverage changes and fixes into release/2.0.5 (#5431)

* [Telemetry Collections] Respect "Latest" Strategy Option (#5421)

* Respect latest strategy in Telemetry Collections to limit potential memory growth.

* fix sourcemaps (#5373)

Co-authored-by: John Hill <john.c.hill@nasa.gov>

* Debounce status summary (#5448)

Co-authored-by: John Hill <john.c.hill@nasa.gov>

* No gauge (#5451)

* Installed gauge plugin by default
* Make gauge part of standard install in e2e suite and add restrictednotebook

Co-authored-by: Andrew Henry <akhenry@gmail.com>

* [CouchDB] Always subscribe to the CouchDB changes feed (#5434)

* Add unknown state, remove maintenance state

* Handle all CouchDB status codes

- Set unknown status if we receive an unhandled code

* Include status code in error messages

* SharedWorker can send unknown status

* Add test for unknown status

* Always subscribe to CouchDB changes feed

- Always subscribe to the CouchDB changes feed, even if there are no observable objects, since we are also checking the status of CouchDB via this feed.

* Update indicator status if not using SharedWorker

* Start listening to changes feed on first request

* fix test

* adjust test to hopefully avoid race condition

* lint

Co-authored-by: John Hill <john.c.hill@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
Co-authored-by: Scott Bell <scott@traclabs.com>

* Fix for Fault Management Visual Bugs  (#5376)

* Closes #5365
* General visual improvements

Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>

* fix pathing (#5452)

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>

* [Static Root] Static Root Plugin not loading (#5455)

* Log if hitting falsy leafValue

* Add some logging

* Remove logs and specify null/undefined

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>

* Allow endpoints with a single enum metadata value in Bar/Line graphs (#5443)

* If there is only 1 metadata value, set yKey to none. Also, fix bug for determining the name of a metadata value
* Update tests for enum metadata values

Co-authored-by: John Hill <john.c.hill@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>

* [Remote Clock] Wait for first tick and recalculate historical request bounds (#5433)

* Updated to ES6 class
* added request intercept functionality to telemetry api, added a request interceptor for remote clock
* add remoteClock e2e test stub

Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>

* Fix for missing object for LADTableSet (#5458)

* Handle missing object errors for display layouts

Co-authored-by: Andrew Henry <akhenry@gmail.com>

* removing the call for default import now that TelemetryAPI is an ES6 class (#5461)

* [Remote Clock] Fix requestInterceptor typo (#5462)

* Fix typo in telemetry request interceptor

Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>

* Lock model (#5457)

* Lock event Model to prevent reactification

* de-reactify all the things

* Make API properties writable to allow test mocks to override them

* Fix merge conflict

* Added plot interceptor for missing series config (#5422)

Co-authored-by: Andrew Henry <akhenry@gmail.com>
Co-authored-by: Shefali Joshi <simplyrender@gmail.com>

* Remove performance marks (#5465)

* Remove performance marks

* Retain performance mark in view large. It doesn't happen very often and it's needed for an automated performance test

* Use timeKey for time comparison (#5471)

* Fix couchdb no response (#5474)

* Update the creation date only when the document is created for the first time

* If there is no response from a bulk get, couch db has issues

* Check the response - if it's null, don't apply interceptors

* Fix shelved alarms (#5479)

* Fix the logic around shelved alarms

* Remove application router listener

* Release 2.0.5 UI and Gauge fixes (#5470)

* Various UI fixes
- Tweak to Gauge properties form for clarity and usability.
- Fix Gauge 'dial' type not obeying "Show units" property setting, closes #5325.
- Tweaks to Operator Status UI label and layout for clarity.
- Changed name and description of Graph object for clarity and consistency.
- Fixed CSS classing that was coloring Export menu items text incorrectly.
- Fixed icon-to-text vertical alignment in `.c-object-label`.
- Fix for broken layout in imagery local controls (brightness, layers, magnification).

Co-authored-by: Andrew Henry <akhenry@gmail.com>

* Stacked plot interceptor rename (#5468)

* Rename stacked plot interceptor and move to folder

Co-authored-by: Andrew Henry <akhenry@gmail.com>

* Clear data when time bounds are changed (#5482)

* Clear data when time bounds are changed
Also react to clear data action
Ensure that the yKey is set to 'none' if there is no range with array Values

* Refactor trace updates to a method

* get rid of root (#5483)

* Do not pass onPartialResponse option on to upstream telemetry (#5486)

* Fix all of the e2e tests (#5477)

* Fix timer test

* be explicit about the warnings text

* add full suite to CI to enable CircleCI Checks

* add back in devtool=false for CI env so firefox tests run

* add framework suite

* Don't install webpack HMR in CI

* Fix playwright version installs

* exclude HMR if running tests in any environment

- use NODE_ENV=TEST to exclude webpack HMR

- deparameterize some of the playwright configs

* use lower-case 'test'

* timer hover fix

* conditionally skip for firefox due to missing console events

* increase timeouts to give time for mutation

* no need to close save banner

* remove devtool setting

* revert

* update snapshots

* disable video to save some resources

* use one worker

* more timeouts :)

* Remove `browser.close()` and `page.close()` as it was breaking other tests

* Remove unnecessary awaits and fix func call syntax

* Fix image reset test

* fix restrictedNotebook tests

* revert playwright-ci.config settings

* increase timeout for polling imagery test

* remove unnecessary waits

* disable notebook lock test for chrome-beta as its unreliable

- remove some unnecessary 'wait for save banner' logic

- remove unused await

- mark imagery test as slow in chrome-beta

* LINT!! *shakes fist*

* don't run full e2e suite per commit

* disable video in all configs

* add flakey zoom comment

* exclude webpack HMR in non-development modes

Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>

* lint fix

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Joshi <simplyrender@gmail.com>
Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
Co-authored-by: Scott Bell <scott@traclabs.com>
Co-authored-by: unlikelyzero <jchill2@gmail.com>
Co-authored-by: Alize Nguyen <alizenguyen@gmail.com>
Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com>
Co-authored-by: Khalid Adil <khalidadil29@gmail.com>
Co-authored-by: rukmini-bose <48999852+rukmini-bose@users.noreply.github.com>
Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
2022-07-14 14:52:28 -07:00
John Hill
43a4bf9606 Update circleCI to run more frequently during Build 6 (#5427)
* Update circleCI to run more frequently during Build 6

* Updated config.yml
2022-07-03 10:31:08 -07:00
John Hill
0f352087f5 e2e Code Coverage and all test fixes (#5328)
Add e2e code coverage and fix all tests
2022-06-30 11:50:47 -07:00
dependabot[bot]
8ce15521de Bump painterro from 1.2.56 to 1.2.78 (#5195)
Bumps [painterro](https://github.com/devforth/painterro) from 1.2.56 to 1.2.78.
- [Release notes](https://github.com/devforth/painterro/releases)
- [Changelog](https://github.com/devforth/painterro/blob/master/Release.md)
- [Commits](https://github.com/devforth/painterro/compare/v1.2.56...v1.2.78)

---
updated-dependencies:
- dependency-name: painterro
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-30 08:52:11 -07:00
dependabot[bot]
c0b0c44dc2 Bump babel-loader from 8.2.3 to 8.2.5 (#5266)
Bumps [babel-loader](https://github.com/babel/babel-loader) from 8.2.3 to 8.2.5.
- [Release notes](https://github.com/babel/babel-loader/releases)
- [Changelog](https://github.com/babel/babel-loader/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel-loader/compare/v8.2.3...v8.2.5)

---
updated-dependencies:
- dependency-name: babel-loader
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-29 00:17:12 +00:00
dependabot[bot]
b9a644cd4f Bump eslint from 8.13.0 to 8.18.0 (#5400)
Bumps [eslint](https://github.com/eslint/eslint) from 8.13.0 to 8.18.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.13.0...v8.18.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-28 16:45:56 -07:00
dependabot[bot]
1afc5ef245 Bump sass-loader from 12.6.0 to 13.0.2 (#5396)
Bumps [sass-loader](https://github.com/webpack-contrib/sass-loader) from 12.6.0 to 13.0.2.
- [Release notes](https://github.com/webpack-contrib/sass-loader/releases)
- [Changelog](https://github.com/webpack-contrib/sass-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/sass-loader/compare/v12.6.0...v13.0.2)

---
updated-dependencies:
- dependency-name: sass-loader
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-28 09:48:58 -07:00
Shefali Joshi
34442c53c6 Update version for 2.1.0 (#5364)
* Bump d3-selection from 1.3.2 to 3.0.0

Bumps [d3-selection](https://github.com/d3/d3-selection) from 1.3.2 to 3.0.0.
- [Release notes](https://github.com/d3/d3-selection/releases)
- [Commits](https://github.com/d3/d3-selection/compare/v1.3.2...v3.0.0)

---
updated-dependencies:
- dependency-name: d3-selection
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Prepare for sprint 2.1.0

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-21 10:10:52 -07:00
dependabot[bot]
451ca075fe Bump mini-css-extract-plugin from 2.6.0 to 2.6.1 (#5352)
Bumps [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) from 2.6.0 to 2.6.1.
- [Release notes](https://github.com/webpack-contrib/mini-css-extract-plugin/releases)
- [Changelog](https://github.com/webpack-contrib/mini-css-extract-plugin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/mini-css-extract-plugin/compare/v2.6.0...v2.6.1)

---
updated-dependencies:
- dependency-name: mini-css-extract-plugin
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-15 20:07:52 +00:00
Jesse Mazzella
84f1a61a8d Allow dragging of imagery filter sliders when embedded in flexible layout (#5342)
* Fix imagery filter sliders within flexible layout

* Add image filter value checks to e2e test

- Tests if filter values are updated correctly when dragging contrast/brightness sliders on imagery embedded in a flexible layout

* More e2e tests, add aria-role and update selectors

- Add 'toolbar' role for Image controls
2022-06-13 15:24:44 -07:00
dependabot[bot]
ea041aaaf9 Bump webpack-cli from 4.9.2 to 4.10.0 (#5344)
Bumps [webpack-cli](https://github.com/webpack/webpack-cli) from 4.9.2 to 4.10.0.
- [Release notes](https://github.com/webpack/webpack-cli/releases)
- [Changelog](https://github.com/webpack/webpack-cli/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-cli/compare/webpack-cli@4.9.2...webpack-cli@4.10.0)

---
updated-dependencies:
- dependency-name: webpack-cli
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2022-06-13 21:57:56 +00:00
dependabot[bot]
ac9420bfa1 Bump eslint-plugin-vue from 9.1.0 to 9.1.1 (#5343)
Bumps [eslint-plugin-vue](https://github.com/vuejs/eslint-plugin-vue) from 9.1.0 to 9.1.1.
- [Release notes](https://github.com/vuejs/eslint-plugin-vue/releases)
- [Commits](https://github.com/vuejs/eslint-plugin-vue/compare/v9.1.0...v9.1.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-vue
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-13 14:53:30 -07:00
dependabot[bot]
0ebab10578 Bump jasmine-core from 4.1.1 to 4.2.0 (#5332)
Bumps [jasmine-core](https://github.com/jasmine/jasmine) from 4.1.1 to 4.2.0.
- [Release notes](https://github.com/jasmine/jasmine/releases)
- [Changelog](https://github.com/jasmine/jasmine/blob/main/RELEASE.md)
- [Commits](https://github.com/jasmine/jasmine/compare/v4.1.1...v4.2.0)

---
updated-dependencies:
- dependency-name: jasmine-core
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-13 16:50:46 +00:00
Jesse Mazzella
cbdb9fc437 Fix test:watch -- begone, infinite watch loop! (#5337) 2022-06-13 08:40:37 -07:00
Adam Fahey
63d2246345 Created Example Imagery in Flexible layout test (#5239)
* Created Example Imagery in Flexible layout test

* Updated test to have describe and rebased

* added image to flexview

* Removed old comments

* Cant figure out how to zoom once in flexable layout

* Zoom in and pan left, right, up and down

* Finished

* Lint indentation fix

* Removed dangle comma

* DRY up tests made functions

* Fix lint errors

* Functions failing

* Zoom and pan functions

* Replaced zoom in with function

* Fixed lint errors

* white space

* Removed xpath using css for flexable layout click
2022-06-11 06:45:09 -07:00
Michael Pingleton
78002f0a24 Fixed bug which prevented historical images from loading. (#5271)
* Fixed bug which prevented historical images from loading.

imageLoadDelay variable would be initialized as a string when it's supposed to be initialized as an integer.

* Updated image load delay in E2E test.

* Implemented suggested tweak for parsing image delay.

Co-authored-by: John Hill <john.c.hill@nasa.gov>
2022-06-09 14:25:33 -07:00
Jesse Mazzella
f08fd58486 Add test for imagery filter reset (#5318) 2022-06-09 14:15:21 -07:00
Charles Hacskaylo
730272e165 Fixes #5303 (#5314)
- Fixed scrambled SVG definitions;
- Removed some unused artwork;

Co-authored-by: Scott Bell <scott@traclabs.com>
2022-06-09 11:54:15 +02:00
22 changed files with 556 additions and 170 deletions

View File

@@ -3,7 +3,7 @@
{
"name": "descriptionRegexp",
"config": {
"regexp": "x] Testing instructions",
"regexp": "[x|X]] Testing instructions",
"errorMessage": ":police_officer: PR Description does not confirm that associated issue(s) contain Testing instructions"
}
},

3
app.js
View File

@@ -12,7 +12,7 @@ const express = require('express');
const app = express();
const fs = require('fs');
const request = require('request');
const __DEV__ = process.env.NODE_ENV === 'development';
const __DEV__ = !process.env.NODE_ENV || process.env.NODE_ENV === 'development';
// Defaults
options.port = options.port || options.p || 8080;
@@ -89,3 +89,4 @@ app.get('/', function (req, res) {
app.listen(options.port, options.host, function () {
console.log('Open MCT application running at %s:%s', options.host, options.port);
});

122
e2e/README.md Normal file
View File

@@ -0,0 +1,122 @@
# e2e testing
This document captures information specific to the e2e testing of Open MCT. For general information about testing, please see [the Open MCT README](https://github.com/nasa/openmct/blob/master/README.md#tests).
## Overview
This document is designed to capture on the What, Why, and How's of writing and running e2e tests in Open MCT.
### About e2e testing
e2e testing is an industry-standard approach to automating the testing of web-based UIs such as Open MCT. Broadly speaking, e2e tests differentiate themselves from unit tests by preferring replication of real user interactions over execution of raw JavaScript functions.
Historically, the abstraction necessary to replicate real user behavior meant that:
- e2e tests were "expensive" due to how much code each test executed. The closer a test replicates the user, the more code is needed run during test execution. Unit tests could run smaller units of code more effeciently.
- e2e tests were flaky due to network conditions or the underlying protocols associated with testing a browser.
- e2e frameworks relied on a browser communication standard which lacked the observability and controls necessary needed to reach the code paths possible with unit and integration tests.
- e2e frameworks provided insufficient debug information on test failure
However, as the web ecosystem has matured to the point where mission-critical UIs can be written for the web (Open MCT), the e2e testing tools have matured as well. There are now fewer "trade-offs" when choosing to write an e2e test over any other type of test.
Modern e2e frameworks:
- Bypass the surface layer of the web-application-under-test and use a raw debugging protocol to observe and control application and browser state.
- These new browser-internal protocols enable near-instant, bi-directional communication between test code and the browser, speeding up test execution and making the tests as reliable as the application itself.
- Provide test debug tooling which enables developers to pinpoint failure
Furthermore, the abstraction necessary to run e2e tests as a user enables them to be extended to run within a variety of contexts. This matches the extensible design of Open MCT.
A single e2e test in Open MCT is extended to run:
- Against a matrix of browser versions.
- Against a matrix of OS platforms.
- Against a local development version of Open MCT.
- A version of Open MCT loaded as a dependency (VIPER, VISTA, etc)
- Against a variety of data sources or telemetry endpoints.
### Why Playwright?
[Playwright](https://playwright.dev/) was chosen as our e2e framework because it solves a few VIPER Mission needs:
1. First-class support for Automated Performance Testing
2. Official Chrome, Chrome Canary, and iPad Capabilities
3. Support for Browserless.io
4. Ability to generate code coverage reports
## Getting Started
### Getting started with Playwright
### Getting started with Open MCT's implementation of Playwright
## Types of Testing
### (TBD) Visual Testing
- Visual tests leverage [Percy](https://percy.io/).
- Visual tests should be written within the `./tests/visual` folder so that they can be ignored during git clones to avoid leaking credentials when executing percy cli
#### (TBD) How to write a good visual test
### (TBD) Snapshot Testing
<https://playwright.dev/docs/test-snapshots>
### (TBD) Mobile Testing
### (TBD) Performance Testing
### (FUTURE) Component Testing
- Component testing is currrently possible in Playwright but not enabled on this project. For more, please see: <https://playwright.dev/docs/test-components>
## Architecture, Test Design and Best Practices
### (TBD) Architecture
#### (TBD) Continuous Integration
- Test maturation
- Difference between full and e2e-ci suites
- Platforms
### (TBD) Multi-browser and Multi-operating system
- Where is it tested
- What's supported
### (TBD) Test Design
- Re-usable tests for VISTA, VIPER, etc.
#### Annotations
- Annotations are a great way of organizing tests outside of a file structure.
- Current list of annotations:
- `@ipad` - Mobile execution possible with Playwright's iPad support.
- `@gds` - Executes a GDS Test Case. Used to track in VIPER Mission.
- `@addInit` - Initializes the browser with an injected and artificial state. Useful for non-default plugins.
- `@localStorage` - Captures or generates session storage to manipulate browser state. Useful for excluding in tests which require a persistent backend (i.e. CouchDB).
- `@snapshot` - Uses Playwright's snapshot functionality to record a copy of the DOM for direct comparison. Must be run inside of a container.
### (TBD) Best Practices
### (TBD) Reporting
### (TBD) Code Coverage
Code coverage is collected during test execution and reported with [nyc](https://github.com/istanbuljs/nyc) and [codecov.io](https://about.codecov.io/)
## Other
### FAQ
- How does this help NASA missions?
- When should I write an e2e test instead of a unit test?
- When should I write a functional vs visual test?
- How is Open MCT extending default Playwright functionality?
### Troubleshooting
- Why is my test failing on CI and not locally?
- How can I view the failing tests on CI?

18
e2e/commonActions.js Normal file
View File

@@ -0,0 +1,18 @@
/**
* Wait for all animations within the given element and subtrees to finish
* See: https://github.com/microsoft/playwright/issues/15660#issuecomment-1184911658
* @param {import('@playwright/test').Locator} locator
*/
function waitForAnimations(locator) {
return locator
.evaluate((element) =>
Promise.all(
element
.getAnimations({ subtree: true })
.map((animation) => animation.finished)));
}
// eslint-disable-next-line no-undef
module.exports = {
waitForAnimations
};

View File

@@ -60,6 +60,7 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => {
getConditionSetIdentifierFromUrl = conditionSetUrl.split('/').pop().split('?')[0];
console.debug('getConditionSetIdentifierFromUrl ' + getConditionSetIdentifierFromUrl);
await page.close();
});
//Load localStorage for subsequent tests

View File

@@ -28,6 +28,7 @@ but only assume that example imagery is present.
const { test } = require('../../../fixtures.js');
const { expect } = require('@playwright/test');
const { waitForAnimations } = require('../../../commonActions.js');
const backgroundImageSelector = '.c-imagery__main-image__background-image';
@@ -81,7 +82,16 @@ test.describe('Example Imagery Object', () => {
expect(imageMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);
expect(imageMouseZoomedOut.height).toBeLessThan(imageMouseZoomedIn.height);
expect(imageMouseZoomedOut.width).toBeLessThan(imageMouseZoomedIn.width);
});
test('Can adjust image brightness/contrast by dragging the sliders', async ({ page, browserName }) => {
test.fixme(browserName === 'firefox', 'This test needs to be updated to work with firefox');
// Open the image filter menu
await page.locator('[role=toolbar] button[title="Brightness and contrast"]').click();
// Drag the brightness and contrast sliders around and assert filter values
await dragBrightnessSliderAndAssertFilterValues(page);
await dragContrastSliderAndAssertFilterValues(page);
});
test('Can use alt+drag to move around image once zoomed in', async ({ page }) => {
@@ -150,78 +160,65 @@ test.describe('Example Imagery Object', () => {
});
test('Can use + - buttons to zoom on the image', async ({ page }) => {
await page.locator(backgroundImageSelector).hover({trial: true});
const zoomInBtn = page.locator('.t-btn-zoom-in').nth(0);
const zoomOutBtn = page.locator('.t-btn-zoom-out').nth(0);
// Get initial image dimensions
const initialBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
await zoomInBtn.click();
await zoomInBtn.click();
// wait for zoom animation to finish
await page.locator(backgroundImageSelector).hover({trial: true});
// Zoom in twice via button
await zoomIntoImageryByButton(page);
await zoomIntoImageryByButton(page);
// Get and assert zoomed in image dimensions
const zoomedInBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
expect(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height);
expect(zoomedInBoundingBox.width).toBeGreaterThan(initialBoundingBox.width);
await zoomOutBtn.click();
// wait for zoom animation to finish
await page.locator(backgroundImageSelector).hover({trial: true});
// Zoom out once via button
await zoomOutOfImageryByButton(page);
// Get and assert zoomed out image dimensions
const zoomedOutBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
expect(zoomedOutBoundingBox.height).toBeLessThan(zoomedInBoundingBox.height);
expect(zoomedOutBoundingBox.width).toBeLessThan(zoomedInBoundingBox.width);
// Zoom out again via button, assert against the initial image dimensions
await zoomOutOfImageryByButton(page);
const finalBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
expect(finalBoundingBox).toEqual(initialBoundingBox);
});
test('Can use the reset button to reset the image', async ({ page }, testInfo) => {
test.slow(testInfo.project === 'chrome-beta', "This test is slow in chrome-beta");
// wait for zoom animation to finish
await page.locator(backgroundImageSelector).hover({trial: true});
const zoomInBtn = page.locator('.t-btn-zoom-in').nth(0);
const zoomResetBtn = page.locator('.t-btn-zoom-reset').nth(0);
// Get initial image dimensions
const initialBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
await zoomInBtn.click();
// wait for zoom animation to finish
await page.locator(backgroundImageSelector).hover({trial: true});
await zoomInBtn.click();
// wait for zoom animation to finish
await page.locator(backgroundImageSelector).hover({trial: true});
// Zoom in twice via button
await zoomIntoImageryByButton(page);
await zoomIntoImageryByButton(page);
// Get and assert zoomed in image dimensions
const zoomedInBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
expect.soft(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height);
expect.soft(zoomedInBoundingBox.width).toBeGreaterThan(initialBoundingBox.width);
// wait for zoom animation to finish
// FIXME: The zoom is flakey, sometimes not returning to original dimensions
// https://github.com/nasa/openmct/issues/5491
await expect.poll(async () => {
await zoomResetBtn.click();
const boundingBox = await page.locator(backgroundImageSelector).boundingBox();
return boundingBox;
}, {
timeout: 10 * 1000
}).toEqual(initialBoundingBox);
// Reset pan and zoom and assert against initial image dimensions
await resetImageryPanAndZoom(page);
const finalBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
expect(finalBoundingBox).toEqual(initialBoundingBox);
});
test('Using the zoom features does not pause telemetry', async ({ page }) => {
const pausePlayButton = page.locator('.c-button.pause-play');
// wait for zoom animation to finish
await page.locator(backgroundImageSelector).hover({trial: true});
// open the time conductor drop down
await page.locator('button:has-text("Fixed Timespan")').click();
// Click local clock
await page.locator('[data-testid="conductor-modeOption-realtime"]').click();
await expect.soft(pausePlayButton).not.toHaveClass(/is-paused/);
const zoomInBtn = page.locator('.t-btn-zoom-in').nth(0);
await zoomInBtn.click();
// wait for zoom animation to finish
await page.locator(backgroundImageSelector).hover({trial: true});
return expect(pausePlayButton).not.toHaveClass(/is-paused/);
// Zoom in via button
await zoomIntoImageryByButton(page);
await expect(pausePlayButton).not.toHaveClass(/is-paused/);
});
});
@@ -249,10 +246,8 @@ test('Example Imagery in Display layout', async ({ page, browserName }) => {
await page.click('text=Example Imagery');
// Clear and set Image load delay to minimum value
// FIXME: Update the value to 5000 ms when this bug is fixed.
// See: https://github.com/nasa/openmct/issues/5265
await page.locator('input[type="number"]').fill('');
await page.locator('input[type="number"]').fill('0');
await page.locator('input[type="number"]').fill('5000');
// Click text=OK
await Promise.all([
@@ -336,6 +331,19 @@ test('Example Imagery in Display layout', async ({ page, browserName }) => {
// Verify selected image is still displayed
await expect(selectedImage).toBeVisible();
// Unpause imagery
await page.locator('.pause-play').click();
//Get background-image url from background-image css prop
await assertBackgroundImageUrlFromBackgroundCss(page);
// Open the image filter menu
await page.locator('[role=toolbar] button[title="Brightness and contrast"]').click();
// Drag the brightness and contrast sliders around and assert filter values
await dragBrightnessSliderAndAssertFilterValues(page);
await dragContrastSliderAndAssertFilterValues(page);
});
test.describe('Example imagery thumbnails resize in display layouts', () => {
@@ -426,6 +434,11 @@ test.describe('Example imagery thumbnails resize in display layouts', () => {
});
});
// test.fixme('Can use Mouse Wheel to zoom in and out of previous image');
// test.fixme('Can use alt+drag to move around image once zoomed in');
// test.fixme('Clicking on the left arrow should pause the imagery and go to previous image');
// test.fixme('If the imagery view is in pause mode, images still come in');
// test.fixme('If the imagery view is not in pause mode, it should be updated when new images come in');
test.describe('Example Imagery in Flexible layout', () => {
test('Example Imagery in Flexible layout', async ({ page, browserName }) => {
test.fixme(browserName === 'firefox', 'This test needs to be updated to work with firefox');
@@ -636,22 +649,6 @@ async function assertBackgroundImageBrightness(page, expected) {
expect(actual).toBe(expected);
}
/**
* Gets the filter:contrast value of the current background-image and
* asserts against an expected value
* @param {import('@playwright/test').Page} page
* @param {String} expected The expected contrast value
*/
async function assertBackgroundImageContrast(page, expected) {
const backgroundImage = page.locator('.c-imagery__main-image__background-image');
// Get the contrast filter value (i.e: filter: contrast(500%) => "500")
const actual = await backgroundImage.evaluate((el) => {
return el.style.filter.match(/contrast\((\d{1,3})%\)/)[1];
});
expect(actual).toBe(expected);
}
/**
* @param {import('@playwright/test').Page} page
*/
@@ -751,3 +748,70 @@ async function mouseZoomIn(page) {
expect(imageMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
expect(imageMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);
}
/**
* Gets the filter:contrast value of the current background-image and
* asserts against an expected value
* @param {import('@playwright/test').Page} page
* @param {String} expected The expected contrast value
*/
async function assertBackgroundImageContrast(page, expected) {
const backgroundImage = page.locator('.c-imagery__main-image__background-image');
// Get the contrast filter value (i.e: filter: contrast(500%) => "500")
const actual = await backgroundImage.evaluate((el) => {
return el.style.filter.match(/contrast\((\d{1,3})%\)/)[1];
});
expect(actual).toBe(expected);
}
/**
* Use the '+' button to zoom in. Hovers first if the toolbar is not visible
* and waits for the zoom animation to finish afterwards.
* @param {import('@playwright/test').Page} page
*/
async function zoomIntoImageryByButton(page) {
// FIXME: There should only be one set of imagery buttons, but there are two?
const zoomInBtn = page.locator("[role='toolbar'][aria-label='Image controls'] .t-btn-zoom-in").nth(0);
const backgroundImage = page.locator(backgroundImageSelector);
if (!(await zoomInBtn.isVisible())) {
await backgroundImage.hover({trial: true});
}
await zoomInBtn.click();
await waitForAnimations(backgroundImage);
}
/**
* Use the '-' button to zoom out. Hovers first if the toolbar is not visible
* and waits for the zoom animation to finish afterwards.
* @param {import('@playwright/test').Page} page
*/
async function zoomOutOfImageryByButton(page) {
// FIXME: There should only be one set of imagery buttons, but there are two?
const zoomOutBtn = page.locator("[role='toolbar'][aria-label='Image controls'] .t-btn-zoom-out").nth(0);
const backgroundImage = page.locator(backgroundImageSelector);
if (!(await zoomOutBtn.isVisible())) {
await backgroundImage.hover({trial: true});
}
await zoomOutBtn.click();
await waitForAnimations(backgroundImage);
}
/**
* Use the reset button to reset image pan and zoom. Hovers first if the toolbar is not visible
* and waits for the zoom animation to finish afterwards.
* @param {import('@playwright/test').Page} page
*/
async function resetImageryPanAndZoom(page) {
// FIXME: There should only be one set of imagery buttons, but there are two?
const panZoomResetBtn = page.locator("[role='toolbar'][aria-label='Image controls'] .t-btn-zoom-reset").nth(0);
const backgroundImage = page.locator(backgroundImageSelector);
if (!(await panZoomResetBtn.isVisible())) {
await backgroundImage.hover({trial: true});
}
await panZoomResetBtn.click();
await waitForAnimations(backgroundImage);
}

View File

@@ -31,7 +31,7 @@ to "fail" on assertions. Instead, they should be used to detect changes between
Note: Larger testsuite sizes are OK due to the setup time associated with these tests.
*/
const { test } = require('@playwright/test');
const { test } = require('../../fixtures.js');
const percySnapshot = require('@percy/playwright');
const path = require('path');
const sinon = require('sinon');

View File

@@ -32,7 +32,8 @@ to "fail" on assertions. Instead, they should be used to detect changes between
Note: Larger testsuite sizes are OK due to the setup time associated with these tests.
*/
const { test, expect } = require('@playwright/test');
const { test } = require('../../fixtures.js');
const { expect } = require('@playwright/test');
const percySnapshot = require('@percy/playwright');
const path = require('path');
const sinon = require('sinon');

View File

@@ -24,7 +24,8 @@
This test suite is dedicated to tests which verify search functionality.
*/
const { test, expect } = require('@playwright/test');
const { test } = require('../../fixtures.js');
const { expect } = require('@playwright/test');
const percySnapshot = require('@percy/playwright');
/**

View File

@@ -1,6 +1,6 @@
{
"name": "openmct",
"version": "2.0.5",
"version": "2.1.0-SNAPSHOT",
"description": "The Open MCT core platform",
"devDependencies": {
"@babel/eslint-parser": "7.18.2",
@@ -13,7 +13,7 @@
"@types/karma": "^6.3.2",
"@types/lodash": "^4.14.178",
"@types/mocha": "^9.1.0",
"babel-loader": "8.2.3",
"babel-loader": "8.2.5",
"babel-plugin-istanbul": "6.1.1",
"comma-separated-values": "3.6.4",
"codecov":"3.8.3",
@@ -23,10 +23,10 @@
"d3-axis": "3.0.0",
"d3-scale": "3.3.0",
"d3-selection": "3.0.0",
"eslint": "8.13.0",
"eslint": "8.18.0",
"eslint-plugin-compat": "4.0.2",
"eslint-plugin-playwright": "0.9.0",
"eslint-plugin-vue": "9.1.0",
"eslint-plugin-vue": "9.1.1",
"eslint-plugin-you-dont-need-lodash-underscore": "6.12.0",
"eventemitter3": "1.2.0",
"express": "4.13.1",
@@ -34,7 +34,7 @@
"git-rev-sync": "3.0.2",
"html2canvas": "1.4.1",
"imports-loader": "0.8.0",
"jasmine-core": "4.1.1",
"jasmine-core": "4.2.0",
"jsdoc": "3.5.5",
"karma": "6.3.20",
"karma-chrome-launcher": "3.1.1",
@@ -42,7 +42,7 @@
"karma-coverage": "2.2.0",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-firefox-launcher": "2.1.2",
"karma-jasmine": "4.0.1",
"karma-jasmine": "5.1.0",
"karma-junit-reporter": "2.0.1",
"karma-sourcemap-loader": "0.3.8",
"karma-spec-reporter": "0.0.34",
@@ -50,20 +50,20 @@
"lighthouse": "9.6.1",
"location-bar": "3.0.1",
"lodash": "4.17.21",
"mini-css-extract-plugin": "2.6.0",
"moment": "2.29.3",
"mini-css-extract-plugin": "2.6.1",
"moment": "2.29.4",
"moment-duration-format": "2.3.2",
"moment-timezone": "0.5.34",
"node-bourbon": "4.2.3",
"painterro": "1.2.56",
"nyc":"15.1.0",
"painterro": "1.2.78",
"plotly.js-basic-dist": "2.12.0",
"plotly.js-gl2d-dist": "2.12.0",
"printj": "1.3.1",
"request": "2.88.2",
"resolve-url-loader": "5.0.0",
"sass": "1.52.2",
"sass-loader": "12.6.0",
"sass-loader": "13.0.2",
"sinon": "14.0.0",
"style-loader": "^1.0.1",
"uuid": "8.3.2",
@@ -72,7 +72,7 @@
"vue-loader": "15.9.8",
"vue-template-compiler": "2.6.14",
"webpack": "5.68.0",
"webpack-cli": "4.9.2",
"webpack-cli": "4.10.0",
"webpack-dev-middleware": "5.3.3",
"webpack-hot-middleware": "2.25.1",
"webpack-merge": "5.8.0"
@@ -92,7 +92,7 @@
"test:firefox": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run --browsers=FirefoxHeadless",
"test:debug": "cross-env NODE_ENV=debug karma start --no-single-run",
"test:e2e": "npx playwright test",
"test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke branding default condition timeConductor clock exampleImagery persistence performance grandsearch notebook/tags",
"test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome visual smoke branding default condition timeConductor clock persistence performance grandsearch tags",
"test:e2e:local": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome",
"test:e2e:updatesnapshots": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @snapshot --update-snapshots",
"test:e2e:visual": "percy exec --config ./e2e/.percy.yml -- npx playwright test --config=e2e/playwright-visual.config.js",

View File

@@ -52,8 +52,8 @@ class MutableDomainObject {
// Property should not be serialized
enumerable: false
},
_observers: {
value: [],
_callbacksForPaths: {
value: {},
// Property should not be serialized
enumerable: false
},
@@ -64,15 +64,31 @@ class MutableDomainObject {
}
});
}
/**
* BRAND new approach
* - Register a listener on $_synchronize_model
* - The $_synchronize_model event provides the path. Figure out whether the mutated path is equal to, or a parent of the observed path.
* - If so, trigger callback with new value
* - As an optimization, ONLY trigger if value has actually changed. Could be deferred until later?
*/
$observe(path, callback) {
let fullPath = qualifiedEventName(this, path);
let eventOff =
this._globalEventEmitter.off.bind(this._globalEventEmitter, fullPath, callback);
let callbacksForPath = this._callbacksForPaths[path];
if (callbacksForPath === undefined) {
callbacksForPath = [];
this._callbacksForPaths[path] = callbacksForPath;
}
this._globalEventEmitter.on(fullPath, callback);
this._observers.push(eventOff);
callbacksForPath.push(callback);
return function unlisten() {
let index = callbacksForPath.indexOf(callback);
callbacksForPath.splice(index, 1);
if (callbacksForPath.length === 0) {
delete this._callbacksForPaths[path];
}
}.bind(this);
return eventOff;
}
$set(path, value) {
_.set(this, path, value);
@@ -88,25 +104,14 @@ class MutableDomainObject {
this._globalEventEmitter.emit(ANY_OBJECT_EVENT, this);
//Emit wildcard event, with path so that callback knows what changed
this._globalEventEmitter.emit(qualifiedEventName(this, '*'), this, path, value);
//Emit events specific to properties affected
let parentPropertiesList = path.split('.');
for (let index = parentPropertiesList.length; index > 0; index--) {
let parentPropertyPath = parentPropertiesList.slice(0, index).join('.');
this._globalEventEmitter.emit(qualifiedEventName(this, parentPropertyPath), _.get(this, parentPropertyPath));
}
//TODO: Emit events for listeners of child properties when parent changes.
// Do it at observer time - also register observers for parent attribute path.
}
$refresh(model) {
//TODO: Currently we are updating the entire object.
// In the future we could update a specific property of the object using the 'path' parameter.
const clone = JSON.parse(JSON.stringify(this));
this._globalEventEmitter.emit(qualifiedEventName(this, '$_synchronize_model'), model);
//Emit wildcard event, with path so that callback knows what changed
this._globalEventEmitter.emit(qualifiedEventName(this, '*'), this);
//Emit wildcard event
this._globalEventEmitter.emit(qualifiedEventName(this, '*'), this, '*', this, clone);
}
$on(event, callback) {
@@ -114,23 +119,53 @@ class MutableDomainObject {
return () => this._instanceEventEmitter.off(event, callback);
}
$destroy() {
while (this._observers.length > 0) {
const observer = this._observers.pop();
observer();
}
$updateListenersOnPath(updatedModel, mutatedPath, newValue, oldModel) {
const isRefresh = mutatedPath === '*';
Object.entries(this._callbacksForPaths).forEach(([observedPath, callbacks]) => {
if (isChildOf(observedPath, mutatedPath)
|| isParentOf(observedPath, mutatedPath)) {
let newValueOfObservedPath;
if (observedPath === '*') {
newValueOfObservedPath = updatedModel;
} else {
newValueOfObservedPath = _.get(updatedModel, observedPath);
}
if (isRefresh && observedPath !== '*') {
const oldValueOfObservedPath = _.get(oldModel, observedPath);
if (!_.isEqual(newValueOfObservedPath, oldValueOfObservedPath)) {
callbacks.forEach(callback => callback(newValueOfObservedPath));
}
} else {
//Assumed to be different if result of mutation.
callbacks.forEach(callback => callback(newValueOfObservedPath));
}
}
});
}
$synchronizeModel(updatedObject) {
let clone = JSON.parse(JSON.stringify(updatedObject));
utils.refresh(this, clone);
}
$destroy() {
Object.keys(this._callbacksForPaths).forEach(key => delete this._callbacksForPaths[key]);
this._instanceEventEmitter.emit('$_destroy');
this._globalEventEmitter.off(qualifiedEventName(this, '$_synchronize_model'), this.$synchronizeModel);
this._globalEventEmitter.off(qualifiedEventName(this, '*'), this.$updateListenersOnPath);
}
static createMutable(object, mutationTopic) {
let mutable = Object.create(new MutableDomainObject(mutationTopic));
Object.assign(mutable, object);
mutable.$observe('$_synchronize_model', (updatedObject) => {
let clone = JSON.parse(JSON.stringify(updatedObject));
utils.refresh(mutable, clone);
});
mutable.$updateListenersOnPath = mutable.$updateListenersOnPath.bind(mutable);
mutable.$synchronizeModel = mutable.$synchronizeModel.bind(mutable);
mutable._globalEventEmitter.on(qualifiedEventName(mutable, '$_synchronize_model'), mutable.$synchronizeModel);
mutable._globalEventEmitter.on(qualifiedEventName(mutable, '*'), mutable.$updateListenersOnPath);
return mutable;
}
@@ -147,4 +182,12 @@ function qualifiedEventName(object, eventName) {
return [keystring, eventName].join(':');
}
function isChildOf(observedPath, mutatedPath) {
return Boolean(mutatedPath === '*' || observedPath?.startsWith(mutatedPath));
}
function isParentOf(observedPath, mutatedPath) {
return Boolean(observedPath === '*' || mutatedPath?.startsWith(observedPath));
}
export default MutableDomainObject;

View File

@@ -230,7 +230,6 @@ export default class ObjectAPI {
return result;
}).catch((result) => {
console.warn(`Failed to retrieve ${keystring}:`, result);
this.openmct.notifications.error(`Failed to retrieve object ${keystring}`);
delete this.cache[keystring];
@@ -388,13 +387,7 @@ export default class ObjectAPI {
}
}
return result.catch((error) => {
if (error instanceof this.errors.Conflict) {
this.openmct.notifications.error(`Conflict detected while saving ${this.makeKeyString(domainObject.identifier)}`);
}
throw error;
});
return result;
}
/**

View File

@@ -1,7 +1,7 @@
import ObjectAPI from './ObjectAPI.js';
import { createOpenMct, resetApplicationState } from '../../utils/testing';
describe("The Object API", () => {
fdescribe("The Object API", () => {
let objectAPI;
let typeRegistry;
let openmct = {};
@@ -287,53 +287,167 @@ describe("The Object API", () => {
mutableSecondInstance.$destroy();
});
it('to stay synchronized when mutated', function () {
objectAPI.mutate(mutable, 'otherAttribute', 'new-attribute-value');
expect(mutableSecondInstance.otherAttribute).toBe('new-attribute-value');
});
it('to indicate when a property changes', function () {
let mutationCallback = jasmine.createSpy('mutation-callback');
let unlisten;
return new Promise(function (resolve) {
mutationCallback.and.callFake(resolve);
unlisten = objectAPI.observe(mutableSecondInstance, 'otherAttribute', mutationCallback);
objectAPI.mutate(mutable, 'otherAttribute', 'some-new-value');
}).then(function () {
expect(mutationCallback).toHaveBeenCalledWith('some-new-value');
unlisten();
describe('on mutation', () => {
it('to stay synchronized', function () {
objectAPI.mutate(mutable, 'otherAttribute', 'new-attribute-value');
expect(mutableSecondInstance.otherAttribute).toBe('new-attribute-value');
});
});
it('to indicate when a child property has changed', function () {
let embeddedKeyCallback = jasmine.createSpy('embeddedKeyCallback');
let embeddedObjectCallback = jasmine.createSpy('embeddedObjectCallback');
let objectAttributeCallback = jasmine.createSpy('objectAttribute');
let listeners = [];
it('to indicate when a property changes', function () {
let mutationCallback = jasmine.createSpy('mutation-callback');
let unlisten;
return new Promise(function (resolve) {
objectAttributeCallback.and.callFake(resolve);
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute.embeddedObject.embeddedKey', embeddedKeyCallback));
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute.embeddedObject', embeddedObjectCallback));
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute', objectAttributeCallback));
objectAPI.mutate(mutable, 'objectAttribute.embeddedObject.embeddedKey', 'updated-embedded-value');
}).then(function () {
expect(embeddedKeyCallback).toHaveBeenCalledWith('updated-embedded-value');
expect(embeddedObjectCallback).toHaveBeenCalledWith({
embeddedKey: 'updated-embedded-value'
return new Promise(function (resolve) {
mutationCallback.and.callFake(resolve);
unlisten = objectAPI.observe(mutableSecondInstance, 'otherAttribute', mutationCallback);
objectAPI.mutate(mutable, 'otherAttribute', 'some-new-value');
}).then(function () {
expect(mutationCallback).toHaveBeenCalledWith('some-new-value');
unlisten();
});
expect(objectAttributeCallback).toHaveBeenCalledWith({
embeddedObject: {
});
it('to indicate when a child property has changed', function () {
let embeddedKeyCallback = jasmine.createSpy('embeddedKeyCallback');
let embeddedObjectCallback = jasmine.createSpy('embeddedObjectCallback');
let objectAttributeCallback = jasmine.createSpy('objectAttribute');
let listeners = [];
return new Promise(function (resolve) {
objectAttributeCallback.and.callFake(resolve);
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute.embeddedObject.embeddedKey', embeddedKeyCallback));
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute.embeddedObject', embeddedObjectCallback));
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute', objectAttributeCallback));
objectAPI.mutate(mutable, 'objectAttribute.embeddedObject.embeddedKey', 'updated-embedded-value');
}).then(function () {
expect(embeddedKeyCallback).toHaveBeenCalledWith('updated-embedded-value');
expect(embeddedObjectCallback).toHaveBeenCalledWith({
embeddedKey: 'updated-embedded-value'
}
});
});
expect(objectAttributeCallback).toHaveBeenCalledWith({
embeddedObject: {
embeddedKey: 'updated-embedded-value'
}
});
listeners.forEach(listener => listener());
listeners.forEach(listener => listener());
});
});
it('to indicate when a parent property has changed', function () {
let embeddedKeyCallback = jasmine.createSpy('embeddedKeyCallback');
let embeddedObjectCallback = jasmine.createSpy('embeddedObjectCallback');
let objectAttributeCallback = jasmine.createSpy('objectAttribute');
let listeners = [];
return new Promise(function (resolve) {
objectAttributeCallback.and.callFake(resolve);
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute.embeddedObject.embeddedKey', embeddedKeyCallback));
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute.embeddedObject', embeddedObjectCallback));
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute', objectAttributeCallback));
objectAPI.mutate(mutable, 'objectAttribute.embeddedObject', 'updated-embedded-value');
}).then(function () {
expect(embeddedKeyCallback).toHaveBeenCalledWith(undefined);
expect(embeddedObjectCallback).toHaveBeenCalledWith('updated-embedded-value');
expect(objectAttributeCallback).toHaveBeenCalledWith({
embeddedObject: 'updated-embedded-value'
});
listeners.forEach(listener => listener());
});
});
});
describe('on refresh', () => {
let refreshModel;
beforeEach(() => {
refreshModel = JSON.parse(JSON.stringify(mutable));
});
it('to stay synchronized', function () {
refreshModel.otherAttribute = 'new-attribute-value';
mutable.$refresh(refreshModel);
expect(mutableSecondInstance.otherAttribute).toBe('new-attribute-value');
});
it('to indicate when a property changes', function () {
let mutationCallback = jasmine.createSpy('mutation-callback');
let unlisten;
return new Promise(function (resolve) {
mutationCallback.and.callFake(resolve);
unlisten = objectAPI.observe(mutableSecondInstance, 'otherAttribute', mutationCallback);
refreshModel.otherAttribute = 'some-new-value';
mutable.$refresh(refreshModel);
}).then(function () {
expect(mutationCallback).toHaveBeenCalledWith('some-new-value');
unlisten();
});
});
it('to indicate when a child property has changed', function () {
let embeddedKeyCallback = jasmine.createSpy('embeddedKeyCallback');
let embeddedObjectCallback = jasmine.createSpy('embeddedObjectCallback');
let objectAttributeCallback = jasmine.createSpy('objectAttribute');
let listeners = [];
return new Promise(function (resolve) {
objectAttributeCallback.and.callFake(resolve);
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute.embeddedObject.embeddedKey', embeddedKeyCallback));
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute.embeddedObject', embeddedObjectCallback));
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute', objectAttributeCallback));
refreshModel.objectAttribute.embeddedObject.embeddedKey = 'updated-embedded-value';
mutable.$refresh(refreshModel);
}).then(function () {
expect(embeddedKeyCallback).toHaveBeenCalledWith('updated-embedded-value');
expect(embeddedObjectCallback).toHaveBeenCalledWith({
embeddedKey: 'updated-embedded-value'
});
expect(objectAttributeCallback).toHaveBeenCalledWith({
embeddedObject: {
embeddedKey: 'updated-embedded-value'
}
});
listeners.forEach(listener => listener());
});
});
it('to indicate when a parent property has changed', function () {
let embeddedKeyCallback = jasmine.createSpy('embeddedKeyCallback');
let embeddedObjectCallback = jasmine.createSpy('embeddedObjectCallback');
let objectAttributeCallback = jasmine.createSpy('objectAttribute');
let listeners = [];
return new Promise(function (resolve) {
objectAttributeCallback.and.callFake(resolve);
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute.embeddedObject.embeddedKey', embeddedKeyCallback));
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute.embeddedObject', embeddedObjectCallback));
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute', objectAttributeCallback));
refreshModel.objectAttribute.embeddedObject = 'updated-embedded-value';
mutable.$refresh(refreshModel);
}).then(function () {
expect(embeddedKeyCallback).toHaveBeenCalledWith(undefined);
expect(embeddedObjectCallback).toHaveBeenCalledWith('updated-embedded-value');
expect(objectAttributeCallback).toHaveBeenCalledWith({
embeddedObject: 'updated-embedded-value'
});
listeners.forEach(listener => listener());
});
});
});
});
});

View File

@@ -155,7 +155,7 @@ describe("The LAD Table", () => {
// add another telemetry object as composition in lad table to test multi rows
mockObj.ladTable.composition.push(anotherTelemetryObj.identifier);
beforeEach(async (done) => {
beforeEach(async () => {
let telemetryRequestResolve;
let telemetryObjectResolve;
let anotherTelemetryObjectResolve;
@@ -204,8 +204,6 @@ describe("The LAD Table", () => {
await Promise.all([telemetryRequestPromise, telemetryObjectPromise, anotherTelemetryObjectPromise]);
await Vue.nextTick();
done();
});
it("should show one row per object in the composition", () => {

View File

@@ -49,6 +49,7 @@ describe("the plugin", () => {
let parentObject;
let parentObjectPath;
let changedParentObject;
let unobserve;
beforeEach((done) => {
parentObject = {
name: 'mock folder',
@@ -73,7 +74,7 @@ describe("the plugin", () => {
});
});
openmct.objects.observe(parentObject, '*', (newObject) => {
unobserve = openmct.objects.observe(parentObject, '*', (newObject) => {
changedParentObject = newObject;
done();
@@ -81,6 +82,9 @@ describe("the plugin", () => {
newFolderAction.invoke(parentObjectPath);
});
afterEach(() => {
unobserve();
});
it('creates a new folder object', () => {
expect(openmct.objects.save).toHaveBeenCalled();

View File

@@ -296,12 +296,17 @@ export default {
window.addEventListener('orientationchange', this.formatSidebar);
window.addEventListener('hashchange', this.setSectionAndPageFromUrl);
this.filterAndSortEntries();
this.unlistenToEntryChanges = this.openmct.objects.observe(this.domainObject, "configuration.entries", () => this.filterAndSortEntries());
},
beforeDestroy() {
if (this.unlisten) {
this.unlisten();
}
if (this.unlistenToEntryChanges) {
this.unlistenToEntryChanges();
}
window.removeEventListener('orientationchange', this.formatSidebar);
window.removeEventListener('hashchange', this.setSectionAndPageFromUrl);
},

View File

@@ -233,6 +233,13 @@ export default {
},
mounted() {
this.dropOnEntry = this.dropOnEntry.bind(this);
this.$on('tags-updated', async () => {
const user = await this.openmct.user.getCurrentUser();
this.entry.modified = Date.now();
this.entry.modifiedBy = user.getId();
this.$emit('updateEntry', this.entry);
});
},
methods: {
async addNewEmbed(objectPath) {

View File

@@ -217,11 +217,9 @@ class CouchObjectProvider {
this.indicator.setIndicatorToState(DISCONNECTED);
console.error(error.message);
throw new Error(`CouchDB Error - No response"`);
} else {
console.error(error.message);
throw error;
}
console.error(error.message);
}
}
@@ -655,6 +653,7 @@ class CouchObjectProvider {
let document = new CouchDocument(key, queued.model);
document.metadata.created = Date.now();
this.request(key, "PUT", document).then((response) => {
console.log('create check response', key);
this.#checkResponse(response, queued.intermediateResponse, key);
}).catch(error => {
queued.intermediateResponse.reject(error);

View File

@@ -71,7 +71,7 @@ describe("the RemoteClock plugin", () => {
parse: (datum) => datum.key
};
beforeEach((done) => {
beforeEach(async () => {
openmct.install(openmct.plugins.RemoteClock(TIME_TELEMETRY_ID));
let clocks = openmct.time.getAllClocks();
@@ -113,9 +113,7 @@ describe("the RemoteClock plugin", () => {
end: OFFSET_END
});
Promise.all([objectPromiseResolve, requestPromise])
.then(done)
.catch(done);
await Promise.all([objectPromiseResolve, requestPromise]);
});
it('is available and sets up initial values and listeners', () => {

View File

@@ -78,13 +78,15 @@ describe("the plugin", () => {
describe('when invoked', () => {
beforeEach((done) => {
beforeEach(() => {
openmct.overlays.overlay = function (options) {};
spyOn(openmct.overlays, 'overlay');
viewDatumAction.invoke(mockObjectPath, mockView);
});
it('creates an overlay', () => {
expect(openmct.overlays.overlay).toHaveBeenCalled();
});
});

View File

@@ -133,8 +133,11 @@ export default {
this.addedTags.push(newTagValue);
this.userAddingTag = true;
},
tagRemoved(tagToRemove) {
return this.openmct.annotation.removeAnnotationTag(this.annotation, tagToRemove);
async tagRemoved(tagToRemove) {
const result = await this.openmct.annotation.removeAnnotationTag(this.annotation, tagToRemove);
this.$emit('tags-updated');
return result;
},
async tagAdded(newTag) {
const annotationWasCreated = this.annotation === null || this.annotation === undefined;
@@ -146,6 +149,7 @@ export default {
this.tagsChanged(this.annotation.tags);
this.userAddingTag = false;
this.$emit('tags-updated');
}
}
};

View File

@@ -6,6 +6,17 @@ const webpack = require('webpack');
module.exports = merge(common, {
mode: 'development',
watchOptions: {
// Since we use require.context, webpack is watching the entire directory.
// We need to exclude any files we don't want webpack to watch.
// See: https://webpack.js.org/configuration/watch/#watchoptions-exclude
ignored: [
'**/{node_modules,dist,docs,e2e}', // All files in node_modules, dist, docs, e2e,
'**/{*.yml,Procfile,webpack*.js,babel*.js,package*.json,tsconfig.json,jsdoc.json}', // Config files
'**/*.{sh,md,png,ttf,woff,svg}', // Non source files
'**/.*' // dotfiles and dotfolders
]
},
resolve: {
alias: {
"vue": path.join(__dirname, "node_modules/vue/dist/vue.js")