Skip to content

Commit

Permalink
Merge branch 'master' into fix-couchdbsearchfolder-and-allow-clocky-r…
Browse files Browse the repository at this point in the history
…eports
  • Loading branch information
jvigliotta authored Jul 12, 2023
2 parents e8287f6 + ac22beb commit 682a452
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 3 deletions.
48 changes: 45 additions & 3 deletions src/plugins/persistence/couch/CouchObjectProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import CouchDocument from './CouchDocument';
import CouchObjectQueue from './CouchObjectQueue';
import { PENDING, CONNECTED, DISCONNECTED, UNKNOWN } from './CouchStatusIndicator';
import { isNotebookOrAnnotationType } from '../../notebook/notebook-constants.js';
import _ from 'lodash';

const REV = '_rev';
const ID = '_id';
Expand All @@ -42,6 +43,8 @@ class CouchObjectProvider {
this.batchIds = [];
this.onEventMessage = this.onEventMessage.bind(this);
this.onEventError = this.onEventError.bind(this);
this.flushPersistenceQueue = _.debounce(this.flushPersistenceQueue.bind(this));
this.persistenceQueue = [];
}

/**
Expand Down Expand Up @@ -668,9 +671,12 @@ class CouchObjectProvider {
if (!this.objectQueue[key].pending) {
this.objectQueue[key].pending = true;
const queued = this.objectQueue[key].dequeue();
let document = new CouchDocument(key, queued.model);
document.metadata.created = Date.now();
this.request(key, 'PUT', document)
let couchDocument = new CouchDocument(key, queued.model);
couchDocument.metadata.created = Date.now();
this.#enqueueForPersistence({
key,
document: couchDocument
})
.then((response) => {
this.#checkResponse(response, queued.intermediateResponse, key);
})
Expand All @@ -683,6 +689,42 @@ class CouchObjectProvider {
return intermediateResponse.promise;
}

#enqueueForPersistence({ key, document }) {
return new Promise((resolve, reject) => {
this.persistenceQueue.push({
key,
document,
resolve,
reject
});
this.flushPersistenceQueue();
});
}

async flushPersistenceQueue() {
if (this.persistenceQueue.length > 1) {
const batch = {
docs: this.persistenceQueue.map((queued) => queued.document)
};
const response = await this.request('_bulk_docs', 'POST', batch);
response.forEach((responseMetadatum) => {
const queued = this.persistenceQueue.find(
(queuedMetadatum) => queuedMetadatum.key === responseMetadatum.id
);
if (responseMetadatum.ok) {
queued.resolve(responseMetadatum);
} else {
queued.reject(responseMetadatum);
}
});
} else if (this.persistenceQueue.length === 1) {
const { key, document, resolve, reject } = this.persistenceQueue[0];

this.request(key, 'PUT', document).then(resolve).catch(reject);
}
this.persistenceQueue = [];
}

/**
* @private
*/
Expand Down
129 changes: 129 additions & 0 deletions src/plugins/persistence/couch/pluginSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,135 @@ describe('the plugin', () => {
expect(requestMethod).toEqual('GET');
});
});
describe('batches persistence', () => {
let successfulMockPromise;
let partialFailureMockPromise;
let objectsToPersist;

beforeEach(() => {
successfulMockPromise = Promise.resolve({
json: () => {
return [
{
id: 'object-1',
ok: true
},
{
id: 'object-2',
ok: true
},
{
id: 'object-3',
ok: true
}
];
}
});

partialFailureMockPromise = Promise.resolve({
json: () => {
return [
{
id: 'object-1',
ok: true
},
{
id: 'object-2',
ok: false
},
{
id: 'object-3',
ok: true
}
];
}
});

objectsToPersist = [
{
identifier: {
namespace: '',
key: 'object-1'
},
name: 'object-1',
type: 'folder',
modified: 0
},
{
identifier: {
namespace: '',
key: 'object-2'
},
name: 'object-2',
type: 'folder',
modified: 0
},
{
identifier: {
namespace: '',
key: 'object-3'
},
name: 'object-3',
type: 'folder',
modified: 0
}
];
});
it('for multiple simultaneous successful saves', async () => {
fetch.and.returnValue(successfulMockPromise);

await Promise.all(
objectsToPersist.map((objectToPersist) => openmct.objects.save(objectToPersist))
);

const requestUrl = fetch.calls.mostRecent().args[0];
const requestMethod = fetch.calls.mostRecent().args[1].method;
const requestBody = JSON.parse(fetch.calls.mostRecent().args[1].body);

expect(fetch).toHaveBeenCalledTimes(1);
expect(requestUrl.includes('_bulk_docs')).toBeTrue();
expect(requestMethod).toEqual('POST');
expect(
objectsToPersist.every(
(object, index) => object.identifier.key === requestBody.docs[index]._id
)
).toBeTrue();
});
it('for multiple simultaneous saves with partial failure', async () => {
fetch.and.returnValue(partialFailureMockPromise);

let saveResults = await Promise.all(
objectsToPersist.map((objectToPersist) =>
openmct.objects
.save(objectToPersist)
.then(() => true)
.catch(() => false)
)
);
expect(saveResults[0]).toBeTrue();
expect(saveResults[1]).toBeFalse();
expect(saveResults[2]).toBeTrue();
});
it('except for a single save', async () => {
fetch.and.returnValue({
json: () => {
return {
id: 'object-1',
ok: true
};
}
});
await openmct.objects.save(objectsToPersist[0]);

const requestUrl = fetch.calls.mostRecent().args[0];
const requestMethod = fetch.calls.mostRecent().args[1].method;

expect(fetch).toHaveBeenCalledTimes(1);
expect(requestUrl.includes('_bulk_docs')).toBeFalse();
expect(requestUrl.endsWith('object-1')).toBeTrue();
expect(requestMethod).toEqual('PUT');
});
});
describe('implements server-side search', () => {
let mockPromise;
beforeEach(() => {
Expand Down

0 comments on commit 682a452

Please sign in to comment.