diff --git a/platform/import-export/bundle.js b/platform/import-export/bundle.js index 1be4c98d4b..a89de82911 100644 --- a/platform/import-export/bundle.js +++ b/platform/import-export/bundle.js @@ -49,9 +49,11 @@ define([ "category": "contextual", "cssClass": "icon-export", "depends": [ + "openmct", "exportService", "policyService", - "identifierService" + "identifierService", + "typeService" ] }, { diff --git a/platform/import-export/src/actions/ExportAsJSONAction.js b/platform/import-export/src/actions/ExportAsJSONAction.js index d4b3b1a2cc..342198adb7 100644 --- a/platform/import-export/src/actions/ExportAsJSONAction.js +++ b/platform/import-export/src/actions/ExportAsJSONAction.js @@ -20,7 +20,7 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ -define([], function () { +define(['lodash'], function (_) { /** * The ExportAsJSONAction is available from context menus and allows a user @@ -31,12 +31,14 @@ define([], function () { * @memberof platform/import-export */ function ExportAsJSONAction( + openmct, exportService, policyService, identifierService, + typeService, context ) { - + this.openmct = openmct; this.root = {}; this.tree = {}; this.calls = 0; @@ -45,15 +47,21 @@ define([], function () { this.exportService = exportService; this.policyService = policyService; this.identifierService = identifierService; + this.typeService = typeService; + + this.idMap = {}; } ExportAsJSONAction.prototype.perform = function () { - this.root = this.context.domainObject; - this.tree[this.root.getId()] = this.root.getModel(); + var root = this.context.domainObject.useCapability('adapter'); + this.root = this.copyObject(root); + var rootId = this.getId(this.root); + this.tree[rootId] = this.root; + this.saveAs = function (completedTree) { this.exportService.exportJSON( completedTree, - {filename: this.root.getModel().name + '.json'} + {filename: this.root.name + '.json'} ); }; @@ -68,22 +76,23 @@ define([], function () { * @param {Object} parent */ ExportAsJSONAction.prototype.write = function (parent) { - this.calls++; - if (parent.hasCapability('composition')) { - parent.useCapability('composition') + var composition = this.openmct.composition.get(parent); + + if (composition !== undefined) { + composition.load() .then(function (children) { children.forEach(function (child, index) { // Only export if object is creatable if (this.isCreatable(child)) { // Prevents infinite export of self-contained objs - if (!this.tree.hasOwnProperty(child.getId())) { + if (!this.tree.hasOwnProperty(this.getId(child))) { // If object is a link to something absent from // tree, generate new id and treat as new object if (this.isExternal(child, parent)) { - this.rewriteLink(child, parent); + child = this.rewriteLink(child, parent); } else { - this.tree[child.getId()] = child.getModel(); + this.tree[this.getId(child)] = child; } this.write(child); } @@ -91,12 +100,14 @@ define([], function () { }.bind(this)); this.calls--; if (this.calls === 0) { + this.rewriteReferences(); this.saveAs(this.wrapTree()); } }.bind(this)); } else { this.calls--; if (this.calls === 0) { + this.rewriteReferences(); this.saveAs(this.wrapTree()); } } @@ -109,29 +120,34 @@ define([], function () { * @private */ ExportAsJSONAction.prototype.rewriteLink = function (child, parent) { - this.externalIdentifiers.push(child.getId()); - var parentModel = parent.getModel(); - var childModel = child.getModel(); - var index = parentModel.composition.indexOf(child.getId()); - var newModel = this.copyModel(childModel); - var newId = this.identifierService.generate(); + this.externalIdentifiers.push(this.getId(child)); + var index = _.findIndex(parent.composition, function (id) { + return _.isEqual(child.identifier, id); + }); + var copyOfChild = this.copyObject(child); + copyOfChild.identifier.key = this.identifierService.generate(); + var newIdString = this.getId(copyOfChild); + var parentId = this.getId(parent); - newModel.location = parent.getId(); - this.tree[newId] = newModel; - this.tree[parent.getId()] = this.copyModel(parentModel); - this.tree[parent.getId()].composition[index] = newId; + this.idMap[this.getId(child)] = newIdString; + copyOfChild.location = parentId; + parent.composition[index] = copyOfChild.identifier; + this.tree[newIdString] = copyOfChild; + this.tree[parentId].composition[index] = newIdString; + + return copyOfChild; }; - ExportAsJSONAction.prototype.copyModel = function (model) { - var jsonString = JSON.stringify(model); + ExportAsJSONAction.prototype.copyObject = function (object) { + var jsonString = JSON.stringify(object); return JSON.parse(jsonString); }; ExportAsJSONAction.prototype.isExternal = function (child, parent) { - if (child.getModel().location !== parent.getId() && - !Object.keys(this.tree).includes(child.getModel().location) && - child.getId() !== this.root.getId() || - this.externalIdentifiers.includes(child.getId())) { + if (child.location !== this.getId(parent) && + !Object.keys(this.tree).includes(child.location) && + this.getId(child) !== this.getId(this.root) || + this.externalIdentifiers.includes(this.getId(child))) { return true; } @@ -147,16 +163,36 @@ define([], function () { ExportAsJSONAction.prototype.wrapTree = function () { return { "openmct": this.tree, - "rootId": this.root.getId() + "rootId": this.getId(this.root) }; }; ExportAsJSONAction.prototype.isCreatable = function (domainObject) { + var type = this.typeService.getType(domainObject.type); return this.policyService.allow( "creation", - domainObject.getCapability("type") + type ); }; + /** + * @private + */ + ExportAsJSONAction.prototype.getId = function (domainObject) { + return this.openmct.objects.makeKeyString(domainObject.identifier); + }; + + /** + * @private + */ + ExportAsJSONAction.prototype.rewriteReferences = function () { + var treeString = JSON.stringify(this.tree); + Object.keys(this.idMap).forEach(function (oldId) { + var newId = this.idMap[oldId]; + treeString = treeString.split(oldId).join(newId); + }.bind(this)); + this.tree = JSON.parse(treeString); + }; + return ExportAsJSONAction; }); diff --git a/platform/import-export/test/actions/ExportAsJSONActionSpec.js b/platform/import-export/test/actions/ExportAsJSONActionSpec.js index c59f71fa81..6eb82ba6ca 100644 --- a/platform/import-export/test/actions/ExportAsJSONActionSpec.js +++ b/platform/import-export/test/actions/ExportAsJSONActionSpec.js @@ -23,9 +23,11 @@ define( [ "../../src/actions/ExportAsJSONAction", - "../../../entanglement/test/DomainObjectFactory" + "../../../entanglement/test/DomainObjectFactory", + "../../../../src/MCT", + '../../../../src/adapter/capabilities/AdapterCapability' ], - function (ExportAsJSONAction, domainObjectFactory) { + function (ExportAsJSONAction, domainObjectFactory, MCT, AdapterCapability) { describe("The export JSON action", function () { @@ -33,29 +35,48 @@ define( action, exportService, identifierService, + typeService, + openmct, policyService, mockType, + mockObjectProvider, exportedTree; beforeEach(function () { + openmct = new MCT(); + mockObjectProvider = { + objects: {}, + get: function (id) { + return Promise.resolve(mockObjectProvider.objects[id.key]); + } + }; + openmct.objects.addProvider('', mockObjectProvider); + exportService = jasmine.createSpyObj('exportService', ['exportJSON']); identifierService = jasmine.createSpyObj('identifierService', ['generate']); policyService = jasmine.createSpyObj('policyService', ['allow']); - mockType = - jasmine.createSpyObj('type', ['hasFeature']); + mockType = jasmine.createSpyObj('type', ['hasFeature']); + typeService = jasmine.createSpyObj('typeService', [ + 'getType' + ]); mockType.hasFeature.andCallFake(function (feature) { return feature === 'creation'; }); + + typeService.getType.andReturn(mockType); + context = {}; context.domainObject = domainObjectFactory( { name: 'test', id: 'someID', - capabilities: {type: mockType} + capabilities: {'adapter': { + invoke: invokeAdapter + }} }); identifierService.generate.andReturn('brandNewId'); exportService.exportJSON.andCallFake(function (tree, options) { @@ -65,10 +86,15 @@ define( return type.hasFeature(capability); }); - action = new ExportAsJSONAction(exportService, policyService, - identifierService, context); + action = new ExportAsJSONAction(openmct, exportService, policyService, + identifierService, typeService, context); }); + function invokeAdapter() { + var newStyleObject = new AdapterCapability(context.domainObject).invoke(); + return newStyleObject; + } + it("initializes happily", function () { expect(action).toBeDefined(); }); @@ -81,32 +107,24 @@ define( } }; - var parentComposition = - jasmine.createSpyObj('parentComposition', ['invoke']); + typeService.getType.andReturn(nonCreatableType); var parent = domainObjectFactory({ name: 'parent', - model: { name: 'parent', location: 'ROOT'}, + model: { name: 'parent', location: 'ROOT', composition: ['childId']}, id: 'parentId', - capabilities: { - composition: parentComposition, - type: mockType - } + capabilities: {'adapter': { + invoke: invokeAdapter + }} }); - var child = domainObjectFactory({ + var child = { + identifier: {namespace: '', key: 'childId'}, name: 'child', - model: { name: 'child', location: 'parentId' }, - id: 'childId', - capabilities: { - type: nonCreatableType - } - }); - - parentComposition.invoke.andReturn( - Promise.resolve([child]) - ); + location: 'parentId' + }; context.domainObject = parent; + addChild(child); var init = false; runs(function () { @@ -128,42 +146,25 @@ define( }); it("can export self-containing objects", function () { - var infiniteParentComposition = - jasmine.createSpyObj('infiniteParentComposition', - ['invoke'] - ); - - var infiniteChildComposition = - jasmine.createSpyObj('infiniteChildComposition', - ['invoke'] - ); - var parent = domainObjectFactory({ name: 'parent', - model: { name: 'parent', location: 'ROOT'}, + model: { name: 'parent', location: 'ROOT', composition: ['infiniteChildId']}, id: 'infiniteParentId', capabilities: { - composition: infiniteParentComposition, - type: mockType + 'adapter': { + invoke: invokeAdapter + } } }); - var child = domainObjectFactory({ + var child = { + identifier: {namespace: '', key: 'infiniteChildId'}, name: 'child', - model: { name: 'child', location: 'infiniteParentId' }, - id: 'infiniteChildId', - capabilities: { - composition: infiniteChildComposition, - type: mockType - } - }); + location: 'infiniteParentId', + composition: ['infiniteParentId'] + }; + addChild(child); - infiniteParentComposition.invoke.andReturn( - Promise.resolve([child]) - ); - infiniteChildComposition.invoke.andReturn( - Promise.resolve([parent]) - ); context.domainObject = parent; var init = false; @@ -188,11 +189,6 @@ define( }); it("exports links to external objects as new objects", function () { - var externallyLinkedComposition = - jasmine.createSpyObj('externallyLinkedComposition', - ['invoke'] - ); - var parent = domainObjectFactory({ name: 'parent', model: { @@ -200,24 +196,18 @@ define( composition: ['externalId'], location: 'ROOT'}, id: 'parentId', - capabilities: { - composition: externallyLinkedComposition, - type: mockType - } + capabilities: {'adapter': { + invoke: invokeAdapter + }} }); - var externalObject = domainObjectFactory({ + var externalObject = { name: 'external', - model: { name: 'external', location: 'outsideOfTree'}, - id: 'externalId', - capabilities: { - type: mockType - } - }); + location: 'outsideOfTree', + identifier: {namespace: '', key: 'externalId'} + }; + addChild(externalObject); - externallyLinkedComposition.invoke.andReturn( - Promise.resolve([externalObject]) - ); context.domainObject = parent; var init = false; @@ -230,7 +220,7 @@ define( waitsFor(function () { return init; - }, "Exported tree sohuld have been built"); + }, "Exported tree should have been built"); runs(function () { expect(Object.keys(action.tree).length).toBe(2); @@ -261,6 +251,10 @@ define( expect(exportedTree.hasOwnProperty('rootId')).toBeTruthy(); }); }); + + function addChild(object) { + mockObjectProvider.objects[object.identifier.key] = object; + } }); } ); diff --git a/src/api/objects/ObjectAPI.js b/src/api/objects/ObjectAPI.js index 334f745437..f0d3f159aa 100644 --- a/src/api/objects/ObjectAPI.js +++ b/src/api/objects/ObjectAPI.js @@ -207,6 +207,14 @@ define([ return mutableObject.stopListening.bind(mutableObject); }; + /** + * @param {module:openmct.ObjectAPI~Identifier} identifier + * @returns {string} A string representation of the given identifier, including namespace and key + */ + ObjectAPI.prototype.makeKeyString = function (identifier) { + return utils.makeKeyString(identifier); + }; + /** * Uniquely identifies a domain object. *