Compare commits
20 Commits
sim3
...
fix-remote
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f7a88090c | ||
|
|
9fcebf8b11 | ||
|
|
d68d7bdd8e | ||
|
|
88cbd8e40c | ||
|
|
d4bce52fb0 | ||
|
|
93214c235a | ||
|
|
907da30516 | ||
|
|
eb9e0d8d4d | ||
|
|
eb3129bdb0 | ||
|
|
d9877759d3 | ||
|
|
ac21c1b70c | ||
|
|
0ea776f688 | ||
|
|
3e3dc7dd83 | ||
|
|
50742c4f82 | ||
|
|
2f04add2a3 | ||
|
|
0ce5060246 | ||
|
|
00353cdccf | ||
|
|
a1ac209d74 | ||
|
|
bdd8477b54 | ||
|
|
f690f36bfb |
@@ -268,6 +268,9 @@ async function getCanvasPixelsWithData(page) {
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
async function assertLimitLinesExistAndAreVisible(page) {
|
||||
// Wait for plot series data to load
|
||||
await expect(page.locator('.js-series-data-loaded')).toBeVisible();
|
||||
// Wait for limit lines to be created
|
||||
await page.waitForSelector('.js-limit-area', { state: 'attached' });
|
||||
const limitLineCount = await page.locator('.c-plot-limit-line').count();
|
||||
// There should be 10 limit lines created by default
|
||||
|
||||
@@ -28,6 +28,14 @@ const { test, expect } = require('../../../../pluginFixtures');
|
||||
const { createDomainObjectWithDefaults, setRealTimeMode, setFixedTimeMode } = require('../../../../appActions');
|
||||
|
||||
test.describe('Plot Tagging', () => {
|
||||
/**
|
||||
* Given a canvas and a set of points, tags the points on the canvas.
|
||||
* @param {import('@playwright/test').Page} page
|
||||
* @param {HTMLCanvasElement} canvas a telemetry item with a plot
|
||||
* @param {Number} xEnd a telemetry item with a plot
|
||||
* @param {Number} yEnd a telemetry item with a plot
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function createTags({page, canvas, xEnd, yEnd}) {
|
||||
await canvas.hover({trial: true});
|
||||
|
||||
@@ -64,12 +72,20 @@ test.describe('Plot Tagging', () => {
|
||||
await page.getByText('Science').click();
|
||||
}
|
||||
|
||||
async function testTelemetryItem(page, canvas, telemetryItem) {
|
||||
/**
|
||||
* Given a telemetry item (e.g., a Sine Wave Generator) with a plot, tests that the plot can be tagged.
|
||||
* @param {import('@playwright/test').Page} page
|
||||
* @param {import('../../../../appActions').CreatedObjectInfo} telemetryItem a telemetry item with a plot
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function testTelemetryItem(page, telemetryItem) {
|
||||
// Check that telemetry item also received the tag
|
||||
await page.goto(telemetryItem.url);
|
||||
|
||||
await expect(page.getByText('No tags to display for this item')).toBeVisible();
|
||||
|
||||
const canvas = page.locator('canvas').nth(1);
|
||||
|
||||
//Wait for canvas to stablize.
|
||||
await canvas.hover({trial: true});
|
||||
|
||||
@@ -85,19 +101,31 @@ test.describe('Plot Tagging', () => {
|
||||
await expect(page.getByText('Driving')).toBeHidden();
|
||||
}
|
||||
|
||||
async function basicTagsTests(page, canvas) {
|
||||
// Search for Science
|
||||
/**
|
||||
* Given a page, tests that tags are searchable, deletable, and persist across reloads.
|
||||
* @param {import('@playwright/test').Page} page
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function basicTagsTests(page) {
|
||||
// Search for Driving
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc');
|
||||
await expect(page.locator('[aria-label="Search Result"]').nth(0)).toContainText("Science");
|
||||
await expect(page.locator('[aria-label="Search Result"]').nth(0)).not.toContainText("Drilling");
|
||||
|
||||
// Clicking elsewhere should cause annotation selection to be cleared
|
||||
await expect(page.getByText('No tags to display for this item')).toBeVisible();
|
||||
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('driv');
|
||||
// click on the search result
|
||||
await page.getByRole('searchbox', { name: 'OpenMCT Search' }).getByText(/Sine Wave/).first().click();
|
||||
|
||||
// Delete Driving
|
||||
await page.hover('[aria-label="Tag"]:has-text("Driving")');
|
||||
await page.locator('[aria-label="Remove tag Driving"]').click();
|
||||
|
||||
await expect(page.locator('[aria-label="Tags Inspector"]')).toContainText("Science");
|
||||
await expect(page.locator('[aria-label="Tags Inspector"]')).not.toContainText("Driving");
|
||||
// Search for Science
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc');
|
||||
await expect(page.locator('[aria-label="Search Result"]').nth(0)).toContainText("Science");
|
||||
await expect(page.locator('[aria-label="Search Result"]').nth(0)).not.toContainText("Drilling");
|
||||
|
||||
// Search for Driving
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
|
||||
@@ -109,12 +137,13 @@ test.describe('Plot Tagging', () => {
|
||||
page.reload(),
|
||||
page.waitForLoadState('networkidle')
|
||||
]);
|
||||
// wait for plot progress bar to disappear
|
||||
await page.locator('.l-view-section.c-progress-bar').waitFor({ state: 'detached' });
|
||||
// wait for plots to load
|
||||
await expect(page.locator('.js-series-data-loaded')).toBeVisible();
|
||||
|
||||
await page.getByText('Annotations').click();
|
||||
await expect(page.getByText('No tags to display for this item')).toBeVisible();
|
||||
|
||||
const canvas = page.locator('canvas').nth(1);
|
||||
// click on the tagged plot point
|
||||
await canvas.click({
|
||||
position: {
|
||||
@@ -171,8 +200,8 @@ test.describe('Plot Tagging', () => {
|
||||
// changing to fixed time mode rebuilds canvas?
|
||||
canvas = page.locator('canvas').nth(1);
|
||||
|
||||
await basicTagsTests(page, canvas);
|
||||
await testTelemetryItem(page, canvas, alphaSineWave);
|
||||
await basicTagsTests(page);
|
||||
await testTelemetryItem(page, alphaSineWave);
|
||||
|
||||
// set to real time mode
|
||||
await setRealTimeMode(page);
|
||||
@@ -182,8 +211,8 @@ test.describe('Plot Tagging', () => {
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc');
|
||||
// click on the search result
|
||||
await page.getByRole('searchbox', { name: 'OpenMCT Search' }).getByText('Alpha Sine Wave').first().click();
|
||||
// wait for plot progress bar to disappear
|
||||
await page.locator('.l-view-section.c-progress-bar').waitFor({ state: 'detached' });
|
||||
// wait for plots to load
|
||||
await expect(page.locator('.js-series-data-loaded')).toBeVisible();
|
||||
// expect plot to be paused
|
||||
await expect(page.locator('[title="Resume displaying real-time data"]')).toBeVisible();
|
||||
|
||||
@@ -202,7 +231,7 @@ test.describe('Plot Tagging', () => {
|
||||
xEnd: 700,
|
||||
yEnd: 480
|
||||
});
|
||||
await basicTagsTests(page, canvas);
|
||||
await basicTagsTests(page);
|
||||
});
|
||||
|
||||
test('Tags work with Stacked Plots', async ({ page }) => {
|
||||
@@ -232,7 +261,7 @@ test.describe('Plot Tagging', () => {
|
||||
xEnd: 700,
|
||||
yEnd: 215
|
||||
});
|
||||
await basicTagsTests(page, canvas);
|
||||
await testTelemetryItem(page, canvas, alphaSineWave);
|
||||
await basicTagsTests(page);
|
||||
await testTelemetryItem(page, alphaSineWave);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,12 +52,12 @@
|
||||
"painterro": "1.2.78",
|
||||
"playwright-core": "1.29.0",
|
||||
"plotly.js-basic-dist": "2.17.0",
|
||||
"plotly.js-gl2d-dist": "2.17.1",
|
||||
"plotly.js-gl2d-dist": "2.20.0",
|
||||
"printj": "1.3.1",
|
||||
"resolve-url-loader": "5.0.0",
|
||||
"sanitize-html": "2.10.0",
|
||||
"sass": "1.57.1",
|
||||
"sass-loader": "13.2.0",
|
||||
"sass": "1.59.3",
|
||||
"sass-loader": "13.2.1",
|
||||
"sinon": "15.0.1",
|
||||
"style-loader": "^3.3.1",
|
||||
"typescript": "4.9.5",
|
||||
@@ -66,7 +66,7 @@
|
||||
"vue-eslint-parser": "9.1.0",
|
||||
"vue-loader": "15.9.8",
|
||||
"vue-template-compiler": "2.6.14",
|
||||
"webpack": "5.74.0",
|
||||
"webpack": "5.76.2",
|
||||
"webpack-cli": "5.0.0",
|
||||
"webpack-dev-server": "4.11.1",
|
||||
"webpack-merge": "5.8.0"
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="!hideOptions && filteredOptions.length > 0"
|
||||
class="c-menu c-input--autocomplete__options"
|
||||
class="c-menu c-input--autocomplete__options js-autocomplete-options"
|
||||
aria-label="Autocomplete Options"
|
||||
@blur="hideOptions = true"
|
||||
>
|
||||
|
||||
@@ -322,7 +322,7 @@ export default {
|
||||
rgba(125,125,125,.2) 8px
|
||||
)`
|
||||
) : ''}`,
|
||||
transform: `scale(${this.zoomFactor}) translate(${this.imageTranslateX}px, ${this.imageTranslateY}px)`,
|
||||
transform: `scale(${this.zoomFactor}) translate(${this.imageTranslateX / 2}px, ${this.imageTranslateY / 2}px)`,
|
||||
transition: `${!this.pan && this.animateZoom ? 'transform 250ms ease-in' : 'initial'}`,
|
||||
width: `${this.sizedImageWidth}px`,
|
||||
height: `${this.sizedImageHeight}px`
|
||||
@@ -709,7 +709,7 @@ export default {
|
||||
getVisibleLayerStyles(layer) {
|
||||
return {
|
||||
backgroundImage: `url(${layer.source})`,
|
||||
transform: `scale(${this.zoomFactor}) translate(${this.imageTranslateX}px, ${this.imageTranslateY}px)`,
|
||||
transform: `scale(${this.zoomFactor}) translate(${this.imageTranslateX / 2}px, ${this.imageTranslateY / 2}px)`,
|
||||
transition: `${!this.pan && this.animateZoom ? 'transform 250ms ease-in' : 'initial'}`
|
||||
};
|
||||
},
|
||||
|
||||
@@ -320,7 +320,7 @@
|
||||
flex-direction: row;
|
||||
position: absolute;
|
||||
left: $interiorMargin; top: $interiorMargin;
|
||||
z-index: 70;
|
||||
z-index: 10;
|
||||
background: $colorLocalControlOvrBg;
|
||||
border-radius: $basicCr;
|
||||
align-items: center;
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="loaded"
|
||||
ref="plot"
|
||||
class="gl-plot"
|
||||
:class="{ 'js-series-data-loaded' : seriesDataLoaded }"
|
||||
>
|
||||
<slot></slot>
|
||||
<div class="plot-wrapper-axis-and-display-area flex-elem grows">
|
||||
@@ -347,6 +349,9 @@ export default {
|
||||
const parentLeftTickWidth = this.parentYTickWidth.leftTickWidth;
|
||||
|
||||
return parentLeftTickWidth || leftTickWidth;
|
||||
},
|
||||
seriesDataLoaded() {
|
||||
return ((this.pending === 0) && this.loaded);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -412,6 +417,7 @@ export default {
|
||||
this.openmct.selection.off('change', this.updateSelection);
|
||||
document.removeEventListener('keydown', this.handleKeyDown);
|
||||
document.removeEventListener('keyup', this.handleKeyUp);
|
||||
document.body.removeEventListener('click', this.cancelSelection);
|
||||
this.destroy();
|
||||
},
|
||||
methods: {
|
||||
@@ -444,6 +450,19 @@ export default {
|
||||
//This section is common to all entry points for annotation display
|
||||
this.prepareExistingAnnotationSelection(selectedAnnotations);
|
||||
},
|
||||
cancelSelection(event) {
|
||||
if (this.$refs?.plot) {
|
||||
const clickedInsidePlot = this.$refs.plot.contains(event.target);
|
||||
const clickedInsideInspector = event.target.closest('.js-inspector') !== null;
|
||||
const clickedOption = event.target.closest('.js-autocomplete-options') !== null;
|
||||
if (!clickedInsidePlot && !clickedInsideInspector && !clickedOption) {
|
||||
this.rectangles = [];
|
||||
this.annotationSelections = [];
|
||||
this.selectPlot();
|
||||
document.body.removeEventListener('click', this.cancelSelection);
|
||||
}
|
||||
}
|
||||
},
|
||||
waitForAxesToLoad() {
|
||||
return new Promise(resolve => {
|
||||
// When there is no plot data, the ranges can be undefined
|
||||
@@ -1276,6 +1295,8 @@ export default {
|
||||
}
|
||||
|
||||
this.openmct.selection.select(selection, true);
|
||||
|
||||
document.body.addEventListener('click', this.cancelSelection);
|
||||
},
|
||||
selectNewPlotAnnotations(boundingBoxPerYAxis, pointsInBox, event) {
|
||||
let targetDomainObjects = {};
|
||||
|
||||
@@ -46,6 +46,7 @@ export default class RemoteClock extends DefaultClock {
|
||||
|
||||
this.timeTelemetryObject = undefined;
|
||||
this.parseTime = undefined;
|
||||
this.formatTime = undefined;
|
||||
this.metadata = undefined;
|
||||
|
||||
this.lastTick = 0;
|
||||
@@ -137,6 +138,10 @@ export default class RemoteClock extends DefaultClock {
|
||||
this.parseTime = (datum) => {
|
||||
return timeFormatter.parse(datum);
|
||||
};
|
||||
|
||||
this.formatTime = (datum) => {
|
||||
return timeFormatter.format(datum);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,9 +38,8 @@
|
||||
import {getValidatedData} from "../plan/util";
|
||||
import ListView from '../../ui/components/List/ListView.vue';
|
||||
import {getPreciseDuration} from "../../utils/duration";
|
||||
import ticker from 'utils/clock/Ticker';
|
||||
import {SORT_ORDER_OPTIONS} from "./constants";
|
||||
|
||||
import _ from 'lodash';
|
||||
import moment from "moment";
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
@@ -53,17 +52,27 @@ const headerItems = [
|
||||
isSortable: true,
|
||||
property: 'start',
|
||||
name: 'Start Time',
|
||||
format: function (value, object) {
|
||||
format: function (value, object, key, openmct) {
|
||||
const clock = openmct.time.clock();
|
||||
if (clock && clock.formatTime) {
|
||||
return clock.formatTime(value);
|
||||
} else {
|
||||
return `${moment(value).format(TIME_FORMAT)}Z`;
|
||||
}
|
||||
}
|
||||
}, {
|
||||
defaultDirection: true,
|
||||
isSortable: true,
|
||||
property: 'end',
|
||||
name: 'End Time',
|
||||
format: function (value, object) {
|
||||
format: function (value, object, key, openmct) {
|
||||
const clock = openmct.time.clock();
|
||||
if (clock && clock.formatTime) {
|
||||
return clock.formatTime(value);
|
||||
} else {
|
||||
return `${moment(value).format(TIME_FORMAT)}Z`;
|
||||
}
|
||||
}
|
||||
}, {
|
||||
defaultDirection: false,
|
||||
property: 'duration',
|
||||
@@ -119,7 +128,7 @@ export default {
|
||||
this.unlistenConfig = this.openmct.objects.observe(this.domainObject, 'configuration', this.setViewFromConfig);
|
||||
this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.setStatus);
|
||||
this.status = this.openmct.status.get(this.domainObject.identifier);
|
||||
this.unlistenTicker = ticker.listen(this.clearPreviousActivities);
|
||||
|
||||
this.openmct.time.on('bounds', this.updateTimestamp);
|
||||
this.openmct.editor.on('isEditing', this.setEditState);
|
||||
|
||||
@@ -144,10 +153,6 @@ export default {
|
||||
this.unlistenConfig();
|
||||
}
|
||||
|
||||
if (this.unlistenTicker) {
|
||||
this.unlistenTicker();
|
||||
}
|
||||
|
||||
if (this.removeStatusListener) {
|
||||
this.removeStatusListener();
|
||||
}
|
||||
@@ -192,8 +197,8 @@ export default {
|
||||
}
|
||||
},
|
||||
updateTimestamp(_bounds, isTick) {
|
||||
if (isTick === true) {
|
||||
this.timestamp = this.openmct.time.clock().currentValue();
|
||||
if (isTick === true && this.openmct.time.clock() !== undefined) {
|
||||
this.updateTimeStampAndListActivities(this.openmct.time.clock().currentValue());
|
||||
}
|
||||
},
|
||||
setViewFromClock(newClock) {
|
||||
@@ -202,12 +207,11 @@ export default {
|
||||
if (isFixedTime) {
|
||||
this.hideAll = false;
|
||||
this.showAll = true;
|
||||
// clear invokes listActivities
|
||||
this.clearPreviousActivities(this.openmct.time.bounds()?.start);
|
||||
this.updateTimeStampAndListActivities(this.openmct.time.bounds()?.start);
|
||||
} else {
|
||||
this.setSort();
|
||||
this.setViewBounds();
|
||||
this.listActivities();
|
||||
this.updateTimeStampAndListActivities(this.openmct.time.clock().currentValue());
|
||||
}
|
||||
},
|
||||
addItem(domainObject) {
|
||||
@@ -346,12 +350,8 @@ export default {
|
||||
// sort by start time
|
||||
this.planActivities = activities.sort(this.sortByStartTime);
|
||||
},
|
||||
clearPreviousActivities(time) {
|
||||
if (time instanceof Date) {
|
||||
this.timestamp = time.getTime();
|
||||
} else {
|
||||
updateTimeStampAndListActivities(time) {
|
||||
this.timestamp = time;
|
||||
}
|
||||
|
||||
this.listActivities();
|
||||
},
|
||||
|
||||
@@ -37,7 +37,7 @@ export default {
|
||||
// eslint-disable-next-line you-dont-need-lodash-underscore/get
|
||||
let value = _.get(this.item, property.key);
|
||||
if (property.format) {
|
||||
value = property.format(value, this.item, property.key);
|
||||
value = property.format(value, this.item, property.key, this.openmct);
|
||||
}
|
||||
|
||||
values.push({
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 0; right: 0; bottom: auto; left: 0;
|
||||
z-index: 2;
|
||||
z-index: 10;
|
||||
|
||||
.c-object-label {
|
||||
visibility: hidden;
|
||||
@@ -99,6 +99,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.c-so-view--flexible-layout,
|
||||
&.c-so-view--layout {
|
||||
// For sub-layouts with hidden frames, completely hide the header to avoid overlapping buttons
|
||||
> .c-so-view__header {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="c-inspector">
|
||||
<div class="c-inspector js-inspector">
|
||||
<object-name />
|
||||
<InspectorTabs
|
||||
:selection="selection"
|
||||
|
||||
Reference in New Issue
Block a user