mirror of
https://github.com/nasa/openmct.git
synced 2025-06-18 15:18:12 +00:00
topic-form-refactor (#4478)
* Form refactor (#3816) * New form API and associated form controls * Actions updated to use new form API. Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov> Co-authored-by: charlesh88 <charles.f.hacskaylo@nasa.gov> * Reimplementation of import export json (#4171) Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov> Co-authored-by: charlesh88 <charles.f.hacskaylo@nasa.gov> Co-authored-by: Henry Hsu <hhsu0219@gmail.com> Co-authored-by: Andrew Henry <akhenry@gmail.com>
This commit is contained in:
@ -1,190 +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.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<li class="c-tree__item-h">
|
||||
<div
|
||||
class="c-tree__item"
|
||||
:class="{ 'is-alias': isAlias, 'is-navigated-object': navigated }"
|
||||
@click="handleItemSelected(node.object, node)"
|
||||
>
|
||||
<view-control
|
||||
v-model="expanded"
|
||||
class="c-tree__item__view-control"
|
||||
:enabled="hasChildren"
|
||||
/>
|
||||
<div class="c-tree__item__label c-object-label">
|
||||
<div
|
||||
class="c-tree__item__type-icon c-object-label__type-icon"
|
||||
:class="typeClass"
|
||||
></div>
|
||||
<div class="c-tree__item__name c-object-label__name">{{ node.object.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
v-if="expanded && !isLoading"
|
||||
class="c-tree"
|
||||
>
|
||||
<li
|
||||
v-if="isLoading && !loaded"
|
||||
class="c-tree__item-h"
|
||||
>
|
||||
<div class="c-tree__item loading">
|
||||
<span class="c-tree__item__label">Loading...</span>
|
||||
</div>
|
||||
</li>
|
||||
<condition-set-dialog-tree-item
|
||||
v-for="child in children"
|
||||
:key="child.id"
|
||||
:node="child"
|
||||
:selected-item="selectedItem"
|
||||
:handle-item-selected="handleItemSelected"
|
||||
/>
|
||||
</ul>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import viewControl from '@/ui/components/viewControl.vue';
|
||||
|
||||
export default {
|
||||
name: 'ConditionSetDialogTreeItem',
|
||||
components: {
|
||||
viewControl
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
node: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
selectedItem: {
|
||||
type: Object,
|
||||
default() {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
handleItemSelected: {
|
||||
type: Function,
|
||||
default() {
|
||||
return (item) => {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hasChildren: false,
|
||||
isLoading: false,
|
||||
loaded: false,
|
||||
children: [],
|
||||
expanded: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
navigated() {
|
||||
const itemId = this.selectedItem && this.selectedItem.itemId;
|
||||
const isSelectedObject = itemId && this.openmct.objects.areIdsEqual(this.node.object.identifier, itemId);
|
||||
if (isSelectedObject && this.node.objectPath && this.node.objectPath.length > 1) {
|
||||
const isParent = this.openmct.objects.areIdsEqual(this.node.objectPath[1].identifier, this.selectedItem.parentId);
|
||||
|
||||
return isSelectedObject && isParent;
|
||||
}
|
||||
|
||||
return isSelectedObject;
|
||||
},
|
||||
isAlias() {
|
||||
let parent = this.node.objectPath[1];
|
||||
if (!parent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let parentKeyString = this.openmct.objects.makeKeyString(parent.identifier);
|
||||
|
||||
return parentKeyString !== this.node.object.location;
|
||||
},
|
||||
typeClass() {
|
||||
let type = this.openmct.types.get(this.node.object.type);
|
||||
if (!type) {
|
||||
return 'icon-object-unknown';
|
||||
}
|
||||
|
||||
return type.definition.cssClass;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
expanded() {
|
||||
if (!this.hasChildren) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.loaded && !this.isLoading) {
|
||||
this.composition = this.openmct.composition.get(this.domainObject);
|
||||
this.composition.on('add', this.addChild);
|
||||
this.composition.on('remove', this.removeChild);
|
||||
this.composition.load().then(this.finishLoading);
|
||||
this.isLoading = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.domainObject = this.node.object;
|
||||
let removeListener = this.openmct.objects.observe(this.domainObject, '*', (newObject) => {
|
||||
this.domainObject = newObject;
|
||||
});
|
||||
|
||||
this.$once('hook:destroyed', removeListener);
|
||||
if (this.openmct.composition.get(this.node.object)) {
|
||||
this.hasChildren = true;
|
||||
}
|
||||
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.expanded = false;
|
||||
},
|
||||
destroyed() {
|
||||
if (this.composition) {
|
||||
this.composition.off('add', this.addChild);
|
||||
this.composition.off('remove', this.removeChild);
|
||||
delete this.composition;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addChild(child) {
|
||||
this.children.push({
|
||||
id: this.openmct.objects.makeKeyString(child.identifier),
|
||||
object: child,
|
||||
objectPath: [child].concat(this.node.objectPath),
|
||||
navigateToParent: this.navigateToPath
|
||||
});
|
||||
},
|
||||
removeChild(identifier) {
|
||||
let removeId = this.openmct.objects.makeKeyString(identifier);
|
||||
this.children = this.children
|
||||
.filter(c => c.id !== removeId);
|
||||
},
|
||||
finishLoading() {
|
||||
this.isLoading = false;
|
||||
this.loaded = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@ -1,192 +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.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="u-contents">
|
||||
<div class="c-overlay__top-bar">
|
||||
<div class="c-overlay__dialog-title">Select Condition Set</div>
|
||||
</div>
|
||||
<div class="c-overlay__contents-main c-selector c-tree-and-search">
|
||||
<div class="c-tree-and-search__search">
|
||||
<search ref="shell-search"
|
||||
class="c-search"
|
||||
:value="searchValue"
|
||||
@input="searchTree"
|
||||
@clear="searchTree"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- loading -->
|
||||
<div v-if="isLoading"
|
||||
class="c-tree-and-search__loading loading"
|
||||
></div>
|
||||
<!-- end loading -->
|
||||
|
||||
<div v-if="shouldDisplayNoResultsText"
|
||||
class="c-tree-and-search__no-results"
|
||||
>
|
||||
No results found
|
||||
</div>
|
||||
|
||||
<!-- main tree -->
|
||||
<ul v-if="!isLoading"
|
||||
v-show="!searchValue"
|
||||
class="c-tree-and-search__tree c-tree"
|
||||
>
|
||||
<condition-set-dialog-tree-item
|
||||
v-for="treeItem in allTreeItems"
|
||||
:key="treeItem.id"
|
||||
:node="treeItem"
|
||||
:selected-item="selectedItem"
|
||||
:handle-item-selected="handleItemSelection"
|
||||
/>
|
||||
</ul>
|
||||
<!-- end main tree -->
|
||||
|
||||
<!-- search tree -->
|
||||
<ul v-if="searchValue && !isLoading"
|
||||
class="c-tree-and-search__tree c-tree"
|
||||
>
|
||||
<condition-set-dialog-tree-item
|
||||
v-for="treeItem in filteredTreeItems"
|
||||
:key="treeItem.id"
|
||||
:node="treeItem"
|
||||
:selected-item="selectedItem"
|
||||
:handle-item-selected="handleItemSelection"
|
||||
/>
|
||||
</ul>
|
||||
<!-- end search tree -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import debounce from 'lodash/debounce';
|
||||
import search from '@/ui/components/search.vue';
|
||||
import ConditionSetDialogTreeItem from './ConditionSetDialogTreeItem.vue';
|
||||
|
||||
export default {
|
||||
name: 'ConditionSetSelectorDialog',
|
||||
components: {
|
||||
search,
|
||||
ConditionSetDialogTreeItem
|
||||
},
|
||||
inject: ['openmct'],
|
||||
data() {
|
||||
return {
|
||||
expanded: false,
|
||||
searchValue: '',
|
||||
allTreeItems: [],
|
||||
filteredTreeItems: [],
|
||||
isLoading: false,
|
||||
selectedItem: undefined
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
shouldDisplayNoResultsText() {
|
||||
if (this.isLoading) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.allTreeItems.length === 0
|
||||
|| (this.searchValue && this.filteredTreeItems.length === 0);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getDebouncedFilteredChildren = debounce(this.getFilteredChildren, 400);
|
||||
},
|
||||
mounted() {
|
||||
this.getAllChildren();
|
||||
},
|
||||
methods: {
|
||||
getAllChildren() {
|
||||
this.isLoading = true;
|
||||
this.openmct.objects.get('ROOT')
|
||||
.then(root => {
|
||||
return this.openmct.composition.get(root).load();
|
||||
})
|
||||
.then(children => {
|
||||
this.isLoading = false;
|
||||
this.allTreeItems = children.map(c => {
|
||||
return {
|
||||
id: this.openmct.objects.makeKeyString(c.identifier),
|
||||
object: c,
|
||||
objectPath: [c],
|
||||
navigateToParent: '/browse'
|
||||
};
|
||||
});
|
||||
});
|
||||
},
|
||||
getFilteredChildren() {
|
||||
// clear any previous search results
|
||||
this.filteredTreeItems = [];
|
||||
|
||||
const promises = this.openmct.objects.search(this.searchValue)
|
||||
.map(promise => promise
|
||||
.then(results => this.aggregateFilteredChildren(results)));
|
||||
|
||||
Promise.all(promises).then(() => {
|
||||
this.isLoading = false;
|
||||
});
|
||||
},
|
||||
async aggregateFilteredChildren(results) {
|
||||
for (const object of results) {
|
||||
const objectPath = await this.openmct.objects.getOriginalPath(object.identifier);
|
||||
|
||||
const navigateToParent = '/browse/'
|
||||
+ objectPath.slice(1)
|
||||
.map(parent => this.openmct.objects.makeKeyString(parent.identifier))
|
||||
.join('/');
|
||||
|
||||
const filteredChild = {
|
||||
id: this.openmct.objects.makeKeyString(object.identifier),
|
||||
object,
|
||||
objectPath,
|
||||
navigateToParent
|
||||
};
|
||||
|
||||
this.filteredTreeItems.push(filteredChild);
|
||||
}
|
||||
},
|
||||
searchTree(value) {
|
||||
this.searchValue = value;
|
||||
this.isLoading = true;
|
||||
|
||||
if (this.searchValue !== '') {
|
||||
this.getDebouncedFilteredChildren();
|
||||
} else {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
handleItemSelection(item, node) {
|
||||
if (item && item.type === 'conditionSet') {
|
||||
const parentId = (node.objectPath && node.objectPath.length > 1) ? node.objectPath[1].identifier : undefined;
|
||||
this.selectedItem = {
|
||||
itemId: item.identifier,
|
||||
parentId
|
||||
};
|
||||
this.$emit('conditionSetSelected', item);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@ -101,12 +101,12 @@
|
||||
<script>
|
||||
|
||||
import StyleEditor from "./StyleEditor.vue";
|
||||
import ConditionSetSelectorDialog from "./ConditionSetSelectorDialog.vue";
|
||||
import SelectorDialogTree from '@/ui/components/SelectorDialogTree.vue';
|
||||
import ConditionDescription from "@/plugins/condition/components/ConditionDescription.vue";
|
||||
import ConditionError from "@/plugins/condition/components/ConditionError.vue";
|
||||
import Vue from 'vue';
|
||||
import PreviewAction from "@/ui/preview/PreviewAction.js";
|
||||
import {getApplicableStylesForItem} from "@/plugins/condition/utils/styleUtils";
|
||||
import { getApplicableStylesForItem } from "@/plugins/condition/utils/styleUtils";
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
|
||||
export default {
|
||||
@ -224,7 +224,7 @@ export default {
|
||||
let conditionSetDomainObject;
|
||||
let self = this;
|
||||
|
||||
function handleItemSelection(item) {
|
||||
function handleItemSelection({ item }) {
|
||||
if (item) {
|
||||
conditionSetDomainObject = item;
|
||||
}
|
||||
@ -240,16 +240,17 @@ export default {
|
||||
}
|
||||
|
||||
let vm = new Vue({
|
||||
components: {ConditionSetSelectorDialog},
|
||||
components: { SelectorDialogTree },
|
||||
provide: {
|
||||
openmct: this.openmct
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
handleItemSelection
|
||||
handleItemSelection,
|
||||
title: 'Select Condition Set'
|
||||
};
|
||||
},
|
||||
template: '<condition-set-selector-dialog @conditionSetSelected="handleItemSelection"></condition-set-selector-dialog>'
|
||||
template: '<selector-dialog-tree :title="title" @treeItemSelected="handleItemSelection"></selector-dialog-tree>'
|
||||
}).$mount();
|
||||
|
||||
let overlay = this.openmct.overlays.overlay({
|
||||
|
@ -127,7 +127,7 @@ import FontStyleEditor from '@/ui/inspector/styles/FontStyleEditor.vue';
|
||||
import StyleEditor from "./StyleEditor.vue";
|
||||
import PreviewAction from "@/ui/preview/PreviewAction.js";
|
||||
import { getApplicableStylesForItem, getConsolidatedStyleValues, getConditionSetIdentifierForItem } from "@/plugins/condition/utils/styleUtils";
|
||||
import ConditionSetSelectorDialog from "@/plugins/condition/components/inspector/ConditionSetSelectorDialog.vue";
|
||||
import SelectorDialogTree from '@/ui/components/SelectorDialogTree.vue';
|
||||
import ConditionError from "@/plugins/condition/components/ConditionError.vue";
|
||||
import ConditionDescription from "@/plugins/condition/components/ConditionDescription.vue";
|
||||
import Vue from 'vue';
|
||||
@ -539,7 +539,7 @@ export default {
|
||||
addConditionSet() {
|
||||
let conditionSetDomainObject;
|
||||
let self = this;
|
||||
function handleItemSelection(item) {
|
||||
function handleItemSelection({ item }) {
|
||||
if (item) {
|
||||
conditionSetDomainObject = item;
|
||||
}
|
||||
@ -556,16 +556,17 @@ export default {
|
||||
}
|
||||
|
||||
let vm = new Vue({
|
||||
components: {ConditionSetSelectorDialog},
|
||||
components: { SelectorDialogTree },
|
||||
provide: {
|
||||
openmct: this.openmct
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
handleItemSelection
|
||||
handleItemSelection,
|
||||
title: 'Select Condition Set'
|
||||
};
|
||||
},
|
||||
template: '<condition-set-selector-dialog @conditionSetSelected="handleItemSelection"></condition-set-selector-dialog>'
|
||||
template: '<SelectorDialogTree :title="title" @treeItemSelected="handleItemSelection"></SelectorDialogTree>'
|
||||
}).$mount();
|
||||
|
||||
let overlay = this.openmct.overlays.overlay({
|
||||
|
@ -42,7 +42,7 @@ define(['lodash'], function (_) {
|
||||
toolbar: function (selectedObjects) {
|
||||
const DIALOG_FORM = {
|
||||
'text': {
|
||||
name: "Text Element Properties",
|
||||
title: "Text Element Properties",
|
||||
sections: [
|
||||
{
|
||||
rows: [
|
||||
@ -50,14 +50,15 @@ define(['lodash'], function (_) {
|
||||
key: "text",
|
||||
control: "textfield",
|
||||
name: "Text",
|
||||
required: true
|
||||
required: true,
|
||||
cssClass: "l-input-lg"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
'image': {
|
||||
name: "Image Properties",
|
||||
title: "Image Properties",
|
||||
sections: [
|
||||
{
|
||||
rows: [
|
||||
@ -65,7 +66,7 @@ define(['lodash'], function (_) {
|
||||
key: "url",
|
||||
control: "textfield",
|
||||
name: "Image URL",
|
||||
"cssClass": "l-input-lg",
|
||||
cssClass: "l-input-lg",
|
||||
required: true
|
||||
}
|
||||
]
|
||||
@ -126,10 +127,6 @@ define(['lodash'], function (_) {
|
||||
]
|
||||
};
|
||||
|
||||
function getUserInput(form) {
|
||||
return openmct.$injector.get('dialogService').getUserInput(form, {});
|
||||
}
|
||||
|
||||
function getPath(selectionPath) {
|
||||
return `configuration.items[${selectionPath[0].context.index}]`;
|
||||
}
|
||||
@ -167,8 +164,7 @@ define(['lodash'], function (_) {
|
||||
let name = option.name.toLowerCase();
|
||||
let form = DIALOG_FORM[name];
|
||||
if (form) {
|
||||
getUserInput(form)
|
||||
.then(element => selectionPath[0].context.addElement(name, element));
|
||||
showForm(form, name, selectionPath);
|
||||
} else {
|
||||
selectionPath[0].context.addElement(name);
|
||||
}
|
||||
@ -643,10 +639,18 @@ define(['lodash'], function (_) {
|
||||
&& !selectionPath[0].context.layoutItem;
|
||||
}
|
||||
|
||||
function showForm(formStructure, name, selectionPath) {
|
||||
openmct.forms.showForm(formStructure)
|
||||
.then(changes => {
|
||||
selectionPath[0].context.addElement(name, changes);
|
||||
});
|
||||
}
|
||||
|
||||
if (isMainLayoutSelected(selectedObjects[0])) {
|
||||
return [
|
||||
getToggleGridButton(selectedObjects),
|
||||
getAddButton(selectedObjects)];
|
||||
getAddButton(selectedObjects)
|
||||
];
|
||||
}
|
||||
|
||||
let toolbar = {
|
||||
|
@ -33,96 +33,78 @@ export default class DuplicateAction {
|
||||
this.openmct = openmct;
|
||||
}
|
||||
|
||||
async invoke(objectPath) {
|
||||
let duplicationTask = new DuplicateTask(this.openmct);
|
||||
let originalObject = objectPath[0];
|
||||
let parent = objectPath[1];
|
||||
let userInput;
|
||||
invoke(objectPath) {
|
||||
this.object = objectPath[0];
|
||||
this.parent = objectPath[1];
|
||||
|
||||
try {
|
||||
userInput = await this.getUserInput(originalObject, parent);
|
||||
} catch (error) {
|
||||
// user most likely canceled
|
||||
return;
|
||||
}
|
||||
this.showForm(this.object, this.parent);
|
||||
}
|
||||
|
||||
let newParent = userInput.location;
|
||||
let inNavigationPath = this.inNavigationPath(originalObject);
|
||||
inNavigationPath() {
|
||||
return this.openmct.router.path
|
||||
.some(objectInPath => this.openmct.objects.areIdsEqual(objectInPath.identifier, this.object.identifier));
|
||||
}
|
||||
|
||||
// legacy check
|
||||
if (this.isLegacyDomainObject(newParent)) {
|
||||
newParent = await this.convertFromLegacy(newParent);
|
||||
}
|
||||
|
||||
// if editing, save
|
||||
onSave(changes) {
|
||||
let inNavigationPath = this.inNavigationPath();
|
||||
if (inNavigationPath && this.openmct.editor.isEditing()) {
|
||||
this.openmct.editor.save();
|
||||
}
|
||||
|
||||
// duplicate
|
||||
let newObject = await duplicationTask.duplicate(originalObject, newParent);
|
||||
this.updateNameCheck(newObject, userInput.name);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
async getUserInput(originalObject, parent) {
|
||||
let dialogService = this.openmct.$injector.get('dialogService');
|
||||
let dialogForm = this.getDialogForm(originalObject, parent);
|
||||
let formState = {
|
||||
name: originalObject.name
|
||||
};
|
||||
let userInput = await dialogService.getUserInput(dialogForm, formState);
|
||||
|
||||
return userInput;
|
||||
}
|
||||
|
||||
updateNameCheck(object, name) {
|
||||
if (object.name !== name) {
|
||||
object.name = name;
|
||||
this.openmct.objects.save(object);
|
||||
let duplicationTask = new DuplicateTask(this.openmct);
|
||||
if (changes.name && (changes.name !== this.object.name)) {
|
||||
duplicationTask.changeName(changes.name);
|
||||
}
|
||||
|
||||
const parentDomainObjectpath = changes.location || [this.parent];
|
||||
const parent = parentDomainObjectpath[0];
|
||||
|
||||
return duplicationTask.duplicate(this.object, parent);
|
||||
}
|
||||
|
||||
inNavigationPath(object) {
|
||||
return this.openmct.router.path
|
||||
.some(objectInPath => this.openmct.objects.areIdsEqual(objectInPath.identifier, object.identifier));
|
||||
}
|
||||
|
||||
getDialogForm(object, parent) {
|
||||
return {
|
||||
name: "Duplicate Item",
|
||||
showForm(domainObject, parentDomainObject) {
|
||||
const formStructure = {
|
||||
title: "Duplicate Item",
|
||||
sections: [
|
||||
{
|
||||
rows: [
|
||||
{
|
||||
key: "name",
|
||||
control: "textfield",
|
||||
name: "Name",
|
||||
name: "Title",
|
||||
pattern: "\\S+",
|
||||
required: true,
|
||||
cssClass: "l-input-lg"
|
||||
cssClass: "l-input-lg",
|
||||
value: domainObject.name
|
||||
},
|
||||
{
|
||||
name: "Location",
|
||||
cssClass: "grows",
|
||||
control: "locator",
|
||||
validate: this.validate(object, parent),
|
||||
required: true,
|
||||
parent: parentDomainObject,
|
||||
validate: this.validate(parentDomainObject),
|
||||
key: 'location'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
this.openmct.forms.showForm(formStructure)
|
||||
.then(this.onSave.bind(this));
|
||||
}
|
||||
|
||||
validate(object, currentParent) {
|
||||
return (parentCandidate) => {
|
||||
let currentParentKeystring = this.openmct.objects.makeKeyString(currentParent.identifier);
|
||||
let parentCandidateKeystring = this.openmct.objects.makeKeyString(parentCandidate.getId());
|
||||
let objectKeystring = this.openmct.objects.makeKeyString(object.identifier);
|
||||
validate(currentParent) {
|
||||
return (data) => {
|
||||
const parentCandidatePath = data.value;
|
||||
const parentCandidate = parentCandidatePath[0];
|
||||
|
||||
if (!parentCandidate || !currentParentKeystring) {
|
||||
let currentParentKeystring = this.openmct.objects.makeKeyString(currentParent.identifier);
|
||||
let parentCandidateKeystring = this.openmct.objects.makeKeyString(parentCandidate.identifier);
|
||||
let objectKeystring = this.openmct.objects.makeKeyString(this.object.identifier);
|
||||
|
||||
if (!parentCandidateKeystring || !currentParentKeystring) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -130,24 +112,15 @@ export default class DuplicateAction {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.openmct.composition.checkPolicy(
|
||||
parentCandidate.useCapability('adapter'),
|
||||
object
|
||||
);
|
||||
const parentCandidateComposition = parentCandidate.composition;
|
||||
if (parentCandidateComposition && parentCandidateComposition.indexOf(objectKeystring) !== -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parentCandidate && this.openmct.composition.checkPolicy(parentCandidate, this.object);
|
||||
};
|
||||
}
|
||||
|
||||
isLegacyDomainObject(domainObject) {
|
||||
return domainObject.getCapability !== undefined;
|
||||
}
|
||||
|
||||
async convertFromLegacy(legacyDomainObject) {
|
||||
let objectContext = legacyDomainObject.getCapability('context');
|
||||
let domainObject = await this.openmct.objects.get(objectContext.domainObject.id);
|
||||
|
||||
return domainObject;
|
||||
}
|
||||
|
||||
appliesTo(objectPath) {
|
||||
let parent = objectPath[1];
|
||||
let parentType = parent && this.openmct.types.get(parent.type);
|
||||
|
@ -34,7 +34,6 @@ import uuid from 'uuid';
|
||||
* @constructor
|
||||
*/
|
||||
export default class DuplicateTask {
|
||||
|
||||
constructor(openmct) {
|
||||
this.domainObject = undefined;
|
||||
this.parent = undefined;
|
||||
@ -43,10 +42,15 @@ export default class DuplicateTask {
|
||||
this.persisted = 0;
|
||||
this.clones = [];
|
||||
this.idMap = {};
|
||||
this.name = undefined;
|
||||
|
||||
this.openmct = openmct;
|
||||
}
|
||||
|
||||
changeName(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the duplicate/copy task with the objects provided.
|
||||
* @returns {promise} Which will resolve with a clone of the object
|
||||
@ -79,11 +83,14 @@ export default class DuplicateTask {
|
||||
*/
|
||||
async buildDuplicationPlan() {
|
||||
let domainObjectClone = await this.duplicateObject(this.domainObject);
|
||||
|
||||
if (domainObjectClone !== this.domainObject) {
|
||||
domainObjectClone.location = this.getKeyString(this.parent);
|
||||
}
|
||||
|
||||
if (this.name) {
|
||||
domainObjectClone.name = this.name;
|
||||
}
|
||||
|
||||
this.firstClone = domainObjectClone;
|
||||
|
||||
return;
|
||||
|
@ -20,7 +20,6 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
import DuplicateActionPlugin from './plugin.js';
|
||||
import DuplicateAction from './DuplicateAction.js';
|
||||
import DuplicateTask from './DuplicateTask.js';
|
||||
import {
|
||||
createOpenMct,
|
||||
@ -29,7 +28,6 @@ import {
|
||||
} from 'utils/testing';
|
||||
|
||||
describe("The Duplicate Action plugin", () => {
|
||||
|
||||
let openmct;
|
||||
let duplicateTask;
|
||||
let childObject;
|
||||
@ -52,6 +50,7 @@ describe("The Duplicate Action plugin", () => {
|
||||
}
|
||||
}
|
||||
}).folder;
|
||||
|
||||
parentObject = getMockObjects({
|
||||
objectKeyStrings: ['folder'],
|
||||
overwrite: {
|
||||
@ -62,6 +61,7 @@ describe("The Duplicate Action plugin", () => {
|
||||
}
|
||||
}
|
||||
}).folder;
|
||||
|
||||
anotherParentObject = getMockObjects({
|
||||
objectKeyStrings: ['folder'],
|
||||
overwrite: {
|
||||
@ -120,7 +120,6 @@ describe("The Duplicate Action plugin", () => {
|
||||
});
|
||||
|
||||
describe("when moving an object to a new parent", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
duplicateTask = new DuplicateTask(openmct);
|
||||
await duplicateTask.duplicate(parentObject, anotherParentObject);
|
||||
@ -142,16 +141,14 @@ describe("The Duplicate Action plugin", () => {
|
||||
});
|
||||
|
||||
describe("when a new name is provided for the duplicated object", () => {
|
||||
it("the name is updated", () => {
|
||||
it("the name is updated", async () => {
|
||||
const NEW_NAME = 'New Name';
|
||||
let childName;
|
||||
|
||||
duplicateTask = new DuplicateAction(openmct);
|
||||
duplicateTask.updateNameCheck(parentObject, NEW_NAME);
|
||||
duplicateTask = new DuplicateTask(openmct);
|
||||
duplicateTask.changeName(NEW_NAME);
|
||||
const child = await duplicateTask.duplicate(childObject, anotherParentObject);
|
||||
|
||||
childName = parentObject.name;
|
||||
|
||||
expect(childName).toEqual(NEW_NAME);
|
||||
expect(child.name).toEqual(NEW_NAME);
|
||||
});
|
||||
});
|
||||
|
||||
|
202
src/plugins/exportAsJSONAction/ExportAsJSONAction.js
Normal file
202
src/plugins/exportAsJSONAction/ExportAsJSONAction.js
Normal file
@ -0,0 +1,202 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
import JSONExporter from '/src/exporters/JSONExporter.js';
|
||||
|
||||
import _ from 'lodash';
|
||||
import { saveAs } from 'saveAs';
|
||||
import uuid from "uuid";
|
||||
|
||||
export default class ExportAsJSONAction {
|
||||
constructor(openmct) {
|
||||
this.openmct = openmct;
|
||||
|
||||
this.name = 'Export as JSON';
|
||||
this.key = 'export.JSON';
|
||||
this.description = '';
|
||||
this.cssClass = "icon-export";
|
||||
this.group = "json";
|
||||
this.priority = 1;
|
||||
|
||||
this.externalIdentifiers = [];
|
||||
this.tree = {};
|
||||
this.calls = 0;
|
||||
this.idMap = {};
|
||||
|
||||
this.JSONExportService = new JSONExporter(saveAs);
|
||||
}
|
||||
|
||||
// Public
|
||||
/**
|
||||
*
|
||||
* @param {object} objectPath
|
||||
* @returns {boolean}
|
||||
*/
|
||||
appliesTo(objectPath) {
|
||||
let domainObject = objectPath[0];
|
||||
|
||||
return this._isCreatable(domainObject);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {object} objectpath
|
||||
*/
|
||||
invoke(objectpath) {
|
||||
const root = objectpath[0];
|
||||
this.root = JSON.parse(JSON.stringify(root));
|
||||
const rootId = this._getId(this.root);
|
||||
this.tree[rootId] = this.root;
|
||||
|
||||
this._write(this.root);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {object} domainObject
|
||||
* @returns {string} A string representation of the given identifier, including namespace and key
|
||||
*/
|
||||
_getId(domainObject) {
|
||||
return this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {object} domainObject
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isCreatable(domainObject) {
|
||||
const type = this.openmct.types.get(domainObject.type);
|
||||
|
||||
return type && type.definition.creatable;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {object} child
|
||||
* @param {object} parent
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isLinkedObject(child, parent) {
|
||||
if (child.location !== this._getId(parent)
|
||||
&& !Object.keys(this.tree).includes(child.location)
|
||||
&& this._getId(child) !== this._getId(this.root)
|
||||
|| this.externalIdentifiers.includes(this._getId(child))) {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {object} child
|
||||
* @param {object} parent
|
||||
* @returns {object}
|
||||
*/
|
||||
_rewriteLink(child, parent) {
|
||||
this.externalIdentifiers.push(this._getId(child));
|
||||
const index = parent.composition.findIndex(id => {
|
||||
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);
|
||||
|
||||
this.idMap[this._getId(child)] = newIdString;
|
||||
copyOfChild.location = parentId;
|
||||
parent.composition[index] = copyOfChild.identifier;
|
||||
this.tree[newIdString] = copyOfChild;
|
||||
this.tree[parentId].composition[index] = copyOfChild.identifier;
|
||||
|
||||
return copyOfChild;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_rewriteReferences() {
|
||||
let treeString = JSON.stringify(this.tree);
|
||||
Object.keys(this.idMap).forEach(function (oldId) {
|
||||
const newId = this.idMap[oldId];
|
||||
treeString = treeString.split(oldId).join(newId);
|
||||
}.bind(this));
|
||||
this.tree = JSON.parse(treeString);
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {object} completedTree
|
||||
*/
|
||||
_saveAs(completedTree) {
|
||||
this.JSONExportService.export(
|
||||
completedTree,
|
||||
{ filename: this.root.name + '.json' }
|
||||
);
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @returns {object}
|
||||
*/
|
||||
_wrapTree() {
|
||||
return {
|
||||
"openmct": this.tree,
|
||||
"rootId": this._getId(this.root)
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {object} parent
|
||||
*/
|
||||
_write(parent) {
|
||||
this.calls++;
|
||||
const composition = this.openmct.composition.get(parent);
|
||||
if (composition !== undefined) {
|
||||
composition.load()
|
||||
.then((children) => {
|
||||
children.forEach((child, index) => {
|
||||
// Only export if object is creatable
|
||||
if (this._isCreatable(child)) {
|
||||
// Prevents infinite export of self-contained objs
|
||||
if (!Object.prototype.hasOwnProperty.call(this.tree, this._getId(child))) {
|
||||
// If object is a link to something absent from
|
||||
// tree, generate new id and treat as new object
|
||||
if (this._isLinkedObject(child, parent)) {
|
||||
child = this._rewriteLink(child, parent);
|
||||
} else {
|
||||
this.tree[this._getId(child)] = child;
|
||||
}
|
||||
|
||||
this._write(child);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.calls--;
|
||||
if (this.calls === 0) {
|
||||
this._rewriteReferences();
|
||||
this._saveAs(this._wrapTree());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.calls--;
|
||||
if (this.calls === 0) {
|
||||
this._rewriteReferences();
|
||||
this._saveAs(this._wrapTree());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
252
src/plugins/exportAsJSONAction/ExportAsJSONAction.spec.js
Normal file
252
src/plugins/exportAsJSONAction/ExportAsJSONAction.spec.js
Normal file
@ -0,0 +1,252 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
28
src/plugins/exportAsJSONAction/plugin.js
Normal file
28
src/plugins/exportAsJSONAction/plugin.js
Normal file
@ -0,0 +1,28 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
import ExportAsJSONAction from './ExportAsJSONAction';
|
||||
|
||||
export default function () {
|
||||
return function (openmct) {
|
||||
openmct.actions.register(new ExportAsJSONAction(openmct));
|
||||
};
|
||||
}
|
150
src/plugins/formActions/CreateAction.js
Normal file
150
src/plugins/formActions/CreateAction.js
Normal file
@ -0,0 +1,150 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
import PropertiesAction from './PropertiesAction';
|
||||
import CreateWizard from './CreateWizard';
|
||||
|
||||
import uuid from 'uuid';
|
||||
|
||||
export default class CreateAction extends PropertiesAction {
|
||||
constructor(openmct, type, parentDomainObject) {
|
||||
super(openmct);
|
||||
|
||||
this.type = type;
|
||||
this.parentDomainObject = parentDomainObject;
|
||||
}
|
||||
|
||||
invoke() {
|
||||
this._showCreateForm(this.type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async _onSave(changes) {
|
||||
let parentDomainObjectPath;
|
||||
|
||||
Object.entries(changes).forEach(([key, value]) => {
|
||||
if (key === 'location') {
|
||||
parentDomainObjectPath = value;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const properties = key.split('.');
|
||||
let object = this.domainObject;
|
||||
const propertiesLength = properties.length;
|
||||
properties.forEach((property, index) => {
|
||||
const isComplexProperty = propertiesLength > 1 && index !== propertiesLength - 1;
|
||||
if (isComplexProperty && object[property] !== null) {
|
||||
object = object[property];
|
||||
} else {
|
||||
object[property] = value;
|
||||
}
|
||||
});
|
||||
|
||||
object = value;
|
||||
});
|
||||
|
||||
const parentDomainObject = parentDomainObjectPath[0];
|
||||
|
||||
this.domainObject.modified = Date.now();
|
||||
this.domainObject.location = this.openmct.objects.makeKeyString(parentDomainObject.identifier);
|
||||
this.domainObject.identifier.namespace = parentDomainObject.identifier.namespace;
|
||||
|
||||
// Show saving progress dialog
|
||||
let dialog = this.openmct.overlays.progressDialog({
|
||||
progressPerc: 'unknown',
|
||||
message: 'Do not navigate away from this page or close this browser tab while this message is displayed.',
|
||||
iconClass: 'info',
|
||||
title: 'Saving'
|
||||
});
|
||||
|
||||
const success = await this.openmct.objects.save(this.domainObject);
|
||||
if (success) {
|
||||
const compositionCollection = await this.openmct.composition.get(parentDomainObject);
|
||||
compositionCollection.add(this.domainObject);
|
||||
|
||||
this._navigateAndEdit(this.domainObject, parentDomainObjectPath);
|
||||
|
||||
this.openmct.notifications.info('Save successful');
|
||||
} else {
|
||||
this.openmct.notifications.error('Error saving objects');
|
||||
}
|
||||
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async _navigateAndEdit(domainObject, parentDomainObjectpath) {
|
||||
let objectPath;
|
||||
if (parentDomainObjectpath) {
|
||||
objectPath = parentDomainObjectpath && [domainObject].concat(parentDomainObjectpath);
|
||||
} else {
|
||||
objectPath = await this.openmct.objects.getOriginalPath(domainObject.identifier);
|
||||
}
|
||||
|
||||
const url = '#/browse/' + objectPath
|
||||
.map(object => object && this.openmct.objects.makeKeyString(object.identifier.key))
|
||||
.reverse()
|
||||
.join('/');
|
||||
|
||||
this.openmct.router.navigate(url);
|
||||
|
||||
const objectView = this.openmct.objectViews.get(domainObject, objectPath)[0];
|
||||
const canEdit = objectView && objectView.canEdit && objectView.canEdit(domainObject, objectPath);
|
||||
if (canEdit) {
|
||||
this.openmct.editor.edit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_showCreateForm(type) {
|
||||
const typeDefinition = this.openmct.types.get(type);
|
||||
const definition = typeDefinition.definition;
|
||||
const domainObject = {
|
||||
name: `Unnamed ${definition.name}`,
|
||||
type,
|
||||
identifier: {
|
||||
key: uuid(),
|
||||
namespace: this.parentDomainObject.identifier.namespace
|
||||
}
|
||||
};
|
||||
|
||||
this.domainObject = domainObject;
|
||||
|
||||
if (definition.initialize) {
|
||||
definition.initialize(domainObject);
|
||||
}
|
||||
|
||||
const createWizard = new CreateWizard(this.openmct, domainObject, this.parentDomainObject);
|
||||
const formStructure = createWizard.getFormStructure(true);
|
||||
formStructure.title = 'Create a New ' + definition.name;
|
||||
|
||||
this.openmct.forms.showForm(formStructure)
|
||||
.then(this._onSave.bind(this));
|
||||
}
|
||||
}
|
135
src/plugins/formActions/CreateWizard.js
Normal file
135
src/plugins/formActions/CreateWizard.js
Normal file
@ -0,0 +1,135 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
export default class CreateWizard {
|
||||
constructor(openmct, domainObject, parent) {
|
||||
this.openmct = openmct;
|
||||
|
||||
this.domainObject = domainObject;
|
||||
this.type = openmct.types.get(domainObject.type);
|
||||
|
||||
this.model = domainObject;
|
||||
this.parent = parent;
|
||||
this.properties = this.type.definition.form || [];
|
||||
}
|
||||
|
||||
addNotes(sections) {
|
||||
const row = {
|
||||
control: 'textarea',
|
||||
cssClass: 'l-input-lg',
|
||||
key: 'notes',
|
||||
name: 'Notes',
|
||||
required: false,
|
||||
value: this.domainObject.notes
|
||||
};
|
||||
|
||||
sections.forEach(section => {
|
||||
if (section.name !== 'Properties') {
|
||||
return;
|
||||
}
|
||||
|
||||
section.rows.unshift(row);
|
||||
});
|
||||
}
|
||||
|
||||
addTitle(sections) {
|
||||
const row = {
|
||||
control: 'textfield',
|
||||
cssClass: 'l-input-lg',
|
||||
key: 'name',
|
||||
name: 'Title',
|
||||
pattern: `\\S+`,
|
||||
required: true,
|
||||
value: this.domainObject.name
|
||||
};
|
||||
|
||||
sections.forEach(section => {
|
||||
if (section.name !== 'Properties') {
|
||||
return;
|
||||
}
|
||||
|
||||
section.rows.unshift(row);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the form model for this wizard; this is a description
|
||||
* that will be rendered to an HTML form. See the
|
||||
* platform/forms bundle
|
||||
* @param {boolean} includeLocation if true, a 'location' section
|
||||
* will be included that will allow the user to select the location
|
||||
* of the newly created object, otherwise the .location property of
|
||||
* the model will be used.
|
||||
*/
|
||||
getFormStructure(includeLocation) {
|
||||
let sections = [];
|
||||
let domainObject = this.domainObject;
|
||||
let self = this;
|
||||
|
||||
sections.push({
|
||||
name: 'Properties',
|
||||
rows: this.properties.map(property => {
|
||||
const row = JSON.parse(JSON.stringify(property));
|
||||
row.value = this.getValue(row);
|
||||
|
||||
return row;
|
||||
}).filter(row => row && row.control)
|
||||
});
|
||||
|
||||
this.addNotes(sections);
|
||||
this.addTitle(sections);
|
||||
|
||||
// Ensure there is always a 'save in' section
|
||||
if (includeLocation) {
|
||||
function validateLocation(data) {
|
||||
return self.openmct.composition.checkPolicy(data.value[0], domainObject);
|
||||
}
|
||||
|
||||
sections.push({
|
||||
name: 'Location',
|
||||
cssClass: 'grows',
|
||||
rows: [{
|
||||
name: 'Save In',
|
||||
cssClass: 'grows',
|
||||
control: 'locator',
|
||||
domainObject,
|
||||
required: true,
|
||||
parent: this.parent,
|
||||
validate: validateLocation.bind(this),
|
||||
key: 'location'
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
sections
|
||||
};
|
||||
}
|
||||
|
||||
getValue(row) {
|
||||
if (row.property) {
|
||||
return row.property.reduce((acc, property) => acc && acc[property], this.domainObject);
|
||||
} else {
|
||||
return this.domainObject[row.key];
|
||||
}
|
||||
}
|
||||
}
|
102
src/plugins/formActions/EditPropertiesAction.js
Normal file
102
src/plugins/formActions/EditPropertiesAction.js
Normal file
@ -0,0 +1,102 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
import PropertiesAction from './PropertiesAction';
|
||||
import CreateWizard from './CreateWizard';
|
||||
export default class EditPropertiesAction extends PropertiesAction {
|
||||
constructor(openmct) {
|
||||
super(openmct);
|
||||
|
||||
this.name = 'Edit Properties...';
|
||||
this.key = 'properties';
|
||||
this.description = 'Edit properties of this object.';
|
||||
this.cssClass = 'major icon-pencil';
|
||||
this.hideInDefaultMenu = true;
|
||||
this.group = 'action';
|
||||
this.priority = 10;
|
||||
this.formProperties = {};
|
||||
}
|
||||
|
||||
appliesTo(objectPath) {
|
||||
const definition = this._getTypeDefinition(objectPath[0].type);
|
||||
|
||||
return definition && definition.creatable;
|
||||
}
|
||||
|
||||
invoke(objectPath) {
|
||||
this._showEditForm(objectPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async _onSave(changes) {
|
||||
Object.entries(changes).forEach(([key, value]) => {
|
||||
const properties = key.split('.');
|
||||
let object = this.domainObject;
|
||||
const propertiesLength = properties.length;
|
||||
properties.forEach((property, index) => {
|
||||
const isComplexProperty = propertiesLength > 1 && index !== propertiesLength - 1;
|
||||
if (isComplexProperty && object[property] !== null) {
|
||||
object = object[property];
|
||||
} else {
|
||||
object[property] = value;
|
||||
}
|
||||
});
|
||||
|
||||
object = value;
|
||||
});
|
||||
|
||||
this.domainObject.modified = Date.now();
|
||||
|
||||
// Show saving progress dialog
|
||||
let dialog = this.openmct.overlays.progressDialog({
|
||||
progressPerc: 'unknown',
|
||||
message: 'Do not navigate away from this page or close this browser tab while this message is displayed.',
|
||||
iconClass: 'info',
|
||||
title: 'Saving'
|
||||
});
|
||||
|
||||
const success = await this.openmct.objects.save(this.domainObject);
|
||||
if (success) {
|
||||
this.openmct.notifications.info('Save successful');
|
||||
} else {
|
||||
this.openmct.notifications.error('Error saving objects');
|
||||
}
|
||||
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_showEditForm(objectPath) {
|
||||
this.domainObject = objectPath[0];
|
||||
|
||||
const createWizard = new CreateWizard(this.openmct, this.domainObject, objectPath[1]);
|
||||
const formStructure = createWizard.getFormStructure(false);
|
||||
formStructure.title = 'Edit ' + this.domainObject.name;
|
||||
|
||||
this.openmct.forms.showForm(formStructure)
|
||||
.then(this._onSave.bind(this));
|
||||
}
|
||||
}
|
35
src/plugins/formActions/PropertiesAction.js
Normal file
35
src/plugins/formActions/PropertiesAction.js
Normal file
@ -0,0 +1,35 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
export default class PropertiesAction {
|
||||
constructor(openmct) {
|
||||
this.openmct = openmct;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_getTypeDefinition(type) {
|
||||
const TypeDefinition = this.openmct.types.get(type);
|
||||
|
||||
return TypeDefinition.definition;
|
||||
}
|
||||
}
|
29
src/plugins/formActions/plugin.js
Normal file
29
src/plugins/formActions/plugin.js
Normal file
@ -0,0 +1,29 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
import EditPropertiesAction from './EditPropertiesAction';
|
||||
|
||||
export default function () {
|
||||
return function (openmct) {
|
||||
openmct.actions.register(new EditPropertiesAction(openmct));
|
||||
};
|
||||
}
|
249
src/plugins/importFromJSONAction/ImportFromJSONAction.js
Normal file
249
src/plugins/importFromJSONAction/ImportFromJSONAction.js
Normal file
@ -0,0 +1,249 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
import objectUtils from 'objectUtils';
|
||||
import uuid from "uuid";
|
||||
|
||||
export default class ImportAsJSONAction {
|
||||
constructor(openmct) {
|
||||
this.name = 'Import from JSON';
|
||||
this.key = 'import.JSON';
|
||||
this.description = '';
|
||||
this.cssClass = "icon-import";
|
||||
this.group = "json";
|
||||
this.priority = 2;
|
||||
|
||||
this.openmct = openmct;
|
||||
}
|
||||
|
||||
// Public
|
||||
/**
|
||||
*
|
||||
* @param {object} objectPath
|
||||
* @returns {boolean}
|
||||
*/
|
||||
appliesTo(objectPath) {
|
||||
const domainObject = objectPath[0];
|
||||
if (domainObject && domainObject.locked) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return domainObject !== undefined
|
||||
&& this.openmct.composition.get(domainObject);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {object} objectPath
|
||||
*/
|
||||
invoke(objectPath) {
|
||||
this._showForm(objectPath[0]);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {object} object
|
||||
* @param {object} changes
|
||||
*/
|
||||
onSave(object, changes) {
|
||||
const selectFile = changes.selectFile;
|
||||
const objectTree = selectFile.body;
|
||||
this._importObjectTree(object, JSON.parse(objectTree));
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {object} parent
|
||||
* @param {object} tree
|
||||
* @param {object} seen
|
||||
*/
|
||||
_deepInstantiate(parent, tree, seen) {
|
||||
if (this.openmct.composition.get(parent)) {
|
||||
let newObj;
|
||||
|
||||
seen.push(parent.id);
|
||||
|
||||
parent.composition.forEach(async (childId) => {
|
||||
const keystring = this.openmct.objects.makeKeyString(childId);
|
||||
if (!tree[keystring] || seen.includes(keystring)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newModel = tree[keystring];
|
||||
delete newModel.persisted;
|
||||
|
||||
newObj = await this._instantiate(newModel);
|
||||
this._deepInstantiate(newObj, tree, seen);
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {object} tree
|
||||
* @param {string} namespace
|
||||
* @returns {object}
|
||||
*/
|
||||
_generateNewIdentifiers(tree, namespace) {
|
||||
// For each domain object in the file, generate new ID, replace in tree
|
||||
Object.keys(tree.openmct).forEach(domainObjectId => {
|
||||
const newId = {
|
||||
namespace,
|
||||
key: uuid()
|
||||
};
|
||||
|
||||
const oldId = objectUtils.parseKeyString(domainObjectId);
|
||||
|
||||
tree = this._rewriteId(oldId, newId, tree);
|
||||
}, this);
|
||||
|
||||
return tree;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {object} domainObject
|
||||
* @param {object} objTree
|
||||
*/
|
||||
async _importObjectTree(domainObject, objTree) {
|
||||
const namespace = domainObject.identifier.namespace;
|
||||
const tree = this._generateNewIdentifiers(objTree, namespace);
|
||||
const rootId = tree.rootId;
|
||||
|
||||
const rootModel = tree.openmct[rootId];
|
||||
delete rootModel.persisted;
|
||||
|
||||
const rootObj = await this._instantiate(rootModel);
|
||||
if (this.openmct.composition.checkPolicy(domainObject, rootObj)) {
|
||||
this._deepInstantiate(rootObj, tree.openmct, []);
|
||||
|
||||
const compositionCollection = this.openmct.composition.get(domainObject);
|
||||
let domainObjectKeyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
this.openmct.objects.mutate(rootObj, 'location', domainObjectKeyString);
|
||||
compositionCollection.add(rootObj);
|
||||
} else {
|
||||
const dialog = this.openmct.overlays.dialog({
|
||||
iconClass: 'alert',
|
||||
message: "We're sorry, but you cannot import that object type into this object.",
|
||||
buttons: [
|
||||
{
|
||||
label: "Ok",
|
||||
emphasis: true,
|
||||
callback: function () {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {object} rootModel
|
||||
* @returns {object}
|
||||
*/
|
||||
async _instantiate(rootModel) {
|
||||
const success = await this.openmct.objects.save(rootModel);
|
||||
if (success) {
|
||||
return rootModel;
|
||||
}
|
||||
|
||||
this.openmct.notifications.error('Error saving objects');
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {object} oldId
|
||||
* @param {object} newId
|
||||
* @param {object} tree
|
||||
* @returns {object}
|
||||
*/
|
||||
_rewriteId(oldId, newId, tree) {
|
||||
let newIdKeyString = this.openmct.objects.makeKeyString(newId);
|
||||
let oldIdKeyString = this.openmct.objects.makeKeyString(oldId);
|
||||
tree = JSON.stringify(tree).replace(new RegExp(oldIdKeyString, 'g'), newIdKeyString);
|
||||
|
||||
return JSON.parse(tree, (key, value) => {
|
||||
if (value !== undefined
|
||||
&& value !== null
|
||||
&& Object.prototype.hasOwnProperty.call(value, 'key')
|
||||
&& Object.prototype.hasOwnProperty.call(value, 'namespace')
|
||||
&& value.key === oldId.key
|
||||
&& value.namespace === oldId.namespace) {
|
||||
return newId;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {object} domainObject
|
||||
*/
|
||||
_showForm(domainObject) {
|
||||
const formStructure = {
|
||||
title: this.name,
|
||||
sections: [
|
||||
{
|
||||
rows: [
|
||||
{
|
||||
name: 'Select File',
|
||||
key: 'selectFile',
|
||||
control: 'file-input',
|
||||
required: true,
|
||||
text: 'Select File...',
|
||||
validate: this._validateJSON,
|
||||
type: 'application/json'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
this.openmct.forms.showForm(formStructure).
|
||||
then(changes => {
|
||||
let onSave = this.onSave.bind(this);
|
||||
onSave(domainObject, changes);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {object} data
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_validateJSON(data) {
|
||||
const value = data.value;
|
||||
const objectTree = value && value.body;
|
||||
let json;
|
||||
let success = true;
|
||||
try {
|
||||
json = JSON.parse(objectTree);
|
||||
} catch (e) {
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (success && (!json.openmct || !json.rootId)) {
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
this.openmct.notifications.error('Invalid File: The selected file was either invalid JSON or was not formatted properly for import into Open MCT.');
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
249
src/plugins/importFromJSONAction/ImportFromJSONAction.spec.js
Normal file
249
src/plugins/importFromJSONAction/ImportFromJSONAction.spec.js
Normal file
@ -0,0 +1,249 @@
|
||||
/*****************************************************************************
|
||||
* 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/ImportAsJSONAction",
|
||||
"../../../entanglement/test/DomainObjectFactory"
|
||||
],
|
||||
function (ImportAsJSONAction, domainObjectFactory) {
|
||||
|
||||
describe("The import JSON action", function () {
|
||||
|
||||
let context = {};
|
||||
let action;
|
||||
let exportService;
|
||||
let identifierService;
|
||||
let dialogService;
|
||||
let openmct;
|
||||
let mockDialog;
|
||||
let compositionCapability;
|
||||
let mockInstantiate;
|
||||
let uniqueId;
|
||||
let newObjects;
|
||||
|
||||
beforeEach(function () {
|
||||
uniqueId = 0;
|
||||
newObjects = [];
|
||||
openmct = {
|
||||
$injector: jasmine.createSpyObj('$injector', ['get']),
|
||||
objects: {
|
||||
makeKeyString: function (identifier) {
|
||||
return identifier.key;
|
||||
},
|
||||
isPersistable: jasmine.createSpy('isPersistable')
|
||||
}
|
||||
};
|
||||
openmct.objects.isPersistable.and.returnValue(true);
|
||||
mockInstantiate = jasmine.createSpy('instantiate').and.callFake(
|
||||
function (model, id) {
|
||||
let config = {
|
||||
"model": model,
|
||||
"id": id,
|
||||
"capabilities": {}
|
||||
};
|
||||
let locationCapability = {
|
||||
setPrimaryLocation: jasmine.createSpy('setPrimaryLocation')
|
||||
.and
|
||||
.callFake(function (newLocation) {
|
||||
config.model.location = newLocation;
|
||||
})
|
||||
};
|
||||
config.capabilities.location = locationCapability;
|
||||
if (model.composition) {
|
||||
let compCapability =
|
||||
jasmine.createSpy('compCapability')
|
||||
.and.returnValue(model.composition);
|
||||
compCapability.add = jasmine.createSpy('add')
|
||||
.and.callFake(function (newObj) {
|
||||
config.model.composition.push(newObj.getId());
|
||||
});
|
||||
config.capabilities.composition = compCapability;
|
||||
}
|
||||
|
||||
newObjects.push(domainObjectFactory(config));
|
||||
|
||||
return domainObjectFactory(config);
|
||||
});
|
||||
openmct.$injector.get.and.returnValue(mockInstantiate);
|
||||
dialogService = jasmine.createSpyObj('dialogService',
|
||||
[
|
||||
'getUserInput',
|
||||
'showBlockingMessage'
|
||||
]
|
||||
);
|
||||
identifierService = jasmine.createSpyObj('identifierService',
|
||||
[
|
||||
'generate'
|
||||
]
|
||||
);
|
||||
identifierService.generate.and.callFake(function () {
|
||||
uniqueId++;
|
||||
|
||||
return uniqueId;
|
||||
});
|
||||
compositionCapability = jasmine.createSpy('compositionCapability');
|
||||
mockDialog = jasmine.createSpyObj("dialog", ["dismiss"]);
|
||||
dialogService.showBlockingMessage.and.returnValue(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 () {
|
||||
let compDomainObject = domainObjectFactory({
|
||||
name: 'compObject',
|
||||
model: { name: 'compObject'},
|
||||
capabilities: {"composition": compositionCapability}
|
||||
});
|
||||
let noCompDomainObject = domainObjectFactory();
|
||||
|
||||
context.domainObject = compDomainObject;
|
||||
expect(ImportAsJSONAction.appliesTo(context, undefined, openmct)).toBe(true);
|
||||
context.domainObject = noCompDomainObject;
|
||||
expect(ImportAsJSONAction.appliesTo(context, undefined, openmct)).toBe(false);
|
||||
});
|
||||
|
||||
it("checks object persistability", function () {
|
||||
const compDomainObject = domainObjectFactory({
|
||||
name: 'compObject',
|
||||
model: { name: 'compObject'},
|
||||
capabilities: {"composition": compositionCapability}
|
||||
});
|
||||
|
||||
context.domainObject = compDomainObject;
|
||||
ImportAsJSONAction.appliesTo(context, undefined, openmct);
|
||||
|
||||
expect(openmct.objects.isPersistable).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("displays error dialog on invalid file choice", function () {
|
||||
dialogService.getUserInput.and.returnValue(Promise.resolve(
|
||||
{
|
||||
selectFile: {
|
||||
body: JSON.stringify({badKey: "INVALID"}),
|
||||
name: "fileName"
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
action.perform();
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
setTimeout(resolve, 100);
|
||||
}).then(function () {
|
||||
expect(dialogService.getUserInput).toHaveBeenCalled();
|
||||
expect(dialogService.showBlockingMessage).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
xit("can import self-containing objects", function () {
|
||||
let compDomainObject = domainObjectFactory({
|
||||
name: 'compObject',
|
||||
model: { name: 'compObject'},
|
||||
capabilities: {"composition": compositionCapability}
|
||||
});
|
||||
context.domainObject = compDomainObject;
|
||||
|
||||
dialogService.getUserInput.and.returnValue(Promise.resolve(
|
||||
{
|
||||
selectFile: {
|
||||
body: JSON.stringify({
|
||||
"openmct": {
|
||||
"infiniteParent": {
|
||||
"composition": [{
|
||||
key: "infinteChild",
|
||||
namespace: ""
|
||||
}],
|
||||
"name": "1",
|
||||
"type": "folder",
|
||||
"modified": 1503598129176,
|
||||
"location": "mine",
|
||||
"persisted": 1503598129176
|
||||
},
|
||||
"infinteChild": {
|
||||
"composition": [{
|
||||
key: "infinteParent",
|
||||
namespace: ""
|
||||
}],
|
||||
"name": "2",
|
||||
"type": "folder",
|
||||
"modified": 1503598132428,
|
||||
"location": "infiniteParent",
|
||||
"persisted": 1503598132428
|
||||
}
|
||||
},
|
||||
"rootId": "infiniteParent"
|
||||
}),
|
||||
name: "fileName"
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
action.perform();
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
setTimeout(resolve, 100);
|
||||
}).then(function () {
|
||||
expect(mockInstantiate.calls.count()).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
xit("assigns new ids to each imported object", function () {
|
||||
dialogService.getUserInput.and.returnValue(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"
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
action.perform();
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
setTimeout(resolve, 100);
|
||||
}).then(function () {
|
||||
expect(mockInstantiate.calls.count()).toEqual(1);
|
||||
expect(newObjects[0].getId()).toBe('1');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
28
src/plugins/importFromJSONAction/plugin.js
Normal file
28
src/plugins/importFromJSONAction/plugin.js
Normal file
@ -0,0 +1,28 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
import ImportFromJSONAction from './ImportFromJSONAction';
|
||||
|
||||
export default function () {
|
||||
return function (openmct) {
|
||||
openmct.actions.register(new ImportFromJSONAction(openmct));
|
||||
};
|
||||
}
|
120
src/plugins/linkAction/LinkAction.js
Normal file
120
src/plugins/linkAction/LinkAction.js
Normal file
@ -0,0 +1,120 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
export default class LinkAction {
|
||||
constructor(openmct) {
|
||||
this.name = 'Create Link';
|
||||
this.key = 'link';
|
||||
this.description = 'Create Link to object in another location.';
|
||||
this.cssClass = "icon-link";
|
||||
this.group = "action";
|
||||
this.priority = 7;
|
||||
|
||||
this.openmct = openmct;
|
||||
}
|
||||
|
||||
appliesTo(objectPath) {
|
||||
let domainObject = objectPath[0];
|
||||
let type = domainObject && this.openmct.types.get(domainObject.type);
|
||||
|
||||
return type && type.definition.creatable;
|
||||
}
|
||||
|
||||
invoke(objectPath) {
|
||||
this.object = objectPath[0];
|
||||
this.parent = objectPath[1];
|
||||
this.showForm(this.object, this.parent);
|
||||
}
|
||||
|
||||
inNavigationPath() {
|
||||
return this.openmct.router.path
|
||||
.some(objectInPath => this.openmct.objects.areIdsEqual(objectInPath.identifier, this.object.identifier));
|
||||
}
|
||||
|
||||
onSave(changes) {
|
||||
let inNavigationPath = this.inNavigationPath();
|
||||
if (inNavigationPath && this.openmct.editor.isEditing()) {
|
||||
this.openmct.editor.save();
|
||||
}
|
||||
|
||||
const parentDomainObjectpath = changes.location || [this.parent];
|
||||
const parent = parentDomainObjectpath[0];
|
||||
|
||||
this.linkInNewParent(this.object, parent);
|
||||
}
|
||||
|
||||
linkInNewParent(child, newParent) {
|
||||
let compositionCollection = this.openmct.composition.get(newParent);
|
||||
|
||||
compositionCollection.add(child);
|
||||
}
|
||||
|
||||
showForm(domainObject, parentDomainObject) {
|
||||
const formStructure = {
|
||||
title: `Link "${domainObject.name}" to a New Location`,
|
||||
sections: [
|
||||
{
|
||||
rows: [
|
||||
{
|
||||
name: "location",
|
||||
control: "locator",
|
||||
required: true,
|
||||
validate: this.validate(parentDomainObject),
|
||||
key: 'location'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
this.openmct.forms.showForm(formStructure)
|
||||
.then(this.onSave.bind(this));
|
||||
}
|
||||
|
||||
validate(currentParent) {
|
||||
return (object, data) => {
|
||||
const parentCandidate = data.value;
|
||||
const currentParentKeystring = this.openmct.objects.makeKeyString(currentParent.identifier);
|
||||
const parentCandidateKeystring = this.openmct.objects.makeKeyString(parentCandidate.identifier);
|
||||
const objectKeystring = this.openmct.objects.makeKeyString(object.identifier);
|
||||
|
||||
if (!parentCandidateKeystring || !currentParentKeystring) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parentCandidateKeystring === currentParentKeystring) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parentCandidateKeystring === objectKeystring) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const parentCandidateComposition = parentCandidate.composition;
|
||||
if (parentCandidateComposition && parentCandidateComposition.indexOf(objectKeystring) !== -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parentCandidate && this.openmct.composition.checkPolicy(parentCandidate, object);
|
||||
};
|
||||
}
|
||||
}
|
28
src/plugins/linkAction/plugin.js
Normal file
28
src/plugins/linkAction/plugin.js
Normal file
@ -0,0 +1,28 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
import LinkAction from "./LinkAction";
|
||||
|
||||
export default function () {
|
||||
return function (openmct) {
|
||||
openmct.actions.register(new LinkAction(openmct));
|
||||
};
|
||||
}
|
125
src/plugins/linkAction/pluginSpec.js
Normal file
125
src/plugins/linkAction/pluginSpec.js
Normal file
@ -0,0 +1,125 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
import LinkActionPlugin from './plugin.js';
|
||||
import LinkAction from './LinkAction.js';
|
||||
import {
|
||||
createOpenMct,
|
||||
resetApplicationState,
|
||||
getMockObjects
|
||||
} from 'utils/testing';
|
||||
|
||||
describe("The Link Action plugin", () => {
|
||||
let openmct;
|
||||
let linkAction;
|
||||
let childObject;
|
||||
let parentObject;
|
||||
let anotherParentObject;
|
||||
const ORIGINAL_PARENT_ID = 'original-parent-object';
|
||||
const LINK_ACITON_KEY = 'link';
|
||||
const LINK_ACITON_NAME = 'Create Link';
|
||||
|
||||
beforeEach((done) => {
|
||||
const appHolder = document.createElement('div');
|
||||
appHolder.style.width = '640px';
|
||||
appHolder.style.height = '480px';
|
||||
|
||||
openmct = createOpenMct();
|
||||
|
||||
childObject = getMockObjects({
|
||||
objectKeyStrings: ['folder'],
|
||||
overwrite: {
|
||||
folder: {
|
||||
name: "Child Folder",
|
||||
location: ORIGINAL_PARENT_ID,
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "child-folder-object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}).folder;
|
||||
|
||||
parentObject = getMockObjects({
|
||||
objectKeyStrings: ['folder'],
|
||||
overwrite: {
|
||||
folder: {
|
||||
name: "Parent Folder",
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "original-parent-object"
|
||||
},
|
||||
composition: [childObject.identifier]
|
||||
}
|
||||
}
|
||||
}).folder;
|
||||
|
||||
anotherParentObject = getMockObjects({
|
||||
objectKeyStrings: ['folder'],
|
||||
overwrite: {
|
||||
folder: {
|
||||
name: "Another Parent Folder"
|
||||
}
|
||||
}
|
||||
}).folder;
|
||||
|
||||
openmct.router.path = [childObject]; // preview action uses this in it's applyTo method
|
||||
|
||||
openmct.install(LinkActionPlugin());
|
||||
|
||||
openmct.on('start', done);
|
||||
openmct.startHeadless(appHolder);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
it("should be defined", () => {
|
||||
expect(LinkActionPlugin).toBeDefined();
|
||||
});
|
||||
|
||||
it("should make the link action available for an appropriate domainObject", () => {
|
||||
const actionCollection = openmct.actions.getActionsCollection([childObject]);
|
||||
const visibleActions = actionCollection.getVisibleActions();
|
||||
linkAction = visibleActions.find(a => a.key === LINK_ACITON_KEY);
|
||||
|
||||
expect(linkAction.name).toEqual(LINK_ACITON_NAME);
|
||||
});
|
||||
|
||||
describe("when linking an object in a new parent", () => {
|
||||
beforeEach(() => {
|
||||
linkAction = new LinkAction(openmct);
|
||||
linkAction.linkInNewParent(childObject, anotherParentObject);
|
||||
});
|
||||
|
||||
it("the child object's identifier should be in the new parent's composition and location set to original parent", () => {
|
||||
let newParentChild = anotherParentObject.composition[0];
|
||||
expect(newParentChild).toEqual(childObject.identifier);
|
||||
expect(childObject.location).toEqual(ORIGINAL_PARENT_ID);
|
||||
});
|
||||
|
||||
it("the child object's identifier should remain in the original parent's composition", () => {
|
||||
let oldParentCompositionChild = parentObject.composition[0];
|
||||
expect(oldParentCompositionChild).toEqual(childObject.identifier);
|
||||
});
|
||||
});
|
||||
});
|
@ -31,53 +31,16 @@ export default class MoveAction {
|
||||
this.openmct = openmct;
|
||||
}
|
||||
|
||||
async invoke(objectPath) {
|
||||
let object = objectPath[0];
|
||||
let inNavigationPath = this.inNavigationPath(object);
|
||||
let oldParent = objectPath[1];
|
||||
let dialogService = this.openmct.$injector.get('dialogService');
|
||||
let dialogForm = this.getDialogForm(object, oldParent);
|
||||
let userInput;
|
||||
invoke(objectPath) {
|
||||
this.object = objectPath[0];
|
||||
this.oldParent = objectPath[1];
|
||||
|
||||
try {
|
||||
userInput = await dialogService.getUserInput(dialogForm, { name: object.name });
|
||||
} catch (err) {
|
||||
// user canceled, most likely
|
||||
return;
|
||||
}
|
||||
|
||||
// if we need to update name
|
||||
if (object.name !== userInput.name) {
|
||||
this.openmct.objects.mutate(object, 'name', userInput.name);
|
||||
}
|
||||
|
||||
let parentContext = userInput.location.getCapability('context');
|
||||
let newParent = await this.openmct.objects.get(parentContext.domainObject.id);
|
||||
|
||||
if (inNavigationPath && this.openmct.editor.isEditing()) {
|
||||
this.openmct.editor.save();
|
||||
}
|
||||
|
||||
this.addToNewParent(object, newParent);
|
||||
this.removeFromOldParent(oldParent, object);
|
||||
|
||||
if (inNavigationPath) {
|
||||
let newObjectPath = await this.openmct.objects.getOriginalPath(object.identifier);
|
||||
let root = await this.openmct.objects.getRoot();
|
||||
let rootChildCount = root.composition.length;
|
||||
|
||||
// if not multiple root children, remove root from path
|
||||
if (rootChildCount < 2) {
|
||||
newObjectPath.pop(); // remove ROOT
|
||||
}
|
||||
|
||||
this.navigateTo(newObjectPath);
|
||||
}
|
||||
this.showForm(this.object, this.oldParent);
|
||||
}
|
||||
|
||||
inNavigationPath(object) {
|
||||
inNavigationPath() {
|
||||
return this.openmct.router.path
|
||||
.some(objectInPath => this.openmct.objects.areIdsEqual(objectInPath.identifier, object.identifier));
|
||||
.some(objectInPath => this.openmct.objects.areIdsEqual(objectInPath.identifier, this.object.identifier));
|
||||
}
|
||||
|
||||
navigateTo(objectPath) {
|
||||
@ -85,7 +48,7 @@ export default class MoveAction {
|
||||
.map(object => this.openmct.objects.makeKeyString(object.identifier))
|
||||
.join("/");
|
||||
|
||||
window.location.href = '#/browse/' + urlPath;
|
||||
this.openmct.router.navigate('#/browse/' + urlPath);
|
||||
}
|
||||
|
||||
addToNewParent(child, newParent) {
|
||||
@ -96,42 +59,91 @@ export default class MoveAction {
|
||||
compositionCollection.add(child);
|
||||
}
|
||||
|
||||
removeFromOldParent(parent, child) {
|
||||
let compositionCollection = this.openmct.composition.get(parent);
|
||||
async onSave(changes) {
|
||||
let inNavigationPath = this.inNavigationPath(this.object);
|
||||
if (inNavigationPath && this.openmct.editor.isEditing()) {
|
||||
this.openmct.editor.save();
|
||||
}
|
||||
|
||||
const parentDomainObjectpath = changes.location || [this.parent];
|
||||
const parent = parentDomainObjectpath[0];
|
||||
|
||||
if (this.openmct.objects.areIdsEqual(parent.identifier, this.oldParent.identifier)) {
|
||||
this.openmct.notifications.error(`Error: new location cant not be same as old`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (changes.name && (changes.name !== this.object.name)) {
|
||||
this.object.name = changes.name;
|
||||
}
|
||||
|
||||
this.addToNewParent(this.object, parent);
|
||||
this.removeFromOldParent(this.object);
|
||||
|
||||
if (!inNavigationPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newObjectPath;
|
||||
if (parentDomainObjectpath) {
|
||||
newObjectPath = parentDomainObjectpath && [this.object].concat(parentDomainObjectpath);
|
||||
} else {
|
||||
newObjectPath = await this.openmct.objects.getOriginalPath(this.object.identifier);
|
||||
let root = await this.openmct.objects.getRoot();
|
||||
let rootChildCount = root.composition.length;
|
||||
|
||||
// if not multiple root children, remove root from path
|
||||
if (rootChildCount < 2) {
|
||||
newObjectPath.pop(); // remove ROOT
|
||||
}
|
||||
}
|
||||
|
||||
this.navigateTo(newObjectPath);
|
||||
}
|
||||
|
||||
removeFromOldParent(child) {
|
||||
let compositionCollection = this.openmct.composition.get(this.oldParent);
|
||||
|
||||
compositionCollection.remove(child);
|
||||
}
|
||||
|
||||
getDialogForm(object, parent) {
|
||||
return {
|
||||
name: "Move Item",
|
||||
showForm(domainObject, parentDomainObject) {
|
||||
const formStructure = {
|
||||
title: "Move Item",
|
||||
sections: [
|
||||
{
|
||||
rows: [
|
||||
{
|
||||
key: "name",
|
||||
control: "textfield",
|
||||
name: "Name",
|
||||
name: "Title",
|
||||
pattern: "\\S+",
|
||||
required: true,
|
||||
cssClass: "l-input-lg"
|
||||
cssClass: "l-input-lg",
|
||||
value: domainObject.name
|
||||
},
|
||||
{
|
||||
name: "Location",
|
||||
control: "locator",
|
||||
validate: this.validate(object, parent),
|
||||
required: true,
|
||||
validate: this.validate(parentDomainObject),
|
||||
key: 'location'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
this.openmct.forms.showForm(formStructure)
|
||||
.then(this.onSave.bind(this));
|
||||
}
|
||||
|
||||
validate(object, currentParent) {
|
||||
return (parentCandidate) => {
|
||||
validate(currentParent) {
|
||||
return (object, data) => {
|
||||
const parentCandidate = data.value;
|
||||
let currentParentKeystring = this.openmct.objects.makeKeyString(currentParent.identifier);
|
||||
let parentCandidateKeystring = this.openmct.objects.makeKeyString(parentCandidate.getId());
|
||||
let parentCandidateKeystring = this.openmct.objects.makeKeyString(parentCandidate.identifier);
|
||||
let objectKeystring = this.openmct.objects.makeKeyString(object.identifier);
|
||||
|
||||
if (!parentCandidateKeystring || !currentParentKeystring) {
|
||||
@ -146,14 +158,12 @@ export default class MoveAction {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parentCandidate.getModel().composition.indexOf(objectKeystring) !== -1) {
|
||||
const parentCandidateComposition = parentCandidate.composition;
|
||||
if (parentCandidateComposition && parentCandidateComposition.indexOf(objectKeystring) !== -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.openmct.composition.checkPolicy(
|
||||
parentCandidate.useCapability('adapter'),
|
||||
object
|
||||
);
|
||||
return parentCandidate && this.openmct.composition.checkPolicy(parentCandidate, object);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,6 @@ import {
|
||||
} from 'utils/testing';
|
||||
|
||||
describe("The Move Action plugin", () => {
|
||||
|
||||
let openmct;
|
||||
let moveAction;
|
||||
let childObject;
|
||||
@ -49,20 +48,30 @@ describe("The Move Action plugin", () => {
|
||||
}
|
||||
}
|
||||
}).folder;
|
||||
|
||||
parentObject = getMockObjects({
|
||||
objectKeyStrings: ['folder'],
|
||||
overwrite: {
|
||||
folder: {
|
||||
name: "Parent Folder",
|
||||
composition: [childObject.identifier]
|
||||
composition: [childObject.identifier],
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "parent-folder-object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}).folder;
|
||||
|
||||
anotherParentObject = getMockObjects({
|
||||
objectKeyStrings: ['folder'],
|
||||
overwrite: {
|
||||
folder: {
|
||||
name: "Another Parent Folder"
|
||||
name: "Another Parent Folder",
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "another-parent-folder-object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}).folder;
|
||||
@ -82,10 +91,26 @@ describe("The Move Action plugin", () => {
|
||||
});
|
||||
|
||||
describe("when moving an object to a new parent and removing from the old parent", () => {
|
||||
beforeEach((done) => {
|
||||
openmct.router.path = [];
|
||||
|
||||
beforeEach(() => {
|
||||
moveAction.addToNewParent(childObject, anotherParentObject);
|
||||
moveAction.removeFromOldParent(parentObject, childObject);
|
||||
spyOn(openmct.objects, "save");
|
||||
openmct.objects.save.and.callThrough();
|
||||
spyOn(openmct.forms, "showForm");
|
||||
openmct.forms.showForm.and.callFake(formStructure => {
|
||||
return Promise.resolve({
|
||||
name: 'test',
|
||||
location: [anotherParentObject]
|
||||
});
|
||||
});
|
||||
|
||||
openmct.objects.observe(parentObject, '*', (newObject) => {
|
||||
done();
|
||||
});
|
||||
|
||||
moveAction.inNavigationPath = () => false;
|
||||
|
||||
moveAction.invoke([childObject, parentObject]);
|
||||
});
|
||||
|
||||
it("the child object's identifier should be in the new parent's composition", () => {
|
||||
@ -98,5 +123,4 @@ describe("The Move Action plugin", () => {
|
||||
expect(oldParentComposition.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -19,11 +19,11 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import uuid from 'uuid';
|
||||
import CreateAction from '@/plugins/formActions/CreateAction';
|
||||
|
||||
export default class NewFolderAction {
|
||||
constructor(openmct) {
|
||||
this.type = 'folder';
|
||||
this.name = 'Add New Folder';
|
||||
this.key = 'newFolder';
|
||||
this.description = 'Create a new folder';
|
||||
@ -32,59 +32,18 @@ export default class NewFolderAction {
|
||||
this.priority = 9;
|
||||
|
||||
this._openmct = openmct;
|
||||
this._dialogForm = {
|
||||
name: "Add New Folder",
|
||||
sections: [
|
||||
{
|
||||
rows: [
|
||||
{
|
||||
key: "name",
|
||||
control: "textfield",
|
||||
name: "Folder Name",
|
||||
pattern: "\\S+",
|
||||
required: true,
|
||||
cssClass: "l-input-lg"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
invoke(objectPath) {
|
||||
let domainObject = objectPath[0];
|
||||
let parentKeystring = this._openmct.objects.makeKeyString(domainObject.identifier);
|
||||
let composition = this._openmct.composition.get(domainObject);
|
||||
let dialogService = this._openmct.$injector.get('dialogService');
|
||||
let folderType = this._openmct.types.get('folder');
|
||||
|
||||
dialogService.getUserInput(this._dialogForm, {name: 'Unnamed Folder'}).then((userInput) => {
|
||||
let name = userInput.name;
|
||||
|
||||
let identifier = {
|
||||
key: uuid(),
|
||||
namespace: domainObject.identifier.namespace
|
||||
};
|
||||
|
||||
let objectModel = {
|
||||
identifier,
|
||||
type: 'folder',
|
||||
location: parentKeystring
|
||||
};
|
||||
|
||||
folderType.definition.initialize(objectModel);
|
||||
objectModel.name = name || 'New Folder';
|
||||
objectModel.modified = Date.now();
|
||||
|
||||
this._openmct.objects.save(objectModel).then(() => {
|
||||
composition.add(objectModel);
|
||||
});
|
||||
|
||||
});
|
||||
const parentDomainObject = objectPath[0];
|
||||
const createAction = new CreateAction(this._openmct, this.type, parentDomainObject);
|
||||
createAction.invoke();
|
||||
}
|
||||
|
||||
appliesTo(objectPath) {
|
||||
let domainObject = objectPath[0];
|
||||
let isPersistable = this._openmct.objects.isPersistable(domainObject.identifier);
|
||||
|
||||
return domainObject.type === 'folder' && isPersistable;
|
||||
return domainObject.type === this.type && isPersistable;
|
||||
}
|
||||
}
|
||||
|
@ -26,13 +26,7 @@ import {
|
||||
|
||||
describe("the plugin", () => {
|
||||
let openmct;
|
||||
let compositionAPI;
|
||||
let newFolderAction;
|
||||
let mockObjectPath;
|
||||
let mockDialogService;
|
||||
let mockComposition;
|
||||
let mockPromise;
|
||||
let newFolderName = 'New Folder';
|
||||
|
||||
beforeEach((done) => {
|
||||
openmct = createOpenMct();
|
||||
@ -52,39 +46,40 @@ describe("the plugin", () => {
|
||||
});
|
||||
|
||||
describe('when invoked', () => {
|
||||
|
||||
let parentObject;
|
||||
let parentObjectPath;
|
||||
let changedParentObject;
|
||||
beforeEach((done) => {
|
||||
compositionAPI = openmct.composition;
|
||||
mockObjectPath = [{
|
||||
parentObject = {
|
||||
name: 'mock folder',
|
||||
type: 'folder',
|
||||
identifier: {
|
||||
key: 'mock-folder',
|
||||
namespace: ''
|
||||
}
|
||||
}];
|
||||
mockPromise = {
|
||||
then: (callback) => {
|
||||
callback({name: newFolderName});
|
||||
done();
|
||||
}
|
||||
},
|
||||
composition: []
|
||||
};
|
||||
parentObjectPath = [parentObject];
|
||||
|
||||
mockDialogService = jasmine.createSpyObj('dialogService', ['getUserInput']);
|
||||
mockComposition = jasmine.createSpyObj('composition', ['add']);
|
||||
spyOn(openmct.objects, "save");
|
||||
openmct.objects.save.and.callThrough();
|
||||
|
||||
mockDialogService.getUserInput.and.returnValue(mockPromise);
|
||||
spyOn(openmct.forms, "showForm");
|
||||
openmct.forms.showForm.and.callFake(formStructure => {
|
||||
return Promise.resolve({
|
||||
name: 'test',
|
||||
notes: 'test notes',
|
||||
location: parentObjectPath
|
||||
});
|
||||
});
|
||||
|
||||
spyOn(openmct.$injector, 'get').and.returnValue(mockDialogService);
|
||||
spyOn(compositionAPI, 'get').and.returnValue(mockComposition);
|
||||
spyOn(openmct.objects, 'save').and.returnValue(Promise.resolve(true));
|
||||
spyOn(openmct.objects, 'isPersistable').and.returnValue(true);
|
||||
openmct.objects.observe(parentObject, '*', (newObject) => {
|
||||
changedParentObject = newObject;
|
||||
|
||||
return newFolderAction.invoke(mockObjectPath);
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
it('gets user input for folder name', () => {
|
||||
expect(mockDialogService.getUserInput).toHaveBeenCalled();
|
||||
newFolderAction.invoke(parentObjectPath);
|
||||
});
|
||||
|
||||
it('creates a new folder object', () => {
|
||||
@ -92,10 +87,23 @@ describe("the plugin", () => {
|
||||
});
|
||||
|
||||
it('adds new folder object to parent composition', () => {
|
||||
expect(mockComposition.add).toHaveBeenCalled();
|
||||
const composition = changedParentObject.composition;
|
||||
|
||||
expect(composition.length).toBe(1);
|
||||
});
|
||||
|
||||
it('checks if the domainObject is persistable', () => {
|
||||
const mockObjectPath = [{
|
||||
name: 'mock folder',
|
||||
type: 'folder',
|
||||
identifier: {
|
||||
key: 'mock-folder',
|
||||
namespace: ''
|
||||
}
|
||||
}];
|
||||
|
||||
spyOn(openmct.objects, 'isPersistable').and.returnValue(true);
|
||||
|
||||
newFolderAction.appliesTo(mockObjectPath);
|
||||
|
||||
expect(openmct.objects.isPersistable).toHaveBeenCalled();
|
||||
|
@ -71,6 +71,7 @@ export default function NotebookPlugin() {
|
||||
name: 'Section Title',
|
||||
control: 'textfield',
|
||||
cssClass: 'l-inline',
|
||||
required: true,
|
||||
property: [
|
||||
"configuration",
|
||||
"sectionTitle"
|
||||
@ -81,6 +82,7 @@ export default function NotebookPlugin() {
|
||||
name: 'Page Title',
|
||||
control: 'textfield',
|
||||
cssClass: 'l-inline',
|
||||
required: true,
|
||||
property: [
|
||||
"configuration",
|
||||
"pageTitle"
|
||||
|
@ -38,7 +38,10 @@ export default function (configuration) {
|
||||
control: 'file-input',
|
||||
required: true,
|
||||
text: 'Select File...',
|
||||
type: 'application/json'
|
||||
type: 'application/json',
|
||||
property: [
|
||||
"selectFile"
|
||||
]
|
||||
}
|
||||
],
|
||||
initialize: function (domainObject) {
|
||||
|
@ -32,7 +32,6 @@ define([
|
||||
'./timeConductor/plugin',
|
||||
'../../example/imagery/plugin',
|
||||
'./imagery/plugin',
|
||||
'../../platform/import-export/bundle',
|
||||
'./summaryWidget/plugin',
|
||||
'./URLIndicatorPlugin/URLIndicatorPlugin',
|
||||
'./telemetryMean/plugin',
|
||||
@ -42,6 +41,7 @@ define([
|
||||
'./staticRootPlugin/plugin',
|
||||
'./notebook/plugin',
|
||||
'./displayLayout/plugin',
|
||||
'./formActions/plugin',
|
||||
'./folderView/plugin',
|
||||
'./flexibleLayout/plugin',
|
||||
'./tabs/plugin',
|
||||
@ -87,7 +87,6 @@ define([
|
||||
TimeConductorPlugin,
|
||||
ExampleImagery,
|
||||
ImageryPlugin,
|
||||
ImportExport,
|
||||
SummaryWidget,
|
||||
URLIndicatorPlugin,
|
||||
TelemetryMean,
|
||||
@ -97,6 +96,7 @@ define([
|
||||
StaticRootPlugin,
|
||||
Notebook,
|
||||
DisplayLayoutPlugin,
|
||||
FormActions,
|
||||
FolderView,
|
||||
FlexibleLayout,
|
||||
Tabs,
|
||||
@ -150,8 +150,6 @@ define([
|
||||
|
||||
plugins.MyItems = MyItems.default;
|
||||
|
||||
plugins.ImportExport = ImportExport;
|
||||
|
||||
plugins.StaticRootPlugin = StaticRootPlugin;
|
||||
|
||||
/**
|
||||
@ -206,6 +204,7 @@ define([
|
||||
plugins.URLIndicator = URLIndicatorPlugin;
|
||||
plugins.Notebook = Notebook.default;
|
||||
plugins.DisplayLayout = DisplayLayoutPlugin.default;
|
||||
plugins.FormActions = FormActions;
|
||||
plugins.FolderView = FolderView;
|
||||
plugins.Tabs = Tabs;
|
||||
plugins.FlexibleLayout = FlexibleLayout;
|
||||
|
@ -85,10 +85,6 @@ describe("the plugin", () => {
|
||||
|
||||
viewDatumAction.invoke(mockObjectPath, mockView);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('uses an overlay to show user datum values', () => {
|
||||
expect(openmct.overlays.overlay).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user