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:
Andrew Henry 2021-12-17 12:14:35 -08:00 committed by GitHub
parent fd0e89ca05
commit 2d64813a4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 281 additions and 507 deletions

View File

@ -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());
```

View File

@ -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
}
]
}
}
};
});

View File

@ -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;
}
);

View File

@ -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;
}
);

View File

@ -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);
});
});
}
);

View File

@ -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);
});
});
}
);

View File

@ -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',

View File

@ -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]) {

View File

@ -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;
};
/**

View File

@ -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 = {};

View File

@ -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);

View File

@ -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',

View File

@ -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();

View 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;
}
}

View 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));
};
}

View 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]);
}
});

View File

@ -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
});

View File

@ -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',

View File

@ -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;

View File

@ -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);
};
}

View File

@ -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', () => {

View File

@ -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;
});