[Import/Export] Adds Import and Export functionality
Added context actions for importing and exporting JSON representations of domain objects. Added FileInputService for triggering file picker and retrieving uploaded data. Also added a File Input form control for integration with MCTForms.
This commit is contained in:
266
platform/import-export/test/actions/ExportAsJSONActionSpec.js
Normal file
266
platform/import-export/test/actions/ExportAsJSONActionSpec.js
Normal file
@@ -0,0 +1,266 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, 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"
|
||||
],
|
||||
function (ExportAsJSONAction, domainObjectFactory) {
|
||||
|
||||
describe("The export JSON action", function () {
|
||||
|
||||
var context,
|
||||
action,
|
||||
exportService,
|
||||
identifierService,
|
||||
policyService,
|
||||
mockType,
|
||||
exportedTree;
|
||||
|
||||
beforeEach(function () {
|
||||
exportService = jasmine.createSpyObj('exportService',
|
||||
['exportJSON']);
|
||||
identifierService = jasmine.createSpyObj('identifierService',
|
||||
['generate']);
|
||||
policyService = jasmine.createSpyObj('policyService',
|
||||
['allow']);
|
||||
mockType =
|
||||
jasmine.createSpyObj('type', ['hasFeature']);
|
||||
|
||||
mockType.hasFeature.andCallFake(function (feature) {
|
||||
return feature === 'creation';
|
||||
});
|
||||
context = {};
|
||||
context.domainObject = domainObjectFactory(
|
||||
{
|
||||
name: 'test',
|
||||
id: 'someID',
|
||||
capabilities: {type: mockType}
|
||||
});
|
||||
identifierService.generate.andReturn('brandNewId');
|
||||
exportService.exportJSON.andCallFake(function (tree, options) {
|
||||
exportedTree = tree;
|
||||
});
|
||||
policyService.allow.andCallFake(function (capability, type) {
|
||||
return type.hasFeature(capability);
|
||||
});
|
||||
|
||||
action = new ExportAsJSONAction(exportService, policyService,
|
||||
identifierService, context);
|
||||
});
|
||||
|
||||
it("initializes happily", function () {
|
||||
expect(action).toBeDefined();
|
||||
});
|
||||
|
||||
it("doesn't export non-creatable objects in tree", function () {
|
||||
var nonCreatableType = {
|
||||
hasFeature :
|
||||
function (feature) {
|
||||
return feature !== 'creation';
|
||||
}
|
||||
};
|
||||
|
||||
var parentComposition =
|
||||
jasmine.createSpyObj('parentComposition', ['invoke']);
|
||||
|
||||
var parent = domainObjectFactory({
|
||||
name: 'parent',
|
||||
model: { name: 'parent', location: 'ROOT'},
|
||||
id: 'parentId',
|
||||
capabilities: {
|
||||
composition: parentComposition,
|
||||
type: mockType
|
||||
}
|
||||
});
|
||||
|
||||
var child = domainObjectFactory({
|
||||
name: 'child',
|
||||
model: { name: 'child', location: 'parentId' },
|
||||
id: 'childId',
|
||||
capabilities: {
|
||||
type: nonCreatableType
|
||||
}
|
||||
});
|
||||
|
||||
parentComposition.invoke.andReturn(
|
||||
Promise.resolve([child])
|
||||
);
|
||||
context.domainObject = parent;
|
||||
|
||||
var init = false;
|
||||
runs(function () {
|
||||
action.perform();
|
||||
setTimeout(function () {
|
||||
init = true;
|
||||
}, 100);
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return init;
|
||||
}, "Exported tree sohuld have been built");
|
||||
|
||||
runs(function () {
|
||||
expect(Object.keys(action.tree).length).toBe(1);
|
||||
expect(action.tree.hasOwnProperty("parentId"))
|
||||
.toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
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'},
|
||||
id: 'infiniteParentId',
|
||||
capabilities: {
|
||||
composition: infiniteParentComposition,
|
||||
type: mockType
|
||||
}
|
||||
});
|
||||
|
||||
var child = domainObjectFactory({
|
||||
name: 'child',
|
||||
model: { name: 'child', location: 'infiniteParentId' },
|
||||
id: 'infiniteChildId',
|
||||
capabilities: {
|
||||
composition: infiniteChildComposition,
|
||||
type: mockType
|
||||
}
|
||||
});
|
||||
|
||||
infiniteParentComposition.invoke.andReturn(
|
||||
Promise.resolve([child])
|
||||
);
|
||||
infiniteChildComposition.invoke.andReturn(
|
||||
Promise.resolve([parent])
|
||||
);
|
||||
context.domainObject = parent;
|
||||
|
||||
var init = false;
|
||||
runs(function () {
|
||||
action.perform();
|
||||
setTimeout(function () {
|
||||
init = true;
|
||||
}, 100);
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return init;
|
||||
}, "Exported tree sohuld have been built");
|
||||
|
||||
runs(function () {
|
||||
expect(Object.keys(action.tree).length).toBe(2);
|
||||
expect(action.tree.hasOwnProperty("infiniteParentId"))
|
||||
.toBeTruthy();
|
||||
expect(action.tree.hasOwnProperty("infiniteChildId"))
|
||||
.toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("exports links to external objects as new objects", function () {
|
||||
var externallyLinkedComposition =
|
||||
jasmine.createSpyObj('externallyLinkedComposition',
|
||||
['invoke']
|
||||
);
|
||||
|
||||
var parent = domainObjectFactory({
|
||||
name: 'parent',
|
||||
model: {
|
||||
name: 'parent',
|
||||
composition: ['externalId'],
|
||||
location: 'ROOT'},
|
||||
id: 'parentId',
|
||||
capabilities: {
|
||||
composition: externallyLinkedComposition,
|
||||
type: mockType
|
||||
}
|
||||
});
|
||||
|
||||
var externalObject = domainObjectFactory({
|
||||
name: 'external',
|
||||
model: { name: 'external', location: 'outsideOfTree'},
|
||||
id: 'externalId',
|
||||
capabilities: {
|
||||
type: mockType
|
||||
}
|
||||
});
|
||||
|
||||
externallyLinkedComposition.invoke.andReturn(
|
||||
Promise.resolve([externalObject])
|
||||
);
|
||||
context.domainObject = parent;
|
||||
|
||||
var init = false;
|
||||
runs(function () {
|
||||
action.perform();
|
||||
setTimeout(function () {
|
||||
init = true;
|
||||
}, 100);
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return init;
|
||||
}, "Exported tree sohuld have been built");
|
||||
|
||||
runs(function () {
|
||||
expect(Object.keys(action.tree).length).toBe(2);
|
||||
expect(action.tree.hasOwnProperty('parentId'))
|
||||
.toBeTruthy();
|
||||
expect(action.tree.hasOwnProperty('brandNewId'))
|
||||
.toBeTruthy();
|
||||
expect(action.tree.brandNewId.location).toBe('parentId');
|
||||
});
|
||||
});
|
||||
|
||||
it("exports object tree in the correct format", function () {
|
||||
var init = false;
|
||||
runs(function () {
|
||||
action.perform();
|
||||
setTimeout(function () {
|
||||
init = true;
|
||||
}, 100);
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return init;
|
||||
}, "Exported tree sohuld have been built");
|
||||
|
||||
runs(function () {
|
||||
expect(Object.keys(exportedTree).length).toBe(2);
|
||||
expect(exportedTree.hasOwnProperty('openmct')).toBeTruthy();
|
||||
expect(exportedTree.hasOwnProperty('rootId')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
240
platform/import-export/test/actions/ImportAsJSONActionSpec.js
Normal file
240
platform/import-export/test/actions/ImportAsJSONActionSpec.js
Normal file
@@ -0,0 +1,240 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, 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/ImportAsJSONAction",
|
||||
"../../../entanglement/test/DomainObjectFactory"
|
||||
],
|
||||
function (ImportAsJSONAction, domainObjectFactory) {
|
||||
|
||||
describe("The import JSON action", function () {
|
||||
|
||||
var context = {};
|
||||
var action,
|
||||
exportService,
|
||||
identifierService,
|
||||
dialogService,
|
||||
openmct,
|
||||
mockDialog,
|
||||
compositionCapability,
|
||||
mockInstantiate,
|
||||
uniqueId,
|
||||
newObjects;
|
||||
|
||||
|
||||
beforeEach(function () {
|
||||
|
||||
uniqueId = 0;
|
||||
newObjects = [];
|
||||
openmct = {
|
||||
$injector: jasmine.createSpyObj('$injector', ['get'])
|
||||
};
|
||||
mockInstantiate = jasmine.createSpy('instantiate').andCallFake(
|
||||
function (model, id) {
|
||||
var config = {
|
||||
"model": model,
|
||||
"id": id,
|
||||
"capabilities": {}
|
||||
};
|
||||
var locationCapability = {
|
||||
setPrimaryLocation: jasmine.createSpy
|
||||
('setPrimaryLocation').andCallFake(
|
||||
function (newLocation) {
|
||||
config.model.location = newLocation;
|
||||
}
|
||||
)
|
||||
};
|
||||
config.capabilities.location = locationCapability;
|
||||
if (model.composition) {
|
||||
var compCapability =
|
||||
jasmine.createSpy('compCapability')
|
||||
.andReturn(model.composition);
|
||||
compCapability.add = jasmine.createSpy('add')
|
||||
.andCallFake(function (newObj) {
|
||||
config.model.composition.push(newObj.getId());
|
||||
});
|
||||
config.capabilities.composition = compCapability;
|
||||
}
|
||||
newObjects.push(domainObjectFactory(config));
|
||||
return domainObjectFactory(config);
|
||||
});
|
||||
openmct.$injector.get.andReturn(mockInstantiate);
|
||||
dialogService = jasmine.createSpyObj('dialogService',
|
||||
[
|
||||
'getUserInput',
|
||||
'showBlockingMessage'
|
||||
]
|
||||
);
|
||||
identifierService = jasmine.createSpyObj('identifierService',
|
||||
[
|
||||
'generate'
|
||||
]
|
||||
);
|
||||
identifierService.generate.andCallFake(function () {
|
||||
uniqueId++;
|
||||
return uniqueId;
|
||||
});
|
||||
compositionCapability = jasmine.createSpy('compositionCapability');
|
||||
mockDialog = jasmine.createSpyObj("dialog", ["dismiss"]);
|
||||
dialogService.showBlockingMessage.andReturn(mockDialog);
|
||||
|
||||
action = new ImportAsJSONAction(exportService, identifierService,
|
||||
dialogService, openmct, context);
|
||||
});
|
||||
|
||||
it("initializes happily", function () {
|
||||
expect(action).toBeDefined();
|
||||
});
|
||||
|
||||
it("only applies to objects with composition capability", function () {
|
||||
var compDomainObject = domainObjectFactory({
|
||||
name: 'compObject',
|
||||
model: { name: 'compObject'},
|
||||
capabilities: {"composition": compositionCapability}
|
||||
});
|
||||
var noCompDomainObject = domainObjectFactory();
|
||||
|
||||
context.domainObject = compDomainObject;
|
||||
expect(ImportAsJSONAction.appliesTo(context)).toBe(true);
|
||||
context.domainObject = noCompDomainObject;
|
||||
expect(ImportAsJSONAction.appliesTo(context)).toBe(false);
|
||||
});
|
||||
|
||||
it("displays error dialog on invalid file choice", function () {
|
||||
dialogService.getUserInput.andReturn(Promise.resolve(
|
||||
{
|
||||
selectFile: {
|
||||
body: JSON.stringify({badKey: "INVALID"}),
|
||||
name: "fileName"
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
var init = false;
|
||||
runs(function () {
|
||||
action.perform();
|
||||
setTimeout(function () {
|
||||
init = true;
|
||||
}, 100);
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return init;
|
||||
}, "Promise containing file data should have resolved");
|
||||
|
||||
runs(function () {
|
||||
expect(dialogService.getUserInput).toHaveBeenCalled();
|
||||
expect(dialogService.showBlockingMessage).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("can import self-containing objects", function () {
|
||||
dialogService.getUserInput.andReturn(Promise.resolve(
|
||||
{
|
||||
selectFile: {
|
||||
body: JSON.stringify({
|
||||
"openmct": {
|
||||
"infiniteParent": {
|
||||
"composition": ["infinteChild"],
|
||||
"name": "1",
|
||||
"type": "folder",
|
||||
"modified": 1503598129176,
|
||||
"location": "mine",
|
||||
"persisted": 1503598129176
|
||||
},
|
||||
"infinteChild": {
|
||||
"composition": ["infiniteParent"],
|
||||
"name": "2",
|
||||
"type": "folder",
|
||||
"modified": 1503598132428,
|
||||
"location": "infiniteParent",
|
||||
"persisted": 1503598132428
|
||||
}
|
||||
},
|
||||
"rootId": "infiniteParent"
|
||||
}),
|
||||
name: "fileName"
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
var init = false;
|
||||
runs(function () {
|
||||
action.perform();
|
||||
setTimeout(function () {
|
||||
init = true;
|
||||
}, 100);
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return init;
|
||||
}, "Promise containing file data should have resolved");
|
||||
|
||||
runs(function () {
|
||||
expect(mockInstantiate.calls.length).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
it("assigns new ids to each imported object", function () {
|
||||
dialogService.getUserInput.andReturn(Promise.resolve(
|
||||
{
|
||||
selectFile: {
|
||||
body: JSON.stringify({
|
||||
"openmct": {
|
||||
"cce9f107-5060-4f55-8151-a00120f4222f": {
|
||||
"composition": [],
|
||||
"name": "test",
|
||||
"type": "folder",
|
||||
"modified": 1503596596639,
|
||||
"location": "mine",
|
||||
"persisted": 1503596596639
|
||||
}
|
||||
},
|
||||
"rootId": "cce9f107-5060-4f55-8151-a00120f4222f"
|
||||
}),
|
||||
name: "fileName"
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
var init = false;
|
||||
runs(function () {
|
||||
action.perform();
|
||||
setTimeout(function () {
|
||||
init = true;
|
||||
}, 100);
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return init;
|
||||
}, "Promise containing file data should have resolved");
|
||||
|
||||
runs(function () {
|
||||
expect(mockInstantiate.calls.length).toEqual(1);
|
||||
expect(newObjects[0].getId()).toBe('1');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user