mirror of
https://github.com/nasa/openmct.git
synced 2024-12-18 12:56:25 +00:00
fix vue reactivity of rows by changing the reference of the updated row (#7940)
* do not call `updateVisibleRows` on horizontal scroll * add example provider for in place row updates
This commit is contained in:
parent
61b982ab99
commit
14b947c101
@ -0,0 +1,67 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import { createDomainObjectWithDefaults, setRealTimeMode } from '../../../appActions.js';
|
||||||
|
import { MISSION_TIME } from '../../../constants.js';
|
||||||
|
import { expect, test } from '../../../pluginFixtures.js';
|
||||||
|
|
||||||
|
const TELEMETRY_RATE = 2500;
|
||||||
|
|
||||||
|
test.describe('Example Event Generator Acknowledge with Controlled Clock @clock', () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.clock.install({ time: MISSION_TIME });
|
||||||
|
await page.clock.resume();
|
||||||
|
|
||||||
|
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
|
await setRealTimeMode(page);
|
||||||
|
|
||||||
|
await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Event Message Generator with Acknowledge'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Rows are updatable in place', async ({ page }) => {
|
||||||
|
test.info().annotations.push({
|
||||||
|
type: 'issue',
|
||||||
|
description: 'https://github.com/nasa/openmct/issues/7938'
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('First telemetry datum gets added as new row', async () => {
|
||||||
|
await page.clock.fastForward(TELEMETRY_RATE);
|
||||||
|
const rows = page.getByLabel('table content').getByLabel('Table Row');
|
||||||
|
const acknowledgeCell = rows.first().getByLabel('acknowledge table cell');
|
||||||
|
|
||||||
|
await expect(rows).toHaveCount(1);
|
||||||
|
await expect(acknowledgeCell).not.toHaveAttribute('title', 'OK');
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('Incoming Telemetry datum matching an existing rows in place update key has data merged to existing row', async () => {
|
||||||
|
await page.clock.fastForward(TELEMETRY_RATE * 2);
|
||||||
|
const rows = page.getByLabel('table content').getByLabel('Table Row');
|
||||||
|
const acknowledgeCell = rows.first().getByLabel('acknowledge table cell');
|
||||||
|
|
||||||
|
await expect(rows).toHaveCount(1);
|
||||||
|
await expect(acknowledgeCell).toHaveAttribute('title', 'OK');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -46,6 +46,24 @@ class EventMetadataProvider {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const inPlaceUpdateMetadataValue = {
|
||||||
|
key: 'messageId',
|
||||||
|
name: 'row identifier',
|
||||||
|
format: 'string',
|
||||||
|
useToUpdateInPlace: true
|
||||||
|
};
|
||||||
|
const eventAcknowledgeMetadataValue = {
|
||||||
|
key: 'acknowledge',
|
||||||
|
name: 'Acknowledge',
|
||||||
|
format: 'string'
|
||||||
|
};
|
||||||
|
|
||||||
|
const eventGeneratorWithAcknowledge = structuredClone(this.METADATA_BY_TYPE.eventGenerator);
|
||||||
|
eventGeneratorWithAcknowledge.values.push(inPlaceUpdateMetadataValue);
|
||||||
|
eventGeneratorWithAcknowledge.values.push(eventAcknowledgeMetadataValue);
|
||||||
|
|
||||||
|
this.METADATA_BY_TYPE.eventGeneratorWithAcknowledge = eventGeneratorWithAcknowledge;
|
||||||
}
|
}
|
||||||
|
|
||||||
supportsMetadata(domainObject) {
|
supportsMetadata(domainObject) {
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module defining EventTelemetryProvider. Created by chacskaylo on 06/18/2015.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import EventTelemetryProvider from './EventTelemetryProvider.js';
|
||||||
|
|
||||||
|
class EventWithAcknowledgeTelemetryProvider extends EventTelemetryProvider {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.unAcknowledgedData = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateData(firstObservedTime, count, startTime, duration, name) {
|
||||||
|
if (this.unAcknowledgedData === undefined) {
|
||||||
|
const unAcknowledgedData = super.generateData(
|
||||||
|
firstObservedTime,
|
||||||
|
count,
|
||||||
|
startTime,
|
||||||
|
duration,
|
||||||
|
name
|
||||||
|
);
|
||||||
|
unAcknowledgedData.messageId = unAcknowledgedData.message;
|
||||||
|
this.unAcknowledgedData = unAcknowledgedData;
|
||||||
|
|
||||||
|
return this.unAcknowledgedData;
|
||||||
|
} else {
|
||||||
|
const acknowledgedData = {
|
||||||
|
...this.unAcknowledgedData,
|
||||||
|
acknowledge: 'OK'
|
||||||
|
};
|
||||||
|
|
||||||
|
this.unAcknowledgedData = undefined;
|
||||||
|
|
||||||
|
return acknowledgedData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
supportsRequest(domainObject) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
supportsSubscribe(domainObject) {
|
||||||
|
return domainObject.type === 'eventGeneratorWithAcknowledge';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EventWithAcknowledgeTelemetryProvider;
|
@ -21,6 +21,7 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
import EventMetadataProvider from './EventMetadataProvider.js';
|
import EventMetadataProvider from './EventMetadataProvider.js';
|
||||||
import EventTelemetryProvider from './EventTelemetryProvider.js';
|
import EventTelemetryProvider from './EventTelemetryProvider.js';
|
||||||
|
import EventWithAcknowledgeTelemetryProvider from './EventWithAcknowledgeTelemetryProvider.js';
|
||||||
|
|
||||||
export default function EventGeneratorPlugin(options) {
|
export default function EventGeneratorPlugin(options) {
|
||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
@ -38,5 +39,20 @@ export default function EventGeneratorPlugin(options) {
|
|||||||
});
|
});
|
||||||
openmct.telemetry.addProvider(new EventTelemetryProvider());
|
openmct.telemetry.addProvider(new EventTelemetryProvider());
|
||||||
openmct.telemetry.addProvider(new EventMetadataProvider());
|
openmct.telemetry.addProvider(new EventMetadataProvider());
|
||||||
|
|
||||||
|
openmct.types.addType('eventGeneratorWithAcknowledge', {
|
||||||
|
name: 'Event Message Generator with Acknowledge',
|
||||||
|
description:
|
||||||
|
'For development use. Creates sample event message data stream and updates the event row with an acknowledgement.',
|
||||||
|
cssClass: 'icon-generator-events',
|
||||||
|
creatable: true,
|
||||||
|
initialize: function (object) {
|
||||||
|
object.telemetry = {
|
||||||
|
duration: 2.5
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
openmct.telemetry.addProvider(new EventWithAcknowledgeTelemetryProvider());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -91,15 +91,19 @@ export default class TelemetryTableRow {
|
|||||||
return [VIEW_DATUM_ACTION_KEY, VIEW_HISTORICAL_DATA_ACTION_KEY];
|
return [VIEW_DATUM_ACTION_KEY, VIEW_HISTORICAL_DATA_ACTION_KEY];
|
||||||
}
|
}
|
||||||
|
|
||||||
updateWithDatum(updatesToDatum) {
|
/**
|
||||||
const normalizedUpdatesToDatum = createNormalizedDatum(updatesToDatum, this.columns);
|
* Merges the row parameter's datum with the current row datum
|
||||||
|
* @param {TelemetryTableRow} row
|
||||||
|
*/
|
||||||
|
updateWithDatum(row) {
|
||||||
this.datum = {
|
this.datum = {
|
||||||
...this.datum,
|
...this.datum,
|
||||||
...normalizedUpdatesToDatum
|
...row.datum
|
||||||
};
|
};
|
||||||
|
|
||||||
this.fullDatum = {
|
this.fullDatum = {
|
||||||
...this.fullDatum,
|
...this.fullDatum,
|
||||||
...updatesToDatum
|
...row.fullDatum
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,11 @@ import { EventEmitter } from 'eventemitter3';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { ORDER } from '../constants.js';
|
import { ORDER } from '../constants.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('.TelemetryTableRow.js').default} TelemetryTableRow
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
@ -124,10 +129,22 @@ export default class TableRowCollection extends EventEmitter {
|
|||||||
return foundIndex;
|
return foundIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRowInPlace(row, index) {
|
/**
|
||||||
const foundRow = this.rows[index];
|
* `incomingRow` exists in the collection,
|
||||||
foundRow.updateWithDatum(row.datum);
|
* so merge existing and incoming row properties
|
||||||
this.rows[index] = foundRow;
|
*
|
||||||
|
* Do to reactivity of Vue, we want to replace the existing row with the updated row
|
||||||
|
* @param {TelemetryTableRow} incomingRow to update
|
||||||
|
* @param {number} index of the existing row in the collection to update
|
||||||
|
*/
|
||||||
|
updateRowInPlace(incomingRow, index) {
|
||||||
|
// Update the incoming row, not the existing row
|
||||||
|
const existingRow = this.rows[index];
|
||||||
|
incomingRow.updateWithDatum(existingRow);
|
||||||
|
|
||||||
|
// Replacing the existing row with the updated, incoming row will trigger Vue reactivity
|
||||||
|
// because the reference to the row has changed
|
||||||
|
this.rows.splice(index, 1, incomingRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
setLimit(rowLimit) {
|
setLimit(rowLimit) {
|
||||||
|
@ -373,7 +373,6 @@ export default {
|
|||||||
configuredColumnWidths: configuration.columnWidths,
|
configuredColumnWidths: configuration.columnWidths,
|
||||||
sizingRows: {},
|
sizingRows: {},
|
||||||
rowHeight: ROW_HEIGHT,
|
rowHeight: ROW_HEIGHT,
|
||||||
scrollOffset: 0,
|
|
||||||
totalHeight: 0,
|
totalHeight: 0,
|
||||||
totalWidth: 0,
|
totalWidth: 0,
|
||||||
rowOffset: 0,
|
rowOffset: 0,
|
||||||
@ -552,6 +551,7 @@ export default {
|
|||||||
//Default sort
|
//Default sort
|
||||||
this.sortOptions = this.table.tableRows.sortBy();
|
this.sortOptions = this.table.tableRows.sortBy();
|
||||||
this.scrollable = this.$refs.scrollable;
|
this.scrollable = this.$refs.scrollable;
|
||||||
|
this.lastScrollLeft = this.scrollable.scrollLeft;
|
||||||
this.contentTable = this.$refs.contentTable;
|
this.contentTable = this.$refs.contentTable;
|
||||||
this.sizingTable = this.$refs.sizingTable;
|
this.sizingTable = this.$refs.sizingTable;
|
||||||
this.headersHolderEl = this.$refs.headersHolderEl;
|
this.headersHolderEl = this.$refs.headersHolderEl;
|
||||||
@ -740,7 +740,9 @@ export default {
|
|||||||
this.table.sortBy(this.sortOptions);
|
this.table.sortBy(this.sortOptions);
|
||||||
},
|
},
|
||||||
scroll() {
|
scroll() {
|
||||||
this.throttledUpdateVisibleRows();
|
if (this.lastScrollLeft === this.scrollable.scrollLeft) {
|
||||||
|
this.throttledUpdateVisibleRows();
|
||||||
|
}
|
||||||
this.synchronizeScrollX();
|
this.synchronizeScrollX();
|
||||||
|
|
||||||
if (this.shouldAutoScroll()) {
|
if (this.shouldAutoScroll()) {
|
||||||
@ -765,6 +767,8 @@ export default {
|
|||||||
this.scrollable.scrollTop = Number.MAX_SAFE_INTEGER;
|
this.scrollable.scrollTop = Number.MAX_SAFE_INTEGER;
|
||||||
},
|
},
|
||||||
synchronizeScrollX() {
|
synchronizeScrollX() {
|
||||||
|
this.lastScrollLeft = this.scrollable.scrollLeft;
|
||||||
|
|
||||||
if (this.$refs.headersHolderEl && this.scrollable) {
|
if (this.$refs.headersHolderEl && this.scrollable) {
|
||||||
this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
|
this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user