diff --git a/src/plugins/staticRootPlugin/StaticModelProvider.js b/src/plugins/staticRootPlugin/StaticModelProvider.js index 3b495e431f..4bf9ffbca3 100644 --- a/src/plugins/staticRootPlugin/StaticModelProvider.js +++ b/src/plugins/staticRootPlugin/StaticModelProvider.js @@ -46,76 +46,96 @@ 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); - } 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; + let mappedLeafValue; + if (oldRootNamespace) { + mappedLeafValue = idMap.get(objectUtils.makeKeyString({ + namespace: oldRootNamespace, + key: leafValue + })); + } else { + mappedLeafValue = idMap.get(leafValue); } - return null; - } else if (idMap.get(leafValue)) { - const newIdentifier = objectUtils.makeKeyString({ - namespace, - key: idMap.get(leafValue) + return mappedLeafValue ?? leafValue; + } else if (leafKey === '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') { + const mappedLeafValue = idMap.get(leafValue); + if (!mappedLeafValue) { + return null; + } + + const newLocationIdentifier = objectUtils.makeKeyString({ + namespace: newRootNamespace, + key: mappedLeafValue }); - return newIdentifier; + return newLocationIdentifier; } else { - return leafValue; + const mappedLeafValue = idMap.get(leafValue); + if (mappedLeafValue) { + const newIdentifier = objectUtils.makeKeyString({ + namespace: newRootNamespace, + key: mappedLeafValue + }); + + return newIdentifier; + } else { + 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; } diff --git a/src/plugins/staticRootPlugin/StaticModelProviderSpec.js b/src/plugins/staticRootPlugin/StaticModelProviderSpec.js index 51481f4e6a..347a160dd9 100644 --- a/src/plugins/staticRootPlugin/StaticModelProviderSpec.js +++ b/src/plugins/staticRootPlugin/StaticModelProviderSpec.js @@ -20,130 +20,265 @@ * 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)); - staticProvider = new StaticModelProvider(staticData, { - namespace: 'my-import', - key: 'root' - }); - }); - - describe('rootObject', function () { - let rootModel; + let staticProvider; beforeEach(function () { - rootModel = staticProvider.get({ + const staticData = JSON.parse(JSON.stringify(testStaticDataEmptyNamespace)); + staticProvider = new StaticModelProvider(staticData, { namespace: 'my-import', key: 'root' }); }); - it('is located at top level', function () { - expect(rootModel.location).toBe('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 identifiers in 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({ + 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 remapped 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 remapped 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('with namespace "foo"', function () { + + let staticProvider; + + beforeEach(function () { + const staticData = JSON.parse(JSON.stringify(testStaticDataFooNamespace)); + staticProvider = new StaticModelProvider(staticData, { 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' - }); - }); - }); + describe('rootObject', function () { + let rootModel; - describe('childObjects', function () { - let swg; - let layout; - let fixed; + beforeEach(function () { + rootModel = staticProvider.get({ + namespace: 'my-import', + key: 'root' + }); + }); - beforeEach(function () { - swg = staticProvider.get({ - namespace: 'my-import', - key: '1' + it('is located at top level', function () { + expect(rootModel.location).toBe('ROOT'); }); - layout = staticProvider.get({ - namespace: 'my-import', - key: '2' + + it('has remapped identifier', function () { + expect(rootModel.identifier).toEqual({ + namespace: 'my-import', + key: 'root' + }); }); - fixed = staticProvider.get({ - namespace: 'my-import', - key: '3' + + it('has remapped composition', function () { + expect(rootModel.composition).toContain({ + namespace: 'my-import', + key: '1' + }); + expect(rootModel.composition).toContain({ + namespace: 'my-import', + key: '2' + }); }); }); - 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'); - }); + describe('childObjects', function () { + let clock; + let layout; + let swg; + let folder; - it('have new-style identifiers', function () { - expect(swg.identifier).toEqual({ - namespace: 'my-import', - key: '1' + 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' + }); }); - expect(layout.identifier).toEqual({ - namespace: 'my-import', - key: '2' + + 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'); }); - expect(fixed.identifier).toEqual({ - namespace: 'my-import', - key: '3' + + 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'); }); }); - - 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'); - }); - }); }); + diff --git a/src/plugins/staticRootPlugin/static-provider-test.json b/src/plugins/staticRootPlugin/test-data/static-provider-test-empty-namespace.json similarity index 100% rename from src/plugins/staticRootPlugin/static-provider-test.json rename to src/plugins/staticRootPlugin/test-data/static-provider-test-empty-namespace.json diff --git a/src/plugins/staticRootPlugin/test-data/static-provider-test-foo-namespace.json b/src/plugins/staticRootPlugin/test-data/static-provider-test-foo-namespace.json new file mode 100644 index 0000000000..49dd9d5926 --- /dev/null +++ b/src/plugins/staticRootPlugin/test-data/static-provider-test-foo-namespace.json @@ -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"} \ No newline at end of file