Compare commits
22 Commits
mct7583
...
eval-sourc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc58dbd5e7 | ||
|
|
d68ac31ab5 | ||
|
|
f504ee29cc | ||
|
|
1d5ddc545e | ||
|
|
42085a4b70 | ||
|
|
b2b0837592 | ||
|
|
e305b46d88 | ||
|
|
fb396ac194 | ||
|
|
a01f21017f | ||
|
|
b7b9ccbe65 | ||
|
|
f189a4d602 | ||
|
|
4027eae299 | ||
|
|
d4695178bc | ||
|
|
c19c4e7065 | ||
|
|
5fc5c13314 | ||
|
|
ceeb761d94 | ||
|
|
10eb749d32 | ||
|
|
faed27c143 | ||
|
|
3bb4df8d39 | ||
|
|
18e976ad12 | ||
|
|
64862634f3 | ||
|
|
cb4c59a464 |
@@ -1,59 +1,33 @@
|
||||
version: 2.1
|
||||
orbs:
|
||||
node: circleci/node@5.2.0
|
||||
browser-tools: circleci/browser-tools@1.3.0
|
||||
executors:
|
||||
pw-focal-development:
|
||||
docker:
|
||||
- image: mcr.microsoft.com/playwright:v1.39.0-focal
|
||||
- image: mcr.microsoft.com/playwright:v1.42.1-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:
|
||||
image: ubuntu-2204:current
|
||||
docker_layer_caching: true
|
||||
parameters:
|
||||
BUST_CACHE:
|
||||
description: "Set this with the CircleCI UI Trigger Workflow button (boolean = true) to bust the cache!"
|
||||
default: false
|
||||
type: boolean
|
||||
commands:
|
||||
build_and_install:
|
||||
description: "All steps used to build and install. Will use cache if found"
|
||||
description: 'All steps used to build and install.'
|
||||
parameters:
|
||||
node-version:
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache_cmd:
|
||||
node-version: << parameters.node-version >>
|
||||
- node/install:
|
||||
node-version: << parameters.node-version >>
|
||||
- run: npm install --no-audit --progress=false
|
||||
restore_cache_cmd:
|
||||
description: "Custom command for restoring cache with the ability to bust cache. When BUST_CACHE is set to true, jobs will not restore cache"
|
||||
parameters:
|
||||
node-version:
|
||||
type: string
|
||||
steps:
|
||||
- when:
|
||||
condition:
|
||||
equal: [false, << pipeline.parameters.BUST_CACHE >>]
|
||||
steps:
|
||||
- restore_cache:
|
||||
key: deps--{{ arch }}--{{ .Branch }}--<< parameters.node-version >>--{{ checksum "package.json" }}-{{ checksum ".circleci/config.yml" }}
|
||||
save_cache_cmd:
|
||||
description: "Custom command for saving cache."
|
||||
parameters:
|
||||
node-version:
|
||||
type: string
|
||||
steps:
|
||||
- save_cache:
|
||||
key: deps--{{ arch }}--{{ .Branch }}--<< parameters.node-version >>--{{ checksum "package.json" }}-{{ checksum ".circleci/config.yml" }}
|
||||
paths:
|
||||
- ~/.npm
|
||||
- node_modules
|
||||
- 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)
|
||||
@@ -64,16 +38,13 @@ commands:
|
||||
- store_artifacts:
|
||||
path: /tmp/artifacts/
|
||||
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
|
||||
steps:
|
||||
- run: npm run cov:e2e:report || true
|
||||
- run: npm run cov:e2e:<<parameters.suite>>:publish
|
||||
orbs:
|
||||
node: circleci/node@5.1.0
|
||||
browser-tools: circleci/browser-tools@1.3.0
|
||||
jobs:
|
||||
npm-audit:
|
||||
parameters:
|
||||
@@ -111,8 +82,6 @@ jobs:
|
||||
TESTFILES=$(circleci tests glob "src/**/*Spec.js")
|
||||
echo "$TESTFILES" | circleci tests run --command="xargs npm run test" --verbose
|
||||
- run: npm run cov:unit:publish
|
||||
- save_cache_cmd:
|
||||
node-version: <<parameters.node-version>>
|
||||
- store_test_results:
|
||||
path: dist/reports/tests/
|
||||
- store_artifacts:
|
||||
@@ -133,7 +102,7 @@ jobs:
|
||||
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:
|
||||
@@ -190,7 +159,7 @@ jobs:
|
||||
steps:
|
||||
- build_and_install:
|
||||
node-version: lts/hydrogen
|
||||
- run: npx playwright@1.39.0 install #Necessary for bare ubuntu machine
|
||||
- run: npx playwright@1.42.1 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
|
||||
@@ -252,14 +221,15 @@ jobs:
|
||||
equal: [42, 42] # Always run codecov reports regardless of test failure https://discuss.circleci.com/t/make-custom-command-run-always-with-when-always/38957/2
|
||||
steps:
|
||||
- generate_and_store_version_and_filesystem_artifacts
|
||||
visual-a11y-tests:
|
||||
visual-a11y:
|
||||
parameters:
|
||||
suite:
|
||||
type: string # ci or full
|
||||
executor: pw-focal-development
|
||||
parallelism: 2
|
||||
steps:
|
||||
- build_and_install:
|
||||
node-version: lts/hydrogen
|
||||
node-version: lts/iron
|
||||
- run: npm run test:e2e:visual:<<parameters.suite>>
|
||||
- store_test_results:
|
||||
path: test-results/results.xml
|
||||
@@ -286,8 +256,8 @@ workflows:
|
||||
name: e2e-stable
|
||||
suite: stable
|
||||
- e2e-mobile
|
||||
- visual-a11y-tests:
|
||||
name: visual-a11y-test-ci
|
||||
- visual-a11y:
|
||||
name: visual-a11y-ci
|
||||
suite: ci
|
||||
|
||||
the-nightly: #These jobs do not run on PRs, but against master at night
|
||||
@@ -306,13 +276,13 @@ workflows:
|
||||
- e2e-mobile
|
||||
- perf-test
|
||||
- mem-test
|
||||
- visual-a11y-tests:
|
||||
name: visual-a11y-test-nightly
|
||||
- visual-a11y:
|
||||
name: visual-a11y-nightly
|
||||
suite: full
|
||||
- e2e-couchdb
|
||||
triggers:
|
||||
- schedule:
|
||||
cron: "0 0 * * *"
|
||||
cron: '0 0 * * *'
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
|
||||
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -17,7 +17,6 @@ Closes <!--- Insert Issue Number(s) this PR addresses. Start by typing # will op
|
||||
* [ ] Has this been smoke tested?
|
||||
* [ ] Have you associated this PR with a `type:` label? Note: this is not necessarily the same as the original issue.
|
||||
* [ ] Have you associated a milestone with this PR? Note: leave blank if unsure.
|
||||
* [ ] Is this a breaking change to be called out in the release notes?
|
||||
* [ ] Testing instructions included in associated issue OR is this a dependency/testcase change?
|
||||
|
||||
### Reviewer Checklist
|
||||
|
||||
4
.github/workflows/e2e-couchdb.yml
vendored
4
.github/workflows/e2e-couchdb.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- run: npm install --cache ~/.npm --no-audit --progress=false
|
||||
- run: npm ci --no-audit --progress=false
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- run: npx playwright@1.39.0 install
|
||||
- run: npx playwright@1.42.1 install
|
||||
|
||||
- name: Start CouchDB Docker Container and Init with Setup Scripts
|
||||
run: |
|
||||
|
||||
4
.github/workflows/e2e-flakefinder.yml
vendored
4
.github/workflows/e2e-flakefinder.yml
vendored
@@ -30,8 +30,8 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- run: npx playwright@1.39.0 install
|
||||
- run: npm install --cache ~/.npm --no-audit --progress=false
|
||||
- run: npx playwright@1.42.1 install
|
||||
- run: npm ci --no-audit --progress=false
|
||||
|
||||
- name: Run E2E Tests (Repeated 10 Times)
|
||||
run: npm run test:e2e:stable -- --retries=0 --repeat-each=10 --max-failures=50
|
||||
|
||||
4
.github/workflows/e2e-perf.yml
vendored
4
.github/workflows/e2e-perf.yml
vendored
@@ -28,8 +28,8 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- run: npx playwright@1.39.0 install
|
||||
- run: npm install --cache ~/.npm --no-audit --progress=false
|
||||
- run: npx playwright@1.42.1 install
|
||||
- run: npm ci --no-audit --progress=false
|
||||
- run: npm run test:perf:localhost
|
||||
- run: npm run test:perf:contract
|
||||
- run: npm run test:perf:memory
|
||||
|
||||
4
.github/workflows/e2e-pr.yml
vendored
4
.github/workflows/e2e-pr.yml
vendored
@@ -33,9 +33,9 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- run: npx playwright@1.39.0 install
|
||||
- run: npx playwright@1.42.1 install
|
||||
- run: npx playwright install chrome-beta
|
||||
- run: npm install --cache ~/.npm --no-audit --progress=false
|
||||
- run: npm ci --no-audit --progress=false
|
||||
- run: npm run test:e2e:full -- --max-failures=40
|
||||
- run: npm run cov:e2e:report || true
|
||||
- shell: bash
|
||||
|
||||
4
.github/workflows/npm-prerelease.yml
vendored
4
.github/workflows/npm-prerelease.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/hydrogen
|
||||
- run: npm install
|
||||
- run: npm ci
|
||||
- run: |
|
||||
echo "//registry.npmjs.org/:_authToken=$NODE_AUTH_TOKEN" >> ~/.npmrc
|
||||
npm whoami
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
with:
|
||||
node-version: lts/hydrogen
|
||||
registry-url: https://registry.npmjs.org/
|
||||
- run: npm install
|
||||
- run: npm ci
|
||||
- run: npm publish --access=public --tag unstable
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
2
.github/workflows/pr-platform.yml
vendored
2
.github/workflows/pr-platform.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.node_version }}-
|
||||
|
||||
- run: npm install --cache ~/.npm --no-audit --progress=false
|
||||
- run: npm ci --no-audit --progress=false
|
||||
|
||||
- run: npm test
|
||||
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -47,6 +47,3 @@ index.html.bak
|
||||
.nyc_output
|
||||
coverage
|
||||
codecov
|
||||
|
||||
# :(
|
||||
package-lock.json
|
||||
|
||||
3
.npmrc
3
.npmrc
@@ -2,6 +2,3 @@ loglevel=warn
|
||||
|
||||
#Prevent folks from ignoring an important error when building from source
|
||||
engine-strict=true
|
||||
|
||||
# Dont include lockfile
|
||||
package-lock=false
|
||||
@@ -5,11 +5,8 @@ information to pull requests.
|
||||
*/
|
||||
|
||||
import config from './webpack.dev.js';
|
||||
// eslint-disable-next-line no-undef
|
||||
const CI = process.env.CI === 'true';
|
||||
|
||||
config.devtool = CI ? false : undefined;
|
||||
|
||||
config.devtool = 'source-map';
|
||||
config.devServer.hot = false;
|
||||
|
||||
config.module.rules.push({
|
||||
|
||||
@@ -16,8 +16,6 @@ The [CodeQL GitHub Actions workflow](https://github.com/nasa/openmct/blob/master
|
||||
|
||||
CodeQL is run for every pull-request in GitHub Actions.
|
||||
|
||||
The project is also monitored by [LGTM](https://lgtm.com/projects/g/nasa/openmct/) and is available to public.
|
||||
|
||||
### ESLint
|
||||
|
||||
Static analysis is run for every push on the master branch and every pull request on all branches in Github Actions.
|
||||
|
||||
14
TESTING.md
14
TESTING.md
@@ -91,12 +91,14 @@ There are a few reasons that your GitHub PR could be failing beyond simple faile
|
||||
### Local=Pass and CI=Fail
|
||||
Although rare, it is possible that your test can pass locally but fail in CI.
|
||||
|
||||
#### Busting Cache
|
||||
In certain circumstances, the CircleCI cache can become stale. In order to bust the cache, we've implemented a runtime boolean parameter in Circle CI creatively name BUST_CACHE. To execute:
|
||||
1. Navigate to the branch in Circle CI believed to have stale cache.
|
||||
1. Click on the 'Trigger Pipeline' button.
|
||||
1. Add Parameter -> Parameter Type = boolean , Name = BUST_CACHE ,Value = true
|
||||
1. Click 'Trigger Pipeline'
|
||||
### Reset your workspace
|
||||
It's possible that you're running with dependencies or a local environment which is out of sync with the branch you're working on. Make sure to execute the following:
|
||||
|
||||
```sh
|
||||
nvm use
|
||||
npm run clean
|
||||
npm install
|
||||
```
|
||||
|
||||
#### Run tests in the same container as CI
|
||||
|
||||
|
||||
@@ -516,6 +516,30 @@ test.describe('foo test suite', () => {
|
||||
- Working with multiple pages
|
||||
There are instances where multiple browser pages will needed to verify multi-page or multi-tab application behavior. Make sure to use the `@2p` annotation as well as name each page appropriately: i.e. `page1` and `page2` or `tab1` and `tab2` depending on the intended use case. Generally pages should be used unless testing `sharedWorker` code, specifically.
|
||||
|
||||
- Working with file downloads and JSON data
|
||||
Open MCT has the capability of exporting certain objects in the form of a JSON file handled by the chrome browser. The best example of this type of test can be found in the exportAsJson test.
|
||||
|
||||
```js
|
||||
const [download] = await Promise.all([
|
||||
page.waitForEvent('download'), // Waits for the download event
|
||||
page.getByLabel('Export as JSON').click() // Triggers the download
|
||||
]);
|
||||
|
||||
// Wait for the download process to complete
|
||||
const path = await download.path();
|
||||
|
||||
// Read the contents of the downloaded file using readFile from fs/promises
|
||||
const fileContents = await fs.readFile(path, 'utf8');
|
||||
const jsonData = JSON.parse(fileContents);
|
||||
|
||||
// Use the function to retrieve the key
|
||||
const key = getFirstKeyFromOpenMctJson(jsonData);
|
||||
|
||||
// Verify the contents of the JSON file
|
||||
expect(jsonData.openmct[key]).toHaveProperty('name', 'e2e folder');
|
||||
```
|
||||
|
||||
|
||||
### Reporting
|
||||
|
||||
Test Reporting is done through official Playwright reporters and the CI Systems which execute them.
|
||||
|
||||
@@ -392,6 +392,8 @@ async function setTimeConductorMode(page, isFixedTimespan = true) {
|
||||
await page.getByRole('menuitem', { name: /Real-Time/ }).click();
|
||||
await page.waitForURL(/tc\.mode=local/);
|
||||
}
|
||||
//dismiss the time conductor popup
|
||||
await page.getByLabel('Discard changes and close time popup').click();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -662,5 +664,6 @@ export {
|
||||
setRealTimeMode,
|
||||
setStartOffset,
|
||||
setTimeConductorBounds,
|
||||
setTimeConductorMode,
|
||||
waitForPlotsToRender
|
||||
};
|
||||
|
||||
@@ -292,6 +292,16 @@ test.describe('Basic Condition Set Use', () => {
|
||||
await expect(page.getByRole('menuitem', { name: /Conditions View/ })).toBeVisible();
|
||||
await expect(page.getByRole('menuitem', { name: /Plot/ })).toBeVisible();
|
||||
await expect(page.getByRole('menuitem', { name: /Telemetry Table/ })).toBeVisible();
|
||||
await page.getByLabel('Plot').click();
|
||||
await expect(
|
||||
page.getByLabel('Plot Legend Collapsed').getByText('Test Condition Set')
|
||||
).toBeVisible();
|
||||
await page.getByLabel('Open the View Switcher Menu').click();
|
||||
await page.getByLabel('Telemetry Table').click();
|
||||
await expect(page.getByRole('searchbox', { name: 'output filter input' })).toBeVisible();
|
||||
await page.getByLabel('Open the View Switcher Menu').click();
|
||||
await page.getByLabel('Conditions View').click();
|
||||
await expect(page.getByText('Current Output')).toBeVisible();
|
||||
});
|
||||
test('ConditionSet has correct outputs when telemetry is and is not available', async ({
|
||||
page
|
||||
@@ -457,4 +467,11 @@ test.describe('Basic Condition Set Use', () => {
|
||||
|
||||
await page.goto(exampleTelemetry.url);
|
||||
});
|
||||
|
||||
test.fixme('Ensure condition sets work with telemetry like operator status', ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/nasa/openmct/issues/7484'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -289,7 +289,7 @@ test.describe('Flexible Layout Toolbar Actions @localStorage', () => {
|
||||
await page.getByTitle('Add Container').click();
|
||||
expect(await containerHandles.count()).toEqual(3);
|
||||
await page.getByTitle('Remove Container').click();
|
||||
await expect(page.getByRole('dialog', { name: 'Overlay' })).toHaveText(
|
||||
await expect(page.getByRole('dialog', { name: 'Overlay' })).toContainText(
|
||||
'This action will permanently delete this container from this Flexible Layout. Do you want to continue?'
|
||||
);
|
||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||
@@ -299,7 +299,7 @@ test.describe('Flexible Layout Toolbar Actions @localStorage', () => {
|
||||
expect(await page.getByRole('group', { name: 'Frame' }).count()).toEqual(2);
|
||||
await page.getByRole('group', { name: 'Child Layout 1' }).click();
|
||||
await page.getByTitle('Remove Frame').click();
|
||||
await expect(page.getByRole('dialog', { name: 'Overlay' })).toHaveText(
|
||||
await expect(page.getByRole('dialog', { name: 'Overlay' })).toContainText(
|
||||
'This action will remove this frame from this Flexible Layout. Do you want to continue?'
|
||||
);
|
||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||
|
||||
@@ -71,42 +71,89 @@ test.describe('Snapshot Container tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
//Navigate to baseURL
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
|
||||
// Create Notebook
|
||||
// const notebook = await createDomainObjectWithDefaults(page, {
|
||||
// type: 'Notebook',
|
||||
// name: "Test Notebook"
|
||||
// });
|
||||
// // Create Overlay Plot
|
||||
// const snapShotObject = await createDomainObjectWithDefaults(page, {
|
||||
// type: 'Overlay Plot',
|
||||
// name: "Dropped Overlay Plot"
|
||||
// });
|
||||
|
||||
await page.getByLabel('Open the Notebook Snapshot Menu').click();
|
||||
await page.getByRole('menuitem', { name: 'Save to Notebook Snapshots' }).click();
|
||||
await page.getByLabel('Show Snapshots').click();
|
||||
});
|
||||
test('A snapshot can be Quick Viewed from Container with 3 dot action menu', async ({ page }) => {
|
||||
await page.locator('.c-snapshot.c-ne__embed').first().getByTitle('More actions').click();
|
||||
await page.getByLabel('My Items Notebook Embed').getByLabel('More actions').click();
|
||||
await page.getByRole('menuitem', { name: 'Quick View' }).click();
|
||||
await expect(page.locator('.c-overlay__outer')).toBeVisible();
|
||||
await expect(page.getByLabel('Modal Overlay')).toBeVisible();
|
||||
await expect(page.getByLabel('Preview Container')).toBeVisible();
|
||||
});
|
||||
test('A snapshot can be Viewed, Annotated, display deleted, and saved from Container with 3 dot action menu', async ({
|
||||
page
|
||||
}) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/nasa/openmct/issues/7552'
|
||||
});
|
||||
//Open Snapshot Object View
|
||||
await page.getByLabel('My Items Notebook Embed').getByLabel('More actions').click();
|
||||
await page.getByRole('menuitem', { name: 'View Snapshot' }).click();
|
||||
await expect(page.getByRole('dialog', { name: 'Modal Overlay' })).toBeVisible();
|
||||
await expect(page.locator('#snapshotDescriptor')).toHaveText(
|
||||
/SNAPSHOT \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/
|
||||
);
|
||||
// Open Annotation Editor with Painterro
|
||||
await page.getByLabel('Annotate this snapshot').click();
|
||||
await expect(page.locator('#snap-annotation-canvas')).toBeVisible();
|
||||
// Clear the canvas
|
||||
await page.getByRole('button', { name: 'Put text [T]' }).click();
|
||||
// Click in the Painterro canvas to add a text annotation
|
||||
await page.locator('.ptro-crp-el').click();
|
||||
await page.locator('.ptro-text-tool-input').fill('...is there life on mars?');
|
||||
// When working with Painterro, we need to check that the Apply button is hidden after clicking
|
||||
await page.getByTitle('Apply').click();
|
||||
await expect(page.getByTitle('Apply')).toBeHidden();
|
||||
|
||||
// Save and exit annotation window
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await page.getByRole('button', { name: 'Done' }).click();
|
||||
|
||||
// Open up annotation again
|
||||
await page.getByRole('img', { name: 'My Items thumbnail' }).click();
|
||||
await expect(page.getByLabel('Modal Overlay').getByRole('img')).toBeVisible();
|
||||
});
|
||||
test('A snapshot can be Annotated and saved as a JPG and PNG', async ({ page }) => {
|
||||
//Open Snapshot Object View
|
||||
await page.getByLabel('My Items Notebook Embed').getByLabel('More actions').click();
|
||||
await page.getByRole('menuitem', { name: 'View Snapshot' }).click();
|
||||
await expect(page.getByRole('dialog', { name: 'Modal Overlay' })).toBeVisible();
|
||||
|
||||
// Open Annotation Editor with Painterro
|
||||
await page.getByLabel('Annotate this snapshot').click();
|
||||
await expect(page.locator('#snap-annotation-canvas')).toBeVisible();
|
||||
// Clear the canvas
|
||||
await page.getByRole('button', { name: 'Put text [T]' }).click();
|
||||
// Click in the Painterro canvas to add a text annotation
|
||||
await page.locator('.ptro-crp-el').click();
|
||||
await page.locator('.ptro-text-tool-input').fill('...is there life on mars?');
|
||||
// When working with Painterro, we need to check that the Apply button is hidden after clicking
|
||||
await page.getByTitle('Apply').click();
|
||||
await expect(page.getByTitle('Apply')).toBeHidden();
|
||||
|
||||
// Save and exit annotation window
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await page.getByRole('button', { name: 'Done' }).click();
|
||||
|
||||
// Open up annotation again
|
||||
await page.getByRole('img', { name: 'My Items thumbnail' }).click();
|
||||
await expect(page.getByLabel('Modal Overlay').getByRole('img')).toBeVisible();
|
||||
|
||||
// Save as JPG
|
||||
await Promise.all([
|
||||
page.waitForEvent('download'), // Waits for the download event
|
||||
page.getByLabel('Export as JPG').click() // Triggers the download
|
||||
]);
|
||||
|
||||
// Save as PNG
|
||||
await expect(page.getByLabel('Modal Overlay').getByRole('img')).toBeVisible();
|
||||
await Promise.all([
|
||||
page.waitForEvent('download'), // Waits for the download event
|
||||
page.getByLabel('Export as PNG').click() // Triggers the download
|
||||
]);
|
||||
});
|
||||
test.fixme(
|
||||
'A snapshot can be Viewed, Annotated, display deleted, and saved from Container with 3 dot action menu',
|
||||
async ({ page }) => {
|
||||
await page.locator('.c-snapshot.c-ne__embed').first().getByTitle('More actions').click();
|
||||
await page.getByRole('menuitem', { name: ' View Snapshot' }).click();
|
||||
await expect(page.locator('.c-overlay__outer')).toBeVisible();
|
||||
await page.getByTitle('Annotate').click();
|
||||
await expect(page.locator('#snap-annotation-canvas')).toBeVisible();
|
||||
await page.getByRole('button', { name: '' }).click();
|
||||
// await expect(page.locator('#snap-annotation-canvas')).not.toBeVisible();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await page.getByRole('button', { name: 'Done' }).click();
|
||||
//await expect(await page.locator)
|
||||
}
|
||||
);
|
||||
test.fixme('5 Snapshots can be added to a container', async ({ page }) => {});
|
||||
test.fixme(
|
||||
'5 Snapshots can be added to a container and Deleted with Delete All action',
|
||||
@@ -116,10 +163,6 @@ test.describe('Snapshot Container tests', () => {
|
||||
'A snapshot can be Deleted from Container with 3 dot action menu',
|
||||
async ({ page }) => {}
|
||||
);
|
||||
test.fixme(
|
||||
'A snapshot can be Navigated To from Container with 3 dot action menu',
|
||||
async ({ page }) => {}
|
||||
);
|
||||
test.fixme(
|
||||
'A snapshot can be Navigated To Item in Time from Container with 3 dot action menu',
|
||||
async ({ page }) => {}
|
||||
@@ -151,11 +194,4 @@ test.describe('Snapshot Container tests', () => {
|
||||
//Snapshot removed from container?
|
||||
}
|
||||
);
|
||||
test.fixme(
|
||||
'Verify Embedded options for PNG, JPG, and Annotate work correctly',
|
||||
async ({ page }) => {
|
||||
//Add snapshot to container
|
||||
//Verify PNG, JPG, and Annotate buttons work correctly
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -24,138 +24,60 @@
|
||||
Tests to verify log plot functionality when objects are missing
|
||||
*/
|
||||
|
||||
import { createDomainObjectWithDefaults } from '../../../../appActions.js';
|
||||
import { expect, test } from '../../../../pluginFixtures.js';
|
||||
|
||||
test.describe('Handle missing object for plots', () => {
|
||||
test('Displays empty div for missing stacked plot item @unstable', async ({
|
||||
page,
|
||||
browserName,
|
||||
openmctConfig
|
||||
}) => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
});
|
||||
test('Displays empty div for missing stacked plot item', async ({ page, browserName }) => {
|
||||
// eslint-disable-next-line playwright/no-skipped-test
|
||||
test.skip(browserName === 'firefox', 'Firefox failing due to console events being missed');
|
||||
|
||||
const { myItemsFolderName } = openmctConfig;
|
||||
const errorLogs = [];
|
||||
let warningReceived = false;
|
||||
|
||||
page.on('console', (message) => {
|
||||
if (message.type() === 'warning' && message.text().includes('Missing domain object')) {
|
||||
errorLogs.push(message.text());
|
||||
warningReceived = true;
|
||||
}
|
||||
});
|
||||
|
||||
//Make stacked plot
|
||||
await makeStackedPlot(page, myItemsFolderName);
|
||||
const stackedPlot = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Stacked Plot'
|
||||
});
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
parent: stackedPlot.uuid
|
||||
});
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
parent: stackedPlot.uuid
|
||||
});
|
||||
|
||||
//Gets local storage and deletes the last sine wave generator in the stacked plot
|
||||
const localStorage = await page.evaluate(() => window.localStorage);
|
||||
const parsedData = JSON.parse(localStorage.mct);
|
||||
const keys = Object.keys(parsedData);
|
||||
const lastKey = keys[keys.length - 1];
|
||||
const mct = await page.evaluate(() => window.localStorage.getItem('mct'));
|
||||
const parsedData = JSON.parse(mct);
|
||||
const key = Object.entries(parsedData).find(([, value]) => value.type === 'generator')?.[0];
|
||||
|
||||
delete parsedData[lastKey];
|
||||
delete parsedData[key];
|
||||
|
||||
//Sets local storage with missing object
|
||||
await page.evaluate(`window.localStorage.setItem('mct', '${JSON.stringify(parsedData)}')`);
|
||||
const jsonData = JSON.stringify(parsedData);
|
||||
await page.evaluate((data) => {
|
||||
window.localStorage.setItem('mct', data);
|
||||
}, jsonData);
|
||||
|
||||
//Reloads page and clicks on stacked plot
|
||||
await Promise.all([page.reload(), page.waitForLoadState('networkidle')]);
|
||||
await page.reload({ waitUntil: 'domcontentloaded' });
|
||||
await page.goto(stackedPlot.url);
|
||||
|
||||
//Verify Main section is there on load
|
||||
await expect
|
||||
.soft(page.locator('.l-browse-bar__object-name'))
|
||||
.toContainText('Unnamed Stacked Plot');
|
||||
|
||||
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator('text=Unnamed Stacked Plot').first().click()
|
||||
]);
|
||||
await expect(page.locator('.l-browse-bar__object-name')).toContainText(stackedPlot.name);
|
||||
|
||||
//Check that there is only one stacked item plot with a plot, the missing one will be empty
|
||||
await expect(page.locator('.c-plot--stacked-container:has(.gl-plot)')).toHaveCount(1);
|
||||
//Verify that console.warn is thrown
|
||||
expect(errorLogs).toHaveLength(1);
|
||||
await expect(page.getByLabel('Stacked Plot Item')).toHaveCount(1);
|
||||
//Verify that console.warn was thrown
|
||||
expect(warningReceived).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* This is used the create a stacked plot object
|
||||
* @private
|
||||
*/
|
||||
async function makeStackedPlot(page, myItemsFolderName) {
|
||||
// fresh page with time range from 2022-03-29 22:00:00.000Z to 2022-03-29 22:00:30.000Z
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
|
||||
// create stacked plot
|
||||
await page.locator('button.c-create-button').click();
|
||||
await page.locator('li[role="menuitem"]:has-text("Stacked Plot")').click();
|
||||
|
||||
await Promise.all([
|
||||
page.waitForNavigation({ waitUntil: 'networkidle' }),
|
||||
page.locator('button:has-text("OK")').click(),
|
||||
//Wait for Save Banner to appear
|
||||
page.waitForSelector('.c-message-banner__message')
|
||||
]);
|
||||
|
||||
// save the stacked plot
|
||||
await saveStackedPlot(page);
|
||||
|
||||
// create a sinewave generator
|
||||
await createSineWaveGenerator(page);
|
||||
|
||||
// click on stacked plot
|
||||
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator('text=Unnamed Stacked Plot').first().click()
|
||||
]);
|
||||
|
||||
// create a second sinewave generator
|
||||
await createSineWaveGenerator(page);
|
||||
|
||||
// click on stacked plot
|
||||
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator('text=Unnamed Stacked Plot').first().click()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used to save a stacked plot object
|
||||
* @private
|
||||
*/
|
||||
async function saveStackedPlot(page) {
|
||||
// save stacked plot
|
||||
await page
|
||||
.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button')
|
||||
.nth(1)
|
||||
.click();
|
||||
|
||||
await Promise.all([
|
||||
page.locator('text=Save and Finish Editing').click(),
|
||||
//Wait for Save Banner to appear
|
||||
page.waitForSelector('.c-message-banner__message')
|
||||
]);
|
||||
//Wait until Save Banner is gone
|
||||
await page.locator('.c-message-banner__close-button').click();
|
||||
await page.waitForSelector('.c-message-banner__message', { state: 'detached' });
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used to create a sine wave generator object
|
||||
* @private
|
||||
*/
|
||||
async function createSineWaveGenerator(page) {
|
||||
//Create sine wave generator
|
||||
await page.locator('button.c-create-button').click();
|
||||
await page.locator('li[role="menuitem"]:has-text("Sine Wave Generator")').click();
|
||||
|
||||
await Promise.all([
|
||||
page.waitForNavigation({ waitUntil: 'networkidle' }),
|
||||
page.locator('button:has-text("OK")').click(),
|
||||
//Wait for Save Banner to appear
|
||||
page.waitForSelector('.c-message-banner__message')
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -20,10 +20,31 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import { createDomainObjectWithDefaults, setTimeConductorBounds } from '../../../../appActions.js';
|
||||
import {
|
||||
createDomainObjectWithDefaults,
|
||||
setTimeConductorBounds,
|
||||
setTimeConductorMode
|
||||
} from '../../../../appActions.js';
|
||||
import { expect, test } from '../../../../pluginFixtures.js';
|
||||
|
||||
test.describe('Telemetry Table', () => {
|
||||
let table;
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
table = await createDomainObjectWithDefaults(page, { type: 'Telemetry Table' });
|
||||
});
|
||||
|
||||
test('Limits to 50 rows by default', async ({ page }) => {
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
parent: table.uuid
|
||||
});
|
||||
await page.goto(table.url);
|
||||
await setTimeConductorMode(page, false);
|
||||
const rows = page.getByLabel('table content').getByLabel('Table Row');
|
||||
await expect(rows).toHaveCount(50);
|
||||
});
|
||||
|
||||
test('unpauses and filters data when paused by button and user changes bounds', async ({
|
||||
page
|
||||
}) => {
|
||||
@@ -34,7 +55,6 @@ test.describe('Telemetry Table', () => {
|
||||
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
|
||||
const table = await createDomainObjectWithDefaults(page, { type: 'Telemetry Table' });
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
parent: table.uuid
|
||||
@@ -78,7 +98,6 @@ test.describe('Telemetry Table', () => {
|
||||
test('Supports filtering telemetry by regular text search', async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
|
||||
const table = await createDomainObjectWithDefaults(page, { type: 'Telemetry Table' });
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Event Message Generator',
|
||||
parent: table.uuid
|
||||
@@ -121,7 +140,6 @@ test.describe('Telemetry Table', () => {
|
||||
test('Supports filtering using Regex', async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
|
||||
const table = await createDomainObjectWithDefaults(page, { type: 'Telemetry Table' });
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Event Message Generator',
|
||||
parent: table.uuid
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
import {
|
||||
createDomainObjectWithDefaults,
|
||||
setIndependentTimeConductorBounds
|
||||
} from '../../../../appActions.js';
|
||||
import { expect, test } from '../../../../pluginFixtures.js';
|
||||
|
||||
const FIXED_TIME =
|
||||
'./#/browse/mine?tc.mode=fixed&tc.startBound=1693592063607&tc.endBound=1693593893607&tc.timeSystem=utc&view=grid&hideInspector=true&hideTree=true';
|
||||
test.describe('Datepicker operations', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(FIXED_TIME);
|
||||
});
|
||||
test('Verify that user can use the datepicker in the TC', async ({ page }) => {
|
||||
await page.getByLabel('Time Conductor Mode').click();
|
||||
// Click on the date picker that is left-most on the screen
|
||||
await page.getByLabel('Global Time Conductor').locator('a').first().click();
|
||||
await expect(page.getByRole('dialog')).toBeVisible();
|
||||
// Click on the first cell
|
||||
await page.getByText('27 239').click();
|
||||
// Expect datepicker to close and time conductor date setting to be changed
|
||||
await expect(page.getByRole('dialog')).toHaveCount(0);
|
||||
});
|
||||
test('Verify that user can use the datepicker in the ITC', async ({ page }) => {
|
||||
const createdTimeList = await createDomainObjectWithDefaults(page, { type: 'Time List' });
|
||||
|
||||
await page.goto(createdTimeList.url, { waitUntil: 'domcontentloaded' });
|
||||
|
||||
await setIndependentTimeConductorBounds(page, {
|
||||
start: '2024-11-12 19:11:11.000Z',
|
||||
end: '2024-11-12 20:11:11.000Z'
|
||||
});
|
||||
// Open ITC
|
||||
await page.getByLabel('Start bounds').nth(0).click();
|
||||
// Click on the datepicker icon
|
||||
await page.locator('form a').first().click();
|
||||
await expect(page.getByRole('dialog')).toBeVisible();
|
||||
// Click on the first cell
|
||||
await page.getByText('7 342').click();
|
||||
// Expect datepicker to close and time conductor date setting to be changed
|
||||
await expect(page.getByRole('dialog')).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
75
e2e/tests/functional/ui/inspector.e2e.spec.js
Normal file
75
e2e/tests/functional/ui/inspector.e2e.spec.js
Normal file
@@ -0,0 +1,75 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
import { createDomainObjectWithDefaults } from '../../../appActions.js';
|
||||
import { expect, test } from '../../../baseFixtures.js';
|
||||
|
||||
// We don't need cspell to check this. It doesn't know latin.
|
||||
/* cSpell:disable */
|
||||
const loremIpsum = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Molestie at elementum eu facilisis sed. Feugiat pretium nibh ipsum consequat. Amet consectetur adipiscing elit duis tristique sollicitudin nibh sit amet. Eget nullam non nisi est sit amet. A pellentesque sit amet porttitor eget dolor morbi non arcu. Ullamcorper sit amet risus nullam eget felis eget nunc. In tellus integer feugiat scelerisque varius morbi enim nunc. Ac feugiat sed lectus vestibulum mattis ullamcorper. Nulla facilisi morbi tempus iaculis urna id volutpat. Massa vitae tortor condimentum lacinia quis vel eros donec. Ornare quam viverra orci sagittis eu. Vestibulum sed arcu non odio. In egestas erat imperdiet sed euismod nisi porta lorem. Vitae auctor eu augue ut lectus arcu bibendum at. Donec adipiscing tristique risus nec feugiat in fermentum posuere urna. Velit euismod in pellentesque massa placerat duis ultricies. Nulla facilisi nullam vehicula ipsum a arcu cursus vitae. Aliquam malesuada bibendum arcu vitae elementum curabitur.
|
||||
Vel eros donec ac odio tempor orci. Et netus et malesuada fames ac turpis egestas sed tempus. Turpis egestas pretium aenean pharetra magna ac placerat. Euismod elementum nisi quis eleifend. Vitae auctor eu augue ut lectus arcu. At imperdiet dui accumsan sit amet nulla facilisi. Est velit egestas dui id ornare arcu odio ut sem. Ornare arcu dui vivamus arcu felis. Luctus venenatis lectus magna fringilla. At elementum eu facilisis sed. Tristique et egestas quis ipsum suspendisse ultrices gravida dictum. Enim eu turpis egestas pretium aenean pharetra magna ac placerat. Lobortis scelerisque fermentum dui faucibus in. Tempor orci eu lobortis elementum nibh tellus molestie nunc non. Dignissim convallis aenean et tortor at risus. Enim tortor at auctor urna nunc id cursus. Libero volutpat sed cras ornare arcu dui vivamus. Scelerisque fermentum dui faucibus in ornare quam viverra.
|
||||
Odio ut sem nulla pharetra. Neque vitae tempus quam pellentesque nec. A arcu cursus vitae congue mauris. Turpis nunc eget lorem dolor sed viverra ipsum nunc aliquet. Nibh tellus molestie nunc non blandit massa enim nec. Risus feugiat in ante metus dictum at tempor commodo ullamcorper. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Pulvinar elementum integer enim neque. Bibendum ut tristique et egestas. Nibh praesent tristique magna sit. Lectus magna fringilla urna porttitor. Eu non diam phasellus vestibulum lorem sed risus. Rhoncus mattis rhoncus urna neque. Rutrum tellus pellentesque eu tincidunt tortor aliquam. Pharetra convallis posuere morbi leo urna molestie at elementum. Quis commodo odio aenean sed adipiscing. Enim sit amet venenatis urna cursus eget nunc.
|
||||
Enim nec dui nunc mattis. Cursus turpis massa tincidunt dui ut. Donec adipiscing tristique risus nec feugiat in. Eleifend mi in nulla posuere sollicitudin. Donec enim diam vulputate ut pharetra sit. Ultricies mi eget mauris pharetra et ultrices neque. Eros in cursus turpis massa tincidunt dui. Cursus risus at ultrices mi tempus imperdiet nulla malesuada. Morbi enim nunc faucibus a pellentesque sit. Porttitor rhoncus dolor purus non. Ac tortor vitae purus faucibus.
|
||||
Proin libero nunc consequat interdum varius sit amet mattis vulputate. Metus dictum at tempor commodo ullamcorper a lacus vestibulum sed. Quisque non tellus orci ac auctor augue mauris. Id ornare arcu odio ut. Rhoncus est pellentesque elit ullamcorper dignissim. Senectus et netus et malesuada fames ac turpis egestas. Volutpat ac tincidunt vitae semper quis lectus nulla. Adipiscing elit duis tristique sollicitudin. Ipsum faucibus vitae aliquet nec ullamcorper sit. Gravida neque convallis a cras semper auctor neque vitae tempus. Porttitor leo a diam sollicitudin tempor id. Dictum non consectetur a erat nam at lectus. At volutpat diam ut venenatis tellus in. Morbi enim nunc faucibus a pellentesque sit amet. Cursus in hac habitasse platea. Sed augue lacus viverra vitae.
|
||||
`;
|
||||
|
||||
test.describe('Inspector tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
});
|
||||
|
||||
test('Content in inspector can be scrolled to vertically', async ({ page }) => {
|
||||
const folderWithOverflowingTitle = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Folder',
|
||||
name: loremIpsum
|
||||
});
|
||||
|
||||
await page.goto(folderWithOverflowingTitle.url);
|
||||
|
||||
const inspectorPropertiesLocator = page
|
||||
.getByRole('tabpanel', { name: 'Inspector Views' })
|
||||
.getByLabel('Inspector Properties Details');
|
||||
const inspectorPropertiesList = inspectorPropertiesLocator.getByRole('list');
|
||||
const firstInspectorPropertyValue = inspectorPropertiesList
|
||||
.getByRole('listitem')
|
||||
.first()
|
||||
.getByLabel('value', { exact: false });
|
||||
const lastInspectorPropertyValue = inspectorPropertiesList
|
||||
.getByRole('listitem')
|
||||
.last()
|
||||
.getByLabel('value', { exact: false });
|
||||
|
||||
// inspector content partially in viewport, but not all the way in viewport
|
||||
await expect(inspectorPropertiesLocator).toBeInViewport();
|
||||
await expect(inspectorPropertiesLocator).not.toBeInViewport({ ratio: 0.9 });
|
||||
|
||||
await expect(firstInspectorPropertyValue).toBeInViewport();
|
||||
await expect(lastInspectorPropertyValue).not.toBeInViewport();
|
||||
|
||||
// using page.mouse.wheel to scroll the inspector content by the height of the content
|
||||
// because click and scrollIntoView will scroll even if scrollbar not available
|
||||
await inspectorPropertiesLocator.hover();
|
||||
const offset = await inspectorPropertiesLocator.evaluate((el) => el.offsetHeight);
|
||||
await page.mouse.wheel(0, offset);
|
||||
|
||||
await expect(lastInspectorPropertyValue).toBeInViewport();
|
||||
});
|
||||
});
|
||||
@@ -23,6 +23,7 @@
|
||||
import percySnapshot from '@percy/playwright';
|
||||
|
||||
import { createDomainObjectWithDefaults, setRealTimeMode } from '../../appActions.js';
|
||||
import { waitForAnimations } from '../../baseFixtures.js';
|
||||
import { VISUAL_URL } from '../../constants.js';
|
||||
import { expect, test } from '../../pluginFixtures.js';
|
||||
|
||||
@@ -75,11 +76,10 @@ test.describe('Visual - Example Imagery', () => {
|
||||
await page.goto(exampleImagery.url, { waitUntil: 'domcontentloaded' });
|
||||
|
||||
await setRealTimeMode(page, true);
|
||||
//Temporary to close the dialog
|
||||
await page.getByLabel('Submit time offsets').click();
|
||||
|
||||
await expect(page.getByLabel('Image Wrapper')).toBeVisible();
|
||||
|
||||
await waitForAnimations(page.locator('.animate-scroll'));
|
||||
await percySnapshot(page, `Example Imagery in Real Time (theme: ${theme})`);
|
||||
});
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
import percySnapshot from '@percy/playwright';
|
||||
|
||||
import { createDomainObjectWithDefaults, expandTreePaneItemByName } from '../../appActions.js';
|
||||
import { test } from '../../avpFixtures.js';
|
||||
import { expect, test } from '../../avpFixtures.js';
|
||||
import { VISUAL_URL } from '../../constants.js';
|
||||
import { enterTextEntry, startAndAddRestrictedNotebookObject } from '../../helper/notebookUtils.js';
|
||||
|
||||
@@ -39,6 +39,44 @@ test.describe('Visual - Restricted Notebook @a11y', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Visual - Notebook Snapshot @a11y', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./?hideTree=true&hideInspector=true', { waitUntil: 'domcontentloaded' });
|
||||
});
|
||||
test('Visual check for Snapshot Annotation', async ({ page, theme }) => {
|
||||
await page.getByLabel('Open the Notebook Snapshot Menu').click();
|
||||
await page.getByRole('menuitem', { name: 'Save to Notebook Snapshots' }).click();
|
||||
await page.getByLabel('Show Snapshots').click();
|
||||
|
||||
await page.getByLabel('My Items Notebook Embed').getByLabel('More actions').click();
|
||||
await page.getByRole('menuitem', { name: 'View Snapshot' }).click();
|
||||
|
||||
await page.getByLabel('Annotate this snapshot').click();
|
||||
await expect(page.locator('#snap-annotation-canvas')).toBeVisible();
|
||||
// Clear the canvas
|
||||
await page.getByRole('button', { name: 'Put text [T]' }).click();
|
||||
// Click in the Painterro canvas to add a text annotation
|
||||
await page.locator('.ptro-crp-el').click();
|
||||
await page.locator('.ptro-text-tool-input').fill('...is there life on mars?');
|
||||
await percySnapshot(page, `Notebook Snapshot with text entry open (theme: '${theme}')`);
|
||||
|
||||
// When working with Painterro, we need to check that the Apply button is hidden after clicking
|
||||
await page.getByTitle('Apply').click();
|
||||
await expect(page.getByTitle('Apply')).toBeHidden();
|
||||
|
||||
// Save and exit annotation window
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await page.getByRole('button', { name: 'Done' }).click();
|
||||
|
||||
// Open up annotation again
|
||||
await page.getByRole('img', { name: 'My Items thumbnail' }).click();
|
||||
await expect(page.getByLabel('Modal Overlay').getByRole('img')).toBeVisible();
|
||||
|
||||
// Take a snapshot
|
||||
await percySnapshot(page, `Notebook Snapshot with annotation (theme: '${theme}')`);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Visual - Notebook @a11y', () => {
|
||||
let notebook;
|
||||
test.beforeEach(async ({ page }) => {
|
||||
|
||||
@@ -72,11 +72,29 @@ test.describe('Visual - Planning', () => {
|
||||
name: 'Plan Visual Test',
|
||||
json: examplePlanSmall2
|
||||
});
|
||||
|
||||
await setBoundsToSpanAllActivities(page, examplePlanSmall2, plan.url);
|
||||
await percySnapshot(page, `Plan View (theme: ${theme})`);
|
||||
});
|
||||
|
||||
test('Resize Plan View @2p', async ({ browser, theme }) => {
|
||||
// need to set viewport to null to allow for resizing
|
||||
const newContext = await browser.newContext({
|
||||
viewport: null
|
||||
});
|
||||
const newPage = await newContext.newPage();
|
||||
|
||||
await newPage.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
||||
const plan = await createPlanFromJSON(newPage, {
|
||||
name: 'Plan Visual Test',
|
||||
json: examplePlanSmall2
|
||||
});
|
||||
|
||||
await setBoundsToSpanAllActivities(newPage, examplePlanSmall2, plan.url);
|
||||
// resize the window
|
||||
await newPage.setViewportSize({ width: 800, height: 600 });
|
||||
await percySnapshot(newPage, `Plan View resized (theme: ${theme})`);
|
||||
});
|
||||
|
||||
test('Plan View w/ draft status', async ({ page, theme }) => {
|
||||
const plan = await createPlanFromJSON(page, {
|
||||
name: 'Plan Visual Test (Draft)',
|
||||
|
||||
12271
package-lock.json
generated
Normal file
12271
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -10,14 +10,14 @@
|
||||
"@braintree/sanitize-url": "6.0.4",
|
||||
"@percy/cli": "1.27.4",
|
||||
"@percy/playwright": "1.0.4",
|
||||
"@playwright/test": "1.39.0",
|
||||
"@playwright/test": "1.42.1",
|
||||
"@types/d3-axis": "3.0.6",
|
||||
"@types/d3-shape": "3.0.0",
|
||||
"@types/d3-scale": "4.0.8",
|
||||
"@types/d3-selection": "3.0.10",
|
||||
"@types/eventemitter3": "1.2.0",
|
||||
"@types/jasmine": "5.1.2",
|
||||
"@types/lodash": "4.14.192",
|
||||
"@types/lodash": "4.17.0",
|
||||
"@vue/compiler-sfc": "3.4.3",
|
||||
"babel-loader": "9.1.0",
|
||||
"babel-plugin-istanbul": "6.1.1",
|
||||
@@ -91,7 +91,7 @@
|
||||
"webpack-merge": "5.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -rf ./dist ./node_modules ./package-lock.json ./coverage ./html-test-results ./test-results ./.nyc_output ",
|
||||
"clean": "rm -rf ./dist ./node_modules ./coverage ./html-test-results ./test-results ./.nyc_output ",
|
||||
"start": "npx webpack serve --config ./.webpack/webpack.dev.js",
|
||||
"start:prod": "npx webpack serve --config ./.webpack/webpack.prod.js",
|
||||
"start:coverage": "npx webpack serve --config ./.webpack/webpack.coverage.js",
|
||||
@@ -156,4 +156,4 @@
|
||||
"keywords": [
|
||||
"nasa"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -249,7 +249,7 @@ export default class ObjectAPI {
|
||||
.get(identifier, abortSignal)
|
||||
.then((domainObject) => {
|
||||
delete this.cache[keystring];
|
||||
if (!domainObject && abortSignal.aborted) {
|
||||
if (!domainObject && abortSignal?.aborted) {
|
||||
// we've aborted the request
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<template>
|
||||
<div class="c-overlay js-overlay">
|
||||
<div class="c-overlay js-overlay" role="dialog" aria-modal="true" aria-label="Modal Overlay">
|
||||
<div class="c-overlay__blocker" @click="destroy"></div>
|
||||
<div class="c-overlay__outer">
|
||||
<button
|
||||
@@ -34,9 +34,6 @@
|
||||
ref="element"
|
||||
class="c-overlay__contents js-notebook-snapshot-item-wrapper"
|
||||
tabindex="0"
|
||||
aria-modal="true"
|
||||
aria-label="Overlay"
|
||||
role="dialog"
|
||||
></div>
|
||||
<div v-if="buttons" class="c-overlay__button-bar">
|
||||
<button
|
||||
@@ -61,7 +58,7 @@
|
||||
export default {
|
||||
inject: ['dismiss', 'element', 'buttons', 'dismissable'],
|
||||
emits: ['destroy'],
|
||||
data: function () {
|
||||
data() {
|
||||
return {
|
||||
focusIndex: -1
|
||||
};
|
||||
|
||||
@@ -442,8 +442,12 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
} else {
|
||||
this.timeKey = undefined;
|
||||
|
||||
this._warn(TIMESYSTEM_KEY_WARNING);
|
||||
this.openmct.notifications.alert(TIMESYSTEM_KEY_NOTIFICATION);
|
||||
// missing objects will never have a domain, if one happens to get through
|
||||
// to this point this warning/notification does not apply
|
||||
if (!this.openmct.objects.isMissing(this.domainObject)) {
|
||||
this._warn(TIMESYSTEM_KEY_WARNING);
|
||||
this.openmct.notifications.alert(TIMESYSTEM_KEY_NOTIFICATION);
|
||||
}
|
||||
}
|
||||
|
||||
let valueFormatter = this.openmct.telemetry.getValueFormatter(metadataValue);
|
||||
|
||||
@@ -56,20 +56,38 @@ export default class ConditionManager extends EventEmitter {
|
||||
);
|
||||
}
|
||||
|
||||
subscribeToTelemetry(endpoint) {
|
||||
const id = this.openmct.objects.makeKeyString(endpoint.identifier);
|
||||
if (this.subscriptions[id]) {
|
||||
console.log('subscription already exists');
|
||||
async requestLatestValue(endpoint) {
|
||||
const options = {
|
||||
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[id] = Object.assign({}, endpoint, {
|
||||
this.telemetryObjects[telemetryKeyString] = Object.assign({}, endpoint, {
|
||||
telemetryMetaData: metadata ? metadata.valueMetadatas : []
|
||||
});
|
||||
this.subscriptions[id] = this.openmct.telemetry.subscribe(
|
||||
|
||||
// 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)
|
||||
);
|
||||
@@ -91,7 +109,7 @@ export default class ConditionManager extends EventEmitter {
|
||||
|
||||
//force re-computation of condition set result as we might be in a state where
|
||||
// there is no telemetry datum coming in for a while or at all.
|
||||
let latestTimestamp = getLatestTimestamp(
|
||||
const latestTimestamp = getLatestTimestamp(
|
||||
{},
|
||||
{},
|
||||
this.timeSystems,
|
||||
@@ -334,57 +352,54 @@ export default class ConditionManager extends EventEmitter {
|
||||
return currentCondition;
|
||||
}
|
||||
|
||||
requestLADConditionSetOutput(options) {
|
||||
async requestLADConditionSetOutput(options) {
|
||||
if (!this.conditions.length) {
|
||||
return Promise.resolve([]);
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.compositionLoad.then(() => {
|
||||
let latestTimestamp;
|
||||
let conditionResults = {};
|
||||
let nextLegOptions = { ...options };
|
||||
delete nextLegOptions.onPartialResponse;
|
||||
await this.compositionLoad;
|
||||
|
||||
const conditionRequests = this.conditions.map((condition) =>
|
||||
condition.requestLADConditionResult(nextLegOptions)
|
||||
let latestTimestamp;
|
||||
let conditionResults = {};
|
||||
let nextLegOptions = { ...options };
|
||||
delete nextLegOptions.onPartialResponse;
|
||||
|
||||
const results = await Promise.all(
|
||||
this.conditions.map((condition) => condition.requestLADConditionResult(nextLegOptions))
|
||||
);
|
||||
|
||||
results.forEach((resultObj) => {
|
||||
const {
|
||||
id,
|
||||
data,
|
||||
data: { result }
|
||||
} = resultObj;
|
||||
|
||||
if (this.findConditionById(id)) {
|
||||
conditionResults[id] = Boolean(result);
|
||||
}
|
||||
|
||||
latestTimestamp = getLatestTimestamp(
|
||||
latestTimestamp,
|
||||
data,
|
||||
this.timeSystems,
|
||||
this.openmct.time.timeSystem()
|
||||
);
|
||||
|
||||
return Promise.all(conditionRequests).then((results) => {
|
||||
results.forEach((resultObj) => {
|
||||
const {
|
||||
id,
|
||||
data,
|
||||
data: { result }
|
||||
} = resultObj;
|
||||
if (this.findConditionById(id)) {
|
||||
conditionResults[id] = Boolean(result);
|
||||
}
|
||||
|
||||
latestTimestamp = getLatestTimestamp(
|
||||
latestTimestamp,
|
||||
data,
|
||||
this.timeSystems,
|
||||
this.openmct.time.timeSystem()
|
||||
);
|
||||
});
|
||||
|
||||
if (!Object.values(latestTimestamp).some((timeSystem) => timeSystem)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const currentCondition = this.getCurrentConditionLAD(conditionResults);
|
||||
const currentOutput = Object.assign(
|
||||
{
|
||||
output: currentCondition.configuration.output,
|
||||
id: this.conditionSetDomainObject.identifier,
|
||||
conditionId: currentCondition.id
|
||||
},
|
||||
latestTimestamp
|
||||
);
|
||||
|
||||
return [currentOutput];
|
||||
});
|
||||
});
|
||||
|
||||
if (!Object.values(latestTimestamp).some((timeSystem) => timeSystem)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const currentCondition = this.getCurrentConditionLAD(conditionResults);
|
||||
const currentOutput = {
|
||||
output: currentCondition.configuration.output,
|
||||
id: this.conditionSetDomainObject.identifier,
|
||||
conditionId: currentCondition.id,
|
||||
...latestTimestamp
|
||||
};
|
||||
|
||||
return [currentOutput];
|
||||
}
|
||||
|
||||
isTelemetryUsed(endpoint) {
|
||||
@@ -409,7 +424,7 @@ export default class ConditionManager extends EventEmitter {
|
||||
}
|
||||
|
||||
const normalizedDatum = this.createNormalizedDatum(datum, endpoint);
|
||||
const timeSystemKey = this.openmct.time.timeSystem().key;
|
||||
const timeSystemKey = this.openmct.time.getTimeSystem().key;
|
||||
let timestamp = {};
|
||||
const currentTimestamp = normalizedDatum[timeSystemKey];
|
||||
timestamp[timeSystemKey] = currentTimestamp;
|
||||
|
||||
@@ -40,12 +40,10 @@ export default class ConditionSetTelemetryProvider {
|
||||
return domainObject.type === 'conditionSet';
|
||||
}
|
||||
|
||||
request(domainObject, options) {
|
||||
async request(domainObject, options) {
|
||||
let conditionManager = this.getConditionManager(domainObject);
|
||||
|
||||
return conditionManager.requestLADConditionSetOutput(options).then((latestOutput) => {
|
||||
return latestOutput;
|
||||
});
|
||||
let latestOutput = await conditionManager.requestLADConditionSetOutput(options);
|
||||
return latestOutput;
|
||||
}
|
||||
|
||||
subscribe(domainObject, callback) {
|
||||
|
||||
@@ -66,7 +66,8 @@ describe('the plugin', function () {
|
||||
format: 'utc',
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
},
|
||||
source: 'utc'
|
||||
},
|
||||
{
|
||||
key: 'testSource',
|
||||
@@ -720,6 +721,23 @@ describe('the plugin', function () {
|
||||
});
|
||||
|
||||
it('should evaluate as old when telemetry is not received in the allotted time', (done) => {
|
||||
openmct.telemetry = jasmine.createSpyObj('telemetry', [
|
||||
'subscribe',
|
||||
'getMetadata',
|
||||
'request',
|
||||
'getValueFormatter',
|
||||
'abortAllRequests'
|
||||
]);
|
||||
openmct.telemetry.getMetadata.and.returnValue({
|
||||
...testTelemetryObject.telemetry,
|
||||
valueMetadatas: []
|
||||
});
|
||||
openmct.telemetry.request.and.returnValue(Promise.resolve([]));
|
||||
openmct.telemetry.getValueFormatter.and.returnValue({
|
||||
parse: function (value) {
|
||||
return value;
|
||||
}
|
||||
});
|
||||
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
||||
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
||||
conditionMgr.telemetryObjects = {
|
||||
@@ -741,6 +759,20 @@ describe('the plugin', function () {
|
||||
});
|
||||
|
||||
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
|
||||
});
|
||||
const testDatum = {
|
||||
'some-key2': '',
|
||||
utc: 1,
|
||||
testSource: '',
|
||||
'some-key': null,
|
||||
id: 'test-object'
|
||||
};
|
||||
openmct.telemetry.request = jasmine.createSpy('request');
|
||||
openmct.telemetry.request.and.returnValue(Promise.resolve([testDatum]));
|
||||
const date = 1;
|
||||
conditionSetDomainObject.configuration.conditionCollection[0].configuration.criteria[0].input =
|
||||
['0.4'];
|
||||
@@ -750,9 +782,7 @@ describe('the plugin', function () {
|
||||
'test-object': testTelemetryObject
|
||||
};
|
||||
conditionMgr.updateConditionTelemetryObjects();
|
||||
conditionMgr.telemetryReceived(testTelemetryObject, {
|
||||
utc: date
|
||||
});
|
||||
conditionMgr.telemetryReceived(testTelemetryObject, testDatum);
|
||||
setTimeout(() => {
|
||||
expect(mockListener).toHaveBeenCalledWith({
|
||||
output: 'Default',
|
||||
@@ -868,6 +898,12 @@ describe('the plugin', function () {
|
||||
it('should stop evaluating conditions when a condition evaluates to true', () => {
|
||||
const date = Date.now();
|
||||
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
||||
|
||||
openmct.telemetry.getMetadata = jasmine.createSpy('getMetadata');
|
||||
openmct.telemetry.getMetadata.and.returnValue({
|
||||
...testTelemetryObject.telemetry,
|
||||
valueMetadatas: []
|
||||
});
|
||||
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
||||
conditionMgr.telemetryObjects = {
|
||||
'test-object': testTelemetryObject
|
||||
|
||||
@@ -150,16 +150,15 @@ export default class ImportAsJSONAction {
|
||||
* @param {string} namespace
|
||||
* @returns {object}
|
||||
*/
|
||||
_generateNewIdentifiers(tree, namespace) {
|
||||
_generateNewIdentifiers(tree, newNamespace) {
|
||||
// For each domain object in the file, generate new ID, replace in tree
|
||||
Object.keys(tree.openmct).forEach((domainObjectId) => {
|
||||
const newId = {
|
||||
namespace,
|
||||
key: uuid()
|
||||
};
|
||||
|
||||
const oldId = parseKeyString(domainObjectId);
|
||||
|
||||
const newId = {
|
||||
namespace: newNamespace,
|
||||
key: uuid()
|
||||
};
|
||||
tree = this._rewriteId(oldId, newId, tree);
|
||||
}, this);
|
||||
|
||||
@@ -228,22 +227,32 @@ export default class ImportAsJSONAction {
|
||||
_rewriteId(oldId, newId, tree) {
|
||||
let newIdKeyString = this.openmct.objects.makeKeyString(newId);
|
||||
let oldIdKeyString = this.openmct.objects.makeKeyString(oldId);
|
||||
tree = JSON.stringify(tree).replace(new RegExp(oldIdKeyString, 'g'), newIdKeyString);
|
||||
|
||||
return JSON.parse(tree, (key, value) => {
|
||||
const newTreeString = JSON.stringify(tree).replace(
|
||||
new RegExp(oldIdKeyString, 'g'),
|
||||
newIdKeyString
|
||||
);
|
||||
const newTree = JSON.parse(newTreeString, (key, value) => {
|
||||
if (
|
||||
value !== undefined &&
|
||||
value !== null &&
|
||||
Object.prototype.hasOwnProperty.call(value, 'key') &&
|
||||
Object.prototype.hasOwnProperty.call(value, 'namespace') &&
|
||||
value.key === oldId.key &&
|
||||
value.namespace === oldId.namespace
|
||||
Object.prototype.hasOwnProperty.call(value, 'namespace')
|
||||
) {
|
||||
return newId;
|
||||
} else {
|
||||
return value;
|
||||
// first check if key is messed up from regex and contains a colon
|
||||
// if it does, repair it
|
||||
if (value.key.includes(':')) {
|
||||
const splitKey = value.key.split(':');
|
||||
value.key = splitKey[1];
|
||||
value.namespace = splitKey[0];
|
||||
}
|
||||
// now check if we need to replace the id
|
||||
if (value.key === oldId.key && value.namespace === oldId.namespace) {
|
||||
return newId;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
return newTree;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
|
||||
@@ -135,11 +135,75 @@ describe('The import JSON action', function () {
|
||||
selectFile: {
|
||||
name: 'imported object',
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
body: "{\"openmct\":{\"c28d230d-e909-4a3e-9840-d9ef469dda70\":{\"identifier\":{\"key\":\"c28d230d-e909-4a3e-9840-d9ef469dda70\",\"namespace\":\"\"},\"name\":\"Unnamed Overlay Plot\",\"type\":\"telemetry.plot.overlay\",\"composition\":[],\"configuration\":{\"series\":[]},\"modified\":1695837546833,\"location\":\"mine\",\"created\":1695837546833,\"persisted\":1695837546833,\"__proto__\":{\"toString\":\"foobar\"}}},\"rootId\":\"c28d230d-e909-4a3e-9840-d9ef469dda70\"}"
|
||||
body: '{"openmct":{"c28d230d-e909-4a3e-9840-d9ef469dda70":{"identifier":{"key":"c28d230d-e909-4a3e-9840-d9ef469dda70","namespace":""},"name":"Unnamed Overlay Plot","type":"telemetry.plot.overlay","composition":[],"configuration":{"series":[]},"modified":1695837546833,"location":"mine","created":1695837546833,"persisted":1695837546833,"__proto__":{"toString":"foobar"}}},"rootId":"c28d230d-e909-4a3e-9840-d9ef469dda70"}'
|
||||
}
|
||||
};
|
||||
|
||||
return Promise.resolve(pollutedResponse);
|
||||
}
|
||||
});
|
||||
it('preserves the integrity of the namespace and key during import', async () => {
|
||||
const incomingObject = {
|
||||
openmct: {
|
||||
'7323f02a-06ac-438d-bd58-6d6e33b8741e': {
|
||||
name: 'Some Folder',
|
||||
type: 'folder',
|
||||
composition: [
|
||||
{
|
||||
key: '9f6c2d21-5ec8-434c-9fe8-31614ae6d7e6',
|
||||
namespace: ''
|
||||
}
|
||||
],
|
||||
modified: 1710843256162,
|
||||
location: 'mine',
|
||||
created: 1710843243471,
|
||||
persisted: 1710843256162,
|
||||
identifier: {
|
||||
namespace: '',
|
||||
key: '7323f02a-06ac-438d-bd58-6d6e33b8741e'
|
||||
}
|
||||
},
|
||||
'9f6c2d21-5ec8-434c-9fe8-31614ae6d7e6': {
|
||||
name: 'Some Clock',
|
||||
type: 'clock',
|
||||
configuration: {
|
||||
baseFormat: 'YYYY/MM/DD hh:mm:ss',
|
||||
use24: 'clock12',
|
||||
timezone: 'UTC'
|
||||
},
|
||||
modified: 1710843256152,
|
||||
location: '7323f02a-06ac-438d-bd58-6d6e33b8741e',
|
||||
created: 1710843256152,
|
||||
persisted: 1710843256152,
|
||||
identifier: {
|
||||
namespace: '',
|
||||
key: '9f6c2d21-5ec8-434c-9fe8-31614ae6d7e6'
|
||||
}
|
||||
}
|
||||
},
|
||||
rootId: '7323f02a-06ac-438d-bd58-6d6e33b8741e'
|
||||
};
|
||||
|
||||
const targetDomainObject = {
|
||||
identifier: {
|
||||
namespace: 'starJones',
|
||||
key: '84438cda-a071-48d1-b9bf-d77bd53e59ba'
|
||||
},
|
||||
type: 'folder'
|
||||
};
|
||||
spyOn(openmct.objects, 'save').and.callFake((model) => Promise.resolve(model));
|
||||
try {
|
||||
await importFromJSONAction.onSave(targetDomainObject, {
|
||||
selectFile: { body: JSON.stringify(incomingObject) }
|
||||
});
|
||||
|
||||
for (const callArgs of openmct.objects.save.calls.allArgs()) {
|
||||
const savedObject = callArgs[0]; // Assuming the first argument is the object being saved.
|
||||
expect(savedObject.identifier.key.includes(':')).toBeFalse(); // Ensure no colon in the key.
|
||||
expect(savedObject.identifier.namespace).toBe(targetDomainObject.identifier.namespace);
|
||||
}
|
||||
} catch (error) {
|
||||
fail(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,7 +22,10 @@
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="c-inspector__properties c-inspect-properties">
|
||||
<div
|
||||
class="c-inspector__properties c-inspect-properties"
|
||||
aria-label="Inspector Properties Details"
|
||||
>
|
||||
<div class="c-inspect-properties__header">Details</div>
|
||||
<ul v-if="hasDetails" class="c-inspect-properties__section">
|
||||
<Component
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<div
|
||||
ref="notebookEmbed"
|
||||
class="c-snapshot c-ne__embed"
|
||||
:aria-label="`${embed.name} Notebook Embed`"
|
||||
@mouseover.ctrl="showToolTip"
|
||||
@mouseleave="hideToolTip"
|
||||
>
|
||||
|
||||
@@ -1,36 +1,32 @@
|
||||
<div class="c-notebook-snapshot">
|
||||
<!-- parent container sets up this for flex column layout -->
|
||||
<div class="c-notebook-snapshot__header l-browse-bar">
|
||||
<div class="l-browse-bar__start">
|
||||
<div class="l-browse-bar__object-name--w">
|
||||
<span class="c-object-label l-browse-bar__object-name">
|
||||
<span class="c-object-label__type-icon" v-bind:class="cssClass"></span>
|
||||
<span class="c-object-label__type-icon" :class="cssClass"></span>
|
||||
<span class="c-object-label__name">{{ name }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="snapshotDescriptor" class="l-browse-bar__snapshot-datetime">
|
||||
SNAPSHOT {{ createdOn }}
|
||||
</div>
|
||||
<div class="c-button-set c-button-set--strip-h" role="toolbar">
|
||||
<button class="c-button icon-download" aria-label="Export as PNG" @click="exportImage('png')">
|
||||
<span class="c-button__label">PNG</span>
|
||||
</button>
|
||||
<button class="c-button icon-download" aria-label="Export as JPG" @click="exportImage('jpg')">
|
||||
<span class="c-button__label">JPG</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="l-browse-bar__end">
|
||||
<div class="l-browse-bar__snapshot-datetime">SNAPSHOT {{ createdOn }}</div>
|
||||
<span class="c-button-set c-button-set--strip-h">
|
||||
<button
|
||||
class="c-button icon-download"
|
||||
title="Export This View's Data as PNG"
|
||||
@click="exportImage('png')"
|
||||
>
|
||||
<span class="c-button__label">PNG</span>
|
||||
</button>
|
||||
<button class="c-button" title="Export This View's Data as JPG" @click="exportImage('jpg')">
|
||||
<span class="c-button__label">JPG</span>
|
||||
</button>
|
||||
</span>
|
||||
<a
|
||||
<button
|
||||
class="l-browse-bar__annotate-button c-button icon-pencil"
|
||||
title="Annotate"
|
||||
aria-label="Annotate this snapshot"
|
||||
@click="annotateSnapshot"
|
||||
>
|
||||
<span class="title-label">Annotate</span>
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -38,5 +34,7 @@
|
||||
ref="snapshot-image"
|
||||
class="c-notebook-snapshot__image"
|
||||
:style="{ backgroundImage: 'url(' + src + ')' }"
|
||||
role="img"
|
||||
alt="Annotatable Snapshot"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
@@ -169,6 +169,8 @@ describe('Notebook plugin:', () => {
|
||||
|
||||
openmct.editor = {};
|
||||
openmct.editor.isEditing = () => false;
|
||||
openmct.editor.on = () => {};
|
||||
openmct.editor.off = () => {};
|
||||
|
||||
const applicableViews = openmct.objectViews.get(notebookViewObject, [notebookViewObject]);
|
||||
notebookViewProvider = applicableViews.find(
|
||||
|
||||
@@ -46,7 +46,7 @@ export default class PainterroInstance {
|
||||
this.config.id = this.elementId;
|
||||
this.config.saveHandler = this.saveHandler.bind(this);
|
||||
|
||||
this.painterro = Painterro(this.config);
|
||||
this.painterro = Painterro.default(this.config);
|
||||
}
|
||||
|
||||
save(callback) {
|
||||
|
||||
@@ -46,6 +46,11 @@ export default class SeriesCollection extends Collection {
|
||||
this.listenTo(this.plot, 'change:domainObject', this.trackPersistedConfig, this);
|
||||
|
||||
const domainObject = this.plot.get('domainObject');
|
||||
|
||||
if (this.openmct.objects.isMissing(domainObject)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (domainObject.telemetry) {
|
||||
this.addTelemetryObject(domainObject);
|
||||
} else {
|
||||
|
||||
@@ -219,6 +219,11 @@ export default {
|
||||
},
|
||||
|
||||
addChild(child) {
|
||||
if (this.openmct.objects.isMissing(child)) {
|
||||
console.warn('Missing domain object for stacked plot: ', child);
|
||||
return;
|
||||
}
|
||||
|
||||
const id = this.openmct.objects.makeKeyString(child.identifier);
|
||||
|
||||
this.tickWidthMap[id] = {
|
||||
|
||||
@@ -170,6 +170,10 @@ export default {
|
||||
//If this object is not persistable, then package it with it's parent
|
||||
const plotObject = this.getPlotObject();
|
||||
|
||||
if (plotObject === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.openmct.telemetry.isTelemetryObject(plotObject)) {
|
||||
this.subscribeToStaleness(plotObject);
|
||||
} else {
|
||||
@@ -215,10 +219,6 @@ export default {
|
||||
},
|
||||
getPlotObject() {
|
||||
this.checkPlotConfiguration();
|
||||
// If object is missing, warn
|
||||
if (this.openmct.objects.isMissing(this.childObject)) {
|
||||
console.warn('Missing domain object for stacked plot', this.childObject);
|
||||
}
|
||||
return this.childObject;
|
||||
},
|
||||
checkPlotConfiguration() {
|
||||
|
||||
@@ -25,7 +25,7 @@ import mount from 'utils/mount';
|
||||
import TableConfigurationComponent from './components/TableConfiguration.vue';
|
||||
import TelemetryTableConfiguration from './TelemetryTableConfiguration.js';
|
||||
|
||||
export default function TableConfigurationViewProvider(openmct) {
|
||||
export default function TableConfigurationViewProvider(openmct, options) {
|
||||
return {
|
||||
key: 'table-configuration',
|
||||
name: 'Config',
|
||||
@@ -45,7 +45,7 @@ export default function TableConfigurationViewProvider(openmct) {
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct);
|
||||
tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct, options);
|
||||
const { destroy } = mount(
|
||||
{
|
||||
el: element,
|
||||
|
||||
@@ -32,14 +32,14 @@ import TelemetryTableRow from './TelemetryTableRow.js';
|
||||
import TelemetryTableUnitColumn from './TelemetryTableUnitColumn.js';
|
||||
|
||||
export default class TelemetryTable extends EventEmitter {
|
||||
constructor(domainObject, openmct) {
|
||||
constructor(domainObject, openmct, options) {
|
||||
super();
|
||||
|
||||
this.domainObject = domainObject;
|
||||
this.openmct = openmct;
|
||||
this.tableComposition = undefined;
|
||||
this.datumCache = [];
|
||||
this.configuration = new TelemetryTableConfiguration(domainObject, openmct);
|
||||
this.configuration = new TelemetryTableConfiguration(domainObject, openmct, options);
|
||||
this.telemetryMode = this.configuration.getTelemetryMode();
|
||||
this.rowLimit = this.configuration.getRowLimit();
|
||||
this.paused = false;
|
||||
@@ -114,7 +114,11 @@ export default class TelemetryTable extends EventEmitter {
|
||||
this.clearAndResubscribe();
|
||||
}
|
||||
|
||||
updateRowLimit() {
|
||||
updateRowLimit(rowLimit) {
|
||||
if (rowLimit) {
|
||||
this.rowLimit = rowLimit;
|
||||
}
|
||||
|
||||
if (this.telemetryMode === 'performance') {
|
||||
this.tableRows.setLimit(this.rowLimit);
|
||||
} else {
|
||||
|
||||
@@ -24,11 +24,12 @@ import EventEmitter from 'EventEmitter';
|
||||
import _ from 'lodash';
|
||||
|
||||
export default class TelemetryTableConfiguration extends EventEmitter {
|
||||
constructor(domainObject, openmct) {
|
||||
constructor(domainObject, openmct, options) {
|
||||
super();
|
||||
|
||||
this.domainObject = domainObject;
|
||||
this.openmct = openmct;
|
||||
this.defaultOptions = options;
|
||||
this.columns = {};
|
||||
|
||||
this.removeColumnsForObject = this.removeColumnsForObject.bind(this);
|
||||
@@ -48,10 +49,12 @@ export default class TelemetryTableConfiguration extends EventEmitter {
|
||||
configuration.columnOrder = configuration.columnOrder || [];
|
||||
configuration.cellFormat = configuration.cellFormat || {};
|
||||
configuration.autosize = configuration.autosize === undefined ? true : configuration.autosize;
|
||||
// anything that doesn't have a telemetryMode existed before the change and should stay as it was for consistency
|
||||
configuration.telemetryMode = configuration.telemetryMode ?? 'unlimited';
|
||||
configuration.persistModeChange = configuration.persistModeChange ?? true;
|
||||
configuration.rowLimit = configuration.rowLimit ?? 50;
|
||||
// anything that doesn't have a telemetryMode existed before the change and should
|
||||
// take the properties of any passed in defaults or the defaults from the plugin
|
||||
configuration.telemetryMode = configuration.telemetryMode ?? this.defaultOptions.telemetryMode;
|
||||
configuration.persistModeChange =
|
||||
configuration.persistModeChange ?? this.defaultOptions.persistModeChange;
|
||||
configuration.rowLimit = configuration.rowLimit ?? this.defaultOptions.rowLimit;
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
export default function getTelemetryTableType(options = {}) {
|
||||
const { telemetryMode = 'performance', persistModeChanges = true, rowLimit = 50 } = options;
|
||||
export default function getTelemetryTableType(options) {
|
||||
let { telemetryMode, persistModeChange, rowLimit } = options;
|
||||
|
||||
return {
|
||||
name: 'Telemetry Table',
|
||||
@@ -32,12 +32,12 @@ export default function getTelemetryTableType(options = {}) {
|
||||
form: [
|
||||
{
|
||||
key: 'telemetryMode',
|
||||
name: 'Telemetry Mode',
|
||||
name: 'Data Mode',
|
||||
control: 'select',
|
||||
options: [
|
||||
{
|
||||
value: 'performance',
|
||||
name: 'Performance Mode'
|
||||
name: 'Limited (Performance) Mode'
|
||||
},
|
||||
{
|
||||
value: 'unlimited',
|
||||
@@ -48,15 +48,15 @@ export default function getTelemetryTableType(options = {}) {
|
||||
property: ['configuration', 'telemetryMode']
|
||||
},
|
||||
{
|
||||
name: 'Persist Telemetry Mode Changes',
|
||||
name: 'Persist Data Mode Changes',
|
||||
control: 'toggleSwitch',
|
||||
cssClass: 'l-input',
|
||||
key: 'persistModeChanges',
|
||||
property: ['configuration', 'persistModeChanges']
|
||||
key: 'persistModeChange',
|
||||
property: ['configuration', 'persistModeChange']
|
||||
},
|
||||
{
|
||||
name: 'Performance Mode Row Limit',
|
||||
control: 'toggleSwitch',
|
||||
name: 'Limited Data Mode Row Limit',
|
||||
control: 'numberfield',
|
||||
cssClass: 'l-input',
|
||||
key: 'rowLimit',
|
||||
property: ['configuration', 'rowLimit']
|
||||
@@ -68,7 +68,7 @@ export default function getTelemetryTableType(options = {}) {
|
||||
columnWidths: {},
|
||||
hiddenColumns: {},
|
||||
telemetryMode,
|
||||
persistModeChanges,
|
||||
persistModeChange,
|
||||
rowLimit
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export default class TelemetryTableView {
|
||||
this.component = null;
|
||||
|
||||
Object.defineProperty(this, 'table', {
|
||||
value: new TelemetryTable(domainObject, openmct),
|
||||
value: new TelemetryTable(domainObject, openmct, options),
|
||||
enumerable: false,
|
||||
configurable: false
|
||||
});
|
||||
|
||||
@@ -398,14 +398,17 @@ export default {
|
||||
totalNumberOfRows: 0,
|
||||
rowContext: {},
|
||||
telemetryMode: configuration.telemetryMode,
|
||||
persistModeChanges: configuration.persistModeChanges
|
||||
rowLimit: configuration.rowLimit,
|
||||
persistModeChange: configuration.persistModeChange,
|
||||
afterLoadActions: [],
|
||||
existingConfiguration: configuration
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dropTargetStyle() {
|
||||
return {
|
||||
top: this.$refs.headersTable.offsetTop + 'px',
|
||||
height: this.totalHeight + this.$refs.headersTable.offsetHeight + 'px',
|
||||
top: this.$refs.headersHolderEl.offsetTop + 'px',
|
||||
height: this.totalHeight + this.$refs.headersHolderEl.offsetHeight + 'px',
|
||||
left: this.dropOffsetLeft && this.dropOffsetLeft + 'px'
|
||||
};
|
||||
},
|
||||
@@ -458,10 +461,8 @@ export default {
|
||||
},
|
||||
loading: {
|
||||
handler(isLoading) {
|
||||
if (isLoading) {
|
||||
this.setLoadingPromise();
|
||||
} else {
|
||||
this.loadFinishResolve();
|
||||
if (!isLoading) {
|
||||
this.runAfterLoadActions();
|
||||
}
|
||||
|
||||
if (this.viewActionsCollection) {
|
||||
@@ -538,6 +539,8 @@ export default {
|
||||
this.table.on('outstanding-requests', this.outstandingRequests);
|
||||
this.table.on('telemetry-staleness', this.handleStaleness);
|
||||
|
||||
this.table.configuration.on('change', this.handleConfigurationChanges);
|
||||
|
||||
this.table.tableRows.on('add', this.rowsAdded);
|
||||
this.table.tableRows.on('remove', this.rowsRemoved);
|
||||
this.table.tableRows.on('sort', this.throttledUpdateVisibleRows);
|
||||
@@ -567,6 +570,8 @@ export default {
|
||||
this.table.off('outstanding-requests', this.outstandingRequests);
|
||||
this.table.off('telemetry-staleness', this.handleStaleness);
|
||||
|
||||
this.table.configuration.off('change', this.handleConfigurationChanges);
|
||||
|
||||
this.table.tableRows.off('add', this.rowsAdded);
|
||||
this.table.tableRows.off('remove', this.rowsRemoved);
|
||||
this.table.tableRows.off('sort', this.throttledUpdateVisibleRows);
|
||||
@@ -581,11 +586,46 @@ export default {
|
||||
this.table.destroy();
|
||||
},
|
||||
methods: {
|
||||
setLoadingPromise() {
|
||||
this.loadFinishResolve = null;
|
||||
this.isFinishedLoading = new Promise((resolve, reject) => {
|
||||
this.loadFinishResolve = resolve;
|
||||
});
|
||||
addToAfterLoadActions(func) {
|
||||
this.afterLoadActions.push(func);
|
||||
},
|
||||
runAfterLoadActions() {
|
||||
if (this.afterLoadActions.length > 0) {
|
||||
this.afterLoadActions.forEach((action) => action());
|
||||
this.afterLoadActions = [];
|
||||
}
|
||||
},
|
||||
handleConfigurationChanges(changes) {
|
||||
const { rowLimit, telemetryMode, persistModeChange } = changes;
|
||||
const telemetryModeChanged = this.existingConfiguration.telemetryMode !== telemetryMode;
|
||||
let rowLimitChanged = false;
|
||||
|
||||
this.persistModeChange = persistModeChange;
|
||||
|
||||
// both rowLimit changes and telemetryMode changes
|
||||
// require a re-request of telemetry
|
||||
|
||||
if (this.rowLimit !== rowLimit) {
|
||||
rowLimitChanged = true;
|
||||
this.rowLimit = rowLimit;
|
||||
this.table.updateRowLimit(rowLimit);
|
||||
}
|
||||
|
||||
// check for telemetry mode change, because you could technically have persist mode changes
|
||||
// set to false, which could create a state where the configuration saved telemetry mode is
|
||||
// different from the currently set telemetry mode
|
||||
if (telemetryModeChanged && this.telemetryMode !== telemetryMode) {
|
||||
this.telemetryMode = telemetryMode;
|
||||
|
||||
// this method also re-requests telemetry
|
||||
this.table.updateTelemetryMode(telemetryMode);
|
||||
}
|
||||
|
||||
if (rowLimitChanged && !telemetryModeChanged) {
|
||||
this.table.clearAndResubscribe();
|
||||
}
|
||||
|
||||
this.existingConfiguration = changes;
|
||||
},
|
||||
updateVisibleRows() {
|
||||
if (!this.updatingView) {
|
||||
@@ -1042,7 +1082,7 @@ export default {
|
||||
let row = allRows[i];
|
||||
row.marked = true;
|
||||
|
||||
if (row !== baseRow) {
|
||||
if (row !== baseRow && this.markedRows.indexOf(row) === -1) {
|
||||
this.markedRows.push(row);
|
||||
}
|
||||
}
|
||||
@@ -1166,11 +1206,9 @@ export default {
|
||||
{
|
||||
label,
|
||||
emphasis: true,
|
||||
callback: async () => {
|
||||
callback: () => {
|
||||
this.addToAfterLoadActions(callback);
|
||||
this.updateTelemetryMode();
|
||||
await this.isFinishedLoading;
|
||||
|
||||
callback();
|
||||
|
||||
dialog.dismiss();
|
||||
}
|
||||
@@ -1187,7 +1225,7 @@ export default {
|
||||
updateTelemetryMode() {
|
||||
this.telemetryMode = this.telemetryMode === 'unlimited' ? 'performance' : 'unlimited';
|
||||
|
||||
if (this.persistModeChanges) {
|
||||
if (this.persistModeChange) {
|
||||
this.table.configuration.setTelemetryMode(this.telemetryMode);
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
:title="rowCountTitle"
|
||||
class="c-table-indicator__elem c-table-indicator__row-count"
|
||||
>
|
||||
{{ rowCount }} Rows
|
||||
{{ rowCount }}
|
||||
</span>
|
||||
|
||||
<span
|
||||
@@ -113,7 +113,7 @@ export default {
|
||||
}
|
||||
},
|
||||
rowCount() {
|
||||
return this.isUnlimitedMode ? this.totalRows : 'LATEST 50';
|
||||
return this.isUnlimitedMode ? `${this.totalRows} ROWS` : `LATEST ${this.totalRows} ROWS`;
|
||||
},
|
||||
rowCountTitle() {
|
||||
return this.isUnlimitedMode
|
||||
@@ -121,12 +121,12 @@ export default {
|
||||
: 'performance mode limited to 50 rows';
|
||||
},
|
||||
telemetryModeButtonLabel() {
|
||||
return this.isUnlimitedMode ? 'SHOW LATEST 50' : 'SHOW ALL';
|
||||
return this.isUnlimitedMode ? 'SHOW LIMITED' : 'SHOW UNLIMITED';
|
||||
},
|
||||
telemetryModeButtonTitle() {
|
||||
return this.isUnlimitedMode
|
||||
? 'Change to Performance mode (latest 50 values)'
|
||||
: 'Change to show all values';
|
||||
? 'Change to Limited (Performance) Mode'
|
||||
: 'Change to Unlimited Mode';
|
||||
},
|
||||
title() {
|
||||
if (this.hasMixedFilters) {
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
:style="{ top: rowTop }"
|
||||
class="noselect"
|
||||
:class="[rowClass, { 'is-selected': marked }]"
|
||||
:aria-label="ariaLabel"
|
||||
v-on="listeners"
|
||||
>
|
||||
<component
|
||||
@@ -99,6 +100,9 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
ariaLabel() {
|
||||
return this.marked ? 'Selected Table Row' : 'Table Row';
|
||||
},
|
||||
listeners() {
|
||||
let listenersObject = {
|
||||
click: this.markRow
|
||||
|
||||
@@ -25,10 +25,12 @@ import getTelemetryTableType from './TelemetryTableType.js';
|
||||
import TelemetryTableViewProvider from './TelemetryTableViewProvider.js';
|
||||
import TelemetryTableViewActions from './ViewActions.js';
|
||||
|
||||
export default function plugin(options) {
|
||||
export default function plugin(
|
||||
options = { telemetryMode: 'performance', persistModeChange: true, rowLimit: 50 }
|
||||
) {
|
||||
return function install(openmct) {
|
||||
openmct.objectViews.addProvider(new TelemetryTableViewProvider(openmct, options));
|
||||
openmct.inspectorViews.addProvider(new TableConfigurationViewProvider(openmct));
|
||||
openmct.inspectorViews.addProvider(new TableConfigurationViewProvider(openmct, options));
|
||||
openmct.types.addType('table', getTelemetryTableType(options));
|
||||
openmct.composition.addPolicy((parent, child) => {
|
||||
if (parent.type === 'table') {
|
||||
|
||||
@@ -195,7 +195,10 @@ describe('the plugin', () => {
|
||||
utc: false,
|
||||
'some-key': false,
|
||||
'some-other-key': false
|
||||
}
|
||||
},
|
||||
persistModeChange: true,
|
||||
rowLimit: 50,
|
||||
telemetryMode: 'performance'
|
||||
}
|
||||
};
|
||||
const testTelemetry = [
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
}"
|
||||
>
|
||||
<a class="c-icon-button icon-calendar" @click="toggle"></a>
|
||||
<div v-if="open" class="c-menu c-menu--mobile-modal c-datetime-picker">
|
||||
<div v-if="open" role="dialog" class="c-menu c-menu--mobile-modal c-datetime-picker">
|
||||
<div class="c-datetime-picker__close-button">
|
||||
<button class="c-click-icon icon-x-in-circle" @click="toggle"></button>
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
/>
|
||||
<date-picker
|
||||
v-if="isUTCBased"
|
||||
class="c-ctrl-wrapper--menus-left"
|
||||
class="c-ctrl-wrapper--menus-right"
|
||||
:default-date-time="formattedBounds.start"
|
||||
:formatter="timeFormatter"
|
||||
@date-selected="startDateSelected"
|
||||
@@ -87,7 +87,7 @@
|
||||
></button>
|
||||
<button
|
||||
class="c-button icon-x"
|
||||
aria-label="Discard time bounds"
|
||||
aria-label="Discard changes and close time popup"
|
||||
@click.prevent="hide"
|
||||
></button>
|
||||
</div>
|
||||
|
||||
@@ -132,7 +132,7 @@
|
||||
></button>
|
||||
<button
|
||||
class="c-button icon-x"
|
||||
aria-label="Discard time offsets"
|
||||
aria-label="Discard changes and close time popup"
|
||||
@click.prevent="hide"
|
||||
></button>
|
||||
</div>
|
||||
|
||||
@@ -454,6 +454,12 @@
|
||||
color: $colorTimeRealtimeFg;
|
||||
}
|
||||
}
|
||||
.c-ctrl-wrapper--menus-up{ // A bit hacky, but we are rewriting the CSS class here for ITC such that the calendar opens at the bottom to avoid cutoff
|
||||
.c-menu {
|
||||
top: auto;
|
||||
bottom: revert !important;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,42 +35,39 @@
|
||||
:item-properties="itemProperties"
|
||||
:execution-state="persistedActivityStates[item.id]"
|
||||
@click.stop="setSelectionForActivity(item, $event.currentTarget)"
|
||||
>
|
||||
</expanded-view-item>
|
||||
/>
|
||||
</template>
|
||||
<div v-else class="c-table c-table--sortable c-list-view c-list-view--sticky-header sticky">
|
||||
<table class="c-table__body js-table__body">
|
||||
<thead class="c-table__header">
|
||||
<tr>
|
||||
<list-header
|
||||
v-for="headerItem in headerItems"
|
||||
:key="headerItem.property"
|
||||
:direction="
|
||||
defaultSort.property === headerItem.property
|
||||
? defaultSort.defaultDirection
|
||||
: headerItem.defaultDirection
|
||||
"
|
||||
:is-sortable="headerItem.isSortable"
|
||||
:aria-label="headerItem.name"
|
||||
:title="headerItem.name"
|
||||
:property="headerItem.property"
|
||||
:current-sort="defaultSort.property"
|
||||
@sort="sort"
|
||||
<template v-else>
|
||||
<div class="c-table c-table--sortable c-list-view c-list-view--sticky-header sticky">
|
||||
<table class="c-table__body js-table__body">
|
||||
<thead class="c-table__header">
|
||||
<tr>
|
||||
<list-header
|
||||
v-for="headerItem in headerItems"
|
||||
:key="headerItem.property"
|
||||
:direction="getSortDirection(headerItem)"
|
||||
:is-sortable="headerItem.isSortable"
|
||||
:aria-label="headerItem.name"
|
||||
:title="headerItem.name"
|
||||
:property="headerItem.property"
|
||||
:current-sort="defaultSort.property"
|
||||
@sort="sort"
|
||||
/>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<list-item
|
||||
v-for="item in sortedItems"
|
||||
:key="item.key"
|
||||
:class="{ '--is-in-progress': persistedActivityStates[item.id] === 'in-progress' }"
|
||||
:item="item"
|
||||
:item-properties="itemProperties"
|
||||
@click.stop="setSelectionForActivity(item, $event.currentTarget)"
|
||||
/>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<list-item
|
||||
v-for="item in sortedItems"
|
||||
:key="item.key"
|
||||
:class="{ '--is-in-progress': persistedActivityStates[item.id] === 'in-progress' }"
|
||||
:item="item"
|
||||
:item-properties="itemProperties"
|
||||
@click.stop="setSelectionForActivity(item, $event.currentTarget)"
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -526,20 +523,16 @@ export default {
|
||||
return activities.map(this.styleActivity);
|
||||
},
|
||||
setSort() {
|
||||
const sortOrder = SORT_ORDER_OPTIONS[this.domainObject.configuration.sortOrderIndex];
|
||||
const property = sortOrder.property;
|
||||
const direction = sortOrder.direction.toLowerCase() === 'asc';
|
||||
const { property, direction } =
|
||||
SORT_ORDER_OPTIONS[this.domainObject.configuration.sortOrderIndex];
|
||||
this.defaultSort = {
|
||||
property,
|
||||
defaultDirection: direction
|
||||
defaultDirection: direction.toLowerCase() === 'asc'
|
||||
};
|
||||
},
|
||||
sortItems(activities) {
|
||||
let sortedItems = _.sortBy(activities, this.defaultSort.property);
|
||||
if (!this.defaultSort.defaultDirection) {
|
||||
sortedItems = sortedItems.reverse();
|
||||
}
|
||||
return sortedItems;
|
||||
const sortedItems = _.sortBy(activities, this.defaultSort.property);
|
||||
return this.defaultSort.defaultDirection ? sortedItems : sortedItems.reverse();
|
||||
},
|
||||
setStatus(status) {
|
||||
this.status = status;
|
||||
@@ -548,10 +541,7 @@ export default {
|
||||
this.isEditing = isEditing;
|
||||
this.setViewFromConfig(this.domainObject.configuration);
|
||||
},
|
||||
sort(data) {
|
||||
const property = data.property;
|
||||
const direction = data.direction;
|
||||
|
||||
sort({ property, direction }) {
|
||||
if (this.defaultSort.property === property) {
|
||||
this.defaultSort.defaultDirection = !this.defaultSort.defaultDirection;
|
||||
} else {
|
||||
@@ -565,10 +555,10 @@ export default {
|
||||
this.openmct.selection.select(
|
||||
[
|
||||
{
|
||||
element: element,
|
||||
element,
|
||||
context: {
|
||||
type: 'activity',
|
||||
activity: activity
|
||||
activity
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -581,6 +571,11 @@ export default {
|
||||
],
|
||||
multiSelect
|
||||
);
|
||||
},
|
||||
getSortDirection(headerItem) {
|
||||
return this.defaultSort.property === headerItem.property
|
||||
? this.defaultSort.defaultDirection
|
||||
: headerItem.defaultDirection;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -23,27 +23,46 @@
|
||||
import EventEmitter from 'EventEmitter';
|
||||
import _ from 'lodash';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Selectable
|
||||
* @property {HTMLElement} element The HTML element that is selectable
|
||||
* @property {Object} context The context of the selectable, which may include a DomainObject
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('../../openmct').OpenMCT} OpenMCT
|
||||
*/
|
||||
|
||||
/**
|
||||
* Manages selection state for Open MCT
|
||||
* @private
|
||||
*/
|
||||
export default class Selection extends EventEmitter {
|
||||
/**
|
||||
* @param {OpenMCT} openmct The Open MCT instance
|
||||
*/
|
||||
constructor(openmct) {
|
||||
super();
|
||||
|
||||
/** @type {OpenMCT} */
|
||||
this.openmct = openmct;
|
||||
/** @type {Selectable[]} */
|
||||
this.selected = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the selected object.
|
||||
* @returns {Selectable[]} The currently selected objects
|
||||
* @public
|
||||
*/
|
||||
get() {
|
||||
return this.selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the selectable object and emits the 'change' event.
|
||||
*
|
||||
* @param {Selectable|Selectable[]} selectable An object or array of objects with element and context properties
|
||||
* @param {object} selectable an object with element and context properties
|
||||
* @param {Boolean} isMultiSelectEvent flag indication shift key is pressed or not
|
||||
* @private
|
||||
|
||||
@@ -499,7 +499,9 @@ select {
|
||||
color: $colorSelectFg;
|
||||
box-shadow: $shdwSelect;
|
||||
background-repeat: no-repeat, no-repeat;
|
||||
background-position: right 0.4em top 80%, 0 0;
|
||||
background-position:
|
||||
right 0.4em top 80%,
|
||||
0 0;
|
||||
border: none;
|
||||
border-radius: $controlCr;
|
||||
padding: 2px 20px 2px $interiorMargin;
|
||||
@@ -718,15 +720,15 @@ select {
|
||||
.c-super-menu__item-description {
|
||||
flex: 1 1 70%;
|
||||
|
||||
[class*="__icon"] {
|
||||
[class*='__icon'] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
[class*="__name"] {
|
||||
[class*='__name'] {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
[class*="__item-description"] {
|
||||
[class*='__item-description'] {
|
||||
min-width: 200px;
|
||||
}
|
||||
}
|
||||
@@ -1133,7 +1135,7 @@ input[type='range'] {
|
||||
// Hidden by default; requires a hover 1 - 3 levels above to display
|
||||
@include transition(opacity, $transOutTime);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -141,6 +141,7 @@ mct-plot {
|
||||
.plot-wrapper-axis-and-display-area {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
//min-height: $plotMinH;
|
||||
}
|
||||
|
||||
|
||||
@@ -711,6 +711,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
&[class*='--menus-down'] {
|
||||
.c-menu {
|
||||
top: auto;
|
||||
bottom: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&[class*='--menus-right'] {
|
||||
.c-menu {
|
||||
left: 0;
|
||||
right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&[class*='--menus-left'],
|
||||
&[class*='menus-to-left'] {
|
||||
.c-menu {
|
||||
|
||||
@@ -190,7 +190,7 @@ export default {
|
||||
this.soViewResizeObserver.observe(this.$refs.soView);
|
||||
}
|
||||
|
||||
const viewKey = this.getViewKey();
|
||||
const viewKey = this.$refs.objectView?.viewKey;
|
||||
this.supportsIndependentTime = this.domainObject && SupportedViewTypes.includes(viewKey);
|
||||
},
|
||||
beforeUnmount() {
|
||||
@@ -257,9 +257,6 @@ export default {
|
||||
|
||||
this.widthClass = wClass.trimStart();
|
||||
},
|
||||
getViewKey() {
|
||||
return this.$refs.objectView?.viewKey;
|
||||
},
|
||||
async showToolTip() {
|
||||
const { BELOW } = this.openmct.tooltips.TOOLTIP_LOCATIONS;
|
||||
this.buildToolTip(await this.getObjectPath(), BELOW, 'objectName');
|
||||
|
||||
@@ -77,12 +77,13 @@ export default {
|
||||
},
|
||||
setup() {
|
||||
const axisHolder = ref(null);
|
||||
const { size, startObserving } = useResizeObserver();
|
||||
const { size: containerSize, startObserving } = useResizeObserver();
|
||||
onMounted(() => {
|
||||
startObserving(axisHolder.value);
|
||||
});
|
||||
return {
|
||||
containerSize: size
|
||||
axisHolder,
|
||||
containerSize
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
@@ -95,8 +96,11 @@ export default {
|
||||
contentHeight() {
|
||||
this.updateNowMarker();
|
||||
},
|
||||
containerSize() {
|
||||
this.resize();
|
||||
containerSize: {
|
||||
handler() {
|
||||
this.resize();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -104,7 +108,7 @@ export default {
|
||||
this.useSVG = true;
|
||||
}
|
||||
|
||||
this.container = select(this.$refs.axisHolder);
|
||||
this.container = select(this.axisHolder);
|
||||
this.svgElement = this.container.append('svg:svg');
|
||||
// draw x axis with labels. CSS is used to position them.
|
||||
this.axisElement = this.svgElement
|
||||
@@ -122,7 +126,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
resize() {
|
||||
if (this.$refs.axisHolder.clientWidth !== this.width) {
|
||||
if (this.axisHolder.clientWidth !== this.width) {
|
||||
this.setDimensions();
|
||||
this.drawAxis(this.bounds, this.timeSystem);
|
||||
this.updateNowMarker();
|
||||
@@ -139,11 +143,10 @@ export default {
|
||||
}
|
||||
},
|
||||
setDimensions() {
|
||||
const axisHolder = this.$refs.axisHolder;
|
||||
this.width = axisHolder.clientWidth;
|
||||
this.width = this.axisHolder.clientWidth;
|
||||
this.offsetWidth = this.width - this.offset;
|
||||
|
||||
this.height = Math.round(axisHolder.getBoundingClientRect().height);
|
||||
this.height = Math.round(this.axisHolder.getBoundingClientRect().height);
|
||||
|
||||
if (this.useSVG) {
|
||||
this.svgElement.attr('width', this.width);
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
.c-inspector {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
gap: $interiorMarginSm;
|
||||
flex-direction: column;
|
||||
|
||||
> * {
|
||||
// This is on purpose: want extra margin on top object-name element
|
||||
margin-top: $interiorMargin;
|
||||
}
|
||||
overflow: hidden;
|
||||
|
||||
&__selected,
|
||||
&__multiple-selected {
|
||||
@@ -79,6 +76,7 @@
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&__elements {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
@click="goToParent"
|
||||
></button>
|
||||
<div class="l-browse-bar__object-name--w c-object-label" :class="[statusClass]">
|
||||
<div class="c-object-label__type-icon" :class="type.cssClass">
|
||||
<div class="c-object-label__type-icon" :class="cssClass">
|
||||
<span class="is-status__indicator" :title="`This item is ${status}`"></span>
|
||||
</div>
|
||||
<span
|
||||
@@ -43,7 +43,7 @@
|
||||
@mouseover.ctrl="showToolTip"
|
||||
@mouseleave="hideToolTip"
|
||||
>
|
||||
{{ domainObject.name }}
|
||||
{{ domainObjectName }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -150,8 +150,6 @@ import tooltipHelpers from '../../api/tooltips/tooltipMixins.js';
|
||||
import { SupportedViewTypes } from '../../utils/constants.js';
|
||||
import ViewSwitcher from './ViewSwitcher.vue';
|
||||
|
||||
const PLACEHOLDER_OBJECT = {};
|
||||
|
||||
export default {
|
||||
components: {
|
||||
IndependentTimeConductor,
|
||||
@@ -168,12 +166,12 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
data() {
|
||||
return {
|
||||
notebookTypes: [],
|
||||
showViewMenu: false,
|
||||
showSaveMenu: false,
|
||||
domainObject: PLACEHOLDER_OBJECT,
|
||||
domainObject: undefined,
|
||||
viewKey: undefined,
|
||||
isEditing: this.openmct.editor.isEditing(),
|
||||
notebookEnabled: this.openmct.types.get('notebook'),
|
||||
@@ -185,11 +183,22 @@ export default {
|
||||
statusClass() {
|
||||
return this.status ? `is-status--${this.status}` : '';
|
||||
},
|
||||
supportsIndependentTime() {
|
||||
return (
|
||||
this.domainObject?.identifier &&
|
||||
!this.openmct.objects.isMissing(this.domainObject) &&
|
||||
SupportedViewTypes.includes(this.viewKey)
|
||||
);
|
||||
},
|
||||
currentView() {
|
||||
return this.views.filter((v) => v.key === this.viewKey)[0] || {};
|
||||
},
|
||||
views() {
|
||||
if (this.domainObject && this.openmct.router.started !== true) {
|
||||
if (this.domainObject && this.openmct.router.started === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!this.domainObject) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -203,25 +212,29 @@ export default {
|
||||
});
|
||||
},
|
||||
hasParent() {
|
||||
return toRaw(this.domainObject) !== PLACEHOLDER_OBJECT && this.parentUrl !== '/browse';
|
||||
return toRaw(this.domainObject) && this.parentUrl !== '/browse';
|
||||
},
|
||||
parentUrl() {
|
||||
const objectKeyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
const objectKeyString = this.openmct.objects.makeKeyString(this.domainObject?.identifier);
|
||||
const hash = this.openmct.router.getCurrentLocation().path;
|
||||
|
||||
return hash.slice(0, hash.lastIndexOf('/' + objectKeyString));
|
||||
},
|
||||
type() {
|
||||
const objectType = this.openmct.types.get(this.domainObject.type);
|
||||
if (!objectType) {
|
||||
return {};
|
||||
cssClass() {
|
||||
if (!this.domainObject) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return objectType.definition;
|
||||
const objectType = this.openmct.types.get(this.domainObject.type);
|
||||
if (!objectType) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return objectType?.definition?.cssClass ?? '';
|
||||
},
|
||||
isPersistable() {
|
||||
let persistable =
|
||||
this.domainObject.identifier &&
|
||||
const persistable =
|
||||
this.domainObject?.identifier &&
|
||||
this.openmct.objects.isPersistable(this.domainObject.identifier);
|
||||
|
||||
return persistable;
|
||||
@@ -246,10 +259,8 @@ export default {
|
||||
return 'Unlocked for editing - click to lock.';
|
||||
}
|
||||
},
|
||||
supportsIndependentTime() {
|
||||
const viewKey = this.getViewKey();
|
||||
|
||||
return this.domainObject && SupportedViewTypes.includes(viewKey);
|
||||
domainObjectName() {
|
||||
return this.domainObject?.name ?? '';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -273,7 +284,7 @@ export default {
|
||||
this.updateActionItems(this.actionCollection.getActionsObject());
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
mounted() {
|
||||
document.addEventListener('click', this.closeViewAndSaveMenu);
|
||||
this.promptUserbeforeNavigatingAway = this.promptUserbeforeNavigatingAway.bind(this);
|
||||
window.addEventListener('beforeunload', this.promptUserbeforeNavigatingAway);
|
||||
@@ -282,7 +293,7 @@ export default {
|
||||
this.isEditing = isEditing;
|
||||
});
|
||||
},
|
||||
beforeUnmount: function () {
|
||||
beforeUnmount() {
|
||||
if (this.mutationObserver) {
|
||||
this.mutationObserver();
|
||||
}
|
||||
@@ -323,9 +334,6 @@ export default {
|
||||
edit() {
|
||||
this.openmct.editor.edit();
|
||||
},
|
||||
getViewKey() {
|
||||
return this.viewKey;
|
||||
},
|
||||
promptUserandCancelEditing() {
|
||||
let dialog = this.openmct.overlays.dialog({
|
||||
iconClass: 'alert',
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
&__tree {
|
||||
flex: 1 1 auto;
|
||||
height: 0; // Chrome 73 overflow bug fix
|
||||
padding-right: $interiorMarginSm;
|
||||
}
|
||||
|
||||
.c-tree {
|
||||
@@ -97,7 +96,6 @@
|
||||
|
||||
@include hover {
|
||||
background: $colorItemTreeHoverBg;
|
||||
//filter: $filterHov; // FILTER REMOVAL, CONVERT TO THEME CONSTANT
|
||||
}
|
||||
|
||||
&.is-navigated-object,
|
||||
@@ -142,8 +140,6 @@
|
||||
}
|
||||
|
||||
.c-tree {
|
||||
padding-right: $interiorMarginSm;
|
||||
|
||||
.c-tree {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
@@ -18,9 +18,6 @@
|
||||
|
||||
&--vertical {
|
||||
height: 100%;
|
||||
> .l-pane .l-pane__contents {
|
||||
padding-right: $interiorMarginSm; // Fend off scrollbar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +90,8 @@
|
||||
&__contents {
|
||||
flex: 1 1 100%;
|
||||
opacity: 1;
|
||||
overflow: hidden;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
pointer-events: inherit;
|
||||
transition: opacity 250ms ease 250ms;
|
||||
|
||||
@@ -194,7 +192,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
&[class*='--collapsed'] { // For Recent Objects Button
|
||||
&[class*='--collapsed'] { // For Recent Objects Button
|
||||
&.collapse-horizontal {
|
||||
[class*='expand-button'] {
|
||||
display: block;
|
||||
@@ -325,7 +323,7 @@
|
||||
}
|
||||
|
||||
/************************** Vertical Splitter Before */
|
||||
// Pane collapses downward. Used by Recent Objects in Tree
|
||||
// Pane collapses downward. Used by Recent Objects in Tree
|
||||
&[class*='-before'] {
|
||||
$m: $interiorMarginLg;
|
||||
margin-top: $m;
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
}
|
||||
body.mobile & {
|
||||
// Add a margin to results so we have room for the close button
|
||||
width: 93%;
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,6 @@
|
||||
|
||||
&__results-section-title {
|
||||
@include propertiesHeader();
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
&__result-pane-msg {
|
||||
|
||||
Reference in New Issue
Block a user