Compare commits
10 Commits
esm
...
code-cover
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d522c1ef44 | ||
|
|
0df672e470 | ||
|
|
c9bc390355 | ||
|
|
5b1664f073 | ||
|
|
e634e09e32 | ||
|
|
8e3947e48d | ||
|
|
b1ea6efd45 | ||
|
|
70f2fad243 | ||
|
|
2d64813a4f | ||
|
|
fd0e89ca05 |
4
API.md
4
API.md
@@ -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:
|
||||
|
||||
@@ -7,6 +7,7 @@ const config = {
|
||||
retries: 0,
|
||||
testDir: 'tests',
|
||||
timeout: 90 * 1000,
|
||||
workers: 1,
|
||||
webServer: {
|
||||
command: 'npm run start',
|
||||
port: 8080,
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
@@ -104,7 +104,7 @@ module.exports = (config) => {
|
||||
reports: ['lcovonly', 'text-summary'],
|
||||
thresholds: {
|
||||
global: {
|
||||
lines: 66
|
||||
lines: 70
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -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());
|
||||
```
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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]) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>'
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
100
src/plugins/localStorage/LocalStorageObjectProvider.js
Normal file
100
src/plugins/localStorage/LocalStorageObjectProvider.js
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
};
|
||||
}
|
||||
96
src/plugins/localStorage/pluginSpec.js
Normal file
96
src/plugins/localStorage/pluginSpec.js
Normal 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]);
|
||||
}
|
||||
});
|
||||
@@ -49,6 +49,7 @@ define([
|
||||
* @memberof platform/commonUI/formats
|
||||
*/
|
||||
function LocalTimeFormat() {
|
||||
this.key = 'local-format';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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());
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -143,6 +143,11 @@ export default {
|
||||
time: undefined
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
defaultDateTime() {
|
||||
this.updateFromModel(this.defaultDateTime);
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
this.updateFromModel(this.defaultDateTime);
|
||||
this.updateViewForMonth();
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
37
src/plugins/utcTimeSystem/DurationFormat.js
Normal file
37
src/plugins/utcTimeSystem/DurationFormat.js
Normal 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;
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user