mirror of
https://github.com/nasa/openmct.git
synced 2025-04-18 16:17:48 +00:00
Merge remote-tracking branch 'origin/master' into telemetry-comps-with-acc
This commit is contained in:
commit
3be414e2e7
@ -5,11 +5,11 @@ orbs:
|
||||
executors:
|
||||
pw-focal-development:
|
||||
docker:
|
||||
- image: mcr.microsoft.com/playwright:v1.45.2-focal
|
||||
- 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:
|
||||
@ -198,7 +198,7 @@ jobs:
|
||||
steps:
|
||||
- build_and_install:
|
||||
node-version: lts/hydrogen
|
||||
- run: npx playwright@1.45.2 install #Necessary for bare ubuntu machine
|
||||
- run: npx playwright@1.47.2 install #Necessary for bare ubuntu machine
|
||||
- run: |
|
||||
export $(cat src/plugins/persistence/couch/.env.ci | xargs)
|
||||
docker compose -f src/plugins/persistence/couch/couchdb-compose.yaml up --detach
|
||||
@ -323,7 +323,7 @@ workflows:
|
||||
- e2e-couchdb
|
||||
triggers:
|
||||
- schedule:
|
||||
cron: "0 0 * * *"
|
||||
cron: '0 0 * * *'
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
|
2
.github/workflows/e2e-couchdb.yml
vendored
2
.github/workflows/e2e-couchdb.yml
vendored
@ -37,7 +37,7 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- run: npx playwright@1.45.2 install
|
||||
- run: npx playwright@1.47.2 install
|
||||
|
||||
- name: Start CouchDB Docker Container and Init with Setup Scripts
|
||||
run: |
|
||||
|
2
.github/workflows/e2e-flakefinder.yml
vendored
2
.github/workflows/e2e-flakefinder.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- run: npx playwright@1.45.2 install
|
||||
- run: npx playwright@1.47.2 install
|
||||
- run: npm ci --no-audit --progress=false
|
||||
|
||||
- name: Run E2E Tests (Repeated 10 Times)
|
||||
|
2
.github/workflows/e2e-perf.yml
vendored
2
.github/workflows/e2e-perf.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- run: npx playwright@1.45.2 install
|
||||
- run: npx playwright@1.47.2 install
|
||||
- run: npm ci --no-audit --progress=false
|
||||
- run: npm run test:perf:localhost
|
||||
- run: npm run test:perf:contract
|
||||
|
2
.github/workflows/e2e-pr.yml
vendored
2
.github/workflows/e2e-pr.yml
vendored
@ -33,7 +33,7 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- run: npx playwright@1.45.2 install
|
||||
- run: npx playwright@1.47.2 install
|
||||
- run: npx playwright install chrome-beta
|
||||
- run: npm ci --no-audit --progress=false
|
||||
- run: npm run test:e2e:full -- --max-failures=40
|
||||
|
116
.github/workflows/release.yml
vendored
Normal file
116
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
# GitHub Actions Workflow for Automated Releases
|
||||
|
||||
name: Automated Release Workflow
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Nightly builds at 6 PM PST every day
|
||||
- cron: '0 2 * * *'
|
||||
release:
|
||||
types:
|
||||
- created
|
||||
- published
|
||||
|
||||
jobs:
|
||||
nightly-build:
|
||||
if: github.event_name == 'schedule'
|
||||
runs-on: ubuntu-latest
|
||||
name: Nightly Build and Release
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set Up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 'lts/iron' # Specify your Node.js version
|
||||
registry-url: 'https://registry.npmjs.org/'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Bump Version for Nightly
|
||||
id: bump_version
|
||||
run: |
|
||||
PACKAGE_VERSION=$(node -p "require('./package.json').version")
|
||||
DATE=$(date +%Y%m%d)
|
||||
NIGHTLY_VERSION=$(echo $PACKAGE_VERSION | awk -F. -v OFS=. '{$NF+=1; print}')-nightly-$DATE
|
||||
echo "NIGHTLY_VERSION=${NIGHTLY_VERSION}" >> $GITHUB_ENV
|
||||
|
||||
- name: Update package.json
|
||||
run: |
|
||||
npm version $NIGHTLY_VERSION --no-git-tag-version
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add package.json
|
||||
git commit -m "chore: bump version to $NIGHTLY_VERSION for nightly build"
|
||||
|
||||
- name: Push Changes
|
||||
uses: ad-m/github-push-action@v0.6.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: ${{ github.ref }}
|
||||
|
||||
- name: Build Project
|
||||
run: npm run build:prod
|
||||
|
||||
- name: Publish Nightly to NPM
|
||||
run: |
|
||||
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
|
||||
npm publish --access public --tag nightly
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
prerelease-build:
|
||||
if: github.event.release.prerelease == true
|
||||
runs-on: ubuntu-latest
|
||||
name: Pre-release (Beta) Build and Publish
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set Up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '16' # Specify your Node.js version
|
||||
registry-url: 'https://registry.npmjs.org/'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build Project
|
||||
run: npm run build:prod
|
||||
|
||||
- name: Publish Beta to NPM
|
||||
run: |
|
||||
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
|
||||
npm publish --access public --tag beta
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
stable-release-build:
|
||||
if: github.event.release.prerelease == false
|
||||
runs-on: ubuntu-latest
|
||||
name: Stable Release Build and Publish
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set Up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '16' # Specify your Node.js version
|
||||
registry-url: 'https://registry.npmjs.org/'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build Project
|
||||
run: npm run build:prod
|
||||
|
||||
- name: Publish to NPM
|
||||
run: |
|
||||
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
|
||||
npm publish --access public
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
@ -227,6 +227,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.
|
||||
@ -629,13 +660,33 @@ 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,
|
||||
linkParameterToObject,
|
||||
navigateToObjectWithFixedTimeBounds,
|
||||
navigateToObjectWithRealTime,
|
||||
setEndOffset,
|
||||
|
@ -16,7 +16,7 @@
|
||||
"devDependencies": {
|
||||
"@percy/cli": "1.27.4",
|
||||
"@percy/playwright": "1.0.4",
|
||||
"@playwright/test": "1.45.2",
|
||||
"@playwright/test": "1.47.2",
|
||||
"@axe-core/playwright": "4.8.5"
|
||||
},
|
||||
"author": {
|
||||
@ -24,4 +24,4 @@
|
||||
"url": "https://www.nasa.gov"
|
||||
},
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
}
|
@ -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.
|
||||
*
|
||||
|
28
package-lock.json
generated
28
package-lock.json
generated
@ -104,7 +104,7 @@
|
||||
"@axe-core/playwright": "4.8.5",
|
||||
"@percy/cli": "1.27.4",
|
||||
"@percy/playwright": "1.0.4",
|
||||
"@playwright/test": "1.45.2"
|
||||
"@playwright/test": "1.47.2"
|
||||
}
|
||||
},
|
||||
"e2e/node_modules/@percy/cli": {
|
||||
@ -1560,12 +1560,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.45.2",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.2.tgz",
|
||||
"integrity": "sha512-JxG9eq92ET75EbVi3s+4sYbcG7q72ECeZNbdBlaMkGcNbiDQ4cAi8U2QP5oKkOx+1gpaiL1LDStmzCaEM1Z6fQ==",
|
||||
"version": "1.47.2",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.47.2.tgz",
|
||||
"integrity": "sha512-jTXRsoSPONAs8Za9QEQdyjFn+0ZQFjCiIztAIF6bi1HqhBzG9Ma7g1WotyiGqFSBRZjIEqMdT8RUlbk1QVhzCQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.45.2"
|
||||
"playwright": "1.47.2"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@ -8741,12 +8742,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.45.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.2.tgz",
|
||||
"integrity": "sha512-ReywF2t/0teRvNBpfIgh5e4wnrI/8Su8ssdo5XsQKpjxJj+jspm00jSoz9BTg91TT0c9HRjXO7LBNVrgYj9X0g==",
|
||||
"version": "1.47.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.47.2.tgz",
|
||||
"integrity": "sha512-nx1cLMmQWqmA3UsnjaaokyoUpdVaaDhJhMoxX2qj3McpjnsqFHs516QAKYhqHAgOP+oCFTEOCOAaD1RgD/RQfA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.2"
|
||||
"playwright-core": "1.47.2"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@ -8759,10 +8761,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.45.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.2.tgz",
|
||||
"integrity": "sha512-ha175tAWb0dTK0X4orvBIqi3jGEt701SMxMhyujxNrgd8K0Uy5wMSwwcQHtyB4om7INUkfndx02XnQ2p6dvLDw==",
|
||||
"version": "1.47.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.2.tgz",
|
||||
"integrity": "sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
@ -8776,6 +8779,7 @@
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
|
@ -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
|
||||
@ -137,7 +137,7 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
* @private
|
||||
*/
|
||||
async _requestHistoricalTelemetry() {
|
||||
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
|
||||
|
@ -39,7 +39,7 @@ export default class ConditionManager extends EventEmitter {
|
||||
this.shouldEvaluateNewTelemetry = this.shouldEvaluateNewTelemetry.bind(this);
|
||||
|
||||
this.compositionLoad = this.composition.load();
|
||||
this.subscriptions = {};
|
||||
this.telemetryCollections = {};
|
||||
this.telemetryObjects = {};
|
||||
this.testData = {
|
||||
conditionTestInputs: this.conditionSetDomainObject.configuration.conditionTestData,
|
||||
@ -48,55 +48,46 @@ export default class ConditionManager extends EventEmitter {
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
async requestLatestValue(endpoint) {
|
||||
const options = {
|
||||
subscribeToTelemetry(telemetryObject) {
|
||||
const keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||
|
||||
if (this.telemetryCollections[keyString]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const requestOptions = {
|
||||
size: 1,
|
||||
strategy: 'latest'
|
||||
};
|
||||
const latestData = await this.openmct.telemetry.request(endpoint, options);
|
||||
|
||||
if (!latestData) {
|
||||
throw new Error('Telemetry request failed by returning a falsy response');
|
||||
}
|
||||
if (latestData.length === 0) {
|
||||
return;
|
||||
}
|
||||
this.telemetryReceived(endpoint, latestData[0]);
|
||||
}
|
||||
|
||||
subscribeToTelemetry(endpoint) {
|
||||
const telemetryKeyString = this.openmct.objects.makeKeyString(endpoint.identifier);
|
||||
if (this.subscriptions[telemetryKeyString]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const metadata = this.openmct.telemetry.getMetadata(endpoint);
|
||||
|
||||
this.telemetryObjects[telemetryKeyString] = Object.assign({}, endpoint, {
|
||||
telemetryMetaData: metadata ? metadata.valueMetadatas : []
|
||||
});
|
||||
|
||||
// get latest telemetry value (in case subscription is cached and no new data is coming in)
|
||||
this.requestLatestValue(endpoint);
|
||||
|
||||
this.subscriptions[telemetryKeyString] = this.openmct.telemetry.subscribe(
|
||||
endpoint,
|
||||
this.telemetryReceived.bind(this, endpoint)
|
||||
this.telemetryCollections[keyString] = this.openmct.telemetry.requestCollection(
|
||||
telemetryObject,
|
||||
requestOptions
|
||||
);
|
||||
|
||||
const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
|
||||
const telemetryMetaData = metadata ? metadata.valueMetadatas : [];
|
||||
|
||||
this.telemetryObjects[keyString] = { ...telemetryObject, telemetryMetaData };
|
||||
|
||||
this.telemetryCollections[keyString].on(
|
||||
'add',
|
||||
this.telemetryReceived.bind(this, telemetryObject)
|
||||
);
|
||||
this.telemetryCollections[keyString].load();
|
||||
|
||||
this.updateConditionTelemetryObjects();
|
||||
}
|
||||
|
||||
unsubscribeFromTelemetry(endpointIdentifier) {
|
||||
const id = this.openmct.objects.makeKeyString(endpointIdentifier);
|
||||
if (!this.subscriptions[id]) {
|
||||
console.log('no subscription to remove');
|
||||
|
||||
const keyString = this.openmct.objects.makeKeyString(endpointIdentifier);
|
||||
if (!this.telemetryCollections[keyString]) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.subscriptions[id]();
|
||||
delete this.subscriptions[id];
|
||||
delete this.telemetryObjects[id];
|
||||
this.telemetryCollections[keyString].destroy();
|
||||
this.telemetryCollections[keyString] = null;
|
||||
this.telemetryObjects[keyString] = null;
|
||||
this.removeConditionTelemetryObjects();
|
||||
|
||||
//force re-computation of condition set result as we might be in a state where
|
||||
@ -107,7 +98,7 @@ export default class ConditionManager extends EventEmitter {
|
||||
this.timeSystems,
|
||||
this.openmct.time.getTimeSystem()
|
||||
);
|
||||
this.updateConditionResults({ id: id });
|
||||
this.updateConditionResults({ id: keyString });
|
||||
this.updateCurrentCondition(latestTimestamp);
|
||||
|
||||
if (Object.keys(this.telemetryObjects).length === 0) {
|
||||
@ -410,11 +401,13 @@ export default class ConditionManager extends EventEmitter {
|
||||
return this.openmct.time.getBounds().end >= currentTimestamp;
|
||||
}
|
||||
|
||||
telemetryReceived(endpoint, datum) {
|
||||
telemetryReceived(endpoint, data) {
|
||||
if (!this.isTelemetryUsed(endpoint)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const datum = data[0];
|
||||
|
||||
const normalizedDatum = this.createNormalizedDatum(datum, endpoint);
|
||||
const timeSystemKey = this.openmct.time.getTimeSystem().key;
|
||||
let timestamp = {};
|
||||
@ -507,8 +500,9 @@ export default class ConditionManager extends EventEmitter {
|
||||
destroy() {
|
||||
this.composition.off('add', this.subscribeToTelemetry, this);
|
||||
this.composition.off('remove', this.unsubscribeFromTelemetry, this);
|
||||
Object.values(this.subscriptions).forEach((unsubscribe) => unsubscribe());
|
||||
delete this.subscriptions;
|
||||
Object.values(this.telemetryCollections).forEach((telemetryCollection) =>
|
||||
telemetryCollection.destroy()
|
||||
);
|
||||
|
||||
this.conditions.forEach((condition) => {
|
||||
condition.destroy();
|
||||
|
@ -720,50 +720,69 @@ describe('the plugin', function () {
|
||||
};
|
||||
});
|
||||
|
||||
it('should evaluate as old when telemetry is not received in the allotted time', (done) => {
|
||||
it('should evaluate as old when telemetry is not received in the allotted time', async () => {
|
||||
let onAddResolve;
|
||||
const onAddCalledPromise = new Promise((resolve) => {
|
||||
onAddResolve = resolve;
|
||||
});
|
||||
const mockTelemetryCollection = {
|
||||
load: jasmine.createSpy('load'),
|
||||
on: jasmine.createSpy('on').and.callFake((event, callback) => {
|
||||
if (event === 'add') {
|
||||
onAddResolve();
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
openmct.telemetry = jasmine.createSpyObj('telemetry', [
|
||||
'subscribe',
|
||||
'getMetadata',
|
||||
'request',
|
||||
'getValueFormatter',
|
||||
'abortAllRequests'
|
||||
'abortAllRequests',
|
||||
'requestCollection'
|
||||
]);
|
||||
openmct.telemetry.request.and.returnValue(Promise.resolve([]));
|
||||
openmct.telemetry.getMetadata.and.returnValue({
|
||||
...testTelemetryObject.telemetry,
|
||||
valueMetadatas: []
|
||||
valueMetadatas: testTelemetryObject.telemetry.values,
|
||||
valuesForHints: jasmine
|
||||
.createSpy('valuesForHints')
|
||||
.and.returnValue(testTelemetryObject.telemetry.values),
|
||||
value: jasmine.createSpy('value').and.callFake((key) => {
|
||||
return testTelemetryObject.telemetry.values.find((value) => value.key === key);
|
||||
})
|
||||
});
|
||||
openmct.telemetry.request.and.returnValue(Promise.resolve([]));
|
||||
openmct.telemetry.requestCollection.and.returnValue(mockTelemetryCollection);
|
||||
openmct.telemetry.getValueFormatter.and.returnValue({
|
||||
parse: function (value) {
|
||||
return value;
|
||||
}
|
||||
});
|
||||
|
||||
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
||||
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
||||
conditionMgr.telemetryObjects = {
|
||||
'test-object': testTelemetryObject
|
||||
};
|
||||
conditionMgr.updateConditionTelemetryObjects();
|
||||
setTimeout(() => {
|
||||
expect(mockListener).toHaveBeenCalledWith({
|
||||
output: 'Any old telemetry',
|
||||
id: {
|
||||
namespace: '',
|
||||
key: 'cf4456a9-296a-4e6b-b182-62ed29cd15b9'
|
||||
},
|
||||
conditionId: '39584410-cbf9-499e-96dc-76f27e69885d',
|
||||
utc: undefined
|
||||
});
|
||||
done();
|
||||
}, 400);
|
||||
// Wait for the 'on' callback to be called
|
||||
await onAddCalledPromise;
|
||||
|
||||
// Simulate the passage of time and no data received
|
||||
await new Promise((resolve) => setTimeout(resolve, 400));
|
||||
|
||||
expect(mockListener).toHaveBeenCalledWith({
|
||||
output: 'Any old telemetry',
|
||||
id: {
|
||||
namespace: '',
|
||||
key: 'cf4456a9-296a-4e6b-b182-62ed29cd15b9'
|
||||
},
|
||||
conditionId: '39584410-cbf9-499e-96dc-76f27e69885d',
|
||||
utc: undefined
|
||||
});
|
||||
});
|
||||
|
||||
it('should not evaluate as old when telemetry is received in the allotted time', (done) => {
|
||||
openmct.telemetry.getMetadata = jasmine.createSpy('getMetadata');
|
||||
openmct.telemetry.getMetadata.and.returnValue({
|
||||
...testTelemetryObject.telemetry,
|
||||
valueMetadatas: testTelemetryObject.telemetry.values
|
||||
});
|
||||
it('should not evaluate as old when telemetry is received in the allotted time', async () => {
|
||||
const testDatum = {
|
||||
'some-key2': '',
|
||||
utc: 1,
|
||||
@ -771,8 +790,49 @@ describe('the plugin', function () {
|
||||
'some-key': null,
|
||||
id: 'test-object'
|
||||
};
|
||||
openmct.telemetry.request = jasmine.createSpy('request');
|
||||
|
||||
let onAddResolve;
|
||||
let onAddCallback;
|
||||
const onAddCalledPromise = new Promise((resolve) => {
|
||||
onAddResolve = resolve;
|
||||
});
|
||||
|
||||
const mockTelemetryCollection = {
|
||||
load: jasmine.createSpy('load'),
|
||||
on: jasmine.createSpy('on').and.callFake((event, callback) => {
|
||||
if (event === 'add') {
|
||||
onAddCallback = callback;
|
||||
onAddResolve();
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
openmct.telemetry = jasmine.createSpyObj('telemetry', [
|
||||
'getMetadata',
|
||||
'getValueFormatter',
|
||||
'request',
|
||||
'subscribe',
|
||||
'requestCollection'
|
||||
]);
|
||||
openmct.telemetry.subscribe.and.returnValue(function () {});
|
||||
openmct.telemetry.request.and.returnValue(Promise.resolve([testDatum]));
|
||||
openmct.telemetry.getMetadata.and.returnValue({
|
||||
...testTelemetryObject.telemetry,
|
||||
valueMetadatas: testTelemetryObject.telemetry.values,
|
||||
valuesForHints: jasmine
|
||||
.createSpy('valuesForHints')
|
||||
.and.returnValue(testTelemetryObject.telemetry.values),
|
||||
value: jasmine.createSpy('value').and.callFake((key) => {
|
||||
return testTelemetryObject.telemetry.values.find((value) => value.key === key);
|
||||
})
|
||||
});
|
||||
openmct.telemetry.requestCollection.and.returnValue(mockTelemetryCollection);
|
||||
openmct.telemetry.getValueFormatter.and.returnValue({
|
||||
parse: function (value) {
|
||||
return value;
|
||||
}
|
||||
});
|
||||
|
||||
const date = 1;
|
||||
conditionSetDomainObject.configuration.conditionCollection[0].configuration.criteria[0].input =
|
||||
['0.4'];
|
||||
@ -782,19 +842,25 @@ describe('the plugin', function () {
|
||||
'test-object': testTelemetryObject
|
||||
};
|
||||
conditionMgr.updateConditionTelemetryObjects();
|
||||
conditionMgr.telemetryReceived(testTelemetryObject, testDatum);
|
||||
setTimeout(() => {
|
||||
expect(mockListener).toHaveBeenCalledWith({
|
||||
output: 'Default',
|
||||
id: {
|
||||
namespace: '',
|
||||
key: 'cf4456a9-296a-4e6b-b182-62ed29cd15b9'
|
||||
},
|
||||
conditionId: '2532d90a-e0d6-4935-b546-3123522da2de',
|
||||
utc: date
|
||||
});
|
||||
done();
|
||||
}, 300);
|
||||
|
||||
// Wait for the 'on' callback to be called
|
||||
await onAddCalledPromise;
|
||||
|
||||
// Simulate receiving telemetry data
|
||||
onAddCallback([testDatum]);
|
||||
|
||||
// Wait a bit for the condition manager to process the data
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
expect(mockListener).toHaveBeenCalledWith({
|
||||
output: 'Default',
|
||||
id: {
|
||||
namespace: '',
|
||||
key: 'cf4456a9-296a-4e6b-b182-62ed29cd15b9'
|
||||
},
|
||||
conditionId: '2532d90a-e0d6-4935-b546-3123522da2de',
|
||||
utc: date
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -902,17 +968,25 @@ describe('the plugin', function () {
|
||||
openmct.telemetry.getMetadata = jasmine.createSpy('getMetadata');
|
||||
openmct.telemetry.getMetadata.and.returnValue({
|
||||
...testTelemetryObject.telemetry,
|
||||
valueMetadatas: []
|
||||
valueMetadatas: testTelemetryObject.telemetry.values,
|
||||
valuesForHints: jasmine
|
||||
.createSpy('valuesForHints')
|
||||
.and.returnValue(testTelemetryObject.telemetry.values),
|
||||
value: jasmine.createSpy('value').and.callFake((key) => {
|
||||
return testTelemetryObject.telemetry.values.find((value) => value.key === key);
|
||||
})
|
||||
});
|
||||
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
||||
conditionMgr.telemetryObjects = {
|
||||
'test-object': testTelemetryObject
|
||||
};
|
||||
conditionMgr.updateConditionTelemetryObjects();
|
||||
conditionMgr.telemetryReceived(testTelemetryObject, {
|
||||
'some-key': 2,
|
||||
utc: date
|
||||
});
|
||||
conditionMgr.telemetryReceived(testTelemetryObject, [
|
||||
{
|
||||
'some-key': 2,
|
||||
utc: date
|
||||
}
|
||||
]);
|
||||
let result = conditionMgr.conditions.map((condition) => condition.result);
|
||||
expect(result[2]).toBeUndefined();
|
||||
});
|
||||
@ -1002,26 +1076,37 @@ describe('the plugin', function () {
|
||||
}
|
||||
};
|
||||
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
|
||||
// const mockTransactionService = jasmine.createSpyObj(
|
||||
// 'transactionService',
|
||||
// ['commit']
|
||||
// );
|
||||
openmct.telemetry = jasmine.createSpyObj('telemetry', [
|
||||
'isTelemetryObject',
|
||||
'request',
|
||||
'subscribe',
|
||||
'getMetadata',
|
||||
'getValueFormatter',
|
||||
'request'
|
||||
'requestCollection'
|
||||
]);
|
||||
openmct.telemetry.isTelemetryObject.and.returnValue(true);
|
||||
openmct.telemetry.subscribe.and.returnValue(function () {});
|
||||
openmct.telemetry.request.and.returnValue(Promise.resolve([]));
|
||||
openmct.telemetry.isTelemetryObject.and.returnValue(true);
|
||||
openmct.telemetry.getMetadata.and.returnValue(testTelemetryObject.telemetry);
|
||||
openmct.telemetry.getMetadata.and.returnValue({
|
||||
...testTelemetryObject.telemetry,
|
||||
valueMetadatas: testTelemetryObject.telemetry.values,
|
||||
valuesForHints: jasmine
|
||||
.createSpy('valuesForHints')
|
||||
.and.returnValue(testTelemetryObject.telemetry.values),
|
||||
value: jasmine.createSpy('value').and.callFake((key) => {
|
||||
return testTelemetryObject.telemetry.values.find((value) => value.key === key);
|
||||
})
|
||||
});
|
||||
openmct.telemetry.getValueFormatter.and.returnValue({
|
||||
parse: function (value) {
|
||||
return value;
|
||||
}
|
||||
});
|
||||
openmct.telemetry.getMetadata.and.returnValue(testTelemetryObject.telemetry);
|
||||
openmct.telemetry.request.and.returnValue(Promise.resolve([]));
|
||||
openmct.telemetry.requestCollection.and.returnValue({
|
||||
load: jasmine.createSpy('load'),
|
||||
on: jasmine.createSpy('on')
|
||||
});
|
||||
|
||||
const styleRuleManger = new StyleRuleManager(stylesObject, openmct, null, true);
|
||||
spyOn(styleRuleManger, 'subscribeToConditionSet');
|
||||
|
@ -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.
|
||||
|
||||
|
@ -128,35 +128,22 @@ export default {
|
||||
}
|
||||
},
|
||||
updateStyle(styleObj) {
|
||||
let elemToStyle = this.getStyleReceiver();
|
||||
const elemToStyle = this.getStyleReceiver();
|
||||
|
||||
if (!styleObj || elemToStyle === undefined) {
|
||||
if (!styleObj || !elemToStyle) {
|
||||
return;
|
||||
}
|
||||
// handle visibility separately
|
||||
if (styleObj.isStyleInvisible !== undefined) {
|
||||
elemToStyle.classList.toggle(STYLE_CONSTANTS.isStyleInvisible, styleObj.isStyleInvisible);
|
||||
styleObj.isStyleInvisible = null;
|
||||
}
|
||||
|
||||
let keys = Object.keys(styleObj);
|
||||
|
||||
keys.forEach((key) => {
|
||||
if (elemToStyle) {
|
||||
if (typeof styleObj[key] === 'string' && styleObj[key].indexOf('__no_value') > -1) {
|
||||
if (elemToStyle.style[key]) {
|
||||
elemToStyle.style[key] = '';
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
!styleObj.isStyleInvisible &&
|
||||
elemToStyle.classList.contains(STYLE_CONSTANTS.isStyleInvisible)
|
||||
) {
|
||||
elemToStyle.classList.remove(STYLE_CONSTANTS.isStyleInvisible);
|
||||
} else if (
|
||||
styleObj.isStyleInvisible &&
|
||||
!elemToStyle.classList.contains(styleObj.isStyleInvisible)
|
||||
) {
|
||||
elemToStyle.classList.add(styleObj.isStyleInvisible);
|
||||
}
|
||||
|
||||
elemToStyle.style[key] = styleObj[key];
|
||||
}
|
||||
Object.entries(styleObj).forEach(([key, value]) => {
|
||||
if (typeof value !== 'string' || !value.includes('__no_value')) {
|
||||
elemToStyle.style[key] = value;
|
||||
} else {
|
||||
elemToStyle.style[key] = ''; // remove the property
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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…
x
Reference in New Issue
Block a user