Prevent infinite loop when updating a table row in place (#7154)

* bump index on update row in place

* add test

* Removing problematic test

* spelling
This commit is contained in:
Scott Bell 2023-10-23 18:22:13 +02:00 committed by GitHub
parent 2daec448da
commit 13311b9fc8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 56 additions and 23 deletions

View File

@ -153,40 +153,41 @@ define(['lodash', 'EventEmitter'], function (_, EventEmitter) {
}); });
} }
mergeSortedRows(rows) { mergeSortedRows(incomingRows) {
const mergedRows = []; const mergedRows = [];
let i = 0; let existingRowIndex = 0;
let j = 0; let incomingRowIndex = 0;
while (i < this.rows.length && j < rows.length) { while (existingRowIndex < this.rows.length && incomingRowIndex < incomingRows.length) {
const existingRow = this.rows[i]; const existingRow = this.rows[existingRowIndex];
const incomingRow = rows[j]; const incomingRow = incomingRows[incomingRowIndex];
const index = this.getInPlaceUpdateIndex(incomingRow); const inPlaceIndex = this.getInPlaceUpdateIndex(incomingRow);
if (index > -1) { if (inPlaceIndex > -1) {
this.updateRowInPlace(incomingRow, index); this.updateRowInPlace(incomingRow, inPlaceIndex);
incomingRowIndex++;
} else { } else {
if (this.firstRowInSortOrder(existingRow, incomingRow) === existingRow) { if (this.firstRowInSortOrder(existingRow, incomingRow) === existingRow) {
mergedRows.push(existingRow); mergedRows.push(existingRow);
i++; existingRowIndex++;
} else { } else {
mergedRows.push(incomingRow); mergedRows.push(incomingRow);
j++; incomingRowIndex++;
} }
} }
} }
// tail of existing rows is all that is left to merge // tail of existing rows is all that is left to merge
if (i < this.rows.length) { if (existingRowIndex < this.rows.length) {
for (i; i < this.rows.length; i++) { for (existingRowIndex; existingRowIndex < this.rows.length; existingRowIndex++) {
mergedRows.push(this.rows[i]); mergedRows.push(this.rows[existingRowIndex]);
} }
} }
// tail of incoming rows is all that is left to merge // tail of incoming rows is all that is left to merge
if (j < rows.length) { if (incomingRowIndex < incomingRows.length) {
for (j; j < rows.length; j++) { for (incomingRowIndex; incomingRowIndex < incomingRows.length; incomingRowIndex++) {
mergedRows.push(rows[j]); mergedRows.push(incomingRows[incomingRowIndex]);
} }
} }

View File

@ -49,7 +49,7 @@ describe('the plugin', () => {
let tablePlugin; let tablePlugin;
let element; let element;
let child; let child;
let historicalProvider; let historicalTelemetryProvider;
let originalRouterPath; let originalRouterPath;
let unlistenConfigMutation; let unlistenConfigMutation;
@ -61,12 +61,12 @@ describe('the plugin', () => {
tablePlugin = new TablePlugin(); tablePlugin = new TablePlugin();
openmct.install(tablePlugin); openmct.install(tablePlugin);
historicalProvider = { historicalTelemetryProvider = {
request: () => { request: () => {
return Promise.resolve([]); return Promise.resolve([]);
} }
}; };
spyOn(openmct.telemetry, 'findRequestProvider').and.returnValue(historicalProvider); spyOn(openmct.telemetry, 'findRequestProvider').and.returnValue(historicalTelemetryProvider);
element = document.createElement('div'); element = document.createElement('div');
child = document.createElement('div'); child = document.createElement('div');
@ -137,11 +137,12 @@ describe('the plugin', () => {
let tableView; let tableView;
let tableInstance; let tableInstance;
let mockClock; let mockClock;
let telemetryCallback;
beforeEach(async () => { beforeEach(async () => {
openmct.time.timeSystem('utc', { openmct.time.timeSystem('utc', {
start: 0, start: 0,
end: 4 end: 10
}); });
mockClock = jasmine.createSpyObj('clock', ['on', 'off', 'currentValue']); mockClock = jasmine.createSpyObj('clock', ['on', 'off', 'currentValue']);
@ -151,7 +152,7 @@ describe('the plugin', () => {
openmct.time.addClock(mockClock); openmct.time.addClock(mockClock);
openmct.time.clock('mockClock', { openmct.time.clock('mockClock', {
start: 0, start: 0,
end: 4 end: 10
}); });
testTelemetryObject = { testTelemetryObject = {
@ -214,7 +215,21 @@ describe('the plugin', () => {
} }
]; ];
historicalProvider.request = () => Promise.resolve(testTelemetry); historicalTelemetryProvider.request = () => {
return Promise.resolve(testTelemetry);
};
const realtimeTelemetryProvider = {
supportsSubscribe: () => true,
subscribe: (domainObject, passedCallback) => {
telemetryCallback = passedCallback;
return Promise.resolve(() => {});
}
};
spyOn(openmct.telemetry, 'findSubscriptionProvider').and.returnValue(
realtimeTelemetryProvider
);
openmct.router.path = [testTelemetryObject]; openmct.router.path = [testTelemetryObject];
@ -256,6 +271,23 @@ describe('the plugin', () => {
expect(rows.length).toBe(3); expect(rows.length).toBe(3);
}); });
it('Adds a row in place when updating with existing telemetry', async () => {
let rows = element.querySelectorAll('table.c-telemetry-table__body tr');
await nextTick();
expect(rows.length).toBe(3);
// fire some telemetry
const newTelemetry = {
utc: 2,
'some-key': 'some-value 2',
'some-other-key': 'spacecraft'
};
spyOn(tableInstance.tableRows, 'getInPlaceUpdateIndex').and.returnValue(1);
spyOn(tableInstance.tableRows, 'updateRowInPlace').and.callThrough();
telemetryCallback(newTelemetry);
expect(tableInstance.tableRows.updateRowInPlace.calls.count()).toBeGreaterThan(0);
});
it('Renders a column for every item in telemetry metadata', () => { it('Renders a column for every item in telemetry metadata', () => {
let headers = element.querySelectorAll('span.c-telemetry-table__headers__label'); let headers = element.querySelectorAll('span.c-telemetry-table__headers__label');
expect(headers.length).toBe(4); expect(headers.length).toBe(4);