Compare commits

...

3 Commits

Author SHA1 Message Date
Shefali Joshi
28b5d7c41c
Time strip marcus banes line "now line" fix for right y-axis and when now is out of bounds (#7993)
Some checks failed
CodeQL / Analyze (push) Has been cancelled
* Account for right y-axes when calculating now line position.
Don't show the now line if it's out of bounds of the time axis

* Add test for now marker in realtime and out of bounds modes
2025-02-17 18:23:48 +00:00
Shefali Joshi
ecd120387c
Independent time conductor related handling for plot synchronization. (#7956)
Some checks failed
CodeQL / Analyze (push) Has been cancelled
* Ensure that the mode set when independent time conductor is enabled/disabled is propagated correctly.
Also ensure that global time conductor changes are not picked up by the independent time conductor when the user has enabled it at least once before

* Use structuredClone instead of deep copy

* Add e2e test

* Assert that you're in fixed mode after sync time conductor

* Comment explaining new time context test

* Change test to be a little less complicated

* Fix linting errors
2025-02-10 21:46:00 +00:00
Shefali Joshi
a6517bb33e
migrate from actions/upload-artifact: v3 to v4. (#8000)
Some checks failed
CodeQL / Analyze (push) Has been cancelled
* migrate from actions/upload-artifact: v3 to v4.
https://github.com/actions/upload-artifact/blob/main/docs/MIGRATION.md

* Add names for artifacts and allow overwriting them
2025-02-07 18:03:00 +00:00
11 changed files with 167 additions and 29 deletions

View File

@ -66,15 +66,19 @@ jobs:
- name: Archive test results
if: success() || failure()
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: e2e-couchdb-test-results
path: test-results
overwrite: true
- name: Archive html test results
if: success() || failure()
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: e2e-couchdb-html-test-results
path: html-test-results
overwrite: true
- name: Remove pr:e2e:couchdb label (if present)
if: always()

View File

@ -38,9 +38,11 @@ jobs:
- name: Archive test results
if: success() || failure()
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: e2e-flakefinder-test-results
path: test-results
overwrite: true
- name: Remove pr:e2e:flakefinder label (if present)
if: always()

View File

@ -35,9 +35,11 @@ jobs:
- run: npm run test:perf:memory
- name: Archive test results
if: success() || failure()
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: e2e-perf-test-results
path: test-results
overwrite: true
- name: Remove pr:e2e:perf label (if present)
if: always()

View File

@ -45,9 +45,11 @@ jobs:
npm run cov:e2e:full:publish
- name: Archive test results
if: success() || failure()
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: e2e-pr-test-results
path: test-results
overwrite: true
- name: Remove pr:e2e label (if present)
if: always()

View File

@ -24,7 +24,9 @@ import {
createDomainObjectWithDefaults,
createPlanFromJSON,
navigateToObjectWithFixedTimeBounds,
setFixedIndependentTimeConductorBounds
setFixedIndependentTimeConductorBounds,
setFixedTimeMode,
setTimeConductorBounds
} from '../../../appActions.js';
import { expect, test } from '../../../pluginFixtures.js';
@ -74,21 +76,14 @@ const testPlan = {
};
test.describe('Time Strip', () => {
test('Create two Time Strips, add a single Plan to both, and verify they can have separate Independent Time Contexts', async ({
page
}) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5627'
});
// Constant locators
const activityBounds = page.locator('.activity-bounds');
let timestrip;
let plan;
test.beforeEach(async ({ page }) => {
// Goto baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
const timestrip = await test.step('Create a Time Strip', async () => {
timestrip = await test.step('Create a Time Strip', async () => {
const createdTimeStrip = await createDomainObjectWithDefaults(page, { type: 'Time Strip' });
const objectName = await page.locator('.l-browse-bar__object-name').innerText();
expect(objectName).toBe(createdTimeStrip.name);
@ -96,7 +91,7 @@ test.describe('Time Strip', () => {
return createdTimeStrip;
});
const plan = await test.step('Create a Plan and add it to the timestrip', async () => {
plan = await test.step('Create a Plan and add it to the timestrip', async () => {
const createdPlan = await createPlanFromJSON(page, {
name: 'Test Plan',
json: testPlan
@ -110,6 +105,22 @@ test.describe('Time Strip', () => {
.dragTo(page.getByLabel('Object View'));
await page.getByLabel('Save').click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
return createdPlan;
});
});
test('Create two Time Strips, add a single Plan to both, and verify they can have separate Independent Time Contexts', async ({
page
}) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5627'
});
// Constant locators
const activityBounds = page.locator('.activity-bounds');
await test.step('Set time strip to fixed timespan mode and verify activities', async () => {
const startBound = testPlan.TEST_GROUP[0].start;
const endBound = testPlan.TEST_GROUP[testPlan.TEST_GROUP.length - 1].end;
@ -119,8 +130,6 @@ test.describe('Time Strip', () => {
// Verify all events are displayed
const eventCount = await page.locator('.activity-bounds').count();
expect(eventCount).toEqual(testPlan.TEST_GROUP.length);
return createdPlan;
});
await test.step('TimeStrip can use the Independent Time Conductor', async () => {
@ -177,4 +186,48 @@ test.describe('Time Strip', () => {
expect(await activityBounds.count()).toEqual(1);
});
});
test('Time strip now line', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/7817'
});
await test.step('Is displayed in realtime mode', async () => {
await expect(page.getByLabel('Now Marker')).toBeVisible();
});
await test.step('Is hidden when out of bounds of the time axis', async () => {
// Switch to fixed timespan mode
await setFixedTimeMode(page);
// Get the end bounds
const endBounds = await page.getByLabel('End bounds').textContent();
// Add 2 minutes to end bound datetime and use it as the new end time
let endTimeStamp = new Date(endBounds);
endTimeStamp.setUTCMinutes(endTimeStamp.getUTCMinutes() + 2);
const endDate = endTimeStamp.toISOString().split('T')[0];
const milliseconds = endTimeStamp.getMilliseconds();
const endTime = endTimeStamp.toISOString().split('T')[1].replace(`.${milliseconds}Z`, '');
// Subtract 1 minute from the end bound and use it as the new start time
let startTimeStamp = new Date(endBounds);
startTimeStamp.setUTCMinutes(startTimeStamp.getUTCMinutes() + 1);
const startDate = startTimeStamp.toISOString().split('T')[0];
const startMilliseconds = startTimeStamp.getMilliseconds();
const startTime = startTimeStamp
.toISOString()
.split('T')[1]
.replace(`.${startMilliseconds}Z`, '');
// Set fixed timespan mode to the future so that "now" is out of bounds.
await setTimeConductorBounds(page, {
startDate,
endDate,
startTime,
endTime
});
await expect(page.getByLabel('Now Marker')).toBeHidden();
});
});
});

View File

@ -108,4 +108,42 @@ test.describe('Plot Controls', () => {
// Expect before and after plot points to match
await expect(plotPixelSizeAtPause).toEqual(plotPixelSizeAfterWait);
});
/*
Test to verify that switching a plot's time context from global to
its own independent time context and then back to global context works correctly.
After switching from fixed time mode (ITC) to real time mode (global context),
the pause control for the plot should be available, indicating that it is following the right context.
*/
test('Plots follow the right time context', async ({ page }) => {
// Set global time conductor to real-time mode
await setRealTimeMode(page);
// hover over plot for plot controls
await page.getByLabel('Plot Canvas').hover();
// Ensure pause control is visible since global time conductor is in Real time mode.
await expect(page.getByTitle('Pause incoming real-time data')).toBeVisible();
// Toggle independent time conductor ON
await page.getByLabel('Enable Independent Time Conductor').click();
// Bring up the independent time conductor popup and switch to fixed time mode
await page.getByLabel('Independent Time Conductor Settings').click();
await page.getByLabel('Independent Time Conductor Mode Menu').click();
await page.getByRole('menuitem', { name: /Fixed Timespan/ }).click();
// hover over plot for plot controls
await page.getByLabel('Plot Canvas').hover();
// Ensure pause control is no longer visible since the plot is following the independent time context
await expect(page.getByTitle('Pause incoming real-time data')).toBeHidden();
// Toggle independent time conductor OFF - Note that the global time conductor is still in Real time mode
await page.getByLabel('Disable Independent Time Conductor').click();
// hover over plot for plot controls
await page.getByLabel('Plot Canvas').hover();
// Ensure pause control is visible since the global time conductor is in real time mode
await expect(page.getByTitle('Pause incoming real-time data')).toBeVisible();
});
});

View File

@ -359,6 +359,18 @@ class IndependentTimeContext extends TimeContext {
}
}
/**
* @returns {boolean}
* @override
*/
isFixed() {
if (this.upstreamTimeContext) {
return this.upstreamTimeContext.isFixed(...arguments);
} else {
return super.isFixed(...arguments);
}
}
/**
* @returns {number}
* @override
@ -400,7 +412,7 @@ class IndependentTimeContext extends TimeContext {
}
/**
* Reset the time context to the global time context
* Reset the time context from the global time context
*/
resetContext() {
if (this.upstreamTimeContext) {
@ -428,6 +440,10 @@ class IndependentTimeContext extends TimeContext {
// Emit bounds so that views that are changing context get the upstream bounds
this.emit('bounds', this.getBounds());
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.getBounds());
// Also emit the mode in case it's different from previous time context
if (this.getMode()) {
this.emit(TIME_CONTEXT_EVENTS.modeChanged, this.#copy(this.getMode()));
}
}
/**
@ -502,6 +518,10 @@ class IndependentTimeContext extends TimeContext {
// Emit bounds so that views that are changing context get the upstream bounds
this.emit('bounds', this.getBounds());
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.getBounds());
// Also emit the mode in case it's different from the global time context
if (this.getMode()) {
this.emit(TIME_CONTEXT_EVENTS.modeChanged, this.#copy(this.getMode()));
}
// now that the view's context is set, tell others to check theirs in case they were following this view's context.
this.globalTimeContext.emit('refreshContext', viewKey);
}

View File

@ -23,6 +23,7 @@
import { FIXED_MODE_KEY, REALTIME_MODE_KEY } from '@/api/time/constants';
import IndependentTimeContext from '@/api/time/IndependentTimeContext';
import { TIME_CONTEXT_EVENTS } from './constants';
import GlobalTimeContext from './GlobalTimeContext.js';
/**
@ -142,7 +143,7 @@ class TimeAPI extends GlobalTimeContext {
addIndependentContext(key, value, clockKey) {
let timeContext = this.getIndependentContext(key);
//stop following upstream time context since the view has it's own
//stop following upstream time context since the view has its own
timeContext.resetContext();
if (clockKey) {
@ -152,6 +153,9 @@ class TimeAPI extends GlobalTimeContext {
timeContext.setMode(FIXED_MODE_KEY, value);
}
// Also emit the mode in case it's different from the previous time context
timeContext.emit(TIME_CONTEXT_EVENTS.modeChanged, structuredClone(timeContext.getMode()));
// Notify any nested views to update, pass in the viewKey so that particular view can skip getting an upstream context
this.emit('refreshContext', key);

View File

@ -539,6 +539,7 @@ export default {
this.followTimeContext();
},
followTimeContext() {
this.updateMode();
this.updateDisplayBounds(this.timeContext.getBounds());
this.timeContext.on('modeChanged', this.updateMode);
this.timeContext.on('boundsChanged', this.updateDisplayBounds);

View File

@ -243,12 +243,20 @@ export default {
this.timeContext.off(TIME_CONTEXT_EVENTS.modeChanged, this.setTimeOptionsMode);
},
setTimeOptionsClock(clock) {
// If the user has persisted any time options, then don't override them with global settings.
if (this.independentTCEnabled) {
return;
}
this.setTimeOptionsOffsets();
this.timeOptions.clock = clock.key;
},
setTimeOptionsMode(mode) {
// If the user has persisted any time options, then don't override them with global settings.
if (this.independentTCEnabled) {
this.setTimeOptionsOffsets();
this.timeOptions.mode = mode;
this.isFixed = this.timeOptions.mode === FIXED_MODE_KEY;
}
},
setTimeOptionsOffsets() {
this.timeOptions.clockOffsets =

View File

@ -21,7 +21,9 @@
-->
<template>
<div ref="axisHolder" class="c-timesystem-axis">
<div class="nowMarker" :style="nowMarkerStyle"><span class="icon-arrow-down"></span></div>
<div class="nowMarker" :style="nowMarkerStyle" aria-label="Now Marker">
<span class="icon-arrow-down"></span>
</div>
<svg :width="svgWidth" :height="svgHeight">
<g class="axis" font-size="1.3em" :transform="axisTransform"></g>
</svg>
@ -116,8 +118,10 @@ export default {
this.axisTransform = `translate(${this.alignmentData.leftWidth + leftOffset}, 20)`;
const rightOffset = this.alignmentData.rightWidth ? AXES_PADDING : 0;
this.leftAlignmentOffset = this.alignmentData.leftWidth + leftOffset;
this.alignmentOffset =
this.alignmentData.leftWidth + leftOffset + this.alignmentData.rightWidth + rightOffset;
this.leftAlignmentOffset + this.alignmentData.rightWidth + rightOffset;
this.refresh();
},
deep: true
@ -175,8 +179,8 @@ export default {
this.nowMarkerStyle.height = this.contentHeight + 'px';
const nowTimeStamp = this.openmct.time.now();
const now = this.xScale(nowTimeStamp);
this.nowMarkerStyle.left = `${now + this.alignmentOffset}px`;
if (now > this.width) {
this.nowMarkerStyle.left = `${now + this.leftAlignmentOffset}px`;
if (now < 0 || now > this.width) {
nowMarker.classList.add('hidden');
}
}