mirror of
https://github.com/nasa/openmct.git
synced 2025-04-15 23:16:46 +00:00
290 lines
8.8 KiB
JavaScript
290 lines
8.8 KiB
JavaScript
/*****************************************************************************
|
|
* Open MCT, Copyright (c) 2014-2023, 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 { v4 as uuid } from 'uuid';
|
|
|
|
/**
|
|
* This class encapsulates the process of duplicating/copying a domain object
|
|
* and all of its children.
|
|
*
|
|
* @param {DomainObject} domainObject The object to duplicate
|
|
* @param {DomainObject} parent The new location of the cloned object tree
|
|
* @param {src/plugins/duplicate.DuplicateService~filter} filter
|
|
* a function used to filter out objects from
|
|
* the cloning process
|
|
* @constructor
|
|
*/
|
|
export default class DuplicateTask {
|
|
constructor(openmct) {
|
|
this.domainObject = undefined;
|
|
this.parent = undefined;
|
|
this.firstClone = undefined;
|
|
this.filter = undefined;
|
|
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
|
|
* once complete.
|
|
*/
|
|
async duplicate(domainObject, parent, filter) {
|
|
this.domainObject = domainObject;
|
|
this.parent = parent;
|
|
this.namespace = parent.identifier.namespace;
|
|
this.filter = filter || this.isCreatable;
|
|
|
|
await this.buildDuplicationPlan();
|
|
await this.persistObjects();
|
|
await this.addClonesToParent();
|
|
|
|
return this.firstClone;
|
|
}
|
|
|
|
/**
|
|
* Will build a graph of an object and all of its child objects in
|
|
* memory
|
|
* @private
|
|
* @param domainObject The original object to be copied
|
|
* @param parent The parent of the original object to be copied
|
|
* @returns {Promise} resolved with an array of clones of the models
|
|
* of the object tree being copied. Duplicating is done in a bottom-up
|
|
* fashion, so that the last member in the array is a clone of the model
|
|
* object being copied. The clones are all full composed with
|
|
* references to their own children.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Will persist a list of {@link objectClones}. It will persist all
|
|
* simultaneously, irrespective of order in the list. This may
|
|
* result in automatic request batching by the browser.
|
|
*/
|
|
async persistObjects() {
|
|
let initialCount = this.clones.length;
|
|
let dialog = this.openmct.overlays.progressDialog({
|
|
progressPerc: 0,
|
|
message: `Duplicating ${initialCount} objects.`,
|
|
iconClass: 'info',
|
|
title: 'Duplicating'
|
|
});
|
|
|
|
let clonesDone = Promise.all(
|
|
this.clones.map((clone) => {
|
|
let percentPersisted = Math.ceil(100 * (++this.persisted / initialCount));
|
|
let message = `Duplicating ${initialCount - this.persisted} objects.`;
|
|
|
|
dialog.updateProgress(percentPersisted, message);
|
|
|
|
return this.openmct.objects.save(clone);
|
|
})
|
|
);
|
|
|
|
await clonesDone;
|
|
|
|
dialog.dismiss();
|
|
this.openmct.notifications.info(`Duplicated ${this.persisted} objects.`);
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Will add a list of clones to the specified parent's composition
|
|
*/
|
|
async addClonesToParent() {
|
|
let parentComposition = this.openmct.composition.get(this.parent);
|
|
await parentComposition.load();
|
|
parentComposition.add(this.firstClone);
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* A recursive function that will perform a bottom-up duplicate of
|
|
* the object tree with originalObject at the root. Recurses to
|
|
* the farthest leaf, then works its way back up again,
|
|
* cloning objects, and composing them with their child clones
|
|
* as it goes
|
|
* @private
|
|
* @returns {DomainObject} If the type of the original object allows for
|
|
* duplication, then a duplicate of the object, otherwise the object
|
|
* itself (to allow linking to non duplicatable objects).
|
|
*/
|
|
async duplicateObject(originalObject) {
|
|
// Check if the creatable (or other passed in filter).
|
|
if (this.filter(originalObject)) {
|
|
let clone = this.cloneObjectModel(originalObject);
|
|
let composeesCollection = this.openmct.composition.get(originalObject);
|
|
let composees;
|
|
|
|
if (composeesCollection) {
|
|
composees = await composeesCollection.load();
|
|
}
|
|
|
|
return this.duplicateComposees(clone, composees);
|
|
}
|
|
|
|
// Not creatable, creating a link, no need to iterate children
|
|
return originalObject;
|
|
}
|
|
|
|
/**
|
|
* Given an array of objects composed by a parent, clone them, then
|
|
* add them to the parent.
|
|
* @private
|
|
* @returns {*}
|
|
*/
|
|
async duplicateComposees(clonedParent, composees = []) {
|
|
let idMappings = [];
|
|
let allComposeesDuplicated = composees.reduce(async (previousPromise, nextComposee) => {
|
|
await previousPromise;
|
|
|
|
let clonedComposee = await this.duplicateObject(nextComposee);
|
|
|
|
if (clonedComposee) {
|
|
idMappings.push({
|
|
newId: clonedComposee.identifier,
|
|
oldId: nextComposee.identifier
|
|
});
|
|
this.composeChild(clonedComposee, clonedParent, clonedComposee !== nextComposee);
|
|
}
|
|
|
|
return;
|
|
}, Promise.resolve());
|
|
|
|
await allComposeesDuplicated;
|
|
|
|
clonedParent = this.rewriteIdentifiers(clonedParent, idMappings);
|
|
this.clones.push(clonedParent);
|
|
|
|
return clonedParent;
|
|
}
|
|
|
|
/**
|
|
* Update identifiers in a cloned object model (or part of
|
|
* a cloned object model) to reflect new identifiers after
|
|
* duplicating.
|
|
* @private
|
|
*/
|
|
rewriteIdentifiers(clonedParent, childIdMappings) {
|
|
for (let { newId, oldId } of childIdMappings) {
|
|
let newIdKeyString = this.openmct.objects.makeKeyString(newId);
|
|
let oldIdKeyString = this.openmct.objects.makeKeyString(oldId);
|
|
|
|
// regex replace keystrings
|
|
clonedParent = JSON.stringify(clonedParent).replace(
|
|
new RegExp(oldIdKeyString, 'g'),
|
|
newIdKeyString
|
|
);
|
|
|
|
// parse reviver to replace identifiers
|
|
clonedParent = JSON.parse(clonedParent, (key, value) => {
|
|
if (
|
|
value !== null &&
|
|
value !== undefined &&
|
|
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;
|
|
}
|
|
});
|
|
}
|
|
|
|
return clonedParent;
|
|
}
|
|
|
|
composeChild(child, parent, setLocation) {
|
|
parent.composition.push(child.identifier);
|
|
|
|
//If a location is not specified, set it.
|
|
if (setLocation && child.location === undefined) {
|
|
let parentKeyString = this.getKeyString(parent);
|
|
child.location = parentKeyString;
|
|
}
|
|
}
|
|
|
|
getTypeDefinition(domainObject, definition) {
|
|
let typeDefinitions = this.openmct.types.get(domainObject.type).definition;
|
|
|
|
return typeDefinitions[definition] || false;
|
|
}
|
|
|
|
cloneObjectModel(domainObject) {
|
|
let clone = JSON.parse(JSON.stringify(domainObject));
|
|
let identifier = {
|
|
key: uuid(),
|
|
namespace: this.namespace // set to NEW parent's namespace
|
|
};
|
|
|
|
if (clone.modified || clone.persisted || clone.location) {
|
|
clone.modified = undefined;
|
|
clone.persisted = undefined;
|
|
clone.location = undefined;
|
|
delete clone.modified;
|
|
delete clone.persisted;
|
|
delete clone.location;
|
|
}
|
|
|
|
if (clone.composition) {
|
|
clone.composition = [];
|
|
}
|
|
|
|
clone.identifier = identifier;
|
|
|
|
return clone;
|
|
}
|
|
|
|
getKeyString(domainObject) {
|
|
return this.openmct.objects.makeKeyString(domainObject.identifier);
|
|
}
|
|
|
|
isCreatable(domainObject) {
|
|
return this.getTypeDefinition(domainObject, 'creatable');
|
|
}
|
|
}
|