diff --git a/src/plugins/exportAsJSONAction/ExportAsJSONAction.js b/src/plugins/exportAsJSONAction/ExportAsJSONAction.js index ccc4b1b346..5ab4aadf58 100644 --- a/src/plugins/exportAsJSONAction/ExportAsJSONAction.js +++ b/src/plugins/exportAsJSONAction/ExportAsJSONAction.js @@ -22,7 +22,6 @@ import JSONExporter from '/src/exporters/JSONExporter.js'; import _ from 'lodash'; -import { saveAs } from 'saveAs'; import uuid from "uuid"; export default class ExportAsJSONAction { @@ -41,7 +40,7 @@ export default class ExportAsJSONAction { this.calls = 0; this.idMap = {}; - this.JSONExportService = new JSONExporter(saveAs); + this.JSONExportService = new JSONExporter(); } // Public @@ -68,7 +67,6 @@ export default class ExportAsJSONAction { this._write(this.root); } - /** * @private * @param {object} domainObject @@ -116,6 +114,7 @@ export default class ExportAsJSONAction { return _.isEqual(child.identifier, id); }); const copyOfChild = JSON.parse(JSON.stringify(child)); + copyOfChild.identifier.key = uuid(); const newIdString = this._getId(copyOfChild); const parentId = this._getId(parent); diff --git a/src/plugins/exportAsJSONAction/ExportAsJSONAction.spec.js b/src/plugins/exportAsJSONAction/ExportAsJSONAction.spec.js deleted file mode 100644 index 99e70dc3e0..0000000000 --- a/src/plugins/exportAsJSONAction/ExportAsJSONAction.spec.js +++ /dev/null @@ -1,252 +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/actions/ExportAsJSONAction", - "../../../entanglement/test/DomainObjectFactory", - "../../../../src/MCT", - '../../../../src/adapter/capabilities/AdapterCapability' - ], - function (ExportAsJSONAction, domainObjectFactory, MCT, AdapterCapability) { - - describe("The export JSON action", function () { - - let context; - let action; - let exportService; - let identifierService; - let typeService; - let openmct; - let policyService; - let mockType; - let mockObjectProvider; - let 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']); - typeService = jasmine.createSpyObj('typeService', [ - 'getType' - ]); - - mockType.hasFeature.and.callFake(function (feature) { - return feature === 'creation'; - }); - - typeService.getType.and.returnValue(mockType); - - context = {}; - context.domainObject = domainObjectFactory( - { - name: 'test', - id: 'someID', - capabilities: { - 'adapter': { - invoke: invokeAdapter - } - } - }); - identifierService.generate.and.returnValue('brandNewId'); - exportService.exportJSON.and.callFake(function (tree, options) { - exportedTree = tree; - }); - policyService.allow.and.callFake(function (capability, type) { - return type.hasFeature(capability); - }); - - action = new ExportAsJSONAction(openmct, exportService, policyService, - identifierService, typeService, context); - }); - - function invokeAdapter() { - let newStyleObject = new AdapterCapability(context.domainObject).invoke(); - - return newStyleObject; - } - - it("initializes happily", function () { - expect(action).toBeDefined(); - }); - - xit("doesn't export non-creatable objects in tree", function () { - let nonCreatableType = { - hasFeature: - function (feature) { - return feature !== 'creation'; - } - }; - - typeService.getType.and.returnValue(nonCreatableType); - - let parent = domainObjectFactory({ - name: 'parent', - model: { - name: 'parent', - location: 'ROOT', - composition: ['childId'] - }, - id: 'parentId', - capabilities: { - 'adapter': { - invoke: invokeAdapter - } - } - }); - - let child = { - identifier: { - namespace: '', - key: 'childId' - }, - name: 'child', - location: 'parentId' - }; - context.domainObject = parent; - addChild(child); - - action.perform(); - - return new Promise(function (resolve, reject) { - setTimeout(resolve, 100); - }).then(function () { - expect(Object.keys(action.tree).length).toBe(1); - expect(Object.prototype.hasOwnProperty.call(action.tree, "parentId")) - .toBeTruthy(); - }); - }); - - xit("can export self-containing objects", function () { - let parent = domainObjectFactory({ - name: 'parent', - model: { - name: 'parent', - location: 'ROOT', - composition: ['infiniteChildId'] - }, - id: 'infiniteParentId', - capabilities: { - 'adapter': { - invoke: invokeAdapter - } - } - }); - - let child = { - identifier: { - namespace: '', - key: 'infiniteChildId' - }, - name: 'child', - location: 'infiniteParentId', - composition: ['infiniteParentId'] - }; - addChild(child); - - context.domainObject = parent; - - action.perform(); - - return new Promise(function (resolve, reject) { - setTimeout(resolve, 100); - }).then(function () { - expect(Object.keys(action.tree).length).toBe(2); - expect(Object.prototype.hasOwnProperty.call(action.tree, "infiniteParentId")) - .toBeTruthy(); - expect(Object.prototype.hasOwnProperty.call(action.tree, "infiniteChildId")) - .toBeTruthy(); - }); - }); - - xit("exports links to external objects as new objects", function () { - let parent = domainObjectFactory({ - name: 'parent', - model: { - name: 'parent', - composition: ['externalId'], - location: 'ROOT' - }, - id: 'parentId', - capabilities: { - 'adapter': { - invoke: invokeAdapter - } - } - }); - - let externalObject = { - name: 'external', - location: 'outsideOfTree', - identifier: { - namespace: '', - key: 'externalId' - } - }; - addChild(externalObject); - - context.domainObject = parent; - - return new Promise (function (resolve) { - action.perform(); - setTimeout(resolve, 100); - }).then(function () { - expect(Object.keys(action.tree).length).toBe(2); - expect(Object.prototype.hasOwnProperty.call(action.tree, "parentId")) - .toBeTruthy(); - expect(Object.prototype.hasOwnProperty.call(action.tree, "brandNewId")) - .toBeTruthy(); - expect(action.tree.brandNewId.location).toBe('parentId'); - }); - }); - - it("exports object tree in the correct format", function () { - action.perform(); - - return new Promise(function (resolve, reject) { - setTimeout(resolve, 100); - }).then(function () { - expect(Object.keys(exportedTree).length).toBe(2); - expect(Object.prototype.hasOwnProperty.call(exportedTree, "openmct")).toBeTruthy(); - expect(Object.prototype.hasOwnProperty.call(exportedTree, "rootId")).toBeTruthy(); - }); - }); - - function addChild(object) { - mockObjectProvider.objects[object.identifier.key] = object; - } - }); - } -); diff --git a/src/plugins/exportAsJSONAction/ExportAsJSONActionSpec.js b/src/plugins/exportAsJSONAction/ExportAsJSONActionSpec.js new file mode 100644 index 0000000000..5499430709 --- /dev/null +++ b/src/plugins/exportAsJSONAction/ExportAsJSONActionSpec.js @@ -0,0 +1,305 @@ +import { + createOpenMct, + resetApplicationState +} from 'utils/testing'; + +describe('Export as JSON plugin', () => { + const ACTION_KEY = 'export.JSON'; + + let openmct; + let domainObject; + let exportAsJSONAction; + + beforeEach((done) => { + openmct = createOpenMct(); + + openmct.on('start', done); + openmct.startHeadless(); + + exportAsJSONAction = openmct.actions.getAction(ACTION_KEY); + }); + + afterEach(() => resetApplicationState(openmct)); + + it('Export as JSON action exist', () => { + expect(exportAsJSONAction.key).toEqual(ACTION_KEY); + }); + + it('ExportAsJSONAction applies to folder', () => { + domainObject = { + composition: [], + location: 'mine', + modified: 1640115501237, + name: 'Unnamed Folder', + persisted: 1640115501237, + type: 'folder' + }; + + expect(exportAsJSONAction.appliesTo([domainObject])).toEqual(true); + }); + + it('ExportAsJSONAction applies to telemetry.plot.overlay', () => { + domainObject = { + composition: [], + location: 'mine', + modified: 1640115501237, + name: 'Unnamed Plot', + persisted: 1640115501237, + type: 'telemetry.plot.overlay' + }; + + expect(exportAsJSONAction.appliesTo([domainObject])).toEqual(true); + }); + + it('ExportAsJSONAction applies to telemetry.plot.stacked', () => { + domainObject = { + composition: [], + location: 'mine', + modified: 1640115501237, + name: 'Unnamed Plot', + persisted: 1640115501237, + type: 'telemetry.plot.stacked' + }; + + expect(exportAsJSONAction.appliesTo([domainObject])).toEqual(true); + }); + + it('ExportAsJSONAction applies does not applies to non-creatable objects', () => { + domainObject = { + composition: [], + location: 'mine', + modified: 1640115501237, + name: 'Non Editable Folder', + persisted: 1640115501237, + type: 'noneditable.folder' + }; + + expect(exportAsJSONAction.appliesTo([domainObject])).toEqual(false); + }); + + it('ExportAsJSONAction exports object from tree', (done) => { + const parent = { + composition: [{ + key: 'child', + namespace: '' + }], + identifier: { + key: 'parent', + namespace: '' + }, + name: 'Parent', + type: 'folder', + modified: 1503598129176, + location: 'mine', + persisted: 1503598129176 + }; + + const child = { + composition: [], + identifier: { + key: 'child', + namespace: '' + }, + name: 'Child', + type: 'folder', + modified: 1503598132428, + location: 'parent', + persisted: 1503598132428 + }; + + spyOn(openmct.composition, 'get').and.callFake(object => { + return { + load: () => { + if (object.name === 'Parent') { + return Promise.resolve([child]); + } + + return Promise.resolve([]); + } + }; + }); + + spyOn(exportAsJSONAction, '_saveAs').and.callFake(completedTree => { + expect(Object.keys(completedTree).length).toBe(2); + expect(Object.prototype.hasOwnProperty.call(completedTree, 'openmct')).toBeTruthy(); + expect(Object.prototype.hasOwnProperty.call(completedTree, 'rootId')).toBeTruthy(); + expect(Object.prototype.hasOwnProperty.call(completedTree.openmct, 'parent')).toBeTruthy(); + expect(Object.prototype.hasOwnProperty.call(completedTree.openmct, 'child')).toBeTruthy(); + + done(); + }); + + exportAsJSONAction.invoke([parent]); + }); + + it('ExportAsJSONAction skips non-creatable objects from tree', (done) => { + const parent = { + composition: [{ + key: 'child', + namespace: '' + }], + identifier: { + key: 'parent', + namespace: '' + }, + name: 'Parent of Non Editable Child Folder', + type: 'folder', + modified: 1503598129176, + location: 'mine', + persisted: 1503598129176 + }; + + const child = { + composition: [], + identifier: { + key: 'child', + namespace: '' + }, + name: 'Non Editable Child Folder', + type: 'noneditable.folder', + modified: 1503598132428, + location: 'parent', + persisted: 1503598132428 + }; + + spyOn(openmct.composition, 'get').and.callFake(object => { + return { + load: () => { + if (object.identifier.key === 'parent') { + return Promise.resolve([child]); + } + + return Promise.resolve([]); + } + }; + }); + + spyOn(exportAsJSONAction, '_saveAs').and.callFake(completedTree => { + expect(Object.keys(completedTree).length).toBe(2); + expect(Object.prototype.hasOwnProperty.call(completedTree, 'openmct')).toBeTruthy(); + expect(Object.prototype.hasOwnProperty.call(completedTree, 'rootId')).toBeTruthy(); + expect(Object.prototype.hasOwnProperty.call(completedTree.openmct, 'parent')).toBeTruthy(); + expect(Object.prototype.hasOwnProperty.call(completedTree.openmct, 'child')).not.toBeTruthy(); + + done(); + }); + + exportAsJSONAction.invoke([parent]); + }); + + it('can export self-containing objects', (done) => { + const parent = { + composition: [{ + key: 'infinteChild', + namespace: '' + }], + identifier: { + key: 'infiniteParent', + namespace: '' + }, + name: 'parent', + type: 'folder', + modified: 1503598129176, + location: 'mine', + persisted: 1503598129176 + }; + + const child = { + composition: [{ + key: 'infiniteParent', + namespace: '' + }], + identifier: { + key: 'infinteChild', + namespace: '' + }, + name: 'child', + type: 'folder', + modified: 1503598132428, + location: 'infiniteParent', + persisted: 1503598132428 + }; + + spyOn(openmct.composition, 'get').and.callFake(object => { + return { + load: () => { + if (object.name === 'parent') { + return Promise.resolve([child]); + } + + return Promise.resolve([]); + } + }; + }); + + spyOn(exportAsJSONAction, '_saveAs').and.callFake(completedTree => { + expect(Object.keys(completedTree).length).toBe(2); + expect(Object.prototype.hasOwnProperty.call(completedTree, 'openmct')).toBeTruthy(); + expect(Object.prototype.hasOwnProperty.call(completedTree, 'rootId')).toBeTruthy(); + expect(Object.prototype.hasOwnProperty.call(completedTree.openmct, 'infiniteParent')).toBeTruthy(); + expect(Object.prototype.hasOwnProperty.call(completedTree.openmct, 'infinteChild')).toBeTruthy(); + + done(); + }); + + exportAsJSONAction.invoke([parent]); + }); + + it('exports links to external objects as new objects', function (done) { + const parent = { + composition: [{ + key: 'child', + namespace: '' + }], + identifier: { + key: 'parent', + namespace: '' + }, + name: 'Parent', + type: 'folder', + modified: 1503598129176, + location: 'mine', + persisted: 1503598129176 + }; + + const child = { + composition: [], + identifier: { + key: 'child', + namespace: '' + }, + name: 'Child', + type: 'folder', + modified: 1503598132428, + location: 'outsideOfTree', + persisted: 1503598132428 + }; + + spyOn(openmct.composition, 'get').and.callFake(object => { + return { + load: () => { + if (object.name === 'Parent') { + return Promise.resolve([child]); + } + + return Promise.resolve([]); + } + }; + }); + + spyOn(exportAsJSONAction, '_saveAs').and.callFake(completedTree => { + expect(Object.keys(completedTree).length).toBe(2); + expect(Object.prototype.hasOwnProperty.call(completedTree, 'openmct')).toBeTruthy(); + expect(Object.prototype.hasOwnProperty.call(completedTree, 'rootId')).toBeTruthy(); + expect(Object.prototype.hasOwnProperty.call(completedTree.openmct, 'parent')).toBeTruthy(); + + // parent and child objects as part of openmct but child with new id/key + expect(Object.prototype.hasOwnProperty.call(completedTree.openmct, 'child')).not.toBeTruthy(); + expect(Object.keys(completedTree.openmct).length).toBe(2); + + done(); + }); + + exportAsJSONAction.invoke([parent]); + }); +}); diff --git a/src/plugins/importFromJSONAction/ImportFromJSONAction.js b/src/plugins/importFromJSONAction/ImportFromJSONAction.js index e9e4539b30..7c10560be4 100644 --- a/src/plugins/importFromJSONAction/ImportFromJSONAction.js +++ b/src/plugins/importFromJSONAction/ImportFromJSONAction.js @@ -68,6 +68,7 @@ export default class ImportAsJSONAction { * @param {object} object * @param {object} changes */ + onSave(object, changes) { const selectFile = changes.selectFile; const objectTree = selectFile.body; diff --git a/src/plugins/plot/plugin.js b/src/plugins/plot/plugin.js index 30277df316..e8f6183350 100644 --- a/src/plugins/plot/plugin.js +++ b/src/plugins/plot/plugin.js @@ -34,7 +34,7 @@ export default function () { name: "Overlay Plot", cssClass: "icon-plot-overlay", description: "Combine multiple telemetry elements and view them together as a plot with common X and Y axes. Can be added to Display Layouts.", - creatable: "true", + creatable: true, initialize: function (domainObject) { domainObject.composition = []; domainObject.configuration = {