Compare commits

...

13 Commits

Author SHA1 Message Date
Khalid Adil
064e517031 Adding some webpack config for testing 2022-02-25 16:30:10 -06:00
Khalid Adil
7096710b1f Updated style, added copyright, and addressed some comments 2022-02-25 16:19:49 -06:00
Khalid Adil
ecfbabcfe3 Remove uuid 2022-02-23 11:41:00 -06:00
Khalid Adil
a76b843fde Merge branch 'master' into bugfix/issue-4684 2022-02-23 11:39:33 -06:00
Khalid Adil
7f0367b838 Update rewriteObjectIdentifiers to loop through the tree 2022-02-23 11:38:20 -06:00
Jamie V
4e7debabb1 [Notebook] Add active user to entries (#4764)
* if user provider, user added to notebook enntries and snapshot entries, updated code to work with asynnc nature of user api

Co-authored-by: Andrew Henry <akhenry@gmail.com>
Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>
2022-02-22 12:37:47 -08:00
Michael Rogers
384e36920c 3175 - Enable listening to clearData action for Imagery (#4733)
* Add clearData listener for imageryData module

* Remove commented out code

* Updated imagery clear data test

* Adjusted telemetry stub to return empty array if data cleared

* Remove forced test

* Restub telemetry before

* Cleanup and reset clear data boolean after

* Remove double blank line

Co-authored-by: Scott Bell <scott@traclabs.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2022-02-18 08:48:48 -06:00
dependabot[bot]
d4429f9686 Bump cross-env from 6.0.3 to 7.0.3 (#4837)
Bumps [cross-env](https://github.com/kentcdodds/cross-env) from 6.0.3 to 7.0.3.
- [Release notes](https://github.com/kentcdodds/cross-env/releases)
- [Changelog](https://github.com/kentcdodds/cross-env/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kentcdodds/cross-env/compare/v6.0.3...v7.0.3)

---
updated-dependencies:
- dependency-name: cross-env
  dependency-type: direct:development
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-17 10:34:14 -08:00
John Hill
0b2d08078b [CI] Add windows OS and Mac OS to CI testing (#4840)
* Update e2e tests to run on windows and linux

* Add pr-platform to run against all supported archictures

* Update dependabot to run pr:platform

* Update to run in fail-fast false

* remove x86

* Update pr-platform.yml

Co-authored-by: unlikelyzero <jchill2@gmail.com>
2022-02-17 14:52:46 +00:00
John Hill
3bbc9e1582 [CI] Enable Per PR Lighthouse CI Execution, Run nightly (#4817)
* [CI] Add lighthouse to our deps to track with dependabot

lighthouse ci is still actively maintained and will be following a traditional release model. We should continue to use this package until it's functionality is replaced in playwright

* Add lighthouse to dependencies to track with dependabot

* Allow lighthouse to be triggered from PRs

* Update lighthouse.yml

* add lhci scrript

* bump to 16

* remove from deps until node 18

* document steps and add caching. Revert to 14

* ignore exit codes

* add secret for app

* remove env for baseline

Co-authored-by: unlikelyzero <jchill2@gmail.com>
Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>
2022-02-16 17:12:00 -08:00
Khalid Adil
04c3728eff Cleanup code 2022-01-19 12:58:10 -06:00
Khalid Adil
afa89ae6b5 Change approach to rewriting object identifiers to use a map of new identifiers 2022-01-19 11:22:54 -06:00
Khalid Adil
58844799be Save WIP code to rewrite object identifiers 2022-01-11 10:00:27 -06:00
20 changed files with 607 additions and 272 deletions

View File

@@ -11,6 +11,8 @@ updates:
- "dependencies"
- "pr:e2e"
- "pr:daveit"
- "pr:visual"
- "pr:platform"
- package-ecosystem: "github-actions"
directory: "/"

View File

@@ -9,7 +9,12 @@ on:
jobs:
e2e-full:
if: ${{ github.event.label.name == 'pr:e2e' }}
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
os:
- ubuntu-latest
- windows-latest
steps:
- name: Trigger Success
uses: actions/github-script@v6

View File

@@ -5,16 +5,96 @@ on:
version:
description: 'Which branch do you want to test?' # Limited to branch for now
required: false
default: 'master'
default: 'master'
pull_request:
types:
- labeled
schedule:
- cron: '28 21 * * 1-5'
jobs:
lighthouse:
lighthouse-pr:
if: ${{ github.event.label.name == 'pr:lighthouse' }}
runs-on: ubuntu-latest
steps:
- name: Checkout Master for Baseline
uses: actions/checkout@v2
with:
ref: master #explicitly checkout master for baseline
- name: Install Node 14
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
- name: npm install with lighthouse cli
run: npm install && npm install -g @lhci/cli
- name: Run lhci against master to generate baseline and ignore exit codes
run: lhci autorun || true
- name: Perform clean checkout of PR
uses: actions/checkout@v2
with:
clean: true
- name: Install Node version which is compatible with PR
uses: actions/setup-node@v2
- name: npm install with lighthouse cli
run: npm install && npm install -g @lhci/cli
- name: Run lhci with PR
run: lhci autorun
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
lighthouse-nightly:
if: ${{ github.event.schedule }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Node 14
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
- name: npm install with lighthouse cli
run: npm install && npm install -g @lhci/cli
- name: Run lhci against master to generate baseline
run: lhci autorun
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
lighthouse-dispatch:
if: ${{ github.event.workflow_dispatch }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.inputs.version }}
- uses: actions/setup-node@v2
- name: Install Node 14
uses: actions/setup-node@v2
with:
node-version: '16'
- run: npm install && npm install -g @lhci/cli #Don't want to include this in our deps
- run: lhci autorun
node-version: '14'
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
- name: npm install with lighthouse cli
run: npm install && npm install -g @lhci/cli
- name: Run lhci against master to generate baseline
run: lhci autorun

34
.github/workflows/pr-platform.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: "pr-platform"
on:
workflow_dispatch:
pull_request:
types: [ labeled ]
jobs:
e2e-full:
if: ${{ github.event.label.name == 'pr:platform' }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- macos-latest
- windows-latest
node_version:
- 12
- 14
- 16
architecture:
- x64
name: Node ${{ matrix.node_version }} - ${{ matrix.architecture }} on ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node_version }}
architecture: ${{ matrix.architecture }}
- run: npm install
- run: npm test
- run: npm run lint

View File

@@ -190,11 +190,12 @@
openmct.install(openmct.plugins.Filters(['table', 'telemetry.plot.overlay']));
openmct.install(openmct.plugins.ObjectMigration());
openmct.install(openmct.plugins.ClearData(
['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'],
['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked', 'example.imagery'],
{indicator: true}
));
openmct.install(openmct.plugins.Clock({ enableClockIndicator: true }));
openmct.install(openmct.plugins.Timer());
openmct.install(openmct.plugins.StaticRootPlugin({ namespace: 'something', exportUrl: './dist/static-root.json' }));
openmct.start();
</script>
</html>

View File

@@ -12,7 +12,7 @@
"comma-separated-values": "3.6.4",
"copy-webpack-plugin": "10.2.0",
"core-js": "3.20.3",
"cross-env": "6.0.3",
"cross-env": "7.0.3",
"css-loader": "4.0.0",
"d3-axis": "1.0.x",
"d3-scale": "1.0.x",

View File

@@ -185,10 +185,14 @@ describe('The Clear Data Plugin:', () => {
beforeEach((done) => {
openmct = createOpenMct();
clearDataPlugin = new ClearDataPlugin(
['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'],
{indicator: true}
);
clearDataPlugin = new ClearDataPlugin([
'table',
'telemetry.plot.overlay',
'telemetry.plot.stacked',
'example.imagery'
], {
indicator: true
});
openmct.install(clearDataPlugin);
appHolder = document.createElement('div');
document.body.appendChild(appHolder);

View File

@@ -30,6 +30,7 @@ export default {
this.timeSystemChange = this.timeSystemChange.bind(this);
this.setDataTimeContext = this.setDataTimeContext.bind(this);
this.setDataTimeContext();
this.openmct.objectViews.on('clearData', this.clearData);
// set
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
@@ -54,6 +55,7 @@ export default {
}
this.stopFollowingDataTimeContext();
this.openmct.objectViews.off('clearData', this.clearData);
},
methods: {
setDataTimeContext() {
@@ -151,6 +153,25 @@ export default {
this.imageHistory = imagery;
}
},
clearData(domainObjectToClear) {
// global clearData button is accepted therefore no truthy check on inputted param
const clearDataForObjectSelected = Boolean(domainObjectToClear);
if (clearDataForObjectSelected) {
const idsEqual = this.openmct.objects.areIdsEqual(
domainObjectToClear.identifier,
this.domainObject.identifier
);
if (!idsEqual) {
return;
}
}
// splice array to encourage garbage collection
this.imageHistory.splice(0, this.imageHistory.length);
// requesting history effectively clears imageHistory array
return this.requestHistory();
},
timeSystemChange() {
this.timeSystem = this.timeContext.timeSystem();
this.timeKey = this.timeSystem.key;

View File

@@ -27,6 +27,7 @@ import {
resetApplicationState,
simulateKeyEvent
} from 'utils/testing';
import ClearDataPlugin from '../clearData/plugin';
const ONE_MINUTE = 1000 * 60;
const TEN_MINUTES = ONE_MINUTE * 10;
@@ -83,6 +84,7 @@ describe("The Imagery View Layouts", () => {
let telemetryPromise;
let telemetryPromiseResolve;
let cleanupFirst;
let isClearDataTriggered;
let openmct;
let parent;
@@ -201,6 +203,10 @@ describe("The Imagery View Layouts", () => {
});
spyOn(openmct.telemetry, 'request').and.callFake(() => {
if (isClearDataTriggered) {
return [];
}
telemetryPromiseResolve(imageTelemetry);
return telemetryPromise;
@@ -323,6 +329,8 @@ describe("The Imagery View Layouts", () => {
let applicableViews;
let imageryViewProvider;
let imageryView;
let clearDataPlugin;
let clearDataAction;
beforeEach(() => {
@@ -330,16 +338,21 @@ describe("The Imagery View Layouts", () => {
imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey);
imageryView = imageryViewProvider.view(imageryObject, [imageryObject]);
imageryView.show(child);
clearDataPlugin = new ClearDataPlugin(
['example.imagery'],
{indicator: true}
);
openmct.install(clearDataPlugin);
clearDataAction = openmct.actions.getAction('clear-data-action');
return Vue.nextTick();
});
// afterEach(() => {
// openmct.time.stopClock();
// openmct.router.removeListener('change:hash', resolveFunction);
//
// imageryView.destroy();
// });
afterEach(() => {
isClearDataTriggered = false;
// openmct.time.stopClock();
// openmct.router.removeListener('change:hash', resolveFunction);
// imageryView.destroy();
});
it("on mount should show the the most recent image", (done) => {
//Looks like we need Vue.nextTick here so that computed properties settle down
@@ -470,6 +483,21 @@ describe("The Imagery View Layouts", () => {
});
});
});
it('clear data action is installed', () => {
expect(clearDataAction).toBeDefined();
});
it('on clearData action should clear data for object is selected', (done) => {
expect(parent.querySelectorAll('.c-imagery__thumb').length).not.toBe(0);
openmct.objectViews.on('clearData', async (_domainObject) => {
await Vue.nextTick();
expect(parent.querySelectorAll('.c-imagery__thumb').length).toBe(0);
done();
});
// stubbed telemetry data will return empty array when true
isClearDataTriggered = true;
clearDataAction.invoke(imageryObject);
});
});
describe("imagery time strip view", () => {

View File

@@ -596,8 +596,9 @@ export default {
this.resetSearch();
const notebookStorage = this.createNotebookStorageObject();
this.updateDefaultNotebook(notebookStorage);
const id = addNotebookEntry(this.openmct, this.domainObject, notebookStorage, embed);
this.focusEntryId = id;
addNotebookEntry(this.openmct, this.domainObject, notebookStorage, embed).then(id => {
this.focusEntryId = id;
});
},
orientationChange() {
this.formatSidebar();

View File

@@ -28,6 +28,9 @@
>
<div class="c-ne__time-and-content">
<div class="c-ne__time">
<template v-if="entry.createdBy">
<span class="c-icon icon-person">{{ entry.createdBy }}</span>
</template>
<span>{{ createdOnDate }}</span>
<span>{{ createdOnTime }}</span>
</div>
@@ -182,7 +185,7 @@ export default {
this.dropOnEntry = this.dropOnEntry.bind(this);
},
methods: {
addNewEmbed(objectPath) {
async addNewEmbed(objectPath) {
const bounds = this.openmct.time.bounds();
const snapshotMeta = {
bounds,
@@ -190,7 +193,7 @@ export default {
objectPath,
openmct: this.openmct
};
const newEmbed = createNewEmbed(snapshotMeta);
const newEmbed = await createNewEmbed(snapshotMeta);
this.entry.embeds.push(newEmbed);
},
cancelEditMode(event) {
@@ -206,7 +209,7 @@ export default {
deleteEntry() {
this.$emit('deleteEntry', this.entry.id);
},
dropOnEntry($event) {
async dropOnEntry($event) {
event.stopImmediatePropagation();
const snapshotId = $event.dataTransfer.getData('openmct/snapshot/id');
@@ -221,7 +224,7 @@ export default {
} else {
const data = $event.dataTransfer.getData('openmct/domain-object-path');
const objectPath = JSON.parse(data);
this.addNewEmbed(objectPath);
await this.addNewEmbed(objectPath);
}
this.$emit('updateEntry', this.entry);

View File

@@ -41,16 +41,17 @@ export default class Snapshot {
fullSizeImageObjectIdentifier: object.identifier,
thumbnailImage
};
const embed = createNewEmbed(snapshotMeta, snapshot);
if (notebookType === NOTEBOOK_DEFAULT) {
const notebookStorage = getDefaultNotebook();
createNewEmbed(snapshotMeta, snapshot).then(embed => {
if (notebookType === NOTEBOOK_DEFAULT) {
const notebookStorage = getDefaultNotebook();
this._saveToDefaultNoteBook(notebookStorage, embed);
const notebookImageDomainObject = updateNamespaceOfDomainObject(object, notebookStorage.identifier.namespace);
saveNotebookImageDomainObject(this.openmct, notebookImageDomainObject);
} else {
this._saveToNotebookSnapshots(object, embed);
}
this._saveToDefaultNoteBook(notebookStorage, embed);
const notebookImageDomainObject = updateNamespaceOfDomainObject(object, notebookStorage.identifier.namespace);
saveNotebookImageDomainObject(this.openmct, notebookImageDomainObject);
} else {
this._saveToNotebookSnapshots(object, embed);
}
});
}
/**
@@ -58,26 +59,26 @@ export default class Snapshot {
*/
_saveToDefaultNoteBook(notebookStorage, embed) {
this.openmct.objects.get(notebookStorage.identifier)
.then(async (domainObject) => {
addNotebookEntry(this.openmct, domainObject, notebookStorage, embed);
.then((domainObject) => {
return addNotebookEntry(this.openmct, domainObject, notebookStorage, embed).then(async () => {
let link = notebookStorage.link;
let link = notebookStorage.link;
// Backwards compatibility fix (old notebook model without link)
if (!link) {
link = await getDefaultNotebookLink(this.openmct, domainObject);
notebookStorage.link = link;
setDefaultNotebook(this.openmct, notebookStorage);
}
// Backwards compatibility fix (old notebook model without link)
if (!link) {
link = await getDefaultNotebookLink(this.openmct, domainObject);
notebookStorage.link = link;
setDefaultNotebook(this.openmct, notebookStorage);
}
const { section, page } = getNotebookSectionAndPage(domainObject, notebookStorage.defaultSectionId, notebookStorage.defaultPageId);
if (!section || !page) {
return;
}
const { section, page } = getNotebookSectionAndPage(domainObject, notebookStorage.defaultSectionId, notebookStorage.defaultPageId);
if (!section || !page) {
return;
}
const defaultPath = `${domainObject.name} - ${section.name} - ${page.name}`;
const msg = `Saved to Notebook ${defaultPath}`;
this._showNotification(msg, link);
const defaultPath = `${domainObject.name} - ${section.name} - ${page.name}`;
const msg = `Saved to Notebook ${defaultPath}`;
this._showNotification(msg, link);
});
});
}

View File

@@ -1,5 +1,17 @@
import objectLink from '../../../ui/mixins/object-link';
async function getUsername(openmct) {
let username = '';
if (openmct.user.hasProvider()) {
const user = await openmct.user.getCurrentUser();
username = user.getName();
}
return username;
}
export const DEFAULT_CLASS = 'notebook-default';
const TIME_BOUNDS = {
START_BOUND: 'tc.startBound',
@@ -61,7 +73,7 @@ export function getHistoricLinkInFixedMode(openmct, bounds, historicLink) {
return params.join('&');
}
export function createNewEmbed(snapshotMeta, snapshot = '') {
export async function createNewEmbed(snapshotMeta, snapshot = '') {
const {
bounds,
link,
@@ -83,10 +95,12 @@ export function createNewEmbed(snapshotMeta, snapshot = '') {
});
const name = domainObject.name;
const type = domainObject.identifier.key;
const createdBy = await getUsername(openmct);
return {
bounds,
createdOn: date,
createdBy,
cssClass,
domainObject,
historicLink,
@@ -97,7 +111,7 @@ export function createNewEmbed(snapshotMeta, snapshot = '') {
};
}
export function addNotebookEntry(openmct, domainObject, notebookStorage, embed = null, entryText = '') {
export async function addNotebookEntry(openmct, domainObject, notebookStorage, embed = null, entryText = '') {
if (!openmct || !domainObject || !notebookStorage) {
return;
}
@@ -109,10 +123,12 @@ export function addNotebookEntry(openmct, domainObject, notebookStorage, embed =
? [embed]
: [];
const createdBy = await getUsername(openmct);
const id = `entry-${date}`;
const entry = {
id,
createdOn: date,
createdBy,
text: entryText,
embeds
};

View File

@@ -127,7 +127,7 @@ describe('Notebook Entries:', () => {
expect(entries.length).toEqual(0);
});
it('addNotebookEntry adds entry', () => {
it('addNotebookEntry adds entry', async () => {
const unlisten = openmct.objects.observe(notebookDomainObject, '*', (object) => {
const entries = NotebookEntries.getNotebookEntries(notebookDomainObject, selectedSection, selectedPage);
@@ -135,17 +135,38 @@ describe('Notebook Entries:', () => {
unlisten();
});
NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
await NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
});
it('getEntryPosById returns valid position', () => {
const entryId1 = NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
it('addNotebookEntry adds active user to entry', async () => {
const USER = 'Timmy';
openmct.user.hasProvider = () => true;
openmct.user.getCurrentUser = () => {
return Promise.resolve({
getName: () => {
return USER;
}
});
};
const unlisten = openmct.objects.observe(notebookDomainObject, '*', (object) => {
const entries = NotebookEntries.getNotebookEntries(notebookDomainObject, selectedSection, selectedPage);
expect(entries[0].createdBy).toEqual(USER);
unlisten();
});
await NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
});
it('getEntryPosById returns valid position', async () => {
const entryId1 = await NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
const position1 = NotebookEntries.getEntryPosById(entryId1, notebookDomainObject, selectedSection, selectedPage);
const entryId2 = NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
const entryId2 = await NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
const position2 = NotebookEntries.getEntryPosById(entryId2, notebookDomainObject, selectedSection, selectedPage);
const entryId3 = NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
const entryId3 = await NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
const position3 = NotebookEntries.getEntryPosById(entryId3, notebookDomainObject, selectedSection, selectedPage);
const success = position1 === 0
@@ -155,9 +176,9 @@ describe('Notebook Entries:', () => {
expect(success).toBe(true);
});
it('deleteNotebookEntries deletes correct page entries', () => {
NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
it('deleteNotebookEntries deletes correct page entries', async () => {
await NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
await NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
NotebookEntries.deleteNotebookEntries(openmct, notebookDomainObject, selectedSection, selectedPage);
const afterEntries = NotebookEntries.getNotebookEntries(notebookDomainObject, selectedSection, selectedPage);

View File

@@ -151,7 +151,7 @@ define([
plugins.MyItems = MyItems.default;
plugins.StaticRootPlugin = StaticRootPlugin;
plugins.StaticRootPlugin = StaticRootPlugin.default;
/**
* A tabular view showing the latest values of multiple telemetry points at

View File

@@ -1,45 +1,139 @@
define([
'objectUtils'
], function (
objectUtils
) {
/*****************************************************************************
* 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
* "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.
*****************************************************************************/
/**
* Transforms an import json blob into a object map that can be used to
* provide objects. Rewrites root identifier in import data with provided
* rootIdentifier, and rewrites all child object identifiers so that they
* exist in the same namespace as the rootIdentifier.
*/
import objectUtils from 'objectUtils';
class StaticModelProvider {
constructor(importData, rootIdentifier) {
this.objectMap = {};
this.rewriteModel(importData, rootIdentifier);
}
/**
* Transforms an import json blob into a object map that can be used to
* provide objects. Rewrites root identifier in import data with provided
* rootIdentifier, and rewrites all child object identifiers so that they
* exist in the same namespace as the rootIdentifier.
* Standard "Get".
*/
function rewriteObjectIdentifiers(importData, rootIdentifier) {
const rootId = importData.rootId;
let objectString = JSON.stringify(importData.openmct);
get(identifier) {
const keyString = objectUtils.makeKeyString(identifier);
if (this.objectMap[keyString]) {
return this.objectMap[keyString];
}
Object.keys(importData.openmct).forEach(function (originalId, i) {
let newId;
if (originalId === rootId) {
newId = objectUtils.makeKeyString(rootIdentifier);
} else {
newId = objectUtils.makeKeyString({
namespace: rootIdentifier.namespace,
key: i
throw new Error(keyString + ' not found in import models.');
}
parseObjectLeaf(objectLeaf, idMap, namespace) {
Object.keys(objectLeaf).forEach((nodeKey) => {
if (idMap.get(nodeKey)) {
const newIdentifier = objectUtils.makeKeyString({
namespace,
key: idMap.get(nodeKey)
});
}
while (objectString.indexOf(originalId) !== -1) {
objectString = objectString.replace(
'"' + originalId + '"',
'"' + newId + '"'
);
objectLeaf[newIdentifier] = { ...objectLeaf[nodeKey] };
delete objectLeaf[nodeKey];
objectLeaf[newIdentifier] = this.parseTreeLeaf(newIdentifier, objectLeaf[newIdentifier], idMap, namespace);
} else {
objectLeaf[nodeKey] = this.parseTreeLeaf(nodeKey, objectLeaf[nodeKey], idMap, namespace);
}
});
return JSON.parse(objectString);
return objectLeaf;
}
parseArrayLeaf(arrayLeaf, idMap, namespace) {
return arrayLeaf.map((leafValue, index) => this.parseTreeLeaf(
null, leafValue, idMap, namespace));
}
parseBranchedLeaf(branchedLeafValue, idMap, namespace) {
if (Array.isArray(branchedLeafValue)) {
return this.parseArrayLeaf(branchedLeafValue, idMap, namespace);
} else {
return this.parseObjectLeaf(branchedLeafValue, idMap, namespace);
}
}
parseTreeLeaf(leafKey, leafValue, idMap, namespace) {
const hasChild = typeof leafValue === 'object';
if (hasChild) {
return this.parseBranchedLeaf(leafValue, idMap, namespace);
}
if (leafKey === 'key') {
return idMap.get(leafValue);
} else if (leafKey === 'namespace') {
return namespace;
} else if (leafKey === 'location') {
if (idMap.get(leafValue)) {
const newLocationIdentifier = objectUtils.makeKeyString({
namespace,
key: idMap.get(leafValue)
});
return newLocationIdentifier;
}
return null;
} else if (idMap.get(leafValue)) {
const newIdentifier = objectUtils.makeKeyString({
namespace,
key: idMap.get(leafValue)
});
return newIdentifier;
} else {
return leafValue;
}
}
rewriteObjectIdentifiers(importData, rootIdentifier) {
const namespace = rootIdentifier.namespace;
const idMap = new Map();
const objectTree = importData.openmct;
Object.keys(objectTree).forEach((originalId, index) => {
let newId = index.toString();
if (originalId === importData.rootId) {
newId = rootIdentifier.key;
}
idMap.set(originalId, newId);
});
const newTree = this.parseTreeLeaf(null, objectTree, idMap, namespace);
return newTree;
}
/**
* Converts all objects in an object make from old format objects to new
* format objects.
*/
function convertToNewObjects(oldObjectMap) {
convertToNewObjects(oldObjectMap) {
return Object.keys(oldObjectMap)
.reduce(function (newObjectMap, key) {
newObjectMap[key] = objectUtils.toNewFormat(oldObjectMap[key], key);
@@ -49,7 +143,7 @@ define([
}
/* Set the root location correctly for a top-level object */
function setRootLocation(objectMap, rootIdentifier) {
setRootLocation(objectMap, rootIdentifier) {
objectMap[objectUtils.makeKeyString(rootIdentifier)].location = 'ROOT';
return objectMap;
@@ -59,24 +153,11 @@ define([
* Takes importData (as provided by the ImportExport plugin) and exposes
* an object provider to fetch those objects.
*/
function StaticModelProvider(importData, rootIdentifier) {
const oldFormatObjectMap = rewriteObjectIdentifiers(importData, rootIdentifier);
const newFormatObjectMap = convertToNewObjects(oldFormatObjectMap);
this.objectMap = setRootLocation(newFormatObjectMap, rootIdentifier);
rewriteModel(importData, rootIdentifier) {
const oldFormatObjectMap = this.rewriteObjectIdentifiers(importData, rootIdentifier);
const newFormatObjectMap = this.convertToNewObjects(oldFormatObjectMap);
this.objectMap = this.setRootLocation(newFormatObjectMap, rootIdentifier);
}
}
/**
* Standard "Get".
*/
StaticModelProvider.prototype.get = function (identifier) {
const keyString = objectUtils.makeKeyString(identifier);
if (this.objectMap[keyString]) {
return this.objectMap[keyString];
}
throw new Error(keyString + ' not found in import models.');
};
return StaticModelProvider;
});
export default StaticModelProvider;

View File

@@ -1,133 +1,149 @@
define([
'./StaticModelProvider',
'./static-provider-test.json'
], function (
StaticModelProvider,
testStaticData
) {
/*****************************************************************************
* 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
* "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.
*****************************************************************************/
describe('StaticModelProvider', function () {
import testStaticData from './static-provider-test.json';
import StaticModelProvider from './StaticModelProvider';
let staticProvider;
describe('StaticModelProvider', function () {
let staticProvider;
beforeEach(function () {
const staticData = JSON.parse(JSON.stringify(testStaticData));
staticProvider = new StaticModelProvider(staticData, {
namespace: 'my-import',
key: 'root'
});
});
describe('rootObject', function () {
let rootModel;
beforeEach(function () {
const staticData = JSON.parse(JSON.stringify(testStaticData));
staticProvider = new StaticModelProvider(staticData, {
rootModel = staticProvider.get({
namespace: 'my-import',
key: 'root'
});
});
describe('rootObject', function () {
let rootModel;
it('is located at top level', function () {
expect(rootModel.location).toBe('ROOT');
});
beforeEach(function () {
rootModel = staticProvider.get({
namespace: 'my-import',
key: 'root'
});
});
it('is located at top level', function () {
expect(rootModel.location).toBe('ROOT');
});
it('has new-format identifier', function () {
expect(rootModel.identifier).toEqual({
namespace: 'my-import',
key: 'root'
});
});
it('has new-format composition', function () {
expect(rootModel.composition).toContain({
namespace: 'my-import',
key: '1'
});
expect(rootModel.composition).toContain({
namespace: 'my-import',
key: '2'
});
it('has new-format identifier', function () {
expect(rootModel.identifier).toEqual({
namespace: 'my-import',
key: 'root'
});
});
describe('childObjects', function () {
let swg;
let layout;
let fixed;
beforeEach(function () {
swg = staticProvider.get({
namespace: 'my-import',
key: '1'
});
layout = staticProvider.get({
namespace: 'my-import',
key: '2'
});
fixed = staticProvider.get({
namespace: 'my-import',
key: '3'
});
it('has new-format composition', function () {
expect(rootModel.composition).toContain({
namespace: 'my-import',
key: '1'
});
it('match expected ordering', function () {
// this is a sanity check to make sure the identifiers map in
// the correct order.
expect(swg.type).toBe('generator');
expect(layout.type).toBe('layout');
expect(fixed.type).toBe('telemetry.fixed');
expect(rootModel.composition).toContain({
namespace: 'my-import',
key: '2'
});
it('have new-style identifiers', function () {
expect(swg.identifier).toEqual({
namespace: 'my-import',
key: '1'
});
expect(layout.identifier).toEqual({
namespace: 'my-import',
key: '2'
});
expect(fixed.identifier).toEqual({
namespace: 'my-import',
key: '3'
});
});
it('have new-style composition', function () {
expect(layout.composition).toContain({
namespace: 'my-import',
key: '1'
});
expect(layout.composition).toContain({
namespace: 'my-import',
key: '3'
});
expect(fixed.composition).toContain({
namespace: 'my-import',
key: '1'
});
});
it('rewrites locations', function () {
expect(swg.location).toBe('my-import:root');
expect(layout.location).toBe('my-import:root');
expect(fixed.location).toBe('my-import:2');
});
it('rewrites matched identifiers in objects', function () {
expect(layout.configuration.layout.panels['my-import:1'])
.toBeDefined();
expect(layout.configuration.layout.panels['my-import:3'])
.toBeDefined();
expect(layout.configuration.layout.panels['483c00d4-bb1d-4b42-b29a-c58e06b322a0'])
.not.toBeDefined();
expect(layout.configuration.layout.panels['20273193-f069-49e9-b4f7-b97a87ed755d'])
.not.toBeDefined();
expect(fixed.configuration['fixed-display'].elements[0].id)
.toBe('my-import:1');
});
});
});
describe('childObjects', function () {
let swg;
let layout;
let fixed;
beforeEach(function () {
swg = staticProvider.get({
namespace: 'my-import',
key: '1'
});
layout = staticProvider.get({
namespace: 'my-import',
key: '2'
});
fixed = staticProvider.get({
namespace: 'my-import',
key: '3'
});
});
it('match expected ordering', function () {
// this is a sanity check to make sure the identifiers map in
// the correct order.
expect(swg.type).toBe('generator');
expect(layout.type).toBe('layout');
expect(fixed.type).toBe('telemetry.fixed');
});
it('have new-style identifiers', function () {
expect(swg.identifier).toEqual({
namespace: 'my-import',
key: '1'
});
expect(layout.identifier).toEqual({
namespace: 'my-import',
key: '2'
});
expect(fixed.identifier).toEqual({
namespace: 'my-import',
key: '3'
});
});
it('have new-style composition', function () {
expect(layout.composition).toContain({
namespace: 'my-import',
key: '1'
});
expect(layout.composition).toContain({
namespace: 'my-import',
key: '3'
});
expect(fixed.composition).toContain({
namespace: 'my-import',
key: '1'
});
});
it('rewrites locations', function () {
expect(swg.location).toBe('my-import:root');
expect(layout.location).toBe('my-import:root');
expect(fixed.location).toBe('my-import:2');
});
it('rewrites matched identifiers in objects', function () {
expect(layout.configuration.layout.panels['my-import:1'])
.toBeDefined();
expect(layout.configuration.layout.panels['my-import:3'])
.toBeDefined();
expect(layout.configuration.layout.panels['483c00d4-bb1d-4b42-b29a-c58e06b322a0'])
.not.toBeDefined();
expect(layout.configuration.layout.panels['20273193-f069-49e9-b4f7-b97a87ed755d'])
.not.toBeDefined();
expect(fixed.configuration['fixed-display'].elements[0].id)
.toBe('my-import:1');
});
});
});

View File

@@ -1,52 +1,63 @@
define([
'./StaticModelProvider'
], function (
StaticModelProvider
) {
/**
* Static Root Plugin: takes an export file and exposes it as a new root
* object.
*/
function StaticRootPlugin(namespace, exportUrl) {
/*****************************************************************************
* 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
* "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.
*****************************************************************************/
const rootIdentifier = {
namespace: namespace,
key: 'root'
};
import StaticModelProvider from './StaticModelProvider';
let cachedProvider;
export default function StaticRootPlugin(options) {
const rootIdentifier = {
namespace: options.namespace,
key: 'root'
};
function loadProvider() {
return fetch(exportUrl)
.then(function (response) {
return response.json();
})
.then(function (importData) {
cachedProvider = new StaticModelProvider(importData, rootIdentifier);
let cachedProvider;
return cachedProvider;
});
}
function loadProvider() {
return fetch(options.exportUrl)
.then(function (response) {
return response.json();
})
.then(function (importData) {
cachedProvider = new StaticModelProvider(importData, rootIdentifier);
function getProvider() {
if (!cachedProvider) {
cachedProvider = loadProvider();
}
return Promise.resolve(cachedProvider);
}
return function install(openmct) {
openmct.objects.addRoot(rootIdentifier);
openmct.objects.addProvider(namespace, {
get: function (identifier) {
return getProvider().then(function (provider) {
return provider.get(identifier);
});
}
return cachedProvider;
});
};
}
return StaticRootPlugin;
});
function getProvider() {
if (!cachedProvider) {
cachedProvider = loadProvider();
}
return Promise.resolve(cachedProvider);
}
return function install(openmct) {
openmct.objects.addRoot(rootIdentifier);
openmct.objects.addProvider(options.namespace, {
get: function (identifier) {
return getProvider().then(function (provider) {
return provider.get(identifier);
});
}
});
};
}

1
static-root.json Normal file
View File

@@ -0,0 +1 @@
{"openmct":{"a2de0f92-e1a9-4ab0-b815-8226eb016b33":{"identifier":{"key":"a2de0f92-e1a9-4ab0-b815-8226eb016b33","namespace":""},"name":"Parent Folder","type":"folder","composition":[{"key":"9cdda73d-9b0c-4b6d-8405-f90c33844e54","namespace":""},{"key":"d0ef5482-8bda-4082-b2e2-73807320d1d9","namespace":""},{"key":"97891e2c-6000-464f-a65d-e928fa0d172d","namespace":""}],"modified":1645824127466,"location":"mine","persisted":1645824127468},"9cdda73d-9b0c-4b6d-8405-f90c33844e54":{"identifier":{"key":"9cdda73d-9b0c-4b6d-8405-f90c33844e54","namespace":""},"name":"Child Folder","type":"folder","composition":[{"key":"aa6e8428-fe16-4033-9b04-8c63d559fe05","namespace":""},{"key":"f473b125-3aa3-419e-8fd9-004e08d42ba3","namespace":""}],"modified":1645824073376,"location":"a2de0f92-e1a9-4ab0-b815-8226eb016b33","persisted":1645824073378},"d0ef5482-8bda-4082-b2e2-73807320d1d9":{"identifier":{"key":"d0ef5482-8bda-4082-b2e2-73807320d1d9","namespace":""},"name":"Sibling Child Folder","type":"folder","composition":[{"key":"b9d53ee9-d5f0-404e-90c1-b0e4b30c3d54","namespace":""}],"modified":1645824073375,"location":"a2de0f92-e1a9-4ab0-b815-8226eb016b33","persisted":1645824073375},"97891e2c-6000-464f-a65d-e928fa0d172d":{"identifier":{"key":"97891e2c-6000-464f-a65d-e928fa0d172d","namespace":""},"name":"A Clock","type":"clock","configuration":{"baseFormat":"YYYY/MM/DD hh:mm:ss","use24":"clock12","timezone":"UTC"},"modified":1645824127465,"location":"a2de0f92-e1a9-4ab0-b815-8226eb016b33","persisted":1645824127465},"aa6e8428-fe16-4033-9b04-8c63d559fe05":{"identifier":{"key":"aa6e8428-fe16-4033-9b04-8c63d559fe05","namespace":""},"name":"Grandchild Folder","type":"folder","composition":[{"key":"f8f9dd1f-50fe-4bed-b0e4-dd85ce6d3498","namespace":""}],"modified":1645824155758,"location":"9cdda73d-9b0c-4b6d-8405-f90c33844e54","persisted":1645824155760},"f473b125-3aa3-419e-8fd9-004e08d42ba3":{"identifier":{"key":"f473b125-3aa3-419e-8fd9-004e08d42ba3","namespace":""},"name":"Secret Web Page","type":"webPage","url":"https://www.youtube.com/embed/dQw4w9WgXcQ","modified":1645823979349,"location":"9cdda73d-9b0c-4b6d-8405-f90c33844e54","persisted":1645823979349},"b9d53ee9-d5f0-404e-90c1-b0e4b30c3d54":{"identifier":{"key":"b9d53ee9-d5f0-404e-90c1-b0e4b30c3d54","namespace":""},"name":"Linky","type":"hyperlink","displayFormat":"link","linkTarget":"_self","url":"https://www.nasa.gov/viper/","displayText":"Viper Info","modified":1645824073374,"location":"d0ef5482-8bda-4082-b2e2-73807320d1d9","persisted":1645824073374},"f8f9dd1f-50fe-4bed-b0e4-dd85ce6d3498":{"identifier":{"key":"f8f9dd1f-50fe-4bed-b0e4-dd85ce6d3498","namespace":""},"name":"Display Layout","type":"layout","composition":[{"key":"f473b125-3aa3-419e-8fd9-004e08d42ba3","namespace":""},{"key":"b9d53ee9-d5f0-404e-90c1-b0e4b30c3d54","namespace":""},{"key":"97891e2c-6000-464f-a65d-e928fa0d172d","namespace":""}],"configuration":{"items":[{"width":58,"height":30,"x":1,"y":3,"identifier":{"key":"f473b125-3aa3-419e-8fd9-004e08d42ba3","namespace":""},"hasFrame":true,"fontSize":"default","font":"default","type":"subobject-view","id":"f7b49d10-d606-467f-bc08-5d660b8ae693"},{"width":11,"height":2,"x":60,"y":3,"identifier":{"key":"b9d53ee9-d5f0-404e-90c1-b0e4b30c3d54","namespace":""},"hasFrame":false,"fontSize":"default","font":"default","type":"subobject-view","id":"483b5762-455f-485e-a17d-24699dbcbe06"},{"width":31,"height":6,"x":60,"y":6,"identifier":{"key":"97891e2c-6000-464f-a65d-e928fa0d172d","namespace":""},"hasFrame":true,"fontSize":"default","font":"default","type":"subobject-view","id":"53026423-72c4-4b5f-9290-9c7abbd1bec7"}],"layoutGrid":[10,10]},"modified":1645824197324,"location":"aa6e8428-fe16-4033-9b04-8c63d559fe05","persisted":1645824201302}},"rootId":"a2de0f92-e1a9-4ab0-b815-8226eb016b33"}

View File

@@ -1,5 +1,6 @@
const { merge } = require('webpack-merge');
const common = require('./webpack.common');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require('path');
const webpack = require('webpack');
@@ -14,6 +15,14 @@ module.exports = merge(common, {
plugins: [
new webpack.DefinePlugin({
__OPENMCT_ROOT_RELATIVE__: '"dist/"'
}),
new CopyWebpackPlugin({
patterns: [
{
from: './static-root.json',
to: '.'
}
]
})
],
devtool: 'eval-source-map'