mirror of
https://github.com/nasa/openmct.git
synced 2024-12-20 05:37:53 +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
|
- image: mcr.microsoft.com/playwright:v1.47.2-focal
|
||||||
environment:
|
environment:
|
||||||
NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
|
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_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_LOGLEVEL: 'debug' # Enable DEBUG level logging for Percy (Issue: https://github.com/nasa/openmct/issues/5742)
|
||||||
PERCY_PARALLEL_TOTAL: 2
|
PERCY_PARALLEL_TOTAL: 2
|
||||||
ubuntu:
|
ubuntu:
|
||||||
machine:
|
machine:
|
||||||
@ -17,7 +17,7 @@ executors:
|
|||||||
docker_layer_caching: true
|
docker_layer_caching: true
|
||||||
commands:
|
commands:
|
||||||
build_and_install:
|
build_and_install:
|
||||||
description: "All steps used to build and install."
|
description: 'All steps used to build and install.'
|
||||||
parameters:
|
parameters:
|
||||||
node-version:
|
node-version:
|
||||||
type: string
|
type: string
|
||||||
@ -27,7 +27,7 @@ commands:
|
|||||||
node-version: << parameters.node-version >>
|
node-version: << parameters.node-version >>
|
||||||
- node/install-packages
|
- node/install-packages
|
||||||
generate_and_store_version_and_filesystem_artifacts:
|
generate_and_store_version_and_filesystem_artifacts:
|
||||||
description: "Track important packages and files"
|
description: 'Track important packages and files'
|
||||||
steps:
|
steps:
|
||||||
- run: |
|
- run: |
|
||||||
[[ $EUID -ne 0 ]] && (sudo mkdir -p /tmp/artifacts && sudo chmod 777 /tmp/artifacts) || (mkdir -p /tmp/artifacts && chmod 777 /tmp/artifacts)
|
[[ $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
|
[[ $EUID -ne 0 ]] && sudo chmod +x codecov || chmod +x codecov
|
||||||
./codecov --help
|
./codecov --help
|
||||||
generate_e2e_code_cov_report:
|
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:
|
parameters:
|
||||||
suite:
|
suite:
|
||||||
type: string
|
type: string
|
||||||
@ -135,13 +135,13 @@ jobs:
|
|||||||
suite: #ci or full
|
suite: #ci or full
|
||||||
type: string
|
type: string
|
||||||
executor: pw-focal-development
|
executor: pw-focal-development
|
||||||
parallelism: 7
|
parallelism: 8
|
||||||
steps:
|
steps:
|
||||||
- build_and_install:
|
- build_and_install:
|
||||||
node-version: lts/hydrogen
|
node-version: lts/hydrogen
|
||||||
- when: #Only install chrome-beta when running the 'full' suite to save $$$
|
- when: #Only install chrome-beta when running the 'full' suite to save $$$
|
||||||
condition:
|
condition:
|
||||||
equal: ["full", <<parameters.suite>>]
|
equal: ['full', <<parameters.suite>>]
|
||||||
steps:
|
steps:
|
||||||
- run: npx playwright install chrome-beta
|
- run: npx playwright install chrome-beta
|
||||||
- run:
|
- run:
|
||||||
@ -323,7 +323,7 @@ workflows:
|
|||||||
- e2e-couchdb
|
- e2e-couchdb
|
||||||
triggers:
|
triggers:
|
||||||
- schedule:
|
- schedule:
|
||||||
cron: "0 0 * * *"
|
cron: '0 0 * * *'
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only:
|
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
|
* 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.
|
* 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 {
|
export {
|
||||||
createDomainObjectWithDefaults,
|
createDomainObjectWithDefaults,
|
||||||
createExampleTelemetryObject,
|
createExampleTelemetryObject,
|
||||||
createNotification,
|
createNotification,
|
||||||
createPlanFromJSON,
|
createPlanFromJSON,
|
||||||
|
createStableStateTelemetry,
|
||||||
expandEntireTree,
|
expandEntireTree,
|
||||||
getCanvasPixels,
|
getCanvasPixels,
|
||||||
getDomainObject,
|
getDomainObject,
|
||||||
|
linkParameterToObject,
|
||||||
navigateToObjectWithFixedTimeBounds,
|
navigateToObjectWithFixedTimeBounds,
|
||||||
navigateToObjectWithRealTime,
|
navigateToObjectWithRealTime,
|
||||||
setEndOffset,
|
setEndOffset,
|
||||||
|
@ -26,8 +26,10 @@ import {
|
|||||||
createExampleTelemetryObject,
|
createExampleTelemetryObject,
|
||||||
createNotification,
|
createNotification,
|
||||||
createPlanFromJSON,
|
createPlanFromJSON,
|
||||||
|
createStableStateTelemetry,
|
||||||
expandEntireTree,
|
expandEntireTree,
|
||||||
getCanvasPixels,
|
getCanvasPixels,
|
||||||
|
linkParameterToObject,
|
||||||
navigateToObjectWithFixedTimeBounds,
|
navigateToObjectWithFixedTimeBounds,
|
||||||
navigateToObjectWithRealTime,
|
navigateToObjectWithRealTime,
|
||||||
setEndOffset,
|
setEndOffset,
|
||||||
@ -339,4 +341,23 @@ test.describe('AppActions @framework', () => {
|
|||||||
// Expect this step to fail
|
// Expect this step to fail
|
||||||
await waitForPlotsToRender(page, { timeout: 1000 });
|
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
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
* Administration. All rights reserved.
|
* 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
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
* Administration. All rights reserved.
|
* 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
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
* Administration. All rights reserved.
|
* 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
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
* Administration. All rights reserved.
|
* Administration. All rights reserved.
|
||||||
*
|
*
|
||||||
|
@ -22,7 +22,11 @@
|
|||||||
|
|
||||||
import percySnapshot from '@percy/playwright';
|
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 { MISSION_TIME, VISUAL_FIXED_URL } from '../../constants.js';
|
||||||
import { test } from '../../pluginFixtures.js';
|
import { test } from '../../pluginFixtures.js';
|
||||||
|
|
||||||
@ -47,16 +51,13 @@ test.describe('Visual - Display Layout @clock', () => {
|
|||||||
name: 'Child Right Layout',
|
name: 'Child Right Layout',
|
||||||
parent: parentLayout.uuid
|
parent: parentLayout.uuid
|
||||||
});
|
});
|
||||||
await createDomainObjectWithDefaults(page, {
|
|
||||||
type: 'Sine Wave Generator',
|
const stableStateTelemetry = await createStableStateTelemetry(page);
|
||||||
name: 'SWG 1',
|
await linkParameterToObject(page, stableStateTelemetry.name, child1Layout.name);
|
||||||
parent: child1Layout.uuid
|
await linkParameterToObject(page, stableStateTelemetry.name, child2Layout.name);
|
||||||
});
|
|
||||||
await createDomainObjectWithDefaults(page, {
|
// Pause the clock at a time where the telemetry is stable 20 minutes in the future
|
||||||
type: 'Sine Wave Generator',
|
await page.clock.pauseAt(new Date(MISSION_TIME + 1200000));
|
||||||
name: 'SWG 2',
|
|
||||||
parent: child2Layout.uuid
|
|
||||||
});
|
|
||||||
|
|
||||||
await page.goto(parentLayout.url, { waitUntil: 'domcontentloaded' });
|
await page.goto(parentLayout.url, { waitUntil: 'domcontentloaded' });
|
||||||
await page.getByRole('button', { name: 'Edit Object' }).click();
|
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
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
* Administration. All rights reserved.
|
* Administration. All rights reserved.
|
||||||
*
|
*
|
||||||
|
@ -128,7 +128,7 @@
|
|||||||
"test:perf:contract": "npm test --workspace e2e -- --config=playwright-performance-dev.config.js",
|
"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: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",
|
"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'",
|
"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",
|
"cov:e2e:report": "nyc report --reporter=lcovonly --report-dir=./coverage/e2e",
|
||||||
"prepare": "npm run build:prod && npx tsc"
|
"prepare": "npm run build:prod && npx tsc"
|
||||||
|
@ -231,26 +231,20 @@ export default class TelemetryAPI {
|
|||||||
* @returns {TelemetryRequestOptions} the options, with defaults filled in
|
* @returns {TelemetryRequestOptions} the options, with defaults filled in
|
||||||
*/
|
*/
|
||||||
standardizeRequestOptions(options = {}) {
|
standardizeRequestOptions(options = {}) {
|
||||||
if (!Object.hasOwn(options, 'start')) {
|
if (!Object.hasOwn(options, 'timeContext')) {
|
||||||
const bounds = options.timeContext?.getBounds();
|
options.timeContext = this.openmct.time;
|
||||||
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, 'domain')) {
|
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;
|
return options;
|
||||||
|
@ -269,36 +269,40 @@ describe('Telemetry API', () => {
|
|||||||
|
|
||||||
await telemetryAPI.request(domainObject);
|
await telemetryAPI.request(domainObject);
|
||||||
const { signal } = new AbortController();
|
const { signal } = new AbortController();
|
||||||
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(jasmine.any(Object), {
|
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(domainObject, {
|
||||||
signal,
|
signal,
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 1,
|
end: 1,
|
||||||
domain: 'system'
|
domain: 'system',
|
||||||
|
timeContext: openmct.time
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), {
|
expect(telemetryProvider.request).toHaveBeenCalledWith(domainObject, {
|
||||||
signal,
|
signal,
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 1,
|
end: 1,
|
||||||
domain: 'system'
|
domain: 'system',
|
||||||
|
timeContext: openmct.time
|
||||||
});
|
});
|
||||||
|
|
||||||
telemetryProvider.supportsRequest.calls.reset();
|
telemetryProvider.supportsRequest.calls.reset();
|
||||||
telemetryProvider.request.calls.reset();
|
telemetryProvider.request.calls.reset();
|
||||||
|
|
||||||
await telemetryAPI.request(domainObject, {});
|
await telemetryAPI.request(domainObject, {});
|
||||||
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(jasmine.any(Object), {
|
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(domainObject, {
|
||||||
signal,
|
signal,
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 1,
|
end: 1,
|
||||||
domain: 'system'
|
domain: 'system',
|
||||||
|
timeContext: openmct.time
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), {
|
expect(telemetryProvider.request).toHaveBeenCalledWith(domainObject, {
|
||||||
signal,
|
signal,
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 1,
|
end: 1,
|
||||||
domain: 'system'
|
domain: 'system',
|
||||||
|
timeContext: openmct.time
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -313,18 +317,20 @@ describe('Telemetry API', () => {
|
|||||||
domain: 'someDomain'
|
domain: 'someDomain'
|
||||||
});
|
});
|
||||||
const { signal } = new AbortController();
|
const { signal } = new AbortController();
|
||||||
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(jasmine.any(Object), {
|
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(domainObject, {
|
||||||
start: 20,
|
start: 20,
|
||||||
end: 30,
|
end: 30,
|
||||||
domain: 'someDomain',
|
domain: 'someDomain',
|
||||||
signal
|
signal,
|
||||||
|
timeContext: openmct.time
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), {
|
expect(telemetryProvider.request).toHaveBeenCalledWith(domainObject, {
|
||||||
start: 20,
|
start: 20,
|
||||||
end: 30,
|
end: 30,
|
||||||
domain: 'someDomain',
|
domain: 'someDomain',
|
||||||
signal
|
signal,
|
||||||
|
timeContext: openmct.time
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('telemetry batching support', () => {
|
describe('telemetry batching support', () => {
|
||||||
|
@ -62,9 +62,6 @@ export default class TelemetryCollection extends EventEmitter {
|
|||||||
this.futureBuffer = [];
|
this.futureBuffer = [];
|
||||||
this.parseTime = undefined;
|
this.parseTime = undefined;
|
||||||
this.metadata = this.openmct.telemetry.getMetadata(domainObject);
|
this.metadata = this.openmct.telemetry.getMetadata(domainObject);
|
||||||
if (!Object.hasOwn(options, 'timeContext')) {
|
|
||||||
options.timeContext = this.openmct.time;
|
|
||||||
}
|
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.unsubscribe = undefined;
|
this.unsubscribe = undefined;
|
||||||
this.pageState = undefined;
|
this.pageState = undefined;
|
||||||
@ -84,6 +81,9 @@ export default class TelemetryCollection extends EventEmitter {
|
|||||||
this._error(LOADED_ERROR);
|
this._error(LOADED_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Object.hasOwn(this.options, 'timeContext')) {
|
||||||
|
this.options.timeContext = this.openmct.time;
|
||||||
|
}
|
||||||
this._setTimeSystem(this.options.timeContext.getTimeSystem());
|
this._setTimeSystem(this.options.timeContext.getTimeSystem());
|
||||||
this.lastBounds = this.options.timeContext.getBounds();
|
this.lastBounds = this.options.timeContext.getBounds();
|
||||||
// prioritize passed options over time bounds
|
// prioritize passed options over time bounds
|
||||||
@ -93,9 +93,6 @@ export default class TelemetryCollection extends EventEmitter {
|
|||||||
if (this.options.end) {
|
if (this.options.end) {
|
||||||
this.lastBounds.end = 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._watchBounds();
|
||||||
this._watchTimeSystem();
|
this._watchTimeSystem();
|
||||||
this._watchTimeModeChange();
|
this._watchTimeModeChange();
|
||||||
@ -140,10 +137,7 @@ export default class TelemetryCollection extends EventEmitter {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async _requestHistoricalTelemetry() {
|
async _requestHistoricalTelemetry() {
|
||||||
console.debug(
|
const options = this.openmct.telemetry.standardizeRequestOptions({ ...this.options });
|
||||||
`🫙 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 historicalProvider = this.openmct.telemetry.findRequestProvider(
|
const historicalProvider = this.openmct.telemetry.findRequestProvider(
|
||||||
this.domainObject,
|
this.domainObject,
|
||||||
options
|
options
|
||||||
@ -243,19 +237,6 @@ export default class TelemetryCollection extends EventEmitter {
|
|||||||
beforeStartOfBounds = parsedValue < boundsToUse.start;
|
beforeStartOfBounds = parsedValue < boundsToUse.start;
|
||||||
afterEndOfBounds = parsedValue > boundsToUse.end;
|
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 (
|
if (
|
||||||
!afterEndOfBounds &&
|
!afterEndOfBounds &&
|
||||||
(!beforeStartOfBounds || (this.isStrategyLatest && this.openmct.telemetry.greedyLAD()))
|
(!beforeStartOfBounds || (this.isStrategyLatest && this.openmct.telemetry.greedyLAD()))
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
export default class CompsManager extends EventEmitter {
|
export default class CompsManager extends EventEmitter {
|
||||||
#openmct;
|
#openmct;
|
||||||
@ -12,6 +11,8 @@ export default class CompsManager extends EventEmitter {
|
|||||||
#loaded = false;
|
#loaded = false;
|
||||||
#compositionLoaded = false;
|
#compositionLoaded = false;
|
||||||
#telemetryProcessors = {};
|
#telemetryProcessors = {};
|
||||||
|
#loadVersion = 0;
|
||||||
|
#currentLoadPromise = null;
|
||||||
|
|
||||||
constructor(openmct, domainObject) {
|
constructor(openmct, domainObject) {
|
||||||
super();
|
super();
|
||||||
@ -59,7 +60,9 @@ export default class CompsManager extends EventEmitter {
|
|||||||
name: `${this.#getNextAlphabeticalParameterName()}`,
|
name: `${this.#getNextAlphabeticalParameterName()}`,
|
||||||
valueToUse,
|
valueToUse,
|
||||||
testValue: 0,
|
testValue: 0,
|
||||||
timeMetaData
|
timeMetaData,
|
||||||
|
accumulateValues: false,
|
||||||
|
sampleSize: null
|
||||||
});
|
});
|
||||||
this.emit('parameterAdded', this.#domainObject);
|
this.emit('parameterAdded', this.#domainObject);
|
||||||
}
|
}
|
||||||
@ -108,23 +111,56 @@ export default class CompsManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async load(telemetryOptions) {
|
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(
|
console.debug(
|
||||||
`😩 Reloading comps manager ${this.#domainObject.name} due to telemetry options change.`,
|
`😩 Reloading comps manager ${this.#domainObject.name} due to telemetry options change.`,
|
||||||
telemetryOptions
|
telemetryOptions
|
||||||
);
|
);
|
||||||
this.#destroy();
|
this.#destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#telemetryOptions = telemetryOptions;
|
this.#telemetryOptions = telemetryOptions;
|
||||||
|
|
||||||
|
// Start the load process and store the promise
|
||||||
|
this.#currentLoadPromise = (async () => {
|
||||||
|
// Load composition if not already loaded
|
||||||
if (!this.#compositionLoaded) {
|
if (!this.#compositionLoaded) {
|
||||||
await this.#loadComposition();
|
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;
|
this.#compositionLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start listening to telemetry if not already done
|
||||||
if (!this.#loaded) {
|
if (!this.#loaded) {
|
||||||
await this.#startListeningToUnderlyingTelemetry();
|
await this.#startListeningToUnderlyingTelemetry();
|
||||||
this.#telemetryLoadedPromises = [];
|
// 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;
|
this.#loaded = true;
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Await the load process
|
||||||
|
await this.#currentLoadPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #startListeningToUnderlyingTelemetry() {
|
async #startListeningToUnderlyingTelemetry() {
|
||||||
@ -173,27 +209,45 @@ export default class CompsManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getFullDataFrame(newTelemetry) {
|
#getParameterForKeyString(keyString) {
|
||||||
const dataFrame = {};
|
return this.#domainObject.configuration.comps.parameters.find(
|
||||||
// can assume on data item
|
(parameter) => parameter.keyString === keyString
|
||||||
const newTelemetryKey = Object.keys(newTelemetry)[0];
|
|
||||||
const newTelemetryData = newTelemetry[newTelemetryKey];
|
|
||||||
const otherTelemetryKeys = Object.keys(this.#telemetryCollections).filter(
|
|
||||||
(keyString) => keyString !== newTelemetryKey
|
|
||||||
);
|
);
|
||||||
// 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) => {
|
otherTelemetryKeys.forEach((keyString) => {
|
||||||
dataFrame[keyString] = [];
|
telemetryForComps[keyString] = [];
|
||||||
});
|
});
|
||||||
|
|
||||||
// march through the new telemetry data and add data to the frame from the other telemetry objects
|
const otherTelemetryKeysNotAccumulating = otherTelemetryKeys.filter(
|
||||||
// using LOCF
|
(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) => {
|
newTelemetryData.forEach((newDatum) => {
|
||||||
otherTelemetryKeys.forEach((otherKeyString) => {
|
otherTelemetryKeysNotAccumulating.forEach((otherKeyString) => {
|
||||||
const otherCollection = this.#telemetryCollections[otherKeyString];
|
const otherCollection = this.#telemetryCollections[otherKeyString];
|
||||||
|
// otherwise we need to find the closest datum to the new datum
|
||||||
let insertionPointForNewData = otherCollection._sortedIndex(newDatum);
|
let insertionPointForNewData = otherCollection._sortedIndex(newDatum);
|
||||||
const otherCollectionData = otherCollection.getAll();
|
const otherCollectionData = otherCollection.getAll();
|
||||||
if (insertionPointForNewData && insertionPointForNewData >= otherCollectionData.length) {
|
if (insertionPointForNewData && insertionPointForNewData >= otherCollectionData.length) {
|
||||||
@ -202,11 +256,11 @@ export default class CompsManager extends EventEmitter {
|
|||||||
// get the closest datum to the new datum
|
// get the closest datum to the new datum
|
||||||
const closestDatum = otherCollectionData[insertionPointForNewData];
|
const closestDatum = otherCollectionData[insertionPointForNewData];
|
||||||
if (closestDatum) {
|
if (closestDatum) {
|
||||||
dataFrame[otherKeyString].push(closestDatum);
|
telemetryForComps[otherKeyString].push(closestDatum);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return dataFrame;
|
return telemetryForComps;
|
||||||
}
|
}
|
||||||
|
|
||||||
#removeTelemetryObject = (telemetryObjectIdentifier) => {
|
#removeTelemetryObject = (telemetryObjectIdentifier) => {
|
||||||
|
@ -5,17 +5,19 @@ onconnect = function (e) {
|
|||||||
const port = e.ports[0];
|
const port = e.ports[0];
|
||||||
|
|
||||||
port.onmessage = function (event) {
|
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 responseType = 'unknown';
|
||||||
let error = null;
|
let error = null;
|
||||||
let result = [];
|
let result = [];
|
||||||
try {
|
try {
|
||||||
if (type === 'calculateRequest') {
|
if (type === 'calculateRequest') {
|
||||||
responseType = 'calculationRequestResult';
|
responseType = 'calculationRequestResult';
|
||||||
|
console.debug(`📫 Received new calculation request with callback ID ${callbackID}`);
|
||||||
result = calculateRequest(telemetryForComps, parameters, expression);
|
result = calculateRequest(telemetryForComps, parameters, expression);
|
||||||
} else if (type === 'calculateSubscription') {
|
} else if (type === 'calculateSubscription') {
|
||||||
responseType = 'calculationSubscriptionResult';
|
responseType = 'calculationSubscriptionResult';
|
||||||
result = calculateSubscription(telemetryForComps, parameters, expression);
|
result = calculateSubscription(telemetryForComps, newTelemetry, parameters, expression);
|
||||||
} else if (type === 'init') {
|
} else if (type === 'init') {
|
||||||
port.postMessage({ type: 'ready' });
|
port.postMessage({ type: 'ready' });
|
||||||
return;
|
return;
|
||||||
@ -25,6 +27,7 @@ onconnect = function (e) {
|
|||||||
} catch (errorInCalculation) {
|
} catch (errorInCalculation) {
|
||||||
error = errorInCalculation;
|
error = errorInCalculation;
|
||||||
}
|
}
|
||||||
|
console.debug(`📭 Sending response for callback ID ${callbackID}`, result);
|
||||||
port.postMessage({ type: responseType, callbackID, result, error });
|
port.postMessage({ type: responseType, callbackID, result, error });
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -40,9 +43,16 @@ function getFullDataFrame(telemetryForComps, parameters) {
|
|||||||
return dataFrame;
|
return dataFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateSubscription(telemetryForComps, parameters, expression) {
|
function calculateSubscription(telemetryForComps, newTelemetry, parameters, expression) {
|
||||||
const dataFrame = getFullDataFrame(telemetryForComps, parameters);
|
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) {
|
function calculateRequest(telemetryForComps, parameters, expression) {
|
||||||
@ -56,14 +66,40 @@ function calculate(dataFrame, parameters, expression) {
|
|||||||
if (!expression) {
|
if (!expression) {
|
||||||
return sumResults;
|
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
|
// take the first parameter keyString as the reference
|
||||||
const referenceParameter = parameters[0];
|
const referenceParameter = parameters[0];
|
||||||
const otherParameters = parameters.slice(1);
|
const otherParameters = parameters.slice(1);
|
||||||
// iterate over the reference telemetry data
|
// iterate over the reference telemetry data
|
||||||
const referenceTelemetry = dataFrame[referenceParameter.keyString];
|
const referenceTelemetry = dataFrame[referenceParameter.keyString];
|
||||||
referenceTelemetry?.forEach((referenceTelemetryItem) => {
|
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 = {
|
const scope = {
|
||||||
[referenceParameter.name]: referenceTelemetryItem[referenceParameter.valueToUse]
|
[referenceParameter.name]: referenceValue
|
||||||
};
|
};
|
||||||
const referenceTime = referenceTelemetryItem[referenceParameter.timeKey];
|
const referenceTime = referenceTelemetryItem[referenceParameter.timeKey];
|
||||||
// iterate over the other parameters to set the scope
|
// iterate over the other parameters to set the scope
|
||||||
@ -75,7 +111,12 @@ function calculate(dataFrame, parameters, expression) {
|
|||||||
missingData = true;
|
missingData = true;
|
||||||
return;
|
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) {
|
if (missingData) {
|
||||||
return;
|
return;
|
||||||
|
@ -61,6 +61,7 @@ export default class CompsMetadataProvider {
|
|||||||
key: 'compsOutput',
|
key: 'compsOutput',
|
||||||
source: 'compsOutput',
|
source: 'compsOutput',
|
||||||
name: 'Output',
|
name: 'Output',
|
||||||
|
derived: true,
|
||||||
formatString: specificCompsManager.getOutputFormat(),
|
formatString: specificCompsManager.getOutputFormat(),
|
||||||
hints: {
|
hints: {
|
||||||
range: 1
|
range: 1
|
||||||
|
@ -95,7 +95,7 @@ export default class CompsTelemetryProvider {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const expression = specificCompsManager.getExpression();
|
const expression = specificCompsManager.getExpression();
|
||||||
const telemetryForComps = specificCompsManager.getFullDataFrame(newTelemetry);
|
const telemetryForComps = specificCompsManager.getTelemetryForComps(newTelemetry);
|
||||||
const parameters = JSON.parse(JSON.stringify(specificCompsManager.getParameters()));
|
const parameters = JSON.parse(JSON.stringify(specificCompsManager.getParameters()));
|
||||||
if (!expression || !parameters) {
|
if (!expression || !parameters) {
|
||||||
return;
|
return;
|
||||||
@ -103,6 +103,7 @@ export default class CompsTelemetryProvider {
|
|||||||
const payload = {
|
const payload = {
|
||||||
type: 'calculateSubscription',
|
type: 'calculateSubscription',
|
||||||
telemetryForComps,
|
telemetryForComps,
|
||||||
|
newTelemetry,
|
||||||
expression,
|
expression,
|
||||||
parameters,
|
parameters,
|
||||||
callbackID
|
callbackID
|
||||||
@ -134,10 +135,6 @@ export default class CompsTelemetryProvider {
|
|||||||
);
|
);
|
||||||
return () => {
|
return () => {
|
||||||
delete this.#subscriptionCallbacks[callbackID];
|
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.stopListeningToUnderlyingTelemetry();
|
||||||
specificCompsManager.off('underlyingTelemetryUpdated', boundComputeOnNewTelemetry);
|
specificCompsManager.off('underlyingTelemetryUpdated', boundComputeOnNewTelemetry);
|
||||||
};
|
};
|
||||||
|
@ -95,6 +95,37 @@
|
|||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<div v-else>{{ parameter.valueToUse }}</div>
|
<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>
|
||||||
|
|
||||||
<span v-if="isEditing" class="c-test-datum__string">Test value</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}`"
|
:aria-label="`Reference Test Value for ${parameter.name}`"
|
||||||
type="text"
|
type="text"
|
||||||
class="c-input--md"
|
class="c-input--md"
|
||||||
@change="updateParameters"
|
@change="updateTestValue(parameter)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -235,6 +266,22 @@ function updateParameters() {
|
|||||||
applyTestData();
|
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() {
|
function toggleTestData() {
|
||||||
testDataApplied.value = !testDataApplied.value;
|
testDataApplied.value = !testDataApplied.value;
|
||||||
if (testDataApplied.value) {
|
if (testDataApplied.value) {
|
||||||
@ -270,6 +317,20 @@ function applyTestData() {
|
|||||||
}
|
}
|
||||||
return acc;
|
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 {
|
try {
|
||||||
const testOutput = evaluate(expression.value, scope);
|
const testOutput = evaluate(expression.value, scope);
|
||||||
const formattedData = getValueFormatter().format(testOutput);
|
const formattedData = getValueFormatter().format(testOutput);
|
||||||
|
@ -69,7 +69,6 @@ const INNER_TEXT_PADDING = 15;
|
|||||||
const TEXT_LEFT_PADDING = 5;
|
const TEXT_LEFT_PADDING = 5;
|
||||||
const ROW_PADDING = 5;
|
const ROW_PADDING = 5;
|
||||||
const SWIMLANE_PADDING = 3;
|
const SWIMLANE_PADDING = 3;
|
||||||
const RESIZE_POLL_INTERVAL = 200;
|
|
||||||
const ROW_HEIGHT = 22;
|
const ROW_HEIGHT = 22;
|
||||||
const MAX_TEXT_WIDTH = 300;
|
const MAX_TEXT_WIDTH = 300;
|
||||||
const MIN_ACTIVITY_WIDTH = 2;
|
const MIN_ACTIVITY_WIDTH = 2;
|
||||||
@ -143,13 +142,15 @@ export default {
|
|||||||
this.canvasContext = canvas.getContext('2d');
|
this.canvasContext = canvas.getContext('2d');
|
||||||
this.setDimensions();
|
this.setDimensions();
|
||||||
this.setTimeContext();
|
this.setTimeContext();
|
||||||
this.resizeTimer = setInterval(this.resize, RESIZE_POLL_INTERVAL);
|
|
||||||
this.handleConfigurationChange(this.configuration);
|
this.handleConfigurationChange(this.configuration);
|
||||||
this.planViewConfiguration.on('change', this.handleConfigurationChange);
|
this.planViewConfiguration.on('change', this.handleConfigurationChange);
|
||||||
this.loadComposition();
|
this.loadComposition();
|
||||||
|
|
||||||
|
this.resizeObserver = new ResizeObserver(this.resize);
|
||||||
|
this.resizeObserver.observe(this.$refs.plan);
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
clearInterval(this.resizeTimer);
|
this.resizeObserver.disconnect();
|
||||||
this.stopFollowingTimeContext();
|
this.stopFollowingTimeContext();
|
||||||
if (this.unlisten) {
|
if (this.unlisten) {
|
||||||
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
|
as represented by the Administrator of the National Aeronautics and Space
|
||||||
Administration. All rights reserved.
|
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
|
as represented by the Administrator of the National Aeronautics and Space
|
||||||
Administration. All rights reserved.
|
Administration. All rights reserved.
|
||||||
|
|
||||||
|
@ -225,7 +225,13 @@ export default class PlotSeries extends Model {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const points = await this.openmct.telemetry.request(this.domainObject, options);
|
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
|
// eslint-disable-next-line you-dont-need-lodash-underscore/concat
|
||||||
const newPoints = _(data)
|
const newPoints = _(data)
|
||||||
.concat(points)
|
.concat(points)
|
||||||
|
@ -133,7 +133,6 @@ export default {
|
|||||||
if (!styleObj || !elemToStyle) {
|
if (!styleObj || !elemToStyle) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle visibility separately
|
// handle visibility separately
|
||||||
if (styleObj.isStyleInvisible !== undefined) {
|
if (styleObj.isStyleInvisible !== undefined) {
|
||||||
elemToStyle.classList.toggle(STYLE_CONSTANTS.isStyleInvisible, styleObj.isStyleInvisible);
|
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
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
* Administration. All rights reserved.
|
* 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
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
* Administration. All rights reserved.
|
* Administration. All rights reserved.
|
||||||
*
|
*
|
||||||
|
@ -152,17 +152,20 @@ export default class RemoteClock extends DefaultClock {
|
|||||||
*/
|
*/
|
||||||
#waitForReady() {
|
#waitForReady() {
|
||||||
const waitForInitialTick = (resolve) => {
|
const waitForInitialTick = (resolve) => {
|
||||||
|
const tickListener = () => {
|
||||||
if (this.lastTick > 0) {
|
if (this.lastTick > 0) {
|
||||||
const offsets = this.openmct.time.getClockOffsets();
|
const offsets = this.openmct.time.getClockOffsets();
|
||||||
|
this.openmct.time.off('tick', tickListener); // Unregister the tick listener
|
||||||
resolve({
|
resolve({
|
||||||
start: this.lastTick + offsets.start,
|
start: this.lastTick + offsets.start,
|
||||||
end: this.lastTick + offsets.end
|
end: this.lastTick + offsets.end
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
setTimeout(() => waitForInitialTick(resolve), 100);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.openmct.time.on('tick', tickListener);
|
||||||
|
};
|
||||||
|
|
||||||
return new Promise(waitForInitialTick);
|
return new Promise(waitForInitialTick);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,16 +20,43 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* 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) {
|
function remoteClockRequestInterceptor(openmct, _remoteClockIdentifier, waitForBounds) {
|
||||||
let remoteClockLoaded = false;
|
let remoteClockLoaded = false;
|
||||||
|
|
||||||
return {
|
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
|
// Get the activeClock from the Global Time Context
|
||||||
|
/** @type {import("../../api/time/TimeContext").default} */
|
||||||
const { activeClock } = openmct.time;
|
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;
|
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) => {
|
invoke: async (request) => {
|
||||||
const timeContext = request?.timeContext ?? openmct.time;
|
const timeContext = request?.timeContext ?? openmct.time;
|
||||||
|
|
||||||
|
@ -150,13 +150,13 @@ export default class TableRowCollection extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
insertOrUpdateRows(rowsToAdd, addToBeginning) {
|
insertOrUpdateRows(rowsToAdd, addToBeginning) {
|
||||||
rowsToAdd.forEach((row) => {
|
rowsToAdd.forEach((row, addRowsIndex) => {
|
||||||
const index = this.getInPlaceUpdateIndex(row);
|
const index = this.getInPlaceUpdateIndex(row);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
this.updateRowInPlace(row, index);
|
this.updateRowInPlace(row, index);
|
||||||
} else {
|
} else {
|
||||||
if (addToBeginning) {
|
if (addToBeginning) {
|
||||||
this.rows.unshift(row);
|
this.rows.splice(addRowsIndex, 0, row);
|
||||||
} else {
|
} else {
|
||||||
this.rows.push(row);
|
this.rows.push(row);
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,7 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
this.handleNewBounds = _.throttle(this.handleNewBounds, 300, {
|
this.handleNewBounds = _.throttle(this.handleNewBounds, 300, {
|
||||||
leading: true,
|
leading: true,
|
||||||
trailing: false
|
trailing: true
|
||||||
});
|
});
|
||||||
this.setTimeSystem(this.copy(this.openmct.time.getTimeSystem()));
|
this.setTimeSystem(this.copy(this.openmct.time.getTimeSystem()));
|
||||||
this.openmct.time.on(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setTimeSystem);
|
this.openmct.time.on(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setTimeSystem);
|
||||||
@ -181,6 +181,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
stopFollowingTime() {
|
stopFollowingTime() {
|
||||||
|
this.handleNewBounds.cancel();
|
||||||
|
|
||||||
if (this.timeContext) {
|
if (this.timeContext) {
|
||||||
this.timeContext.off(TIME_CONTEXT_EVENTS.boundsChanged, this.handleNewBounds);
|
this.timeContext.off(TIME_CONTEXT_EVENTS.boundsChanged, this.handleNewBounds);
|
||||||
this.timeContext.off(TIME_CONTEXT_EVENTS.clockOffsetsChanged, this.setViewFromOffsets);
|
this.timeContext.off(TIME_CONTEXT_EVENTS.clockOffsetsChanged, this.setViewFromOffsets);
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
/******************************************************** PROGRESS BAR */
|
/******************************************************** PROGRESS BAR */
|
||||||
@keyframes progressIndeterminate {
|
@keyframes progressIndeterminate {
|
||||||
0% {
|
0% {
|
||||||
left: 0;
|
transform:scaleX(0);
|
||||||
width: 0;
|
|
||||||
}
|
}
|
||||||
70% {
|
90% {
|
||||||
left: 0;
|
transform:scaleX(1);
|
||||||
width: 100%;
|
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
left: 100%;
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -24,11 +21,10 @@
|
|||||||
|
|
||||||
&__bar {
|
&__bar {
|
||||||
background: $colorProgressBar;
|
background: $colorProgressBar;
|
||||||
height: 100%;
|
transform-origin: left;
|
||||||
min-height: $progressBarMinH;
|
|
||||||
|
|
||||||
&.--indeterminate {
|
&.--indeterminate {
|
||||||
position: absolute;
|
@include abs();
|
||||||
animation: progressIndeterminate 1.5s ease-in infinite;
|
animation: progressIndeterminate 1.5s ease-in infinite;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
<h1 class="l-title s-title">Open MCT</h1>
|
<h1 class="l-title s-title">Open MCT</h1>
|
||||||
<div class="l-description s-description">
|
<div class="l-description s-description">
|
||||||
<p>
|
<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.
|
Administrator of the National Aeronautics and Space Administration. All rights reserved.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
Loading…
Reference in New Issue
Block a user