Skip to content

Commit

Permalink
fix vue reactivity of rows by changing the reference of the updated r…
Browse files Browse the repository at this point in the history
…ow (#7940)

* do not call `updateVisibleRows` on horizontal scroll
* add example provider for in place row updates
  • Loading branch information
davetsay authored Dec 4, 2024
1 parent 61b982a commit 14b947c
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -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');
});
});
});
18 changes: 18 additions & 0 deletions example/eventGenerator/EventMetadataProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
70 changes: 70 additions & 0 deletions example/eventGenerator/EventWithAcknowledgeTelemetryProvider.js
Original file line number Diff line number Diff line change
@@ -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;
16 changes: 16 additions & 0 deletions example/eventGenerator/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
*****************************************************************************/
import EventMetadataProvider from './EventMetadataProvider.js';
import EventTelemetryProvider from './EventTelemetryProvider.js';
import EventWithAcknowledgeTelemetryProvider from './EventWithAcknowledgeTelemetryProvider.js';

export default function EventGeneratorPlugin(options) {
return function install(openmct) {
Expand All @@ -38,5 +39,20 @@ export default function EventGeneratorPlugin(options) {
});
openmct.telemetry.addProvider(new EventTelemetryProvider());
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());
};
}
12 changes: 8 additions & 4 deletions src/plugins/telemetryTable/TelemetryTableRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,19 @@ export default class TelemetryTableRow {
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,
...normalizedUpdatesToDatum
...row.datum
};

this.fullDatum = {
...this.fullDatum,
...updatesToDatum
...row.fullDatum
};
}
}
Expand Down
25 changes: 21 additions & 4 deletions src/plugins/telemetryTable/collections/TableRowCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ import { EventEmitter } from 'eventemitter3';
import _ from 'lodash';

import { ORDER } from '../constants.js';

/**
* @typedef {import('.TelemetryTableRow.js').default} TelemetryTableRow
*/

/**
* @constructor
*/
Expand Down Expand Up @@ -124,10 +129,22 @@ export default class TableRowCollection extends EventEmitter {
return foundIndex;
}

updateRowInPlace(row, index) {
const foundRow = this.rows[index];
foundRow.updateWithDatum(row.datum);
this.rows[index] = foundRow;
/**
* `incomingRow` exists in the collection,
* so merge existing and incoming row properties
*
* 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) {
Expand Down
8 changes: 6 additions & 2 deletions src/plugins/telemetryTable/components/TableComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,6 @@ export default {
configuredColumnWidths: configuration.columnWidths,
sizingRows: {},
rowHeight: ROW_HEIGHT,
scrollOffset: 0,
totalHeight: 0,
totalWidth: 0,
rowOffset: 0,
Expand Down Expand Up @@ -552,6 +551,7 @@ export default {
//Default sort
this.sortOptions = this.table.tableRows.sortBy();
this.scrollable = this.$refs.scrollable;
this.lastScrollLeft = this.scrollable.scrollLeft;
this.contentTable = this.$refs.contentTable;
this.sizingTable = this.$refs.sizingTable;
this.headersHolderEl = this.$refs.headersHolderEl;
Expand Down Expand Up @@ -740,7 +740,9 @@ export default {
this.table.sortBy(this.sortOptions);
},
scroll() {
this.throttledUpdateVisibleRows();
if (this.lastScrollLeft === this.scrollable.scrollLeft) {
this.throttledUpdateVisibleRows();
}
this.synchronizeScrollX();

if (this.shouldAutoScroll()) {
Expand All @@ -765,6 +767,8 @@ export default {
this.scrollable.scrollTop = Number.MAX_SAFE_INTEGER;
},
synchronizeScrollX() {
this.lastScrollLeft = this.scrollable.scrollLeft;

if (this.$refs.headersHolderEl && this.scrollable) {
this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
}
Expand Down

0 comments on commit 14b947c

Please sign in to comment.