Compare commits
12 Commits
watch-plot
...
hex-values
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6633c0557a | ||
|
|
e449fd0eda | ||
|
|
7d25c967a5 | ||
|
|
dc9bd8bcb1 | ||
|
|
29d83e9c6d | ||
|
|
6393a77c19 | ||
|
|
6bbabf9c45 | ||
|
|
f18d1d2a51 | ||
|
|
5894c66df1 | ||
|
|
6ff8c42041 | ||
|
|
9870a6bc9c | ||
|
|
317ea8c275 |
@@ -160,6 +160,31 @@ jobs:
|
||||
equal: [42, 42] # Always generate version artifacts 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
|
||||
e2e-mobile:
|
||||
executor: pw-focal-development
|
||||
steps:
|
||||
- build_and_install:
|
||||
node-version: lts/hydrogen
|
||||
- run: npm run test:e2e:mobile
|
||||
- when:
|
||||
condition:
|
||||
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_e2e_code_cov_report:
|
||||
suite: full
|
||||
- store_test_results:
|
||||
path: test-results/results.xml
|
||||
- store_artifacts:
|
||||
path: test-results
|
||||
- store_artifacts:
|
||||
path: coverage
|
||||
- store_artifacts:
|
||||
path: html-test-results
|
||||
- when:
|
||||
condition:
|
||||
equal: [42, 42] # Always generate version artifacts 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
|
||||
e2e-couchdb:
|
||||
executor: ubuntu
|
||||
steps:
|
||||
@@ -260,8 +285,9 @@ workflows:
|
||||
- e2e-test:
|
||||
name: e2e-stable
|
||||
suite: stable
|
||||
- e2e-mobile
|
||||
- visual-a11y-tests:
|
||||
name: visual-test-ci
|
||||
name: visual-a11y-test-ci
|
||||
suite: ci
|
||||
|
||||
the-nightly: #These jobs do not run on PRs, but against master at night
|
||||
@@ -277,10 +303,11 @@ workflows:
|
||||
- e2e-test:
|
||||
name: e2e-full-nightly
|
||||
suite: full
|
||||
- mem-test
|
||||
- e2e-mobile
|
||||
- perf-test
|
||||
- mem-test
|
||||
- visual-a11y-tests:
|
||||
name: visual-test-nightly
|
||||
name: visual-a11y-test-nightly
|
||||
suite: full
|
||||
- e2e-couchdb
|
||||
triggers:
|
||||
|
||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -8,7 +8,7 @@ Closes <!--- Insert Issue Number(s) this PR addresses. Start by typing # will op
|
||||
|
||||
* [ ] Have you followed the guidelines in our [Contributing document](https://github.com/nasa/openmct/blob/master/CONTRIBUTING.md)?
|
||||
* [ ] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/nasa/openmct/pulls) for the same update/change?
|
||||
* [ ] Is this change backwards compatible? For example, developers won't need to change how they are calling the API or how they've extended core plugins such as Tables or Plots.
|
||||
* [ ] Is this a notable change that will require a special callout in the release notes [Notable Change](../docs/src/process/release.md) ? For example, will this break compatibility with existing APIs or projects which source these plugins?
|
||||
|
||||
### Author Checklist
|
||||
|
||||
|
||||
5
.github/release.yml
vendored
5
.github/release.yml
vendored
@@ -1,5 +1,8 @@
|
||||
changelog:
|
||||
categories:
|
||||
- title: 💥 Notable Changes
|
||||
labels:
|
||||
- notable_change
|
||||
- title: 🏕 Features
|
||||
labels:
|
||||
- type:feature
|
||||
@@ -20,4 +23,4 @@ changelog:
|
||||
- dependencies
|
||||
- title: 🐛 Bug Fixes
|
||||
labels:
|
||||
- '*'
|
||||
- "*"
|
||||
|
||||
@@ -15,5 +15,5 @@ export default merge(common, {
|
||||
__OPENMCT_ROOT_RELATIVE__: '""'
|
||||
})
|
||||
],
|
||||
devtool: 'source-map'
|
||||
devtool: 'eval-source-map'
|
||||
});
|
||||
|
||||
30
docs/src/process/release.md
Normal file
30
docs/src/process/release.md
Normal file
@@ -0,0 +1,30 @@
|
||||
|
||||
# Release of NASA Open MCT NPM Package
|
||||
|
||||
This document outlines the process and key considerations for releasing a new version of the NASA Open MCT project as an NPM (Node Package Manager) package.
|
||||
|
||||
## 1. Pre-requisites
|
||||
|
||||
Before releasing a new version of the NASA Open MCT NPM package, ensure all dependencies are updated, and comprehensive tests are performed. This ensures compatibility and performance of the Open MCT within the Node.js ecosystem.
|
||||
|
||||
## 2. Versioning
|
||||
|
||||
Versioning is a critical step for package release. The Open MCT team follows [Semantic Versioning (SemVer)](https://semver.org) that consists of three major components: MAJOR.MINOR.PATCH. These ensure a structured process for updating, bug fixes, backward compatibility, and software progress.
|
||||
|
||||
## 3. Changelog Maintenance
|
||||
|
||||
A comprehensive changelog file, `CHANGELOG.md`, documents any changes, adding a high level of transparencies for anyone desiring to look into the status of new and past progress. It includes the summation of any major new enhancements, changes, bug fixes, and the credits to the users responsible for each unique progress.
|
||||
|
||||
## 4. Notable Changes Labels on GitHub PRs
|
||||
|
||||
For the Open MCT package, we leverage GitHub's Pull Request (PR) mechanisms extensively, with three important PR labels dedicated to signifying 'notable_changes':
|
||||
|
||||
- **Breaking Change** Highlights the integration of changes that are suspected to break, or without a doubt will break, backward compatibility. These should signal to users the upgrade might be seamless only if dependency and integration factors are properly managed, if not, one should expect to manage atypical technical snags.
|
||||
- **API change** Signifies when a contribution makes any complete or under layer changes to the communication or its supporting access processes. This label flags required see-through insight on how the web-based control panel sees and manipulates any value and or network logs.
|
||||
- **Default Behavior Change:** In the incident an update either adjusts a form to or integrates a not previously kept setting or plugin. i.e. autoscale is enabled by default when working with plots.
|
||||
|
||||
## 6. Community & Contributions
|
||||
|
||||
A flat community and the rounded center are kept in continuous celebration, with the given station open for two open-specifying dialogues, research, and all-for development probing. State the ownership for a handed looped, a welcome for even structure-core and architectural draft and impend.
|
||||
|
||||
Thank you for your collaboration and commitment to moving the project onto a text big club.
|
||||
@@ -229,7 +229,7 @@ Current list of test tags:
|
||||
|
||||
|Test Tag|Description|
|
||||
|:-:|-|
|
||||
|`@ipad` | Test case or test suite is compatible with Playwright's iPad support and Open MCT's read-only mobile view (i.e. no create button).|
|
||||
|`@mobile` | Test case or test suite is compatible with Playwright's iPad support and Open MCT's read-only mobile view (i.e. no create button).|
|
||||
|`@a11y` | Test case or test suite to execute playwright-axe accessibility checks and generate a11y reports.|
|
||||
|`@gds` | Denotes a GDS Test Case used in the VIPER Mission.|
|
||||
|`@addInit` | Initializes the browser with an injected and artificial state. Useful for loading non-default plugins. Likely will not work outside of `npm start`.|
|
||||
@@ -329,9 +329,15 @@ In terms of operating system testing, we're only limited by what the CI provider
|
||||
|
||||
#### **Mobile**
|
||||
|
||||
We have the Mission-need to support iPad. To run our iPad suite, please see our `playwright-*.config.js` with the 'iPad' project.
|
||||
We have a Mission-need to support iPad and mobile devices. To run our test suites with mobile devices, please see our `playwright-mobile.config.js` projects.
|
||||
|
||||
In general, our test suite is not designed to run against mobile devices as the mobile experience is a focused version of the application. Core functionality is missing (chiefly the 'Create' button) and so this will likely turn into a separate suite.
|
||||
In general, our test suite is not designed to run against mobile devices as the mobile experience is a focused version of the application. Core functionality is missing (chiefly the 'Create' button). To bypass the object creation, we leverage the `storageState` properties for starting the mobile tests with localstorage.
|
||||
|
||||
For now, the mobile tests will exist in the /tests/mobile/ suites and be executed with the
|
||||
```sh
|
||||
npm run test:e2e:mobile
|
||||
```
|
||||
command.
|
||||
|
||||
#### **Skipping or executing tests based on browser, os, and/os browser version:**
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ const NUM_WORKERS = 2;
|
||||
const config = {
|
||||
retries: 2, //Retries 2 times for a total of 3 runs. When running sharded and with max-failures=5, this should ensure that flake is managed without failing the full suite
|
||||
testDir: 'tests',
|
||||
grepInvert: /@mobile/, //Ignore mobile tests
|
||||
testIgnore: '**/*.perf.spec.js', //Ignore performance tests and define in playwright-perfromance.config.js
|
||||
timeout: 60 * 1000,
|
||||
webServer: {
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
// playwright.config.js
|
||||
// @ts-check
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { devices } from '@playwright/test';
|
||||
|
||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||
const config = {
|
||||
retries: 0,
|
||||
testDir: 'tests',
|
||||
testMatch: '**/*.e2e.spec.js', // only run e2e tests
|
||||
testIgnore: '**/*.perf.spec.js',
|
||||
timeout: 30 * 1000,
|
||||
webServer: {
|
||||
@@ -35,7 +33,6 @@ const config = {
|
||||
},
|
||||
{
|
||||
name: 'MMOC',
|
||||
testMatch: '**/*.e2e.spec.js', // only run e2e tests
|
||||
grepInvert: /@snapshot/,
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
@@ -47,8 +44,6 @@ const config = {
|
||||
},
|
||||
{
|
||||
name: 'safari',
|
||||
testMatch: '**/*.e2e.spec.js', // only run e2e tests
|
||||
grep: /@ipad/, // only run ipad tests due to this bug https://github.com/microsoft/playwright/issues/8340
|
||||
grepInvert: /@snapshot/,
|
||||
use: {
|
||||
browserName: 'webkit'
|
||||
@@ -56,7 +51,6 @@ const config = {
|
||||
},
|
||||
{
|
||||
name: 'firefox',
|
||||
testMatch: '**/*.e2e.spec.js', // only run e2e tests
|
||||
grepInvert: /@snapshot/,
|
||||
use: {
|
||||
browserName: 'firefox'
|
||||
@@ -64,7 +58,6 @@ const config = {
|
||||
},
|
||||
{
|
||||
name: 'canary',
|
||||
testMatch: '**/*.e2e.spec.js', // only run e2e tests
|
||||
grepInvert: /@snapshot/,
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
@@ -73,22 +66,11 @@ const config = {
|
||||
},
|
||||
{
|
||||
name: 'chrome-beta',
|
||||
testMatch: '**/*.e2e.spec.js', // only run e2e tests
|
||||
grepInvert: /@snapshot/,
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome-beta'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'ipad',
|
||||
testMatch: '**/*.e2e.spec.js', // only run e2e tests
|
||||
grep: /@ipad/,
|
||||
grepInvert: /@snapshot/,
|
||||
use: {
|
||||
browserName: 'webkit',
|
||||
...devices['iPad (gen 7) landscape'] // Complete List https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json
|
||||
}
|
||||
}
|
||||
],
|
||||
reporter: [
|
||||
|
||||
69
e2e/playwright-mobile.config.js
Normal file
69
e2e/playwright-mobile.config.js
Normal file
@@ -0,0 +1,69 @@
|
||||
// playwright.config.js
|
||||
// @ts-check
|
||||
|
||||
import { devices } from '@playwright/test';
|
||||
const MAX_FAILURES = 5;
|
||||
const NUM_WORKERS = 2;
|
||||
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||
const config = {
|
||||
retries: 1, //Retries 2 times for a total of 3 runs. When running sharded and with max-failures=5, this should ensure that flake is managed without failing the full suite
|
||||
testDir: 'tests',
|
||||
testIgnore: '**/*.perf.spec.js', //Ignore performance tests and define in playwright-perfromance.config.js
|
||||
timeout: 30 * 1000,
|
||||
webServer: {
|
||||
command: 'npm run start:coverage',
|
||||
url: 'http://localhost:8080/#',
|
||||
timeout: 200 * 1000,
|
||||
reuseExistingServer: true //This was originally disabled to prevent differences in local debugging vs. CI. However, it significantly speeds up local debugging.
|
||||
},
|
||||
maxFailures: MAX_FAILURES, //Limits failures to 5 to reduce CI Waste
|
||||
workers: NUM_WORKERS, //Limit to 2 for CircleCI Agent
|
||||
use: {
|
||||
baseURL: 'http://localhost:8080/',
|
||||
headless: true,
|
||||
ignoreHTTPSErrors: true,
|
||||
screenshot: 'only-on-failure',
|
||||
trace: 'on-first-retry',
|
||||
video: 'off'
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'ipad',
|
||||
grep: /@mobile/,
|
||||
use: {
|
||||
storageState: fileURLToPath(
|
||||
new URL('./test-data/display_layout_with_child_layouts.json', import.meta.url)
|
||||
),
|
||||
browserName: 'webkit',
|
||||
...devices['iPad (gen 7) landscape'] // Complete List https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'iphone',
|
||||
grep: /@mobile/,
|
||||
use: {
|
||||
storageState: fileURLToPath(
|
||||
new URL('./test-data/display_layout_with_child_layouts.json', import.meta.url)
|
||||
),
|
||||
browserName: 'webkit',
|
||||
...devices['iPhone 14 Pro'] // Complete List https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json
|
||||
}
|
||||
}
|
||||
],
|
||||
reporter: [
|
||||
['list'],
|
||||
[
|
||||
'html',
|
||||
{
|
||||
open: 'never',
|
||||
outputFolder: '../html-test-results' //Must be in different location due to https://github.com/microsoft/playwright/issues/12840
|
||||
}
|
||||
],
|
||||
['junit', { outputFile: '../test-results/results.xml' }]
|
||||
]
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -1,6 +1,9 @@
|
||||
// playwright.config.js
|
||||
// @ts-check
|
||||
|
||||
import { devices } from '@playwright/test';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||
const config = {
|
||||
retries: 0, //Retries are not needed with watch mode
|
||||
@@ -28,6 +31,28 @@ const config = {
|
||||
use: {
|
||||
browserName: 'chromium'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'ipad',
|
||||
grep: /@mobile/,
|
||||
use: {
|
||||
storageState: fileURLToPath(
|
||||
new URL('./test-data/display_layout_with_child_layouts.json', import.meta.url)
|
||||
),
|
||||
browserName: 'webkit',
|
||||
...devices['iPad (gen 7) landscape'] // Complete List https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'iphone',
|
||||
grep: /@mobile/,
|
||||
use: {
|
||||
storageState: fileURLToPath(
|
||||
new URL('./test-data/display_layout_with_child_layouts.json', import.meta.url)
|
||||
),
|
||||
browserName: 'webkit',
|
||||
...devices['iPhone 14 Pro'] // Complete List https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json
|
||||
}
|
||||
}
|
||||
],
|
||||
reporter: [
|
||||
|
||||
@@ -298,7 +298,7 @@ test.describe('Basic Condition Set Use', () => {
|
||||
}) => {
|
||||
const exampleTelemetry = await createExampleTelemetryObject(page);
|
||||
|
||||
await page.getByTitle('Show selected item in tree').click();
|
||||
await page.getByLabel('Show selected item in tree').click();
|
||||
await page.goto(conditionSet.url);
|
||||
// Change the object to edit mode
|
||||
await page.getByLabel('Edit Object').click();
|
||||
@@ -378,4 +378,83 @@ test.describe('Basic Condition Set Use', () => {
|
||||
await page.goto(conditionSet.url);
|
||||
await expect(outputValue).toHaveText('---');
|
||||
});
|
||||
|
||||
test('ConditionSet has correct outputs when test data is enabled', async ({ page }) => {
|
||||
const exampleTelemetry = await createExampleTelemetryObject(page);
|
||||
|
||||
await page.getByLabel('Show selected item in tree').click();
|
||||
await page.goto(conditionSet.url);
|
||||
// Change the object to edit mode
|
||||
await page.getByLabel('Edit Object').click();
|
||||
|
||||
// Create two conditions
|
||||
await page.locator('#addCondition').click();
|
||||
await page.locator('#addCondition').click();
|
||||
await page.locator('#conditionCollection').getByRole('textbox').nth(0).fill('First Condition');
|
||||
await page.locator('#conditionCollection').getByRole('textbox').nth(1).fill('Second Condition');
|
||||
|
||||
// Add Telemetry to ConditionSet
|
||||
const sineWaveGeneratorTreeItem = page
|
||||
.getByRole('tree', {
|
||||
name: 'Main Tree'
|
||||
})
|
||||
.getByRole('treeitem', {
|
||||
name: exampleTelemetry.name
|
||||
});
|
||||
const conditionCollection = page.locator('#conditionCollection');
|
||||
await sineWaveGeneratorTreeItem.dragTo(conditionCollection);
|
||||
|
||||
// Modify First Criterion
|
||||
const firstCriterionTelemetry = page.locator(
|
||||
'[aria-label="Criterion Telemetry Selection"] >> nth=0'
|
||||
);
|
||||
firstCriterionTelemetry.selectOption({ label: exampleTelemetry.name });
|
||||
const firstCriterionMetadata = page.locator(
|
||||
'[aria-label="Criterion Metadata Selection"] >> nth=0'
|
||||
);
|
||||
firstCriterionMetadata.selectOption({ label: 'Sine' });
|
||||
const firstCriterionComparison = page.locator(
|
||||
'[aria-label="Criterion Comparison Selection"] >> nth=0'
|
||||
);
|
||||
firstCriterionComparison.selectOption({ label: 'is greater than or equal to' });
|
||||
const firstCriterionInput = page.locator('[aria-label="Criterion Input"] >> nth=0');
|
||||
await firstCriterionInput.fill('0');
|
||||
|
||||
// Modify Second Criterion
|
||||
const secondCriterionTelemetry = page.locator(
|
||||
'[aria-label="Criterion Telemetry Selection"] >> nth=1'
|
||||
);
|
||||
await secondCriterionTelemetry.selectOption({ label: exampleTelemetry.name });
|
||||
|
||||
const secondCriterionMetadata = page.locator(
|
||||
'[aria-label="Criterion Metadata Selection"] >> nth=1'
|
||||
);
|
||||
await secondCriterionMetadata.selectOption({ label: 'Sine' });
|
||||
|
||||
const secondCriterionComparison = page.locator(
|
||||
'[aria-label="Criterion Comparison Selection"] >> nth=1'
|
||||
);
|
||||
await secondCriterionComparison.selectOption({ label: 'is less than' });
|
||||
|
||||
const secondCriterionInput = page.locator('[aria-label="Criterion Input"] >> nth=1');
|
||||
await secondCriterionInput.fill('0');
|
||||
|
||||
// Enable test data
|
||||
await page.getByLabel('Apply Test Data').nth(1).click();
|
||||
const testDataTelemetry = page.locator('[aria-label="Test Data Telemetry Selection"] >> nth=0');
|
||||
await testDataTelemetry.selectOption({ label: exampleTelemetry.name });
|
||||
|
||||
const testDataMetadata = page.locator('[aria-label="Test Data Metadata Selection"] >> nth=0');
|
||||
await testDataMetadata.selectOption({ label: 'Sine' });
|
||||
|
||||
const testInput = page.locator('[aria-label="Test Data Input"] >> nth=0');
|
||||
await testInput.fill('0');
|
||||
|
||||
// Validate that the condition set is evaluating and outputting
|
||||
// the correct value when the underlying telemetry subscription is active.
|
||||
let outputValue = page.locator('[aria-label="Current Output Value"]');
|
||||
await expect(outputValue).toHaveText('false');
|
||||
|
||||
await page.goto(exampleTelemetry.url);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -363,7 +363,7 @@ test.describe('Example Imagery in Display Layout', () => {
|
||||
await page.locator('li[title="View Large"]').click();
|
||||
await expect(pausePlayButton).toHaveClass(/is-paused/);
|
||||
|
||||
await page.locator('[aria-label="Close"]').click();
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
await expect.soft(pausePlayButton).not.toHaveClass(/is-paused/);
|
||||
});
|
||||
|
||||
@@ -386,7 +386,7 @@ test.describe('Example Imagery in Display Layout', () => {
|
||||
await page.locator('li[title="View Large"]').click();
|
||||
await expect(pausePlayButton).toHaveClass(/is-paused/);
|
||||
|
||||
await page.locator('[aria-label="Close"]').click();
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
await expect.soft(pausePlayButton).toHaveClass(/is-paused/);
|
||||
});
|
||||
|
||||
@@ -509,7 +509,7 @@ test.describe('Example Imagery in Flexible layout', () => {
|
||||
await page.getByRole('button', { name: 'Background Image', state: 'visible' });
|
||||
|
||||
// Close the large view
|
||||
await page.getByLabel('Close').click();
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
|
||||
@@ -308,7 +308,7 @@ test.describe('Notebook entry tests', () => {
|
||||
await page.goto(notebookObject.url);
|
||||
|
||||
// Reveal the notebook in the tree
|
||||
await page.getByTitle('Show selected item in tree').click();
|
||||
await page.getByLabel('Show selected item in tree').click();
|
||||
|
||||
await page
|
||||
.getByRole('treeitem', { name: overlayPlot.name })
|
||||
@@ -332,7 +332,7 @@ test.describe('Notebook entry tests', () => {
|
||||
await page.goto(notebookObject.url);
|
||||
|
||||
// Reveal the notebook in the tree
|
||||
await page.getByTitle('Show selected item in tree').click();
|
||||
await page.getByLabel('Show selected item in tree').click();
|
||||
|
||||
await nbUtils.enterTextEntry(page, 'Entry to drop into');
|
||||
await page
|
||||
@@ -377,7 +377,7 @@ test.describe('Notebook entry tests', () => {
|
||||
await page.goto(notebookObject.url);
|
||||
|
||||
// Reveal the notebook in the tree
|
||||
await page.getByTitle('Show selected item in tree').click();
|
||||
await page.getByLabel('Show selected item in tree').click();
|
||||
|
||||
await nbUtils.enterTextEntry(page, `This should be a link: ${TEST_LINK} is it?`);
|
||||
|
||||
@@ -404,7 +404,7 @@ test.describe('Notebook entry tests', () => {
|
||||
await page.goto(notebookObject.url);
|
||||
|
||||
// Reveal the notebook in the tree
|
||||
await page.getByTitle('Show selected item in tree').click();
|
||||
await page.getByLabel('Show selected item in tree').click();
|
||||
|
||||
await nbUtils.enterTextEntry(page, `This should NOT be a link: ${TEST_LINK} is it?`);
|
||||
|
||||
@@ -421,7 +421,7 @@ test.describe('Notebook entry tests', () => {
|
||||
await page.goto(notebookObject.url);
|
||||
|
||||
// Reveal the notebook in the tree
|
||||
await page.getByTitle('Show selected item in tree').click();
|
||||
await page.getByLabel('Show selected item in tree').click();
|
||||
|
||||
await nbUtils.enterTextEntry(page, `This should NOT be a link: ${TEST_LINK} is it?`);
|
||||
|
||||
@@ -438,7 +438,7 @@ test.describe('Notebook entry tests', () => {
|
||||
await page.goto(notebookObject.url);
|
||||
|
||||
// Reveal the notebook in the tree
|
||||
await page.getByTitle('Show selected item in tree').click();
|
||||
await page.getByLabel('Show selected item in tree').click();
|
||||
|
||||
await nbUtils.enterTextEntry(page, `This should be a link: ${INVALID_TEST_LINK} is it?`);
|
||||
|
||||
@@ -455,7 +455,7 @@ test.describe('Notebook entry tests', () => {
|
||||
await page.goto(notebookObject.url);
|
||||
|
||||
// Reveal the notebook in the tree
|
||||
await page.getByTitle('Show selected item in tree').click();
|
||||
await page.getByLabel('Show selected item in tree').click();
|
||||
|
||||
await nbUtils.enterTextEntry(page, `This should be a link: ${TEST_LINK} is it?`);
|
||||
|
||||
@@ -483,7 +483,7 @@ test.describe('Notebook entry tests', () => {
|
||||
await page.goto(notebookObject.url);
|
||||
|
||||
// Reveal the notebook in the tree
|
||||
await page.getByTitle('Show selected item in tree').click();
|
||||
await page.getByLabel('Show selected item in tree').click();
|
||||
|
||||
await nbUtils.enterTextEntry(
|
||||
page,
|
||||
|
||||
@@ -68,7 +68,7 @@ test.describe('Snapshot image tests', () => {
|
||||
// expect large image to be displayed
|
||||
await expect(page.getByRole('dialog').getByText('favicon-96x96.png')).toBeVisible();
|
||||
|
||||
await page.getByLabel('Close').click();
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
|
||||
// drop another image onto the entry
|
||||
await page.dispatchEvent('.c-snapshots', 'drop', { dataTransfer: dropTransfer });
|
||||
|
||||
@@ -33,15 +33,15 @@ import {
|
||||
import { expect, test } from '../../../../pluginFixtures.js';
|
||||
|
||||
test.describe('Overlay Plot', () => {
|
||||
let overlayPlot;
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
overlayPlot = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Overlay Plot'
|
||||
});
|
||||
});
|
||||
|
||||
test('Plot legend color is in sync with plot series color', async ({ page }) => {
|
||||
const overlayPlot = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Overlay Plot'
|
||||
});
|
||||
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
parent: overlayPlot.uuid
|
||||
@@ -68,9 +68,6 @@ test.describe('Overlay Plot', () => {
|
||||
type: 'issue',
|
||||
description: 'https://github.com/nasa/openmct/issues/7403'
|
||||
});
|
||||
const overlayPlot = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Overlay Plot'
|
||||
});
|
||||
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
@@ -130,10 +127,6 @@ test.describe('Overlay Plot', () => {
|
||||
type: 'issue',
|
||||
description: 'https://github.com/nasa/openmct/issues/6338'
|
||||
});
|
||||
// Create an Overlay Plot with a default SWG
|
||||
const overlayPlot = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Overlay Plot'
|
||||
});
|
||||
|
||||
const swgA = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
@@ -199,10 +192,6 @@ test.describe('Overlay Plot', () => {
|
||||
test('The elements pool supports dragging series into multiple y-axis buckets', async ({
|
||||
page
|
||||
}) => {
|
||||
const overlayPlot = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Overlay Plot'
|
||||
});
|
||||
|
||||
const swgA = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
parent: overlayPlot.uuid
|
||||
@@ -292,10 +281,6 @@ test.describe('Overlay Plot', () => {
|
||||
description: 'https://github.com/nasa/openmct/issues/7421'
|
||||
});
|
||||
|
||||
const overlayPlot = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Overlay Plot'
|
||||
});
|
||||
|
||||
const swgA = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
parent: overlayPlot.uuid
|
||||
@@ -309,12 +294,32 @@ test.describe('Overlay Plot', () => {
|
||||
await page.getByRole('tab', { name: 'Elements' }).click();
|
||||
|
||||
await page.locator(`#inspector-elements-tree >> text=${swgA.name}`).click();
|
||||
|
||||
const plotPixels = await getCanvasPixels(page, '.js-overlay canvas');
|
||||
const plotPixelSize = plotPixels.length;
|
||||
expect(plotPixelSize).toBeGreaterThan(0);
|
||||
}
|
||||
);
|
||||
|
||||
test('Can remove an item via the elements pool action menu', async ({ page }) => {
|
||||
const swgA = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
parent: overlayPlot.uuid
|
||||
});
|
||||
|
||||
await page.goto(overlayPlot.url);
|
||||
// Wait for plot series data to load and be drawn
|
||||
await waitForPlotsToRender(page);
|
||||
await page.getByLabel('Edit Object').click();
|
||||
|
||||
await page.getByRole('tab', { name: 'Elements' }).click();
|
||||
|
||||
const swgAElementsPoolItem = page.getByLabel(`Preview ${swgA.name}`);
|
||||
await expect(swgAElementsPoolItem).toBeVisible();
|
||||
await swgAElementsPoolItem.click({ button: 'right' });
|
||||
await page.getByRole('menuitem', { name: 'Remove' }).click();
|
||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||
await expect(swgAElementsPoolItem).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -57,7 +57,7 @@ test.describe('Plots work in Previews', () => {
|
||||
await page.getByLabel('Sine', { exact: true }).click({ button: 'right' });
|
||||
await page.getByLabel('View Historical Data').click();
|
||||
await expect(page.getByLabel('Preview Container').getByLabel('Plot Canvas')).toBeVisible();
|
||||
await page.getByLabel('Close').click();
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
await page.getByLabel('Expand Test Display Layout layout').click();
|
||||
|
||||
// change to a plot and ensure embiggen works
|
||||
@@ -73,7 +73,7 @@ test.describe('Plots work in Previews', () => {
|
||||
await expect(page.getByLabel('Preview Container')).toBeHidden();
|
||||
await page.getByLabel('Large View').click();
|
||||
await expect(page.getByLabel('Preview Container').getByLabel('Plot Canvas')).toBeVisible();
|
||||
await page.getByLabel('Close').click();
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
|
||||
// get last sinewave tree item (in the display layout)
|
||||
await page
|
||||
@@ -83,6 +83,6 @@ test.describe('Plots work in Previews', () => {
|
||||
.click({ button: 'right' });
|
||||
await page.getByLabel('View', { exact: true }).click();
|
||||
await expect(page.getByLabel('Preview Container').getByLabel('Plot Canvas')).toBeVisible();
|
||||
await page.getByLabel('Close').click();
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,7 +53,7 @@ test.describe('Preview mode', () => {
|
||||
await expect(page.getByLabel('Export Table Data')).toBeVisible();
|
||||
await expect(page.getByLabel('Export Marked Rows')).toBeVisible();
|
||||
await page.getByRole('menuitem', { name: 'Pause' }).click();
|
||||
await page.getByLabel('Close').click();
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
|
||||
await expandEntireTree(page);
|
||||
|
||||
|
||||
@@ -191,7 +191,7 @@ test.describe('Recent Objects', () => {
|
||||
|
||||
// Navigate to the clock and reveal it in the tree
|
||||
await page.goto(clock.url);
|
||||
await page.getByTitle('Show selected item in tree').click();
|
||||
await page.getByLabel('Show selected item in tree').click();
|
||||
|
||||
// Right click the clock and create an alias using the "link" context menu action
|
||||
const clockTreeItem = page
|
||||
@@ -298,7 +298,7 @@ test.describe('Recent Objects', () => {
|
||||
// Assert that the list is empty
|
||||
expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(0);
|
||||
});
|
||||
test('Ensure clear recent objects button is active or inactive', async ({ page }) => {
|
||||
test('Verify functionality of "clear" and "collapse pane" buttons', async ({ page }) => {
|
||||
// Assert that the list initially contains 3 objects (clock, folder, my items)
|
||||
expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(3);
|
||||
|
||||
@@ -331,6 +331,24 @@ test.describe('Recent Objects', () => {
|
||||
expect(await page.getByRole('button', { name: 'Clear Recently Viewed' }).isEnabled()).toBe(
|
||||
true
|
||||
);
|
||||
|
||||
// Assert initial state of pane and collapse the Recent Objects panel
|
||||
await expect(page.getByLabel('Expand Recently Viewed Pane')).toBeHidden();
|
||||
await expect(page.getByLabel('Collapse Recently Viewed Pane')).toBeVisible();
|
||||
await page.getByLabel('Collapse Recently Viewed Pane').click();
|
||||
|
||||
// Assert that the "Expand Recently Viewed Pane" button is visible
|
||||
// and that the "Collapse Recently Viewed Pane" button is hidden
|
||||
await expect(page.getByLabel('Expand Recently Viewed Pane')).toBeVisible();
|
||||
await expect(page.getByLabel('Collapse Recently Viewed Pane')).toBeHidden();
|
||||
|
||||
// Expand the Recent Objects panel by clicking on the "Expand Recently Viewed Pane" button
|
||||
await page.getByLabel('Expand Recently Viewed Pane').click();
|
||||
|
||||
// Assert that the "Expand Recently Viewed Pane" button is hidden
|
||||
// and that the "Collapse Recently Viewed Pane" button is visible
|
||||
await expect(page.getByLabel('Expand Recently Viewed Pane')).toBeHidden();
|
||||
await expect(page.getByLabel('Collapse Recently Viewed Pane')).toBeVisible();
|
||||
});
|
||||
|
||||
function assertInitialRecentObjectsListState() {
|
||||
|
||||
@@ -48,7 +48,7 @@ test('Verify that the create button appears and that the Folder Domain Object is
|
||||
await expect(page.locator(':nth-match(:text("Folder"), 2)')).toBeEnabled();
|
||||
});
|
||||
|
||||
test('Verify that My Items Tree appears @ipad', async ({ page, openmctConfig }) => {
|
||||
test('Verify that My Items Tree appears', async ({ page, openmctConfig }) => {
|
||||
const { myItemsFolderName } = openmctConfig;
|
||||
//Go to baseURL
|
||||
await page.goto('./');
|
||||
|
||||
@@ -40,7 +40,7 @@ test.describe('Main Tree', () => {
|
||||
type: 'Folder'
|
||||
});
|
||||
|
||||
await page.getByTitle('Show selected item in tree').click();
|
||||
await page.getByLabel('Show selected item in tree').click();
|
||||
|
||||
const clock = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Clock',
|
||||
|
||||
59
e2e/tests/mobile/smoke.e2e.spec.js
Normal file
59
e2e/tests/mobile/smoke.e2e.spec.js
Normal file
@@ -0,0 +1,59 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/*
|
||||
This test suite is dedicated to tests which can quickly verify that any openmct installation is
|
||||
operable and that any type of testing can proceed.
|
||||
|
||||
Ideally, smoke tests should make zero assumptions about how and where they are run. This makes them
|
||||
more resilient to change and therefor a better indicator of failure. Smoke tests will also run quickly
|
||||
as they cover a very "thin surface" of functionality.
|
||||
|
||||
When deciding between authoring new smoke tests or functional tests, ask yourself "would I feel
|
||||
comfortable running this test during a live mission?" Avoid creating or deleting Domain Objects.
|
||||
Make no assumptions about the order that elements appear in the DOM.
|
||||
*/
|
||||
|
||||
import { expect, test } from '../../pluginFixtures.js';
|
||||
|
||||
test('Verify that My Items Tree appears @mobile', async ({ page, openmctConfig }) => {
|
||||
const { myItemsFolderName } = openmctConfig;
|
||||
//Go to baseURL
|
||||
await page.goto('./');
|
||||
|
||||
//My Items to be visible
|
||||
await expect(page.getByRole('treeitem', { name: `${myItemsFolderName}` })).toBeVisible();
|
||||
});
|
||||
|
||||
test('Verify that user can search @mobile', async ({ page }) => {
|
||||
//For now, this test is going to be hardcoded against './test-data/display_layout_with_child_layouts.json'
|
||||
await page.goto('./');
|
||||
|
||||
await page.getByRole('searchbox', { name: 'Search Input' }).click();
|
||||
await page.getByRole('searchbox', { name: 'Search Input' }).fill('Parent Display Layout');
|
||||
//Search Results appear in search modal
|
||||
await expect(page.getByLabel('Object Results').getByText('Parent Display Layout')).toBeVisible();
|
||||
//Clicking on the search result takes you to the object
|
||||
await page.getByLabel('Object Results').getByText('Parent Display Layout').click();
|
||||
await page.getByTitle('Collapse Browse Pane').click();
|
||||
await expect(page.getByRole('main').getByText('Parent Display Layout')).toBeVisible();
|
||||
});
|
||||
@@ -93,4 +93,14 @@ test.describe('Visual - Display Layout', () => {
|
||||
await page.getByLabel('Parent Layout Layout', { exact: true }).click();
|
||||
await percySnapshot(page, `Parent outer layout selected (theme: '${theme}')`);
|
||||
});
|
||||
|
||||
test('Toolbar does not overflow into inspector', async ({ page, theme }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/nasa/openmct/issues/7036'
|
||||
});
|
||||
await page.getByLabel('Expand Inspect Pane').click();
|
||||
await page.getByLabel('Resize Inspect Pane').dragTo(page.getByLabel('X:'));
|
||||
await percySnapshot(page, `Toolbar does not overflow into inspector (theme: '${theme}')`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,7 +47,7 @@ if (document.currentScript) {
|
||||
* @property {*} inspectorViews
|
||||
* @property {*} propertyEditors
|
||||
* @property {*} toolbars
|
||||
* @property {*} types
|
||||
* @property {import('./src/api/types/TypeRegistry').default} types
|
||||
* @property {import('./src/api/objects/ObjectAPI').default} objects
|
||||
* @property {import('./src/api/telemetry/TelemetryAPI').default} telemetry
|
||||
* @property {import('./src/api/indicators/IndicatorAPI').default} indicators
|
||||
@@ -67,6 +67,7 @@ if (document.currentScript) {
|
||||
* @property {import('./src/api/annotation/AnnotationAPI').default} annotation
|
||||
* @property {{(plugin: OpenMCTPlugin) => void}} install
|
||||
* @property {{() => string}} getAssetPath
|
||||
* @property {{(assetPath: string) => void}} setAssetPath
|
||||
* @property {{(domElement: HTMLElement, isHeadlessMode: boolean) => void}} start
|
||||
* @property {{() => void}} startHeadless
|
||||
* @property {{() => void}} destroy
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
"tiny-emitter": "2.1.0",
|
||||
"typescript": "5.3.3",
|
||||
"uuid": "9.0.1",
|
||||
"vue": "3.3.8",
|
||||
"vue": "3.4.19",
|
||||
"vue-eslint-parser": "9.3.2",
|
||||
"vue-loader": "16.8.3",
|
||||
"webpack": "5.89.0",
|
||||
@@ -107,6 +107,7 @@
|
||||
"test:debug": "KARMA_DEBUG=true karma start karma.conf.cjs",
|
||||
"test:e2e": "npx playwright test",
|
||||
"test:e2e:a11y": "npx playwright test --config=e2e/playwright-visual-a11y.config.js --project=chrome --grep @a11y",
|
||||
"test:e2e:mobile": "npx playwright test --config=e2e/playwright-mobile.config.js",
|
||||
"test:e2e:couchdb": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @couchdb --workers=1",
|
||||
"test:e2e:stable": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep-invert \"@unstable|@couchdb|@generatedata\"",
|
||||
"test:e2e:unstable": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @unstable",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
import _ from 'lodash';
|
||||
|
||||
import objectUtils from '../objects/object-utils.js';
|
||||
import { makeKeyString, parseKeyString } from '../objects/object-utils.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../objects/ObjectAPI').DomainObject} DomainObject
|
||||
@@ -223,18 +223,18 @@ export default class CompositionProvider {
|
||||
* @param {DomainObject} oldDomainObject
|
||||
*/
|
||||
#onMutation(newDomainObject, oldDomainObject) {
|
||||
const id = objectUtils.makeKeyString(oldDomainObject.identifier);
|
||||
const id = makeKeyString(oldDomainObject.identifier);
|
||||
const listeners = this.#listeningTo[id];
|
||||
|
||||
if (!listeners) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldComposition = oldDomainObject.composition.map(objectUtils.makeKeyString);
|
||||
const newComposition = newDomainObject.composition.map(objectUtils.makeKeyString);
|
||||
const oldComposition = oldDomainObject.composition.map(makeKeyString);
|
||||
const newComposition = newDomainObject.composition.map(makeKeyString);
|
||||
|
||||
const added = _.difference(newComposition, oldComposition).map(objectUtils.parseKeyString);
|
||||
const removed = _.difference(oldComposition, newComposition).map(objectUtils.parseKeyString);
|
||||
const added = _.difference(newComposition, oldComposition).map(parseKeyString);
|
||||
const removed = _.difference(oldComposition, newComposition).map(parseKeyString);
|
||||
|
||||
function notify(value) {
|
||||
return function (listener) {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
import { toRaw } from 'vue';
|
||||
|
||||
import objectUtils from '../objects/object-utils.js';
|
||||
import { makeKeyString } from '../objects/object-utils.js';
|
||||
import CompositionProvider from './CompositionProvider.js';
|
||||
|
||||
/**
|
||||
@@ -91,7 +91,7 @@ export default class DefaultCompositionProvider extends CompositionProvider {
|
||||
this.establishTopicListener();
|
||||
|
||||
/** @type {string} */
|
||||
const keyString = objectUtils.makeKeyString(domainObject.identifier);
|
||||
const keyString = makeKeyString(domainObject.identifier);
|
||||
let objectListeners = this.listeningTo[keyString];
|
||||
|
||||
if (!objectListeners) {
|
||||
@@ -120,7 +120,7 @@ export default class DefaultCompositionProvider extends CompositionProvider {
|
||||
*/
|
||||
off(domainObject, event, callback, context) {
|
||||
/** @type {string} */
|
||||
const keyString = objectUtils.makeKeyString(domainObject.identifier);
|
||||
const keyString = makeKeyString(domainObject.identifier);
|
||||
const objectListeners = this.listeningTo[keyString];
|
||||
|
||||
const index = objectListeners[event].findIndex((l) => {
|
||||
@@ -228,7 +228,7 @@ export default class DefaultCompositionProvider extends CompositionProvider {
|
||||
this.publicAPI.objects.mutate(domainObject, 'composition', newComposition);
|
||||
|
||||
/** @type {string} */
|
||||
let id = objectUtils.makeKeyString(domainObject.identifier);
|
||||
let id = makeKeyString(domainObject.identifier);
|
||||
const listeners = this.listeningTo[id];
|
||||
|
||||
if (!listeners) {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
import EventEmitter from 'EventEmitter';
|
||||
import _ from 'lodash';
|
||||
|
||||
import utils from './object-utils.js';
|
||||
import { makeKeyString, refresh } from './object-utils.js';
|
||||
|
||||
const ANY_OBJECT_EVENT = 'mutation';
|
||||
|
||||
@@ -152,7 +152,7 @@ class MutableDomainObject {
|
||||
|
||||
mutable.$observe('$_synchronize_model', (updatedObject) => {
|
||||
let clone = JSON.parse(JSON.stringify(updatedObject));
|
||||
utils.refresh(mutable, clone);
|
||||
refresh(mutable, clone);
|
||||
});
|
||||
|
||||
return mutable;
|
||||
@@ -168,7 +168,7 @@ class MutableDomainObject {
|
||||
}
|
||||
|
||||
function qualifiedEventName(object, eventName) {
|
||||
let keystring = utils.makeKeyString(object.identifier);
|
||||
let keystring = makeKeyString(object.identifier);
|
||||
|
||||
return [keystring, eventName].join(':');
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
import EventEmitter from 'EventEmitter';
|
||||
import utils from 'objectUtils';
|
||||
import { identifierEquals, makeKeyString, parseKeyString, refresh } from 'objectUtils';
|
||||
|
||||
import ConflictError from './ConflictError.js';
|
||||
import InMemorySearchProvider from './InMemorySearchProvider.js';
|
||||
@@ -82,8 +82,19 @@ import Transaction from './Transaction.js';
|
||||
* @memberof module:openmct
|
||||
*/
|
||||
export default class ObjectAPI {
|
||||
#makeKeyString;
|
||||
#parseKeyString;
|
||||
#identifierEquals;
|
||||
#refresh;
|
||||
#openmct;
|
||||
|
||||
constructor(typeRegistry, openmct) {
|
||||
this.openmct = openmct;
|
||||
this.#makeKeyString = makeKeyString;
|
||||
this.#parseKeyString = parseKeyString;
|
||||
this.#identifierEquals = identifierEquals;
|
||||
this.#refresh = refresh;
|
||||
this.#openmct = openmct;
|
||||
|
||||
this.typeRegistry = typeRegistry;
|
||||
this.SEARCH_TYPES = Object.freeze({
|
||||
OBJECTS: 'OBJECTS',
|
||||
@@ -206,14 +217,14 @@ export default class ObjectAPI {
|
||||
* has been saved, or be rejected if it cannot be saved
|
||||
*/
|
||||
get(identifier, abortSignal, forceRemote = false) {
|
||||
let keystring = this.makeKeyString(identifier);
|
||||
let keystring = this.#makeKeyString(identifier);
|
||||
|
||||
if (!forceRemote) {
|
||||
if (this.cache[keystring] !== undefined) {
|
||||
return this.cache[keystring];
|
||||
}
|
||||
|
||||
identifier = utils.parseKeyString(identifier);
|
||||
identifier = parseKeyString(identifier);
|
||||
|
||||
if (this.isTransactionActive()) {
|
||||
let dirtyObject = this.transaction.getDirtyObject(identifier);
|
||||
@@ -227,7 +238,7 @@ export default class ObjectAPI {
|
||||
const provider = this.getProvider(identifier);
|
||||
|
||||
if (!provider) {
|
||||
throw new Error(`No Provider Matched for keyString "${this.makeKeyString(identifier)}"`);
|
||||
throw new Error(`No Provider Matched for keyString "${this.#makeKeyString(identifier)}"`);
|
||||
}
|
||||
|
||||
if (!provider.get) {
|
||||
@@ -325,7 +336,7 @@ export default class ObjectAPI {
|
||||
*/
|
||||
getMutable(identifier) {
|
||||
if (!this.supportsMutation(identifier)) {
|
||||
throw new Error(`Object "${this.makeKeyString(identifier)}" does not support mutation.`);
|
||||
throw new Error(`Object "${this.#makeKeyString(identifier)}" does not support mutation.`);
|
||||
}
|
||||
|
||||
return this.get(identifier).then((object) => {
|
||||
@@ -352,7 +363,7 @@ export default class ObjectAPI {
|
||||
}
|
||||
|
||||
isPersistable(idOrKeyString) {
|
||||
let identifier = utils.parseKeyString(idOrKeyString);
|
||||
let identifier = parseKeyString(idOrKeyString);
|
||||
let provider = this.getProvider(identifier);
|
||||
if (provider?.isReadOnly) {
|
||||
return !provider.isReadOnly();
|
||||
@@ -362,7 +373,7 @@ export default class ObjectAPI {
|
||||
}
|
||||
|
||||
isMissing(domainObject) {
|
||||
let identifier = utils.makeKeyString(domainObject.identifier);
|
||||
let identifier = makeKeyString(domainObject.identifier);
|
||||
let missingName = 'Missing: ' + identifier;
|
||||
|
||||
return domainObject.name === missingName;
|
||||
@@ -442,21 +453,21 @@ export default class ObjectAPI {
|
||||
if (error instanceof this.errors.Conflict) {
|
||||
// Synchronized objects will resolve their own conflicts
|
||||
if (this.SYNCHRONIZED_OBJECT_TYPES.includes(domainObject.type)) {
|
||||
this.openmct.notifications.info(
|
||||
`Conflict detected while saving "${this.makeKeyString(
|
||||
this.#openmct.notifications.info(
|
||||
`Conflict detected while saving "${this.#makeKeyString(
|
||||
domainObject.name
|
||||
)}", attempting to resolve`
|
||||
);
|
||||
} else {
|
||||
this.openmct.notifications.error(
|
||||
`Conflict detected while saving ${this.makeKeyString(domainObject.identifier)}`
|
||||
this.#openmct.notifications.error(
|
||||
`Conflict detected while saving ${this.#makeKeyString(domainObject.identifier)}`
|
||||
);
|
||||
|
||||
if (this.isTransactionActive()) {
|
||||
this.endTransaction();
|
||||
}
|
||||
|
||||
await this.refresh(domainObject);
|
||||
await this.#refresh(domainObject);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,7 +476,7 @@ export default class ObjectAPI {
|
||||
}
|
||||
|
||||
async #getCurrentUsername() {
|
||||
const user = await this.openmct.user.getCurrentUser();
|
||||
const user = await this.#openmct.user.getCurrentUser();
|
||||
let username;
|
||||
|
||||
if (user !== undefined) {
|
||||
@@ -554,7 +565,7 @@ export default class ObjectAPI {
|
||||
*/
|
||||
getRelativePath(objectPath) {
|
||||
return objectPath
|
||||
.map((p) => this.makeKeyString(p.identifier))
|
||||
.map((p) => this.#makeKeyString(p.identifier))
|
||||
.reverse()
|
||||
.join('/');
|
||||
}
|
||||
@@ -574,13 +585,13 @@ export default class ObjectAPI {
|
||||
}
|
||||
|
||||
let sourceTelemetry = null;
|
||||
if (telemetryIdentifier && utils.identifierEquals(identifier, telemetryIdentifier)) {
|
||||
if (telemetryIdentifier && this.#identifierEquals(identifier, telemetryIdentifier)) {
|
||||
sourceTelemetry = identifier;
|
||||
} else if (objectDetails.composition) {
|
||||
sourceTelemetry = objectDetails.composition[0];
|
||||
if (telemetryIdentifier) {
|
||||
sourceTelemetry = objectDetails.composition.find((telemetrySource) =>
|
||||
utils.identifierEquals(telemetrySource, telemetryIdentifier)
|
||||
this.#identifierEquals(telemetrySource, telemetryIdentifier)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -666,7 +677,7 @@ export default class ObjectAPI {
|
||||
mutableObject = MutableDomainObject.createMutable(domainObject, this.eventEmitter);
|
||||
|
||||
// Check if provider supports realtime updates
|
||||
let identifier = utils.parseKeyString(mutableObject.identifier);
|
||||
let identifier = parseKeyString(mutableObject.identifier);
|
||||
let provider = this.getProvider(identifier);
|
||||
|
||||
if (
|
||||
@@ -706,7 +717,7 @@ export default class ObjectAPI {
|
||||
if (domainObject.isMutable) {
|
||||
domainObject.$refresh(refreshedObject);
|
||||
} else {
|
||||
utils.refresh(domainObject, refreshedObject);
|
||||
refresh(domainObject, refreshedObject);
|
||||
}
|
||||
|
||||
return domainObject;
|
||||
@@ -745,7 +756,7 @@ export default class ObjectAPI {
|
||||
* @returns {string} A string representation of the given identifier, including namespace and key
|
||||
*/
|
||||
makeKeyString(identifier) {
|
||||
return utils.makeKeyString(identifier);
|
||||
return makeKeyString(identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -753,7 +764,7 @@ export default class ObjectAPI {
|
||||
* @returns {module:openmct.ObjectAPI~Identifier} An identifier object
|
||||
*/
|
||||
parseKeyString(keyString) {
|
||||
return utils.parseKeyString(keyString);
|
||||
return parseKeyString(keyString);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -761,9 +772,9 @@ export default class ObjectAPI {
|
||||
* @param {module:openmct.ObjectAPI~Identifier[]} identifiers
|
||||
*/
|
||||
areIdsEqual(...identifiers) {
|
||||
const firstIdentifier = utils.parseKeyString(identifiers[0]);
|
||||
const firstIdentifier = this.#parseKeyString(identifiers[0]);
|
||||
|
||||
return identifiers.map(utils.parseKeyString).every((identifier) => {
|
||||
return identifiers.map(this.#parseKeyString).every((identifier) => {
|
||||
return (
|
||||
identifier === firstIdentifier ||
|
||||
(identifier.namespace === firstIdentifier.namespace &&
|
||||
@@ -791,7 +802,7 @@ export default class ObjectAPI {
|
||||
}
|
||||
|
||||
return path.some((pathElement) => {
|
||||
const identifierToCheck = utils.parseKeyString(keyStringToCheck);
|
||||
const identifierToCheck = this.#parseKeyString(keyStringToCheck);
|
||||
|
||||
return this.areIdsEqual(identifierToCheck, pathElement.identifier);
|
||||
});
|
||||
@@ -814,7 +825,7 @@ export default class ObjectAPI {
|
||||
if (location && !this.#pathContainsDomainObject(location, path)) {
|
||||
// if we have a location, and we don't already have this in our constructed path,
|
||||
// then keep walking up the path
|
||||
return this.getOriginalPath(utils.parseKeyString(location), path, abortSignal);
|
||||
return this.getOriginalPath(this.#parseKeyString(location), path, abortSignal);
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
@@ -853,8 +864,8 @@ export default class ObjectAPI {
|
||||
await Promise.all(
|
||||
keyStrings.map((keyString) =>
|
||||
this.supportsMutation(keyString)
|
||||
? this.getMutable(utils.parseKeyString(keyString))
|
||||
: this.get(utils.parseKeyString(keyString))
|
||||
? this.getMutable(this.#parseKeyString(keyString))
|
||||
: this.get(this.#parseKeyString(keyString))
|
||||
)
|
||||
)
|
||||
).reverse();
|
||||
@@ -866,7 +877,7 @@ export default class ObjectAPI {
|
||||
return (
|
||||
objectPath !== undefined &&
|
||||
objectPath.length > 1 &&
|
||||
domainObject.location !== this.makeKeyString(objectPath[1].identifier)
|
||||
domainObject.location !== this.#makeKeyString(objectPath[1].identifier)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import utils from './object-utils.js';
|
||||
import { isIdentifier } from './object-utils.js';
|
||||
|
||||
export default class RootRegistry {
|
||||
constructor(openmct) {
|
||||
@@ -47,12 +47,12 @@ export default class RootRegistry {
|
||||
}
|
||||
|
||||
_isValid(rootItem) {
|
||||
if (utils.isIdentifier(rootItem) || typeof rootItem === 'function') {
|
||||
if (isIdentifier(rootItem) || typeof rootItem === 'function') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Array.isArray(rootItem)) {
|
||||
return rootItem.every(utils.isIdentifier);
|
||||
return rootItem.every(isIdentifier);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import utils from 'objectUtils';
|
||||
import { makeKeyString, parseKeyString } from 'objectUtils';
|
||||
|
||||
import Transaction from './Transaction.js';
|
||||
|
||||
@@ -9,7 +9,7 @@ let transaction;
|
||||
describe('Transaction Class', () => {
|
||||
beforeEach(() => {
|
||||
objectAPI = {
|
||||
makeKeyString: (identifier) => utils.makeKeyString(identifier),
|
||||
makeKeyString: (identifier) => makeKeyString(identifier),
|
||||
save: () => Promise.resolve(true),
|
||||
mutate: (object, prop, value) => {
|
||||
object[prop] = value;
|
||||
@@ -18,7 +18,7 @@ describe('Transaction Class', () => {
|
||||
},
|
||||
refresh: (object) => Promise.resolve(object),
|
||||
areIdsEqual: (...identifiers) => {
|
||||
return identifiers.map(utils.parseKeyString).every((identifier) => {
|
||||
return identifiers.map(parseKeyString).every((identifier) => {
|
||||
return (
|
||||
identifier === identifiers[0] ||
|
||||
(identifier.namespace === identifiers[0].namespace &&
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
* Utility for checking if a thing is an Open MCT Identifier.
|
||||
* @private
|
||||
*/
|
||||
function isIdentifier(thing) {
|
||||
export function isIdentifier(thing) {
|
||||
return (
|
||||
typeof thing === 'object' &&
|
||||
Object.prototype.hasOwnProperty.call(thing, 'key') &&
|
||||
@@ -36,7 +36,7 @@ function isIdentifier(thing) {
|
||||
* Utility for checking if a thing is a key string. Not perfect.
|
||||
* @private
|
||||
*/
|
||||
function isKeyString(thing) {
|
||||
export function isKeyString(thing) {
|
||||
return typeof thing === 'string';
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ function isKeyString(thing) {
|
||||
* @param keyString
|
||||
* @returns identifier
|
||||
*/
|
||||
function parseKeyString(keyString) {
|
||||
export function parseKeyString(keyString) {
|
||||
if (isIdentifier(keyString)) {
|
||||
return keyString;
|
||||
}
|
||||
@@ -86,7 +86,7 @@ function parseKeyString(keyString) {
|
||||
* @param identifier
|
||||
* @returns keyString
|
||||
*/
|
||||
function makeKeyString(identifier) {
|
||||
export function makeKeyString(identifier) {
|
||||
if (!identifier) {
|
||||
throw new Error('Cannot make key string from null identifier');
|
||||
}
|
||||
@@ -112,7 +112,7 @@ function makeKeyString(identifier) {
|
||||
* @param domainObject
|
||||
* @returns oldFormatModel
|
||||
*/
|
||||
function toOldFormat(model) {
|
||||
export function toOldFormat(model) {
|
||||
model = JSON.parse(JSON.stringify(model));
|
||||
delete model.identifier;
|
||||
if (model.composition) {
|
||||
@@ -131,7 +131,7 @@ function toOldFormat(model) {
|
||||
* @param keyString
|
||||
* @returns domainObject
|
||||
*/
|
||||
function toNewFormat(model, keyString) {
|
||||
export function toNewFormat(model, keyString) {
|
||||
model = JSON.parse(JSON.stringify(model));
|
||||
model.identifier = parseKeyString(keyString);
|
||||
if (model.composition) {
|
||||
@@ -148,7 +148,7 @@ function toNewFormat(model, keyString) {
|
||||
* @param otherIdentifier
|
||||
* @returns Boolean true if identifiers are equal.
|
||||
*/
|
||||
function identifierEquals(a, b) {
|
||||
export function identifierEquals(a, b) {
|
||||
return a.key === b.key && a.namespace === b.namespace;
|
||||
}
|
||||
|
||||
@@ -160,23 +160,12 @@ function identifierEquals(a, b) {
|
||||
* @param otherDomainOBject
|
||||
* @returns Boolean true if objects are equal.
|
||||
*/
|
||||
function objectEquals(a, b) {
|
||||
export function objectEquals(a, b) {
|
||||
return identifierEquals(a.identifier, b.identifier);
|
||||
}
|
||||
|
||||
function refresh(oldObject, newObject) {
|
||||
export function refresh(oldObject, newObject) {
|
||||
let deleted = _.difference(Object.keys(oldObject), Object.keys(newObject));
|
||||
deleted.forEach((propertyName) => delete oldObject[propertyName]);
|
||||
Object.assign(oldObject, newObject);
|
||||
}
|
||||
|
||||
export default {
|
||||
isIdentifier: isIdentifier,
|
||||
toOldFormat: toOldFormat,
|
||||
toNewFormat: toNewFormat,
|
||||
makeKeyString: makeKeyString,
|
||||
parseKeyString: parseKeyString,
|
||||
equals: objectEquals,
|
||||
identifierEquals: identifierEquals,
|
||||
refresh: refresh
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import objectUtils from 'objectUtils';
|
||||
import { makeKeyString, parseKeyString, toNewFormat, toOldFormat } from 'objectUtils';
|
||||
|
||||
describe('objectUtils', function () {
|
||||
describe('keyString util', function () {
|
||||
@@ -31,27 +31,27 @@ describe('objectUtils', function () {
|
||||
|
||||
Object.keys(EXPECTATIONS).forEach(function (keyString) {
|
||||
it('parses "' + keyString + '".', function () {
|
||||
expect(objectUtils.parseKeyString(keyString)).toEqual(EXPECTATIONS[keyString]);
|
||||
expect(parseKeyString(keyString)).toEqual(EXPECTATIONS[keyString]);
|
||||
});
|
||||
|
||||
it('parses and re-encodes "' + keyString + '"', function () {
|
||||
const identifier = objectUtils.parseKeyString(keyString);
|
||||
expect(objectUtils.makeKeyString(identifier)).toEqual(keyString);
|
||||
const identifier = parseKeyString(keyString);
|
||||
expect(makeKeyString(identifier)).toEqual(keyString);
|
||||
});
|
||||
|
||||
it('is idempotent for "' + keyString + '".', function () {
|
||||
const identifier = objectUtils.parseKeyString(keyString);
|
||||
let again = objectUtils.parseKeyString(identifier);
|
||||
const identifier = parseKeyString(keyString);
|
||||
let again = parseKeyString(identifier);
|
||||
expect(identifier).toEqual(again);
|
||||
again = objectUtils.parseKeyString(again);
|
||||
again = objectUtils.parseKeyString(again);
|
||||
again = parseKeyString(again);
|
||||
again = parseKeyString(again);
|
||||
expect(identifier).toEqual(again);
|
||||
|
||||
let againKeyString = objectUtils.makeKeyString(again);
|
||||
let againKeyString = makeKeyString(again);
|
||||
expect(againKeyString).toEqual(keyString);
|
||||
againKeyString = objectUtils.makeKeyString(againKeyString);
|
||||
againKeyString = objectUtils.makeKeyString(againKeyString);
|
||||
againKeyString = objectUtils.makeKeyString(againKeyString);
|
||||
againKeyString = makeKeyString(againKeyString);
|
||||
againKeyString = makeKeyString(againKeyString);
|
||||
againKeyString = makeKeyString(againKeyString);
|
||||
expect(againKeyString).toEqual(keyString);
|
||||
});
|
||||
});
|
||||
@@ -60,7 +60,7 @@ describe('objectUtils', function () {
|
||||
describe('old object conversions', function () {
|
||||
it('translate ids', function () {
|
||||
expect(
|
||||
objectUtils.toNewFormat(
|
||||
toNewFormat(
|
||||
{
|
||||
prop: 'someValue'
|
||||
},
|
||||
@@ -77,7 +77,7 @@ describe('objectUtils', function () {
|
||||
|
||||
it('translates composition', function () {
|
||||
expect(
|
||||
objectUtils.toNewFormat(
|
||||
toNewFormat(
|
||||
{
|
||||
prop: 'someValue',
|
||||
composition: ['anotherObjectId', 'scratch:anotherObjectId']
|
||||
@@ -107,7 +107,7 @@ describe('objectUtils', function () {
|
||||
describe('new object conversions', function () {
|
||||
it('removes ids', function () {
|
||||
expect(
|
||||
objectUtils.toOldFormat({
|
||||
toOldFormat({
|
||||
prop: 'someValue',
|
||||
identifier: {
|
||||
namespace: '',
|
||||
@@ -121,7 +121,7 @@ describe('objectUtils', function () {
|
||||
|
||||
it('translates composition', function () {
|
||||
expect(
|
||||
objectUtils.toOldFormat({
|
||||
toOldFormat({
|
||||
prop: 'someValue',
|
||||
composition: [
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import objectUtils from 'objectUtils';
|
||||
import { makeKeyString } from 'objectUtils';
|
||||
|
||||
import CustomStringFormatter from '../../plugins/displayLayout/CustomStringFormatter.js';
|
||||
import BatchingWebSocket from './BatchingWebSocket.js';
|
||||
@@ -429,7 +429,7 @@ export default class TelemetryAPI {
|
||||
this.#subscribeCache = {};
|
||||
}
|
||||
|
||||
const keyString = objectUtils.makeKeyString(domainObject.identifier);
|
||||
const keyString = makeKeyString(domainObject.identifier);
|
||||
const supportedStrategy = supportsBatching ? requestedStrategy : SUBSCRIBE_STRATEGY.LATEST;
|
||||
// Override the requested strategy with the strategy supported by the provider
|
||||
const optionsWithSupportedStrategy = {
|
||||
@@ -541,7 +541,7 @@ export default class TelemetryAPI {
|
||||
this.stalenessSubscriberCache = {};
|
||||
}
|
||||
|
||||
const keyString = objectUtils.makeKeyString(domainObject.identifier);
|
||||
const keyString = makeKeyString(domainObject.identifier);
|
||||
let stalenessSubscriber = this.stalenessSubscriberCache[keyString];
|
||||
|
||||
if (!stalenessSubscriber) {
|
||||
@@ -600,7 +600,7 @@ export default class TelemetryAPI {
|
||||
this.limitsSubscribeCache = {};
|
||||
}
|
||||
|
||||
const keyString = objectUtils.makeKeyString(domainObject.identifier);
|
||||
const keyString = makeKeyString(domainObject.identifier);
|
||||
let subscriber = this.limitsSubscribeCache[keyString];
|
||||
|
||||
if (!subscriber) {
|
||||
|
||||
@@ -31,10 +31,10 @@
|
||||
<div class="c-cs__header-label c-section__label">Test Data</div>
|
||||
</div>
|
||||
<div v-if="expanded" class="c-cs__content">
|
||||
<div class="c-cs__test-data__controls c-cdef__controls" :disabled="!telemetry.length">
|
||||
<div :class="['c-cs__test-data__controls c-cdef__controls', { disabled: !telemetry.length }]">
|
||||
<label class="c-toggle-switch">
|
||||
<input type="checkbox" :checked="isApplied" @change="applyTestData" />
|
||||
<span class="c-toggle-switch__slider"></span>
|
||||
<span class="c-toggle-switch__slider" aria-label="Apply Test Data"></span>
|
||||
<span class="c-toggle-switch__label">Apply Test Data</span>
|
||||
</label>
|
||||
</div>
|
||||
@@ -47,7 +47,11 @@
|
||||
<span class="c-cs-test__label">Set</span>
|
||||
<span class="c-cs-test__controls">
|
||||
<span class="c-cdef__control">
|
||||
<select v-model="testInput.telemetry" @change="updateMetadata(testInput)">
|
||||
<select
|
||||
v-model="testInput.telemetry"
|
||||
aria-label="Test Data Telemetry Selection"
|
||||
@change="updateMetadata(testInput)"
|
||||
>
|
||||
<option value="">- Select Telemetry -</option>
|
||||
<option
|
||||
v-for="(telemetryOption, index) in telemetry"
|
||||
@@ -59,7 +63,11 @@
|
||||
</select>
|
||||
</span>
|
||||
<span v-if="testInput.telemetry" class="c-cdef__control">
|
||||
<select v-model="testInput.metadata" @change="updateTestData">
|
||||
<select
|
||||
v-model="testInput.metadata"
|
||||
aria-label="Test Data Metadata Selection"
|
||||
@change="updateTestData"
|
||||
>
|
||||
<option value="">- Select Field -</option>
|
||||
<option
|
||||
v-for="(option, index) in telemetryMetadataOptions[getId(testInput.telemetry)]"
|
||||
@@ -76,6 +84,7 @@
|
||||
placeholder="Enter test input"
|
||||
type="text"
|
||||
class="c-cdef__control__input"
|
||||
aria-label="Test Data Input"
|
||||
@change="updateTestData"
|
||||
/>
|
||||
</span>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import objectUtils from 'objectUtils';
|
||||
import { parseKeyString } from 'objectUtils';
|
||||
import { filter__proto__ } from 'utils/sanitization';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
@@ -158,7 +158,7 @@ export default class ImportAsJSONAction {
|
||||
key: uuid()
|
||||
};
|
||||
|
||||
const oldId = objectUtils.parseKeyString(domainObjectId);
|
||||
const oldId = parseKeyString(domainObjectId);
|
||||
|
||||
tree = this._rewriteId(oldId, newId, tree);
|
||||
}, this);
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
<template>
|
||||
<li
|
||||
draggable="true"
|
||||
:aria-label="`${elementObject.name} Element Item`"
|
||||
:aria-grabbed="hover"
|
||||
@dragstart="emitDragStartEvent"
|
||||
@dragenter="onDragenter"
|
||||
@dragover.prevent
|
||||
@@ -76,16 +78,19 @@ export default {
|
||||
},
|
||||
emits: ['drop-custom', 'dragstart-custom'],
|
||||
data() {
|
||||
const isAlias =
|
||||
this.elementObject.location !==
|
||||
this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
|
||||
return {
|
||||
contextClickActive: false,
|
||||
hover: false,
|
||||
isAlias
|
||||
hover: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isAlias() {
|
||||
return (
|
||||
this.elementObject.location !==
|
||||
this.openmct.objects.makeKeyString(this.domainObject.identifier)
|
||||
);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
emitDropEvent(event) {
|
||||
this.$emit('drop-custom', event);
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
/>
|
||||
<div class="c-elements-pool__elements">
|
||||
<ul
|
||||
v-if="hasElements"
|
||||
v-show="hasElements"
|
||||
id="inspector-elements-tree"
|
||||
class="c-tree c-elements-pool__tree js-elements-pool__tree"
|
||||
>
|
||||
@@ -63,7 +63,7 @@
|
||||
></li>
|
||||
</element-item-group>
|
||||
</ul>
|
||||
<div v-if="!hasElements">No contained elements</div>
|
||||
<div v-show="!hasElements">No contained elements</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -169,7 +169,8 @@ export default {
|
||||
setYAxisIds() {
|
||||
const configId = this.openmct.objects.makeKeyString(this.parentObject.identifier);
|
||||
this.config = configStore.get(configId);
|
||||
this.yAxes = [];
|
||||
// Clear the yAxes array and repopulate it with the current YAxis elements
|
||||
this.yAxes.splice(0);
|
||||
this.yAxes.push({
|
||||
id: this.config.yAxis.id,
|
||||
elements: this.parentObject.configuration.series.filter(
|
||||
@@ -207,7 +208,7 @@ export default {
|
||||
}
|
||||
|
||||
// Store the element in the cache and set its yAxisId
|
||||
this.elementsCache[keyString] = JSON.parse(JSON.stringify(element));
|
||||
this.elementsCache[keyString] = element;
|
||||
if (this.elementsCache[keyString].yAxisId !== yAxisId) {
|
||||
// Mutate the YAxisId on the domainObject itself
|
||||
this.updateCacheAndMutate(element, yAxisId);
|
||||
@@ -276,7 +277,7 @@ export default {
|
||||
yAxisId
|
||||
});
|
||||
this.composition.add(domainObject);
|
||||
this.elementsCache[keyString] = JSON.parse(JSON.stringify(domainObject));
|
||||
this.elementsCache[keyString] = domainObject;
|
||||
}
|
||||
|
||||
this.elementsCache[keyString].yAxisId = yAxisId;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import objectUtils from 'objectUtils';
|
||||
import { makeKeyString } from 'objectUtils';
|
||||
|
||||
const NOTEBOOK_LOCAL_STORAGE = 'notebook-storage';
|
||||
let currentNotebookObjectIdentifier = null;
|
||||
@@ -22,8 +22,7 @@ function defaultNotebookObjectChanged(newDomainObject) {
|
||||
function observeDefaultNotebookObject(openmct, notebookStorage, domainObject) {
|
||||
if (
|
||||
currentNotebookObjectIdentifier &&
|
||||
objectUtils.makeKeyString(currentNotebookObjectIdentifier) ===
|
||||
objectUtils.makeKeyString(notebookStorage.identifier)
|
||||
makeKeyString(currentNotebookObjectIdentifier) === makeKeyString(notebookStorage.identifier)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ export default {
|
||||
this.role = activeRole;
|
||||
const status = await this.openmct.user.status.getStatusForRole(activeRole);
|
||||
if (status !== undefined) {
|
||||
this.setStatus({ status });
|
||||
this.setStatus({ role: this.role, status });
|
||||
}
|
||||
},
|
||||
subscribeToMyStatus() {
|
||||
@@ -141,7 +141,11 @@ export default {
|
||||
subscribeToRoleChange() {
|
||||
this.openmct.user.on('roleChanged', this.fetchMyStatus);
|
||||
},
|
||||
setStatus({ status }) {
|
||||
setStatus({ role, status }) {
|
||||
if (role !== this.role) {
|
||||
// not my role
|
||||
return;
|
||||
}
|
||||
status = this.applyStyling(status);
|
||||
this.selectedStatus = status.key;
|
||||
this.indicator.iconClass(status.iconClassPoll);
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
* rootIdentifier, and rewrites all child object identifiers so that they
|
||||
* exist in the same namespace as the rootIdentifier.
|
||||
*/
|
||||
import objectUtils from 'objectUtils';
|
||||
import { makeKeyString, parseKeyString, toNewFormat } from 'objectUtils';
|
||||
|
||||
class StaticModelProvider {
|
||||
constructor(importData, rootIdentifier) {
|
||||
@@ -38,7 +38,7 @@ class StaticModelProvider {
|
||||
* Standard "Get".
|
||||
*/
|
||||
get(identifier) {
|
||||
const keyString = objectUtils.makeKeyString(identifier);
|
||||
const keyString = makeKeyString(identifier);
|
||||
if (this.objectMap[keyString]) {
|
||||
return this.objectMap[keyString];
|
||||
}
|
||||
@@ -49,7 +49,7 @@ class StaticModelProvider {
|
||||
parseObjectLeaf(objectLeaf, idMap, newRootNamespace, oldRootNamespace) {
|
||||
Object.keys(objectLeaf).forEach((nodeKey) => {
|
||||
if (idMap.get(nodeKey)) {
|
||||
const newIdentifier = objectUtils.makeKeyString({
|
||||
const newIdentifier = makeKeyString({
|
||||
namespace: newRootNamespace,
|
||||
key: idMap.get(nodeKey)
|
||||
});
|
||||
@@ -104,7 +104,7 @@ class StaticModelProvider {
|
||||
let mappedLeafValue;
|
||||
if (oldRootNamespace) {
|
||||
mappedLeafValue = idMap.get(
|
||||
objectUtils.makeKeyString({
|
||||
makeKeyString({
|
||||
namespace: oldRootNamespace,
|
||||
key: leafValue
|
||||
})
|
||||
@@ -125,7 +125,7 @@ class StaticModelProvider {
|
||||
return null;
|
||||
}
|
||||
|
||||
const newLocationIdentifier = objectUtils.makeKeyString({
|
||||
const newLocationIdentifier = makeKeyString({
|
||||
namespace: newRootNamespace,
|
||||
key: mappedLeafValue
|
||||
});
|
||||
@@ -134,7 +134,7 @@ class StaticModelProvider {
|
||||
} else {
|
||||
const mappedLeafValue = idMap.get(leafValue);
|
||||
if (mappedLeafValue) {
|
||||
const newIdentifier = objectUtils.makeKeyString({
|
||||
const newIdentifier = makeKeyString({
|
||||
namespace: newRootNamespace,
|
||||
key: mappedLeafValue
|
||||
});
|
||||
@@ -147,7 +147,7 @@ class StaticModelProvider {
|
||||
}
|
||||
|
||||
rewriteObjectIdentifiers(importData, rootIdentifier) {
|
||||
const { namespace: oldRootNamespace } = objectUtils.parseKeyString(importData.rootId);
|
||||
const { namespace: oldRootNamespace } = parseKeyString(importData.rootId);
|
||||
const { namespace: newRootNamespace } = rootIdentifier;
|
||||
const idMap = new Map();
|
||||
const objectTree = importData.openmct;
|
||||
@@ -172,7 +172,7 @@ class StaticModelProvider {
|
||||
*/
|
||||
convertToNewObjects(oldObjectMap) {
|
||||
return Object.keys(oldObjectMap).reduce(function (newObjectMap, key) {
|
||||
newObjectMap[key] = objectUtils.toNewFormat(oldObjectMap[key], key);
|
||||
newObjectMap[key] = toNewFormat(oldObjectMap[key], key);
|
||||
|
||||
return newObjectMap;
|
||||
}, {});
|
||||
@@ -180,7 +180,7 @@ class StaticModelProvider {
|
||||
|
||||
/* Set the root location correctly for a top-level object */
|
||||
setRootLocation(objectMap, rootIdentifier) {
|
||||
objectMap[objectUtils.makeKeyString(rootIdentifier)].location = 'ROOT';
|
||||
objectMap[makeKeyString(rootIdentifier)].location = 'ROOT';
|
||||
|
||||
return objectMap;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import EventEmitter from 'EventEmitter';
|
||||
import _ from 'lodash';
|
||||
import objectUtils from 'objectUtils';
|
||||
import { makeKeyString } from 'objectUtils';
|
||||
|
||||
import ConditionEvaluator from './ConditionEvaluator.js';
|
||||
|
||||
@@ -119,7 +119,7 @@ ConditionManager.prototype.addGlobalPropertyType = function (key, type) {
|
||||
* has completed and types have been parsed
|
||||
*/
|
||||
ConditionManager.prototype.parsePropertyTypes = function (object) {
|
||||
const objectId = objectUtils.makeKeyString(object.identifier);
|
||||
const objectId = makeKeyString(object.identifier);
|
||||
|
||||
this.telemetryTypesById[objectId] = {};
|
||||
Object.values(this.telemetryMetadataById[objectId]).forEach(function (valueMetadata) {
|
||||
@@ -182,7 +182,7 @@ ConditionManager.prototype.createNormalizedDatum = function (objId, telemetryDat
|
||||
ConditionManager.prototype.onCompositionAdd = function (obj) {
|
||||
let compositionKeys;
|
||||
const telemetryAPI = this.openmct.telemetry;
|
||||
const objId = objectUtils.makeKeyString(obj.identifier);
|
||||
const objId = makeKeyString(obj.identifier);
|
||||
let telemetryMetadata;
|
||||
const self = this;
|
||||
|
||||
@@ -191,7 +191,7 @@ ConditionManager.prototype.onCompositionAdd = function (obj) {
|
||||
self.telemetryMetadataById[objId] = {};
|
||||
|
||||
// FIXME: this should just update based on listener.
|
||||
compositionKeys = self.domainObject.composition.map(objectUtils.makeKeyString);
|
||||
compositionKeys = self.domainObject.composition.map(makeKeyString);
|
||||
if (!compositionKeys.includes(objId)) {
|
||||
self.domainObject.composition.push(obj.identifier);
|
||||
}
|
||||
@@ -245,7 +245,7 @@ ConditionManager.prototype.onCompositionAdd = function (obj) {
|
||||
* @private
|
||||
*/
|
||||
ConditionManager.prototype.onCompositionRemove = function (identifier) {
|
||||
const objectId = objectUtils.makeKeyString(identifier);
|
||||
const objectId = makeKeyString(identifier);
|
||||
// FIXME: this should just update by listener.
|
||||
_.remove(this.domainObject.composition, function (id) {
|
||||
return id.key === identifier.key && id.namespace === identifier.namespace;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import objectUtils from 'objectUtils';
|
||||
import { makeKeyString } from 'objectUtils';
|
||||
|
||||
import Select from './Select.js';
|
||||
|
||||
@@ -39,7 +39,7 @@ export default function ObjectSelect(config, manager, baseOptions) {
|
||||
* @private
|
||||
*/
|
||||
function onCompositionAdd(obj) {
|
||||
self.select.addOption(objectUtils.makeKeyString(obj.identifier), obj.name);
|
||||
self.select.addOption(makeKeyString(obj.identifier), obj.name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,7 +77,7 @@ export default function ObjectSelect(config, manager, baseOptions) {
|
||||
*/
|
||||
ObjectSelect.prototype.generateOptions = function () {
|
||||
const items = Object.values(this.compositionObjs).map(function (obj) {
|
||||
return [objectUtils.makeKeyString(obj.identifier), obj.name];
|
||||
return [makeKeyString(obj.identifier), obj.name];
|
||||
});
|
||||
this.baseOptions.forEach(function (option, index) {
|
||||
items.splice(index, 0, option);
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import objectUtils from 'objectUtils';
|
||||
import { makeKeyString } from 'objectUtils';
|
||||
|
||||
import SummaryWidgetEvaluator from './SummaryWidgetEvaluator.js';
|
||||
|
||||
@@ -31,7 +31,7 @@ export default function EvaluatorPool(openmct) {
|
||||
}
|
||||
|
||||
EvaluatorPool.prototype.get = function (domainObject) {
|
||||
const objectId = objectUtils.makeKeyString(domainObject.identifier);
|
||||
const objectId = makeKeyString(domainObject.identifier);
|
||||
let poolEntry = this.byObjectId[objectId];
|
||||
if (!poolEntry) {
|
||||
poolEntry = {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
import _ from 'lodash';
|
||||
import objectUtils from 'objectUtils';
|
||||
import { makeKeyString } from 'objectUtils';
|
||||
|
||||
import eventHelpers from '../eventHelpers.js';
|
||||
import SummaryWidgetRule from './SummaryWidgetRule.js';
|
||||
@@ -113,7 +113,7 @@ SummaryWidgetEvaluator.prototype.updateRules = function (domainObject) {
|
||||
};
|
||||
|
||||
SummaryWidgetEvaluator.prototype.addChild = function (childObject) {
|
||||
const childId = objectUtils.makeKeyString(childObject.identifier);
|
||||
const childId = makeKeyString(childObject.identifier);
|
||||
const metadata = this.openmct.telemetry.getMetadata(childObject);
|
||||
const formats = this.openmct.telemetry.getFormatMap(metadata);
|
||||
|
||||
@@ -126,7 +126,7 @@ SummaryWidgetEvaluator.prototype.addChild = function (childObject) {
|
||||
};
|
||||
|
||||
SummaryWidgetEvaluator.prototype.removeChild = function (childObject) {
|
||||
const childId = objectUtils.makeKeyString(childObject.identifier);
|
||||
const childId = makeKeyString(childObject.identifier);
|
||||
delete this.baseState[childId];
|
||||
};
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import objectUtils from 'objectUtils';
|
||||
import { parseKeyString } from 'objectUtils';
|
||||
|
||||
import TelemetryAverager from './TelemetryAverager.js';
|
||||
|
||||
@@ -43,7 +43,7 @@ MeanTelemetryProvider.prototype.supportsRequest =
|
||||
MeanTelemetryProvider.prototype.subscribe = function (domainObject, callback) {
|
||||
let wrappedUnsubscribe;
|
||||
let unsubscribeCalled = false;
|
||||
const objectId = objectUtils.parseKeyString(domainObject.telemetryPoint);
|
||||
const objectId = parseKeyString(domainObject.telemetryPoint);
|
||||
const samples = domainObject.samples;
|
||||
|
||||
this.objectAPI
|
||||
@@ -79,7 +79,7 @@ MeanTelemetryProvider.prototype.subscribeToAverage = function (domainObject, sam
|
||||
};
|
||||
|
||||
MeanTelemetryProvider.prototype.request = function (domainObject, request) {
|
||||
const objectId = objectUtils.parseKeyString(domainObject.telemetryPoint);
|
||||
const objectId = parseKeyString(domainObject.telemetryPoint);
|
||||
const samples = domainObject.samples;
|
||||
|
||||
return this.objectAPI.get(objectId).then(
|
||||
@@ -119,7 +119,7 @@ MeanTelemetryProvider.prototype.requestAverageTelemetry = function (
|
||||
* @private
|
||||
*/
|
||||
MeanTelemetryProvider.prototype.getLinkedObject = function (domainObject) {
|
||||
const objectId = objectUtils.parseKeyString(domainObject.telemetryPoint);
|
||||
const objectId = parseKeyString(domainObject.telemetryPoint);
|
||||
|
||||
return this.objectAPI.get(objectId);
|
||||
};
|
||||
|
||||
@@ -85,8 +85,6 @@ import { getFilteredValues, getValidatedData, getValidatedGroups } from '../plan
|
||||
import { SORT_ORDER_OPTIONS } from './constants.js';
|
||||
import ExpandedViewItem from './ExpandedViewItem.vue';
|
||||
|
||||
const SCROLL_TIMEOUT = 10000;
|
||||
|
||||
const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
|
||||
const SAME_DAY_PRECISION_SECONDS = 'HH:mm:ss';
|
||||
|
||||
@@ -209,7 +207,6 @@ export default {
|
||||
},
|
||||
created() {
|
||||
this.updateTimestamp = _.throttle(this.updateTimestamp, 1000);
|
||||
this.deferAutoScroll = _.debounce(this.deferAutoScroll, 500);
|
||||
|
||||
this.setTimeContext();
|
||||
this.timestamp = this.timeContext.now();
|
||||
@@ -238,8 +235,6 @@ export default {
|
||||
|
||||
this.openmct.editor.on('isEditing', this.setEditState);
|
||||
|
||||
this.$el.parentElement.addEventListener('scroll', this.deferAutoScroll, true);
|
||||
|
||||
if (this.composition) {
|
||||
this.composition.on('add', this.addToComposition);
|
||||
this.composition.on('remove', this.removeItem);
|
||||
@@ -272,11 +267,6 @@ export default {
|
||||
this.openmct.editor.off('isEditing', this.setEditState);
|
||||
this.stopFollowingTimeContext();
|
||||
|
||||
this.$el.parentElement?.removeEventListener('scroll', this.deferAutoScroll, true);
|
||||
if (this.clearAutoScrollDisabledTimer) {
|
||||
clearTimeout(this.clearAutoScrollDisabledTimer);
|
||||
}
|
||||
|
||||
if (this.composition) {
|
||||
this.composition.off('add', this.addToComposition);
|
||||
this.composition.off('remove', this.removeItem);
|
||||
@@ -429,8 +419,6 @@ export default {
|
||||
const filteredItems = this.planActivities.filter(this.filterActivities);
|
||||
const sortedItems = this.sortItems(filteredItems);
|
||||
this.sortedItems = this.applyStyles(sortedItems);
|
||||
//We need to wait for the next tick since we need the height of the row from the DOM
|
||||
this.$nextTick(this.setScrollTop);
|
||||
},
|
||||
updateTimeStampAndListActivities(time) {
|
||||
this.timestamp = time;
|
||||
@@ -509,24 +497,13 @@ export default {
|
||||
});
|
||||
},
|
||||
// Add activity classes, increase activity counts by type,
|
||||
// set indices of the first occurrences of current and future activities - used for scrolling
|
||||
styleActivity(activity, index) {
|
||||
if (this.timestamp >= activity.start && this.timestamp <= activity.end) {
|
||||
activity.cssClass = CURRENT_CSS_SUFFIX;
|
||||
if (this.firstCurrentActivityIndex < 0) {
|
||||
this.firstCurrentActivityIndex = index;
|
||||
}
|
||||
this.currentActivitiesCount = this.currentActivitiesCount + 1;
|
||||
} else if (this.timestamp < activity.start) {
|
||||
activity.cssClass = FUTURE_CSS_SUFFIX;
|
||||
//the index of the first activity that's greater than the current timestamp
|
||||
if (this.firstFutureActivityIndex < 0) {
|
||||
this.firstFutureActivityIndex = index;
|
||||
}
|
||||
this.futureActivitiesCount = this.futureActivitiesCount + 1;
|
||||
} else {
|
||||
activity.cssClass = PAST_CSS_SUFFIX;
|
||||
this.pastActivitiesCount = this.pastActivitiesCount + 1;
|
||||
}
|
||||
|
||||
if (!activity.key) {
|
||||
@@ -545,111 +522,7 @@ export default {
|
||||
return activity;
|
||||
},
|
||||
applyStyles(activities) {
|
||||
this.firstCurrentOrFutureActivityIndex = -1;
|
||||
this.firstCurrentActivityIndex = -1;
|
||||
this.firstFutureActivityIndex = -1;
|
||||
this.currentActivitiesCount = 0;
|
||||
this.pastActivitiesCount = 0;
|
||||
this.futureActivitiesCount = 0;
|
||||
|
||||
const styledActivities = activities.map(this.styleActivity);
|
||||
|
||||
if (this.firstCurrentActivityIndex > -1) {
|
||||
this.firstCurrentOrFutureActivityIndex = this.firstCurrentActivityIndex;
|
||||
} else if (this.firstFutureActivityIndex > -1) {
|
||||
this.firstCurrentOrFutureActivityIndex = this.firstFutureActivityIndex;
|
||||
}
|
||||
|
||||
return styledActivities;
|
||||
},
|
||||
canAutoScroll() {
|
||||
//this distinguishes between programmatic vs user-triggered scroll events
|
||||
this.autoScrolled = this.dontAutoScroll !== true;
|
||||
|
||||
return this.autoScrolled;
|
||||
},
|
||||
resetScroll() {
|
||||
if (this.canAutoScroll() === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.firstCurrentOrFutureActivityIndex = -1;
|
||||
this.pastActivitiesCount = 0;
|
||||
this.currentActivitiesCount = 0;
|
||||
this.futureActivitiesCount = 0;
|
||||
this.$el.parentElement?.scrollTo({ top: 0 });
|
||||
this.autoScrolled = false;
|
||||
},
|
||||
setScrollTop() {
|
||||
//The view isn't ready yet
|
||||
if (!this.$el.parentElement || this.isExpanded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.canAutoScroll() === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// See #7167 for scrolling algorithm
|
||||
const scrollTop = this.calculateScrollOffset();
|
||||
|
||||
if (scrollTop === undefined) {
|
||||
this.resetScroll();
|
||||
} else {
|
||||
this.$el.parentElement?.scrollTo({
|
||||
top: scrollTop,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
this.autoScrolled = false;
|
||||
}
|
||||
},
|
||||
calculateScrollOffset() {
|
||||
let scrollTop;
|
||||
|
||||
//No scrolling necessary if no past events are present
|
||||
if (this.pastActivitiesCount > 0) {
|
||||
const row = this.$el.querySelector('.js-list-item');
|
||||
const ROW_HEIGHT = row.getBoundingClientRect().height;
|
||||
|
||||
const maxViewableActivities =
|
||||
Math.floor(this.$el.parentElement.getBoundingClientRect().height / ROW_HEIGHT) - 1;
|
||||
|
||||
const currentAndFutureActivities = this.currentActivitiesCount + this.futureActivitiesCount;
|
||||
|
||||
//If there is more viewable area than all current and future activities combined, then show some past events
|
||||
const numberOfPastEventsToShow = maxViewableActivities - currentAndFutureActivities;
|
||||
if (numberOfPastEventsToShow > 0) {
|
||||
//some past events can be shown - get that scroll index
|
||||
if (this.pastActivitiesCount > numberOfPastEventsToShow) {
|
||||
scrollTop =
|
||||
ROW_HEIGHT * (this.firstCurrentOrFutureActivityIndex + numberOfPastEventsToShow);
|
||||
}
|
||||
} else {
|
||||
// only show current and future events
|
||||
scrollTop = ROW_HEIGHT * this.firstCurrentOrFutureActivityIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return scrollTop;
|
||||
},
|
||||
deferAutoScroll() {
|
||||
//if this is not a user-triggered event, don't defer auto scrolling
|
||||
if (this.autoScrolled) {
|
||||
this.autoScrolled = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.dontAutoScroll = true;
|
||||
const self = this;
|
||||
if (this.clearAutoScrollDisabledTimer) {
|
||||
clearTimeout(this.clearAutoScrollDisabledTimer);
|
||||
}
|
||||
|
||||
this.clearAutoScrollDisabledTimer = setTimeout(() => {
|
||||
self.dontAutoScroll = false;
|
||||
self.setScrollTop();
|
||||
}, SCROLL_TIMEOUT);
|
||||
return activities.map(this.styleActivity);
|
||||
},
|
||||
setSort() {
|
||||
const sortOrder = SORT_ORDER_OPTIONS[this.domainObject.configuration.sortOrderIndex];
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
class="c-tree__item__label c-object-label"
|
||||
:class="[statusClass]"
|
||||
draggable="true"
|
||||
:aria-label="ariaLabel"
|
||||
@dragstart="dragStart"
|
||||
@click="navigateOrPreview"
|
||||
>
|
||||
@@ -47,7 +48,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { inject } from 'vue';
|
||||
|
||||
import tooltipHelpers from '../../api/tooltips/tooltipMixins.js';
|
||||
import { useIsEditing } from '../../ui/composables/edit.js';
|
||||
import ContextMenuGesture from '../mixins/context-menu-gesture.js';
|
||||
import ObjectLink from '../mixins/object-link.js';
|
||||
import PreviewAction from '../preview/PreviewAction.js';
|
||||
@@ -76,6 +80,13 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const openmct = inject('openmct');
|
||||
const { isEditing } = useIsEditing(openmct);
|
||||
return {
|
||||
isEditing
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
status: ''
|
||||
@@ -92,6 +103,9 @@ export default {
|
||||
},
|
||||
statusClass() {
|
||||
return this.status ? `is-status--${this.status}` : '';
|
||||
},
|
||||
ariaLabel() {
|
||||
return `${this.isEditing ? 'Preview' : 'Navigate to'} ${this.domainObject.name} ${this.domainObject.type} Object`;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
@@ -33,7 +33,7 @@ import StyleRuleManager from '@/plugins/condition/StyleRuleManager';
|
||||
import { STYLE_CONSTANTS } from '@/plugins/condition/utils/constants';
|
||||
import stalenessMixin from '@/ui/mixins/staleness-mixin';
|
||||
|
||||
import objectUtils from '../../api/objects/object-utils.js';
|
||||
import { objectEquals } from '../../api/objects/object-utils.js';
|
||||
import VisibilityObserver from '../../utils/visibility/VisibilityObserver.js';
|
||||
|
||||
export default {
|
||||
@@ -224,7 +224,7 @@ export default {
|
||||
this.updateView(true);
|
||||
},
|
||||
reload(domainObjectToReload) {
|
||||
if (objectUtils.equals(domainObjectToReload, this.domainObject)) {
|
||||
if (objectEquals(domainObjectToReload, this.domainObject)) {
|
||||
this.updateView(true);
|
||||
this.initObjectStyles();
|
||||
this.triggerStalenessSubscribe(this.domainObject);
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
&:before {
|
||||
// Mag glass icon
|
||||
content: $glyph-icon-magnify;
|
||||
body.mobile & { // Make search icon stand out in mobile
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__use-regex {
|
||||
@@ -47,15 +50,26 @@
|
||||
display: none;
|
||||
order: 99;
|
||||
padding: 1px 0;
|
||||
body.mobile & {
|
||||
display: block;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
body.mobile & { // In mobile, persist the expanded search bar instead of collapsing upon clicking away
|
||||
background-color: rgba($colorHeadFg, 0.2) !important;
|
||||
width: 50vw !important;
|
||||
}
|
||||
.c-search__use-regex {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:before {
|
||||
width: 0;
|
||||
body.mobile & {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='text'],
|
||||
|
||||
43
src/ui/composables/edit.js
Normal file
43
src/ui/composables/edit.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/*****************************************************************************
|
||||
* 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 { ref } from 'vue';
|
||||
|
||||
import { useEventEmitter } from './event.js';
|
||||
|
||||
/**
|
||||
* Provides a reactive `isEditing` property that reflects the current editing state of the
|
||||
* application.
|
||||
* @param {OpenMCT} openmct the Open MCT API
|
||||
* @returns {{
|
||||
* isEditing: import('vue').Ref<boolean>
|
||||
* }}
|
||||
*/
|
||||
export function useIsEditing(openmct) {
|
||||
const isEditing = ref(openmct.editor.isEditing());
|
||||
|
||||
useEventEmitter(openmct.editor, 'isEditing', (_isEditing) => {
|
||||
isEditing.value = _isEditing;
|
||||
});
|
||||
|
||||
return { isEditing };
|
||||
}
|
||||
@@ -105,7 +105,13 @@
|
||||
class="l-shell__tree"
|
||||
/>
|
||||
</pane>
|
||||
<pane handle="before" label="Recently Viewed" :persist-position="true">
|
||||
<pane
|
||||
handle="before"
|
||||
label="Recently Viewed"
|
||||
:persist-position="true"
|
||||
collapse-type="horizontal"
|
||||
hide-param="hideRecents"
|
||||
>
|
||||
<RecentObjectsList
|
||||
ref="recentObjectsList"
|
||||
class="l-shell__tree"
|
||||
|
||||
@@ -21,7 +21,13 @@
|
||||
-->
|
||||
<template>
|
||||
<div class="l-pane" :class="paneClasses">
|
||||
<div v-if="handle" class="l-pane__handle" @mousedown.prevent="startResizing"></div>
|
||||
<div
|
||||
v-if="handle"
|
||||
class="l-pane__handle"
|
||||
:aria-label="handleLabel"
|
||||
:aria-grabbed="resizing"
|
||||
@mousedown.prevent="startResizing"
|
||||
></div>
|
||||
<div class="l-pane__header">
|
||||
<span v-if="label" class="l-pane__label">{{ label }}</span>
|
||||
<slot name="controls"></slot>
|
||||
@@ -56,6 +62,10 @@ const LOCAL_STORAGE_KEY__PANE_POSITIONS = 'mct-pane-positions';
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
collapseType: {
|
||||
type: String,
|
||||
default: 'vertical'
|
||||
},
|
||||
handle: {
|
||||
type: String,
|
||||
default: '',
|
||||
@@ -84,6 +94,9 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
handleLabel() {
|
||||
return `Resize ${this.label} Pane`;
|
||||
},
|
||||
isCollapsable() {
|
||||
return this.hideParam?.length > 0;
|
||||
},
|
||||
@@ -107,6 +120,7 @@ export default {
|
||||
'l-pane--vertical-handle-before': this.type === 'vertical' && this.handle === 'before',
|
||||
'l-pane--vertical-handle-after': this.type === 'vertical' && this.handle === 'after',
|
||||
'l-pane--collapsed': this.collapsed,
|
||||
'collapse-horizontal': this.collapseType === 'horizontal',
|
||||
'l-pane--reacts': !this.handle,
|
||||
'l-pane--resizing': this.resizing === true
|
||||
};
|
||||
@@ -177,7 +191,9 @@ export default {
|
||||
this.collapsed = true;
|
||||
},
|
||||
handleExpand() {
|
||||
this.$el.style[this.styleProp] = this.currentSize;
|
||||
let size = this.currentSize ? this.currentSize : this.getSavedPosition();
|
||||
this.$el.style[this.styleProp] = size;
|
||||
|
||||
delete this.currentSize;
|
||||
delete this.dragCollapse;
|
||||
this.collapsed = false;
|
||||
|
||||
@@ -171,7 +171,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__head,
|
||||
//&__head,
|
||||
&__pane-inspector {
|
||||
body.mobile & {
|
||||
display: none;
|
||||
@@ -189,6 +189,18 @@
|
||||
margin-bottom: $interiorMargin; // Needs some additional visual separation
|
||||
}
|
||||
|
||||
&__head {
|
||||
body.mobile & {
|
||||
.c-create-button,
|
||||
.c-create-menu,
|
||||
.c-indicator,
|
||||
.c-icon-button {
|
||||
// Making status area visible, but hiding some widgets for mobile.
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.mobile & .l-shell__main-view-browse-bar {
|
||||
margin-left: $mobileMenuIconD; // Make room for the hamburger!
|
||||
.c-button[class*='__actions__edit'] {
|
||||
@@ -241,7 +253,20 @@
|
||||
border-left: $brdr;
|
||||
align-items: start;
|
||||
$p: $interiorMarginSm;
|
||||
padding-left: $p; padding-right: $p;
|
||||
padding-left: $p;
|
||||
padding-right: $p;
|
||||
}
|
||||
|
||||
&__create-button,
|
||||
&__app-logo {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
&__create-button {
|
||||
body.mobile & {
|
||||
// Fixes weird gap in mobile status area
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
&__indicators {
|
||||
@@ -254,7 +279,6 @@
|
||||
height: 24px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/******************************* MAIN AREA */
|
||||
@@ -355,14 +379,18 @@
|
||||
.is-editing {
|
||||
.l-shell__main-container {
|
||||
$m: 3px;
|
||||
box-shadow: $colorBodyBg 0 0 0 1px, $editUIAreaShdw;
|
||||
box-shadow:
|
||||
$colorBodyBg 0 0 0 1px,
|
||||
$editUIAreaShdw;
|
||||
margin-left: $m;
|
||||
margin-right: $m;
|
||||
top: $shellToolBarH + $shellMainBrowseBarH + $interiorMarginLg !important;
|
||||
|
||||
&[s-selected] {
|
||||
// Provide a clearer selection context articulation for the main edit area
|
||||
box-shadow: $colorBodyBg 0 0 0 1px, $editUIAreaShdwSelected;
|
||||
box-shadow:
|
||||
$colorBodyBg 0 0 0 1px,
|
||||
$editUIAreaShdwSelected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
transition: opacity 150ms ease;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
overflow: hidden; // Prevents toolbar from extending into Inspector
|
||||
|
||||
> * {
|
||||
min-width: 0 !important;
|
||||
@@ -93,6 +93,7 @@
|
||||
&__contents {
|
||||
flex: 1 1 100%;
|
||||
opacity: 1;
|
||||
overflow: hidden;
|
||||
pointer-events: inherit;
|
||||
transition: opacity 250ms ease 250ms;
|
||||
|
||||
@@ -146,7 +147,9 @@
|
||||
&:before {
|
||||
// '+' icon
|
||||
font-size: 0.8em;
|
||||
margin-bottom: $interiorMarginSm;
|
||||
margin-bottom: $interiorMarginSm; // margin-bottom is needed for Tree and Inspector
|
||||
margin-right: $interiorMarginSm; // margin-right and margin-left are needed for Recent Objects
|
||||
margin-left: $interiorMarginSm;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@@ -191,6 +194,36 @@
|
||||
}
|
||||
}
|
||||
|
||||
&[class*='--collapsed'] { // For Recent Objects Button
|
||||
&.collapse-horizontal {
|
||||
[class*='expand-button'] {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
border-top-right-radius: $controlCr;
|
||||
border-top-left-radius: $controlCr;
|
||||
}
|
||||
}
|
||||
[class*='expand-button'] {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
padding: $interiorMarginSm 1px;
|
||||
font-size: 11px;
|
||||
|
||||
[class*='label'] {
|
||||
text-orientation: mixed;
|
||||
text-transform: uppercase;
|
||||
writing-mode: horizontal-tb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&[class*='--horizontal'] {
|
||||
> .l-pane__handle {
|
||||
cursor: col-resize;
|
||||
@@ -256,7 +289,7 @@
|
||||
}
|
||||
|
||||
/************************** Horizontal Splitter After */
|
||||
// Example: Tree pane
|
||||
// Example: Tree pane and Recent Objects
|
||||
&[class*='-after'] {
|
||||
margin-right: nth($shellPanePad, 2);
|
||||
padding-right: nth($shellPanePad, 2);
|
||||
@@ -292,7 +325,7 @@
|
||||
}
|
||||
|
||||
/************************** Vertical Splitter Before */
|
||||
// Pane collapses downward. Used by Elements pool in Inspector
|
||||
// Pane collapses downward. Used by Recent Objects in Tree
|
||||
&[class*='-before'] {
|
||||
$m: $interiorMarginLg;
|
||||
margin-top: $m;
|
||||
@@ -303,7 +336,7 @@
|
||||
}
|
||||
|
||||
.l-pane__collapse-button:before {
|
||||
content: $glyph-icon-arrow-down;
|
||||
content: $glyph-icon-line-horz;
|
||||
}
|
||||
|
||||
&.l-pane--collapsed {
|
||||
|
||||
@@ -27,6 +27,11 @@
|
||||
aria-label="Search Results Dropdown"
|
||||
class="c-gsearch__dropdown"
|
||||
>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="c-gsearch__results-close c-click-icon c-overlay__close-button icon-x"
|
||||
@click="selectedResult"
|
||||
></button>
|
||||
<div class="c-gsearch__results" :class="{ 'search-finished': !searchLoading }">
|
||||
<div
|
||||
v-if="objectResults?.length"
|
||||
|
||||
@@ -28,6 +28,19 @@
|
||||
background: rgba($colorHeadFg, 0.2);
|
||||
box-shadow: none;
|
||||
flex: 1 1 auto;
|
||||
body.mobile & {
|
||||
@include phonePortrait() {
|
||||
// This logic of expanding the search input upon click only happens in mobile portrait mode
|
||||
background: $colorHeadBg;
|
||||
width: 15%;
|
||||
&:hover {
|
||||
// When clicked, expand the search bar
|
||||
background-color: rgba($colorHeadFg, 0.2);
|
||||
width: 50vw;
|
||||
transition: width 120ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +53,17 @@
|
||||
max-height: 500px;
|
||||
top: $formInputH;
|
||||
z-index: 60;
|
||||
body.mobile & {
|
||||
// Makes search in mobile look less like an overlay, and more fullscreen.
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
left: -12px;
|
||||
height: calc(100vh - 22px);
|
||||
min-width: 100vw;
|
||||
max-height: none;
|
||||
border-radius: 0px;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__results,
|
||||
@@ -55,6 +79,10 @@
|
||||
> * + * {
|
||||
margin-top: $interiorMarginLg;
|
||||
}
|
||||
body.mobile & {
|
||||
// Add a margin to results so we have room for the close button
|
||||
width: 93%;
|
||||
}
|
||||
}
|
||||
|
||||
&__results-section {
|
||||
@@ -65,6 +93,7 @@
|
||||
|
||||
&__results-section-title {
|
||||
@include propertiesHeader();
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
&__result-pane-msg {
|
||||
@@ -72,6 +101,22 @@
|
||||
margin-top: $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
||||
&__results-close {
|
||||
// Close button that appears for mobile only
|
||||
display: none;
|
||||
body.mobile & {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
body.mobile & {
|
||||
width: 50vw;
|
||||
@include phonePortrait() {
|
||||
// This logic only appears for a mobile portrait mode
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-gsearch-result {
|
||||
@@ -113,9 +158,9 @@
|
||||
}
|
||||
|
||||
.c-location {
|
||||
//color: $colorBodyFg;
|
||||
//font-size: 0.9em;
|
||||
//opacity: 0.8;
|
||||
color: $colorBodyFg;
|
||||
font-size: 0.9em;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import { isProxy, toRaw } from 'vue';
|
||||
|
||||
import { isIdentifier } from '@/api/objects/object-utils';
|
||||
import StalenessUtils from '@/utils/staleness';
|
||||
|
||||
export default {
|
||||
@@ -40,7 +41,9 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
getSubscriptionId(domainObject) {
|
||||
return this.openmct?.objects.makeKeyString(domainObject.identifier);
|
||||
// Only extract the identifier if it is not already an identifier
|
||||
const identifier = isIdentifier(domainObject) ? domainObject : domainObject.identifier;
|
||||
return this.openmct?.objects.makeKeyString(identifier);
|
||||
},
|
||||
setupClockChangedEvent(callback) {
|
||||
this.setupClockChanged = true;
|
||||
@@ -97,6 +100,7 @@ export default {
|
||||
if (isProxy(domainObject)) {
|
||||
domainObject = toRaw(domainObject);
|
||||
}
|
||||
|
||||
const id = this.getSubscriptionId(domainObject);
|
||||
if (!this.stalenessSubscription[id]) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user