mirror of
https://github.com/nasa/openmct.git
synced 2024-12-19 21:27:52 +00:00
resolve conflicts
This commit is contained in:
commit
ae03fa4121
@ -8,8 +8,8 @@ executors:
|
||||
- image: mcr.microsoft.com/playwright:v1.47.2-focal
|
||||
environment:
|
||||
NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
|
||||
PERCY_POSTINSTALL_BROWSER: "true" # Needed to store the percy browser in cache deps
|
||||
PERCY_LOGLEVEL: "debug" # Enable DEBUG level logging for Percy (Issue: https://github.com/nasa/openmct/issues/5742)
|
||||
PERCY_POSTINSTALL_BROWSER: 'true' # Needed to store the percy browser in cache deps
|
||||
PERCY_LOGLEVEL: 'debug' # Enable DEBUG level logging for Percy (Issue: https://github.com/nasa/openmct/issues/5742)
|
||||
PERCY_PARALLEL_TOTAL: 2
|
||||
ubuntu:
|
||||
machine:
|
||||
@ -17,7 +17,7 @@ executors:
|
||||
docker_layer_caching: true
|
||||
commands:
|
||||
build_and_install:
|
||||
description: "All steps used to build and install."
|
||||
description: 'All steps used to build and install.'
|
||||
parameters:
|
||||
node-version:
|
||||
type: string
|
||||
@ -27,7 +27,7 @@ commands:
|
||||
node-version: << parameters.node-version >>
|
||||
- node/install-packages
|
||||
generate_and_store_version_and_filesystem_artifacts:
|
||||
description: "Track important packages and files"
|
||||
description: 'Track important packages and files'
|
||||
steps:
|
||||
- run: |
|
||||
[[ $EUID -ne 0 ]] && (sudo mkdir -p /tmp/artifacts && sudo chmod 777 /tmp/artifacts) || (mkdir -p /tmp/artifacts && chmod 777 /tmp/artifacts)
|
||||
@ -61,7 +61,7 @@ commands:
|
||||
[[ $EUID -ne 0 ]] && sudo chmod +x codecov || chmod +x codecov
|
||||
./codecov --help
|
||||
generate_e2e_code_cov_report:
|
||||
description: "Generate e2e code coverage artifacts and publish to codecov.io. Needed to that we can ignore the exit code status of the npm run test"
|
||||
description: 'Generate e2e code coverage artifacts and publish to codecov.io. Needed to that we can ignore the exit code status of the npm run test'
|
||||
parameters:
|
||||
suite:
|
||||
type: string
|
||||
@ -135,13 +135,13 @@ jobs:
|
||||
suite: #ci or full
|
||||
type: string
|
||||
executor: pw-focal-development
|
||||
parallelism: 7
|
||||
parallelism: 8
|
||||
steps:
|
||||
- build_and_install:
|
||||
node-version: lts/hydrogen
|
||||
- when: #Only install chrome-beta when running the 'full' suite to save $$$
|
||||
condition:
|
||||
equal: ["full", <<parameters.suite>>]
|
||||
equal: ['full', <<parameters.suite>>]
|
||||
steps:
|
||||
- run: npx playwright install chrome-beta
|
||||
- run:
|
||||
@ -323,7 +323,7 @@ workflows:
|
||||
- e2e-couchdb
|
||||
triggers:
|
||||
- schedule:
|
||||
cron: "0 0 * * *"
|
||||
cron: '0 0 * * *'
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
|
@ -243,6 +243,37 @@ async function createExampleTelemetryObject(page, parent = 'mine') {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Stable State Telemetry Object (State Generator) for use in visual tests
|
||||
* and tests against plotting telemetry (e.g. logPlot tests). This will change state every 2 seconds.
|
||||
* @param {import('@playwright/test').Page} page
|
||||
* @param {string | import('../src/api/objects/ObjectAPI').Identifier} [parent] the uuid or identifier of the parent object. Defaults to 'mine'
|
||||
* @returns {Promise<CreatedObjectInfo>} An object containing information about the telemetry object.
|
||||
*/
|
||||
async function createStableStateTelemetry(page, parent = 'mine') {
|
||||
const parentUrl = await getHashUrlToDomainObject(page, parent);
|
||||
|
||||
await page.goto(`${parentUrl}`);
|
||||
const createdObject = await createDomainObjectWithDefaults(page, {
|
||||
type: 'State Generator',
|
||||
name: 'Stable State Generator'
|
||||
});
|
||||
// edit the state generator to have a 1 second update rate
|
||||
await page.getByLabel('More actions').click();
|
||||
await page.getByRole('menuitem', { name: 'Edit Properties...' }).click();
|
||||
await page.getByLabel('State Duration (seconds)', { exact: true }).fill('2');
|
||||
await page.getByLabel('Save').click();
|
||||
// Wait until the URL is updated
|
||||
const uuid = await getFocusedObjectUuid(page);
|
||||
const url = await getHashUrlToDomainObject(page, uuid);
|
||||
|
||||
return {
|
||||
name: createdObject.name,
|
||||
uuid,
|
||||
url
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates directly to a given object url, in fixed time mode, with the given start and end bounds. Note: does not set
|
||||
* default view type.
|
||||
@ -645,14 +676,34 @@ async function getCanvasPixels(page, canvasSelector) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for telemetry and link it to an object. objectName should come from the domainObject.name function.
|
||||
* @param {import('@playwright/test').Page} page
|
||||
* @param {string} parameterName
|
||||
* @param {string} objectName
|
||||
*/
|
||||
async function linkParameterToObject(page, parameterName, objectName) {
|
||||
await page.getByRole('searchbox', { name: 'Search Input' }).click();
|
||||
await page.getByRole('searchbox', { name: 'Search Input' }).fill(parameterName);
|
||||
await page.getByLabel('Object Results').getByText(parameterName).click();
|
||||
await page.getByLabel('More actions').click();
|
||||
await page.getByLabel('Create Link').click();
|
||||
await page.getByLabel('Modal Overlay').getByLabel('Search Input').click();
|
||||
await page.getByLabel('Modal Overlay').getByLabel('Search Input').fill(objectName);
|
||||
await page.getByLabel('Modal Overlay').getByLabel(`Navigate to ${objectName}`).click();
|
||||
await page.getByLabel('Save').click();
|
||||
}
|
||||
|
||||
export {
|
||||
createDomainObjectWithDefaults,
|
||||
createExampleTelemetryObject,
|
||||
createNotification,
|
||||
createPlanFromJSON,
|
||||
createStableStateTelemetry,
|
||||
expandEntireTree,
|
||||
getCanvasPixels,
|
||||
getDomainObject,
|
||||
linkParameterToObject,
|
||||
navigateToObjectWithFixedTimeBounds,
|
||||
navigateToObjectWithRealTime,
|
||||
setEndOffset,
|
||||
|
@ -26,8 +26,10 @@ import {
|
||||
createExampleTelemetryObject,
|
||||
createNotification,
|
||||
createPlanFromJSON,
|
||||
createStableStateTelemetry,
|
||||
expandEntireTree,
|
||||
getCanvasPixels,
|
||||
linkParameterToObject,
|
||||
navigateToObjectWithFixedTimeBounds,
|
||||
navigateToObjectWithRealTime,
|
||||
setEndOffset,
|
||||
@ -339,4 +341,23 @@ test.describe('AppActions @framework', () => {
|
||||
// Expect this step to fail
|
||||
await waitForPlotsToRender(page, { timeout: 1000 });
|
||||
});
|
||||
test('createStableStateTelemetry', async ({ page }) => {
|
||||
const stableStateTelemetry = await createStableStateTelemetry(page);
|
||||
expect(stableStateTelemetry.name).toBe('Stable State Generator');
|
||||
expect(stableStateTelemetry.url).toBe(`./#/browse/mine/${stableStateTelemetry.uuid}`);
|
||||
expect(stableStateTelemetry.uuid).toBeDefined();
|
||||
});
|
||||
test('linkParameterToObject', async ({ page }) => {
|
||||
const displayLayout = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Display Layout',
|
||||
name: 'Test Display Layout'
|
||||
});
|
||||
const exampleTelemetry = await createExampleTelemetryObject(page);
|
||||
|
||||
await linkParameterToObject(page, exampleTelemetry.name, displayLayout.name);
|
||||
await page.goto(displayLayout.url);
|
||||
await expect(page.getByRole('main').getByText('Test Display Layout')).toBeVisible();
|
||||
await expandEntireTree(page);
|
||||
await expect(page.getByLabel('Navigate to VIPER Rover').first()).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2023, United States Government
|
||||
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2023, United States Government
|
||||
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
|
@ -0,0 +1,163 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
/*
|
||||
This test suite is dedicated to tests which verify the basic operations surrounding conditionSets and styling
|
||||
*/
|
||||
|
||||
import {
|
||||
createDomainObjectWithDefaults,
|
||||
linkParameterToObject,
|
||||
setRealTimeMode
|
||||
} from '../../../../appActions.js';
|
||||
import { MISSION_TIME } from '../../../../constants.js';
|
||||
import { expect, test } from '../../../../pluginFixtures.js';
|
||||
|
||||
test.describe('Conditionally Styling, using a Condition Set', () => {
|
||||
let stateGenerator;
|
||||
let conditionSet;
|
||||
let displayLayout;
|
||||
const STATE_CHANGE_INTERVAL = '1';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Install the clock and set the time to the mission time such that the state generator will be controllable
|
||||
await page.clock.install({ time: MISSION_TIME });
|
||||
await page.clock.resume();
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
// Create Condition Set, State Generator, and Display Layout
|
||||
conditionSet = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Condition Set',
|
||||
name: 'Test Condition Set'
|
||||
});
|
||||
stateGenerator = await createDomainObjectWithDefaults(page, {
|
||||
type: 'State Generator',
|
||||
name: 'One Second State Generator'
|
||||
});
|
||||
// edit the state generator to have a 1 second update rate
|
||||
await page.getByTitle('More actions').click();
|
||||
await page.getByRole('menuitem', { name: 'Edit Properties...' }).click();
|
||||
await page.getByLabel('State Duration (seconds)', { exact: true }).fill(STATE_CHANGE_INTERVAL);
|
||||
await page.getByLabel('Save').click();
|
||||
|
||||
displayLayout = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Display Layout',
|
||||
name: 'Test Display Layout'
|
||||
});
|
||||
});
|
||||
|
||||
test('Conditional styling, using a Condition Set, will style correctly based on the output @clock', async ({
|
||||
page
|
||||
}) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/nasa/openmct/issues/7840'
|
||||
});
|
||||
|
||||
// set up the condition set to use the state generator
|
||||
await page.goto(conditionSet.url, { waitUntil: 'domcontentloaded' });
|
||||
|
||||
// Add the State Generator to the Condition Set by dragging from the main tree
|
||||
await page.getByLabel('Show selected item in tree').click();
|
||||
await page
|
||||
.getByRole('tree', {
|
||||
name: 'Main Tree'
|
||||
})
|
||||
.getByRole('treeitem', {
|
||||
name: stateGenerator.name
|
||||
})
|
||||
.dragTo(page.locator('#conditionCollection'));
|
||||
|
||||
// Add the state generator to the first criterion such that there is a condition named 'OFF' when the state generator is off
|
||||
await page.getByLabel('Add Condition').click();
|
||||
await page
|
||||
.getByLabel('Criterion Telemetry Selection')
|
||||
.selectOption({ label: stateGenerator.name });
|
||||
await page.getByLabel('Criterion Metadata Selection').selectOption({ label: 'State' });
|
||||
await page.getByLabel('Criterion Comparison Selection').selectOption({ label: 'is' });
|
||||
await page.getByLabel('Condition Name Input').first().fill('OFF');
|
||||
await page.getByLabel('Save').click();
|
||||
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
||||
|
||||
await linkParameterToObject(page, stateGenerator.name, displayLayout.name);
|
||||
|
||||
//Add a box to the display layout
|
||||
await page.goto(displayLayout.url, { waitUntil: 'domcontentloaded' });
|
||||
await page.getByLabel('Edit Object').click();
|
||||
|
||||
//Add a box to the display layout and move it to the right
|
||||
//TEMP: Click the layout such that the state generator is deselected
|
||||
await page.getByLabel('Test Display Layout Layout Grid').locator('div').nth(1).click();
|
||||
await page.getByLabel('Add Drawing Object').click();
|
||||
await page.getByText('Box').click();
|
||||
await page.getByLabel('X:').click();
|
||||
await page.getByLabel('X:').fill('10');
|
||||
await page.getByLabel('X:').press('Enter');
|
||||
|
||||
// set up conditional styling such that the box is red when the state generator condition is 'OFF'
|
||||
await page.getByRole('tab', { name: 'Styles' }).click();
|
||||
await page.getByRole('button', { name: 'Use Conditional Styling...' }).click();
|
||||
await page.getByLabel('Modal Overlay').getByLabel('Expand My Items folder').click();
|
||||
await page.getByLabel('Modal Overlay').getByLabel(`Preview ${conditionSet.name}`).click();
|
||||
await page.getByText('Ok').click();
|
||||
await page.getByLabel('Set background color').first().click();
|
||||
await page.getByLabel('#ff0000').click();
|
||||
await page.getByLabel('Save', { exact: true }).click();
|
||||
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
||||
|
||||
await setRealTimeMode(page);
|
||||
|
||||
//Pause at a time when the state generator is 'OFF' which is 20 minutes in the future
|
||||
await page.clock.pauseAt(new Date(MISSION_TIME + 1200000));
|
||||
|
||||
const redBG = 'background-color: rgb(255, 0, 0);';
|
||||
const defaultBG = 'background-color: rgb(102, 102, 102);';
|
||||
const textElement = page.getByLabel('Alpha-numeric telemetry value').locator('div:first-child');
|
||||
const styledElement = page.getByLabel('Box', { exact: true });
|
||||
|
||||
await page.clock.resume();
|
||||
|
||||
// Check if the style is red when text is 'OFF'
|
||||
await expect(textElement).toHaveText('OFF');
|
||||
await waitForStyleChange(styledElement, redBG);
|
||||
|
||||
// Fast forward to the next state change
|
||||
await page.clock.fastForward(STATE_CHANGE_INTERVAL * 1000);
|
||||
|
||||
// Check if the style is not red when text is 'ON'
|
||||
await expect(textElement).toHaveText('ON');
|
||||
await waitForStyleChange(styledElement, defaultBG);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Wait for the style of an element to change to the expected style.
|
||||
* @param {import('@playwright/test').Locator} element - The element to check.
|
||||
* @param {string} expectedStyle - The expected style to wait for.
|
||||
* @param {number} timeout - The timeout in milliseconds.
|
||||
*/
|
||||
async function waitForStyleChange(element, expectedStyle, timeout = 0) {
|
||||
await expect(async () => {
|
||||
const style = await element.getAttribute('style');
|
||||
|
||||
// eslint-disable-next-line playwright/prefer-web-first-assertions
|
||||
expect(style).toBe(expectedStyle);
|
||||
}).toPass({ timeout: 1000 }); // timeout allows for the style to be applied
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2023, United States Government
|
||||
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2023, United States Government
|
||||
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
|
@ -22,7 +22,11 @@
|
||||
|
||||
import percySnapshot from '@percy/playwright';
|
||||
|
||||
import { createDomainObjectWithDefaults } from '../../appActions.js';
|
||||
import {
|
||||
createDomainObjectWithDefaults,
|
||||
createStableStateTelemetry,
|
||||
linkParameterToObject
|
||||
} from '../../appActions.js';
|
||||
import { MISSION_TIME, VISUAL_FIXED_URL } from '../../constants.js';
|
||||
import { test } from '../../pluginFixtures.js';
|
||||
|
||||
@ -47,16 +51,13 @@ test.describe('Visual - Display Layout @clock', () => {
|
||||
name: 'Child Right Layout',
|
||||
parent: parentLayout.uuid
|
||||
});
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
name: 'SWG 1',
|
||||
parent: child1Layout.uuid
|
||||
});
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
name: 'SWG 2',
|
||||
parent: child2Layout.uuid
|
||||
});
|
||||
|
||||
const stableStateTelemetry = await createStableStateTelemetry(page);
|
||||
await linkParameterToObject(page, stableStateTelemetry.name, child1Layout.name);
|
||||
await linkParameterToObject(page, stableStateTelemetry.name, child2Layout.name);
|
||||
|
||||
// Pause the clock at a time where the telemetry is stable 20 minutes in the future
|
||||
await page.clock.pauseAt(new Date(MISSION_TIME + 1200000));
|
||||
|
||||
await page.goto(parentLayout.url, { waitUntil: 'domcontentloaded' });
|
||||
await page.getByRole('button', { name: 'Edit Object' }).click();
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2023, United States Government
|
||||
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
|
@ -128,7 +128,7 @@
|
||||
"test:perf:contract": "npm test --workspace e2e -- --config=playwright-performance-dev.config.js",
|
||||
"test:perf:localhost": "npm test --workspace e2e -- --config=playwright-performance-prod.config.js --project=chrome",
|
||||
"test:perf:memory": "npm test --workspace e2e -- --config=playwright-performance-prod.config.js --project=chrome-memory",
|
||||
"update-about-dialog-copyright": "perl -pi -e 's/20\\d\\d\\-202\\d/2014\\-2023/gm' ./src/ui/layout/AboutDialog.vue",
|
||||
"update-about-dialog-copyright": "perl -pi -e 's/20\\d\\d\\-202\\d/2014\\-2024/gm' ./src/ui/layout/AboutDialog.vue",
|
||||
"update-copyright-date": "npm run update-about-dialog-copyright && grep -lr --null --include=*.{js,scss,vue,ts,sh,html,md,frag} 'Copyright (c) 20' . | xargs -r0 perl -pi -e 's/Copyright\\s\\(c\\)\\s20\\d\\d\\-20\\d\\d/Copyright \\(c\\)\\ 2014\\-2024/gm'",
|
||||
"cov:e2e:report": "nyc report --reporter=lcovonly --report-dir=./coverage/e2e",
|
||||
"prepare": "npm run build:prod && npx tsc"
|
||||
|
@ -231,26 +231,20 @@ export default class TelemetryAPI {
|
||||
* @returns {TelemetryRequestOptions} the options, with defaults filled in
|
||||
*/
|
||||
standardizeRequestOptions(options = {}) {
|
||||
if (!Object.hasOwn(options, 'start')) {
|
||||
const bounds = options.timeContext?.getBounds();
|
||||
if (bounds?.start) {
|
||||
options.start = options.timeContext.getBounds().start;
|
||||
} else {
|
||||
options.start = this.openmct.time.getBounds().start;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Object.hasOwn(options, 'end')) {
|
||||
const bounds = options.timeContext?.getBounds();
|
||||
if (bounds?.end) {
|
||||
options.end = options.timeContext.getBounds().end;
|
||||
} else {
|
||||
options.end = this.openmct.time.getBounds().end;
|
||||
}
|
||||
if (!Object.hasOwn(options, 'timeContext')) {
|
||||
options.timeContext = this.openmct.time;
|
||||
}
|
||||
|
||||
if (!Object.hasOwn(options, 'domain')) {
|
||||
options.domain = this.openmct.time.getTimeSystem().key;
|
||||
options.domain = options.timeContext.getTimeSystem().key;
|
||||
}
|
||||
|
||||
if (!Object.hasOwn(options, 'start')) {
|
||||
options.start = options.timeContext.getBounds().start;
|
||||
}
|
||||
|
||||
if (!Object.hasOwn(options, 'end')) {
|
||||
options.end = options.timeContext.getBounds().end;
|
||||
}
|
||||
|
||||
return options;
|
||||
|
@ -269,36 +269,40 @@ describe('Telemetry API', () => {
|
||||
|
||||
await telemetryAPI.request(domainObject);
|
||||
const { signal } = new AbortController();
|
||||
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(jasmine.any(Object), {
|
||||
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(domainObject, {
|
||||
signal,
|
||||
start: 0,
|
||||
end: 1,
|
||||
domain: 'system'
|
||||
domain: 'system',
|
||||
timeContext: openmct.time
|
||||
});
|
||||
|
||||
expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), {
|
||||
expect(telemetryProvider.request).toHaveBeenCalledWith(domainObject, {
|
||||
signal,
|
||||
start: 0,
|
||||
end: 1,
|
||||
domain: 'system'
|
||||
domain: 'system',
|
||||
timeContext: openmct.time
|
||||
});
|
||||
|
||||
telemetryProvider.supportsRequest.calls.reset();
|
||||
telemetryProvider.request.calls.reset();
|
||||
|
||||
await telemetryAPI.request(domainObject, {});
|
||||
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(jasmine.any(Object), {
|
||||
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(domainObject, {
|
||||
signal,
|
||||
start: 0,
|
||||
end: 1,
|
||||
domain: 'system'
|
||||
domain: 'system',
|
||||
timeContext: openmct.time
|
||||
});
|
||||
|
||||
expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), {
|
||||
expect(telemetryProvider.request).toHaveBeenCalledWith(domainObject, {
|
||||
signal,
|
||||
start: 0,
|
||||
end: 1,
|
||||
domain: 'system'
|
||||
domain: 'system',
|
||||
timeContext: openmct.time
|
||||
});
|
||||
});
|
||||
|
||||
@ -313,18 +317,20 @@ describe('Telemetry API', () => {
|
||||
domain: 'someDomain'
|
||||
});
|
||||
const { signal } = new AbortController();
|
||||
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(jasmine.any(Object), {
|
||||
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(domainObject, {
|
||||
start: 20,
|
||||
end: 30,
|
||||
domain: 'someDomain',
|
||||
signal
|
||||
signal,
|
||||
timeContext: openmct.time
|
||||
});
|
||||
|
||||
expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), {
|
||||
expect(telemetryProvider.request).toHaveBeenCalledWith(domainObject, {
|
||||
start: 20,
|
||||
end: 30,
|
||||
domain: 'someDomain',
|
||||
signal
|
||||
signal,
|
||||
timeContext: openmct.time
|
||||
});
|
||||
});
|
||||
describe('telemetry batching support', () => {
|
||||
|
@ -62,9 +62,6 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
this.futureBuffer = [];
|
||||
this.parseTime = undefined;
|
||||
this.metadata = this.openmct.telemetry.getMetadata(domainObject);
|
||||
if (!Object.hasOwn(options, 'timeContext')) {
|
||||
options.timeContext = this.openmct.time;
|
||||
}
|
||||
this.options = options;
|
||||
this.unsubscribe = undefined;
|
||||
this.pageState = undefined;
|
||||
@ -84,6 +81,9 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
this._error(LOADED_ERROR);
|
||||
}
|
||||
|
||||
if (!Object.hasOwn(this.options, 'timeContext')) {
|
||||
this.options.timeContext = this.openmct.time;
|
||||
}
|
||||
this._setTimeSystem(this.options.timeContext.getTimeSystem());
|
||||
this.lastBounds = this.options.timeContext.getBounds();
|
||||
// prioritize passed options over time bounds
|
||||
@ -93,9 +93,6 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
if (this.options.end) {
|
||||
this.lastBounds.end = this.options.end;
|
||||
}
|
||||
console.debug(
|
||||
`🫙 Bounds for collection are start ${new Date(this.lastBounds.start).toISOString()} and end ${new Date(this.lastBounds.end).toISOString()}`
|
||||
);
|
||||
this._watchBounds();
|
||||
this._watchTimeSystem();
|
||||
this._watchTimeModeChange();
|
||||
@ -140,10 +137,7 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
* @private
|
||||
*/
|
||||
async _requestHistoricalTelemetry() {
|
||||
console.debug(
|
||||
`🫙 Requesting historical telemetry with start ${new Date(this.lastBounds.start).toISOString()} and end ${new Date(this.lastBounds.end).toISOString()}}`
|
||||
);
|
||||
let options = this.openmct.telemetry.standardizeRequestOptions({ ...this.options });
|
||||
const options = this.openmct.telemetry.standardizeRequestOptions({ ...this.options });
|
||||
const historicalProvider = this.openmct.telemetry.findRequestProvider(
|
||||
this.domainObject,
|
||||
options
|
||||
@ -243,19 +237,6 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
beforeStartOfBounds = parsedValue < boundsToUse.start;
|
||||
afterEndOfBounds = parsedValue > boundsToUse.end;
|
||||
|
||||
if (beforeStartOfBounds) {
|
||||
console.debug(
|
||||
`🫙 Datum is BEFORE start of bounds: ${new Date(parsedValue).toISOString()} < ${new Date(this.lastBounds.start).toISOString()}`,
|
||||
this.options
|
||||
);
|
||||
}
|
||||
if (afterEndOfBounds) {
|
||||
console.debug(
|
||||
`🫙 Datum is AFTER start of bounds: ${new Date(parsedValue).toISOString()} < ${new Date(this.lastBounds.start).toISOString()}`,
|
||||
this.options
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!afterEndOfBounds &&
|
||||
(!beforeStartOfBounds || (this.isStrategyLatest && this.openmct.telemetry.greedyLAD()))
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import _ from 'lodash';
|
||||
|
||||
export default class CompsManager extends EventEmitter {
|
||||
#openmct;
|
||||
@ -12,6 +11,8 @@ export default class CompsManager extends EventEmitter {
|
||||
#loaded = false;
|
||||
#compositionLoaded = false;
|
||||
#telemetryProcessors = {};
|
||||
#loadVersion = 0;
|
||||
#currentLoadPromise = null;
|
||||
|
||||
constructor(openmct, domainObject) {
|
||||
super();
|
||||
@ -59,7 +60,9 @@ export default class CompsManager extends EventEmitter {
|
||||
name: `${this.#getNextAlphabeticalParameterName()}`,
|
||||
valueToUse,
|
||||
testValue: 0,
|
||||
timeMetaData
|
||||
timeMetaData,
|
||||
accumulateValues: false,
|
||||
sampleSize: null
|
||||
});
|
||||
this.emit('parameterAdded', this.#domainObject);
|
||||
}
|
||||
@ -108,23 +111,56 @@ export default class CompsManager extends EventEmitter {
|
||||
}
|
||||
|
||||
async load(telemetryOptions) {
|
||||
if (!_.isEqual(telemetryOptions, this.#telemetryOptions)) {
|
||||
// Increment the load version to mark a new load operation
|
||||
const loadVersion = ++this.#loadVersion;
|
||||
|
||||
if (!_.isEqual(this.#telemetryOptions, telemetryOptions)) {
|
||||
console.debug(
|
||||
`😩 Reloading comps manager ${this.#domainObject.name} due to telemetry options change.`,
|
||||
telemetryOptions
|
||||
);
|
||||
this.#destroy();
|
||||
}
|
||||
|
||||
this.#telemetryOptions = telemetryOptions;
|
||||
if (!this.#compositionLoaded) {
|
||||
await this.#loadComposition();
|
||||
this.#compositionLoaded = true;
|
||||
}
|
||||
if (!this.#loaded) {
|
||||
await this.#startListeningToUnderlyingTelemetry();
|
||||
this.#telemetryLoadedPromises = [];
|
||||
this.#loaded = true;
|
||||
}
|
||||
|
||||
// Start the load process and store the promise
|
||||
this.#currentLoadPromise = (async () => {
|
||||
// Load composition if not already loaded
|
||||
if (!this.#compositionLoaded) {
|
||||
await this.#loadComposition();
|
||||
// Check if a newer load has been initiated
|
||||
if (loadVersion !== this.#loadVersion) {
|
||||
console.debug(
|
||||
`🔄 Reloading comps manager in composition wait ${this.#domainObject.name} due to newer load.`
|
||||
);
|
||||
await this.#currentLoadPromise;
|
||||
return;
|
||||
}
|
||||
this.#compositionLoaded = true;
|
||||
}
|
||||
|
||||
// Start listening to telemetry if not already done
|
||||
if (!this.#loaded) {
|
||||
await this.#startListeningToUnderlyingTelemetry();
|
||||
// Check again for newer load
|
||||
if (loadVersion !== this.#loadVersion) {
|
||||
console.debug(
|
||||
`🔄 Reloading comps manager in telemetry wait ${this.#domainObject.name} due to newer load.`
|
||||
);
|
||||
await this.#currentLoadPromise;
|
||||
return;
|
||||
}
|
||||
console.debug(
|
||||
`✅ Comps manager ${this.#domainObject.name} is ready.`,
|
||||
this.#telemetryCollections
|
||||
);
|
||||
this.#loaded = true;
|
||||
}
|
||||
})();
|
||||
|
||||
// Await the load process
|
||||
await this.#currentLoadPromise;
|
||||
}
|
||||
|
||||
async #startListeningToUnderlyingTelemetry() {
|
||||
@ -173,27 +209,45 @@ export default class CompsManager extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
getFullDataFrame(newTelemetry) {
|
||||
const dataFrame = {};
|
||||
// can assume on data item
|
||||
const newTelemetryKey = Object.keys(newTelemetry)[0];
|
||||
const newTelemetryData = newTelemetry[newTelemetryKey];
|
||||
const otherTelemetryKeys = Object.keys(this.#telemetryCollections).filter(
|
||||
(keyString) => keyString !== newTelemetryKey
|
||||
#getParameterForKeyString(keyString) {
|
||||
return this.#domainObject.configuration.comps.parameters.find(
|
||||
(parameter) => parameter.keyString === keyString
|
||||
);
|
||||
// initialize the data frame with the new telemetry data
|
||||
dataFrame[newTelemetryKey] = newTelemetryData;
|
||||
// initialize the other telemetry data
|
||||
}
|
||||
|
||||
getTelemetryForComps(newTelemetry) {
|
||||
const telemetryForComps = {};
|
||||
const newTelemetryKey = Object.keys(newTelemetry)[0];
|
||||
const newTelemetryParameter = this.#getParameterForKeyString(newTelemetryKey);
|
||||
const newTelemetryData = newTelemetry[newTelemetryKey];
|
||||
const otherTelemetryKeys = Object.keys(this.#telemetryCollections).slice(0);
|
||||
if (newTelemetryParameter.accumulateValues) {
|
||||
telemetryForComps[newTelemetryKey] = this.#telemetryCollections[newTelemetryKey].getAll();
|
||||
} else {
|
||||
telemetryForComps[newTelemetryKey] = newTelemetryData;
|
||||
}
|
||||
otherTelemetryKeys.forEach((keyString) => {
|
||||
dataFrame[keyString] = [];
|
||||
telemetryForComps[keyString] = [];
|
||||
});
|
||||
|
||||
// march through the new telemetry data and add data to the frame from the other telemetry objects
|
||||
// using LOCF
|
||||
const otherTelemetryKeysNotAccumulating = otherTelemetryKeys.filter(
|
||||
(keyString) => !this.#getParameterForKeyString(keyString).accumulateValues
|
||||
);
|
||||
const otherTelemetryKeysAccumulating = otherTelemetryKeys.filter(
|
||||
(keyString) => this.#getParameterForKeyString(keyString).accumulateValues
|
||||
);
|
||||
|
||||
// if we're accumulating, just add all the data
|
||||
otherTelemetryKeysAccumulating.forEach((keyString) => {
|
||||
telemetryForComps[keyString] = this.#telemetryCollections[keyString].getAll();
|
||||
});
|
||||
|
||||
// for the others, march through the new telemetry data and add data to the frame from the other telemetry objects
|
||||
// using LOCF
|
||||
newTelemetryData.forEach((newDatum) => {
|
||||
otherTelemetryKeys.forEach((otherKeyString) => {
|
||||
otherTelemetryKeysNotAccumulating.forEach((otherKeyString) => {
|
||||
const otherCollection = this.#telemetryCollections[otherKeyString];
|
||||
// otherwise we need to find the closest datum to the new datum
|
||||
let insertionPointForNewData = otherCollection._sortedIndex(newDatum);
|
||||
const otherCollectionData = otherCollection.getAll();
|
||||
if (insertionPointForNewData && insertionPointForNewData >= otherCollectionData.length) {
|
||||
@ -202,11 +256,11 @@ export default class CompsManager extends EventEmitter {
|
||||
// get the closest datum to the new datum
|
||||
const closestDatum = otherCollectionData[insertionPointForNewData];
|
||||
if (closestDatum) {
|
||||
dataFrame[otherKeyString].push(closestDatum);
|
||||
telemetryForComps[otherKeyString].push(closestDatum);
|
||||
}
|
||||
});
|
||||
});
|
||||
return dataFrame;
|
||||
return telemetryForComps;
|
||||
}
|
||||
|
||||
#removeTelemetryObject = (telemetryObjectIdentifier) => {
|
||||
|
@ -5,17 +5,19 @@ onconnect = function (e) {
|
||||
const port = e.ports[0];
|
||||
|
||||
port.onmessage = function (event) {
|
||||
const { type, callbackID, telemetryForComps, expression, parameters } = event.data;
|
||||
const { type, callbackID, telemetryForComps, expression, parameters, newTelemetry } =
|
||||
event.data;
|
||||
let responseType = 'unknown';
|
||||
let error = null;
|
||||
let result = [];
|
||||
try {
|
||||
if (type === 'calculateRequest') {
|
||||
responseType = 'calculationRequestResult';
|
||||
console.debug(`📫 Received new calculation request with callback ID ${callbackID}`);
|
||||
result = calculateRequest(telemetryForComps, parameters, expression);
|
||||
} else if (type === 'calculateSubscription') {
|
||||
responseType = 'calculationSubscriptionResult';
|
||||
result = calculateSubscription(telemetryForComps, parameters, expression);
|
||||
result = calculateSubscription(telemetryForComps, newTelemetry, parameters, expression);
|
||||
} else if (type === 'init') {
|
||||
port.postMessage({ type: 'ready' });
|
||||
return;
|
||||
@ -25,6 +27,7 @@ onconnect = function (e) {
|
||||
} catch (errorInCalculation) {
|
||||
error = errorInCalculation;
|
||||
}
|
||||
console.debug(`📭 Sending response for callback ID ${callbackID}`, result);
|
||||
port.postMessage({ type: responseType, callbackID, result, error });
|
||||
};
|
||||
};
|
||||
@ -40,9 +43,16 @@ function getFullDataFrame(telemetryForComps, parameters) {
|
||||
return dataFrame;
|
||||
}
|
||||
|
||||
function calculateSubscription(telemetryForComps, parameters, expression) {
|
||||
function calculateSubscription(telemetryForComps, newTelemetry, parameters, expression) {
|
||||
const dataFrame = getFullDataFrame(telemetryForComps, parameters);
|
||||
return calculate(dataFrame, parameters, expression);
|
||||
const calculation = calculate(dataFrame, parameters, expression);
|
||||
const newTelemetryKey = Object.keys(newTelemetry)[0];
|
||||
const newTelemetrySize = newTelemetry[newTelemetryKey].length;
|
||||
let trimmedCalculation = calculation;
|
||||
if (calculation.length > newTelemetrySize) {
|
||||
trimmedCalculation = calculation.slice(calculation.length - newTelemetrySize);
|
||||
}
|
||||
return trimmedCalculation;
|
||||
}
|
||||
|
||||
function calculateRequest(telemetryForComps, parameters, expression) {
|
||||
@ -56,14 +66,40 @@ function calculate(dataFrame, parameters, expression) {
|
||||
if (!expression) {
|
||||
return sumResults;
|
||||
}
|
||||
// set up accumulated data structure
|
||||
const accumulatedData = {};
|
||||
parameters.forEach((parameter) => {
|
||||
if (parameter.accumulateValues) {
|
||||
accumulatedData[parameter.name] = [];
|
||||
}
|
||||
});
|
||||
|
||||
// take the first parameter keyString as the reference
|
||||
const referenceParameter = parameters[0];
|
||||
const otherParameters = parameters.slice(1);
|
||||
// iterate over the reference telemetry data
|
||||
const referenceTelemetry = dataFrame[referenceParameter.keyString];
|
||||
referenceTelemetry?.forEach((referenceTelemetryItem) => {
|
||||
let referenceValue = referenceTelemetryItem[referenceParameter.valueToUse];
|
||||
if (referenceParameter.accumulateValues) {
|
||||
accumulatedData[referenceParameter.name].push(referenceValue);
|
||||
referenceValue = accumulatedData[referenceParameter.name];
|
||||
}
|
||||
if (
|
||||
referenceParameter.accumulateValues &&
|
||||
referenceParameter.sampleSize &&
|
||||
referenceParameter.sampleSize > 0
|
||||
) {
|
||||
// enforce sample size by ensuring referenceValue has the latest n elements
|
||||
// if we don't have at least the sample size, skip this iteration
|
||||
if (!referenceValue.length || referenceValue.length < referenceParameter.sampleSize) {
|
||||
return;
|
||||
}
|
||||
referenceValue = referenceValue.slice(-referenceParameter.sampleSize);
|
||||
}
|
||||
|
||||
const scope = {
|
||||
[referenceParameter.name]: referenceTelemetryItem[referenceParameter.valueToUse]
|
||||
[referenceParameter.name]: referenceValue
|
||||
};
|
||||
const referenceTime = referenceTelemetryItem[referenceParameter.timeKey];
|
||||
// iterate over the other parameters to set the scope
|
||||
@ -75,7 +111,12 @@ function calculate(dataFrame, parameters, expression) {
|
||||
missingData = true;
|
||||
return;
|
||||
}
|
||||
scope[parameter.name] = otherTelemetry[parameter.valueToUse];
|
||||
let otherValue = otherTelemetry[parameter.valueToUse];
|
||||
if (parameter.accumulateValues) {
|
||||
accumulatedData[parameter.name].push(referenceValue);
|
||||
otherValue = accumulatedData[referenceParameter.name];
|
||||
}
|
||||
scope[parameter.name] = otherValue;
|
||||
});
|
||||
if (missingData) {
|
||||
return;
|
||||
|
@ -61,6 +61,7 @@ export default class CompsMetadataProvider {
|
||||
key: 'compsOutput',
|
||||
source: 'compsOutput',
|
||||
name: 'Output',
|
||||
derived: true,
|
||||
formatString: specificCompsManager.getOutputFormat(),
|
||||
hints: {
|
||||
range: 1
|
||||
|
@ -95,7 +95,7 @@ export default class CompsTelemetryProvider {
|
||||
return;
|
||||
}
|
||||
const expression = specificCompsManager.getExpression();
|
||||
const telemetryForComps = specificCompsManager.getFullDataFrame(newTelemetry);
|
||||
const telemetryForComps = specificCompsManager.getTelemetryForComps(newTelemetry);
|
||||
const parameters = JSON.parse(JSON.stringify(specificCompsManager.getParameters()));
|
||||
if (!expression || !parameters) {
|
||||
return;
|
||||
@ -103,6 +103,7 @@ export default class CompsTelemetryProvider {
|
||||
const payload = {
|
||||
type: 'calculateSubscription',
|
||||
telemetryForComps,
|
||||
newTelemetry,
|
||||
expression,
|
||||
parameters,
|
||||
callbackID
|
||||
@ -134,10 +135,6 @@ export default class CompsTelemetryProvider {
|
||||
);
|
||||
return () => {
|
||||
delete this.#subscriptionCallbacks[callbackID];
|
||||
console.debug(
|
||||
`🛑 Stopping subscription for ${domainObject.name} with callback ID ${callbackID}. We now have ${Object.keys(this.#subscriptionCallbacks).length} subscribers`,
|
||||
this.#subscriptionCallbacks
|
||||
);
|
||||
specificCompsManager.stopListeningToUnderlyingTelemetry();
|
||||
specificCompsManager.off('underlyingTelemetryUpdated', boundComputeOnNewTelemetry);
|
||||
};
|
||||
|
@ -95,6 +95,37 @@
|
||||
</option>
|
||||
</select>
|
||||
<div v-else>{{ parameter.valueToUse }}</div>
|
||||
<div
|
||||
:class="[
|
||||
'c-comps__refs-controls c-cdef__controls',
|
||||
{ disabled: !parameters?.length }
|
||||
]"
|
||||
>
|
||||
<label v-if="isEditing" class="c-toggle-switch">
|
||||
<span class="c-toggle-switch__label">Accumulate Values</span>
|
||||
<input
|
||||
v-model="parameter.accumulateValues"
|
||||
type="checkbox"
|
||||
@change="updateAccumulateValues(parameter)"
|
||||
/>
|
||||
<span
|
||||
class="c-toggle-switch__slider"
|
||||
aria-label="Toggle Parameter Accumulation"
|
||||
></span>
|
||||
</label>
|
||||
|
||||
<span v-if="isEditing && parameter.accumulateValues" class="c-test-datum__string"
|
||||
>Sample Size</span
|
||||
>
|
||||
<input
|
||||
v-if="isEditing && parameter.accumulateValues"
|
||||
v-model="parameter.sampleSize"
|
||||
:aria-label="`Sample Size for ${parameter.name}`"
|
||||
type="number"
|
||||
class="c-input--md"
|
||||
@change="updateParameters"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
<span v-if="isEditing" class="c-test-datum__string">Test value</span>
|
||||
@ -104,7 +135,7 @@
|
||||
:aria-label="`Reference Test Value for ${parameter.name}`"
|
||||
type="text"
|
||||
class="c-input--md"
|
||||
@change="updateParameters"
|
||||
@change="updateTestValue(parameter)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -235,6 +266,22 @@ function updateParameters() {
|
||||
applyTestData();
|
||||
}
|
||||
|
||||
function updateAccumulateValues(parameter) {
|
||||
if (parameter.accumulateValues) {
|
||||
parameter.testValue = [''];
|
||||
} else {
|
||||
parameter.testValue = '';
|
||||
}
|
||||
updateParameters();
|
||||
}
|
||||
|
||||
function updateTestValue(parameter) {
|
||||
if (parameter.accumulateValues && parameter.testValue === '') {
|
||||
parameter.testValue = [];
|
||||
}
|
||||
updateParameters();
|
||||
}
|
||||
|
||||
function toggleTestData() {
|
||||
testDataApplied.value = !testDataApplied.value;
|
||||
if (testDataApplied.value) {
|
||||
@ -270,6 +317,20 @@ function applyTestData() {
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// see which parameters are misconfigured as non-arrays
|
||||
const misconfiguredParameterNames = parameters.value
|
||||
.filter((parameter) => {
|
||||
return parameter.accumulateValues && !Array.isArray(scope[parameter.name]);
|
||||
})
|
||||
.map((parameter) => parameter.name);
|
||||
if (misconfiguredParameterNames.length) {
|
||||
const misconfiguredParameterNamesString = misconfiguredParameterNames.join(', ');
|
||||
currentTestOutput.value = null;
|
||||
expressionOutput.value = `Reference "${misconfiguredParameterNamesString}" set to accumulating, but test values aren't arrays.`;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const testOutput = evaluate(expression.value, scope);
|
||||
const formattedData = getValueFormatter().format(testOutput);
|
||||
|
@ -69,7 +69,6 @@ const INNER_TEXT_PADDING = 15;
|
||||
const TEXT_LEFT_PADDING = 5;
|
||||
const ROW_PADDING = 5;
|
||||
const SWIMLANE_PADDING = 3;
|
||||
const RESIZE_POLL_INTERVAL = 200;
|
||||
const ROW_HEIGHT = 22;
|
||||
const MAX_TEXT_WIDTH = 300;
|
||||
const MIN_ACTIVITY_WIDTH = 2;
|
||||
@ -143,13 +142,15 @@ export default {
|
||||
this.canvasContext = canvas.getContext('2d');
|
||||
this.setDimensions();
|
||||
this.setTimeContext();
|
||||
this.resizeTimer = setInterval(this.resize, RESIZE_POLL_INTERVAL);
|
||||
this.handleConfigurationChange(this.configuration);
|
||||
this.planViewConfiguration.on('change', this.handleConfigurationChange);
|
||||
this.loadComposition();
|
||||
|
||||
this.resizeObserver = new ResizeObserver(this.resize);
|
||||
this.resizeObserver.observe(this.$refs.plan);
|
||||
},
|
||||
beforeUnmount() {
|
||||
clearInterval(this.resizeTimer);
|
||||
this.resizeObserver.disconnect();
|
||||
this.stopFollowingTimeContext();
|
||||
if (this.unlisten) {
|
||||
this.unlisten();
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2023, United States Government
|
||||
Open MCT, Copyright (c) 2014-2024, United States Government
|
||||
as represented by the Administrator of the National Aeronautics and Space
|
||||
Administration. All rights reserved.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2023, United States Government
|
||||
Open MCT, Copyright (c) 2014-2024, United States Government
|
||||
as represented by the Administrator of the National Aeronautics and Space
|
||||
Administration. All rights reserved.
|
||||
|
||||
|
@ -225,7 +225,13 @@ export default class PlotSeries extends Model {
|
||||
|
||||
try {
|
||||
const points = await this.openmct.telemetry.request(this.domainObject, options);
|
||||
const data = this.getSeriesData();
|
||||
// if derived, we can't use the old data
|
||||
let data = this.getSeriesData();
|
||||
|
||||
if (this.metadata.value(this.get('yKey')).derived) {
|
||||
data = [];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line you-dont-need-lodash-underscore/concat
|
||||
const newPoints = _(data)
|
||||
.concat(points)
|
||||
|
@ -133,7 +133,6 @@ export default {
|
||||
if (!styleObj || !elemToStyle) {
|
||||
return;
|
||||
}
|
||||
|
||||
// handle visibility separately
|
||||
if (styleObj.isStyleInvisible !== undefined) {
|
||||
elemToStyle.classList.toggle(STYLE_CONSTANTS.isStyleInvisible, styleObj.isStyleInvisible);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2023, United States Government
|
||||
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2023, United States Government
|
||||
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
|
@ -152,15 +152,18 @@ export default class RemoteClock extends DefaultClock {
|
||||
*/
|
||||
#waitForReady() {
|
||||
const waitForInitialTick = (resolve) => {
|
||||
if (this.lastTick > 0) {
|
||||
const offsets = this.openmct.time.getClockOffsets();
|
||||
resolve({
|
||||
start: this.lastTick + offsets.start,
|
||||
end: this.lastTick + offsets.end
|
||||
});
|
||||
} else {
|
||||
setTimeout(() => waitForInitialTick(resolve), 100);
|
||||
}
|
||||
const tickListener = () => {
|
||||
if (this.lastTick > 0) {
|
||||
const offsets = this.openmct.time.getClockOffsets();
|
||||
this.openmct.time.off('tick', tickListener); // Unregister the tick listener
|
||||
resolve({
|
||||
start: this.lastTick + offsets.start,
|
||||
end: this.lastTick + offsets.end
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.openmct.time.on('tick', tickListener);
|
||||
};
|
||||
|
||||
return new Promise(waitForInitialTick);
|
||||
|
@ -20,16 +20,43 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Intercepts requests to ensure the remote clock is ready.
|
||||
*
|
||||
* @param {import('../../openmct').OpenMCT} openmct - The OpenMCT instance.
|
||||
* @param {import('../../openmct').Identifier} _remoteClockIdentifier - The identifier for the remote clock.
|
||||
* @param {Function} waitForBounds - A function that returns a promise resolving to the initial bounds.
|
||||
* @returns {Object} The request interceptor.
|
||||
*/
|
||||
function remoteClockRequestInterceptor(openmct, _remoteClockIdentifier, waitForBounds) {
|
||||
let remoteClockLoaded = false;
|
||||
|
||||
return {
|
||||
appliesTo: () => {
|
||||
/**
|
||||
* Determines if the interceptor applies to the given request.
|
||||
*
|
||||
* @param {Object} _ - Unused parameter.
|
||||
* @param {import('../../api/telemetry/TelemetryAPI').TelemetryRequestOptions} request - The request object.
|
||||
* @returns {boolean} True if the interceptor applies, false otherwise.
|
||||
*/
|
||||
appliesTo: (_, request) => {
|
||||
// Get the activeClock from the Global Time Context
|
||||
/** @type {import("../../api/time/TimeContext").default} */
|
||||
const { activeClock } = openmct.time;
|
||||
|
||||
// this type of request does not rely on clock having bounds
|
||||
if (request.strategy === 'latest' && request.timeContext.isRealTime()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return activeClock?.key === 'remote-clock' && !remoteClockLoaded;
|
||||
},
|
||||
/**
|
||||
* Invokes the interceptor to modify the request.
|
||||
*
|
||||
* @param {Object} request - The request object.
|
||||
* @returns {Promise<Object>} The modified request object.
|
||||
*/
|
||||
invoke: async (request) => {
|
||||
const timeContext = request?.timeContext ?? openmct.time;
|
||||
|
||||
|
@ -150,13 +150,13 @@ export default class TableRowCollection extends EventEmitter {
|
||||
}
|
||||
|
||||
insertOrUpdateRows(rowsToAdd, addToBeginning) {
|
||||
rowsToAdd.forEach((row) => {
|
||||
rowsToAdd.forEach((row, addRowsIndex) => {
|
||||
const index = this.getInPlaceUpdateIndex(row);
|
||||
if (index > -1) {
|
||||
this.updateRowInPlace(row, index);
|
||||
} else {
|
||||
if (addToBeginning) {
|
||||
this.rows.unshift(row);
|
||||
this.rows.splice(addRowsIndex, 0, row);
|
||||
} else {
|
||||
this.rows.push(row);
|
||||
}
|
||||
|
@ -150,7 +150,7 @@ export default {
|
||||
mounted() {
|
||||
this.handleNewBounds = _.throttle(this.handleNewBounds, 300, {
|
||||
leading: true,
|
||||
trailing: false
|
||||
trailing: true
|
||||
});
|
||||
this.setTimeSystem(this.copy(this.openmct.time.getTimeSystem()));
|
||||
this.openmct.time.on(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setTimeSystem);
|
||||
@ -181,6 +181,8 @@ export default {
|
||||
}
|
||||
},
|
||||
stopFollowingTime() {
|
||||
this.handleNewBounds.cancel();
|
||||
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off(TIME_CONTEXT_EVENTS.boundsChanged, this.handleNewBounds);
|
||||
this.timeContext.off(TIME_CONTEXT_EVENTS.clockOffsetsChanged, this.setViewFromOffsets);
|
||||
|
@ -1,16 +1,13 @@
|
||||
/******************************************************** PROGRESS BAR */
|
||||
@keyframes progressIndeterminate {
|
||||
0% {
|
||||
left: 0;
|
||||
width: 0;
|
||||
transform:scaleX(0);
|
||||
}
|
||||
70% {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
90% {
|
||||
transform:scaleX(1);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
left: 100%;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@ -24,11 +21,10 @@
|
||||
|
||||
&__bar {
|
||||
background: $colorProgressBar;
|
||||
height: 100%;
|
||||
min-height: $progressBarMinH;
|
||||
transform-origin: left;
|
||||
|
||||
&.--indeterminate {
|
||||
position: absolute;
|
||||
@include abs();
|
||||
animation: progressIndeterminate 1.5s ease-in infinite;
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@
|
||||
<h1 class="l-title s-title">Open MCT</h1>
|
||||
<div class="l-description s-description">
|
||||
<p>
|
||||
Open MCT, Copyright © 2014-2023, United States Government as represented by the
|
||||
Open MCT, Copyright © 2014-2024, United States Government as represented by the
|
||||
Administrator of the National Aeronautics and Space Administration. All rights reserved.
|
||||
</p>
|
||||
<p>
|
||||
|
Loading…
Reference in New Issue
Block a user