Compare commits
	
		
			9 Commits
		
	
	
		
			import-per
			...
			cs-fix
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					5092ba8763 | ||
| 
						 | 
					1d5ddc545e | ||
| 
						 | 
					42085a4b70 | ||
| 
						 | 
					b2b0837592 | ||
| 
						 | 
					e305b46d88 | ||
| 
						 | 
					fb396ac194 | ||
| 
						 | 
					a01f21017f | ||
| 
						 | 
					b7b9ccbe65 | ||
| 
						 | 
					f189a4d602 | 
@@ -10,6 +10,7 @@ executors:
 | 
			
		||||
      NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
 | 
			
		||||
      PERCY_POSTINSTALL_BROWSER: "true" # Needed to store the percy browser in cache deps
 | 
			
		||||
      PERCY_LOGLEVEL: "debug" # Enable DEBUG level logging for Percy (Issue: https://github.com/nasa/openmct/issues/5742)
 | 
			
		||||
      PERCY_PARALLEL_TOTAL: 2
 | 
			
		||||
  ubuntu:
 | 
			
		||||
    machine:
 | 
			
		||||
      image: ubuntu-2204:current
 | 
			
		||||
@@ -220,15 +221,20 @@ jobs:
 | 
			
		||||
            equal: [42, 42] # Always run codecov reports regardless of test failure https://discuss.circleci.com/t/make-custom-command-run-always-with-when-always/38957/2
 | 
			
		||||
          steps:
 | 
			
		||||
            - generate_and_store_version_and_filesystem_artifacts
 | 
			
		||||
  visual-a11y-tests:
 | 
			
		||||
  visual-a11y:
 | 
			
		||||
    parameters:
 | 
			
		||||
      suite:
 | 
			
		||||
        type: string # ci or full
 | 
			
		||||
    executor: pw-focal-development
 | 
			
		||||
    parallelism: 2
 | 
			
		||||
    steps:
 | 
			
		||||
      - build_and_install:
 | 
			
		||||
          node-version: lts/hydrogen
 | 
			
		||||
      - run: npm run test:e2e:visual:<<parameters.suite>>
 | 
			
		||||
          node-version: lts/iron
 | 
			
		||||
      - run:
 | 
			
		||||
          command: |
 | 
			
		||||
            mkdir test-results
 | 
			
		||||
            TESTFILES=$(circleci tests glob "e2e/**/*.spec.js")
 | 
			
		||||
            echo "$TESTFILES" | circleci tests run --command="xargs npm run test:e2e:visual:<<parameters.suite>>" --verbose --split-by=timings
 | 
			
		||||
      - store_test_results:
 | 
			
		||||
          path: test-results/results.xml
 | 
			
		||||
      - store_artifacts:
 | 
			
		||||
@@ -254,8 +260,8 @@ workflows:
 | 
			
		||||
          name: e2e-stable
 | 
			
		||||
          suite: stable
 | 
			
		||||
      - e2e-mobile
 | 
			
		||||
      - visual-a11y-tests:
 | 
			
		||||
          name: visual-a11y-test-ci
 | 
			
		||||
      - visual-a11y:
 | 
			
		||||
          name: visual-a11y-ci
 | 
			
		||||
          suite: ci
 | 
			
		||||
 | 
			
		||||
  the-nightly: #These jobs do not run on PRs, but against master at night
 | 
			
		||||
@@ -274,8 +280,8 @@ workflows:
 | 
			
		||||
      - e2e-mobile
 | 
			
		||||
      - perf-test
 | 
			
		||||
      - mem-test
 | 
			
		||||
      - visual-a11y-tests:
 | 
			
		||||
          name: visual-a11y-test-nightly
 | 
			
		||||
      - visual-a11y:
 | 
			
		||||
          name: visual-a11y-nightly
 | 
			
		||||
          suite: full
 | 
			
		||||
      - e2e-couchdb
 | 
			
		||||
    triggers:
 | 
			
		||||
 
 | 
			
		||||
@@ -392,7 +392,8 @@ async function setTimeConductorMode(page, isFixedTimespan = true) {
 | 
			
		||||
    await page.getByRole('menuitem', { name: /Real-Time/ }).click();
 | 
			
		||||
    await page.waitForURL(/tc\.mode=local/);
 | 
			
		||||
  }
 | 
			
		||||
  await page.getByLabel('Submit time offsets').or(page.getByLabel('Submit time bounds')).click();
 | 
			
		||||
  //dismiss the time conductor popup
 | 
			
		||||
  await page.getByLabel('Discard changes and close time popup').click();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
@@ -457,4 +457,11 @@ test.describe('Basic Condition Set Use', () => {
 | 
			
		||||
 | 
			
		||||
    await page.goto(exampleTelemetry.url);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test.fixme('Ensure condition sets work with telemetry like operator status', ({ page }) => {
 | 
			
		||||
    test.info().annotations.push({
 | 
			
		||||
      type: 'issue',
 | 
			
		||||
      description: 'https://github.com/nasa/openmct/issues/7484'
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,64 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2024, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 | 
			
		||||
 * "License"); you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0.
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
 * License for the specific language governing permissions and limitations
 | 
			
		||||
 * under the License.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT includes source code licensed under additional open source
 | 
			
		||||
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  createDomainObjectWithDefaults,
 | 
			
		||||
  setIndependentTimeConductorBounds
 | 
			
		||||
} from '../../../../appActions.js';
 | 
			
		||||
import { expect, test } from '../../../../pluginFixtures.js';
 | 
			
		||||
 | 
			
		||||
const FIXED_TIME =
 | 
			
		||||
  './#/browse/mine?tc.mode=fixed&tc.startBound=1693592063607&tc.endBound=1693593893607&tc.timeSystem=utc&view=grid&hideInspector=true&hideTree=true';
 | 
			
		||||
test.describe('Datepicker operations', () => {
 | 
			
		||||
  test.beforeEach(async ({ page }) => {
 | 
			
		||||
    await page.goto(FIXED_TIME);
 | 
			
		||||
  });
 | 
			
		||||
  test('Verify that user can use the datepicker in the TC', async ({ page }) => {
 | 
			
		||||
    await page.getByLabel('Time Conductor Mode').click();
 | 
			
		||||
    // Click on the date picker that is left-most on the screen
 | 
			
		||||
    await page.getByLabel('Global Time Conductor').locator('a').first().click();
 | 
			
		||||
    await expect(page.getByRole('dialog')).toBeVisible();
 | 
			
		||||
    // Click on the first cell
 | 
			
		||||
    await page.getByText('27 239').click();
 | 
			
		||||
    // Expect datepicker to close and time conductor date setting to be changed
 | 
			
		||||
    await expect(page.getByRole('dialog')).toHaveCount(0);
 | 
			
		||||
  });
 | 
			
		||||
  test('Verify that user can use the datepicker in the ITC', async ({ page }) => {
 | 
			
		||||
    const createdTimeList = await createDomainObjectWithDefaults(page, { type: 'Time List' });
 | 
			
		||||
 | 
			
		||||
    await page.goto(createdTimeList.url, { waitUntil: 'domcontentloaded' });
 | 
			
		||||
 | 
			
		||||
    await setIndependentTimeConductorBounds(page, {
 | 
			
		||||
      start: '2024-11-12 19:11:11.000Z',
 | 
			
		||||
      end: '2024-11-12 20:11:11.000Z'
 | 
			
		||||
    });
 | 
			
		||||
    // Open ITC
 | 
			
		||||
    await page.getByLabel('Start bounds').nth(0).click();
 | 
			
		||||
    // Click on the datepicker icon
 | 
			
		||||
    await page.locator('form a').first().click();
 | 
			
		||||
    await expect(page.getByRole('dialog')).toBeVisible();
 | 
			
		||||
    // Click on the first cell
 | 
			
		||||
    await page.getByText('7 342').click();
 | 
			
		||||
    // Expect datepicker to close and time conductor date setting to be changed
 | 
			
		||||
    await expect(page.getByRole('dialog')).toHaveCount(0);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@@ -72,11 +72,29 @@ test.describe('Visual - Planning', () => {
 | 
			
		||||
      name: 'Plan Visual Test',
 | 
			
		||||
      json: examplePlanSmall2
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    await setBoundsToSpanAllActivities(page, examplePlanSmall2, plan.url);
 | 
			
		||||
    await percySnapshot(page, `Plan View (theme: ${theme})`);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('Resize Plan View @2p', async ({ browser, theme }) => {
 | 
			
		||||
    // need to set viewport to null to allow for resizing
 | 
			
		||||
    const newContext = await browser.newContext({
 | 
			
		||||
      viewport: null
 | 
			
		||||
    });
 | 
			
		||||
    const newPage = await newContext.newPage();
 | 
			
		||||
 | 
			
		||||
    await newPage.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
 | 
			
		||||
    const plan = await createPlanFromJSON(newPage, {
 | 
			
		||||
      name: 'Plan Visual Test',
 | 
			
		||||
      json: examplePlanSmall2
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    await setBoundsToSpanAllActivities(newPage, examplePlanSmall2, plan.url);
 | 
			
		||||
    // resize the window
 | 
			
		||||
    await newPage.setViewportSize({ width: 800, height: 600 });
 | 
			
		||||
    await percySnapshot(newPage, `Plan View resized (theme: ${theme})`);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('Plan View w/ draft status', async ({ page, theme }) => {
 | 
			
		||||
    const plan = await createPlanFromJSON(page, {
 | 
			
		||||
      name: 'Plan Visual Test (Draft)',
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -21,7 +21,7 @@
 | 
			
		||||
        "@types/d3-shape": "3.0.0",
 | 
			
		||||
        "@types/eventemitter3": "1.2.0",
 | 
			
		||||
        "@types/jasmine": "5.1.2",
 | 
			
		||||
        "@types/lodash": "4.14.192",
 | 
			
		||||
        "@types/lodash": "4.17.0",
 | 
			
		||||
        "@vue/compiler-sfc": "3.4.3",
 | 
			
		||||
        "babel-loader": "9.1.0",
 | 
			
		||||
        "babel-plugin-istanbul": "6.1.1",
 | 
			
		||||
@@ -1821,9 +1821,9 @@
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/lodash": {
 | 
			
		||||
      "version": "4.14.192",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.192.tgz",
 | 
			
		||||
      "integrity": "sha512-km+Vyn3BYm5ytMO13k9KTp27O75rbQ0NFw+U//g+PX7VZyjCioXaRFisqSIJRECljcTv73G3i6BpglNGHgUQ5A==",
 | 
			
		||||
      "version": "4.17.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz",
 | 
			
		||||
      "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/mime": {
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@
 | 
			
		||||
    "@types/d3-selection": "3.0.10",
 | 
			
		||||
    "@types/eventemitter3": "1.2.0",
 | 
			
		||||
    "@types/jasmine": "5.1.2",
 | 
			
		||||
    "@types/lodash": "4.14.192",
 | 
			
		||||
    "@types/lodash": "4.17.0",
 | 
			
		||||
    "@vue/compiler-sfc": "3.4.3",
 | 
			
		||||
    "babel-loader": "9.1.0",
 | 
			
		||||
    "babel-plugin-istanbul": "6.1.1",
 | 
			
		||||
 
 | 
			
		||||
@@ -56,20 +56,38 @@ export default class ConditionManager extends EventEmitter {
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  subscribeToTelemetry(endpoint) {
 | 
			
		||||
    const id = this.openmct.objects.makeKeyString(endpoint.identifier);
 | 
			
		||||
    if (this.subscriptions[id]) {
 | 
			
		||||
      console.log('subscription already exists');
 | 
			
		||||
  async requestLatestValue(endpoint) {
 | 
			
		||||
    const options = {
 | 
			
		||||
      size: 1,
 | 
			
		||||
      strategy: 'latest'
 | 
			
		||||
    };
 | 
			
		||||
    const latestData = await this.openmct.telemetry.request(endpoint, options);
 | 
			
		||||
 | 
			
		||||
    if (!latestData) {
 | 
			
		||||
      throw new Error('Telemetry request failed by returning a falsy response');
 | 
			
		||||
    }
 | 
			
		||||
    if (latestData.length === 0) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    this.telemetryReceived(endpoint, latestData[0]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  subscribeToTelemetry(endpoint) {
 | 
			
		||||
    const telemetryKeyString = this.openmct.objects.makeKeyString(endpoint.identifier);
 | 
			
		||||
    if (this.subscriptions[telemetryKeyString]) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const metadata = this.openmct.telemetry.getMetadata(endpoint);
 | 
			
		||||
 | 
			
		||||
    this.telemetryObjects[id] = Object.assign({}, endpoint, {
 | 
			
		||||
    this.telemetryObjects[telemetryKeyString] = Object.assign({}, endpoint, {
 | 
			
		||||
      telemetryMetaData: metadata ? metadata.valueMetadatas : []
 | 
			
		||||
    });
 | 
			
		||||
    this.subscriptions[id] = this.openmct.telemetry.subscribe(
 | 
			
		||||
 | 
			
		||||
    // get latest telemetry value (in case subscription is cached and no new data is coming in)
 | 
			
		||||
    this.requestLatestValue(endpoint);
 | 
			
		||||
 | 
			
		||||
    this.subscriptions[telemetryKeyString] = this.openmct.telemetry.subscribe(
 | 
			
		||||
      endpoint,
 | 
			
		||||
      this.telemetryReceived.bind(this, endpoint)
 | 
			
		||||
    );
 | 
			
		||||
@@ -91,7 +109,7 @@ export default class ConditionManager extends EventEmitter {
 | 
			
		||||
 | 
			
		||||
    //force re-computation of condition set result as we might be in a state where
 | 
			
		||||
    // there is no telemetry datum coming in for a while or at all.
 | 
			
		||||
    let latestTimestamp = getLatestTimestamp(
 | 
			
		||||
    const latestTimestamp = getLatestTimestamp(
 | 
			
		||||
      {},
 | 
			
		||||
      {},
 | 
			
		||||
      this.timeSystems,
 | 
			
		||||
@@ -334,57 +352,54 @@ export default class ConditionManager extends EventEmitter {
 | 
			
		||||
    return currentCondition;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  requestLADConditionSetOutput(options) {
 | 
			
		||||
  async requestLADConditionSetOutput(options) {
 | 
			
		||||
    if (!this.conditions.length) {
 | 
			
		||||
      return Promise.resolve([]);
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return this.compositionLoad.then(() => {
 | 
			
		||||
      let latestTimestamp;
 | 
			
		||||
      let conditionResults = {};
 | 
			
		||||
      let nextLegOptions = { ...options };
 | 
			
		||||
      delete nextLegOptions.onPartialResponse;
 | 
			
		||||
    await this.compositionLoad;
 | 
			
		||||
 | 
			
		||||
      const conditionRequests = this.conditions.map((condition) =>
 | 
			
		||||
        condition.requestLADConditionResult(nextLegOptions)
 | 
			
		||||
    let latestTimestamp;
 | 
			
		||||
    let conditionResults = {};
 | 
			
		||||
    let nextLegOptions = { ...options };
 | 
			
		||||
    delete nextLegOptions.onPartialResponse;
 | 
			
		||||
 | 
			
		||||
    const results = await Promise.all(
 | 
			
		||||
      this.conditions.map((condition) => condition.requestLADConditionResult(nextLegOptions))
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    results.forEach((resultObj) => {
 | 
			
		||||
      const {
 | 
			
		||||
        id,
 | 
			
		||||
        data,
 | 
			
		||||
        data: { result }
 | 
			
		||||
      } = resultObj;
 | 
			
		||||
 | 
			
		||||
      if (this.findConditionById(id)) {
 | 
			
		||||
        conditionResults[id] = Boolean(result);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      latestTimestamp = getLatestTimestamp(
 | 
			
		||||
        latestTimestamp,
 | 
			
		||||
        data,
 | 
			
		||||
        this.timeSystems,
 | 
			
		||||
        this.openmct.time.timeSystem()
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      return Promise.all(conditionRequests).then((results) => {
 | 
			
		||||
        results.forEach((resultObj) => {
 | 
			
		||||
          const {
 | 
			
		||||
            id,
 | 
			
		||||
            data,
 | 
			
		||||
            data: { result }
 | 
			
		||||
          } = resultObj;
 | 
			
		||||
          if (this.findConditionById(id)) {
 | 
			
		||||
            conditionResults[id] = Boolean(result);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          latestTimestamp = getLatestTimestamp(
 | 
			
		||||
            latestTimestamp,
 | 
			
		||||
            data,
 | 
			
		||||
            this.timeSystems,
 | 
			
		||||
            this.openmct.time.timeSystem()
 | 
			
		||||
          );
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (!Object.values(latestTimestamp).some((timeSystem) => timeSystem)) {
 | 
			
		||||
          return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const currentCondition = this.getCurrentConditionLAD(conditionResults);
 | 
			
		||||
        const currentOutput = Object.assign(
 | 
			
		||||
          {
 | 
			
		||||
            output: currentCondition.configuration.output,
 | 
			
		||||
            id: this.conditionSetDomainObject.identifier,
 | 
			
		||||
            conditionId: currentCondition.id
 | 
			
		||||
          },
 | 
			
		||||
          latestTimestamp
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        return [currentOutput];
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (!Object.values(latestTimestamp).some((timeSystem) => timeSystem)) {
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const currentCondition = this.getCurrentConditionLAD(conditionResults);
 | 
			
		||||
    const currentOutput = {
 | 
			
		||||
      output: currentCondition.configuration.output,
 | 
			
		||||
      id: this.conditionSetDomainObject.identifier,
 | 
			
		||||
      conditionId: currentCondition.id,
 | 
			
		||||
      ...latestTimestamp
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return [currentOutput];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isTelemetryUsed(endpoint) {
 | 
			
		||||
@@ -409,7 +424,7 @@ export default class ConditionManager extends EventEmitter {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const normalizedDatum = this.createNormalizedDatum(datum, endpoint);
 | 
			
		||||
    const timeSystemKey = this.openmct.time.timeSystem().key;
 | 
			
		||||
    const timeSystemKey = this.openmct.time.getTimeSystem().key;
 | 
			
		||||
    let timestamp = {};
 | 
			
		||||
    const currentTimestamp = normalizedDatum[timeSystemKey];
 | 
			
		||||
    timestamp[timeSystemKey] = currentTimestamp;
 | 
			
		||||
 
 | 
			
		||||
@@ -40,12 +40,10 @@ export default class ConditionSetTelemetryProvider {
 | 
			
		||||
    return domainObject.type === 'conditionSet';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  request(domainObject, options) {
 | 
			
		||||
  async request(domainObject, options) {
 | 
			
		||||
    let conditionManager = this.getConditionManager(domainObject);
 | 
			
		||||
 | 
			
		||||
    return conditionManager.requestLADConditionSetOutput(options).then((latestOutput) => {
 | 
			
		||||
      return latestOutput;
 | 
			
		||||
    });
 | 
			
		||||
    let latestOutput = await conditionManager.requestLADConditionSetOutput(options);
 | 
			
		||||
    return latestOutput;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  subscribe(domainObject, callback) {
 | 
			
		||||
 
 | 
			
		||||
@@ -66,7 +66,8 @@ describe('the plugin', function () {
 | 
			
		||||
            format: 'utc',
 | 
			
		||||
            hints: {
 | 
			
		||||
              domain: 1
 | 
			
		||||
            }
 | 
			
		||||
            },
 | 
			
		||||
            source: 'utc'
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            key: 'testSource',
 | 
			
		||||
@@ -720,6 +721,23 @@ describe('the plugin', function () {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should evaluate as old when telemetry is not received in the allotted time', (done) => {
 | 
			
		||||
      openmct.telemetry = jasmine.createSpyObj('telemetry', [
 | 
			
		||||
        'subscribe',
 | 
			
		||||
        'getMetadata',
 | 
			
		||||
        'request',
 | 
			
		||||
        'getValueFormatter',
 | 
			
		||||
        'abortAllRequests'
 | 
			
		||||
      ]);
 | 
			
		||||
      openmct.telemetry.getMetadata.and.returnValue({
 | 
			
		||||
        ...testTelemetryObject.telemetry,
 | 
			
		||||
        valueMetadatas: []
 | 
			
		||||
      });
 | 
			
		||||
      openmct.telemetry.request.and.returnValue(Promise.resolve([]));
 | 
			
		||||
      openmct.telemetry.getValueFormatter.and.returnValue({
 | 
			
		||||
        parse: function (value) {
 | 
			
		||||
          return value;
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
 | 
			
		||||
      conditionMgr.on('conditionSetResultUpdated', mockListener);
 | 
			
		||||
      conditionMgr.telemetryObjects = {
 | 
			
		||||
@@ -741,6 +759,20 @@ describe('the plugin', function () {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should not evaluate as old when telemetry is received in the allotted time', (done) => {
 | 
			
		||||
      openmct.telemetry.getMetadata = jasmine.createSpy('getMetadata');
 | 
			
		||||
      openmct.telemetry.getMetadata.and.returnValue({
 | 
			
		||||
        ...testTelemetryObject.telemetry,
 | 
			
		||||
        valueMetadatas: testTelemetryObject.telemetry.values
 | 
			
		||||
      });
 | 
			
		||||
      const testDatum = {
 | 
			
		||||
        'some-key2': '',
 | 
			
		||||
        utc: 1,
 | 
			
		||||
        testSource: '',
 | 
			
		||||
        'some-key': null,
 | 
			
		||||
        id: 'test-object'
 | 
			
		||||
      };
 | 
			
		||||
      openmct.telemetry.request = jasmine.createSpy('request');
 | 
			
		||||
      openmct.telemetry.request.and.returnValue(Promise.resolve([testDatum]));
 | 
			
		||||
      const date = 1;
 | 
			
		||||
      conditionSetDomainObject.configuration.conditionCollection[0].configuration.criteria[0].input =
 | 
			
		||||
        ['0.4'];
 | 
			
		||||
@@ -750,9 +782,7 @@ describe('the plugin', function () {
 | 
			
		||||
        'test-object': testTelemetryObject
 | 
			
		||||
      };
 | 
			
		||||
      conditionMgr.updateConditionTelemetryObjects();
 | 
			
		||||
      conditionMgr.telemetryReceived(testTelemetryObject, {
 | 
			
		||||
        utc: date
 | 
			
		||||
      });
 | 
			
		||||
      conditionMgr.telemetryReceived(testTelemetryObject, testDatum);
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        expect(mockListener).toHaveBeenCalledWith({
 | 
			
		||||
          output: 'Default',
 | 
			
		||||
@@ -868,6 +898,12 @@ describe('the plugin', function () {
 | 
			
		||||
    it('should stop evaluating conditions when a condition evaluates to true', () => {
 | 
			
		||||
      const date = Date.now();
 | 
			
		||||
      let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
 | 
			
		||||
 | 
			
		||||
      openmct.telemetry.getMetadata = jasmine.createSpy('getMetadata');
 | 
			
		||||
      openmct.telemetry.getMetadata.and.returnValue({
 | 
			
		||||
        ...testTelemetryObject.telemetry,
 | 
			
		||||
        valueMetadatas: []
 | 
			
		||||
      });
 | 
			
		||||
      conditionMgr.on('conditionSetResultUpdated', mockListener);
 | 
			
		||||
      conditionMgr.telemetryObjects = {
 | 
			
		||||
        'test-object': testTelemetryObject
 | 
			
		||||
 
 | 
			
		||||
@@ -150,16 +150,15 @@ export default class ImportAsJSONAction {
 | 
			
		||||
   * @param {string} namespace
 | 
			
		||||
   * @returns {object}
 | 
			
		||||
   */
 | 
			
		||||
  _generateNewIdentifiers(tree, namespace) {
 | 
			
		||||
  _generateNewIdentifiers(tree, newNamespace) {
 | 
			
		||||
    // For each domain object in the file, generate new ID, replace in tree
 | 
			
		||||
    Object.keys(tree.openmct).forEach((domainObjectId) => {
 | 
			
		||||
      const newId = {
 | 
			
		||||
        namespace,
 | 
			
		||||
        key: uuid()
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      const oldId = parseKeyString(domainObjectId);
 | 
			
		||||
 | 
			
		||||
      const newId = {
 | 
			
		||||
        namespace: newNamespace,
 | 
			
		||||
        key: uuid()
 | 
			
		||||
      };
 | 
			
		||||
      tree = this._rewriteId(oldId, newId, tree);
 | 
			
		||||
    }, this);
 | 
			
		||||
 | 
			
		||||
@@ -228,22 +227,32 @@ export default class ImportAsJSONAction {
 | 
			
		||||
  _rewriteId(oldId, newId, tree) {
 | 
			
		||||
    let newIdKeyString = this.openmct.objects.makeKeyString(newId);
 | 
			
		||||
    let oldIdKeyString = this.openmct.objects.makeKeyString(oldId);
 | 
			
		||||
    tree = JSON.stringify(tree).replace(new RegExp(oldIdKeyString, 'g'), newIdKeyString);
 | 
			
		||||
 | 
			
		||||
    return JSON.parse(tree, (key, value) => {
 | 
			
		||||
    const newTreeString = JSON.stringify(tree).replace(
 | 
			
		||||
      new RegExp(oldIdKeyString, 'g'),
 | 
			
		||||
      newIdKeyString
 | 
			
		||||
    );
 | 
			
		||||
    const newTree = JSON.parse(newTreeString, (key, value) => {
 | 
			
		||||
      if (
 | 
			
		||||
        value !== undefined &&
 | 
			
		||||
        value !== null &&
 | 
			
		||||
        Object.prototype.hasOwnProperty.call(value, 'key') &&
 | 
			
		||||
        Object.prototype.hasOwnProperty.call(value, 'namespace') &&
 | 
			
		||||
        value.key === oldId.key &&
 | 
			
		||||
        value.namespace === oldId.namespace
 | 
			
		||||
        Object.prototype.hasOwnProperty.call(value, 'namespace')
 | 
			
		||||
      ) {
 | 
			
		||||
        return newId;
 | 
			
		||||
      } else {
 | 
			
		||||
        return value;
 | 
			
		||||
        // first check if key is messed up from regex and contains a colon
 | 
			
		||||
        // if it does, repair it
 | 
			
		||||
        if (value.key.includes(':')) {
 | 
			
		||||
          const splitKey = value.key.split(':');
 | 
			
		||||
          value.key = splitKey[1];
 | 
			
		||||
          value.namespace = splitKey[0];
 | 
			
		||||
        }
 | 
			
		||||
        // now check if we need to replace the id
 | 
			
		||||
        if (value.key === oldId.key && value.namespace === oldId.namespace) {
 | 
			
		||||
          return newId;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return value;
 | 
			
		||||
    });
 | 
			
		||||
    return newTree;
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   * @private
 | 
			
		||||
 
 | 
			
		||||
@@ -135,11 +135,75 @@ describe('The import JSON action', function () {
 | 
			
		||||
        selectFile: {
 | 
			
		||||
          name: 'imported object',
 | 
			
		||||
          // eslint-disable-next-line prettier/prettier
 | 
			
		||||
          body: "{\"openmct\":{\"c28d230d-e909-4a3e-9840-d9ef469dda70\":{\"identifier\":{\"key\":\"c28d230d-e909-4a3e-9840-d9ef469dda70\",\"namespace\":\"\"},\"name\":\"Unnamed Overlay Plot\",\"type\":\"telemetry.plot.overlay\",\"composition\":[],\"configuration\":{\"series\":[]},\"modified\":1695837546833,\"location\":\"mine\",\"created\":1695837546833,\"persisted\":1695837546833,\"__proto__\":{\"toString\":\"foobar\"}}},\"rootId\":\"c28d230d-e909-4a3e-9840-d9ef469dda70\"}"
 | 
			
		||||
          body: '{"openmct":{"c28d230d-e909-4a3e-9840-d9ef469dda70":{"identifier":{"key":"c28d230d-e909-4a3e-9840-d9ef469dda70","namespace":""},"name":"Unnamed Overlay Plot","type":"telemetry.plot.overlay","composition":[],"configuration":{"series":[]},"modified":1695837546833,"location":"mine","created":1695837546833,"persisted":1695837546833,"__proto__":{"toString":"foobar"}}},"rootId":"c28d230d-e909-4a3e-9840-d9ef469dda70"}'
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      return Promise.resolve(pollutedResponse);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  it('preserves the integrity of the namespace and key during import', async () => {
 | 
			
		||||
    const incomingObject = {
 | 
			
		||||
      openmct: {
 | 
			
		||||
        '7323f02a-06ac-438d-bd58-6d6e33b8741e': {
 | 
			
		||||
          name: 'Some Folder',
 | 
			
		||||
          type: 'folder',
 | 
			
		||||
          composition: [
 | 
			
		||||
            {
 | 
			
		||||
              key: '9f6c2d21-5ec8-434c-9fe8-31614ae6d7e6',
 | 
			
		||||
              namespace: ''
 | 
			
		||||
            }
 | 
			
		||||
          ],
 | 
			
		||||
          modified: 1710843256162,
 | 
			
		||||
          location: 'mine',
 | 
			
		||||
          created: 1710843243471,
 | 
			
		||||
          persisted: 1710843256162,
 | 
			
		||||
          identifier: {
 | 
			
		||||
            namespace: '',
 | 
			
		||||
            key: '7323f02a-06ac-438d-bd58-6d6e33b8741e'
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        '9f6c2d21-5ec8-434c-9fe8-31614ae6d7e6': {
 | 
			
		||||
          name: 'Some Clock',
 | 
			
		||||
          type: 'clock',
 | 
			
		||||
          configuration: {
 | 
			
		||||
            baseFormat: 'YYYY/MM/DD hh:mm:ss',
 | 
			
		||||
            use24: 'clock12',
 | 
			
		||||
            timezone: 'UTC'
 | 
			
		||||
          },
 | 
			
		||||
          modified: 1710843256152,
 | 
			
		||||
          location: '7323f02a-06ac-438d-bd58-6d6e33b8741e',
 | 
			
		||||
          created: 1710843256152,
 | 
			
		||||
          persisted: 1710843256152,
 | 
			
		||||
          identifier: {
 | 
			
		||||
            namespace: '',
 | 
			
		||||
            key: '9f6c2d21-5ec8-434c-9fe8-31614ae6d7e6'
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      rootId: '7323f02a-06ac-438d-bd58-6d6e33b8741e'
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const targetDomainObject = {
 | 
			
		||||
      identifier: {
 | 
			
		||||
        namespace: 'starJones',
 | 
			
		||||
        key: '84438cda-a071-48d1-b9bf-d77bd53e59ba'
 | 
			
		||||
      },
 | 
			
		||||
      type: 'folder'
 | 
			
		||||
    };
 | 
			
		||||
    spyOn(openmct.objects, 'save').and.callFake((model) => Promise.resolve(model));
 | 
			
		||||
    try {
 | 
			
		||||
      await importFromJSONAction.onSave(targetDomainObject, {
 | 
			
		||||
        selectFile: { body: JSON.stringify(incomingObject) }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      for (const callArgs of openmct.objects.save.calls.allArgs()) {
 | 
			
		||||
        const savedObject = callArgs[0]; // Assuming the first argument is the object being saved.
 | 
			
		||||
        expect(savedObject.identifier.key.includes(':')).toBeFalse(); // Ensure no colon in the key.
 | 
			
		||||
        expect(savedObject.identifier.namespace).toBe(targetDomainObject.identifier.namespace);
 | 
			
		||||
      }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      fail(error);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -169,6 +169,8 @@ describe('Notebook plugin:', () => {
 | 
			
		||||
 | 
			
		||||
      openmct.editor = {};
 | 
			
		||||
      openmct.editor.isEditing = () => false;
 | 
			
		||||
      openmct.editor.on = () => {};
 | 
			
		||||
      openmct.editor.off = () => {};
 | 
			
		||||
 | 
			
		||||
      const applicableViews = openmct.objectViews.get(notebookViewObject, [notebookViewObject]);
 | 
			
		||||
      notebookViewProvider = applicableViews.find(
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ import mount from 'utils/mount';
 | 
			
		||||
import TableConfigurationComponent from './components/TableConfiguration.vue';
 | 
			
		||||
import TelemetryTableConfiguration from './TelemetryTableConfiguration.js';
 | 
			
		||||
 | 
			
		||||
export default function TableConfigurationViewProvider(openmct) {
 | 
			
		||||
export default function TableConfigurationViewProvider(openmct, options) {
 | 
			
		||||
  return {
 | 
			
		||||
    key: 'table-configuration',
 | 
			
		||||
    name: 'Config',
 | 
			
		||||
@@ -45,7 +45,7 @@ export default function TableConfigurationViewProvider(openmct) {
 | 
			
		||||
 | 
			
		||||
      return {
 | 
			
		||||
        show: function (element) {
 | 
			
		||||
          tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct);
 | 
			
		||||
          tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct, options);
 | 
			
		||||
          const { destroy } = mount(
 | 
			
		||||
            {
 | 
			
		||||
              el: element,
 | 
			
		||||
 
 | 
			
		||||
@@ -32,14 +32,14 @@ import TelemetryTableRow from './TelemetryTableRow.js';
 | 
			
		||||
import TelemetryTableUnitColumn from './TelemetryTableUnitColumn.js';
 | 
			
		||||
 | 
			
		||||
export default class TelemetryTable extends EventEmitter {
 | 
			
		||||
  constructor(domainObject, openmct) {
 | 
			
		||||
  constructor(domainObject, openmct, options) {
 | 
			
		||||
    super();
 | 
			
		||||
 | 
			
		||||
    this.domainObject = domainObject;
 | 
			
		||||
    this.openmct = openmct;
 | 
			
		||||
    this.tableComposition = undefined;
 | 
			
		||||
    this.datumCache = [];
 | 
			
		||||
    this.configuration = new TelemetryTableConfiguration(domainObject, openmct);
 | 
			
		||||
    this.configuration = new TelemetryTableConfiguration(domainObject, openmct, options);
 | 
			
		||||
    this.telemetryMode = this.configuration.getTelemetryMode();
 | 
			
		||||
    this.rowLimit = this.configuration.getRowLimit();
 | 
			
		||||
    this.paused = false;
 | 
			
		||||
 
 | 
			
		||||
@@ -24,11 +24,12 @@ import EventEmitter from 'EventEmitter';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
export default class TelemetryTableConfiguration extends EventEmitter {
 | 
			
		||||
  constructor(domainObject, openmct) {
 | 
			
		||||
  constructor(domainObject, openmct, options) {
 | 
			
		||||
    super();
 | 
			
		||||
 | 
			
		||||
    this.domainObject = domainObject;
 | 
			
		||||
    this.openmct = openmct;
 | 
			
		||||
    this.defaultOptions = options;
 | 
			
		||||
    this.columns = {};
 | 
			
		||||
 | 
			
		||||
    this.removeColumnsForObject = this.removeColumnsForObject.bind(this);
 | 
			
		||||
@@ -48,10 +49,12 @@ export default class TelemetryTableConfiguration extends EventEmitter {
 | 
			
		||||
    configuration.columnOrder = configuration.columnOrder || [];
 | 
			
		||||
    configuration.cellFormat = configuration.cellFormat || {};
 | 
			
		||||
    configuration.autosize = configuration.autosize === undefined ? true : configuration.autosize;
 | 
			
		||||
    // anything that doesn't have a telemetryMode existed before the change and should stay as it was for consistency
 | 
			
		||||
    configuration.telemetryMode = configuration.telemetryMode ?? 'unlimited';
 | 
			
		||||
    configuration.persistModeChange = configuration.persistModeChange ?? true;
 | 
			
		||||
    configuration.rowLimit = configuration.rowLimit ?? 50;
 | 
			
		||||
    // anything that doesn't have a telemetryMode existed before the change and should
 | 
			
		||||
    // take the properties of any passed in defaults or the defaults from the plugin
 | 
			
		||||
    configuration.telemetryMode = configuration.telemetryMode ?? this.defaultOptions.telemetryMode;
 | 
			
		||||
    configuration.persistModeChange =
 | 
			
		||||
      configuration.persistModeChange ?? this.defaultOptions.persistModeChange;
 | 
			
		||||
    configuration.rowLimit = configuration.rowLimit ?? this.defaultOptions.rowLimit;
 | 
			
		||||
 | 
			
		||||
    return configuration;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -20,8 +20,8 @@
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
export default function getTelemetryTableType(options = {}) {
 | 
			
		||||
  const { telemetryMode = 'performance', persistModeChange = true, rowLimit = 50 } = options;
 | 
			
		||||
export default function getTelemetryTableType(options) {
 | 
			
		||||
  let { telemetryMode, persistModeChange, rowLimit } = options;
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    name: 'Telemetry Table',
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@ export default class TelemetryTableView {
 | 
			
		||||
    this.component = null;
 | 
			
		||||
 | 
			
		||||
    Object.defineProperty(this, 'table', {
 | 
			
		||||
      value: new TelemetryTable(domainObject, openmct),
 | 
			
		||||
      value: new TelemetryTable(domainObject, openmct, options),
 | 
			
		||||
      enumerable: false,
 | 
			
		||||
      configurable: false
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -398,15 +398,17 @@ export default {
 | 
			
		||||
      totalNumberOfRows: 0,
 | 
			
		||||
      rowContext: {},
 | 
			
		||||
      telemetryMode: configuration.telemetryMode,
 | 
			
		||||
      rowLimit: configuration.rowLimit,
 | 
			
		||||
      persistModeChange: configuration.persistModeChange,
 | 
			
		||||
      afterLoadActions: []
 | 
			
		||||
      afterLoadActions: [],
 | 
			
		||||
      existingConfiguration: configuration
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    dropTargetStyle() {
 | 
			
		||||
      return {
 | 
			
		||||
        top: this.$refs.headersTable.offsetTop + 'px',
 | 
			
		||||
        height: this.totalHeight + this.$refs.headersTable.offsetHeight + 'px',
 | 
			
		||||
        top: this.$refs.headersHolderEl.offsetTop + 'px',
 | 
			
		||||
        height: this.totalHeight + this.$refs.headersHolderEl.offsetHeight + 'px',
 | 
			
		||||
        left: this.dropOffsetLeft && this.dropOffsetLeft + 'px'
 | 
			
		||||
      };
 | 
			
		||||
    },
 | 
			
		||||
@@ -595,23 +597,35 @@ export default {
 | 
			
		||||
    },
 | 
			
		||||
    handleConfigurationChanges(changes) {
 | 
			
		||||
      const { rowLimit, telemetryMode, persistModeChange } = changes;
 | 
			
		||||
      const telemetryModeChanged = this.existingConfiguration.telemetryMode !== telemetryMode;
 | 
			
		||||
      let rowLimitChanged = false;
 | 
			
		||||
 | 
			
		||||
      this.persistModeChange = persistModeChange;
 | 
			
		||||
 | 
			
		||||
      // both rowLimit changes and telemetryMode changes
 | 
			
		||||
      // require a re-request of telemetry
 | 
			
		||||
 | 
			
		||||
      if (this.rowLimit !== rowLimit) {
 | 
			
		||||
        rowLimitChanged = true;
 | 
			
		||||
        this.rowLimit = rowLimit;
 | 
			
		||||
        this.table.updateRowLimit(rowLimit);
 | 
			
		||||
 | 
			
		||||
        if (this.telemetryMode !== telemetryMode) {
 | 
			
		||||
          // need to clear and resubscribe, if different, handled below
 | 
			
		||||
          this.table.clearAndResubscribe();
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (this.telemetryMode !== telemetryMode) {
 | 
			
		||||
      // check for telemetry mode change, because you could technically have persist mode changes
 | 
			
		||||
      // set to false, which could create a state where the configuration saved telemetry mode is
 | 
			
		||||
      // different from the currently set telemetry mode
 | 
			
		||||
      if (telemetryModeChanged && this.telemetryMode !== telemetryMode) {
 | 
			
		||||
        this.telemetryMode = telemetryMode;
 | 
			
		||||
 | 
			
		||||
        // this method also re-requests telemetry
 | 
			
		||||
        this.table.updateTelemetryMode(telemetryMode);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (rowLimitChanged && !telemetryModeChanged) {
 | 
			
		||||
        this.table.clearAndResubscribe();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this.existingConfiguration = changes;
 | 
			
		||||
    },
 | 
			
		||||
    updateVisibleRows() {
 | 
			
		||||
      if (!this.updatingView) {
 | 
			
		||||
 
 | 
			
		||||
@@ -25,10 +25,12 @@ import getTelemetryTableType from './TelemetryTableType.js';
 | 
			
		||||
import TelemetryTableViewProvider from './TelemetryTableViewProvider.js';
 | 
			
		||||
import TelemetryTableViewActions from './ViewActions.js';
 | 
			
		||||
 | 
			
		||||
export default function plugin(options) {
 | 
			
		||||
export default function plugin(
 | 
			
		||||
  options = { telemetryMode: 'performance', persistModeChange: true, rowLimit: 50 }
 | 
			
		||||
) {
 | 
			
		||||
  return function install(openmct) {
 | 
			
		||||
    openmct.objectViews.addProvider(new TelemetryTableViewProvider(openmct, options));
 | 
			
		||||
    openmct.inspectorViews.addProvider(new TableConfigurationViewProvider(openmct));
 | 
			
		||||
    openmct.inspectorViews.addProvider(new TableConfigurationViewProvider(openmct, options));
 | 
			
		||||
    openmct.types.addType('table', getTelemetryTableType(options));
 | 
			
		||||
    openmct.composition.addPolicy((parent, child) => {
 | 
			
		||||
      if (parent.type === 'table') {
 | 
			
		||||
 
 | 
			
		||||
@@ -195,7 +195,10 @@ describe('the plugin', () => {
 | 
			
		||||
            utc: false,
 | 
			
		||||
            'some-key': false,
 | 
			
		||||
            'some-other-key': false
 | 
			
		||||
          }
 | 
			
		||||
          },
 | 
			
		||||
          persistModeChange: true,
 | 
			
		||||
          rowLimit: 50,
 | 
			
		||||
          telemetryMode: 'performance'
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
      const testTelemetry = [
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@
 | 
			
		||||
    }"
 | 
			
		||||
  >
 | 
			
		||||
    <a class="c-icon-button icon-calendar" @click="toggle"></a>
 | 
			
		||||
    <div v-if="open" class="c-menu c-menu--mobile-modal c-datetime-picker">
 | 
			
		||||
    <div v-if="open" role="dialog" class="c-menu c-menu--mobile-modal c-datetime-picker">
 | 
			
		||||
      <div class="c-datetime-picker__close-button">
 | 
			
		||||
        <button class="c-click-icon icon-x-in-circle" @click="toggle"></button>
 | 
			
		||||
      </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@
 | 
			
		||||
        />
 | 
			
		||||
        <date-picker
 | 
			
		||||
          v-if="isUTCBased"
 | 
			
		||||
          class="c-ctrl-wrapper--menus-left"
 | 
			
		||||
          class="c-ctrl-wrapper--menus-right"
 | 
			
		||||
          :default-date-time="formattedBounds.start"
 | 
			
		||||
          :formatter="timeFormatter"
 | 
			
		||||
          @date-selected="startDateSelected"
 | 
			
		||||
@@ -87,7 +87,7 @@
 | 
			
		||||
        ></button>
 | 
			
		||||
        <button
 | 
			
		||||
          class="c-button icon-x"
 | 
			
		||||
          aria-label="Discard time bounds"
 | 
			
		||||
          aria-label="Discard changes and close time popup"
 | 
			
		||||
          @click.prevent="hide"
 | 
			
		||||
        ></button>
 | 
			
		||||
      </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -132,7 +132,7 @@
 | 
			
		||||
        ></button>
 | 
			
		||||
        <button
 | 
			
		||||
          class="c-button icon-x"
 | 
			
		||||
          aria-label="Discard time offsets"
 | 
			
		||||
          aria-label="Discard changes and close time popup"
 | 
			
		||||
          @click.prevent="hide"
 | 
			
		||||
        ></button>
 | 
			
		||||
      </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -454,6 +454,12 @@
 | 
			
		||||
                color: $colorTimeRealtimeFg;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        .c-ctrl-wrapper--menus-up{ // A bit hacky, but we are rewriting the CSS class here for ITC such that the calendar opens at the bottom to avoid cutoff
 | 
			
		||||
            .c-menu {
 | 
			
		||||
                top: auto;
 | 
			
		||||
                bottom: revert !important;
 | 
			
		||||
              };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -711,6 +711,20 @@
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &[class*='--menus-down'] {
 | 
			
		||||
    .c-menu {
 | 
			
		||||
      top: auto;
 | 
			
		||||
      bottom: 100%;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &[class*='--menus-right'] {
 | 
			
		||||
    .c-menu {
 | 
			
		||||
      left: 0;
 | 
			
		||||
      right: auto;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &[class*='--menus-left'],
 | 
			
		||||
  &[class*='menus-to-left'] {
 | 
			
		||||
    .c-menu {
 | 
			
		||||
 
 | 
			
		||||
@@ -77,12 +77,13 @@ export default {
 | 
			
		||||
  },
 | 
			
		||||
  setup() {
 | 
			
		||||
    const axisHolder = ref(null);
 | 
			
		||||
    const { size, startObserving } = useResizeObserver();
 | 
			
		||||
    const { size: containerSize, startObserving } = useResizeObserver();
 | 
			
		||||
    onMounted(() => {
 | 
			
		||||
      startObserving(axisHolder.value);
 | 
			
		||||
    });
 | 
			
		||||
    return {
 | 
			
		||||
      containerSize: size
 | 
			
		||||
      axisHolder,
 | 
			
		||||
      containerSize
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  watch: {
 | 
			
		||||
@@ -95,8 +96,11 @@ export default {
 | 
			
		||||
    contentHeight() {
 | 
			
		||||
      this.updateNowMarker();
 | 
			
		||||
    },
 | 
			
		||||
    containerSize() {
 | 
			
		||||
      this.resize();
 | 
			
		||||
    containerSize: {
 | 
			
		||||
      handler() {
 | 
			
		||||
        this.resize();
 | 
			
		||||
      },
 | 
			
		||||
      deep: true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
@@ -104,7 +108,7 @@ export default {
 | 
			
		||||
      this.useSVG = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.container = select(this.$refs.axisHolder);
 | 
			
		||||
    this.container = select(this.axisHolder);
 | 
			
		||||
    this.svgElement = this.container.append('svg:svg');
 | 
			
		||||
    // draw x axis with labels. CSS is used to position them.
 | 
			
		||||
    this.axisElement = this.svgElement
 | 
			
		||||
@@ -122,7 +126,7 @@ export default {
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    resize() {
 | 
			
		||||
      if (this.$refs.axisHolder.clientWidth !== this.width) {
 | 
			
		||||
      if (this.axisHolder.clientWidth !== this.width) {
 | 
			
		||||
        this.setDimensions();
 | 
			
		||||
        this.drawAxis(this.bounds, this.timeSystem);
 | 
			
		||||
        this.updateNowMarker();
 | 
			
		||||
@@ -139,11 +143,10 @@ export default {
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    setDimensions() {
 | 
			
		||||
      const axisHolder = this.$refs.axisHolder;
 | 
			
		||||
      this.width = axisHolder.clientWidth;
 | 
			
		||||
      this.width = this.axisHolder.clientWidth;
 | 
			
		||||
      this.offsetWidth = this.width - this.offset;
 | 
			
		||||
 | 
			
		||||
      this.height = Math.round(axisHolder.getBoundingClientRect().height);
 | 
			
		||||
      this.height = Math.round(this.axisHolder.getBoundingClientRect().height);
 | 
			
		||||
 | 
			
		||||
      if (this.useSVG) {
 | 
			
		||||
        this.svgElement.attr('width', this.width);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user