[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:
Preston Crowe
2017-07-11 14:45:26 -07:00
parent 586901aee7
commit 3674808a13
14 changed files with 1323 additions and 2 deletions

View 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();
});
});
});
}
);

View 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');
});
});
});
}
);