[MyItems Plugin] Migrate/remove legacy MyItems code into new plugin (#4107)

* removed legacy my items, created my items plugin, moved relevant code to new plugin
* added object api method for checking if a domainObject is a missing object

Co-authored-by: Andrew Henry <akhenry@gmail.com>
This commit is contained in:
Jamie V 2021-11-05 11:17:49 -07:00 committed by GitHub
parent 1680c3cc1b
commit b8fabb7e73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 145 additions and 318 deletions

View File

@ -28,7 +28,6 @@ define([
"./src/models/ModelCacheService",
"./src/models/PersistedModelProvider",
"./src/models/CachingModelDecorator",
"./src/models/MissingModelDecorator",
"./src/types/TypeProvider",
"./src/actions/ActionProvider",
"./src/actions/ActionAggregator",
@ -57,7 +56,6 @@ define([
ModelCacheService,
PersistedModelProvider,
CachingModelDecorator,
MissingModelDecorator,
TypeProvider,
ActionProvider,
ActionAggregator,
@ -148,12 +146,6 @@ define([
"cacheService"
]
},
{
"provides": "modelService",
"type": "decorator",
"priority": "fallback",
"implementation": MissingModelDecorator
},
{
"provides": "typeService",
"type": "provider",

View File

@ -1,62 +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.
*****************************************************************************/
define(
[],
function () {
/**
* Adds placeholder domain object models for any models which
* fail to load from the underlying model service.
* @constructor
* @memberof platform/core
* @param {ModelService} modelService this service to decorate
* @implements {ModelService}
*/
function MissingModelDecorator(modelService) {
this.modelService = modelService;
}
function missingModel(id) {
return {
type: "unknown",
name: "Missing: " + id
};
}
MissingModelDecorator.prototype.getModels = function (ids) {
function addMissingModels(models) {
var result = {};
ids.forEach(function (id) {
result[id] = models[id] || missingModel(id);
});
return result;
}
return this.modelService.getModels(ids).then(addMissingModels);
};
return MissingModelDecorator;
}
);

View File

@ -1,86 +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.
*****************************************************************************/
define(
["../../src/models/MissingModelDecorator"],
function (MissingModelDecorator) {
describe("The missing model decorator", function () {
var mockModelService,
testModels,
decorator;
function asPromise(value) {
return (value || {}).then ? value : {
then: function (callback) {
return asPromise(callback(value));
}
};
}
beforeEach(function () {
mockModelService = jasmine.createSpyObj(
"modelService",
["getModels"]
);
testModels = {
testId: { someKey: "some value" }
};
mockModelService.getModels.and.returnValue(asPromise(testModels));
decorator = new MissingModelDecorator(mockModelService);
});
it("delegates to the wrapped model service", function () {
decorator.getModels(['a', 'b', 'c']);
expect(mockModelService.getModels)
.toHaveBeenCalledWith(['a', 'b', 'c']);
});
it("provides models for any IDs which are missing", function () {
var models;
decorator.getModels(['testId', 'otherId'])
.then(function (m) {
models = m;
});
expect(models.otherId).toBeDefined();
});
it("does not overwrite existing models", function () {
var models;
decorator.getModels(['testId', 'otherId'])
.then(function (m) {
models = m;
});
expect(models.testId).toEqual({ someKey: "some value" });
});
it("does not modify the wrapped service's response", function () {
decorator.getModels(['testId', 'otherId']);
expect(testModels.otherId).toBeUndefined();
});
});
}
);

View File

@ -25,7 +25,6 @@ define([
'./capabilities/AdapterCapability',
'./directives/MCTView',
'./services/Instantiate',
'./services/MissingModelCompatibilityDecorator',
'./capabilities/APICapabilityDecorator',
'./policies/AdaptedViewPolicy',
'./runs/AlternateCompositionInitializer',
@ -41,7 +40,6 @@ define([
AdapterCapability,
MCTView,
Instantiate,
MissingModelCompatibilityDecorator,
APICapabilityDecorator,
AdaptedViewPolicy,
AlternateCompositionInitializer,
@ -97,12 +95,6 @@ define([
implementation: ActionDialogDecorator,
depends: ["openmct"]
},
{
type: "decorator",
provides: "modelService",
implementation: MissingModelCompatibilityDecorator,
depends: ["openmct"]
},
{
provides: "objectService",
type: "decorator",

View File

@ -133,9 +133,13 @@ define([
return this.objectService.getObjects([keyString])
.then(function (results) {
let model = results[keyString].getModel();
if (results[keyString]) {
let model = results[keyString].getModel();
return utils.toNewFormat(model, key);
return utils.toNewFormat(model, key);
}
return;
});
};

View File

@ -1,91 +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.
*****************************************************************************/
define([
'objectUtils'
], function (
objectUtils
) {
/**
* Compatibility decorator for New API.
*
* When the model service returns no results, this attempts to load
* the model from the new Object API and returns that instead. In order
* to prevent infinite recursion, this only tries to fetch from the API
* a single time.
*
*/
function MissingModelCompatibilityDecorator(api, modelService) {
this.api = api;
this.modelService = modelService;
this.apiFetching = {}; // to prevent loops, if we have already
}
/**
* Fetch a set of ids from the public api and return a promise for their
* models. If a model is requested twice, respond with a missing result.
*/
MissingModelCompatibilityDecorator.prototype.apiFetch = function (ids) {
const results = {};
const promises = ids.map(function (id) {
if (this.apiFetching[id]) {
return Promise.resolve();
}
this.apiFetching[id] = true;
return this.api.objects.get(objectUtils.parseKeyString(id))
.then(function (newDO) {
results[id] = objectUtils.toOldFormat(newDO);
});
}, this);
return Promise.all(promises).then(function () {
return results;
});
};
/**
* Return a promise for model results based on provided ids. Will attempt
* to fetch any missing results from the object api.
*/
MissingModelCompatibilityDecorator.prototype.getModels = function (ids) {
return this.modelService.getModels(ids)
.then(function (models) {
const missingIds = ids.filter(function (id) {
return !models[id];
});
if (!missingIds.length) {
return models;
}
//Temporary fix for missing models - don't retry using this.apiFetch
return models;
}.bind(this));
};
return MissingModelCompatibilityDecorator;
});

View File

@ -302,6 +302,13 @@ ObjectAPI.prototype.isPersistable = function (idOrKeyString) {
&& provider.update !== undefined;
};
ObjectAPI.prototype.isMissing = function (domainObject) {
let identifier = utils.makeKeyString(domainObject.identifier);
let missingName = 'Missing: ' + identifier;
return domainObject.name === missingName;
};
/**
* Save this domain object in its current state. EXPERIMENTAL
*

View File

@ -78,7 +78,6 @@ define([
'../platform/execution/bundle',
'../platform/exporters/bundle',
'../platform/features/clock/bundle',
'../platform/features/my-items/bundle',
'../platform/features/static-markup/bundle',
'../platform/forms/bundle',
'../platform/framework/bundle',

View File

@ -23,7 +23,7 @@
export default function MissingObjectInterceptor(openmct) {
openmct.objects.addGetInterceptor({
appliesTo: (identifier, domainObject) => {
return identifier.key !== 'mine';
return true;
},
invoke: (identifier, object) => {
if (object === undefined) {

View File

@ -1,9 +1,7 @@
import missingObjectInterceptor from "./missingObjectInterceptor";
import myItemsInterceptor from "./myItemsInterceptor";
export default function plugin() {
return function install(openmct) {
myItemsInterceptor(openmct);
missingObjectInterceptor(openmct);
};
}

View File

@ -75,22 +75,5 @@ describe('the plugin', function () {
});
});
it('returns the My items object if not found', () => {
const identifier = {
namespace: TEST_NAMESPACE,
key: 'mine'
};
return openmct.objects.get(identifier).then((testObject) => {
expect(testObject).toEqual({
identifier,
"name": "My Items",
"type": "folder",
"composition": [],
"location": "ROOT"
});
});
});
});
});

View File

@ -0,0 +1,8 @@
export const MY_ITEMS_KEY = 'mine';
export function createMyItemsIdentifier(namespace = '') {
return {
key: MY_ITEMS_KEY,
namespace
};
}

View File

@ -20,24 +20,30 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
export default function MyItemsInterceptor(openmct) {
import { MY_ITEMS_KEY } from "./createMyItemsIdentifier";
openmct.objects.addGetInterceptor({
appliesTo: (identifier, domainObject) => {
return identifier.key === 'mine';
function myItemsInterceptor(identifierObject, openmct) {
const myItemsModel = {
identifier: identifierObject,
"name": "My Items",
"type": "folder",
"composition": [],
"location": "ROOT"
};
return {
appliesTo: (identifier) => {
return identifier.key === MY_ITEMS_KEY;
},
invoke: (identifier, object) => {
if (object === undefined) {
return {
identifier,
"name": "My Items",
"type": "folder",
"composition": [],
"location": "ROOT"
};
if (openmct.objects.isMissing(object)) {
return myItemsModel;
}
return object;
}
});
};
}
export default myItemsInterceptor;

View File

@ -20,30 +20,14 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
return {
name: "platform/features/my-items",
definition: {
"name": "My Items",
"description": "Defines a root named My Items",
"extensions": {
"roots": [
{
"id": "mine"
}
],
"models": [
{
"id": "mine",
"model": {
"name": "My Items",
"type": "folder",
"composition": [],
"location": "ROOT"
}
}
]
}
}
import { createMyItemsIdentifier } from "./createMyItemsIdentifier";
import myItemsInterceptor from "./myItemsInterceptor";
export default function MyItemsPlugin(namespace = '') {
return function install(openmct) {
const identifier = createMyItemsIdentifier(namespace);
openmct.objects.addGetInterceptor(myItemsInterceptor(identifier, openmct));
openmct.objects.addRoot(identifier);
};
});
}

View File

@ -0,0 +1,90 @@
/*****************************************************************************
* 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 {
createOpenMct,
resetApplicationState
} from 'utils/testing';
import {
createMyItemsIdentifier,
MY_ITEMS_KEY
} from './createMyItemsIdentifier';
const MISSING_NAME = `Missing: ${MY_ITEMS_KEY}`;
const myItemsIdentifier = createMyItemsIdentifier();
describe("the plugin", () => {
let openmct;
let missingObj = {
identifier: myItemsIdentifier,
type: 'unknown',
name: MISSING_NAME
};
beforeEach((done) => {
openmct = createOpenMct();
openmct.install(openmct.plugins.MyItems());
openmct.on('start', done);
openmct.startHeadless();
});
afterEach(() => {
return resetApplicationState(openmct);
});
it('when installed, adds "My Items" to the root', async () => {
const root = await openmct.objects.get('ROOT');
const rootCompostionCollection = openmct.composition.get(root);
const rootCompostion = await rootCompostionCollection.load();
let myItems = rootCompostion.filter((domainObject) => {
return openmct.objects.areIdsEqual(domainObject.identifier, myItemsIdentifier);
})[0];
expect(myItems).toBeDefined();
});
describe('adds an interceptor that returns a "My Items" model for', () => {
let myItemsMissing;
let mockMissingProvider;
let activeProvider;
beforeEach(async () => {
mockMissingProvider = {
get: () => Promise.resolve(missingObj)
};
activeProvider = mockMissingProvider;
spyOn(openmct.objects, 'getProvider').and.returnValue(activeProvider);
myItemsMissing = await openmct.objects.get(myItemsIdentifier);
});
it('missing objects', () => {
let idsMatchMissing = openmct.objects.areIdsEqual(myItemsMissing.identifier, myItemsIdentifier);
expect(myItemsMissing).toBeDefined();
expect(idsMatchMissing).toBeTrue();
});
});
});

View File

@ -26,6 +26,7 @@ define([
'./remoteClock/plugin',
'./localTimeSystem/plugin',
'./ISOTimeFormat/plugin',
'./myItems/plugin',
'../../example/generator/plugin',
'./autoflow/AutoflowTabularPlugin',
'./timeConductor/plugin',
@ -78,6 +79,7 @@ define([
RemoteClock,
LocalTimeSystem,
ISOTimeFormat,
MyItems,
GeneratorPlugin,
AutoflowPlugin,
TimeConductorPlugin,
@ -127,7 +129,6 @@ define([
) {
const bundleMap = {
LocalStorage: 'platform/persistence/local',
MyItems: 'platform/features/my-items',
Elasticsearch: 'platform/persistence/elastic'
};
@ -143,6 +144,8 @@ define([
plugins.LocalTimeSystem = LocalTimeSystem;
plugins.RemoteClock = RemoteClock.default;
plugins.MyItems = MyItems.default;
plugins.ImportExport = ImportExport;
plugins.StaticRootPlugin = StaticRootPlugin;