mirror of
https://github.com/nasa/openmct.git
synced 2025-04-07 19:34:25 +00:00
Rewrite local storage (#4583)
* Reimplementation of Local Storage provider * Added tests * Remove identifierService mocks from all test specs * Do not persist identifiers in couch * Constant rename * Fix broken test * Clean up mock window functions * Updated copyright notice * Fixed bug in in-memory search indexer
This commit is contained in:
parent
fd0e89ca05
commit
2d64813a4f
@ -1,9 +0,0 @@
|
||||
# Local Storage Plugin
|
||||
Provides persistence of user-created objects in browser Local Storage. Objects persisted in this way will only be
|
||||
available from the browser and machine on which they were persisted. For shared persistence, consider the
|
||||
[Elasticsearch](../elastic/) and [CouchDB](../couch/) persistence plugins.
|
||||
|
||||
## Installation
|
||||
```js
|
||||
openmct.install(openmct.plugins.LocalStorage());
|
||||
```
|
@ -1,61 +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/LocalStoragePersistenceProvider",
|
||||
"./src/LocalStorageIndicator"
|
||||
], function (
|
||||
LocalStoragePersistenceProvider,
|
||||
LocalStorageIndicator
|
||||
) {
|
||||
|
||||
return {
|
||||
name: "platform/persistence/local",
|
||||
definition: {
|
||||
"extensions": {
|
||||
"components": [
|
||||
{
|
||||
"provides": "persistenceService",
|
||||
"type": "provider",
|
||||
"implementation": LocalStoragePersistenceProvider,
|
||||
"depends": [
|
||||
"$window",
|
||||
"$q",
|
||||
"PERSISTENCE_SPACE"
|
||||
]
|
||||
}
|
||||
],
|
||||
"constants": [
|
||||
{
|
||||
"key": "PERSISTENCE_SPACE",
|
||||
"value": "mct"
|
||||
}
|
||||
],
|
||||
"indicators": [
|
||||
{
|
||||
"implementation": LocalStorageIndicator
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
@ -1,61 +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 () {
|
||||
|
||||
var LOCAL_STORAGE_WARNING = [
|
||||
"Using browser local storage for persistence.",
|
||||
"Anything you create or change will only be saved",
|
||||
"in this browser on this machine."
|
||||
].join(' ');
|
||||
|
||||
/**
|
||||
* Indicator for local storage persistence. Provides a minimum
|
||||
* level of feedback indicating that local storage is in use.
|
||||
* @constructor
|
||||
* @memberof platform/persistence/local
|
||||
* @implements {Indicator}
|
||||
*/
|
||||
function LocalStorageIndicator() {
|
||||
}
|
||||
|
||||
LocalStorageIndicator.prototype.getCssClass = function () {
|
||||
return "c-indicator--clickable icon-suitcase s-status-caution";
|
||||
};
|
||||
|
||||
LocalStorageIndicator.prototype.getGlyphClass = function () {
|
||||
return 'caution';
|
||||
};
|
||||
|
||||
LocalStorageIndicator.prototype.getText = function () {
|
||||
return "Off-line storage";
|
||||
};
|
||||
|
||||
LocalStorageIndicator.prototype.getDescription = function () {
|
||||
return LOCAL_STORAGE_WARNING;
|
||||
};
|
||||
|
||||
return LocalStorageIndicator;
|
||||
}
|
||||
);
|
@ -1,97 +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 () {
|
||||
|
||||
/**
|
||||
* The LocalStoragePersistenceProvider reads and writes JSON documents
|
||||
* (more specifically, domain object models) to/from the browser's
|
||||
* local storage.
|
||||
* @memberof platform/persistence/local
|
||||
* @constructor
|
||||
* @implements {PersistenceService}
|
||||
* @param q Angular's $q, for promises
|
||||
* @param $interval Angular's $interval service
|
||||
* @param {string} space the name of the persistence space being served
|
||||
*/
|
||||
function LocalStoragePersistenceProvider($window, $q, space) {
|
||||
this.$q = $q;
|
||||
this.space = space;
|
||||
this.spaces = space ? [space] : [];
|
||||
this.localStorage = $window.localStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value in local storage.
|
||||
* @private
|
||||
*/
|
||||
LocalStoragePersistenceProvider.prototype.setValue = function (key, value) {
|
||||
this.localStorage[key] = JSON.stringify(value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a value from local storage.
|
||||
* @private
|
||||
*/
|
||||
LocalStoragePersistenceProvider.prototype.getValue = function (key) {
|
||||
return this.localStorage[key]
|
||||
? JSON.parse(this.localStorage[key]) : {};
|
||||
};
|
||||
|
||||
LocalStoragePersistenceProvider.prototype.listSpaces = function () {
|
||||
return this.$q.when(this.spaces);
|
||||
};
|
||||
|
||||
LocalStoragePersistenceProvider.prototype.listObjects = function (space) {
|
||||
return this.$q.when(Object.keys(this.getValue(space)));
|
||||
};
|
||||
|
||||
LocalStoragePersistenceProvider.prototype.createObject = function (space, key, value) {
|
||||
var spaceObj = this.getValue(space);
|
||||
spaceObj[key] = value;
|
||||
this.setValue(space, spaceObj);
|
||||
|
||||
return this.$q.when(true);
|
||||
};
|
||||
|
||||
LocalStoragePersistenceProvider.prototype.readObject = function (space, key) {
|
||||
var spaceObj = this.getValue(space);
|
||||
|
||||
return this.$q.when(spaceObj[key]);
|
||||
};
|
||||
|
||||
LocalStoragePersistenceProvider.prototype.deleteObject = function (space, key) {
|
||||
var spaceObj = this.getValue(space);
|
||||
delete spaceObj[key];
|
||||
this.setValue(space, spaceObj);
|
||||
|
||||
return this.$q.when(true);
|
||||
};
|
||||
|
||||
LocalStoragePersistenceProvider.prototype.updateObject =
|
||||
LocalStoragePersistenceProvider.prototype.createObject;
|
||||
|
||||
return LocalStoragePersistenceProvider;
|
||||
}
|
||||
);
|
@ -1,58 +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/LocalStorageIndicator"],
|
||||
function (LocalStorageIndicator) {
|
||||
|
||||
xdescribe("The local storage status indicator", function () {
|
||||
var indicator;
|
||||
|
||||
beforeEach(function () {
|
||||
indicator = new LocalStorageIndicator();
|
||||
});
|
||||
|
||||
it("provides text to display in status area", function () {
|
||||
// Don't particularly care what is there so long
|
||||
// as interface is appropriately implemented.
|
||||
expect(indicator.getText()).toEqual(jasmine.any(String));
|
||||
});
|
||||
|
||||
it("has a database icon", function () {
|
||||
expect(indicator.getCssClass()).toEqual("icon-suitcase s-status-caution");
|
||||
});
|
||||
|
||||
it("has a 'caution' class to draw attention", function () {
|
||||
expect(indicator.getGlyphClass()).toEqual("caution");
|
||||
});
|
||||
|
||||
it("provides a description for a tooltip", function () {
|
||||
// Just want some non-empty string here. Providing a
|
||||
// message here is important but don't want to test wording.
|
||||
var description = indicator.getDescription();
|
||||
expect(description).toEqual(jasmine.any(String));
|
||||
expect(description.length).not.toEqual(0);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -1,113 +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/LocalStoragePersistenceProvider"],
|
||||
function (LocalStoragePersistenceProvider) {
|
||||
|
||||
describe("The local storage persistence provider", function () {
|
||||
var mockQ,
|
||||
testSpace = "testSpace",
|
||||
mockCallback,
|
||||
testLocalStorage,
|
||||
provider;
|
||||
|
||||
function mockPromise(value) {
|
||||
return (value || {}).then ? value : {
|
||||
then: function (callback) {
|
||||
return mockPromise(callback(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
testLocalStorage = {};
|
||||
|
||||
mockQ = jasmine.createSpyObj("$q", ["when", "reject"]);
|
||||
mockCallback = jasmine.createSpy('callback');
|
||||
|
||||
mockQ.when.and.callFake(mockPromise);
|
||||
|
||||
provider = new LocalStoragePersistenceProvider(
|
||||
{ localStorage: testLocalStorage },
|
||||
mockQ,
|
||||
testSpace
|
||||
);
|
||||
});
|
||||
|
||||
it("reports available spaces", function () {
|
||||
provider.listSpaces().then(mockCallback);
|
||||
expect(mockCallback).toHaveBeenCalledWith([testSpace]);
|
||||
});
|
||||
|
||||
it("lists all available documents", function () {
|
||||
provider.listObjects(testSpace).then(mockCallback);
|
||||
expect(mockCallback.calls.mostRecent().args[0]).toEqual([]);
|
||||
provider.createObject(testSpace, 'abc', { a: 42 });
|
||||
provider.listObjects(testSpace).then(mockCallback);
|
||||
expect(mockCallback.calls.mostRecent().args[0]).toEqual(['abc']);
|
||||
});
|
||||
|
||||
it("allows object creation", function () {
|
||||
var model = { someKey: "some value" };
|
||||
provider.createObject(testSpace, "abc", model)
|
||||
.then(mockCallback);
|
||||
expect(JSON.parse(testLocalStorage[testSpace]).abc)
|
||||
.toEqual(model);
|
||||
expect(mockCallback.calls.mostRecent().args[0]).toBeTruthy();
|
||||
});
|
||||
|
||||
it("allows object models to be read back", function () {
|
||||
var model = { someKey: "some other value" };
|
||||
testLocalStorage[testSpace] = JSON.stringify({ abc: model });
|
||||
provider.readObject(testSpace, "abc").then(mockCallback);
|
||||
expect(mockCallback).toHaveBeenCalledWith(model);
|
||||
});
|
||||
|
||||
it("allows object update", function () {
|
||||
var model = { someKey: "some new value" };
|
||||
testLocalStorage[testSpace] = JSON.stringify({
|
||||
abc: { somethingElse: 42 }
|
||||
});
|
||||
provider.updateObject(testSpace, "abc", model)
|
||||
.then(mockCallback);
|
||||
expect(JSON.parse(testLocalStorage[testSpace]).abc)
|
||||
.toEqual(model);
|
||||
});
|
||||
|
||||
it("allows object deletion", function () {
|
||||
testLocalStorage[testSpace] = JSON.stringify({
|
||||
abc: { somethingElse: 42 }
|
||||
});
|
||||
provider.deleteObject(testSpace, "abc").then(mockCallback);
|
||||
expect(testLocalStorage[testSpace].abc)
|
||||
.toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined when objects are not found", function () {
|
||||
provider.readObject("testSpace", "abc").then(mockCallback);
|
||||
expect(mockCallback).toHaveBeenCalledWith(undefined);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -29,22 +29,10 @@ describe('The ActionCollection', () => {
|
||||
let mockApplicableActions;
|
||||
let mockObjectPath;
|
||||
let mockView;
|
||||
let mockIdentifierService;
|
||||
|
||||
beforeEach(() => {
|
||||
openmct = createOpenMct();
|
||||
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
|
||||
mockIdentifierService = jasmine.createSpyObj(
|
||||
'identifierService',
|
||||
['parse']
|
||||
);
|
||||
mockIdentifierService.parse.and.returnValue({
|
||||
getSpace: () => {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
openmct.$injector.get.and.returnValue(mockIdentifierService);
|
||||
mockObjectPath = [
|
||||
{
|
||||
name: 'mock folder',
|
||||
|
@ -187,7 +187,7 @@ class InMemorySearchProvider {
|
||||
*/
|
||||
scheduleForIndexing(identifier) {
|
||||
const keyString = this.openmct.objects.makeKeyString(identifier);
|
||||
const objectProvider = this.openmct.objects.getProvider(identifier.key);
|
||||
const objectProvider = this.openmct.objects.getProvider(identifier);
|
||||
|
||||
if (objectProvider === undefined || objectProvider.search === undefined) {
|
||||
if (!this.indexedIds[keyString] && !this.pendingIndex[keyString]) {
|
||||
|
@ -43,9 +43,6 @@ function ObjectAPI(typeRegistry, openmct) {
|
||||
this.providers = {};
|
||||
this.rootRegistry = new RootRegistry();
|
||||
this.inMemorySearchProvider = new InMemorySearchProvider(openmct);
|
||||
this.injectIdentifierService = function () {
|
||||
this.identifierService = this.openmct.$injector.get("identifierService");
|
||||
};
|
||||
|
||||
this.rootProvider = new RootObjectProvider(this.rootRegistry);
|
||||
this.cache = {};
|
||||
@ -66,33 +63,17 @@ ObjectAPI.prototype.supersecretSetFallbackProvider = function (p) {
|
||||
this.fallbackProvider = p;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ObjectAPI.prototype.getIdentifierService = function () {
|
||||
// Lazily acquire identifier service
|
||||
if (!this.identifierService) {
|
||||
this.injectIdentifierService();
|
||||
}
|
||||
|
||||
return this.identifierService;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the provider for a given identifier.
|
||||
* @private
|
||||
*/
|
||||
ObjectAPI.prototype.getProvider = function (identifier) {
|
||||
//handles the '' vs 'mct' namespace issue
|
||||
const keyString = utils.makeKeyString(identifier);
|
||||
const identifierService = this.getIdentifierService();
|
||||
const namespace = identifierService.parse(keyString).getSpace();
|
||||
|
||||
if (identifier.key === 'ROOT') {
|
||||
return this.rootProvider;
|
||||
}
|
||||
|
||||
return this.providers[namespace] || this.fallbackProvider;
|
||||
return this.providers[identifier.namespace] || this.fallbackProvider;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -5,7 +5,6 @@ describe("The Object API", () => {
|
||||
let objectAPI;
|
||||
let typeRegistry;
|
||||
let openmct = {};
|
||||
let mockIdentifierService;
|
||||
let mockDomainObject;
|
||||
const TEST_NAMESPACE = "test-namespace";
|
||||
const FIFTEEN_MINUTES = 15 * 60 * 1000;
|
||||
@ -15,18 +14,6 @@ describe("The Object API", () => {
|
||||
'get'
|
||||
]);
|
||||
openmct = createOpenMct();
|
||||
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
|
||||
mockIdentifierService = jasmine.createSpyObj(
|
||||
'identifierService',
|
||||
['parse']
|
||||
);
|
||||
mockIdentifierService.parse.and.returnValue({
|
||||
getSpace: () => {
|
||||
return TEST_NAMESPACE;
|
||||
}
|
||||
});
|
||||
|
||||
openmct.$injector.get.and.returnValue(mockIdentifierService);
|
||||
objectAPI = openmct.objects;
|
||||
|
||||
openmct.editor = {};
|
||||
|
@ -9,7 +9,7 @@ describe("Transaction Class", () => {
|
||||
beforeEach(() => {
|
||||
objectAPI = {
|
||||
makeKeyString: (identifier) => utils.makeKeyString(identifier),
|
||||
save: (object) => Promise.resolve(object),
|
||||
save: () => Promise.resolve(true),
|
||||
mutate: (object, prop, value) => {
|
||||
object[prop] = value;
|
||||
|
||||
@ -61,6 +61,7 @@ describe("Transaction Class", () => {
|
||||
|
||||
expect(transaction.dirtyObjects.size).toEqual(3);
|
||||
spyOn(objectAPI, 'save').and.callThrough();
|
||||
|
||||
transaction.commit()
|
||||
.then(success => {
|
||||
expect(transaction.dirtyObjects.size).toEqual(0);
|
||||
|
@ -74,7 +74,6 @@ define([
|
||||
'../platform/identity/bundle',
|
||||
'../platform/persistence/aggregator/bundle',
|
||||
'../platform/persistence/elastic/bundle',
|
||||
'../platform/persistence/local/bundle',
|
||||
'../platform/persistence/queue/bundle',
|
||||
'../platform/policy/bundle',
|
||||
'../platform/representation/bundle',
|
||||
|
@ -810,21 +810,6 @@ describe('the plugin', function () {
|
||||
openmct.telemetry.getMetadata.and.returnValue(testTelemetryObject.telemetry);
|
||||
openmct.telemetry.request.and.returnValue(Promise.resolve([]));
|
||||
|
||||
// mockTransactionService.commit = async () => {};
|
||||
const mockIdentifierService = jasmine.createSpyObj(
|
||||
'identifierService',
|
||||
['parse']
|
||||
);
|
||||
mockIdentifierService.parse.and.returnValue({
|
||||
getSpace: () => {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
|
||||
openmct.$injector.get.withArgs('identifierService').and.returnValue(mockIdentifierService);
|
||||
// .withArgs('transactionService').and.returnValue(mockTransactionService);
|
||||
|
||||
const styleRuleManger = new StyleRuleManager(stylesObject, openmct, null, true);
|
||||
spyOn(styleRuleManger, 'subscribeToConditionSet');
|
||||
openmct.editor.edit();
|
||||
|
100
src/plugins/localStorage/LocalStorageObjectProvider.js
Normal file
100
src/plugins/localStorage/LocalStorageObjectProvider.js
Normal file
@ -0,0 +1,100 @@
|
||||
/*****************************************************************************
|
||||
* 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 LocalStorageObjectProvider {
|
||||
constructor(spaceKey = 'mct') {
|
||||
this.localStorage = window.localStorage;
|
||||
this.spaceKey = spaceKey;
|
||||
this.initializeSpace(spaceKey);
|
||||
}
|
||||
|
||||
get(identifier) {
|
||||
if (this.getSpaceAsObject()[identifier.key] !== undefined) {
|
||||
const persistedModel = this.getSpaceAsObject()[identifier.key];
|
||||
const domainObject = {
|
||||
identifier,
|
||||
...persistedModel
|
||||
};
|
||||
|
||||
return Promise.resolve(domainObject);
|
||||
} else {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
create(object) {
|
||||
return this.persistObject(object);
|
||||
}
|
||||
|
||||
update(object) {
|
||||
return this.persistObject(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
persistObject(domainObject) {
|
||||
let space = this.getSpaceAsObject();
|
||||
space[domainObject.identifier.key] = domainObject;
|
||||
|
||||
this.persistSpace(space);
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
persistSpace(space) {
|
||||
this.localStorage[this.spaceKey] = JSON.stringify(space);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
getSpace() {
|
||||
return this.localStorage[this.spaceKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
getSpaceAsObject() {
|
||||
return JSON.parse(this.getSpace());
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
initializeSpace() {
|
||||
if (this.isEmpty()) {
|
||||
this.localStorage[this.spaceKey] = JSON.stringify({});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
isEmpty() {
|
||||
return this.getSpace() === undefined;
|
||||
}
|
||||
}
|
29
src/plugins/localStorage/plugin.js
Normal file
29
src/plugins/localStorage/plugin.js
Normal file
@ -0,0 +1,29 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web 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 LocalStorageObjectProvider from './LocalStorageObjectProvider';
|
||||
|
||||
export default function (namespace = '', storageSpace = 'mct') {
|
||||
return function (openmct) {
|
||||
openmct.objects.addProvider(namespace, new LocalStorageObjectProvider(storageSpace));
|
||||
};
|
||||
}
|
96
src/plugins/localStorage/pluginSpec.js
Normal file
96
src/plugins/localStorage/pluginSpec.js
Normal file
@ -0,0 +1,96 @@
|
||||
/* eslint-disable no-invalid-this */
|
||||
/*****************************************************************************
|
||||
* 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';
|
||||
|
||||
describe("The local storage plugin", () => {
|
||||
let space;
|
||||
let openmct;
|
||||
|
||||
beforeEach(() => {
|
||||
space = `test-${Date.now()}`;
|
||||
openmct = createOpenMct();
|
||||
|
||||
openmct.install(openmct.plugins.LocalStorage('', space));
|
||||
|
||||
});
|
||||
|
||||
it('initializes localstorage if not already initialized', () => {
|
||||
const ls = getLocalStorage();
|
||||
expect(ls[space]).toBeDefined();
|
||||
});
|
||||
|
||||
it('successfully persists an object to localstorage', async () => {
|
||||
const domainObject = {
|
||||
identifier: {
|
||||
namespace: '',
|
||||
key: 'test-key'
|
||||
},
|
||||
name: 'A test object'
|
||||
};
|
||||
let spaceAsObject = getSpaceAsObject();
|
||||
expect(spaceAsObject['test-key']).not.toBeDefined();
|
||||
|
||||
await openmct.objects.save(domainObject);
|
||||
|
||||
spaceAsObject = getSpaceAsObject();
|
||||
expect(spaceAsObject['test-key']).toBeDefined();
|
||||
});
|
||||
|
||||
it('successfully retrieves an object from localstorage', async () => {
|
||||
const domainObject = {
|
||||
identifier: {
|
||||
namespace: '',
|
||||
key: 'test-key'
|
||||
},
|
||||
name: 'A test object',
|
||||
anotherProperty: Date.now()
|
||||
};
|
||||
await openmct.objects.save(domainObject);
|
||||
|
||||
let testObject = await openmct.objects.get(domainObject.identifier);
|
||||
|
||||
expect(testObject.name).toEqual(domainObject.name);
|
||||
expect(testObject.anotherProperty).toEqual(domainObject.anotherProperty);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetApplicationState(openmct);
|
||||
resetLocalStorage();
|
||||
});
|
||||
|
||||
function resetLocalStorage() {
|
||||
delete window.localStorage[space];
|
||||
}
|
||||
|
||||
function getLocalStorage() {
|
||||
return window.localStorage;
|
||||
}
|
||||
|
||||
function getSpaceAsObject() {
|
||||
return JSON.parse(getLocalStorage()[space]);
|
||||
}
|
||||
});
|
@ -95,23 +95,10 @@ const selectedPage = {
|
||||
};
|
||||
|
||||
let openmct;
|
||||
let mockIdentifierService;
|
||||
|
||||
describe('Notebook Entries:', () => {
|
||||
beforeEach(() => {
|
||||
openmct = createOpenMct();
|
||||
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
|
||||
mockIdentifierService = jasmine.createSpyObj(
|
||||
'identifierService',
|
||||
['parse']
|
||||
);
|
||||
mockIdentifierService.parse.and.returnValue({
|
||||
getSpace: () => {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
openmct.$injector.get.and.returnValue(mockIdentifierService);
|
||||
openmct.types.addType('notebook', {
|
||||
creatable: true
|
||||
});
|
||||
|
@ -64,23 +64,11 @@ const notebookStorage = {
|
||||
};
|
||||
|
||||
let openmct;
|
||||
let mockIdentifierService;
|
||||
|
||||
describe('Notebook Storage:', () => {
|
||||
beforeEach(() => {
|
||||
openmct = createOpenMct();
|
||||
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
|
||||
mockIdentifierService = jasmine.createSpyObj(
|
||||
'identifierService',
|
||||
['parse']
|
||||
);
|
||||
mockIdentifierService.parse.and.returnValue({
|
||||
getSpace: () => {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
openmct.$injector.get.and.returnValue(mockIdentifierService);
|
||||
window.localStorage.setItem('notebook-storage', null);
|
||||
openmct.objects.addProvider('', jasmine.createSpyObj('mockNotebookProvider', [
|
||||
'create',
|
||||
|
@ -84,17 +84,17 @@ class CouchObjectProvider {
|
||||
this.changesFeedSharedWorkerConnectionId = event.data.connectionId;
|
||||
} else {
|
||||
let objectChanges = event.data.objectChanges;
|
||||
objectChanges.identifier = {
|
||||
const objectIdentifier = {
|
||||
namespace: this.namespace,
|
||||
key: objectChanges.id
|
||||
};
|
||||
let keyString = this.openmct.objects.makeKeyString(objectChanges.identifier);
|
||||
let keyString = this.openmct.objects.makeKeyString(objectIdentifier);
|
||||
//TODO: Optimize this so that we don't 'get' the object if it's current revision (from this.objectQueue) is the same as the one we already have.
|
||||
let observersForObject = this.observers[keyString];
|
||||
|
||||
if (observersForObject) {
|
||||
observersForObject.forEach(async (observer) => {
|
||||
const updatedObject = await this.get(objectChanges.identifier);
|
||||
const updatedObject = await this.get(objectIdentifier);
|
||||
if (this.isSynchronizedObject(updatedObject)) {
|
||||
observer(updatedObject);
|
||||
}
|
||||
@ -179,11 +179,8 @@ class CouchObjectProvider {
|
||||
getModel(response) {
|
||||
if (response && response.model) {
|
||||
let key = response[ID];
|
||||
let object = response.model;
|
||||
object.identifier = {
|
||||
namespace: this.namespace,
|
||||
key: key
|
||||
};
|
||||
let object = this.fromPersistedModel(response.model, key);
|
||||
|
||||
if (!this.objectQueue[key]) {
|
||||
this.objectQueue[key] = new CouchObjectQueue(undefined, response[REV]);
|
||||
}
|
||||
@ -445,17 +442,17 @@ class CouchObjectProvider {
|
||||
}
|
||||
|
||||
onEventMessage(event) {
|
||||
const object = JSON.parse(event.data);
|
||||
object.identifier = {
|
||||
const eventData = JSON.parse(event.data);
|
||||
const identifier = {
|
||||
namespace: this.namespace,
|
||||
key: object.id
|
||||
key: eventData.id
|
||||
};
|
||||
let keyString = this.openmct.objects.makeKeyString(object.identifier);
|
||||
const keyString = this.openmct.objects.makeKeyString(identifier);
|
||||
let observersForObject = this.observers[keyString];
|
||||
|
||||
if (observersForObject) {
|
||||
observersForObject.forEach(async (observer) => {
|
||||
const updatedObject = await this.get(object.identifier);
|
||||
const updatedObject = await this.get(identifier);
|
||||
if (this.isSynchronizedObject(updatedObject)) {
|
||||
observer(updatedObject);
|
||||
}
|
||||
@ -520,7 +517,9 @@ class CouchObjectProvider {
|
||||
create(model) {
|
||||
let intermediateResponse = this.getIntermediateResponse();
|
||||
const key = model.identifier.key;
|
||||
model = this.toPersistableModel(model);
|
||||
this.enqueueObject(key, model, intermediateResponse);
|
||||
|
||||
if (!this.objectQueue[key].pending) {
|
||||
this.objectQueue[key].pending = true;
|
||||
const queued = this.objectQueue[key].dequeue();
|
||||
@ -557,11 +556,31 @@ class CouchObjectProvider {
|
||||
update(model) {
|
||||
let intermediateResponse = this.getIntermediateResponse();
|
||||
const key = model.identifier.key;
|
||||
model = this.toPersistableModel(model);
|
||||
|
||||
this.enqueueObject(key, model, intermediateResponse);
|
||||
this.updateQueued(key);
|
||||
|
||||
return intermediateResponse.promise;
|
||||
}
|
||||
|
||||
toPersistableModel(model) {
|
||||
//First make a copy so we are not mutating the provided model.
|
||||
const persistableModel = JSON.parse(JSON.stringify(model));
|
||||
//Delete the identifier. Couch manages namespaces dynamically.
|
||||
delete persistableModel.identifier;
|
||||
|
||||
return persistableModel;
|
||||
}
|
||||
|
||||
fromPersistedModel(model, key) {
|
||||
model.identifier = {
|
||||
namespace: this.namespace,
|
||||
key
|
||||
};
|
||||
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
||||
CouchObjectProvider.HTTP_CONFLICT = 409;
|
||||
|
@ -22,11 +22,15 @@
|
||||
|
||||
import CouchObjectProvider from './CouchObjectProvider';
|
||||
const NAMESPACE = '';
|
||||
const PERSISTENCE_SPACE = 'mct';
|
||||
const LEGACY_SPACE = 'mct';
|
||||
|
||||
export default function CouchPlugin(options) {
|
||||
return function install(openmct) {
|
||||
install.couchProvider = new CouchObjectProvider(openmct, options, NAMESPACE);
|
||||
openmct.objects.addProvider(PERSISTENCE_SPACE, install.couchProvider);
|
||||
|
||||
// Unfortunately, for historical reasons, Couch DB produces objects with a mix of namepaces (alternately "mct", and "")
|
||||
// Installing the same provider under both namespaces means that it can respond to object gets for both namespaces.
|
||||
openmct.objects.addProvider(LEGACY_SPACE, install.couchProvider);
|
||||
openmct.objects.addProvider(NAMESPACE, install.couchProvider);
|
||||
};
|
||||
}
|
||||
|
@ -129,8 +129,9 @@ describe('the plugin', () => {
|
||||
|
||||
it('works without Shared Workers', async () => {
|
||||
let sharedWorkerCallback;
|
||||
const restoreSharedWorker = window.SharedWorker;
|
||||
const cachedSharedWorker = window.SharedWorker;
|
||||
window.SharedWorker = undefined;
|
||||
|
||||
const mockEventSource = {
|
||||
addEventListener: (topic, addedListener) => {
|
||||
sharedWorkerCallback = addedListener;
|
||||
@ -139,6 +140,8 @@ describe('the plugin', () => {
|
||||
sharedWorkerCallback = null;
|
||||
}
|
||||
};
|
||||
const cachedEventSource = window.EventSource;
|
||||
|
||||
window.EventSource = function (url) {
|
||||
return mockEventSource;
|
||||
};
|
||||
@ -163,17 +166,21 @@ describe('the plugin', () => {
|
||||
expect(result).toBeTrue();
|
||||
expect(provider.create).toHaveBeenCalled();
|
||||
expect(provider.startSharedWorker).not.toHaveBeenCalled();
|
||||
|
||||
//Set modified timestamp it detects a change and persists the updated model.
|
||||
mockDomainObject.modified = mockDomainObject.persisted + 1;
|
||||
const updatedResult = await openmct.objects.save(mockDomainObject);
|
||||
openmct.objects.observe(mockDomainObject, '*', (updatedObject) => {
|
||||
});
|
||||
|
||||
expect(updatedResult).toBeTrue();
|
||||
expect(provider.update).toHaveBeenCalled();
|
||||
expect(provider.fetchChanges).toHaveBeenCalled();
|
||||
sharedWorkerCallback(fakeUpdateEvent);
|
||||
expect(provider.onEventMessage).toHaveBeenCalled();
|
||||
window.SharedWorker = restoreSharedWorker;
|
||||
|
||||
window.SharedWorker = cachedSharedWorker;
|
||||
window.EventSource = cachedEventSource;
|
||||
});
|
||||
});
|
||||
describe('batches requests', () => {
|
||||
|
@ -73,7 +73,8 @@ define([
|
||||
'./hyperlink/plugin',
|
||||
'./clock/plugin',
|
||||
'./DeviceClassifier/plugin',
|
||||
'./timer/plugin'
|
||||
'./timer/plugin',
|
||||
'./localStorage/plugin'
|
||||
], function (
|
||||
_,
|
||||
UTCTimeSystem,
|
||||
@ -127,10 +128,10 @@ define([
|
||||
Hyperlink,
|
||||
Clock,
|
||||
DeviceClassifier,
|
||||
Timer
|
||||
Timer,
|
||||
LocalStorage
|
||||
) {
|
||||
const bundleMap = {
|
||||
LocalStorage: 'platform/persistence/local',
|
||||
Elasticsearch: 'platform/persistence/elastic'
|
||||
};
|
||||
|
||||
@ -235,6 +236,7 @@ define([
|
||||
plugins.Clock = Clock.default;
|
||||
plugins.Timer = Timer.default;
|
||||
plugins.DeviceClassifier = DeviceClassifier.default;
|
||||
plugins.LocalStorage = LocalStorage.default;
|
||||
|
||||
return plugins;
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user