Support for remote mutation of Notebooks with Couch DB (#3887)

* Update notebook automatically when modified by another user
* Don't persist selected and default page and section IDs on notebook object
* Fixing object synchronization bugs
* Adding unit tests
* Synchronize notebooks AND plans
* Removed observeEnabled flag

Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
This commit is contained in:
Andrew Henry
2021-06-21 10:25:17 -07:00
committed by GitHub
parent 333e8b5583
commit 6755ef4641
13 changed files with 547 additions and 290 deletions

View File

@@ -31,7 +31,7 @@
</div>
<SearchResults v-if="search.length"
ref="searchResults"
:domain-object="internalDomainObject"
:domain-object="domainObject"
:results="searchResults"
@changeSectionPage="changeSelectedSection"
@updateEntries="updateEntries"
@@ -43,15 +43,18 @@
class="c-notebook__nav c-sidebar c-drawer c-drawer--align-left"
:class="[{'is-expanded': showNav}, {'c-drawer--push': !sidebarCoversEntries}, {'c-drawer--overlays': sidebarCoversEntries}]"
:default-page-id="defaultPageId"
:selected-page-id="selectedPageId"
:default-section-id="defaultSectionId"
:domain-object="internalDomainObject"
:page-title="internalDomainObject.configuration.pageTitle"
:section-title="internalDomainObject.configuration.sectionTitle"
:selected-section-id="selectedSectionId"
:domain-object="domainObject"
:page-title="domainObject.configuration.pageTitle"
:section-title="domainObject.configuration.sectionTitle"
:sections="sections"
:selected-section="selectedSection"
:sidebar-covers-entries="sidebarCoversEntries"
@pagesChanged="pagesChanged"
@selectPage="selectPage"
@sectionsChanged="sectionsChanged"
@selectSection="selectSection"
@toggleNav="toggleNav"
/>
<div class="c-notebook__page-view">
@@ -61,10 +64,10 @@
></button>
<div class="c-notebook__page-view__path c-path">
<span class="c-notebook__path__section c-path__item">
{{ getSelectedSection() ? getSelectedSection().name : '' }}
{{ selectedSection ? selectedSection.name : '' }}
</span>
<span class="c-notebook__path__page c-path__item">
{{ getSelectedPage() ? getSelectedPage().name : '' }}
{{ selectedPage ? selectedPage.name : '' }}
</span>
</div>
<div class="c-notebook__page-view__controls">
@@ -115,9 +118,9 @@
<NotebookEntry v-for="entry in filteredAndSortedEntries"
:key="entry.id"
:entry="entry"
:domain-object="internalDomainObject"
:selected-page="getSelectedPage()"
:selected-section="getSelectedSection()"
:domain-object="domainObject"
:selected-page="selectedPage"
:selected-section="selectedSection"
:read-only="false"
@deleteEntry="deleteEntry"
@updateEntry="updateEntry"
@@ -152,14 +155,19 @@ export default {
SearchResults,
Sidebar
},
inject: ['openmct', 'domainObject', 'snapshotContainer'],
inject: ['openmct', 'snapshotContainer'],
props: {
domainObject: {
type: Object,
required: true
}
},
data() {
return {
defaultPageId: getDefaultNotebook() ? getDefaultNotebook().page.id : '',
defaultSectionId: getDefaultNotebook() ? getDefaultNotebook().section.id : '',
selectedSectionId: this.getDefaultSectionId(),
selectedPageId: this.getDefaultPageId(),
defaultSort: this.domainObject.configuration.defaultSort,
focusEntryId: null,
internalDomainObject: this.domainObject,
search: '',
searchResults: [],
showTime: 0,
@@ -168,9 +176,15 @@ export default {
};
},
computed: {
defaultPageId() {
return this.getDefaultPageId();
},
defaultSectionId() {
return this.getDefaultSectionId();
},
filteredAndSortedEntries() {
const filterTime = Date.now();
const pageEntries = getNotebookEntries(this.internalDomainObject, this.selectedSection, this.selectedPage) || [];
const pageEntries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage) || [];
const hours = parseInt(this.showTime, 10);
const filteredPageEntriesByTime = hours
@@ -185,22 +199,28 @@ export default {
return this.getPages() || [];
},
sections() {
return this.internalDomainObject.configuration.sections || [];
return this.getSections();
},
selectedPage() {
const pages = this.getPages();
if (!pages) {
return {};
const selectedPage = pages.find(page => page.id === this.selectedPageId);
if (selectedPage) {
return selectedPage;
}
return pages.find(page => page.isSelected);
if (!selectedPage && !pages.length) {
return undefined;
}
return pages[0];
},
selectedSection() {
if (!this.sections.length) {
return {};
return null;
}
return this.sections.find(section => section.isSelected);
return this.sections.find(section => section.id === this.selectedSectionId);
}
},
watch: {
@@ -210,16 +230,14 @@ export default {
},
beforeMount() {
this.getSearchResults = debounce(this.getSearchResults, 500);
this.syncUrlWithPageAndSection = debounce(this.syncUrlWithPageAndSection, 100);
},
mounted() {
this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', this.updateInternalDomainObject);
this.formatSidebar();
this.setSectionAndPageFromUrl();
window.addEventListener('orientationchange', this.formatSidebar);
window.addEventListener("hashchange", this.navigateToSectionPage, false);
this.openmct.router.on('change:params', this.changeSectionPage);
this.navigateToSectionPage();
window.addEventListener('hashchange', this.setSectionAndPageFromUrl);
},
beforeDestroy() {
if (this.unlisten) {
@@ -227,8 +245,7 @@ export default {
}
window.removeEventListener('orientationchange', this.formatSidebar);
window.removeEventListener("hashchange", this.navigateToSectionPage);
this.openmct.router.off('change:params', this.changeSectionPage);
window.removeEventListener('hashchange', this.setSectionAndPageFromUrl);
},
updated: function () {
this.$nextTick(() => {
@@ -284,14 +301,21 @@ export default {
this.sectionsChanged({ sections });
this.resetSearch();
},
setSectionAndPageFromUrl() {
let sectionId = this.getSectionIdFromUrl() || this.selectedSectionId;
let pageId = this.getPageIdFromUrl() || this.selectedPageId;
this.selectSection(sectionId);
this.selectPage(pageId);
},
createNotebookStorageObject() {
const notebookMeta = {
name: this.internalDomainObject.name,
identifier: this.internalDomainObject.identifier,
name: this.domainObject.name,
identifier: this.domainObject.identifier,
link: this.getLinktoNotebook()
};
const page = this.getSelectedPage();
const section = this.getSelectedSection();
const page = this.selectedPage;
const section = this.selectedSection;
return {
notebookMeta,
@@ -300,8 +324,7 @@ export default {
};
},
deleteEntry(entryId) {
const self = this;
const entryPos = getEntryPosById(entryId, this.internalDomainObject, this.selectedSection, this.selectedPage);
const entryPos = getEntryPosById(entryId, this.domainObject, this.selectedSection, this.selectedPage);
if (entryPos === -1) {
this.openmct.notifications.alert('Warning: unable to delete entry');
console.error(`unable to delete entry ${entryId} from section ${this.selectedSection}, page ${this.selectedPage}`);
@@ -317,9 +340,9 @@ export default {
label: "Ok",
emphasis: true,
callback: () => {
const entries = getNotebookEntries(self.internalDomainObject, self.selectedSection, self.selectedPage);
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
entries.splice(entryPos, 1);
self.updateEntries(entries);
this.updateEntries(entries);
dialog.dismiss();
}
},
@@ -395,6 +418,37 @@ export default {
const sidebarCoversEntries = (isPhone || (isTablet && isPortrait) || isInLayout);
this.sidebarCoversEntries = sidebarCoversEntries;
},
getDefaultPageId() {
let defaultPageId;
if (this.isDefaultNotebook()) {
defaultPageId = getDefaultNotebook().page.id;
} else {
const firstSection = this.getSections()[0];
defaultPageId = firstSection && firstSection.pages[0].id;
}
return defaultPageId;
},
isDefaultNotebook() {
const defaultNotebook = getDefaultNotebook();
const defaultNotebookIdentifier = defaultNotebook && defaultNotebook.notebookMeta.identifier;
return defaultNotebookIdentifier !== null
&& this.openmct.objects.areIdsEqual(defaultNotebookIdentifier, this.domainObject.identifier);
},
getDefaultSectionId() {
let defaultSectionId;
if (this.isDefaultNotebook()) {
defaultSectionId = getDefaultNotebook().section.id;
} else {
const firstSection = this.getSections()[0];
defaultSectionId = firstSection && firstSection.id;
}
return defaultSectionId;
},
getDefaultNotebookObject() {
const oldNotebookStorage = getDefaultNotebook();
if (!oldNotebookStorage) {
@@ -423,14 +477,17 @@ export default {
getSection(id) {
return this.sections.find(s => s.id === id);
},
getSections() {
return this.domainObject.configuration.sections || [];
},
getSearchResults() {
if (!this.search.length) {
return [];
}
const output = [];
const sections = this.internalDomainObject.configuration.sections;
const entries = this.internalDomainObject.configuration.entries;
const sections = this.domainObject.configuration.sections;
const entries = this.domainObject.configuration.entries;
const searchTextLower = this.search.toLowerCase();
const originalSearchText = this.search;
let sectionTrackPageHit;
@@ -509,77 +566,25 @@ export default {
this.searchResults = output;
},
getPages() {
const selectedSection = this.getSelectedSection();
const selectedSection = this.selectedSection;
if (!selectedSection || !selectedSection.pages.length) {
return [];
}
return selectedSection.pages;
},
getSelectedPage() {
const pages = this.getPages();
if (!pages) {
return null;
}
const selectedPage = pages.find(page => page.isSelected);
if (selectedPage) {
return selectedPage;
}
if (!selectedPage && !pages.length) {
return null;
}
pages[0].isSelected = true;
return pages[0];
},
getSelectedSection() {
if (!this.sections.length) {
return null;
}
return this.sections.find(section => section.isSelected);
},
navigateToSectionPage() {
let { pageId, sectionId } = this.openmct.router.getParams();
if (!pageId || !sectionId) {
sectionId = this.selectedSection.id;
pageId = this.selectedPage.id;
}
const sections = this.sections.map(s => {
s.isSelected = false;
if (s.id === sectionId) {
s.isSelected = true;
s.pages.forEach(p => p.isSelected = (p.id === pageId));
}
return s;
});
const selectedSectionId = this.selectedSection && this.selectedSection.id;
const selectedPageId = this.selectedPage && this.selectedPage.id;
if (selectedPageId === pageId && selectedSectionId === sectionId) {
return;
}
this.sectionsChanged({ sections });
},
newEntry(embed = null) {
this.resetSearch();
const notebookStorage = this.createNotebookStorageObject();
this.updateDefaultNotebook(notebookStorage);
const id = addNotebookEntry(this.openmct, this.internalDomainObject, notebookStorage, embed);
const id = addNotebookEntry(this.openmct, this.domainObject, notebookStorage, embed);
this.focusEntryId = id;
},
orientationChange() {
this.formatSidebar();
},
pagesChanged({ pages = [], id = null}) {
const selectedSection = this.getSelectedSection();
const selectedSection = this.selectedSection;
if (!selectedSection) {
return;
}
@@ -594,7 +599,6 @@ export default {
});
this.sectionsChanged({ sections });
this.updateDefaultNotebookPage(pages, id);
},
removeDefaultClass(domainObject) {
if (!domainObject) {
@@ -613,10 +617,10 @@ export default {
async updateDefaultNotebook(notebookStorage) {
const defaultNotebookObject = await this.getDefaultNotebookObject();
if (!defaultNotebookObject) {
setDefaultNotebook(this.openmct, notebookStorage, this.internalDomainObject);
setDefaultNotebook(this.openmct, notebookStorage, this.domainObject);
} else if (objectUtils.makeKeyString(defaultNotebookObject.identifier) !== objectUtils.makeKeyString(notebookStorage.notebookMeta.identifier)) {
this.removeDefaultClass(defaultNotebookObject);
setDefaultNotebook(this.openmct, notebookStorage, this.internalDomainObject);
setDefaultNotebook(this.openmct, notebookStorage, this.domainObject);
}
if (this.defaultSectionId && this.defaultSectionId.length === 0 || this.defaultSectionId !== notebookStorage.section.id) {
@@ -636,7 +640,7 @@ export default {
const notebookStorage = getDefaultNotebook();
if (!notebookStorage
|| notebookStorage.notebookMeta.identifier.key !== this.internalDomainObject.identifier.key) {
|| notebookStorage.notebookMeta.identifier.key !== this.domainObject.identifier.key) {
return;
}
@@ -645,7 +649,7 @@ export default {
if (!page && defaultNotebookPage.id === id) {
this.defaultSectionId = null;
this.defaultPageId = null;
this.removeDefaultClass(this.internalDomainObject);
this.removeDefaultClass(this.domainObject);
clearDefaultNotebook();
return;
@@ -664,7 +668,7 @@ export default {
const notebookStorage = getDefaultNotebook();
if (!notebookStorage
|| notebookStorage.notebookMeta.identifier.key !== this.internalDomainObject.identifier.key) {
|| notebookStorage.notebookMeta.identifier.key !== this.domainObject.identifier.key) {
return;
}
@@ -673,7 +677,7 @@ export default {
if (!section && defaultNotebookSection.id === id) {
this.defaultSectionId = null;
this.defaultPageId = null;
this.removeDefaultClass(this.internalDomainObject);
this.removeDefaultClass(this.domainObject);
clearDefaultNotebook();
return;
@@ -686,50 +690,46 @@ export default {
setDefaultNotebookSection(section);
},
updateEntry(entry) {
const entries = getNotebookEntries(this.internalDomainObject, this.selectedSection, this.selectedPage);
const entryPos = getEntryPosById(entry.id, this.internalDomainObject, this.selectedSection, this.selectedPage);
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);
},
updateEntries(entries) {
const configuration = this.internalDomainObject.configuration;
const configuration = this.domainObject.configuration;
const notebookEntries = configuration.entries || {};
notebookEntries[this.selectedSection.id][this.selectedPage.id] = entries;
mutateObject(this.openmct, this.internalDomainObject, 'configuration.entries', notebookEntries);
mutateObject(this.openmct, this.domainObject, 'configuration.entries', notebookEntries);
},
updateInternalDomainObject(domainObject) {
this.internalDomainObject = domainObject;
getPageIdFromUrl() {
return this.openmct.router.getParams().pageId;
},
updateParams(sections) {
const selectedSection = sections.find(s => s.isSelected);
if (!selectedSection) {
return;
}
const selectedPage = selectedSection.pages.find(p => p.isSelected);
if (!selectedPage) {
return;
}
const sectionId = selectedSection.id;
const pageId = selectedPage.id;
if (!sectionId || !pageId) {
return;
}
getSectionIdFromUrl() {
return this.openmct.router.getParams().sectionId;
},
syncUrlWithPageAndSection() {
this.openmct.router.updateParams({
sectionId,
pageId
pageId: this.selectedPageId,
sectionId: this.selectedSectionId
});
},
sectionsChanged({ sections, id = null }) {
mutateObject(this.openmct, this.internalDomainObject, 'configuration.sections', sections);
this.updateParams(sections);
mutateObject(this.openmct, this.domainObject, 'configuration.sections', sections);
this.updateDefaultNotebookSection(sections, id);
},
selectPage(pageId) {
this.selectedPageId = pageId;
this.syncUrlWithPageAndSection();
},
selectSection(sectionId) {
this.selectedSectionId = sectionId;
const defaultPageId = this.selectedSection.pages[0].id;
this.selectPage(defaultPageId);
this.syncUrlWithPageAndSection();
}
}
};