Skip to content

Commit

Permalink
Table performance paging (#7399)
Browse files Browse the repository at this point in the history
* dereactifying the row before passing it to the commponent

* debouncin

* i mean... throttle

* initial

* UI functionality, switching between modes, prevention of export in performance mode, respect size option in swgs

* added limit maintenance in table row collectins, autoscroll respecting sort order

* updating the logic to work correctly :)

* added handling for overflow rows, this way if an object is removed, we can go back to the most recent rows for all remaining items and repopulate the table if necessary

* removing debug row numbers

* Closes #7268
- Layout and style sanding and polishing.
- Added title to button.
- More direct button labeling.

* Closes #7268
Partially closes #7147
- Removed footer hover behavior: table footer now always visible.
- Tweaks to style, margin etc. to make footer more compact.

* moved row limiting out of table row collections and into telemetry collections, table row collections will only limit what they return in getRows, handling sorting when in different modes

* have swgs return enough data to fill the requested bounds

* support minmax in swgs

* using undefined for more clarity

* clearing up boolean typo

* Address lint fixes

* removing autoscroll for descending, it is not necessary

* update snapshots

* lint

---------

Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
  • Loading branch information
3 people authored Jan 26, 2024
1 parent b985619 commit b9df97e
Show file tree
Hide file tree
Showing 19 changed files with 369 additions and 95 deletions.
3 changes: 2 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,8 @@
"gcov",
"WCAG",
"stackedplot",
"Andale"
"Andale",
"checksnapshots"
],
"dictionaries": ["npm", "softwareTerms", "node", "html", "css", "bash", "en_US"],
"ignorePaths": [
Expand Down
8 changes: 7 additions & 1 deletion e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ For those interested in the mechanics of snapshot testing with Playwright, you c
// from our package.json or circleCI configuration file
docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v{X.X.X}-focal /bin/bash
npm install
npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @snapshot
npm run test:e2e:checksnapshots
```

### Updating Snapshots
Expand All @@ -134,6 +134,12 @@ npm install
npm run test:e2e:updatesnapshots
```

Once that's done, you'll need to run the following to verify that the changes do not cause more problems:

```sh
npm run test:e2e:checksnapshots
```

## Automated Accessibility (a11y) Testing

Open MCT incorporates accessibility testing through two primary methods to ensure its compliance with accessibility standards:
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions example/generator/GeneratorProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ GeneratorProvider.prototype.request = function (domainObject, request) {
var workerRequest = this.makeWorkerRequest(domainObject, request);
workerRequest.start = request.start;
workerRequest.end = request.end;
workerRequest.size = request.size;
workerRequest.strategy = request.strategy;

return this.workerInterface.request(workerRequest);
};
Expand Down
86 changes: 52 additions & 34 deletions example/generator/generatorWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,48 +130,37 @@
var now = Date.now();
var start = request.start;
var end = request.end > now ? now : request.end;
var amplitude = request.amplitude;
var period = request.period;
var offset = request.offset;
var dataRateInHz = request.dataRateInHz;
var phase = request.phase;
var randomness = request.randomness;
var loadDelay = Math.max(request.loadDelay, 0);
var infinityValues = request.infinityValues;
var exceedFloat32 = request.exceedFloat32;

var size = request.size;
var duration = end - start;
var step = 1000 / dataRateInHz;
var maxPoints = Math.floor(duration / step);
var nextStep = start - (start % step) + step;

var data = [];

for (; nextStep < end && data.length < 5000; nextStep += step) {
data.push({
utc: nextStep,
yesterday: nextStep - 60 * 60 * 24 * 1000,
sin: sin(
nextStep,
period,
amplitude,
offset,
phase,
randomness,
infinityValues,
exceedFloat32
),
wavelengths: wavelengths(),
intensities: intensities(),
cos: cos(
nextStep,
period,
amplitude,
offset,
phase,
randomness,
infinityValues,
exceedFloat32
)
});
if (request.strategy === 'minmax' && size) {
// Calculate the number of cycles to include based on size (2 points per cycle)
var totalCycles = Math.min(Math.floor(size / 2), Math.floor(duration / period));

for (let cycle = 0; cycle < totalCycles; cycle++) {
// Distribute cycles evenly across the time range
let cycleStart = start + (duration / totalCycles) * cycle;
let minPointTime = cycleStart; // Assuming min at the start of the cycle
let maxPointTime = cycleStart + period / 2; // Assuming max at the halfway of the cycle

data.push(createDataPoint(minPointTime, request), createDataPoint(maxPointTime, request));
}
} else {
for (let i = 0; i < maxPoints && nextStep < end; i++, nextStep += step) {
data.push(createDataPoint(nextStep, request));
}
}

if (request.strategy !== 'minmax' && size) {
data = data.slice(-size);
}

if (loadDelay === 0) {
Expand All @@ -181,6 +170,35 @@
}
}

function createDataPoint(time, request) {
return {
utc: time,
yesterday: time - 60 * 60 * 24 * 1000,
sin: sin(
time,
request.period,
request.amplitude,
request.offset,
request.phase,
request.randomness,
request.infinityValues,
request.exceedFloat32
),
wavelengths: wavelengths(),
intensities: intensities(),
cos: cos(
time,
request.period,
request.amplitude,
request.offset,
request.phase,
request.randomness,
request.infinityValues,
request.exceedFloat32
)
};
}

function postOnRequest(message, request, data) {
self.postMessage({
id: message.id,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
"test:e2e:unstable": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @unstable",
"test:e2e:local": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome",
"test:e2e:generatedata": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @generatedata",
"test:e2e:checksnapshots": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @snapshot --retries=0",
"test:e2e:updatesnapshots": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @snapshot --update-snapshots",
"test:e2e:visual:ci": "percy exec --config ./e2e/.percy.ci.yml --partial -- npx playwright test --config=e2e/playwright-visual-a11y.config.js --project=chrome --grep-invert @unstable",
"test:e2e:visual:full": "percy exec --config ./e2e/.percy.nightly.yml -- npx playwright test --config=e2e/playwright-visual-a11y.config.js --grep-invert @unstable",
Expand Down
9 changes: 9 additions & 0 deletions src/api/telemetry/TelemetryCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ export default class TelemetryCollection extends EventEmitter {
let added = [];
let addedIndices = [];
let hasDataBeforeStartBound = false;
let size = this.options.size;
let enforceSize = size !== undefined && this.options.enforceSize;

// loop through, sort and dedupe
for (let datum of data) {
Expand Down Expand Up @@ -271,6 +273,13 @@ export default class TelemetryCollection extends EventEmitter {
}
} else {
this.emit('add', added, addedIndices);

if (enforceSize && this.boundedTelemetry.length > size) {
const removeCount = this.boundedTelemetry.length - size;
const removed = this.boundedTelemetry.splice(0, removeCount);

this.emit('remove', removed);
}
}
}
}
Expand Down
34 changes: 31 additions & 3 deletions src/plugins/telemetryTable/TelemetryTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ export default class TelemetryTable extends EventEmitter {

this.domainObject = domainObject;
this.openmct = openmct;
this.rowCount = 100;
this.tableComposition = undefined;
this.datumCache = [];
this.configuration = new TelemetryTableConfiguration(domainObject, openmct);
this.telemetryMode = this.configuration.getTelemetryMode();
this.rowLimit = this.configuration.getRowLimit();
this.paused = false;
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);

Expand Down Expand Up @@ -101,18 +102,40 @@ export default class TelemetryTable extends EventEmitter {
}
}

updateTelemetryMode(mode) {
if (this.telemetryMode === mode) {
return;
}

this.telemetryMode = mode;

this.updateRowLimit();

this.clearAndResubscribe();
}

updateRowLimit() {
if (this.telemetryMode === 'performance') {
this.tableRows.setLimit(this.rowLimit);
} else {
this.tableRows.removeLimit();
}
}

createTableRowCollections() {
this.tableRows = new TableRowCollection();

//Fetch any persisted default sort
let sortOptions = this.configuration.getConfiguration().sortOptions;

//If no persisted sort order, default to sorting by time system, ascending.
//If no persisted sort order, default to sorting by time system, descending.
sortOptions = sortOptions || {
key: this.openmct.time.timeSystem().key,
direction: 'asc'
direction: 'desc'
};

this.updateRowLimit();

this.tableRows.sortBy(sortOptions);
this.tableRows.on('resetRowsFromAllData', this.resetRowsFromAllData);
}
Expand Down Expand Up @@ -144,6 +167,11 @@ export default class TelemetryTable extends EventEmitter {

this.removeTelemetryCollection(keyString);

if (this.telemetryMode === 'performance') {
requestOptions.size = this.rowLimit;
requestOptions.enforceSize = true;
}

this.telemetryCollections[keyString] = this.openmct.telemetry.requestCollection(
telemetryObject,
requestOptions
Expand Down
40 changes: 40 additions & 0 deletions src/plugins/telemetryTable/TelemetryTableConfiguration.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ export default class TelemetryTableConfiguration extends EventEmitter {
configuration.columnOrder = configuration.columnOrder || [];
configuration.cellFormat = configuration.cellFormat || {};
configuration.autosize = configuration.autosize === undefined ? true : configuration.autosize;
// anything that doesn't have a telemetryMode existed before the change and should stay as it was for consistency
configuration.telemetryMode = configuration.telemetryMode ?? 'unlimited';
configuration.persistModeChange = configuration.persistModeChange ?? true;
configuration.rowLimit = configuration.rowLimit ?? 50;

return configuration;
}
Expand Down Expand Up @@ -137,6 +141,42 @@ export default class TelemetryTableConfiguration extends EventEmitter {
}, {});
}

getTelemetryMode() {
let configuration = this.getConfiguration();

return configuration.telemetryMode;
}

setTelemetryMode(mode) {
let configuration = this.getConfiguration();
configuration.telemetryMode = mode;
this.updateConfiguration(configuration);
}

getRowLimit() {
let configuration = this.getConfiguration();

return configuration.rowLimit;
}

setRowLimit(limit) {
let configuration = this.getConfiguration();
configuration.rowLimit = limit;
this.updateConfiguration(configuration);
}

getPersistModeChange() {
let configuration = this.getConfiguration();

return configuration.persistModeChange;
}

setPersistModeChange(value) {
let configuration = this.getConfiguration();
configuration.persistModeChange = value;
this.updateConfiguration(configuration);
}

getColumnWidths() {
let configuration = this.getConfiguration();

Expand Down
68 changes: 54 additions & 14 deletions src/plugins/telemetryTable/TelemetryTableType.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,57 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/

export default {
name: 'Telemetry Table',
description:
'Display values for one or more telemetry end points in a scrolling table. Each row is a time-stamped value.',
creatable: true,
cssClass: 'icon-tabular-scrolling',
initialize(domainObject) {
domainObject.composition = [];
domainObject.configuration = {
columnWidths: {},
hiddenColumns: {}
};
}
};
export default function getTelemetryTableType(options = {}) {
const { telemetryMode = 'performance', persistModeChanges = true, rowLimit = 50 } = options;

return {
name: 'Telemetry Table',
description:
'Display values for one or more telemetry end points in a scrolling table. Each row is a time-stamped value.',
creatable: true,
cssClass: 'icon-tabular-scrolling',
form: [
{
key: 'telemetryMode',
name: 'Telemetry Mode',
control: 'select',
options: [
{
value: 'performance',
name: 'Performance Mode'
},
{
value: 'unlimited',
name: 'Unlimited Mode'
}
],
cssClass: 'l-inline',
property: ['configuration', 'telemetryMode']
},
{
name: 'Persist Telemetry Mode Changes',
control: 'toggleSwitch',
cssClass: 'l-input',
key: 'persistModeChanges',
property: ['configuration', 'persistModeChanges']
},
{
name: 'Performance Mode Row Limit',
control: 'toggleSwitch',
cssClass: 'l-input',
key: 'rowLimit',
property: ['configuration', 'rowLimit']
}
],
initialize(domainObject) {
domainObject.composition = [];
domainObject.configuration = {
columnWidths: {},
hiddenColumns: {},
telemetryMode,
persistModeChanges,
rowLimit
};
}
};
}
2 changes: 1 addition & 1 deletion src/plugins/telemetryTable/TelemetryTableView.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import TableComponent from './components/TableComponent.vue';
import TelemetryTable from './TelemetryTable.js';

export default class TelemetryTableView {
constructor(openmct, domainObject, objectPath) {
constructor(openmct, domainObject, objectPath, options) {
this.openmct = openmct;
this.domainObject = domainObject;
this.objectPath = objectPath;
Expand Down
Loading

0 comments on commit b9df97e

Please sign in to comment.