From d5a53cf9e4e07e2f7fa8672e90e565fd6f12cff2 Mon Sep 17 00:00:00 2001 From: Wojciech Trocki Date: Wed, 30 Aug 2017 19:12:01 +0100 Subject: [PATCH] DataManger extensions --- client/datasync-client/package.json | 4 +- client/datasync-client/src/DataManager.ts | 191 +++++++++++++----- .../datasync-client/test/DataManagerTest.ts | 69 ++++++- 3 files changed, 201 insertions(+), 63 deletions(-) diff --git a/client/datasync-client/package.json b/client/datasync-client/package.json index 435746a..660b85c 100644 --- a/client/datasync-client/package.json +++ b/client/datasync-client/package.json @@ -30,7 +30,7 @@ "check-coverage": true, "lines": 75, "functions": 60, - "branches": 50 + "branches": 45 }, "devDependencies": { "@types/chai": "^4.0.3", @@ -38,6 +38,7 @@ "@types/mocha": "^2.2.41", "@types/proxyquire": "^1.3.27", "@types/sinon": "^2.3.3", + "@types/bluebird": "^3.5.8", "assert": "^1.4.1", "browserify": "^14.4.0", "browserify-shim": "^3.8.14", @@ -54,6 +55,7 @@ }, "dependencies": { "@raincatcher/logger": "0.0.1", + "bluebird": "^3.5.0", "fh-sync-js": "^1.0.3", "lodash": "^4.17.4" } diff --git a/client/datasync-client/src/DataManager.ts b/client/datasync-client/src/DataManager.ts index 1d4e43d..280918c 100644 --- a/client/datasync-client/src/DataManager.ts +++ b/client/datasync-client/src/DataManager.ts @@ -1,4 +1,5 @@ import { getLogger } from '@raincatcher/logger'; +import * as Bluebird from 'bluebird'; import * as syncApi from 'fh-sync-js'; import * as _ from 'lodash'; @@ -18,14 +19,18 @@ export class DataManager { /** * Listing all data for this data set. + * + * @returns Promise (bluebird) */ - public list(callback: (err?: Error, results?: any) => void) { + public list(): Bluebird { const self = this; - syncApi.doList(self.datasetId, function(syncDataSetList: any) { - const dataSetData = self.extractDataFromSyncResponse(syncDataSetList); - callback(undefined, dataSetData); - }, function handleSyncListError(syncErrorCode: string, syncErrorMessage: string) { - callback(new Error(self.formatSyncErrorMessage(syncErrorCode, syncErrorMessage))); + return new Bluebird(function(resolve, reject) { + syncApi.doList(self.datasetId, function(syncDataSetList: any) { + const dataSetData = self.extractDataFromSyncResponse(syncDataSetList); + resolve(dataSetData); + }, function handleSyncListError(syncErrorCode: string, syncErrorMessage: string) { + reject(new Error(self.formatSyncErrorMessage(syncErrorCode, syncErrorMessage))); + }); }); } @@ -33,62 +38,73 @@ export class DataManager { * Adding a new item to the data set. * * @param itemToCreate - The item to add to the data set. + * + * @returns Promise (bluebird) */ - public create(itemToCreate: any, callback: (err?: Error, results?: any) => void) { + public create(itemToCreate: any): Bluebird { const self = this; - function handleCreateSuccess(syncDataCreateResult: any) { - itemToCreate.uid = syncDataCreateResult.uid; - callback(undefined, itemToCreate); - } - function handleCreateError(errorCode: string, syncErrorMessage: string) { - callback(new Error(self.formatSyncErrorMessage(errorCode, syncErrorMessage))); - } - syncApi.doCreate(this.datasetId, itemToCreate, handleCreateSuccess, handleCreateError); + return new Bluebird(function(resolve, reject) { + function handleCreateSuccess(syncDataCreateResult: any) { + itemToCreate.uid = syncDataCreateResult.uid; + resolve(itemToCreate); + } + function handleCreateError(errorCode: string, syncErrorMessage: string) { + reject(new Error(self.formatSyncErrorMessage(errorCode, syncErrorMessage))); + } + syncApi.doCreate(self.datasetId, itemToCreate, handleCreateSuccess, handleCreateError); + }); } /** * Reading a single item from the data set. */ - public read(uid: string, callback: (err?: Error, result?: any) => void) { + public read(uid: string): Bluebird { const self = this; - function handleSuccess(syncDataCreateResult: any) { - syncDataCreateResult.data.uid = uid; - callback(undefined, syncDataCreateResult.data); - } - function handleError(errorCode: string, syncErrorMessage: string) { - callback(new Error(self.formatSyncErrorMessage(errorCode, syncErrorMessage))); - } - syncApi.doRead(this.datasetId, uid, handleSuccess, handleError); + return new Bluebird(function(resolve, reject) { + function handleSuccess(syncDataCreateResult: any) { + syncDataCreateResult.data.uid = uid; + return resolve(syncDataCreateResult.data); + } + function handleError(errorCode: string, syncErrorMessage: string) { + return reject(new Error(self.formatSyncErrorMessage(errorCode, syncErrorMessage))); + } + syncApi.doRead(self.datasetId, uid, handleSuccess, handleError); + }); } /** * Updating a single item in the data set. */ - public update(itemToUpdate: any, callback: (err?: Error, result?: any) => void) { + public update(itemToUpdate: any): Bluebird { const self = this; - function handleSuccess(syncDataCreateResult: any) { - callback(undefined, itemToUpdate); - } - function handleError(errorCode: string, syncErrorMessage: string) { - callback(new Error(self.formatSyncErrorMessage(errorCode, syncErrorMessage))); - } - syncApi.doUpdate(this.datasetId, itemToUpdate.uid, itemToUpdate, handleSuccess, handleError); + return new Bluebird(function(resolve, reject) { + function handleSuccess(syncDataCreateResult: any) { + resolve(itemToUpdate); + } + function handleError(errorCode: string, syncErrorMessage: string) { + reject(new Error(self.formatSyncErrorMessage(errorCode, syncErrorMessage))); + } + syncApi.doUpdate(self.datasetId, itemToUpdate.uid, itemToUpdate, handleSuccess, handleError); + }); } /** * Deleting an item from the data set. * * @param itemToDelete + * @returns Promise (bluebird) */ - public delete(itemToDelete: any, callback: (err?: Error, result?: any) => void) { + public delete(itemToDelete: any): Bluebird { const self = this; - function handleSuccess() { - callback(undefined); - } - function handleError(errorCode: string, syncErrorMessage: string) { - callback(new Error(self.formatSyncErrorMessage(errorCode, syncErrorMessage))); - } - syncApi.doDelete(this.datasetId, itemToDelete.uid, handleSuccess, handleError); + return new Bluebird(function(resolve, reject) { + function handleSuccess() { + resolve(); + } + function handleError(errorCode: string, syncErrorMessage: string) { + reject(new Error(self.formatSyncErrorMessage(errorCode, syncErrorMessage))); + } + syncApi.doDelete(self.datasetId, itemToDelete.uid, handleSuccess, handleError); + }); } /** @@ -98,13 +114,16 @@ export class DataManager { * * See the sync Client API docs for more information on $fh.sync.startSync * - * @returns {*} + * @returns Promise (bluebird) */ - public start(callback: (err?: Error, result?: any) => void) { - syncApi.startSync(this.datasetId, function() { - return callback(); - }, function(error) { - return callback(error); + public start(): Bluebird { + const self = this; + return new Bluebird(function(resolve, reject) { + syncApi.startSync(self.datasetId, function() { + resolve(); + }, function(error) { + reject(new Error(error)); + }); }); } @@ -113,16 +132,72 @@ export class DataManager { * * This will stop the sync Client API from sending/recieving updates from the remote server. * - * @returns {*} + * @returns Promise (bluebird) + */ + public stop(): Bluebird { + const self = this; + return new Bluebird(function(resolve, reject) { + syncApi.stopSync(self.datasetId, function() { + resolve(); + }, function(error) { + reject(new Error(error)); + }); + }); + } + + /** + * Forcing the sync framework to do a sync request to the remote server to exchange data. + * + * @returns Promise (bluebird) */ - public stop(callback: (err?: Error, result?: any) => void) { - syncApi.startSync(this.datasetId, function() { - return callback(); - }, function(error) { - return callback(error); + public forceSync(): Bluebird { + const self = this; + return new Bluebird(function(resolve, reject) { + syncApi.forceSync(self.datasetId, function() { + resolve(); + }, function(error) { + reject(new Error(error)); + }); }); } + /** + * A utility function to push any updates to the remote server and then stop syncing. + * If there are pending sync operations, then force them to sync and then stop syncing. + * @param userOptions - object that can contain delay + * @returns {*} + */ + public safeStop = function(userOptions) { + const self = this; + const defaultOptions = { + delay: 5000 + }; + + const options = _.defaults(userOptions, defaultOptions); + + function forceSyncThenStop(pendingUpdateQueueSize) { + if (pendingUpdateQueueSize === 0) { + self.stop().then(Bluebird.resolve); + return; + } + return self.forceSync() + .delay(options.delay) + .then(self.getQueueSize.bind(self)) + .then(function(size) { + if (size > 0) { + Bluebird.reject(new Error('forceSync failed, outstanding results still present')); + } + }) + .then(self.stop.bind(self)) + .then(function() { + Bluebird.resolve(); + }, function() { + Bluebird.reject(new Error('forceSync error')); + }); + } + return self.getQueueSize().then(forceSyncThenStop); + }; + /** * Subscribe to sync changes in dataset * @@ -137,6 +212,18 @@ export class DataManager { }); } + /** + * Get the current number of pending sync requests for this data set. + */ + private getQueueSize = function() { + const self = this; + return new Bluebird(function(resolve) { + syncApi.getPending(self.datasetId, function(pending?) { + resolve(_.size(pending)); + }); + }); + }; + /** * Extracting the Data Set data from a Sync Client API response * diff --git a/client/datasync-client/test/DataManagerTest.ts b/client/datasync-client/test/DataManagerTest.ts index 9dc19b2..308f736 100644 --- a/client/datasync-client/test/DataManagerTest.ts +++ b/client/datasync-client/test/DataManagerTest.ts @@ -38,9 +38,8 @@ describe('Data Manager', function() { const dataManager = new DataManager(mockDataSetId); - return dataManager.list(function(err, dataSetList) { + return dataManager.list().then(function(dataSetList) { sinon.assert.called(mock$fh.doList); - assert.strictEqual(mockSyncDataSetListAPIResponse.dataentryid.data, dataSetList[0]); }); }); @@ -57,9 +56,8 @@ describe('Data Manager', function() { const dataManager = new DataManager(mockDataSetId); - return dataManager.list(function(err, dataSetList) { + return dataManager.list().catch(function(err) { sinon.assert.called(mock$fh.doList); - assert.ok(err); }); }); @@ -91,7 +89,7 @@ describe('Data Manager', function() { const dataManager = new DataManager(mockDataSetId); - dataManager.create(mockDataItem, function(err, createResult) { + dataManager.create(mockDataItem).then(function(createResult) { expect(createResult).to.deep.equal(expectedCreatedData); sinon.assert.calledOnce(mock$fh.doCreate); @@ -122,16 +120,14 @@ describe('Data Manager', function() { }).DataManager; const dataManager = new DataManager(mockDataSetId); - return dataManager.read(mockRecordUid, function(err, readRecord) { + return dataManager.read(mockRecordUid).then(function(readRecord) { expect(readRecord).to.deep.equal(mockDataItem); sinon.assert.calledOnce(mock$fh.doRead); sinon.assert.calledWith(mock$fh.doRead, sinon.match(mockDataSetId), sinon.match(mockRecordUid), sinon.match.func, sinon.match.func); - }); - }); it('should update a new item', function() { @@ -159,7 +155,7 @@ describe('Data Manager', function() { const dataManager = new DataManager(mockDataSetId); - return dataManager.update(mockDataItem, function(err, createResult) { + return dataManager.update(mockDataItem).then(function(createResult) { expect(createResult).to.deep.equal(expectedCreatedData); sinon.assert.calledOnce(mock$fh.doUpdate); }); @@ -203,10 +199,63 @@ describe('Data Manager', function() { const dataManager = new DataManager(mockDataSetId); - return dataManager.delete(mockDataItem, function() { + return dataManager.delete(mockDataItem).then(function() { sinon.assert.calledOnce(mock$fh.doDelete); }); }); }); + it('should start stop', function() { + const mockRecordUid = 'syncRecordUID'; + const mock$fh = { + startSync: sinon.stub().callsArgWith(1), + stopSync: sinon.stub().callsArgWith(1) + }; + + const DataManager = proxyquire.noCallThru().load('../src/DataManager', { + 'fh-sync-js': mock$fh + }).DataManager; + + const dataManager = new DataManager(mockDataSetId); + + return dataManager.start().then(function(error) { + assert.ok(!error); + dataManager.stop().then(function(err) { + assert.ok(!err); + }); + }); + }); + + it('should force sync', function() { + const mockRecordUid = 'syncRecordUID'; + const mock$fh = { + forceSync: sinon.stub().callsArgWith(1) + }; + const DataManager = proxyquire.noCallThru().load('../src/DataManager', { + 'fh-sync-js': mock$fh + }).DataManager; + + const dataManager = new DataManager(mockDataSetId); + return dataManager.forceSync().then(function(error) { + assert.ok(!error); + }); + }); + + it('should safeStop', function() { + const mockRecordUid = 'syncRecordUID'; + const results = [{}, {}]; + const mock$fh = { + getPending: sinon.stub().callsArgWith(1, results), + }; + + const DataManager = proxyquire.noCallThru().load('../src/DataManager', { + 'fh-sync-js': mock$fh + }).DataManager; + + const dataManager = new DataManager(mockDataSetId); + + return dataManager.safeStop({ delay: 1 }).then(function(error) { + assert.ok(!error); + }); + }); });