mirror of
https://github.com/nasa/openmct.git
synced 2024-12-18 20:57:53 +00:00
fix(#6549): [StaticRootPlugin] Remap non-empty root namespaces and non-root namespaces correctly (#6583)
* fix: preserve truthy namespaces and unmapped values - Fixes the issue of the Static Root provider overriding existing namespaces, such as those from a telemetry dictionary - Fixes the issue of keys of child objects NOT present in the idMapping (such as those from a telemetry dictionary) being overwritten as undefined - TODO: This will not work for objects exported from an environment that has the "MyItems" namespace defined to anything other than an empty string. Need to figure out how to handle this. * fix: handle the case of rootId having a namespace !== "" * refactor: use `parseKeyString` * fix: StaticRootPlugin object mapping for non-empty namespaces * fix: use index, fix location identifiers * tets: add non-empty namespace tests (wip) * refactor: rename and move test files * test: update StaticModelProvider tests * fix: remap to identifiers for config, not keystring --------- Co-authored-by: Khalid Adil <khalidadil29@gmail.com>
This commit is contained in:
parent
a9a98380f2
commit
dbdc9bb4e2
@ -46,66 +46,84 @@ class StaticModelProvider {
|
||||
throw new Error(keyString + ' not found in import models.');
|
||||
}
|
||||
|
||||
parseObjectLeaf(objectLeaf, idMap, namespace) {
|
||||
parseObjectLeaf(objectLeaf, idMap, newRootNamespace, oldRootNamespace) {
|
||||
Object.keys(objectLeaf).forEach((nodeKey) => {
|
||||
if (idMap.get(nodeKey)) {
|
||||
const newIdentifier = objectUtils.makeKeyString({
|
||||
namespace,
|
||||
namespace: newRootNamespace,
|
||||
key: idMap.get(nodeKey)
|
||||
});
|
||||
objectLeaf[newIdentifier] = { ...objectLeaf[nodeKey] };
|
||||
delete objectLeaf[nodeKey];
|
||||
objectLeaf[newIdentifier] = this.parseTreeLeaf(newIdentifier, objectLeaf[newIdentifier], idMap, namespace);
|
||||
objectLeaf[newIdentifier] = this.parseTreeLeaf(newIdentifier, objectLeaf[newIdentifier], idMap, newRootNamespace, oldRootNamespace);
|
||||
} else {
|
||||
objectLeaf[nodeKey] = this.parseTreeLeaf(nodeKey, objectLeaf[nodeKey], idMap, namespace);
|
||||
objectLeaf[nodeKey] = this.parseTreeLeaf(nodeKey, objectLeaf[nodeKey], idMap, newRootNamespace, oldRootNamespace);
|
||||
}
|
||||
});
|
||||
|
||||
return objectLeaf;
|
||||
}
|
||||
|
||||
parseArrayLeaf(arrayLeaf, idMap, namespace) {
|
||||
parseArrayLeaf(arrayLeaf, idMap, newRootNamespace, oldRootNamespace) {
|
||||
return arrayLeaf.map((leafValue, index) => this.parseTreeLeaf(
|
||||
null, leafValue, idMap, namespace));
|
||||
null, leafValue, idMap, newRootNamespace, oldRootNamespace));
|
||||
}
|
||||
|
||||
parseBranchedLeaf(branchedLeafValue, idMap, namespace) {
|
||||
parseBranchedLeaf(branchedLeafValue, idMap, newRootNamespace, oldRootNamespace) {
|
||||
if (Array.isArray(branchedLeafValue)) {
|
||||
return this.parseArrayLeaf(branchedLeafValue, idMap, namespace);
|
||||
return this.parseArrayLeaf(branchedLeafValue, idMap, newRootNamespace, oldRootNamespace);
|
||||
} else {
|
||||
return this.parseObjectLeaf(branchedLeafValue, idMap, namespace);
|
||||
return this.parseObjectLeaf(branchedLeafValue, idMap, newRootNamespace, oldRootNamespace);
|
||||
}
|
||||
}
|
||||
|
||||
parseTreeLeaf(leafKey, leafValue, idMap, namespace) {
|
||||
parseTreeLeaf(leafKey, leafValue, idMap, newRootNamespace, oldRootNamespace) {
|
||||
if (leafValue === null || leafValue === undefined) {
|
||||
return leafValue;
|
||||
}
|
||||
|
||||
const hasChild = typeof leafValue === 'object';
|
||||
if (hasChild) {
|
||||
return this.parseBranchedLeaf(leafValue, idMap, namespace);
|
||||
return this.parseBranchedLeaf(leafValue, idMap, newRootNamespace, oldRootNamespace);
|
||||
}
|
||||
|
||||
if (leafKey === 'key') {
|
||||
return idMap.get(leafValue);
|
||||
let mappedLeafValue;
|
||||
if (oldRootNamespace) {
|
||||
mappedLeafValue = idMap.get(objectUtils.makeKeyString({
|
||||
namespace: oldRootNamespace,
|
||||
key: leafValue
|
||||
}));
|
||||
} else {
|
||||
mappedLeafValue = idMap.get(leafValue);
|
||||
}
|
||||
|
||||
return mappedLeafValue ?? leafValue;
|
||||
} else if (leafKey === 'namespace') {
|
||||
return namespace;
|
||||
// Only rewrite the namespace if it matches the old root namespace.
|
||||
// This is to prevent rewriting namespaces of objects that are not
|
||||
// children of the root object (e.g.: objects from a telemetry dictionary)
|
||||
return leafValue === oldRootNamespace
|
||||
? newRootNamespace
|
||||
: leafValue;
|
||||
} else if (leafKey === 'location') {
|
||||
if (idMap.get(leafValue)) {
|
||||
const mappedLeafValue = idMap.get(leafValue);
|
||||
if (!mappedLeafValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const newLocationIdentifier = objectUtils.makeKeyString({
|
||||
namespace,
|
||||
key: idMap.get(leafValue)
|
||||
namespace: newRootNamespace,
|
||||
key: mappedLeafValue
|
||||
});
|
||||
|
||||
return newLocationIdentifier;
|
||||
}
|
||||
|
||||
return null;
|
||||
} else if (idMap.get(leafValue)) {
|
||||
} else {
|
||||
const mappedLeafValue = idMap.get(leafValue);
|
||||
if (mappedLeafValue) {
|
||||
const newIdentifier = objectUtils.makeKeyString({
|
||||
namespace,
|
||||
key: idMap.get(leafValue)
|
||||
namespace: newRootNamespace,
|
||||
key: mappedLeafValue
|
||||
});
|
||||
|
||||
return newIdentifier;
|
||||
@ -113,9 +131,11 @@ class StaticModelProvider {
|
||||
return leafValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rewriteObjectIdentifiers(importData, rootIdentifier) {
|
||||
const namespace = rootIdentifier.namespace;
|
||||
const { namespace: oldRootNamespace } = objectUtils.parseKeyString(importData.rootId);
|
||||
const { namespace: newRootNamespace } = rootIdentifier;
|
||||
const idMap = new Map();
|
||||
const objectTree = importData.openmct;
|
||||
|
||||
@ -128,7 +148,7 @@ class StaticModelProvider {
|
||||
idMap.set(originalId, newId);
|
||||
});
|
||||
|
||||
const newTree = this.parseTreeLeaf(null, objectTree, idMap, namespace);
|
||||
const newTree = this.parseTreeLeaf(null, objectTree, idMap, newRootNamespace, oldRootNamespace);
|
||||
|
||||
return newTree;
|
||||
}
|
||||
|
@ -20,15 +20,17 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import testStaticData from './static-provider-test.json';
|
||||
import testStaticDataEmptyNamespace from './test-data/static-provider-test-empty-namespace.json';
|
||||
import testStaticDataFooNamespace from './test-data/static-provider-test-foo-namespace.json';
|
||||
import StaticModelProvider from './StaticModelProvider';
|
||||
|
||||
describe('StaticModelProvider', function () {
|
||||
describe('with empty namespace', function () {
|
||||
|
||||
let staticProvider;
|
||||
|
||||
beforeEach(function () {
|
||||
const staticData = JSON.parse(JSON.stringify(testStaticData));
|
||||
const staticData = JSON.parse(JSON.stringify(testStaticDataEmptyNamespace));
|
||||
staticProvider = new StaticModelProvider(staticData, {
|
||||
namespace: 'my-import',
|
||||
key: 'root'
|
||||
@ -49,14 +51,14 @@ describe('StaticModelProvider', function () {
|
||||
expect(rootModel.location).toBe('ROOT');
|
||||
});
|
||||
|
||||
it('has new-format identifier', function () {
|
||||
it('has remapped identifier', function () {
|
||||
expect(rootModel.identifier).toEqual({
|
||||
namespace: 'my-import',
|
||||
key: 'root'
|
||||
});
|
||||
});
|
||||
|
||||
it('has new-format composition', function () {
|
||||
it('has remapped identifiers in composition', function () {
|
||||
expect(rootModel.composition).toContain({
|
||||
namespace: 'my-import',
|
||||
key: '1'
|
||||
@ -96,7 +98,7 @@ describe('StaticModelProvider', function () {
|
||||
expect(fixed.type).toBe('telemetry.fixed');
|
||||
});
|
||||
|
||||
it('have new-style identifiers', function () {
|
||||
it('have remapped identifiers', function () {
|
||||
expect(swg.identifier).toEqual({
|
||||
namespace: 'my-import',
|
||||
key: '1'
|
||||
@ -111,7 +113,7 @@ describe('StaticModelProvider', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('have new-style composition', function () {
|
||||
it('have remapped composition', function () {
|
||||
expect(layout.composition).toContain({
|
||||
namespace: 'my-import',
|
||||
key: '1'
|
||||
@ -147,3 +149,136 @@ describe('StaticModelProvider', function () {
|
||||
|
||||
});
|
||||
});
|
||||
describe('with namespace "foo"', function () {
|
||||
|
||||
let staticProvider;
|
||||
|
||||
beforeEach(function () {
|
||||
const staticData = JSON.parse(JSON.stringify(testStaticDataFooNamespace));
|
||||
staticProvider = new StaticModelProvider(staticData, {
|
||||
namespace: 'my-import',
|
||||
key: 'root'
|
||||
});
|
||||
});
|
||||
|
||||
describe('rootObject', function () {
|
||||
let rootModel;
|
||||
|
||||
beforeEach(function () {
|
||||
rootModel = staticProvider.get({
|
||||
namespace: 'my-import',
|
||||
key: 'root'
|
||||
});
|
||||
});
|
||||
|
||||
it('is located at top level', function () {
|
||||
expect(rootModel.location).toBe('ROOT');
|
||||
});
|
||||
|
||||
it('has remapped identifier', function () {
|
||||
expect(rootModel.identifier).toEqual({
|
||||
namespace: 'my-import',
|
||||
key: 'root'
|
||||
});
|
||||
});
|
||||
|
||||
it('has remapped composition', function () {
|
||||
expect(rootModel.composition).toContain({
|
||||
namespace: 'my-import',
|
||||
key: '1'
|
||||
});
|
||||
expect(rootModel.composition).toContain({
|
||||
namespace: 'my-import',
|
||||
key: '2'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('childObjects', function () {
|
||||
let clock;
|
||||
let layout;
|
||||
let swg;
|
||||
let folder;
|
||||
|
||||
beforeEach(function () {
|
||||
folder = staticProvider.get({
|
||||
namespace: 'my-import',
|
||||
key: 'root'
|
||||
});
|
||||
layout = staticProvider.get({
|
||||
namespace: 'my-import',
|
||||
key: '1'
|
||||
});
|
||||
swg = staticProvider.get({
|
||||
namespace: 'my-import',
|
||||
key: '2'
|
||||
});
|
||||
clock = 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(folder.type).toBe('folder');
|
||||
expect(swg.type).toBe('generator');
|
||||
expect(layout.type).toBe('layout');
|
||||
expect(clock.type).toBe('clock');
|
||||
});
|
||||
|
||||
it('have remapped identifiers', function () {
|
||||
expect(folder.identifier).toEqual({
|
||||
namespace: 'my-import',
|
||||
key: 'root'
|
||||
});
|
||||
expect(layout.identifier).toEqual({
|
||||
namespace: 'my-import',
|
||||
key: '1'
|
||||
});
|
||||
expect(swg.identifier).toEqual({
|
||||
namespace: 'my-import',
|
||||
key: '2'
|
||||
});
|
||||
expect(clock.identifier).toEqual({
|
||||
namespace: 'my-import',
|
||||
key: '3'
|
||||
});
|
||||
});
|
||||
|
||||
it('have remapped identifiers in composition', function () {
|
||||
expect(layout.composition).toContain({
|
||||
namespace: 'my-import',
|
||||
key: '2'
|
||||
});
|
||||
expect(layout.composition).toContain({
|
||||
namespace: 'my-import',
|
||||
key: '3'
|
||||
});
|
||||
});
|
||||
|
||||
it('layout has remapped identifiers in configuration', function () {
|
||||
const identifiers = layout.configuration.items
|
||||
.map(item => item.identifier)
|
||||
.filter(identifier => identifier !== undefined);
|
||||
expect(identifiers).toContain({
|
||||
namespace: 'my-import',
|
||||
key: '2'
|
||||
});
|
||||
expect(identifiers).toContain({
|
||||
namespace: 'my-import',
|
||||
key: '3'
|
||||
});
|
||||
});
|
||||
|
||||
it('rewrites locations', function () {
|
||||
expect(folder.location).toBe('ROOT');
|
||||
expect(swg.location).toBe('my-import:root');
|
||||
expect(layout.location).toBe('my-import:root');
|
||||
expect(clock.location).toBe('my-import:root');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -0,0 +1 @@
|
||||
{"openmct":{"foo:a6c9e745-89e5-4c3a-8f54-7a34711aaeb1":{"identifier":{"key":"a6c9e745-89e5-4c3a-8f54-7a34711aaeb1","namespace":"foo"},"name":"Folder Foo","type":"folder","composition":[{"key":"95729018-86ed-4484-867d-10c63c41c5a1","namespace":"foo"},{"key":"22c438f0-953b-42c5-8fb2-9d5dbeb88a0c","namespace":"foo"},{"key":"3545554b-53c8-467d-a70d-e90d1a120e4a","namespace":"foo"}],"modified":1681164966705,"location":"foo:mine","created":1681164829371,"persisted":1681164966706},"foo:95729018-86ed-4484-867d-10c63c41c5a1":{"identifier":{"key":"95729018-86ed-4484-867d-10c63c41c5a1","namespace":"foo"},"name":"Display Layout Bar","type":"layout","composition":[{"key":"22c438f0-953b-42c5-8fb2-9d5dbeb88a0c","namespace":"foo"},{"key":"3545554b-53c8-467d-a70d-e90d1a120e4a","namespace":"foo"}],"configuration":{"items":[{"fill":"#666666","stroke":"","x":42,"y":42,"width":20,"height":4,"type":"box-view","id":"14505a5d-b846-4504-961f-8c9bcdf19f39"},{"identifier":{"key":"22c438f0-953b-42c5-8fb2-9d5dbeb88a0c","namespace":"foo"},"x":0,"y":0,"width":40,"height":15,"displayMode":"all","value":"sin","stroke":"","fill":"","color":"","fontSize":"default","font":"default","type":"telemetry-view","id":"05baa95f-2064-4cb0-ad9f-575758491220"},{"width":40,"height":15,"x":0,"y":15,"identifier":{"key":"3545554b-53c8-467d-a70d-e90d1a120e4a","namespace":"foo"},"hasFrame":true,"fontSize":"default","font":"default","type":"subobject-view","id":"70e1b8b7-cd59-4a52-b796-d68fb0c48fc5"}],"layoutGrid":[10,10],"objectStyles":{"05baa95f-2064-4cb0-ad9f-575758491220":{"staticStyle":{"style":{"border":"1px solid #00ff00","backgroundColor":"#0000ff","color":"#ff00ff"}}}}},"modified":1681165037189,"location":"foo:a6c9e745-89e5-4c3a-8f54-7a34711aaeb1","created":1681164838178,"persisted":1681165037190},"foo:22c438f0-953b-42c5-8fb2-9d5dbeb88a0c":{"identifier":{"key":"22c438f0-953b-42c5-8fb2-9d5dbeb88a0c","namespace":"foo"},"name":"SWG Baz","type":"generator","telemetry":{"period":"20","amplitude":"2","offset":"5","dataRateInHz":1,"phase":0,"randomness":0,"loadDelay":0,"infinityValues":false,"staleness":false},"modified":1681164910719,"location":"foo:a6c9e745-89e5-4c3a-8f54-7a34711aaeb1","created":1681164903684,"persisted":1681164910719},"foo:3545554b-53c8-467d-a70d-e90d1a120e4a":{"identifier":{"key":"3545554b-53c8-467d-a70d-e90d1a120e4a","namespace":"foo"},"name":"Clock Qux","type":"clock","configuration":{"baseFormat":"YYYY/MM/DD hh:mm:ss","use24":"clock12","timezone":"UTC"},"modified":1681164989837,"location":"foo:a6c9e745-89e5-4c3a-8f54-7a34711aaeb1","created":1681164966702,"persisted":1681164989837}},"rootId":"foo:a6c9e745-89e5-4c3a-8f54-7a34711aaeb1"}
|
Loading…
Reference in New Issue
Block a user