Compare commits

...

10 Commits

Author SHA1 Message Date
Nikhil Mandlik
d522c1ef44 bump up code coverage to lines: 70% 2021-12-22 14:58:39 -08:00
John Hill
0df672e470 condition widget (#4628) 2021-12-22 14:24:59 -08:00
John Hill
c9bc390355 Condition set visual test (#4625)
* ensure we're running one worker at a time for visual tests

* New test for condition sets

* Update testcase name and notes
2021-12-22 14:02:06 -08:00
dependabot[bot]
5b1664f073 Bump eslint-plugin-playwright from 0.6.0 to 0.7.0 (#4622)
Bumps [eslint-plugin-playwright](https://github.com/playwright-community/eslint-plugin-playwright) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/playwright-community/eslint-plugin-playwright/releases)
- [Commits](https://github.com/playwright-community/eslint-plugin-playwright/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-playwright
  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>
2021-12-22 12:19:55 -08:00
Jamie V
e634e09e32 [Persistability] Various checks in actions (Edit Properties, ImportFromJSON) (#4587)
Co-authored-by: Joshi <simplyrender@gmail.com>
2021-12-21 14:30:31 -08:00
Nikhil
8e3947e48d Condition set e2e (#4604)
New Condition set test
2021-12-20 12:01:16 -08:00
Nikhil
b1ea6efd45 [OpenMCT Tutorial] Using only Realtime (no historical data) telemetry issue #3641 (#4346)
* Reject promise if telemetryService is undefined
* added error and reformatted
* added notification and error if provider is missing
2021-12-17 17:08:37 -08:00
Shefali Joshi
70f2fad243 1.8.2 merge into master - the version of open mct after the last but equally as important (#4611)
* Release 1.8.2

* Trasactions tests are ids equal fix 1.8.2 (#4593)

* test fix

* return promise on 'onSave'

* "Export as JSON" yielding corrupted data #4577 (#4585)

https://github.com/nasa/openmct/issues/4577

* Fix date picker default time setting (#4581)

Fix mode dropdown position
Fix unlistening of upstream events

* Bar graph composition policy fix to allow condition set creation. (#4598)

* Use image timestamp instead of image index to show large view (#4591)

* Use image timestamp instead of image index to show large view

* Fix failing test

Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>
2021-12-17 12:57:49 -08:00
Andrew Henry
2d64813a4f Rewrite local storage (#4583)
* Reimplementation of Local Storage provider

* Added tests

* Remove identifierService mocks from all test specs

* Do not persist identifiers in couch

* Constant rename

* Fix broken test

* Clean up mock window functions

* Updated copyright notice

* Fixed bug in in-memory search indexer
2021-12-17 12:14:35 -08:00
Andrew Henry
fd0e89ca05 Remove legacy formats (#4531)
* Remove legacy bundles

* Replace legacy format management with vanilla JS

* Redefine legacy formats in plugins instead of bundles

* Remove 'draft' from API documentation

* Remove focus from test spec

* Register local time system using new API

* Fixed broken custom formatter test spec

* Make capitalization consistent

* Rewrite test for terse formatting for time conductor

* Make dependency on UTCTimeFormat explicit

Co-authored-by: John Hill <john.c.hill@nasa.gov>
2021-12-17 09:11:13 -08:00
59 changed files with 645 additions and 1077 deletions

4
API.md
View File

@@ -27,7 +27,7 @@
- [Request Strategies **draft**](#request-strategies-draft)
- [`latest` request strategy](#latest-request-strategy)
- [`minmax` request strategy](#minmax-request-strategy)
- [Telemetry Formats **draft**](#telemetry-formats-draft)
- [Telemetry Formats](#telemetry-formats)
- [Registering Formats](#registering-formats)
- [Telemetry Data](#telemetry-data)
- [Telemetry Datums](#telemetry-datums)
@@ -525,7 +525,7 @@ example:
MinMax queries are issued by plots, and may be issued by other types as well. The aim is to reduce the amount of data returned but still faithfully represent the full extent of the data. In order to do this, the view calculates the maximum data resolution it can display (i.e. the number of horizontal pixels in a plot) and sends that as the `size`. The response should include at least one minimum and one maximum value per point of resolution.
#### Telemetry Formats **draft**
#### Telemetry Formats
Telemetry format objects define how to interpret and display telemetry data.
They have a simple structure:

View File

@@ -7,6 +7,7 @@ const config = {
retries: 0,
testDir: 'tests',
timeout: 90 * 1000,
workers: 1,
webServer: {
command: 'npm run start',
port: 8080,

View File

@@ -20,42 +20,29 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
"./src/LocalStoragePersistenceProvider",
"./src/LocalStorageIndicator"
], function (
LocalStoragePersistenceProvider,
LocalStorageIndicator
) {
/*
This test suite is dedicated to tests which verify the basic operations surrounding conditionSets.
*/
return {
name: "platform/persistence/local",
definition: {
"extensions": {
"components": [
{
"provides": "persistenceService",
"type": "provider",
"implementation": LocalStoragePersistenceProvider,
"depends": [
"$window",
"$q",
"PERSISTENCE_SPACE"
]
}
],
"constants": [
{
"key": "PERSISTENCE_SPACE",
"value": "mct"
}
],
"indicators": [
{
"implementation": LocalStorageIndicator
}
]
}
}
};
const { test, expect } = require('@playwright/test');
test.describe('condition set', () => {
test('create new button `condition set` creates new condition object', async ({ page }) => {
//Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
//Click the Create button
await page.click('button:has-text("Create")');
// Click text=Condition Set
await page.click('text=Condition Set');
// Click text=OK
await Promise.all([
page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/dab945d4-5a84-480e-8180-222b4aa730fa?tc.mode=fixed&tc.startBound=1639696164435&tc.endBound=1639697964435&tc.timeSystem=utc&view=conditionSet.view' }*/),
page.click('text=OK')
]);
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set');
});
});

View File

@@ -21,11 +21,15 @@
*****************************************************************************/
/*
Collection of Visual Tests set to run in a default context. These should only use functional
expect statements to verify assumptions about the state in a test and not for functional
verification of correctness.
Note: Larger testsuite sizes are OK due to the setup time associated with these tests. Visual
tests are not supposed to "fail" on assertions.
Collection of Visual Tests set to run in a default context. The tests within this suite
are only meant to run against openmct's app.js started by `npm run start` within the
`./e2e/playwright-visual.config.js` file.
These should only use functional expect statements to verify assumptions about the state
in a test and not for functional verification of correctness. Visual tests are not supposed
to "fail" on assertions. Instead, they should be used to detect changes between builds or branches.
Note: Larger testsuite sizes are OK due to the setup time associated with these tests.
*/
const { test, expect } = require('@playwright/test');
@@ -71,3 +75,39 @@ test('Visual - Root and About', async ({ page }) => {
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
await percySnapshot(page, 'About');
});
test('Visual - Default Condition Set', async ({ page }) => {
//Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
//Click the Create button
await page.click('button:has-text("Create")');
// Click text=Condition Set
await page.click('text=Condition Set');
// Click text=OK
await page.click('text=OK');
// Take a snapshot of the newly created Condition Set object
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
await percySnapshot(page, 'Default Condition Set');
});
test('Visual - Default Condition Widget', async ({ page }) => {
//Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
//Click the Create button
await page.click('button:has-text("Create")');
// Click text=Condition Widget
await page.click('text=Condition Widget');
// Click text=OK
await page.click('text=OK');
// Take a snapshot of the newly created Condition Widget object
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
await percySnapshot(page, 'Default Condition Widget');
});

View File

@@ -104,7 +104,7 @@ module.exports = (config) => {
reports: ['lcovonly', 'text-summary'],
thresholds: {
global: {
lines: 66
lines: 70
}
}
},

View File

@@ -1,6 +1,6 @@
{
"name": "openmct",
"version": "1.8.1",
"version": "1.8.2",
"description": "The Open MCT core platform",
"devDependencies": {
"@braintree/sanitize-url": "^5.0.2",
@@ -20,7 +20,7 @@
"d3-scale": "1.0.x",
"d3-selection": "1.3.x",
"eslint": "7.0.0",
"eslint-plugin-playwright": "0.6.0",
"eslint-plugin-playwright": "0.7.0",
"eslint-plugin-vue": "^7.5.0",
"eslint-plugin-you-dont-need-lodash-underscore": "^6.10.0",
"eventemitter3": "^1.2.0",

View File

@@ -1,72 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
"./src/FormatProvider",
"./src/DurationFormat"
], function (
FormatProvider,
DurationFormat
) {
return {
name: "platform/commonUI/formats",
definition: {
"name": "Format Registry",
"description": "Provides a registry for formats, which allow parsing and formatting of values.",
"extensions": {
"components": [
{
"provides": "formatService",
"type": "provider",
"implementation": FormatProvider,
"depends": [
"formats[]"
]
}
],
"formats": [
{
"key": "duration",
"implementation": DurationFormat
}
],
"constants": [
{
"key": "DEFAULT_TIME_FORMAT",
"value": "utc"
}
],
"licenses": [
{
"name": "d3",
"version": "3.0.0",
"description": "Incorporates modified code from d3 Time Scales",
"author": "Mike Bostock",
"copyright": "Copyright 2010-2016 Mike Bostock. "
+ "All rights reserved.",
"link": "https://github.com/d3/d3/blob/master/LICENSE"
}
]
}
}
};
});

View File

@@ -1,62 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'moment'
], function (
moment
) {
var DATE_FORMAT = "HH:mm:ss",
DATE_FORMATS = [
DATE_FORMAT
];
/**
* Formatter for duration. Uses moment to produce a date from a given
* value, but output is formatted to display only time. Can be used for
* specifying a time duration. For specifying duration, it's best to
* specify a date of January 1, 1970, as the ms offset will equal the
* duration represented by the time.
*
* @implements {Format}
* @constructor
* @memberof platform/commonUI/formats
*/
function DurationFormat() {
this.key = "duration";
}
DurationFormat.prototype.format = function (value) {
return moment.utc(value).format(DATE_FORMAT);
};
DurationFormat.prototype.parse = function (text) {
return moment.duration(text).asMilliseconds();
};
DurationFormat.prototype.validate = function (text) {
return moment.utc(text, DATE_FORMATS, true).isValid();
};
return DurationFormat;
});

View File

@@ -1,123 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
], function (
) {
/**
* An object used to convert between numeric values and text values,
* typically used to display these values to the user and to convert
* user input to a numeric format, particularly for time formats.
* @interface Format
*/
/**
* Parse text (typically user input) to a numeric value.
* Behavior is undefined when the text cannot be parsed;
* `validate` should be called first if the text may be invalid.
* @method Format#parse
* @memberof Format#
* @param {string} text the text to parse
* @returns {number} the parsed numeric value
*/
/**
* @property {string} key A unique identifier for this formatter.
* @memberof Format#
*/
/**
* Determine whether or not some text (typically user input) can
* be parsed to a numeric value by this format.
* @method validate
* @memberof Format#
* @param {string} text the text to parse
* @returns {boolean} true if the text can be parsed
*/
/**
* Convert a numeric value to a text value for display using
* this format.
* @method format
* @memberof Format#
* @param {number} value the numeric value to format
* @param {number} [minValue] Contextual information for scaled formatting used in linear scales such as conductor
* and plot axes. Specifies the smallest number on the scale.
* @param {number} [maxValue] Contextual information for scaled formatting used in linear scales such as conductor
* and plot axes. Specifies the largest number on the scale
* @param {number} [count] Contextual information for scaled formatting used in linear scales such as conductor
* and plot axes. The number of labels on the scale.
* @returns {string} the text representation of the value
*/
/**
* Provides access to `Format` objects which can be used to
* convert values between human-readable text and numeric
* representations.
* @interface FormatService
*/
/**
* Look up a format by its symbolic identifier.
* @method getFormat
* @memberof FormatService#
* @param {string} key the identifier for this format
* @returns {Format} the format
* @throws {Error} errors when the requested format is unrecognized
*/
/**
* Provides formats from the `formats` extension category.
* @constructor
* @implements {FormatService}
* @memberof platform/commonUI/formats
* @param {Array.<function(new : Format)>} format constructors,
* from the `formats` extension category.
*/
function FormatProvider(formats) {
var formatMap = {};
function addToMap(Format) {
var key = Format.key;
if (key && !formatMap[key]) {
formatMap[key] = new Format();
}
}
formats.forEach(addToMap);
this.formatMap = formatMap;
}
FormatProvider.prototype.getFormat = function (key) {
var format = this.formatMap[key];
if (!format) {
throw new Error("FormatProvider: No format found for " + key);
}
return format;
};
return FormatProvider;
});

View File

@@ -1,69 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
['../src/FormatProvider'],
function (FormatProvider) {
var KEYS = ['a', 'b', 'c'];
describe("The FormatProvider", function () {
var mockFormats,
mockFormatInstances,
provider;
beforeEach(function () {
mockFormatInstances = KEYS.map(function (k) {
return jasmine.createSpyObj(
'format-' + k,
['parse', 'validate', 'format']
);
});
// Return constructors
mockFormats = KEYS.map(function (k, i) {
function MockFormat() {
return mockFormatInstances[i];
}
MockFormat.key = k;
return MockFormat;
});
provider = new FormatProvider(mockFormats);
});
it("looks up formats by key", function () {
KEYS.forEach(function (k, i) {
expect(provider.getFormat(k))
.toEqual(mockFormatInstances[i]);
});
});
it("throws an error about unknown formats", function () {
expect(function () {
provider.getFormat('some-unknown-format');
}).toThrow();
});
});
}
);

View File

@@ -1,9 +0,0 @@
# Local Storage Plugin
Provides persistence of user-created objects in browser Local Storage. Objects persisted in this way will only be
available from the browser and machine on which they were persisted. For shared persistence, consider the
[Elasticsearch](../elastic/) and [CouchDB](../couch/) persistence plugins.
## Installation
```js
openmct.install(openmct.plugins.LocalStorage());
```

View File

@@ -1,61 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[],
function () {
var LOCAL_STORAGE_WARNING = [
"Using browser local storage for persistence.",
"Anything you create or change will only be saved",
"in this browser on this machine."
].join(' ');
/**
* Indicator for local storage persistence. Provides a minimum
* level of feedback indicating that local storage is in use.
* @constructor
* @memberof platform/persistence/local
* @implements {Indicator}
*/
function LocalStorageIndicator() {
}
LocalStorageIndicator.prototype.getCssClass = function () {
return "c-indicator--clickable icon-suitcase s-status-caution";
};
LocalStorageIndicator.prototype.getGlyphClass = function () {
return 'caution';
};
LocalStorageIndicator.prototype.getText = function () {
return "Off-line storage";
};
LocalStorageIndicator.prototype.getDescription = function () {
return LOCAL_STORAGE_WARNING;
};
return LocalStorageIndicator;
}
);

View File

@@ -1,97 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[],
function () {
/**
* The LocalStoragePersistenceProvider reads and writes JSON documents
* (more specifically, domain object models) to/from the browser's
* local storage.
* @memberof platform/persistence/local
* @constructor
* @implements {PersistenceService}
* @param q Angular's $q, for promises
* @param $interval Angular's $interval service
* @param {string} space the name of the persistence space being served
*/
function LocalStoragePersistenceProvider($window, $q, space) {
this.$q = $q;
this.space = space;
this.spaces = space ? [space] : [];
this.localStorage = $window.localStorage;
}
/**
* Set a value in local storage.
* @private
*/
LocalStoragePersistenceProvider.prototype.setValue = function (key, value) {
this.localStorage[key] = JSON.stringify(value);
};
/**
* Get a value from local storage.
* @private
*/
LocalStoragePersistenceProvider.prototype.getValue = function (key) {
return this.localStorage[key]
? JSON.parse(this.localStorage[key]) : {};
};
LocalStoragePersistenceProvider.prototype.listSpaces = function () {
return this.$q.when(this.spaces);
};
LocalStoragePersistenceProvider.prototype.listObjects = function (space) {
return this.$q.when(Object.keys(this.getValue(space)));
};
LocalStoragePersistenceProvider.prototype.createObject = function (space, key, value) {
var spaceObj = this.getValue(space);
spaceObj[key] = value;
this.setValue(space, spaceObj);
return this.$q.when(true);
};
LocalStoragePersistenceProvider.prototype.readObject = function (space, key) {
var spaceObj = this.getValue(space);
return this.$q.when(spaceObj[key]);
};
LocalStoragePersistenceProvider.prototype.deleteObject = function (space, key) {
var spaceObj = this.getValue(space);
delete spaceObj[key];
this.setValue(space, spaceObj);
return this.$q.when(true);
};
LocalStoragePersistenceProvider.prototype.updateObject =
LocalStoragePersistenceProvider.prototype.createObject;
return LocalStoragePersistenceProvider;
}
);

View File

@@ -1,58 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
["../src/LocalStorageIndicator"],
function (LocalStorageIndicator) {
xdescribe("The local storage status indicator", function () {
var indicator;
beforeEach(function () {
indicator = new LocalStorageIndicator();
});
it("provides text to display in status area", function () {
// Don't particularly care what is there so long
// as interface is appropriately implemented.
expect(indicator.getText()).toEqual(jasmine.any(String));
});
it("has a database icon", function () {
expect(indicator.getCssClass()).toEqual("icon-suitcase s-status-caution");
});
it("has a 'caution' class to draw attention", function () {
expect(indicator.getGlyphClass()).toEqual("caution");
});
it("provides a description for a tooltip", function () {
// Just want some non-empty string here. Providing a
// message here is important but don't want to test wording.
var description = indicator.getDescription();
expect(description).toEqual(jasmine.any(String));
expect(description.length).not.toEqual(0);
});
});
}
);

View File

@@ -1,113 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
["../src/LocalStoragePersistenceProvider"],
function (LocalStoragePersistenceProvider) {
describe("The local storage persistence provider", function () {
var mockQ,
testSpace = "testSpace",
mockCallback,
testLocalStorage,
provider;
function mockPromise(value) {
return (value || {}).then ? value : {
then: function (callback) {
return mockPromise(callback(value));
}
};
}
beforeEach(function () {
testLocalStorage = {};
mockQ = jasmine.createSpyObj("$q", ["when", "reject"]);
mockCallback = jasmine.createSpy('callback');
mockQ.when.and.callFake(mockPromise);
provider = new LocalStoragePersistenceProvider(
{ localStorage: testLocalStorage },
mockQ,
testSpace
);
});
it("reports available spaces", function () {
provider.listSpaces().then(mockCallback);
expect(mockCallback).toHaveBeenCalledWith([testSpace]);
});
it("lists all available documents", function () {
provider.listObjects(testSpace).then(mockCallback);
expect(mockCallback.calls.mostRecent().args[0]).toEqual([]);
provider.createObject(testSpace, 'abc', { a: 42 });
provider.listObjects(testSpace).then(mockCallback);
expect(mockCallback.calls.mostRecent().args[0]).toEqual(['abc']);
});
it("allows object creation", function () {
var model = { someKey: "some value" };
provider.createObject(testSpace, "abc", model)
.then(mockCallback);
expect(JSON.parse(testLocalStorage[testSpace]).abc)
.toEqual(model);
expect(mockCallback.calls.mostRecent().args[0]).toBeTruthy();
});
it("allows object models to be read back", function () {
var model = { someKey: "some other value" };
testLocalStorage[testSpace] = JSON.stringify({ abc: model });
provider.readObject(testSpace, "abc").then(mockCallback);
expect(mockCallback).toHaveBeenCalledWith(model);
});
it("allows object update", function () {
var model = { someKey: "some new value" };
testLocalStorage[testSpace] = JSON.stringify({
abc: { somethingElse: 42 }
});
provider.updateObject(testSpace, "abc", model)
.then(mockCallback);
expect(JSON.parse(testLocalStorage[testSpace]).abc)
.toEqual(model);
});
it("allows object deletion", function () {
testLocalStorage[testSpace] = JSON.stringify({
abc: { somethingElse: 42 }
});
provider.deleteObject(testSpace, "abc").then(mockCallback);
expect(testLocalStorage[testSpace].abc)
.toBeUndefined();
});
it("returns undefined when objects are not found", function () {
provider.readObject("testSpace", "abc").then(mockCallback);
expect(mockCallback).toHaveBeenCalledWith(undefined);
});
});
}
);

View File

@@ -255,8 +255,11 @@ define(
// If a telemetryService is not available,
// getTelemetryService() should reject, and this should
// bubble through subsequent then calls.
return telemetryService
&& requestTelemetryFromService().then(getRelevantResponse);
if (!telemetryService) {
return Promise.reject(new Error('TelemetryService is not available'));
}
return requestTelemetryFromService().then(getRelevantResponse);
} else {
return telemetryAPI.request(domainObject, fullRequest).then(function (telemetry) {
return asSeries(telemetry, defaultDomain, defaultRange, sourceMap);

View File

@@ -41,6 +41,9 @@ define(
return {
then: function (callback) {
return mockPromise(callback(value));
},
catch: (rejected) => {
return Promise.reject(rejected);
}
};
}
@@ -225,19 +228,6 @@ define(
});
});
it("warns if no telemetry service can be injected", function () {
mockInjector.get.and.callFake(function () {
throw "";
});
// Verify precondition
expect(mockLog.warn).not.toHaveBeenCalled();
telemetry.requestData();
expect(mockLog.info).toHaveBeenCalled();
});
it("if a new style telemetry source is available, use it", function () {
var mockProvider = {};
mockTelemetryAPI.findSubscriptionProvider.and.returnValue(mockProvider);

View File

@@ -299,7 +299,6 @@ define([
this.install(this.plugins.ObjectInterceptors());
this.install(this.plugins.NonEditableFolder());
this.install(this.plugins.DeviceClassifier());
this.install(this.plugins.UTCTimeFormat());
}
MCT.prototype = Object.create(EventEmitter.prototype);

View File

@@ -29,22 +29,10 @@ describe('The ActionCollection', () => {
let mockApplicableActions;
let mockObjectPath;
let mockView;
let mockIdentifierService;
beforeEach(() => {
openmct = createOpenMct();
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
mockIdentifierService = jasmine.createSpyObj(
'identifierService',
['parse']
);
mockIdentifierService.parse.and.returnValue({
getSpace: () => {
return '';
}
});
openmct.$injector.get.and.returnValue(mockIdentifierService);
mockObjectPath = [
{
name: 'mock folder',

View File

@@ -187,7 +187,7 @@ class InMemorySearchProvider {
*/
scheduleForIndexing(identifier) {
const keyString = this.openmct.objects.makeKeyString(identifier);
const objectProvider = this.openmct.objects.getProvider(identifier.key);
const objectProvider = this.openmct.objects.getProvider(identifier);
if (objectProvider === undefined || objectProvider.search === undefined) {
if (!this.indexedIds[keyString] && !this.pendingIndex[keyString]) {

View File

@@ -43,9 +43,6 @@ function ObjectAPI(typeRegistry, openmct) {
this.providers = {};
this.rootRegistry = new RootRegistry();
this.inMemorySearchProvider = new InMemorySearchProvider(openmct);
this.injectIdentifierService = function () {
this.identifierService = this.openmct.$injector.get("identifierService");
};
this.rootProvider = new RootObjectProvider(this.rootRegistry);
this.cache = {};
@@ -66,33 +63,17 @@ ObjectAPI.prototype.supersecretSetFallbackProvider = function (p) {
this.fallbackProvider = p;
};
/**
* @private
*/
ObjectAPI.prototype.getIdentifierService = function () {
// Lazily acquire identifier service
if (!this.identifierService) {
this.injectIdentifierService();
}
return this.identifierService;
};
/**
* Retrieve the provider for a given identifier.
* @private
*/
ObjectAPI.prototype.getProvider = function (identifier) {
//handles the '' vs 'mct' namespace issue
const keyString = utils.makeKeyString(identifier);
const identifierService = this.getIdentifierService();
const namespace = identifierService.parse(keyString).getSpace();
if (identifier.key === 'ROOT') {
return this.rootProvider;
}
return this.providers[namespace] || this.fallbackProvider;
return this.providers[identifier.namespace] || this.fallbackProvider;
};
/**

View File

@@ -5,7 +5,6 @@ describe("The Object API", () => {
let objectAPI;
let typeRegistry;
let openmct = {};
let mockIdentifierService;
let mockDomainObject;
const TEST_NAMESPACE = "test-namespace";
const FIFTEEN_MINUTES = 15 * 60 * 1000;
@@ -15,18 +14,6 @@ describe("The Object API", () => {
'get'
]);
openmct = createOpenMct();
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
mockIdentifierService = jasmine.createSpyObj(
'identifierService',
['parse']
);
mockIdentifierService.parse.and.returnValue({
getSpace: () => {
return TEST_NAMESPACE;
}
});
openmct.$injector.get.and.returnValue(mockIdentifierService);
objectAPI = openmct.objects;
openmct.editor = {};

View File

@@ -9,7 +9,7 @@ describe("Transaction Class", () => {
beforeEach(() => {
objectAPI = {
makeKeyString: (identifier) => utils.makeKeyString(identifier),
save: (object) => object,
save: () => Promise.resolve(true),
mutate: (object, prop, value) => {
object[prop] = value;
@@ -60,7 +60,8 @@ describe("Transaction Class", () => {
mockDomainObjects.forEach(transaction.add.bind(transaction));
expect(transaction.dirtyObjects.size).toEqual(3);
spyOn(objectAPI, 'save');
spyOn(objectAPI, 'save').and.callThrough();
transaction.commit()
.then(success => {
expect(transaction.dirtyObjects.size).toEqual(0);

View File

@@ -137,14 +137,17 @@ define([
*/
function TelemetryAPI(openmct) {
this.openmct = openmct;
this.requestProviders = [];
this.subscriptionProviders = [];
this.metadataProviders = [new DefaultMetadataProvider(this.openmct)];
this.formatMapCache = new WeakMap();
this.formatters = new Map();
this.limitProviders = [];
this.metadataCache = new WeakMap();
this.formatMapCache = new WeakMap();
this.valueFormatterCache = new WeakMap();
this.metadataProviders = [new DefaultMetadataProvider(this.openmct)];
this.noRequestProviderForAllObjects = false;
this.requestAbortControllers = new Set();
this.requestProviders = [];
this.subscriptionProviders = [];
this.valueFormatterCache = new WeakMap();
}
TelemetryAPI.prototype.abortAllRequests = function () {
@@ -313,6 +316,10 @@ define([
* telemetry data
*/
TelemetryAPI.prototype.request = function (domainObject) {
if (this.noRequestProviderForAllObjects) {
return Promise.resolve([]);
}
if (arguments.length === 1) {
arguments.length = 2;
arguments[1] = {};
@@ -325,19 +332,22 @@ define([
this.standardizeRequestOptions(arguments[1]);
const provider = this.findRequestProvider.apply(this, arguments);
if (!provider) {
return Promise.reject('No provider found');
this.requestAbortControllers.delete(abortController);
return this.handleMissingRequestProvider(domainObject);
}
return provider.request.apply(provider, arguments).catch((rejected) => {
if (rejected.name !== 'AbortError') {
this.openmct.notifications.error('Error requesting telemetry data, see console for details');
console.error(rejected);
}
return provider.request.apply(provider, arguments)
.catch((rejected) => {
if (rejected.name !== 'AbortError') {
this.openmct.notifications.error('Error requesting telemetry data, see console for details');
console.error(rejected);
}
return Promise.reject(rejected);
}).finally(() => {
this.requestAbortControllers.delete(abortController);
});
return Promise.reject(rejected);
}).finally(() => {
this.requestAbortControllers.delete(abortController);
});
};
/**
@@ -445,17 +455,6 @@ define([
return _.sortBy(options, sortKeys);
};
/**
* @private
*/
TelemetryAPI.prototype.getFormatService = function () {
if (!this.formatService) {
this.formatService = this.openmct.$injector.get('formatService');
}
return this.formatService;
};
/**
* Get a value formatter for a given valueMetadata.
*
@@ -465,7 +464,7 @@ define([
if (!this.valueFormatterCache.has(valueMetadata)) {
this.valueFormatterCache.set(
valueMetadata,
new TelemetryValueFormatter(valueMetadata, this.getFormatService())
new TelemetryValueFormatter(valueMetadata, this.formatters)
);
}
@@ -479,9 +478,7 @@ define([
* @returns {Format}
*/
TelemetryAPI.prototype.getFormatter = function (key) {
const formatMap = this.getFormatService().formatMap;
return formatMap[key];
return this.formatters.get(key);
};
/**
@@ -507,17 +504,42 @@ define([
return this.formatMapCache.get(metadata);
};
/**
* Error Handling: Missing Request provider
*
* @returns Promise
*/
TelemetryAPI.prototype.handleMissingRequestProvider = function (domainObject) {
this.noRequestProviderForAllObjects = this.requestProviders.every(requestProvider => {
const supportsRequest = requestProvider.supportsRequest.apply(requestProvider, arguments);
const hasRequestProvider = Object.hasOwn(requestProvider, 'request');
return supportsRequest && hasRequestProvider;
});
let message = '';
let detailMessage = '';
if (this.noRequestProviderForAllObjects) {
message = 'Missing request providers, see console for details';
detailMessage = 'Missing request provider for all request providers';
} else {
message = 'Missing request provider, see console for details';
const { name, identifier } = domainObject;
detailMessage = `Missing request provider for domainObject, name: ${name}, identifier: ${JSON.stringify(identifier)}`;
}
this.openmct.notifications.error(message);
console.error(detailMessage);
return Promise.resolve([]);
};
/**
* Register a new telemetry data formatter.
* @param {Format} format the
*/
TelemetryAPI.prototype.addFormat = function (format) {
this.openmct.legacyExtension('formats', {
key: format.key,
implementation: function () {
return format;
}
});
this.formatters.set(format.key, format);
};
/**

View File

@@ -24,7 +24,6 @@ import TelemetryAPI from './TelemetryAPI';
const { TelemetryCollection } = require("./TelemetryCollection");
describe('Telemetry API', function () {
const NO_PROVIDER = 'No provider found';
let openmct;
let telemetryAPI;
@@ -66,6 +65,12 @@ describe('Telemetry API', function () {
},
type: 'sample-type'
};
openmct.notifications = {
error: () => {
console.log('sample error notification');
}
};
});
it('provides consistent results without providers', function (done) {
@@ -73,12 +78,11 @@ describe('Telemetry API', function () {
expect(unsubscribe).toEqual(jasmine.any(Function));
telemetryAPI.request(domainObject).then(
() => {},
(error) => {
expect(error).toBe(NO_PROVIDER);
}
).finally(done);
telemetryAPI.request(domainObject)
.then((data) => {
expect(data).toEqual([]);
})
.finally(done);
});
it('skips providers that do not match', function (done) {
@@ -98,8 +102,6 @@ describe('Telemetry API', function () {
expect(telemetryProvider.supportsRequest)
.toHaveBeenCalledWith(domainObject, jasmine.any(Object));
expect(telemetryProvider.request).not.toHaveBeenCalled();
}, (error) => {
expect(error).toBe(NO_PROVIDER);
}).finally(done);
});

View File

@@ -29,7 +29,7 @@ define([
) {
// TODO: needs reference to formatService;
function TelemetryValueFormatter(valueMetadata, formatService) {
function TelemetryValueFormatter(valueMetadata, formatMap) {
const numberFormatter = {
parse: function (x) {
return Number(x);
@@ -43,13 +43,7 @@ define([
};
this.valueMetadata = valueMetadata;
try {
this.formatter = formatService
.getFormat(valueMetadata.format, valueMetadata);
} catch (e) {
// TODO: Better formatting
this.formatter = numberFormatter;
}
this.formatter = formatMap.get(valueMetadata.format) || numberFormatter;
if (valueMetadata.format === 'enum') {
this.formatter = {};

View File

@@ -145,7 +145,6 @@ class IndependentTimeContext extends TimeContext {
/**
* Causes this time context to follow another time context (either the global context, or another upstream time context)
* This allows views to have their own time context which points to the appropriate upstream context as necessary, achieving nesting.
* @param {*} upstreamTimeContext
*/
followTimeContext() {
this.stopFollowingTimeContext();
@@ -153,7 +152,9 @@ class IndependentTimeContext extends TimeContext {
TIME_CONTEXT_EVENTS.forEach((eventName) => {
const thisTimeContext = this;
this.upstreamTimeContext.on(eventName, passthrough);
this.unlisteners.push(() => this.upstreamTimeContext.off(eventName, passthrough));
this.unlisteners.push(() => {
thisTimeContext.upstreamTimeContext.off(eventName, passthrough);
});
function passthrough() {
thisTimeContext.emit(eventName, ...arguments);
}
@@ -167,6 +168,7 @@ class IndependentTimeContext extends TimeContext {
*/
stopFollowingTimeContext() {
this.unlisteners.forEach(unlisten => unlisten());
this.unlisteners = [];
}
resetContext() {
@@ -180,17 +182,19 @@ class IndependentTimeContext extends TimeContext {
* Refresh the time context, following any upstream time contexts as necessary
*/
refreshContext(viewKey) {
//TODO: find a better way to skip upstream context for the view that just got an independent time context
const key = this.openmct.objects.makeKeyString(this.objectPath[0].identifier);
if (viewKey && key === viewKey) {
return;
}
//this is necessary as the upstream context gets reassigned after this
this.stopFollowingTimeContext();
this.upstreamTimeContext = this.getUpstreamContext();
this.followTimeContext();
// Emit bounds so that views that are changing context get the upstream bounds
this.emit('bounds', this.upstreamTimeContext.bounds());
this.emit('bounds', this.bounds());
}
hasOwnContext() {

View File

@@ -27,7 +27,6 @@ const DEFAULTS = [
'platform/commonUI/browse',
'platform/commonUI/edit',
'platform/commonUI/dialog',
'platform/commonUI/formats',
'platform/commonUI/general',
'platform/commonUI/inspect',
'platform/commonUI/mobile',
@@ -60,7 +59,6 @@ define([
'../platform/commonUI/browse/bundle',
'../platform/commonUI/dialog/bundle',
'../platform/commonUI/edit/bundle',
'../platform/commonUI/formats/bundle',
'../platform/commonUI/general/bundle',
'../platform/commonUI/inspect/bundle',
'../platform/commonUI/mobile/bundle',
@@ -76,7 +74,6 @@ define([
'../platform/identity/bundle',
'../platform/persistence/aggregator/bundle',
'../platform/persistence/elastic/bundle',
'../platform/persistence/local/bundle',
'../platform/persistence/queue/bundle',
'../platform/policy/bundle',
'../platform/representation/bundle',

View File

@@ -1,94 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import UTCTimeFormat from './UTCTimeFormat.js';
describe('the plugin', () => {
const UTC_KEY = 'utc';
const JUNK = 'junk';
const MOON_LANDING_TIMESTAMP = -14256000000;
const MOON_LANDING_DEFAULT_FORMAT = '1969-07-20 00:00:00.000Z';
const MOON_LANDING_FORMATTED_DATES = [
'1969-07-20 00:00:00.000',
'1969-07-20 00:00:00.000+00:00',
'1969-07-20 00:00:00',
'1969-07-20 00:00',
'1969-07-20'
];
let utcFormatter;
beforeEach(() => {
utcFormatter = new UTCTimeFormat();
});
describe('creates a new UTC based formatter', function () {
it("with the key 'utc'", () => {
expect(utcFormatter.key).toBe(UTC_KEY);
});
it('that will format a timestamp in UTC Standard Date', () => {
//default format
expect(utcFormatter.format(MOON_LANDING_TIMESTAMP)).toBe(
MOON_LANDING_DEFAULT_FORMAT
);
//possible formats
const formattedDates = utcFormatter.DATE_FORMATS.map((format) =>
utcFormatter.format(MOON_LANDING_TIMESTAMP, format)
);
expect(formattedDates).toEqual(MOON_LANDING_FORMATTED_DATES);
});
it('that will parse an UTC Standard Date into milliseconds', () => {
//default format
expect(utcFormatter.parse(MOON_LANDING_DEFAULT_FORMAT)).toBe(
MOON_LANDING_TIMESTAMP
);
//possible formats
const parsedDates = MOON_LANDING_FORMATTED_DATES.map((format) =>
utcFormatter.parse(format)
);
parsedDates.forEach((v) => expect(v).toEqual(MOON_LANDING_TIMESTAMP));
});
it('that will validate correctly', () => {
//default format
expect(utcFormatter.validate(MOON_LANDING_DEFAULT_FORMAT)).toBe(
true
);
//possible formats
const validatedFormats = MOON_LANDING_FORMATTED_DATES.map((date) =>
utcFormatter.validate(date)
);
validatedFormats.forEach((v) => expect(v).toBe(true));
//junk
expect(utcFormatter.validate(JUNK)).toBe(false);
});
});
});

View File

@@ -41,14 +41,10 @@ export default function BarGraphCompositionPolicy(openmct) {
return {
allow: function (parent, child) {
if (child.type === 'conditionSet') {
return false;
}
if ((parent.type === BAR_GRAPH_KEY)
&& (!hasBarGraphTelemetry(child))
) {
return false;
if (parent.type === BAR_GRAPH_KEY) {
if ((child.type === 'conditionSet') || (!hasBarGraphTelemetry(child))) {
return false;
}
}
return true;

View File

@@ -810,21 +810,6 @@ describe('the plugin', function () {
openmct.telemetry.getMetadata.and.returnValue(testTelemetryObject.telemetry);
openmct.telemetry.request.and.returnValue(Promise.resolve([]));
// mockTransactionService.commit = async () => {};
const mockIdentifierService = jasmine.createSpyObj(
'identifierService',
['parse']
);
mockIdentifierService.parse.and.returnValue({
getSpace: () => {
return '';
}
});
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
openmct.$injector.get.withArgs('identifierService').and.returnValue(mockIdentifierService);
// .withArgs('transactionService').and.returnValue(mockTransactionService);
const styleRuleManger = new StyleRuleManager(stylesObject, openmct, null, true);
spyOn(styleRuleManger, 'subscribeToConditionSet');
openmct.editor.edit();

View File

@@ -44,13 +44,12 @@ describe('CustomStringFormatter', function () {
element = document.createElement('div');
child = document.createElement('div');
element.appendChild(child);
CUSTOM_FORMATS.forEach(openmct.telemetry.addFormat.bind({openmct}));
CUSTOM_FORMATS.forEach((formatter) => {
openmct.telemetry.addFormat(formatter);
});
openmct.on('start', done);
openmct.startHeadless();
spyOn(openmct.telemetry, 'getFormatter');
openmct.telemetry.getFormatter.and.callFake((key) => CUSTOM_FORMATS.find(d => d.key === key));
customStringFormatter = new CustomStringFormatter(openmct, valueMetadata);
});

View File

@@ -60,6 +60,7 @@ export default class ExportAsJSONAction {
* @param {object} objectpath
*/
invoke(objectpath) {
this.tree = {};
const root = objectpath[0];
this.root = JSON.parse(JSON.stringify(root));
const rootId = this._getId(this.root);

View File

@@ -37,9 +37,11 @@ export default class EditPropertiesAction extends PropertiesAction {
}
appliesTo(objectPath) {
const definition = this._getTypeDefinition(objectPath[0].type);
const object = objectPath[0];
const definition = this._getTypeDefinition(object.type);
const persistable = this.openmct.objects.isPersistable(object.identifier);
return definition && definition.creatable;
return persistable && definition && definition.creatable;
}
invoke(objectPath) {

View File

@@ -12,9 +12,9 @@ export default class ImageryView {
show(element, isEditing, viewOptions) {
let alternateObjectPath;
let indexForFocusedImage;
let focusedImageTimestamp;
if (viewOptions) {
indexForFocusedImage = viewOptions.indexForFocusedImage;
focusedImageTimestamp = viewOptions.timestamp;
alternateObjectPath = viewOptions.objectPath;
}
@@ -31,10 +31,10 @@ export default class ImageryView {
},
data() {
return {
indexForFocusedImage
focusedImageTimestamp
};
},
template: '<imagery-view :index-for-focused-image="indexForFocusedImage" ref="ImageryContainer"></imagery-view>'
template: '<imagery-view :focused-image-timestamp="focusedImageTimestamp" ref="ImageryContainer"></imagery-view>'
});
}

View File

@@ -119,10 +119,10 @@ export default {
this.timeContext.off("bounds", this.updateViewBounds);
}
},
expand(index) {
expand(imageTimestamp) {
const path = this.objectPath[0];
this.previewAction.invoke([path], {
indexForFocusedImage: index,
timestamp: imageTimestamp,
objectPath: this.objectPath
});
},
@@ -395,7 +395,7 @@ export default {
//handle mousedown event to show the image in a large view
imageWrapper.addEventListener('mousedown', (e) => {
if (e.button === 0) {
this.expand(index);
this.expand(item.time);
}
});

View File

@@ -201,7 +201,7 @@ export default {
mixins: [imageryData],
inject: ['openmct', 'domainObject', 'objectPath', 'currentView'],
props: {
indexForFocusedImage: {
focusedImageTimestamp: {
type: Number,
default() {
return undefined;
@@ -411,8 +411,11 @@ export default {
watch: {
imageHistorySize(newSize, oldSize) {
let imageIndex;
if (this.indexForFocusedImage !== undefined) {
imageIndex = this.initFocusedImageIndex;
if (this.focusedImageTimestamp !== undefined) {
const foundImageIndex = this.imageHistory.findIndex(image => {
return image.time === this.focusedImageTimestamp;
});
imageIndex = foundImageIndex > -1 ? foundImageIndex : newSize - 1;
} else {
imageIndex = newSize > 0 ? newSize - 1 : undefined;
}
@@ -429,8 +432,7 @@ export default {
},
async mounted() {
//We only need to use this till the user focuses an image manually
if (this.indexForFocusedImage !== undefined) {
this.initFocusedImageIndex = this.indexForFocusedImage;
if (this.focusedImageTimestamp !== undefined) {
this.isPaused = true;
}
@@ -701,10 +703,10 @@ export default {
if (thumbnailClick) {
//We use the props till the user changes what they want to see
this.initFocusedImageIndex = undefined;
this.focusedImageTimestamp = undefined;
}
if (this.isPaused && !thumbnailClick && this.initFocusedImageIndex === undefined) {
if (this.isPaused && !thumbnailClick && this.focusedImageTimestamp === undefined) {
this.nextImageIndex = focusedIndex;
//this could happen if bounds changes
if (this.focusedImageIndex > this.imageHistory.length - 1) {

View File

@@ -528,10 +528,10 @@ describe("The Imagery View Layouts", () => {
const mouseDownEvent = createMouseEvent("mousedown");
let imageWrapper = parent.querySelectorAll(`.c-imagery-tsv__image-wrapper`);
imageWrapper[2].dispatchEvent(mouseDownEvent);
Vue.nextTick(() => {
const timestamp = imageWrapper[2].id.replace('wrapper-', '');
expect(componentView.previewAction.invoke).toHaveBeenCalledWith([componentView.objectPath[0]], {
indexForFocusedImage: 2,
timestamp: Number(timestamp),
objectPath: componentView.objectPath
});
done();

View File

@@ -43,7 +43,13 @@ export default class ImportAsJSONAction {
*/
appliesTo(objectPath) {
const domainObject = objectPath[0];
if (domainObject && domainObject.locked) {
const locked = domainObject && domainObject.locked;
const persistable = this.openmct.objects.isPersistable(domainObject.identifier);
const TypeDefinition = this.openmct.types.get(domainObject.type);
const definition = TypeDefinition.definition;
const creatable = definition && definition.creatable;
if (locked || !persistable || !creatable) {
return false;
}

View File

@@ -97,6 +97,7 @@ describe("The import JSON action", function () {
domainObject
];
spyOn(openmct.types, 'get').and.returnValue({});
spyOn(openmct.composition, 'get').and.returnValue(false);
expect(importFromJSONAction.appliesTo(objectPath)).toBe(false);

View File

@@ -0,0 +1,100 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
export default class LocalStorageObjectProvider {
constructor(spaceKey = 'mct') {
this.localStorage = window.localStorage;
this.spaceKey = spaceKey;
this.initializeSpace(spaceKey);
}
get(identifier) {
if (this.getSpaceAsObject()[identifier.key] !== undefined) {
const persistedModel = this.getSpaceAsObject()[identifier.key];
const domainObject = {
identifier,
...persistedModel
};
return Promise.resolve(domainObject);
} else {
return Promise.resolve(undefined);
}
}
create(object) {
return this.persistObject(object);
}
update(object) {
return this.persistObject(object);
}
/**
* @private
*/
persistObject(domainObject) {
let space = this.getSpaceAsObject();
space[domainObject.identifier.key] = domainObject;
this.persistSpace(space);
return Promise.resolve(true);
}
/**
* @private
*/
persistSpace(space) {
this.localStorage[this.spaceKey] = JSON.stringify(space);
}
/**
* @private
*/
getSpace() {
return this.localStorage[this.spaceKey];
}
/**
* @private
*/
getSpaceAsObject() {
return JSON.parse(this.getSpace());
}
/**
* @private
*/
initializeSpace() {
if (this.isEmpty()) {
this.localStorage[this.spaceKey] = JSON.stringify({});
}
}
/**
* @private
*/
isEmpty() {
return this.getSpace() === undefined;
}
}

View File

@@ -1,9 +1,9 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
@@ -14,16 +14,16 @@
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import UTCTimeFormat from './UTCTimeFormat';
import LocalStorageObjectProvider from './LocalStorageObjectProvider';
export default function () {
return function install(openmct) {
openmct.telemetry.addFormat(new UTCTimeFormat());
export default function (namespace = '', storageSpace = 'mct') {
return function (openmct) {
openmct.objects.addProvider(namespace, new LocalStorageObjectProvider(storageSpace));
};
}

View File

@@ -0,0 +1,96 @@
/* eslint-disable no-invalid-this */
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import {
createOpenMct,
resetApplicationState
} from 'utils/testing';
describe("The local storage plugin", () => {
let space;
let openmct;
beforeEach(() => {
space = `test-${Date.now()}`;
openmct = createOpenMct();
openmct.install(openmct.plugins.LocalStorage('', space));
});
it('initializes localstorage if not already initialized', () => {
const ls = getLocalStorage();
expect(ls[space]).toBeDefined();
});
it('successfully persists an object to localstorage', async () => {
const domainObject = {
identifier: {
namespace: '',
key: 'test-key'
},
name: 'A test object'
};
let spaceAsObject = getSpaceAsObject();
expect(spaceAsObject['test-key']).not.toBeDefined();
await openmct.objects.save(domainObject);
spaceAsObject = getSpaceAsObject();
expect(spaceAsObject['test-key']).toBeDefined();
});
it('successfully retrieves an object from localstorage', async () => {
const domainObject = {
identifier: {
namespace: '',
key: 'test-key'
},
name: 'A test object',
anotherProperty: Date.now()
};
await openmct.objects.save(domainObject);
let testObject = await openmct.objects.get(domainObject.identifier);
expect(testObject.name).toEqual(domainObject.name);
expect(testObject.anotherProperty).toEqual(domainObject.anotherProperty);
});
afterEach(() => {
resetApplicationState(openmct);
resetLocalStorage();
});
function resetLocalStorage() {
delete window.localStorage[space];
}
function getLocalStorage() {
return window.localStorage;
}
function getSpaceAsObject() {
return JSON.parse(getLocalStorage()[space]);
}
});

View File

@@ -49,6 +49,7 @@ define([
* @memberof platform/commonUI/formats
*/
function LocalTimeFormat() {
this.key = 'local-format';
}
/**

View File

@@ -30,11 +30,7 @@ define([
return function () {
return function (openmct) {
openmct.time.addTimeSystem(new LocalTimeSystem());
openmct.legacyExtension('formats', {
key: 'local-format',
implementation: LocalTimeFormat
});
openmct.telemetry.addFormat(new LocalTimeFormat());
};
};
});

View File

@@ -95,23 +95,10 @@ const selectedPage = {
};
let openmct;
let mockIdentifierService;
describe('Notebook Entries:', () => {
beforeEach(() => {
openmct = createOpenMct();
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
mockIdentifierService = jasmine.createSpyObj(
'identifierService',
['parse']
);
mockIdentifierService.parse.and.returnValue({
getSpace: () => {
return '';
}
});
openmct.$injector.get.and.returnValue(mockIdentifierService);
openmct.types.addType('notebook', {
creatable: true
});

View File

@@ -64,23 +64,11 @@ const notebookStorage = {
};
let openmct;
let mockIdentifierService;
describe('Notebook Storage:', () => {
beforeEach(() => {
openmct = createOpenMct();
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
mockIdentifierService = jasmine.createSpyObj(
'identifierService',
['parse']
);
mockIdentifierService.parse.and.returnValue({
getSpace: () => {
return '';
}
});
openmct.$injector.get.and.returnValue(mockIdentifierService);
window.localStorage.setItem('notebook-storage', null);
openmct.objects.addProvider('', jasmine.createSpyObj('mockNotebookProvider', [
'create',

View File

@@ -84,17 +84,17 @@ class CouchObjectProvider {
this.changesFeedSharedWorkerConnectionId = event.data.connectionId;
} else {
let objectChanges = event.data.objectChanges;
objectChanges.identifier = {
const objectIdentifier = {
namespace: this.namespace,
key: objectChanges.id
};
let keyString = this.openmct.objects.makeKeyString(objectChanges.identifier);
let keyString = this.openmct.objects.makeKeyString(objectIdentifier);
//TODO: Optimize this so that we don't 'get' the object if it's current revision (from this.objectQueue) is the same as the one we already have.
let observersForObject = this.observers[keyString];
if (observersForObject) {
observersForObject.forEach(async (observer) => {
const updatedObject = await this.get(objectChanges.identifier);
const updatedObject = await this.get(objectIdentifier);
if (this.isSynchronizedObject(updatedObject)) {
observer(updatedObject);
}
@@ -179,11 +179,8 @@ class CouchObjectProvider {
getModel(response) {
if (response && response.model) {
let key = response[ID];
let object = response.model;
object.identifier = {
namespace: this.namespace,
key: key
};
let object = this.fromPersistedModel(response.model, key);
if (!this.objectQueue[key]) {
this.objectQueue[key] = new CouchObjectQueue(undefined, response[REV]);
}
@@ -445,17 +442,17 @@ class CouchObjectProvider {
}
onEventMessage(event) {
const object = JSON.parse(event.data);
object.identifier = {
const eventData = JSON.parse(event.data);
const identifier = {
namespace: this.namespace,
key: object.id
key: eventData.id
};
let keyString = this.openmct.objects.makeKeyString(object.identifier);
const keyString = this.openmct.objects.makeKeyString(identifier);
let observersForObject = this.observers[keyString];
if (observersForObject) {
observersForObject.forEach(async (observer) => {
const updatedObject = await this.get(object.identifier);
const updatedObject = await this.get(identifier);
if (this.isSynchronizedObject(updatedObject)) {
observer(updatedObject);
}
@@ -520,7 +517,9 @@ class CouchObjectProvider {
create(model) {
let intermediateResponse = this.getIntermediateResponse();
const key = model.identifier.key;
model = this.toPersistableModel(model);
this.enqueueObject(key, model, intermediateResponse);
if (!this.objectQueue[key].pending) {
this.objectQueue[key].pending = true;
const queued = this.objectQueue[key].dequeue();
@@ -557,11 +556,31 @@ class CouchObjectProvider {
update(model) {
let intermediateResponse = this.getIntermediateResponse();
const key = model.identifier.key;
model = this.toPersistableModel(model);
this.enqueueObject(key, model, intermediateResponse);
this.updateQueued(key);
return intermediateResponse.promise;
}
toPersistableModel(model) {
//First make a copy so we are not mutating the provided model.
const persistableModel = JSON.parse(JSON.stringify(model));
//Delete the identifier. Couch manages namespaces dynamically.
delete persistableModel.identifier;
return persistableModel;
}
fromPersistedModel(model, key) {
model.identifier = {
namespace: this.namespace,
key
};
return model;
}
}
CouchObjectProvider.HTTP_CONFLICT = 409;

View File

@@ -22,11 +22,15 @@
import CouchObjectProvider from './CouchObjectProvider';
const NAMESPACE = '';
const PERSISTENCE_SPACE = 'mct';
const LEGACY_SPACE = 'mct';
export default function CouchPlugin(options) {
return function install(openmct) {
install.couchProvider = new CouchObjectProvider(openmct, options, NAMESPACE);
openmct.objects.addProvider(PERSISTENCE_SPACE, install.couchProvider);
// Unfortunately, for historical reasons, Couch DB produces objects with a mix of namepaces (alternately "mct", and "")
// Installing the same provider under both namespaces means that it can respond to object gets for both namespaces.
openmct.objects.addProvider(LEGACY_SPACE, install.couchProvider);
openmct.objects.addProvider(NAMESPACE, install.couchProvider);
};
}

View File

@@ -129,8 +129,9 @@ describe('the plugin', () => {
it('works without Shared Workers', async () => {
let sharedWorkerCallback;
const restoreSharedWorker = window.SharedWorker;
const cachedSharedWorker = window.SharedWorker;
window.SharedWorker = undefined;
const mockEventSource = {
addEventListener: (topic, addedListener) => {
sharedWorkerCallback = addedListener;
@@ -139,6 +140,8 @@ describe('the plugin', () => {
sharedWorkerCallback = null;
}
};
const cachedEventSource = window.EventSource;
window.EventSource = function (url) {
return mockEventSource;
};
@@ -163,17 +166,21 @@ describe('the plugin', () => {
expect(result).toBeTrue();
expect(provider.create).toHaveBeenCalled();
expect(provider.startSharedWorker).not.toHaveBeenCalled();
//Set modified timestamp it detects a change and persists the updated model.
mockDomainObject.modified = mockDomainObject.persisted + 1;
const updatedResult = await openmct.objects.save(mockDomainObject);
openmct.objects.observe(mockDomainObject, '*', (updatedObject) => {
});
expect(updatedResult).toBeTrue();
expect(provider.update).toHaveBeenCalled();
expect(provider.fetchChanges).toHaveBeenCalled();
sharedWorkerCallback(fakeUpdateEvent);
expect(provider.onEventMessage).toHaveBeenCalled();
window.SharedWorker = restoreSharedWorker;
window.SharedWorker = cachedSharedWorker;
window.EventSource = cachedEventSource;
});
});
describe('batches requests', () => {

View File

@@ -71,8 +71,7 @@ export default class XAxisModel extends Model {
defaults(options) {
const bounds = options.openmct.time.bounds();
const timeSystem = options.openmct.time.timeSystem();
const format = options.openmct.$injector.get('formatService')
.getFormat(timeSystem.timeFormat);
const format = options.openmct.telemetry.getFormatter(timeSystem.timeFormat);
return {
name: timeSystem.name,

View File

@@ -73,8 +73,8 @@ define([
'./hyperlink/plugin',
'./clock/plugin',
'./DeviceClassifier/plugin',
'./UTCTimeFormat/plugin',
'./timer/plugin'
'./timer/plugin',
'./localStorage/plugin'
], function (
_,
UTCTimeSystem,
@@ -128,11 +128,10 @@ define([
Hyperlink,
Clock,
DeviceClassifier,
UTCTimeFormat,
Timer
Timer,
LocalStorage
) {
const bundleMap = {
LocalStorage: 'platform/persistence/local',
Elasticsearch: 'platform/persistence/elastic'
};
@@ -144,7 +143,7 @@ define([
};
});
plugins.UTCTimeSystem = UTCTimeSystem;
plugins.UTCTimeSystem = UTCTimeSystem.default;
plugins.LocalTimeSystem = LocalTimeSystem;
plugins.RemoteClock = RemoteClock.default;
@@ -237,7 +236,7 @@ define([
plugins.Clock = Clock.default;
plugins.Timer = Timer.default;
plugins.DeviceClassifier = DeviceClassifier.default;
plugins.UTCTimeFormat = UTCTimeFormat.default;
plugins.LocalStorage = LocalStorage.default;
return plugins;
});

View File

@@ -41,6 +41,7 @@ const LOCAL_STORAGE_HISTORY_KEY_REALTIME = 'tcHistoryRealtime';
const DEFAULT_RECORDS = 10;
import { millisecondsToDHMS } from "utils/duration";
import UTCTimeFormat from "../utcTimeSystem/UTCTimeFormat.js";
export default {
inject: ['openmct', 'configuration'],
@@ -263,7 +264,15 @@ export default {
format: format
}).formatter;
return (isNegativeOffset ? '-' : '') + formatter.format(time, 'YYYY-MM-DD HH:mm:ss');
let formattedDate;
if (formatter instanceof UTCTimeFormat) {
formattedDate = formatter.format(time, formatter.DATE_FORMATS.PRECISION_SECONDS);
} else {
formattedDate = formatter.format(time);
}
return (isNegativeOffset ? '-' : '') + formattedDate;
},
showHistoryMenu() {
const elementBoundingClientRect = this.$refs.historyButton.getBoundingClientRect();

View File

@@ -143,6 +143,11 @@ export default {
time: undefined
};
},
watch: {
defaultDateTime() {
this.updateFromModel(this.defaultDateTime);
}
},
mounted: function () {
this.updateFromModel(this.defaultDateTime);
this.updateViewForMonth();

View File

@@ -109,11 +109,11 @@ export default {
showModesMenu() {
const elementBoundingClientRect = this.$refs.modeMenuButton.getBoundingClientRect();
const x = elementBoundingClientRect.x;
const y = elementBoundingClientRect.y;
const y = elementBoundingClientRect.y + elementBoundingClientRect.height;
const menuOptions = {
menuClass: 'c-conductor__mode-menu',
placement: this.openmct.menus.menuPlacement.TOP_RIGHT
placement: this.openmct.menus.menuPlacement.BOTTOM_RIGHT
};
this.openmct.menus.showSuperMenu(x, y, this.modes, menuOptions);
},

View File

@@ -0,0 +1,37 @@
import moment from 'moment';
const DATE_FORMAT = "HH:mm:ss";
const DATE_FORMATS = [
DATE_FORMAT
];
/**
* Formatter for duration. Uses moment to produce a date from a given
* value, but output is formatted to display only time. Can be used for
* specifying a time duration. For specifying duration, it's best to
* specify a date of January 1, 1970, as the ms offset will equal the
* duration represented by the time.
*
* @implements {Format}
* @constructor
* @memberof platform/commonUI/formats
*/
class DurationFormat {
constructor() {
this.key = "duration";
}
format(value) {
return moment.utc(value).format(DATE_FORMAT);
}
parse(text) {
return moment.duration(text).asMilliseconds();
}
validate(text) {
return moment.utc(text, DATE_FORMATS, true).isValid();
}
}
export default DurationFormat;

View File

@@ -34,39 +34,39 @@ export default class UTCTimeFormat {
constructor() {
this.key = 'utc';
this.DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss.SSS';
this.DATE_FORMATS = [
this.DATE_FORMAT,
this.DATE_FORMAT + 'Z',
'YYYY-MM-DD HH:mm:ss',
'YYYY-MM-DD HH:mm',
'YYYY-MM-DD'
];
this.DATE_FORMATS = {
PRECISION_DEFAULT: this.DATE_FORMAT,
PRECISION_DEFAULT_WITH_ZULU: this.DATE_FORMAT + 'Z',
PRECISION_SECONDS: 'YYYY-MM-DD HH:mm:ss',
PRECISION_MINUTES: 'YYYY-MM-DD HH:mm',
PRECISION_DAYS: 'YYYY-MM-DD'
};
}
/**
* @param {string} formatString
* @returns the value of formatString if the value is a string type and exists in the DATE_FORMATS array; otherwise the DATE_FORMAT value.
*/
validateFormatString(formatString) {
return typeof formatString === 'string'
&& this.DATE_FORMATS.includes(formatString)
? formatString
: this.DATE_FORMAT;
isValidFormatString(formatString) {
return Object.values(this.DATE_FORMATS).includes(formatString);
}
/**
* @param {number} value The value to format.
* @param {string} formatString The string format to format. Default "YYYY-MM-DD HH:mm:ss.SSS" + "Z"
* @returns {string} the formatted date(s) according to the proper parameter of formatString or the default value of "YYYY-MM-DD HH:mm:ss.SSS" + "Z".
* If multiple values were requested, then an array of
* @returns {string} the formatted date(s). If multiple values were requested, then an array of
* formatted values will be returned. Where a value could not be formatted, `undefined` will be returned at its position
* in the array.
*/
format(value, formatString) {
if (value !== undefined) {
const format = this.validateFormatString(formatString);
const utc = moment.utc(value);
if (formatString !== undefined && !this.isValidFormatString(formatString)) {
throw "Invalid format requested from UTC Time Formatter ";
}
let format = formatString || this.DATE_FORMATS.PRECISION_DEFAULT;
return utc.format(format) + (formatString ? '' : 'Z');
} else {
return value;
@@ -78,10 +78,11 @@ export default class UTCTimeFormat {
return text;
}
return moment.utc(text, this.DATE_FORMATS).valueOf();
return moment.utc(text, Object.values(this.DATE_FORMATS)).valueOf();
}
validate(text) {
return moment.utc(text, this.DATE_FORMATS, true).isValid();
return moment.utc(text, Object.values(this.DATE_FORMATS), true).isValid();
}
}

View File

@@ -20,22 +20,21 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
"./UTCTimeSystem",
"./LocalClock"
], function (
UTCTimeSystem,
LocalClock
) {
/**
* Install a time system that supports UTC times. It also installs a local
* clock source that ticks every 100ms, providing UTC times.
*/
return function () {
return function (openmct) {
const timeSystem = new UTCTimeSystem();
openmct.time.addTimeSystem(timeSystem);
openmct.time.addClock(new LocalClock.default(100));
};
import UTCTimeSystem from './UTCTimeSystem';
import LocalClock from './LocalClock';
import UTCTimeFormat from './UTCTimeFormat';
import DurationFormat from './DurationFormat';
/**
* Install a time system that supports UTC times. It also installs a local
* clock source that ticks every 100ms, providing UTC times.
*/
export default function () {
return function (openmct) {
const timeSystem = new UTCTimeSystem();
openmct.time.addTimeSystem(timeSystem);
openmct.time.addClock(new LocalClock(100));
openmct.telemetry.addFormat(new UTCTimeFormat());
openmct.telemetry.addFormat(new DurationFormat());
};
});
}

View File

@@ -26,9 +26,11 @@ import {
createOpenMct,
resetApplicationState
} from 'utils/testing';
import UTCTimeFormat from './UTCTimeFormat.js';
describe("The UTC Time System", () => {
const UTC_SYSTEM_AND_FORMAT_KEY = 'utc';
const DURATION_FORMAT_KEY = 'duration';
let openmct;
let utcTimeSystem;
let mockTimeout;
@@ -100,4 +102,93 @@ describe("The UTC Time System", () => {
expect(mockListener).toHaveBeenCalledWith(jasmine.any(Number));
});
});
describe("UTC Time Format", () => {
let utcTimeFormatter;
beforeEach(() => {
utcTimeFormatter = openmct.telemetry.getFormatter(UTC_SYSTEM_AND_FORMAT_KEY);
});
it("is installed by the plugin", () => {
expect(utcTimeFormatter).toBeDefined();
});
it("formats from ms since Unix epoch into Open MCT UTC time format", () => {
const TIME_IN_MS = 1638574560945;
const TIME_AS_STRING = "2021-12-03 23:36:00.945Z";
const formattedTime = utcTimeFormatter.format(TIME_IN_MS);
expect(formattedTime).toEqual(TIME_AS_STRING);
});
it("formats from ms since Unix epoch into terse UTC formats", () => {
const utcTimeFormatterInstance = new UTCTimeFormat();
const TIME_IN_MS = 1638574560945;
const EXPECTED_FORMATS = {
PRECISION_DEFAULT: "2021-12-03 23:36:00.945",
PRECISION_SECONDS: "2021-12-03 23:36:00",
PRECISION_MINUTES: "2021-12-03 23:36",
PRECISION_DAYS: "2021-12-03"
};
Object.keys(EXPECTED_FORMATS).forEach((formatKey) => {
const formattedTime = utcTimeFormatterInstance.format(TIME_IN_MS, utcTimeFormatterInstance.DATE_FORMATS[formatKey]);
expect(formattedTime).toEqual(EXPECTED_FORMATS[formatKey]);
});
});
it("parses from Open MCT UTC time format to ms since Unix epoch.", () => {
const TIME_IN_MS = 1638574560945;
const TIME_AS_STRING = "2021-12-03 23:36:00.945Z";
const parsedTime = utcTimeFormatter.parse(TIME_AS_STRING);
expect(parsedTime).toEqual(TIME_IN_MS);
});
it("validates correctly formatted Open MCT UTC times.", () => {
const TIME_AS_STRING = "2021-12-03 23:36:00.945Z";
const isValid = utcTimeFormatter.validate(TIME_AS_STRING);
expect(isValid).toBeTrue();
});
});
describe("Duration Format", () => {
let durationTimeFormatter;
beforeEach(() => {
durationTimeFormatter = openmct.telemetry.getFormatter(DURATION_FORMAT_KEY);
});
it("is installed by the plugin", () => {
expect(durationTimeFormatter).toBeDefined();
});
it("formats from ms into Open MCT duration format", () => {
const TIME_IN_MS = 2000;
const TIME_AS_STRING = "00:00:02";
const formattedTime = durationTimeFormatter.format(TIME_IN_MS);
expect(formattedTime).toEqual(TIME_AS_STRING);
});
it("parses from Open MCT duration format to ms", () => {
const TIME_IN_MS = 2000;
const TIME_AS_STRING = "00:00:02";
const parsedTime = durationTimeFormatter.parse(TIME_AS_STRING);
expect(parsedTime).toEqual(TIME_IN_MS);
});
it("validates correctly formatted Open MCT duration strings.", () => {
const TIME_AS_STRING = "00:00:02";
const isValid = durationTimeFormatter.validate(TIME_AS_STRING);
expect(isValid).toBeTrue();
});
});
});