mirror of
https://github.com/nasa/openmct.git
synced 2024-12-20 05:37:53 +00:00
[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:
parent
586901aee7
commit
3674808a13
@ -43,6 +43,7 @@
|
|||||||
openmct.install(openmct.plugins.Generator());
|
openmct.install(openmct.plugins.Generator());
|
||||||
openmct.install(openmct.plugins.ExampleImagery());
|
openmct.install(openmct.plugins.ExampleImagery());
|
||||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||||
|
openmct.install(openmct.plugins.ImportExport());
|
||||||
openmct.install(openmct.plugins.Conductor({
|
openmct.install(openmct.plugins.Conductor({
|
||||||
menuOptions: [
|
menuOptions: [
|
||||||
{
|
{
|
||||||
|
@ -65,6 +65,20 @@ define(['csv'], function (CSV) {
|
|||||||
this.saveAs(blob, filename);
|
this.saveAs(blob, filename);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export an object as a JSON file. Triggers a download using the function
|
||||||
|
* provided when the ExportService was instantiated.
|
||||||
|
*
|
||||||
|
* @param {Object} obj an object to be exported as JSON
|
||||||
|
* @param {ExportOptions} [options] additional parameters for the file
|
||||||
|
* export
|
||||||
|
*/
|
||||||
|
ExportService.prototype.exportJSON = function (obj, options) {
|
||||||
|
var filename = (options && options.filename) || "test-export.json";
|
||||||
|
var jsonText = JSON.stringify(obj);
|
||||||
|
var blob = new Blob([jsonText], {type: "application/json"});
|
||||||
|
this.saveAs(blob, filename);
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* Additional parameters for file export.
|
* Additional parameters for file export.
|
||||||
* @typedef ExportOptions
|
* @typedef ExportOptions
|
||||||
|
@ -24,6 +24,8 @@ define([
|
|||||||
"./src/MCTForm",
|
"./src/MCTForm",
|
||||||
"./src/MCTToolbar",
|
"./src/MCTToolbar",
|
||||||
"./src/MCTControl",
|
"./src/MCTControl",
|
||||||
|
"./src/MCTFileInput",
|
||||||
|
"./src/FileInputService",
|
||||||
"./src/controllers/AutocompleteController",
|
"./src/controllers/AutocompleteController",
|
||||||
"./src/controllers/DateTimeController",
|
"./src/controllers/DateTimeController",
|
||||||
"./src/controllers/CompositeController",
|
"./src/controllers/CompositeController",
|
||||||
@ -42,11 +44,14 @@ define([
|
|||||||
"text!./res/templates/controls/menu-button.html",
|
"text!./res/templates/controls/menu-button.html",
|
||||||
"text!./res/templates/controls/dialog.html",
|
"text!./res/templates/controls/dialog.html",
|
||||||
"text!./res/templates/controls/radio.html",
|
"text!./res/templates/controls/radio.html",
|
||||||
|
"text!./res/templates/controls/file-input.html",
|
||||||
'legacyRegistry'
|
'legacyRegistry'
|
||||||
], function (
|
], function (
|
||||||
MCTForm,
|
MCTForm,
|
||||||
MCTToolbar,
|
MCTToolbar,
|
||||||
MCTControl,
|
MCTControl,
|
||||||
|
MCTFileInput,
|
||||||
|
FileInputService,
|
||||||
AutocompleteController,
|
AutocompleteController,
|
||||||
DateTimeController,
|
DateTimeController,
|
||||||
CompositeController,
|
CompositeController,
|
||||||
@ -65,6 +70,7 @@ define([
|
|||||||
menuButtonTemplate,
|
menuButtonTemplate,
|
||||||
dialogTemplate,
|
dialogTemplate,
|
||||||
radioTemplate,
|
radioTemplate,
|
||||||
|
fileInputTemplate,
|
||||||
legacyRegistry
|
legacyRegistry
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@ -88,6 +94,13 @@ define([
|
|||||||
"templateLinker",
|
"templateLinker",
|
||||||
"controls[]"
|
"controls[]"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "mctFileInput",
|
||||||
|
"implementation": MCTFileInput,
|
||||||
|
"depends": [
|
||||||
|
"fileInputService"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"controls": [
|
"controls": [
|
||||||
@ -142,6 +155,10 @@ define([
|
|||||||
{
|
{
|
||||||
"key": "dialog-button",
|
"key": "dialog-button",
|
||||||
"template": dialogTemplate
|
"template": dialogTemplate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "file-input",
|
||||||
|
"template": fileInputTemplate
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"controllers": [
|
"controllers": [
|
||||||
@ -176,6 +193,14 @@ define([
|
|||||||
"dialogService"
|
"dialogService"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"provides": "fileInputService",
|
||||||
|
"type": "provider",
|
||||||
|
"implementation": FileInputService
|
||||||
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
30
platform/forms/res/templates/controls/file-input.html
Normal file
30
platform/forms/res/templates/controls/file-input.html
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<a class="s-button {{structure.cssClass}}"
|
||||||
|
ng-model="ngModel[field]"
|
||||||
|
ng-class="{ labeled: structure.text }"
|
||||||
|
mct-file-input>
|
||||||
|
<span class="title-label" ng-if="structure.text">
|
||||||
|
{{structure.text}}
|
||||||
|
</span>
|
||||||
|
</a>
|
90
platform/forms/src/FileInputService.js
Normal file
90
platform/forms/src/FileInputService.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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(["zepto"], function ($) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The FileInputService provides an interface for triggering a file input.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @memberof platform/forms
|
||||||
|
*/
|
||||||
|
function FileInputService() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates, triggers, and destroys a file picker element and returns a
|
||||||
|
* promise for an object containing the chosen file's name and contents.
|
||||||
|
*
|
||||||
|
* @returns {Promise} promise for an object containing file meta-data
|
||||||
|
*/
|
||||||
|
FileInputService.prototype.getInput = function () {
|
||||||
|
var input = this.newInput();
|
||||||
|
var read = this.readFile;
|
||||||
|
var fileInfo = {};
|
||||||
|
var file;
|
||||||
|
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
input.trigger("click");
|
||||||
|
input.on('change', function (event) {
|
||||||
|
file = this.files[0];
|
||||||
|
input.remove();
|
||||||
|
if (file) {
|
||||||
|
read(file)
|
||||||
|
.then(function (contents) {
|
||||||
|
fileInfo.name = file.name;
|
||||||
|
fileInfo.body = contents;
|
||||||
|
resolve(fileInfo);
|
||||||
|
}, function () {
|
||||||
|
reject("File read error");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
FileInputService.prototype.readFile = function (file) {
|
||||||
|
var fileReader = new FileReader();
|
||||||
|
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
fileReader.onload = function (event) {
|
||||||
|
resolve(event.target.result);
|
||||||
|
};
|
||||||
|
|
||||||
|
fileReader.onerror = function () {
|
||||||
|
return reject(event.target.result);
|
||||||
|
};
|
||||||
|
fileReader.readAsText(file);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
FileInputService.prototype.newInput = function () {
|
||||||
|
var input = $(document.createElement('input'));
|
||||||
|
input.attr("type", "file");
|
||||||
|
input.css("display", "none");
|
||||||
|
$('body').append(input);
|
||||||
|
return input;
|
||||||
|
};
|
||||||
|
|
||||||
|
return FileInputService;
|
||||||
|
});
|
66
platform/forms/src/MCTFileInput.js
Normal file
66
platform/forms/src/MCTFileInput.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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(
|
||||||
|
['zepto'],
|
||||||
|
function ($) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The mct-file-input handles behavior of the file input form control.
|
||||||
|
* @constructor
|
||||||
|
* @memberof platform/forms
|
||||||
|
*/
|
||||||
|
function MCTFileInput(fileInputService) {
|
||||||
|
|
||||||
|
function link(scope, element, attrs, control) {
|
||||||
|
|
||||||
|
function setText(fileName) {
|
||||||
|
scope.structure.text = fileName.length > 20 ?
|
||||||
|
fileName.substr(0, 20) + "..." :
|
||||||
|
fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClick() {
|
||||||
|
fileInputService.getInput().then(function (result) {
|
||||||
|
setText(result.name);
|
||||||
|
scope.ngModel[scope.field] = result;
|
||||||
|
control.$setValidity("file-input", true);
|
||||||
|
}, function () {
|
||||||
|
setText('Select File');
|
||||||
|
control.$setValidity("file-input", false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
control.$setValidity("file-input", false);
|
||||||
|
element.on('click', handleClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
restrict: "A",
|
||||||
|
require: "^form",
|
||||||
|
link: link
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return MCTFileInput;
|
||||||
|
}
|
||||||
|
);
|
74
platform/forms/test/FileInputServiceSpec.js
Normal file
74
platform/forms/test/FileInputServiceSpec.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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/FileInputService"],
|
||||||
|
function (FileInputService) {
|
||||||
|
|
||||||
|
describe("The FileInputService", function () {
|
||||||
|
var fileInputService,
|
||||||
|
mockInput;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
fileInputService = new FileInputService();
|
||||||
|
mockInput = jasmine.createSpyObj('input',
|
||||||
|
[
|
||||||
|
'on',
|
||||||
|
'trigger',
|
||||||
|
'remove'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
mockInput.on.andCallFake(function (event, changeHandler) {
|
||||||
|
changeHandler.apply(mockInput);
|
||||||
|
});
|
||||||
|
spyOn(fileInputService, "newInput").andReturn(
|
||||||
|
mockInput
|
||||||
|
);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can read a file", function () {
|
||||||
|
mockInput.files = [new File(["file content"], "file name")];
|
||||||
|
fileInputService.getInput().then(function (result) {
|
||||||
|
expect(result.name).toBe("file name");
|
||||||
|
expect(result.body).toBe("file content");
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockInput.trigger).toHaveBeenCalledWith('click');
|
||||||
|
expect(mockInput.remove).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("catches file read errors", function () {
|
||||||
|
mockInput.files = ["GARBAGE"];
|
||||||
|
fileInputService.getInput().then(
|
||||||
|
function (result) {},
|
||||||
|
function (err) {
|
||||||
|
expect(err).toBe("File read error");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockInput.trigger).toHaveBeenCalledWith('click');
|
||||||
|
expect(mockInput.remove).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
98
platform/forms/test/MCTFileInputSpec.js
Normal file
98
platform/forms/test/MCTFileInputSpec.js
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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/MCTFileInput"],
|
||||||
|
function (MCTFileInput) {
|
||||||
|
|
||||||
|
describe("The mct-file-input directive", function () {
|
||||||
|
|
||||||
|
var mockScope,
|
||||||
|
mockFileInputService,
|
||||||
|
mctFileInput,
|
||||||
|
element,
|
||||||
|
attrs,
|
||||||
|
control;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
attrs = [];
|
||||||
|
control = jasmine.createSpyObj('control', ['$setValidity']);
|
||||||
|
element = jasmine.createSpyObj('element', ['on', 'trigger']);
|
||||||
|
mockFileInputService = jasmine.createSpyObj('fileInputService',
|
||||||
|
['getInput']
|
||||||
|
);
|
||||||
|
mockScope = jasmine.createSpyObj(
|
||||||
|
'$scope',
|
||||||
|
['$watch']
|
||||||
|
);
|
||||||
|
|
||||||
|
mockScope.structure = {text: 'Select File'};
|
||||||
|
mockScope.field = "file-input";
|
||||||
|
mockScope.ngModel = {"file-input" : undefined};
|
||||||
|
|
||||||
|
element.on.andCallFake(function (event, clickHandler) {
|
||||||
|
clickHandler();
|
||||||
|
});
|
||||||
|
mockFileInputService.getInput.andReturn(
|
||||||
|
Promise.resolve({name: "file-name", body: "file-body"})
|
||||||
|
);
|
||||||
|
|
||||||
|
mctFileInput = new MCTFileInput(mockFileInputService);
|
||||||
|
|
||||||
|
// Need to wait for mock promise
|
||||||
|
var init = false;
|
||||||
|
runs(function () {
|
||||||
|
mctFileInput.link(mockScope, element, attrs, control);
|
||||||
|
setTimeout(function () {
|
||||||
|
init = true;
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
waitsFor(function () {
|
||||||
|
return init;
|
||||||
|
}, "File selection should have beeen simulated");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is restricted to attributes", function () {
|
||||||
|
expect(mctFileInput.restrict).toEqual("A");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("changes button text to match file name", function () {
|
||||||
|
expect(element.on).toHaveBeenCalledWith(
|
||||||
|
'click',
|
||||||
|
jasmine.any(Function)
|
||||||
|
);
|
||||||
|
expect(mockScope.structure.text).toEqual("file-name");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("validates control on file selection", function () {
|
||||||
|
expect(control.$setValidity.callCount).toBe(2);
|
||||||
|
expect(control.$setValidity.argsForCall[0]).toEqual(
|
||||||
|
['file-input', false]
|
||||||
|
);
|
||||||
|
expect(control.$setValidity.argsForCall[1]).toEqual(
|
||||||
|
['file-input', true]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
76
platform/import-export/bundle.js
Normal file
76
platform/import-export/bundle.js
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define([
|
||||||
|
"./src/actions/ExportAsJSONAction",
|
||||||
|
"./src/actions/ImportAsJSONAction"
|
||||||
|
], function (
|
||||||
|
ExportAsJSONAction,
|
||||||
|
ImportAsJSONAction
|
||||||
|
) {
|
||||||
|
|
||||||
|
return function ImportExportPlugin() {
|
||||||
|
return function (openmct) {
|
||||||
|
ExportAsJSONAction.appliesTo = function (context) {
|
||||||
|
return openmct.$injector.get('policyService')
|
||||||
|
.allow("creation", context.domainObject.getCapability("type")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
openmct.legacyRegistry.register("platform/import-export", {
|
||||||
|
"name": "Import-export plugin",
|
||||||
|
"description": "Allows importing / exporting of domain objects as JSON.",
|
||||||
|
"extensions": {
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"key": "export.JSON",
|
||||||
|
"name": "Export as JSON",
|
||||||
|
"implementation": ExportAsJSONAction,
|
||||||
|
"category": "contextual",
|
||||||
|
"cssClass": "icon-save",
|
||||||
|
"depends": [
|
||||||
|
"exportService",
|
||||||
|
"policyService",
|
||||||
|
"identifierService"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "import.JSON",
|
||||||
|
"name": "Import from JSON",
|
||||||
|
"implementation": ImportAsJSONAction,
|
||||||
|
"category": "contextual",
|
||||||
|
"cssClass": "icon-download",
|
||||||
|
"depends": [
|
||||||
|
"exportService",
|
||||||
|
"identifierService",
|
||||||
|
"dialogService",
|
||||||
|
"openmct"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
openmct.legacyRegistry.enable('platform/import-export');
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
162
platform/import-export/src/actions/ExportAsJSONAction.js
Normal file
162
platform/import-export/src/actions/ExportAsJSONAction.js
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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([], function () {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ExportAsJSONAction is available from context menus and allows a user
|
||||||
|
* to export any creatable domain object as a JSON file.
|
||||||
|
*
|
||||||
|
* @implements {Action}
|
||||||
|
* @constructor
|
||||||
|
* @memberof platform/import-export
|
||||||
|
*/
|
||||||
|
function ExportAsJSONAction(
|
||||||
|
exportService,
|
||||||
|
policyService,
|
||||||
|
identifierService,
|
||||||
|
context
|
||||||
|
) {
|
||||||
|
|
||||||
|
this.root = {};
|
||||||
|
this.tree = {};
|
||||||
|
this.calls = 0;
|
||||||
|
this.context = context;
|
||||||
|
this.externalIdentifiers = [];
|
||||||
|
this.exportService = exportService;
|
||||||
|
this.policyService = policyService;
|
||||||
|
this.identifierService = identifierService;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExportAsJSONAction.prototype.perform = function () {
|
||||||
|
this.root = this.context.domainObject;
|
||||||
|
this.tree[this.root.getId()] = this.root.getModel();
|
||||||
|
this.saveAs = function (completedTree) {
|
||||||
|
this.exportService.exportJSON(
|
||||||
|
completedTree,
|
||||||
|
{filename: this.root.getModel().name + '.json'}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.write(this.root);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traverses object hierarchy and populates tree object with models and
|
||||||
|
* identifiers.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Object} parent
|
||||||
|
*/
|
||||||
|
ExportAsJSONAction.prototype.write = function (parent) {
|
||||||
|
|
||||||
|
this.calls++;
|
||||||
|
if (parent.hasCapability('composition')) {
|
||||||
|
parent.useCapability('composition')
|
||||||
|
.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 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);
|
||||||
|
} else {
|
||||||
|
this.tree[child.getId()] = child.getModel();
|
||||||
|
}
|
||||||
|
this.write(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
this.calls--;
|
||||||
|
if (this.calls === 0) {
|
||||||
|
this.saveAs(this.wrapTree());
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
} else {
|
||||||
|
this.calls--;
|
||||||
|
if (this.calls === 0) {
|
||||||
|
this.saveAs(this.wrapTree());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports an externally linked object as an entirely new object in the
|
||||||
|
* case where the original is not present in the exported tree.
|
||||||
|
*
|
||||||
|
* @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();
|
||||||
|
|
||||||
|
newModel.location = parent.getId();
|
||||||
|
this.tree[newId] = newModel;
|
||||||
|
this.tree[parent.getId()] = this.copyModel(parentModel);
|
||||||
|
this.tree[parent.getId()].composition[index] = newId;
|
||||||
|
};
|
||||||
|
|
||||||
|
ExportAsJSONAction.prototype.copyModel = function (model) {
|
||||||
|
var jsonString = JSON.stringify(model);
|
||||||
|
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())) {
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps root object for identification on reimport and wraps entire
|
||||||
|
* exported JSON construct for validation.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ExportAsJSONAction.prototype.wrapTree = function () {
|
||||||
|
return {
|
||||||
|
"openmct": this.tree,
|
||||||
|
"rootId": this.root.getId()
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
ExportAsJSONAction.prototype.isCreatable = function (domainObject) {
|
||||||
|
return this.policyService.allow(
|
||||||
|
"creation",
|
||||||
|
domainObject.getCapability("type")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return ExportAsJSONAction;
|
||||||
|
});
|
175
platform/import-export/src/actions/ImportAsJSONAction.js
Normal file
175
platform/import-export/src/actions/ImportAsJSONAction.js
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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(['zepto'], function ($) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ImportAsJSONAction is available from context menus and allows a user
|
||||||
|
* to import a previously exported domain object into any domain object
|
||||||
|
* that has the composition capability.
|
||||||
|
*
|
||||||
|
* @implements {Action}
|
||||||
|
* @constructor
|
||||||
|
* @memberof platform/import-export
|
||||||
|
*/
|
||||||
|
function ImportAsJSONAction(
|
||||||
|
exportService,
|
||||||
|
identifierService,
|
||||||
|
dialogService,
|
||||||
|
openmct,
|
||||||
|
context
|
||||||
|
) {
|
||||||
|
|
||||||
|
this.openmct = openmct;
|
||||||
|
this.context = context;
|
||||||
|
this.exportService = exportService;
|
||||||
|
this.dialogService = dialogService;
|
||||||
|
this.identifierService = identifierService;
|
||||||
|
this.instantiate = openmct.$injector.get("instantiate");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImportAsJSONAction.prototype.perform = function () {
|
||||||
|
this.dialogService.getUserInput(this.getFormModel(), {})
|
||||||
|
.then(function (form) {
|
||||||
|
var objectTree = form.selectFile.body;
|
||||||
|
if (this.validateJSON(objectTree)) {
|
||||||
|
this.importObjectTree(JSON.parse(objectTree));
|
||||||
|
} else {
|
||||||
|
this.displayError();
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
ImportAsJSONAction.prototype.importObjectTree = function (objTree) {
|
||||||
|
var parent = this.context.domainObject;
|
||||||
|
var tree = this.generateNewIdentifiers(objTree);
|
||||||
|
var rootId = tree.rootId;
|
||||||
|
var rootObj = this.instantiate(tree.openmct[rootId], rootId);
|
||||||
|
|
||||||
|
// Instantiate all objects in tree with their newly genereated ids,
|
||||||
|
// adding each to its rightful parent's composition
|
||||||
|
rootObj.getCapability("location").setPrimaryLocation(parent.getId());
|
||||||
|
this.deepInstantiate(rootObj, tree.openmct, []);
|
||||||
|
parent.getCapability("composition").add(rootObj);
|
||||||
|
};
|
||||||
|
|
||||||
|
ImportAsJSONAction.prototype.deepInstantiate = function (parent, tree, seen) {
|
||||||
|
// Traverses object tree, instantiates all domain object w/ new IDs and
|
||||||
|
// adds to parent's composition
|
||||||
|
if (parent.hasCapability("composition")) {
|
||||||
|
var parentModel = parent.getModel();
|
||||||
|
var newObj;
|
||||||
|
|
||||||
|
seen.push(parent.getId());
|
||||||
|
parentModel.composition.forEach(function (childId, index) {
|
||||||
|
if (!tree[childId] || seen.includes(childId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
newObj = this.instantiate(tree[childId], childId);
|
||||||
|
parent.getCapability("composition").add(newObj);
|
||||||
|
newObj.getCapability("location")
|
||||||
|
.setPrimaryLocation(tree[childId].location);
|
||||||
|
this.deepInstantiate(newObj, tree, seen);
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ImportAsJSONAction.prototype.generateNewIdentifiers = function (tree) {
|
||||||
|
// For each domain object in the file, generate new ID, replace in tree
|
||||||
|
Object.keys(tree.openmct).forEach(function (domainObjectId) {
|
||||||
|
var newId = this.identifierService.generate();
|
||||||
|
tree = this.rewriteId(domainObjectId, newId, tree);
|
||||||
|
}, this);
|
||||||
|
return tree;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rewrites all instances of a given id in the tree with a newly generated
|
||||||
|
* replacement to prevent collision.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ImportAsJSONAction.prototype.rewriteId = function (oldID, newID, tree) {
|
||||||
|
tree = JSON.stringify(tree).replace(new RegExp(oldID, 'g'), newID);
|
||||||
|
return JSON.parse(tree);
|
||||||
|
};
|
||||||
|
|
||||||
|
ImportAsJSONAction.prototype.getFormModel = function () {
|
||||||
|
return {
|
||||||
|
name: "Import as JSON",
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
name: "Import A File",
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
name: 'Select File',
|
||||||
|
key: 'selectFile',
|
||||||
|
control: 'file-input',
|
||||||
|
required: true,
|
||||||
|
text: 'Select File'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
ImportAsJSONAction.prototype.validateJSON = function (jsonString) {
|
||||||
|
var json;
|
||||||
|
try {
|
||||||
|
json = JSON.parse(jsonString);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!json.openmct || !json.rootId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
ImportAsJSONAction.prototype.displayError = function () {
|
||||||
|
var dialog,
|
||||||
|
model = {
|
||||||
|
title: "Invalid File",
|
||||||
|
actionText: "The selected file was either invalid JSON or was " +
|
||||||
|
"not formatted properly for import into Open MCT.",
|
||||||
|
severity: "error",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "Ok",
|
||||||
|
callback: function () {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
dialog = this.dialogService.showBlockingMessage(model);
|
||||||
|
};
|
||||||
|
|
||||||
|
ImportAsJSONAction.appliesTo = function (context) {
|
||||||
|
return context.domainObject !== undefined &&
|
||||||
|
context.domainObject.hasCapability("composition");
|
||||||
|
};
|
||||||
|
|
||||||
|
return ImportAsJSONAction;
|
||||||
|
});
|
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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -26,14 +26,16 @@ define([
|
|||||||
'../../example/generator/plugin',
|
'../../example/generator/plugin',
|
||||||
'../../platform/features/autoflow/plugin',
|
'../../platform/features/autoflow/plugin',
|
||||||
'./timeConductor/plugin',
|
'./timeConductor/plugin',
|
||||||
'../../example/imagery/plugin'
|
'../../example/imagery/plugin',
|
||||||
|
'../../platform/import-export/bundle'
|
||||||
], function (
|
], function (
|
||||||
_,
|
_,
|
||||||
UTCTimeSystem,
|
UTCTimeSystem,
|
||||||
GeneratorPlugin,
|
GeneratorPlugin,
|
||||||
AutoflowPlugin,
|
AutoflowPlugin,
|
||||||
TimeConductorPlugin,
|
TimeConductorPlugin,
|
||||||
ExampleImagery
|
ExampleImagery,
|
||||||
|
ImportExport
|
||||||
) {
|
) {
|
||||||
var bundleMap = {
|
var bundleMap = {
|
||||||
CouchDB: 'platform/persistence/couch',
|
CouchDB: 'platform/persistence/couch',
|
||||||
@ -54,6 +56,8 @@ define([
|
|||||||
|
|
||||||
plugins.UTCTimeSystem = UTCTimeSystem;
|
plugins.UTCTimeSystem = UTCTimeSystem;
|
||||||
|
|
||||||
|
plugins.ImportExport = ImportExport;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A tabular view showing the latest values of multiple telemetry points at
|
* A tabular view showing the latest values of multiple telemetry points at
|
||||||
* once. Formatted so that labels and values are aligned.
|
* once. Formatted so that labels and values are aligned.
|
||||||
|
Loading…
Reference in New Issue
Block a user