Compare commits
	
		
			4 Commits
		
	
	
		
			tc-nav-abo
			...
			release/2.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					4958594336 | ||
| 
						 | 
					532cec1531 | ||
| 
						 | 
					a11a4a23e1 | ||
| 
						 | 
					1e4d585e9d | 
@@ -44,6 +44,7 @@ async function createNotebookAndEntry(page, iterations = 1) {
 | 
			
		||||
        const entryLocator = `[aria-label="Notebook Entry Input"] >> nth = ${iteration}`;
 | 
			
		||||
        await page.locator(entryLocator).click();
 | 
			
		||||
        await page.locator(entryLocator).fill(`Entry ${iteration}`);
 | 
			
		||||
        await page.locator(entryLocator).press('Enter');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return notebook;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "openmct",
 | 
			
		||||
  "version": "2.1.5-SNAPSHOT",
 | 
			
		||||
  "version": "2.1.5",
 | 
			
		||||
  "description": "The Open MCT core platform",
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@babel/eslint-parser": "7.18.9",
 | 
			
		||||
 
 | 
			
		||||
@@ -193,23 +193,27 @@ export default class ObjectAPI {
 | 
			
		||||
     * @memberof module:openmct.ObjectProvider#
 | 
			
		||||
     * @param {string} key the key for the domain object to load
 | 
			
		||||
     * @param {AbortController.signal} abortSignal (optional) signal to abort fetch requests
 | 
			
		||||
     * @param {boolean} forceRemote defaults to false. If true, will skip cached and
 | 
			
		||||
     *          dirty/in-transaction objects use and the provider.get method
 | 
			
		||||
     * @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, 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);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -391,7 +395,6 @@ export default class ObjectAPI {
 | 
			
		||||
                lastPersistedTime = domainObject.persisted;
 | 
			
		||||
                const persistedTime = Date.now();
 | 
			
		||||
                this.#mutate(domainObject, 'persisted', persistedTime);
 | 
			
		||||
 | 
			
		||||
                savedObjectPromise = provider.update(domainObject);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -399,7 +402,7 @@ export default class ObjectAPI {
 | 
			
		||||
                savedObjectPromise.then(response => {
 | 
			
		||||
                    savedResolve(response);
 | 
			
		||||
                }).catch((error) => {
 | 
			
		||||
                    if (lastPersistedTime !== undefined) {
 | 
			
		||||
                    if (!isNewObject) {
 | 
			
		||||
                        this.#mutate(domainObject, 'persisted', lastPersistedTime);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
@@ -412,11 +415,12 @@ export default class ObjectAPI {
 | 
			
		||||
 | 
			
		||||
        return result.catch(async (error) => {
 | 
			
		||||
            if (error instanceof this.errors.Conflict) {
 | 
			
		||||
                this.openmct.notifications.error(`Conflict detected while saving ${this.makeKeyString(domainObject.identifier)}`);
 | 
			
		||||
                // Synchronized objects will resolve their own conflicts
 | 
			
		||||
                if (this.SYNCHRONIZED_OBJECT_TYPES.includes(domainObject.type)) {
 | 
			
		||||
                    this.openmct.notifications.info(`Conflict detected while saving "${this.makeKeyString(domainObject.name)}", attempting to resolve`);
 | 
			
		||||
                } else {
 | 
			
		||||
                    this.openmct.notifications.error(`Conflict detected while saving ${this.makeKeyString(domainObject.identifier)}`);
 | 
			
		||||
 | 
			
		||||
                // Synchronized objects will resolve their own conflicts, so
 | 
			
		||||
                // bypass the refresh here and throw the error.
 | 
			
		||||
                if (!this.SYNCHRONIZED_OBJECT_TYPES.includes(domainObject.type)) {
 | 
			
		||||
                    if (this.isTransactionActive()) {
 | 
			
		||||
                        this.endTransaction();
 | 
			
		||||
                    }
 | 
			
		||||
 
 | 
			
		||||
@@ -788,7 +788,7 @@ export default {
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        persistVisibleLayers() {
 | 
			
		||||
            if (this.domainObject.configuration) {
 | 
			
		||||
            if (this.domainObject.configuration && this.openmct.objects.supportsMutation(this.domainObject.identifier)) {
 | 
			
		||||
                this.openmct.objects.mutate(this.domainObject, 'configuration.layers', this.layers);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@ function copyRelatedMetadata(metadata) {
 | 
			
		||||
    return copiedMetadata;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
import IndependentTimeContext from "@/api/time/IndependentTimeContext";
 | 
			
		||||
export default class RelatedTelemetry {
 | 
			
		||||
 | 
			
		||||
    constructor(openmct, domainObject, telemetryKeys) {
 | 
			
		||||
@@ -88,9 +89,31 @@ export default class RelatedTelemetry {
 | 
			
		||||
            this[key].historicalDomainObject = await this._openmct.objects.get(this[key].historical.telemetryObjectId);
 | 
			
		||||
 | 
			
		||||
            this[key].requestLatestFor = async (datum) => {
 | 
			
		||||
                const options = {
 | 
			
		||||
                // We need to create a throwaway time context and pass it along
 | 
			
		||||
                // as a request option. We do this to "trick" the Time API
 | 
			
		||||
                // into thinking we are in fixed time mode in order to bypass this logic:
 | 
			
		||||
                // https://github.com/akhenry/openmct-yamcs/blob/1060d42ebe43bf346dac0f6a8068cb288ade4ba4/src/providers/historical-telemetry-provider.js#L59
 | 
			
		||||
                // Context: https://github.com/akhenry/openmct-yamcs/pull/217
 | 
			
		||||
                const ephemeralContext = new IndependentTimeContext(
 | 
			
		||||
                    this._openmct,
 | 
			
		||||
                    this._openmct.time,
 | 
			
		||||
                    [this[key].historicalDomainObject]
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                // Stop following the global context, stop the clock,
 | 
			
		||||
                // and set bounds.
 | 
			
		||||
                ephemeralContext.resetContext();
 | 
			
		||||
                const newBounds = {
 | 
			
		||||
                    start: this._openmct.time.bounds().start,
 | 
			
		||||
                    end: this._parseTime(datum),
 | 
			
		||||
                    end: this._parseTime(datum)
 | 
			
		||||
                };
 | 
			
		||||
                ephemeralContext.stopClock();
 | 
			
		||||
                ephemeralContext.bounds(newBounds);
 | 
			
		||||
 | 
			
		||||
                const options = {
 | 
			
		||||
                    start: newBounds.start,
 | 
			
		||||
                    end: newBounds.end,
 | 
			
		||||
                    timeContext: ephemeralContext,
 | 
			
		||||
                    strategy: 'latest'
 | 
			
		||||
                };
 | 
			
		||||
                let results = await this._openmct.telemetry
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
@@ -123,6 +123,7 @@
 | 
			
		||||
            </div>
 | 
			
		||||
            <div
 | 
			
		||||
                v-if="selectedPage && !selectedPage.isLocked"
 | 
			
		||||
                :class="{ 'disabled': activeTransaction }"
 | 
			
		||||
                class="c-notebook__drag-area icon-plus"
 | 
			
		||||
                @click="newEntry()"
 | 
			
		||||
                @dragover="dragOver"
 | 
			
		||||
@@ -133,6 +134,11 @@
 | 
			
		||||
                    To start a new entry, click here or drag and drop any object
 | 
			
		||||
                </span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <progress-bar
 | 
			
		||||
                v-if="savingTransaction"
 | 
			
		||||
                class="c-telemetry-table__progress-bar"
 | 
			
		||||
                :model="{ progressPerc: undefined }"
 | 
			
		||||
            />
 | 
			
		||||
            <div
 | 
			
		||||
                v-if="selectedPage && selectedPage.isLocked"
 | 
			
		||||
                class="c-notebook__page-locked"
 | 
			
		||||
@@ -183,6 +189,7 @@ import NotebookEntry from './NotebookEntry.vue';
 | 
			
		||||
import Search from '@/ui/components/search.vue';
 | 
			
		||||
import SearchResults from './SearchResults.vue';
 | 
			
		||||
import Sidebar from './Sidebar.vue';
 | 
			
		||||
import ProgressBar from '../../../ui/components/ProgressBar.vue';
 | 
			
		||||
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSectionId, setDefaultNotebookPageId } from '../utils/notebook-storage';
 | 
			
		||||
import { addNotebookEntry, createNewEmbed, getEntryPosById, getNotebookEntries, mutateObject } from '../utils/notebook-entries';
 | 
			
		||||
import { saveNotebookImageDomainObject, updateNamespaceOfDomainObject } from '../utils/notebook-image';
 | 
			
		||||
@@ -200,7 +207,8 @@ export default {
 | 
			
		||||
        NotebookEntry,
 | 
			
		||||
        Search,
 | 
			
		||||
        SearchResults,
 | 
			
		||||
        Sidebar
 | 
			
		||||
        Sidebar,
 | 
			
		||||
        ProgressBar
 | 
			
		||||
    },
 | 
			
		||||
    inject: ['agent', 'openmct', 'snapshotContainer'],
 | 
			
		||||
    props: {
 | 
			
		||||
@@ -225,7 +233,9 @@ export default {
 | 
			
		||||
            showNav: false,
 | 
			
		||||
            sidebarCoversEntries: false,
 | 
			
		||||
            filteredAndSortedEntries: [],
 | 
			
		||||
            notebookAnnotations: {}
 | 
			
		||||
            notebookAnnotations: {},
 | 
			
		||||
            activeTransaction: false,
 | 
			
		||||
            savingTransaction: false
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
@@ -270,6 +280,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);
 | 
			
		||||
 | 
			
		||||
@@ -297,6 +321,8 @@ export default {
 | 
			
		||||
        this.formatSidebar();
 | 
			
		||||
        this.setSectionAndPageFromUrl();
 | 
			
		||||
 | 
			
		||||
        this.transaction = null;
 | 
			
		||||
 | 
			
		||||
        window.addEventListener('orientationchange', this.formatSidebar);
 | 
			
		||||
        window.addEventListener('hashchange', this.setSectionAndPageFromUrl);
 | 
			
		||||
        this.filterAndSortEntries();
 | 
			
		||||
@@ -749,6 +775,7 @@ export default {
 | 
			
		||||
            return section.id;
 | 
			
		||||
        },
 | 
			
		||||
        async newEntry(embed = null) {
 | 
			
		||||
            this.startTransaction();
 | 
			
		||||
            this.resetSearch();
 | 
			
		||||
            const notebookStorage = this.createNotebookStorageObject();
 | 
			
		||||
            this.updateDefaultNotebook(notebookStorage);
 | 
			
		||||
@@ -891,20 +918,34 @@ export default {
 | 
			
		||||
        },
 | 
			
		||||
        startTransaction() {
 | 
			
		||||
            if (!this.openmct.objects.isTransactionActive()) {
 | 
			
		||||
                this.activeTransaction = true;
 | 
			
		||||
                this.transaction = this.openmct.objects.startTransaction();
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        async saveTransaction() {
 | 
			
		||||
            if (this.transaction !== undefined) {
 | 
			
		||||
                await this.transaction.commit();
 | 
			
		||||
                this.openmct.objects.endTransaction();
 | 
			
		||||
            if (this.transaction !== null) {
 | 
			
		||||
                this.savingTransaction = true;
 | 
			
		||||
                try {
 | 
			
		||||
                    await this.transaction.commit();
 | 
			
		||||
                } finally {
 | 
			
		||||
                    this.endTransaction();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        async cancelTransaction() {
 | 
			
		||||
            if (this.transaction !== undefined) {
 | 
			
		||||
                await this.transaction.cancel();
 | 
			
		||||
                this.openmct.objects.endTransaction();
 | 
			
		||||
            if (this.transaction !== null) {
 | 
			
		||||
                try {
 | 
			
		||||
                    await this.transaction.cancel();
 | 
			
		||||
                } finally {
 | 
			
		||||
                    this.endTransaction();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        endTransaction() {
 | 
			
		||||
            this.openmct.objects.endTransaction();
 | 
			
		||||
            this.transaction = null;
 | 
			
		||||
            this.savingTransaction = false;
 | 
			
		||||
            this.activeTransaction = false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -74,19 +74,22 @@ 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;
 | 
			
		||||
 | 
			
		||||
@@ -110,8 +113,13 @@ function applyLocalEntries(mutable, entries, openmct) {
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@
 | 
			
		||||
                connected = false;
 | 
			
		||||
                // stop listening for events
 | 
			
		||||
                couchEventSource.removeEventListener('message', self.onCouchMessage);
 | 
			
		||||
                couchEventSource.close();
 | 
			
		||||
                console.debug('🚪 Closed couch connection 🚪');
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
 
 | 
			
		||||
@@ -96,8 +96,13 @@ class CouchObjectProvider {
 | 
			
		||||
            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];
 | 
			
		||||
            let isInTransaction = false;
 | 
			
		||||
 | 
			
		||||
            if (observersForObject) {
 | 
			
		||||
            if (this.openmct.objects.isTransactionActive()) {
 | 
			
		||||
                isInTransaction = this.openmct.objects.transaction.getDirtyObject(objectIdentifier);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (observersForObject && !isInTransaction) {
 | 
			
		||||
                observersForObject.forEach(async (observer) => {
 | 
			
		||||
                    const updatedObject = await this.get(objectIdentifier);
 | 
			
		||||
                    if (this.isSynchronizedObject(updatedObject)) {
 | 
			
		||||
@@ -219,7 +224,12 @@ class CouchObjectProvider {
 | 
			
		||||
                console.error(error.message);
 | 
			
		||||
                throw new Error(`CouchDB Error - No response"`);
 | 
			
		||||
            } else {
 | 
			
		||||
                console.error(error.message);
 | 
			
		||||
                if (body?.model && isNotebookOrAnnotationType(body.model)) {
 | 
			
		||||
                    // warn since we handle conflicts for notebooks
 | 
			
		||||
                    console.warn(error.message);
 | 
			
		||||
                } else {
 | 
			
		||||
                    console.error(error.message);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                throw error;
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -101,7 +101,8 @@ export default {
 | 
			
		||||
                if (nowMarker) {
 | 
			
		||||
                    nowMarker.classList.remove('hidden');
 | 
			
		||||
                    nowMarker.style.height = this.contentHeight + 'px';
 | 
			
		||||
                    const now = this.xScale(Date.now());
 | 
			
		||||
                    const nowTimeStamp = this.openmct.time.clock().currentValue();
 | 
			
		||||
                    const now = this.xScale(nowTimeStamp);
 | 
			
		||||
                    nowMarker.style.left = now + this.offset + 'px';
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user