Compare commits
	
		
			4 Commits
		
	
	
		
			dave/inspe
			...
			nb-conflic
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | c25095a801 | ||
|   | 0e3e32f037 | ||
|   | 9f6463105a | ||
|   | 317abfe2c1 | 
| @@ -75,7 +75,8 @@ | ||||
|         const TWO_HOURS = ONE_HOUR * 2; | ||||
|         const ONE_DAY = ONE_HOUR * 24; | ||||
|  | ||||
|         openmct.install(openmct.plugins.LocalStorage()); | ||||
|         // openmct.install(openmct.plugins.LocalStorage()); | ||||
|         openmct.install(openmct.plugins.CouchDB("http://localhost:5984/openmct")); | ||||
|  | ||||
|         openmct.install(openmct.plugins.example.Generator()); | ||||
|         openmct.install(openmct.plugins.example.EventGeneratorPlugin()); | ||||
|   | ||||
| @@ -196,20 +196,22 @@ export default class ObjectAPI { | ||||
|      * @returns {Promise} a promise which will resolve when the domain object | ||||
|      *          has been saved, or be rejected if it cannot be saved | ||||
|      */ | ||||
|     get(identifier, abortSignal) { | ||||
|     get(identifier, abortSignal = undefined, forceRemote = false) { | ||||
|         let keystring = this.makeKeyString(identifier); | ||||
|  | ||||
|         if (this.cache[keystring] !== undefined) { | ||||
|             return this.cache[keystring]; | ||||
|         } | ||||
|         if (!forceRemote) { | ||||
|             if (this.cache[keystring] !== undefined) { | ||||
|                 return this.cache[keystring]; | ||||
|             } | ||||
|  | ||||
|         identifier = utils.parseKeyString(identifier); | ||||
|             identifier = utils.parseKeyString(identifier); | ||||
|  | ||||
|         if (this.isTransactionActive()) { | ||||
|             let dirtyObject = this.transaction.getDirtyObject(identifier); | ||||
|             if (this.isTransactionActive()) { | ||||
|                 let dirtyObject = this.transaction.getDirtyObject(identifier); | ||||
|  | ||||
|             if (dirtyObject) { | ||||
|                 return Promise.resolve(dirtyObject); | ||||
|                 if (dirtyObject) { | ||||
|                     return Promise.resolve(dirtyObject); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -399,7 +401,7 @@ export default class ObjectAPI { | ||||
|                 savedObjectPromise.then(response => { | ||||
|                     savedResolve(response); | ||||
|                 }).catch((error) => { | ||||
|                     if (lastPersistedTime !== undefined) { | ||||
|                     if (isNewObject) { | ||||
|                         this.#mutate(domainObject, 'persisted', lastPersistedTime); | ||||
|                     } | ||||
|  | ||||
| @@ -412,7 +414,9 @@ export default class ObjectAPI { | ||||
|  | ||||
|         return result.catch((error) => { | ||||
|             if (error instanceof this.errors.Conflict) { | ||||
|                 this.openmct.notifications.error(`Conflict detected while saving ${this.makeKeyString(domainObject.identifier)}`); | ||||
|                 if (!this.SYNCHRONIZED_OBJECT_TYPES.includes(domainObject.type)) { | ||||
|                     this.openmct.notifications.error(`Conflict detected while saving ${this.makeKeyString(domainObject.identifier)}`); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             throw error; | ||||
|   | ||||
| @@ -50,7 +50,7 @@ | ||||
|         <Sidebar | ||||
|             ref="sidebar" | ||||
|             class="c-notebook__nav c-sidebar c-drawer c-drawer--align-left" | ||||
|             :class="[{'is-expanded': showNav}, {'c-drawer--push': !sidebarCoversEntries}, {'c-drawer--overlays': sidebarCoversEntries}]" | ||||
|             :class="sidebarClasses" | ||||
|             :default-page-id="defaultPageId" | ||||
|             :selected-page-id="getSelectedPageId()" | ||||
|             :default-section-id="defaultSectionId" | ||||
| @@ -270,6 +270,20 @@ export default { | ||||
|  | ||||
|             return this.sections[0]; | ||||
|         }, | ||||
|         sidebarClasses() { | ||||
|             let sidebarClasses = []; | ||||
|             if (this.showNav) { | ||||
|                 sidebarClasses.push('is-expanded'); | ||||
|             } | ||||
|  | ||||
|             if (this.sidebarCoversEntries) { | ||||
|                 sidebarClasses.push('c-drawer--overlays'); | ||||
|             } else { | ||||
|                 sidebarClasses.push('c-drawer--push'); | ||||
|             } | ||||
|  | ||||
|             return sidebarClasses; | ||||
|         }, | ||||
|         showLockButton() { | ||||
|             const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage); | ||||
|  | ||||
| @@ -300,16 +314,14 @@ export default { | ||||
|         window.addEventListener('orientationchange', this.formatSidebar); | ||||
|         window.addEventListener('hashchange', this.setSectionAndPageFromUrl); | ||||
|         this.filterAndSortEntries(); | ||||
|         this.unobserveEntries = this.openmct.objects.observe(this.domainObject, '*', this.filterAndSortEntries); | ||||
|         this.startObservingEntries(); | ||||
|     }, | ||||
|     beforeDestroy() { | ||||
|         if (this.unlisten) { | ||||
|             this.unlisten(); | ||||
|         } | ||||
|  | ||||
|         if (this.unobserveEntries) { | ||||
|             this.unobserveEntries(); | ||||
|         } | ||||
|         this.stopObservingEntries(); | ||||
|  | ||||
|         Object.keys(this.notebookAnnotations).forEach(entryID => { | ||||
|             const notebookAnnotationsForEntry = this.notebookAnnotations[entryID]; | ||||
| @@ -327,6 +339,17 @@ export default { | ||||
|         }); | ||||
|     }, | ||||
|     methods: { | ||||
|         startObservingEntries() { | ||||
|             if (this.unobserveEntries) { | ||||
|                 this.stopObservingEntries(); | ||||
|             } | ||||
|  | ||||
|             this.unobserveEntries = this.openmct.objects.observe(this.domainObject, '*', this.filterAndSortEntries); | ||||
|         }, | ||||
|         stopObservingEntries() { | ||||
|             this.unobserveEntries(); | ||||
|             delete this.unobserveEntries; | ||||
|         }, | ||||
|         changeSectionPage(newParams, oldParams, changedParams) { | ||||
|             if (isNotebookViewType(newParams.view)) { | ||||
|                 return; | ||||
| @@ -749,6 +772,7 @@ export default { | ||||
|             return section.id; | ||||
|         }, | ||||
|         async newEntry(embed = null) { | ||||
|             this.startTransaction(); | ||||
|             this.resetSearch(); | ||||
|             const notebookStorage = this.createNotebookStorageObject(); | ||||
|             this.updateDefaultNotebook(notebookStorage); | ||||
| @@ -835,21 +859,21 @@ export default { | ||||
|  | ||||
|             setDefaultNotebookSectionId(defaultNotebookSectionId); | ||||
|         }, | ||||
|         updateEntry(entry) { | ||||
|         async updateEntry(entry) { | ||||
|             const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage); | ||||
|             const entryPos = getEntryPosById(entry.id, this.domainObject, this.selectedSection, this.selectedPage); | ||||
|             entries[entryPos] = entry; | ||||
|  | ||||
|             this.updateEntries(entries); | ||||
|             await this.updateEntries(entries); | ||||
|         }, | ||||
|         updateEntries(entries) { | ||||
|         async updateEntries(entries) { | ||||
|             const configuration = this.domainObject.configuration; | ||||
|             const notebookEntries = configuration.entries || {}; | ||||
|             notebookEntries[this.selectedSection.id][this.selectedPage.id] = entries; | ||||
|  | ||||
|             mutateObject(this.openmct, this.domainObject, 'configuration.entries', notebookEntries); | ||||
|  | ||||
|             this.saveTransaction(); | ||||
|             await this.saveTransaction(); | ||||
|         }, | ||||
|         getPageIdFromUrl() { | ||||
|             return this.openmct.router.getParams().pageId; | ||||
| @@ -891,20 +915,36 @@ export default { | ||||
|         }, | ||||
|         startTransaction() { | ||||
|             if (!this.openmct.objects.isTransactionActive()) { | ||||
|                 this.stopObservingEntries(); | ||||
|                 this.transaction = this.openmct.objects.startTransaction(); | ||||
|             } | ||||
|         }, | ||||
|         async saveTransaction() { | ||||
|             if (this.transaction !== undefined) { | ||||
|                 await this.transaction.commit(); | ||||
|                 this.openmct.objects.endTransaction(); | ||||
|                 try { | ||||
|                     await this.transaction.commit(); | ||||
|                 } catch (error) { | ||||
|                     console.warn('Error saving Notebook transaction', error); | ||||
|                 } finally { | ||||
|                     this.endTransaction(); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         async cancelTransaction() { | ||||
|             if (this.transaction !== undefined) { | ||||
|                 await this.transaction.cancel(); | ||||
|                 this.openmct.objects.endTransaction(); | ||||
|                 try { | ||||
|                     await this.transaction.cancel(); | ||||
|                 } catch (error) { | ||||
|                     console.warn('Error canceling Notebook transaction', error); | ||||
|                 } finally { | ||||
|                     this.endTransaction(); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         endTransaction() { | ||||
|             this.openmct.objects.endTransaction(); | ||||
|             this.transaction = undefined; | ||||
|             this.startObservingEntries(); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|   | ||||
| @@ -74,21 +74,25 @@ async function resolveNotebookTagConflicts(localAnnotation, openmct) { | ||||
|  | ||||
| async function resolveNotebookEntryConflicts(localMutable, openmct) { | ||||
|     if (localMutable.configuration.entries) { | ||||
|         const FORCE_REMOTE = true; | ||||
|         const localEntries = structuredClone(localMutable.configuration.entries); | ||||
|         const remoteMutable = await openmct.objects.getMutable(localMutable.identifier); | ||||
|         applyLocalEntries(remoteMutable, localEntries, openmct); | ||||
|         openmct.objects.destroyMutable(remoteMutable); | ||||
|         const remoteObject = await openmct.objects.get(localMutable.identifier, undefined, FORCE_REMOTE); | ||||
|  | ||||
|         return applyLocalEntries(remoteObject, localEntries, openmct); | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| function applyLocalEntries(mutable, entries, openmct) { | ||||
| function applyLocalEntries(remoteObject, entries, openmct) { | ||||
|     let shouldSave = false; | ||||
|  | ||||
|     Object.entries(entries).forEach(([sectionKey, pagesInSection]) => { | ||||
|         Object.entries(pagesInSection).forEach(([pageKey, localEntries]) => { | ||||
|             const remoteEntries = mutable.configuration.entries[sectionKey][pageKey]; | ||||
|             const remoteEntries = remoteObject.configuration.entries[sectionKey][pageKey]; | ||||
|             const mergedEntries = [].concat(remoteEntries); | ||||
|             let shouldMutate = false; | ||||
|             console.log('merging after conflict, remote, local', remoteEntries, entries); | ||||
|  | ||||
|             const locallyAddedEntries = _.differenceBy(localEntries, remoteEntries, 'id'); | ||||
|             const locallyModifiedEntries = _.differenceWith(localEntries, remoteEntries, (localEntry, remoteEntry) => { | ||||
| @@ -108,10 +112,17 @@ function applyLocalEntries(mutable, entries, openmct) { | ||||
|                     shouldMutate = true; | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             console.log('merged entries', mergedEntries); | ||||
|             if (shouldMutate) { | ||||
|                 openmct.objects.mutate(mutable, `configuration.entries.${sectionKey}.${pageKey}`, mergedEntries); | ||||
|                 shouldSave = true; | ||||
|                 openmct.objects.mutate(remoteObject, `configuration.entries.${sectionKey}.${pageKey}`, mergedEntries); | ||||
|             } | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     if (shouldSave) { | ||||
|         return openmct.objects.save(remoteObject); | ||||
|     } else { | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -36,8 +36,8 @@ export default function () { | ||||
|         } | ||||
|  | ||||
|         let wrappedFunction = openmct.objects.get; | ||||
|         openmct.objects.get = function migrate(identifier) { | ||||
|             return wrappedFunction.apply(openmct.objects, [identifier]) | ||||
|         openmct.objects.get = function migrate() { | ||||
|             return wrappedFunction.apply(openmct.objects, [...arguments]) | ||||
|                 .then(function (object) { | ||||
|                     if (needsMigration(object)) { | ||||
|                         migrateObject(object) | ||||
|   | ||||
| @@ -219,7 +219,12 @@ class CouchObjectProvider { | ||||
|                 console.error(error.message); | ||||
|                 throw new Error(`CouchDB Error - No response"`); | ||||
|             } else { | ||||
|                 console.error(error.message); | ||||
|                 if (!isNotebookOrAnnotationType(body.model)) { | ||||
|                     console.error(error.message); | ||||
|                 } else { | ||||
|                     // warn since we handle conflicts for notebooks | ||||
|                     console.warn(error.message); | ||||
|                 } | ||||
|  | ||||
|                 throw error; | ||||
|             } | ||||
| @@ -554,8 +559,9 @@ class CouchObjectProvider { | ||||
|         let observersForObject = this.observers[keyString]; | ||||
|  | ||||
|         if (observersForObject) { | ||||
|             const FORCE_REMOTE = true; | ||||
|             observersForObject.forEach(async (observer) => { | ||||
|                 const updatedObject = await this.get(identifier); | ||||
|                 const updatedObject = await this.get(identifier, undefined, FORCE_REMOTE); | ||||
|                 if (this.isSynchronizedObject(updatedObject)) { | ||||
|                     observer(updatedObject); | ||||
|                 } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user