Compare commits

..

3 Commits

Author SHA1 Message Date
d5a03e0dd0 only run this one test please... 2022-06-08 11:49:14 -07:00
ff975656d4 Simulate a e2e test failure due to console error 2022-06-08 11:41:55 -07:00
58aeab3d31 [e2e] Add clarity to console.error failures
- Create a separate assert for each message

- Format the `ConsoleMessage` to provide location, line, and col numbers
2022-06-08 11:06:08 -07:00
38 changed files with 531 additions and 1440 deletions

View File

@ -23,7 +23,8 @@
const { test } = require('../../../fixtures'); const { test } = require('../../../fixtures');
const { expect } = require('@playwright/test'); const { expect } = require('@playwright/test');
test.describe('Telemetry Table', () => { // eslint-disable-next-line playwright/no-focused-test
test.describe.only('Telemetry Table', () => {
test('unpauses when paused by button and user changes bounds', async ({ page }) => { test('unpauses when paused by button and user changes bounds', async ({ page }) => {
test.info().annotations.push({ test.info().annotations.push({
type: 'issue', type: 'issue',

View File

@ -19,4 +19,4 @@
] ]
} }
] ]
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "openmct", "name": "openmct",
"version": "2.0.5", "version": "2.0.5-SNAPSHOT",
"description": "The Open MCT core platform", "description": "The Open MCT core platform",
"devDependencies": { "devDependencies": {
"@babel/eslint-parser": "7.18.2", "@babel/eslint-parser": "7.18.2",
@ -89,7 +89,7 @@
"test": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run", "test": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run",
"test:firefox": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run --browsers=FirefoxHeadless", "test:firefox": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run --browsers=FirefoxHeadless",
"test:debug": "cross-env NODE_ENV=debug karma start --no-single-run", "test:debug": "cross-env NODE_ENV=debug karma start --no-single-run",
"test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke branding default condition timeConductor clock exampleImagery persistence performance", "test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome telemetryTable",
"test:e2e:local": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome", "test:e2e:local": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome",
"test:e2e:updatesnapshots": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome --grep @snapshot --update-snapshots", "test:e2e:updatesnapshots": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome --grep @snapshot --update-snapshots",
"test:e2e:visual": "percy exec --config ./e2e/.percy.yml -- npx playwright test --config=e2e/playwright-visual.config.js", "test:e2e:visual": "percy exec --config ./e2e/.percy.yml -- npx playwright test --config=e2e/playwright-visual.config.js",

View File

@ -28,6 +28,7 @@
&[s-selected] { &[s-selected] {
// All frames selected while editing // All frames selected while editing
border: $editFrameSelectedBorder;
box-shadow: $editFrameSelectedShdw; box-shadow: $editFrameSelectedShdw;
.c-frame__move-bar { .c-frame__move-bar {

View File

@ -41,7 +41,7 @@ describe('the plugin', function () {
element.appendChild(child); element.appendChild(child);
openmct.on('start', done); openmct.on('start', done);
openmct.start(child); openmct.startHeadless();
}); });
afterEach(() => { afterEach(() => {

View File

@ -141,10 +141,6 @@
} }
} }
} }
[s-selected].c-fl-frame__drag-wrapper {
border: $editFrameSelectedBorder;
}
} }
/****** THEIR FRAMES */ /****** THEIR FRAMES */

View File

@ -22,7 +22,6 @@
import { createOpenMct, resetApplicationState } from 'utils/testing'; import { createOpenMct, resetApplicationState } from 'utils/testing';
import FlexibleLayout from './plugin'; import FlexibleLayout from './plugin';
import Vue from 'vue';
describe('the plugin', function () { describe('the plugin', function () {
let element; let element;
@ -62,7 +61,7 @@ describe('the plugin', function () {
element.appendChild(child); element.appendChild(child);
openmct.on('start', done); openmct.on('start', done);
openmct.start(child); openmct.startHeadless();
}); });
afterEach(() => { afterEach(() => {
@ -84,16 +83,6 @@ describe('the plugin', function () {
it('provides a view', () => { it('provides a view', () => {
expect(flexibleLayoutViewProvider).toBeDefined(); expect(flexibleLayoutViewProvider).toBeDefined();
}); });
it('renders a view', async () => {
const flexibleView = flexibleLayoutViewProvider.view(testViewObject, []);
flexibleView.show(child, false);
await Vue.nextTick();
const flexTitle = child.querySelector('.l-browse-bar .c-object-label__name');
expect(flexTitle).not.toBeNull();
});
}); });
describe('the toolbar', () => { describe('the toolbar', () => {

View File

@ -14,8 +14,6 @@
type="range" type="range"
min="0" min="0"
max="500" max="500"
draggable="true"
@dragstart.stop.prevent
@change="notifyFiltersChanged" @change="notifyFiltersChanged"
@input="notifyFiltersChanged" @input="notifyFiltersChanged"
> >
@ -26,8 +24,6 @@
type="range" type="range"
min="0" min="0"
max="500" max="500"
draggable="true"
@dragstart.stop.prevent
@change="notifyFiltersChanged" @change="notifyFiltersChanged"
@input="notifyFiltersChanged" @input="notifyFiltersChanged"
> >

View File

@ -21,11 +21,7 @@
*****************************************************************************/ *****************************************************************************/
<template> <template>
<div <div class="h-local-controls h-local-controls--overlay-content h-local-controls--menus-aligned c-local-controls--show-on-hover">
class="h-local-controls h-local-controls--overlay-content h-local-controls--menus-aligned c-local-controls--show-on-hover"
role="toolbar"
aria-label="Image controls"
>
<imagery-view-menu-switcher <imagery-view-menu-switcher
:icon-class="'icon-brightness'" :icon-class="'icon-brightness'"
:title="'Brightness and contrast'" :title="'Brightness and contrast'"
@ -177,7 +173,7 @@ export default {
this.$emit('filtersUpdated', this.filters); this.$emit('filtersUpdated', this.filters);
}, },
handleResetFilters() { handleResetFilters() {
this.filters = {...DEFAULT_FILTER_VALUES}; this.filters = DEFAULT_FILTER_VALUES;
this.notifyFiltersChanged(); this.notifyFiltersChanged();
}, },
limitZoomRange(factor) { limitZoomRange(factor) {

View File

@ -403,9 +403,6 @@ export default {
formattedDuration() { formattedDuration() {
let result = 'N/A'; let result = 'N/A';
let negativeAge = -1; let negativeAge = -1;
if (!Number.isInteger(this.numericDuration)) {
return result;
}
if (this.numericDuration > TWENTYFOUR_HOURS) { if (this.numericDuration > TWENTYFOUR_HOURS) {
negativeAge *= (this.numericDuration / TWENTYFOUR_HOURS); negativeAge *= (this.numericDuration / TWENTYFOUR_HOURS);
@ -908,10 +905,8 @@ export default {
let currentTime = this.timeContext.clock() && this.timeContext.clock().currentValue(); let currentTime = this.timeContext.clock() && this.timeContext.clock().currentValue();
if (currentTime === undefined) { if (currentTime === undefined) {
this.numericDuration = currentTime; this.numericDuration = currentTime;
} else if (Number.isInteger(this.parsedSelectedTime)) {
this.numericDuration = currentTime - this.parsedSelectedTime;
} else { } else {
this.numericDuration = undefined; this.numericDuration = currentTime - this.parsedSelectedTime;
} }
}, },
resetAgeCSS() { resetAgeCSS() {

View File

@ -68,24 +68,15 @@
overflow: hidden; overflow: hidden;
} }
&__background-image { &__background-image {
// Actually does the image display
background-position: center; background-position: center;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: contain; background-size: contain;
height: 100%; //fallback value
} }
&__image { &__image {
// Present to allow Save As... image
position: absolute;
height: 100%; height: 100%;
width: 100%; width: 100%;
opacity: 0; visibility: hidden;
} display: contents;
&__image-save-proxy {
height: 100%;
width: 100%;
z-index: 10;
} }
} }

View File

@ -70,18 +70,22 @@ export default {
this.timeContext.off('timeSystem', this.timeSystemChange); this.timeContext.off('timeSystem', this.timeSystemChange);
} }
}, },
isDatumValid(datum) { datumIsNotValid(datum) {
//TODO: Add a check to see if there are duplicate images (identical image timestamp and url subsequently) if (this.imageHistory.length === 0) {
if (!datum) {
return false; return false;
} }
const datumURL = this.formatImageUrl(datum);
const lastHistoryURL = this.formatImageUrl(this.imageHistory.slice(-1)[0]);
// datum is not valid if it matches the last datum in history,
// or it is before the last datum in the history
const datumTimeCheck = this.parseTime(datum); const datumTimeCheck = this.parseTime(datum);
const bounds = this.timeContext.bounds(); const historyTimeCheck = this.parseTime(this.imageHistory.slice(-1)[0]);
const matchesLast = (datumTimeCheck === historyTimeCheck) && (datumURL === lastHistoryURL);
const isStale = datumTimeCheck < historyTimeCheck;
const isOutOfBounds = datumTimeCheck < bounds.start || datumTimeCheck > bounds.end; return matchesLast || isStale;
return !isOutOfBounds;
}, },
formatImageUrl(datum) { formatImageUrl(datum) {
if (!datum) { if (!datum) {
@ -128,19 +132,25 @@ export default {
return this.requestHistory(); return this.requestHistory();
}, },
async requestHistory() { async requestHistory() {
let bounds = this.timeContext.bounds();
this.requestCount++; this.requestCount++;
const requestId = this.requestCount; const requestId = this.requestCount;
const bounds = this.timeContext.bounds(); this.imageHistory = [];
const data = await this.openmct.telemetry let data = await this.openmct.telemetry
.request(this.domainObject, bounds) || []; .request(this.domainObject, bounds) || [];
// wait until new request resolves to do comparison
if (this.requestCount !== requestId) {
return this.imageHistory = [];
}
const imagery = data.filter(this.isDatumValid).map(this.normalizeDatum); if (this.requestCount === requestId) {
this.imageHistory = imagery; let imagery = [];
data.forEach((datum) => {
let image = this.normalizeDatum(datum);
if (image) {
imagery.push(image);
}
});
//this is to optimize anything that reacts to imageHistory length
this.imageHistory = imagery;
}
}, },
clearData(domainObjectToClear) { clearData(domainObjectToClear) {
// global clearData button is accepted therefore no truthy check on inputted param // global clearData button is accepted therefore no truthy check on inputted param
@ -170,29 +180,27 @@ export default {
.subscribe(this.domainObject, (datum) => { .subscribe(this.domainObject, (datum) => {
let parsedTimestamp = this.parseTime(datum); let parsedTimestamp = this.parseTime(datum);
let bounds = this.timeContext.bounds(); let bounds = this.timeContext.bounds();
if (!(parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end)) {
return;
}
if (this.isDatumValid(datum)) { if (parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end) {
this.imageHistory.push(this.normalizeDatum(datum)); let image = this.normalizeDatum(datum);
if (image) {
this.imageHistory.push(image);
}
} }
}); });
}, },
normalizeDatum(datum) { normalizeDatum(datum) {
if (this.datumIsNotValid(datum)) {
return;
}
const formattedTime = this.formatTime(datum); let image = { ...datum };
const url = this.formatImageUrl(datum); image.formattedTime = this.formatTime(datum);
const time = this.parseTime(formattedTime); image.url = this.formatImageUrl(datum);
const imageDownloadName = this.getImageDownloadName(datum); image.time = this.parseTime(image.formattedTime);
image.imageDownloadName = this.getImageDownloadName(datum);
return { return image;
...datum,
formattedTime,
url,
time,
imageDownloadName
};
}, },
getFormatter(key) { getFormatter(key) {
let metadataValue = this.metadata.value(key) || { format: key }; let metadataValue = this.metadata.value(key) || { format: key };

View File

@ -84,6 +84,7 @@ describe("The Imagery View Layouts", () => {
let telemetryPromise; let telemetryPromise;
let telemetryPromiseResolve; let telemetryPromiseResolve;
let cleanupFirst; let cleanupFirst;
let isClearDataTriggered;
let openmct; let openmct;
let parent; let parent;
@ -204,12 +205,20 @@ describe("The Imagery View Layouts", () => {
cleanupFirst = []; cleanupFirst = [];
openmct = createOpenMct(); openmct = createOpenMct();
openmct.time.timeSystem('utc', {
start: START - (5 * ONE_MINUTE),
end: START + (5 * ONE_MINUTE)
});
telemetryPromise = new Promise((resolve) => { telemetryPromise = new Promise((resolve) => {
telemetryPromiseResolve = resolve; telemetryPromiseResolve = resolve;
}); });
spyOn(openmct.telemetry, 'request').and.callFake(() => { spyOn(openmct.telemetry, 'request').and.callFake(() => {
if (isClearDataTriggered) {
return [];
}
telemetryPromiseResolve(imageTelemetry); telemetryPromiseResolve(imageTelemetry);
return telemetryPromise; return telemetryPromise;
@ -328,93 +337,44 @@ describe("The Imagery View Layouts", () => {
expect(imageryView).toBeDefined(); expect(imageryView).toBeDefined();
}); });
describe("Clear data action for imagery", () => { describe("imagery view", () => {
let applicableViews; let applicableViews;
let imageryViewProvider; let imageryViewProvider;
let imageryView; let imageryView;
let componentView;
let clearDataPlugin; let clearDataPlugin;
let clearDataAction; let clearDataAction;
beforeEach(() => { beforeEach(() => {
openmct.time.timeSystem('utc', {
start: START - (5 * ONE_MINUTE),
end: START + (5 * ONE_MINUTE)
});
applicableViews = openmct.objectViews.get(imageryObject, [imageryObject]); applicableViews = openmct.objectViews.get(imageryObject, [imageryObject]);
imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey); imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey);
imageryView = imageryViewProvider.view(imageryObject, [imageryObject]); imageryView = imageryViewProvider.view(imageryObject, [imageryObject]);
imageryView.show(child); imageryView.show(child);
componentView = imageryView._getInstance().$children[0];
clearDataPlugin = new ClearDataPlugin( clearDataPlugin = new ClearDataPlugin(
['example.imagery'], ['example.imagery'],
{indicator: true} {indicator: true}
); );
openmct.install(clearDataPlugin); openmct.install(clearDataPlugin);
clearDataAction = openmct.actions.getAction('clear-data-action'); clearDataAction = openmct.actions.getAction('clear-data-action');
return Vue.nextTick();
});
it('clear data action is installed', () => {
expect(clearDataAction).toBeDefined();
});
it('on clearData action should clear data for object is selected', (done) => {
// force show the thumbnails // force show the thumbnails
componentView.forceShowThumbnails = true;
Vue.nextTick(() => {
let clearDataResolve;
let telemetryRequestPromise = new Promise((resolve) => {
clearDataResolve = resolve;
});
expect(parent.querySelectorAll('.c-imagery__thumb').length).not.toBe(0);
openmct.objectViews.on('clearData', (_domainObject) => {
return Vue.nextTick(() => {
expect(parent.querySelectorAll('.c-imagery__thumb').length).toBe(0);
clearDataResolve();
});
});
clearDataAction.invoke(imageryObject);
telemetryRequestPromise.then(() => {
done();
});
});
});
});
describe("imagery view", () => {
let applicableViews;
let imageryViewProvider;
let imageryView;
beforeEach(() => {
openmct.time.timeSystem('utc', {
start: START - (5 * ONE_MINUTE),
end: START + (5 * ONE_MINUTE)
});
applicableViews = openmct.objectViews.get(imageryObject, [imageryObject]);
imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey);
imageryView = imageryViewProvider.view(imageryObject, [imageryObject]);
imageryView.show(child);
imageryView._getInstance().$children[0].forceShowThumbnails = true; imageryView._getInstance().$children[0].forceShowThumbnails = true;
return Vue.nextTick(); return Vue.nextTick();
}); });
afterEach(() => {
isClearDataTriggered = false;
// openmct.time.stopClock();
// openmct.router.removeListener('change:hash', resolveFunction);
// imageryView.destroy();
});
it("on mount should show the the most recent image", () => { it("on mount should show the the most recent image", (done) => {
//Looks like we need Vue.nextTick here so that computed properties settle down //Looks like we need Vue.nextTick here so that computed properties settle down
return Vue.nextTick(() => { Vue.nextTick(() => {
const imageInfo = getImageInfo(parent); const imageInfo = getImageInfo(parent);
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1); expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1);
done();
}); });
}); });
@ -462,7 +422,7 @@ describe("The Imagery View Layouts", () => {
it("should show that an image is not new", (done) => { it("should show that an image is not new", (done) => {
Vue.nextTick(() => { Vue.nextTick(() => {
const target = imageTelemetry[4].url; const target = imageTelemetry[2].url;
parent.querySelectorAll(`img[src='${target}']`)[0].click(); parent.querySelectorAll(`img[src='${target}']`)[0].click();
Vue.nextTick(() => { Vue.nextTick(() => {
@ -584,6 +544,25 @@ describe("The Imagery View Layouts", () => {
expect(imageSizeAfter.width).toBeLessThan(imageSizeBefore.width); expect(imageSizeAfter.width).toBeLessThan(imageSizeBefore.width);
done(); done();
}); });
it('clear data action is installed', () => {
expect(clearDataAction).toBeDefined();
});
it('on clearData action should clear data for object is selected', async (done) => {
// force show the thumbnails
imageryView._getInstance().$children[0].forceShowThumbnails = true;
await Vue.nextTick();
expect(parent.querySelectorAll('.c-imagery__thumb').length).not.toBe(0);
openmct.objectViews.on('clearData', async (_domainObject) => {
await Vue.nextTick();
expect(parent.querySelectorAll('.c-imagery__thumb').length).toBe(0);
done();
});
// stubbed telemetry data will return empty array when true
isClearDataTriggered = true;
clearDataAction.invoke(imageryObject);
});
}); });
describe("imagery time strip view", () => { describe("imagery time strip view", () => {

View File

@ -26,7 +26,6 @@
:class="[plotLegendExpandedStateClass, plotLegendPositionClass]" :class="[plotLegendExpandedStateClass, plotLegendPositionClass]"
> >
<plot-legend <plot-legend
v-if="!isNestedWithinAStackedPlot"
:cursor-locked="!!lockHighlightPoint" :cursor-locked="!!lockHighlightPoint"
:series="seriesModels" :series="seriesModels"
:highlights="highlights" :highlights="highlights"
@ -247,18 +246,6 @@ export default {
default() { default() {
return 0; return 0;
} }
},
limitLineLabels: {
type: Object,
default() {
return {};
}
},
colorPalette: {
type: Object,
default() {
return undefined;
}
} }
}, },
data() { data() {
@ -279,7 +266,7 @@ export default {
isRealTime: this.openmct.time.clock() !== undefined, isRealTime: this.openmct.time.clock() !== undefined,
loaded: false, loaded: false,
isTimeOutOfSync: false, isTimeOutOfSync: false,
showLimitLineLabels: this.limitLineLabels, showLimitLineLabels: undefined,
isFrozenOnMouseDown: false, isFrozenOnMouseDown: false,
hasSameRangeValue: true, hasSameRangeValue: true,
cursorGuide: this.initCursorGuide, cursorGuide: this.initCursorGuide,
@ -287,22 +274,13 @@ export default {
}; };
}, },
computed: { computed: {
isNestedWithinAStackedPlot() {
const isNavigatedObject = this.openmct.router.isNavigatedObject([this.domainObject].concat(this.path));
return !isNavigatedObject && this.path.find((pathObject, pathObjIndex) => pathObject.type === 'telemetry.plot.stacked');
},
isFrozen() { isFrozen() {
return this.config.xAxis.get('frozen') === true && this.config.yAxis.get('frozen') === true; return this.config.xAxis.get('frozen') === true && this.config.yAxis.get('frozen') === true;
}, },
plotLegendPositionClass() { plotLegendPositionClass() {
return !this.isNestedWithinAStackedPlot ? `plot-legend-${this.config.legend.get('position')}` : ''; return `plot-legend-${this.config.legend.get('position')}`;
}, },
plotLegendExpandedStateClass() { plotLegendExpandedStateClass() {
if (this.isNestedWithinAStackedPlot) {
return '';
}
if (this.config.legend.get('expanded')) { if (this.config.legend.get('expanded')) {
return 'plot-legend-expanded'; return 'plot-legend-expanded';
} else { } else {
@ -314,12 +292,6 @@ export default {
} }
}, },
watch: { watch: {
limitLineLabels: {
handler(limitLineLabels) {
this.legendHoverChanged(limitLineLabels);
},
deep: true
},
initGridLines(newGridLines) { initGridLines(newGridLines) {
this.gridLines = newGridLines; this.gridLines = newGridLines;
}, },
@ -338,11 +310,6 @@ export default {
this.config = this.getConfig(); this.config = this.getConfig();
this.legend = this.config.legend; this.legend = this.config.legend;
if (this.isNestedWithinAStackedPlot) {
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.$emit('configLoaded', configId);
}
this.listenTo(this.config.series, 'add', this.addSeries, this); this.listenTo(this.config.series, 'add', this.addSeries, this);
this.listenTo(this.config.series, 'remove', this.removeSeries, this); this.listenTo(this.config.series, 'remove', this.removeSeries, this);
@ -408,7 +375,6 @@ export default {
id: configId, id: configId,
domainObject: this.domainObject, domainObject: this.domainObject,
openmct: this.openmct, openmct: this.openmct,
palette: this.colorPalette,
callback: (data) => { callback: (data) => {
this.data = data; this.data = data;
} }
@ -792,8 +758,6 @@ export default {
}; };
}); });
} }
this.$emit('highlights', this.highlights);
}, },
untrackMousePosition() { untrackMousePosition() {
@ -828,7 +792,6 @@ export default {
if (this.isMouseClick()) { if (this.isMouseClick()) {
this.lockHighlightPoint = !this.lockHighlightPoint; this.lockHighlightPoint = !this.lockHighlightPoint;
this.$emit('lockHighlightPoint', this.lockHighlightPoint);
} }
if (this.pan) { if (this.pan) {

View File

@ -68,8 +68,7 @@ export default class PlotConfigurationModel extends Model {
this.series = new SeriesCollection({ this.series = new SeriesCollection({
models: options.model.series, models: options.model.series,
plot: this, plot: this,
openmct: options.openmct, openmct: options.openmct
palette: options.palette
}); });
if (this.get('domainObject').type === 'telemetry.plot.overlay') { if (this.get('domainObject').type === 'telemetry.plot.overlay') {

View File

@ -39,7 +39,7 @@ export default class SeriesCollection extends Collection {
this.modelClass = PlotSeries; this.modelClass = PlotSeries;
this.plot = options.plot; this.plot = options.plot;
this.openmct = options.openmct; this.openmct = options.openmct;
this.palette = options.palette || new ColorPalette(); this.palette = new ColorPalette();
this.listenTo(this, 'add', this.onSeriesAdd, this); this.listenTo(this, 'add', this.onSeriesAdd, this);
this.listenTo(this, 'remove', this.onSeriesRemove, this); this.listenTo(this, 'remove', this.onSeriesRemove, this);
this.listenTo(this.plot, 'change:domainObject', this.trackPersistedConfig, this); this.listenTo(this.plot, 'change:domainObject', this.trackPersistedConfig, this);

View File

@ -260,7 +260,7 @@ export default class YAxisModel extends Model {
const plotModel = this.plot.get('domainObject'); const plotModel = this.plot.get('domainObject');
const label = plotModel.configuration?.yAxis?.label; const label = plotModel.configuration?.yAxis?.label;
const sampleSeries = seriesCollection.first(); const sampleSeries = seriesCollection.first();
if (!sampleSeries || !sampleSeries.metadata) { if (!sampleSeries) {
if (!label) { if (!label) {
this.unset('label'); this.unset('label');
} }

View File

@ -24,10 +24,7 @@
v-if="loaded" v-if="loaded"
class="js-plot-options-browse" class="js-plot-options-browse"
> >
<ul <ul class="c-tree">
v-if="!isStackedPlotObject"
class="c-tree"
>
<h2 title="Plot series display properties in this object">Plot Series</h2> <h2 title="Plot series display properties in this object">Plot Series</h2>
<plot-options-item <plot-options-item
v-for="series in plotSeries" v-for="series in plotSeries"
@ -39,10 +36,7 @@
v-if="plotSeries.length" v-if="plotSeries.length"
class="grid-properties" class="grid-properties"
> >
<ul <ul class="l-inspector-part">
v-if="!isStackedPlotObject"
class="l-inspector-part js-yaxis-properties"
>
<h2 title="Y axis settings for this object">Y Axis</h2> <h2 title="Y axis settings for this object">Y Axis</h2>
<li class="grid-row"> <li class="grid-row">
<div <div
@ -90,10 +84,7 @@
<div class="grid-cell value">{{ rangeMax }}</div> <div class="grid-cell value">{{ rangeMax }}</div>
</li> </li>
</ul> </ul>
<ul <ul class="l-inspector-part">
v-if="isStackedPlotObject || !isNestedWithinAStackedPlot"
class="l-inspector-part js-legend-properties"
>
<h2 title="Legend settings for this object">Legend</h2> <h2 title="Legend settings for this object">Legend</h2>
<li class="grid-row"> <li class="grid-row">
<div <div
@ -153,7 +144,7 @@ export default {
components: { components: {
PlotOptionsItem PlotOptionsItem
}, },
inject: ['openmct', 'domainObject', 'path'], inject: ['openmct', 'domainObject'],
data() { data() {
return { return {
config: {}, config: {},
@ -176,21 +167,12 @@ export default {
plotSeries: [] plotSeries: []
}; };
}, },
computed: {
isNestedWithinAStackedPlot() {
return this.path.find((pathObject, pathObjIndex) => pathObjIndex > 0 && pathObject.type === 'telemetry.plot.stacked');
},
isStackedPlotObject() {
return this.path.find((pathObject, pathObjIndex) => pathObjIndex === 0 && pathObject.type === 'telemetry.plot.stacked');
}
},
mounted() { mounted() {
eventHelpers.extend(this); eventHelpers.extend(this);
this.config = this.getConfig(); this.config = this.getConfig();
this.registerListeners(); this.registerListeners();
this.initConfiguration(); this.initConfiguration();
this.loaded = true; this.loaded = true;
}, },
beforeDestroy() { beforeDestroy() {
this.stopListening(); this.stopListening();

View File

@ -24,31 +24,21 @@
v-if="loaded" v-if="loaded"
class="js-plot-options-edit" class="js-plot-options-edit"
> >
<ul <ul class="c-tree">
v-if="!isStackedPlotObject"
class="c-tree"
>
<h2 title="Display properties for this object">Plot Series</h2> <h2 title="Display properties for this object">Plot Series</h2>
<li <li
v-for="series in plotSeries" v-for="series in plotSeries"
:key="series.key" :key="series.key"
> >
<series-form <series-form :series="series" />
:series="series"
@seriesUpdated="updateSeriesConfigForObject"
/>
</li> </li>
</ul> </ul>
<y-axis-form <y-axis-form
v-if="plotSeries.length && !isStackedPlotObject" v-if="plotSeries.length"
class="grid-properties" class="grid-properties"
:y-axis="config.yAxis" :y-axis="config.yAxis"
@seriesUpdated="updateSeriesConfigForObject"
/> />
<ul <ul class="l-inspector-part">
v-if="isStackedPlotObject || !isStackedPlotNestedObject"
class="l-inspector-part"
>
<h2 title="Legend options">Legend</h2> <h2 title="Legend options">Legend</h2>
<legend-form <legend-form
v-if="plotSeries.length" v-if="plotSeries.length"
@ -71,7 +61,7 @@ export default {
SeriesForm, SeriesForm,
YAxisForm YAxisForm
}, },
inject: ['openmct', 'domainObject', 'path'], inject: ['openmct', 'domainObject'],
data() { data() {
return { return {
config: {}, config: {},
@ -79,14 +69,6 @@ export default {
loaded: false loaded: false
}; };
}, },
computed: {
isStackedPlotNestedObject() {
return this.path.find((pathObject, pathObjIndex) => pathObjIndex > 0 && pathObject.type === 'telemetry.plot.stacked');
},
isStackedPlotObject() {
return this.path.find((pathObject, pathObjIndex) => pathObjIndex === 0 && pathObject.type === 'telemetry.plot.stacked');
}
},
mounted() { mounted() {
eventHelpers.extend(this); eventHelpers.extend(this);
this.config = this.getConfig(); this.config = this.getConfig();
@ -116,24 +98,6 @@ export default {
resetAllSeries() { resetAllSeries() {
this.plotSeries = []; this.plotSeries = [];
this.config.series.forEach(this.addSeries, this); this.config.series.forEach(this.addSeries, this);
},
updateSeriesConfigForObject(config) {
const stackedPlotObject = this.path.find((pathObject) => pathObject.type === 'telemetry.plot.stacked');
let index = stackedPlotObject.configuration.series.findIndex((seriesConfig) => {
return this.openmct.objects.areIdsEqual(seriesConfig.identifier, config.identifier);
});
if (index < 0) {
index = stackedPlotObject.configuration.series.length;
}
const configPath = `configuration.series[${index}].${config.path}`;
this.openmct.objects.mutate(
stackedPlotObject,
configPath,
config.value
);
} }
} }
}; };

View File

@ -13,10 +13,8 @@ export default function PlotsInspectorViewProvider(openmct) {
let object = selection[0][0].context.item; let object = selection[0][0].context.item;
const isOverlayPlotObject = object && object.type === 'telemetry.plot.overlay'; return object
const isStackedPlotObject = object && object.type === 'telemetry.plot.stacked'; && object.type === 'telemetry.plot.overlay';
return isStackedPlotObject || isOverlayPlotObject;
}, },
view: function (selection) { view: function (selection) {
let component; let component;

View File

@ -1,59 +0,0 @@
import PlotOptions from "./PlotOptions.vue";
import Vue from 'vue';
export default function StackedPlotsInspectorViewProvider(openmct) {
return {
key: 'stacked-plots-inspector',
name: 'Stacked Plots Inspector View',
canView: function (selection) {
if (selection.length === 0 || selection[0].length === 0) {
return false;
}
const object = selection[0][0].context.item;
const parent = selection[0].length > 1 && selection[0][1].context.item;
const isOverlayPlotObject = object && object.type === 'telemetry.plot.overlay';
const isParentStackedPlotObject = parent && parent.type === 'telemetry.plot.stacked';
return !isOverlayPlotObject && isParentStackedPlotObject;
},
view: function (selection) {
let component;
let objectPath;
if (selection.length) {
objectPath = selection[0].map((selectionItem) => {
return selectionItem.context.item;
});
}
return {
show: function (element) {
component = new Vue({
el: element,
components: {
PlotOptions: PlotOptions
},
provide: {
openmct,
domainObject: selection[0][0].context.item,
path: objectPath
},
template: '<plot-options></plot-options>'
});
},
destroy: function () {
if (component) {
component.$destroy();
component = undefined;
}
}
};
},
priority: function () {
return 1;
}
};
}

View File

@ -298,45 +298,28 @@ export default {
this.series.set('color', color); this.series.set('color', color);
if (!this.domainObject.configuration || !this.domainObject.configuration.series) { const getPath = this.dynamicPathForKey('color');
this.$emit('seriesUpdated', { const seriesColorPath = getPath(this.domainObject, this.series);
identifier: this.domainObject.identifier,
path: `series.color`,
value: color.asHexString()
});
} else {
const getPath = this.dynamicPathForKey('color');
const seriesColorPath = getPath(this.domainObject, this.series);
this.openmct.objects.mutate( this.openmct.objects.mutate(
this.domainObject, this.domainObject,
seriesColorPath, seriesColorPath,
color.asHexString() color.asHexString()
); );
}
if (otherSeriesWithColor) { if (otherSeriesWithColor) {
otherSeriesWithColor.set('color', oldColor); otherSeriesWithColor.set('color', oldColor);
if (!this.domainObject.configuration || !this.domainObject.configuration.series) { const otherSeriesColorPath = getPath(
this.$emit('seriesUpdated', { this.domainObject,
identifier: this.domainObject.identifier, otherSeriesWithColor
path: `series.color`, );
value: oldColor.asHexString()
});
} else {
const getPath = this.dynamicPathForKey('color');
const otherSeriesColorPath = getPath(
this.domainObject,
otherSeriesWithColor
);
this.openmct.objects.mutate( this.openmct.objects.mutate(
this.domainObject, this.domainObject,
otherSeriesColorPath, otherSeriesColorPath,
oldColor.asHexString() oldColor.asHexString()
); );
}
} }
}, },
toggleExpanded() { toggleExpanded() {
@ -360,19 +343,11 @@ export default {
if (!_.isEqual(coerce(newVal, formField.coerce), coerce(oldVal, formField.coerce))) { if (!_.isEqual(coerce(newVal, formField.coerce), coerce(oldVal, formField.coerce))) {
this.series.set(formKey, coerce(newVal, formField.coerce)); this.series.set(formKey, coerce(newVal, formField.coerce));
if (path) { if (path) {
if (!this.domainObject.configuration || !this.domainObject.configuration.series) { this.openmct.objects.mutate(
this.$emit('seriesUpdated', { this.domainObject,
identifier: this.domainObject.identifier, path(this.domainObject, this.series),
path: `series.${formKey}`, coerce(newVal, formField.coerce)
value: coerce(newVal, formField.coerce) );
});
} else {
this.openmct.objects.mutate(
this.domainObject,
path(this.domainObject, this.series),
coerce(newVal, formField.coerce)
);
}
} }
} }
}, },

View File

@ -230,19 +230,11 @@ export default {
// TODO: Why do we mutate yAxis twice, once directly, once via objects.mutate? Or are they different objects? // TODO: Why do we mutate yAxis twice, once directly, once via objects.mutate? Or are they different objects?
this.yAxis.set(formKey, newVal); this.yAxis.set(formKey, newVal);
if (path) { if (path) {
if (!this.domainObject.configuration || !this.domainObject.configuration.series) { this.openmct.objects.mutate(
this.$emit('seriesUpdated', { this.domainObject,
identifier: this.domainObject.identifier, path(this.domainObject, this.yAxis),
path: `yAxis.${formKey}`, newVal
value: newVal );
});
} else {
this.openmct.objects.mutate(
this.domainObject,
path(this.domainObject, this.yAxis),
newVal
);
}
} }
} }
} }

View File

@ -49,8 +49,8 @@
title="Cursor is point locked. Click anywhere in the plot to unlock." title="Cursor is point locked. Click anywhere in the plot to unlock."
></div> ></div>
<plot-legend-item-collapsed <plot-legend-item-collapsed
v-for="(seriesObject, seriesIndex) in series" v-for="seriesObject in series"
:key="`seriesObject.keyString-${seriesIndex}`" :key="seriesObject.keyString"
:highlights="highlights" :highlights="highlights"
:value-to-show-when-collapsed="legend.get('valueToShowWhenCollapsed')" :value-to-show-when-collapsed="legend.get('valueToShowWhenCollapsed')"
:series-object="seriesObject" :series-object="seriesObject"
@ -95,8 +95,8 @@
</thead> </thead>
<tbody> <tbody>
<plot-legend-item-expanded <plot-legend-item-expanded
v-for="(seriesObject, seriesIndex) in series" v-for="seriesObject in series"
:key="`seriesObject.keyString-${seriesIndex}`" :key="seriesObject.keyString"
:series-object="seriesObject" :series-object="seriesObject"
:highlights="highlights" :highlights="highlights"
:legend="legend" :legend="legend"

View File

@ -41,7 +41,7 @@
<span class="plot-series-name">{{ nameWithUnit }}</span> <span class="plot-series-name">{{ nameWithUnit }}</span>
</div> </div>
<div <div
v-show="!!highlights.length && (valueToShowWhenCollapsed !== 'none' && valueToShowWhenCollapsed !== 'units')" v-show="!!highlights.length && (valueToShowWhenCollapsed !== 'none')"
class="plot-series-value hover-value-enabled" class="plot-series-value hover-value-enabled"
:class="[{ 'cursor-hover': notNearest }, valueToDisplayWhenCollapsedClass, mctLimitStateClass]" :class="[{ 'cursor-hover': notNearest }, valueToDisplayWhenCollapsedClass, mctLimitStateClass]"
> >

View File

@ -26,7 +26,6 @@ import PlotsInspectorViewProvider from './inspector/PlotsInspectorViewProvider';
import OverlayPlotCompositionPolicy from './overlayPlot/OverlayPlotCompositionPolicy'; import OverlayPlotCompositionPolicy from './overlayPlot/OverlayPlotCompositionPolicy';
import StackedPlotCompositionPolicy from './stackedPlot/StackedPlotCompositionPolicy'; import StackedPlotCompositionPolicy from './stackedPlot/StackedPlotCompositionPolicy';
import PlotViewActions from "./actions/ViewActions"; import PlotViewActions from "./actions/ViewActions";
import StackedPlotsInspectorViewProvider from "./inspector/StackedPlotsInspectorViewProvider";
export default function () { export default function () {
return function install(openmct) { return function install(openmct) {
@ -40,8 +39,9 @@ export default function () {
initialize: function (domainObject) { initialize: function (domainObject) {
domainObject.composition = []; domainObject.composition = [];
domainObject.configuration = { domainObject.configuration = {
//series is an array of objects of type: {identifier, series: {color...}, yAxis:{}} series: [],
series: [] yAxis: {},
xAxis: {}
}; };
}, },
priority: 891 priority: 891
@ -55,11 +55,7 @@ export default function () {
creatable: true, creatable: true,
initialize: function (domainObject) { initialize: function (domainObject) {
domainObject.composition = []; domainObject.composition = [];
domainObject.configuration = { domainObject.configuration = {};
series: [],
yAxis: {},
xAxis: {}
};
}, },
priority: 890 priority: 890
}); });
@ -69,7 +65,6 @@ export default function () {
openmct.objectViews.addProvider(new PlotViewProvider(openmct)); openmct.objectViews.addProvider(new PlotViewProvider(openmct));
openmct.inspectorViews.addProvider(new PlotsInspectorViewProvider(openmct)); openmct.inspectorViews.addProvider(new PlotsInspectorViewProvider(openmct));
openmct.inspectorViews.addProvider(new StackedPlotsInspectorViewProvider(openmct));
openmct.composition.addPolicy(new OverlayPlotCompositionPolicy(openmct).allow); openmct.composition.addPolicy(new OverlayPlotCompositionPolicy(openmct).allow);
openmct.composition.addPolicy(new StackedPlotCompositionPolicy(openmct).allow); openmct.composition.addPolicy(new StackedPlotCompositionPolicy(openmct).allow);

View File

@ -23,6 +23,7 @@
import {createMouseEvent, createOpenMct, resetApplicationState, spyOnBuiltins} from "utils/testing"; import {createMouseEvent, createOpenMct, resetApplicationState, spyOnBuiltins} from "utils/testing";
import PlotVuePlugin from "./plugin"; import PlotVuePlugin from "./plugin";
import Vue from "vue"; import Vue from "vue";
import StackedPlot from "./stackedPlot/StackedPlot.vue";
import configStore from "./configuration/ConfigStore"; import configStore from "./configuration/ConfigStore";
import EventEmitter from "EventEmitter"; import EventEmitter from "EventEmitter";
import PlotOptions from "./inspector/PlotOptions.vue"; import PlotOptions from "./inspector/PlotOptions.vue";
@ -347,20 +348,14 @@ describe("the plugin", function () {
} }
}; };
openmct.router.path = [testTelemetryObject];
applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath); applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
plotViewProvider = applicableViews.find((viewProvider) => viewProvider.key === "plot-single"); plotViewProvider = applicableViews.find((viewProvider) => viewProvider.key === "plot-single");
plotView = plotViewProvider.view(testTelemetryObject, []); plotView = plotViewProvider.view(testTelemetryObject, [testTelemetryObject]);
plotView.show(child, true); plotView.show(child, true);
return Vue.nextTick(); return Vue.nextTick();
}); });
afterEach(() => {
openmct.router.path = null;
});
it("Makes only one request for telemetry on load", () => { it("Makes only one request for telemetry on load", () => {
expect(openmct.telemetry.request).toHaveBeenCalledTimes(1); expect(openmct.telemetry.request).toHaveBeenCalledTimes(1);
}); });
@ -528,6 +523,360 @@ describe("the plugin", function () {
}); });
}); });
describe("The stacked plot view", () => {
let testTelemetryObject;
let testTelemetryObject2;
let config;
let stackedPlotObject;
let component;
let mockComposition;
let plotViewComponentObject;
beforeEach(() => {
stackedPlotObject = {
identifier: {
namespace: "",
key: "test-plot"
},
type: "telemetry.plot.stacked",
name: "Test Stacked Plot"
};
testTelemetryObject = {
identifier: {
namespace: "",
key: "test-object"
},
type: "test-object",
name: "Test Object",
telemetry: {
values: [{
key: "utc",
format: "utc",
name: "Time",
hints: {
domain: 1
}
}, {
key: "some-key",
name: "Some attribute",
hints: {
range: 1
}
}, {
key: "some-other-key",
name: "Another attribute",
hints: {
range: 2
}
}]
},
configuration: {
objectStyles: {
staticStyle: {
style: {
backgroundColor: 'rgb(0, 200, 0)',
color: '',
border: ''
}
},
conditionSetIdentifier: {
namespace: '',
key: 'testConditionSetId'
},
selectedConditionId: 'conditionId1',
defaultConditionId: 'conditionId1',
styles: [
{
conditionId: 'conditionId1',
style: {
backgroundColor: 'rgb(0, 155, 0)',
color: '',
output: '',
border: ''
}
}
]
}
}
};
testTelemetryObject2 = {
identifier: {
namespace: "",
key: "test-object2"
},
type: "test-object",
name: "Test Object2",
telemetry: {
values: [{
key: "utc",
format: "utc",
name: "Time",
hints: {
domain: 1
}
}, {
key: "some-key2",
name: "Some attribute2",
hints: {
range: 1
}
}, {
key: "some-other-key2",
name: "Another attribute2",
hints: {
range: 2
}
}]
}
};
mockComposition = new EventEmitter();
mockComposition.load = () => {
mockComposition.emit('add', testTelemetryObject);
return [testTelemetryObject];
};
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
let viewContainer = document.createElement("div");
child.append(viewContainer);
component = new Vue({
el: viewContainer,
components: {
StackedPlot
},
provide: {
openmct: openmct,
domainObject: stackedPlotObject,
composition: openmct.composition.get(stackedPlotObject),
path: [stackedPlotObject]
},
template: "<stacked-plot></stacked-plot>"
});
return telemetryPromise
.then(Vue.nextTick())
.then(() => {
plotViewComponentObject = component.$root.$children[0];
const configId = openmct.objects.makeKeyString(testTelemetryObject.identifier);
config = configStore.get(configId);
});
});
it("Renders a collapsed legend for every telemetry", () => {
let legend = element.querySelectorAll(".plot-wrapper-collapsed-legend .plot-series-name");
expect(legend.length).toBe(1);
expect(legend[0].innerHTML).toEqual("Test Object");
});
it("Renders an expanded legend for every telemetry", () => {
let legendControl = element.querySelector(".c-plot-legend__view-control.gl-plot-legend__view-control.c-disclosure-triangle");
const clickEvent = createMouseEvent("click");
legendControl.dispatchEvent(clickEvent);
let legend = element.querySelectorAll(".plot-wrapper-expanded-legend .plot-legend-item td");
expect(legend.length).toBe(6);
});
it("Renders X-axis ticks for the telemetry object", (done) => {
let xAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-x .gl-plot-tick-wrapper");
expect(xAxisElement.length).toBe(1);
config.xAxis.set('displayRange', {
min: 0,
max: 4
});
Vue.nextTick(() => {
let ticks = xAxisElement[0].querySelectorAll(".gl-plot-tick");
expect(ticks.length).toBe(9);
done();
});
});
it("Renders Y-axis ticks for the telemetry object", (done) => {
config.yAxis.set('displayRange', {
min: 10,
max: 20
});
Vue.nextTick(() => {
let yAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-y .gl-plot-tick-wrapper");
expect(yAxisElement.length).toBe(1);
let ticks = yAxisElement[0].querySelectorAll(".gl-plot-tick");
expect(ticks.length).toBe(6);
done();
});
});
it("Renders Y-axis options for the telemetry object", () => {
let yAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-y .gl-plot-y-label__select");
expect(yAxisElement.length).toBe(1);
let options = yAxisElement[0].querySelectorAll("option");
expect(options.length).toBe(2);
expect(options[0].value).toBe("Some attribute");
expect(options[1].value).toBe("Another attribute");
});
it("turns on cursor Guides all telemetry objects", (done) => {
expect(plotViewComponentObject.$children[0].cursorGuide).toBeFalse();
plotViewComponentObject.$children[0].cursorGuide = true;
Vue.nextTick(() => {
expect(plotViewComponentObject.$children[0].cursorGuide).toBeTrue();
done();
});
});
it("shows grid lines for all telemetry objects", () => {
expect(plotViewComponentObject.$children[0].gridLines).toBeTrue();
let gridLinesContainer = element.querySelectorAll(".gl-plot-display-area .js-ticks");
let visible = 0;
gridLinesContainer.forEach(el => {
if (el.style.display !== "none") {
visible++;
}
});
expect(visible).toBe(2);
});
it("hides grid lines for all telemetry objects", (done) => {
expect(plotViewComponentObject.$children[0].gridLines).toBeTrue();
plotViewComponentObject.$children[0].gridLines = false;
Vue.nextTick(() => {
expect(plotViewComponentObject.$children[0].gridLines).toBeFalse();
let gridLinesContainer = element.querySelectorAll(".gl-plot-display-area .js-ticks");
let visible = 0;
gridLinesContainer.forEach(el => {
if (el.style.display !== "none") {
visible++;
}
});
expect(visible).toBe(0);
done();
});
});
it('plots a new series when a new telemetry object is added', (done) => {
mockComposition.emit('add', testTelemetryObject2);
Vue.nextTick(() => {
let legend = element.querySelectorAll(".plot-wrapper-collapsed-legend .plot-series-name");
expect(legend.length).toBe(2);
expect(legend[1].innerHTML).toEqual("Test Object2");
done();
});
});
it('removes plots from series when a telemetry object is removed', (done) => {
mockComposition.emit('remove', testTelemetryObject.identifier);
Vue.nextTick(() => {
let legend = element.querySelectorAll(".plot-wrapper-collapsed-legend .plot-series-name");
expect(legend.length).toBe(0);
done();
});
});
it("Changes the label of the y axis when the option changes", (done) => {
let selectEl = element.querySelector('.gl-plot-y-label__select');
selectEl.value = 'Another attribute';
selectEl.dispatchEvent(new Event("change"));
Vue.nextTick(() => {
expect(config.yAxis.get('label')).toEqual('Another attribute');
done();
});
});
it("Renders a new series when added to one of the plots", (done) => {
mockComposition.emit('add', testTelemetryObject2);
Vue.nextTick(() => {
let legend = element.querySelectorAll(".plot-wrapper-collapsed-legend .plot-series-name");
expect(legend.length).toBe(2);
expect(legend[1].innerHTML).toEqual("Test Object2");
done();
});
});
it("Adds a new point to the plot", (done) => {
let originalLength = config.series.models[0].getSeriesData().length;
config.series.models[0].add({
utc: 2,
'some-key': 1,
'some-other-key': 2
});
Vue.nextTick(() => {
const seriesData = config.series.models[0].getSeriesData();
expect(seriesData.length).toEqual(originalLength + 1);
done();
});
});
it("updates the xscale", (done) => {
config.xAxis.set('displayRange', {
min: 0,
max: 10
});
Vue.nextTick(() => {
expect(plotViewComponentObject.$children[0].component.$children[0].xScale.domain()).toEqual({
min: 0,
max: 10
});
done();
});
});
it("updates the yscale", (done) => {
config.yAxis.set('displayRange', {
min: 10,
max: 20
});
Vue.nextTick(() => {
expect(plotViewComponentObject.$children[0].component.$children[0].yScale.domain()).toEqual({
min: 10,
max: 20
});
done();
});
});
it("shows styles for telemetry objects if available", (done) => {
Vue.nextTick(() => {
let conditionalStylesContainer = element.querySelectorAll(".c-plot--stacked-container .js-style-receiver");
let hasStyles = 0;
conditionalStylesContainer.forEach(el => {
if (el.style.backgroundColor !== '') {
hasStyles++;
}
});
expect(hasStyles).toBe(1);
done();
});
});
describe('limits', () => {
it('lines are not displayed by default', () => {
let limitEl = element.querySelectorAll(".js-limit-area hr");
expect(limitEl.length).toBe(0);
});
it('lines are displayed when configuration is set to true', (done) => {
config.series.models[0].set('limitLines', true);
Vue.nextTick(() => {
let limitEl = element.querySelectorAll(".js-limit-area .js-limit-line");
expect(limitEl.length).toBe(4);
done();
});
});
});
});
describe('the inspector view', () => { describe('the inspector view', () => {
let component; let component;
let viewComponentObject; let viewComponentObject;
@ -606,7 +955,6 @@ describe("the plugin", function () {
] ]
]; ];
openmct.router.path = [testTelemetryObject];
mockComposition = new EventEmitter(); mockComposition = new EventEmitter();
mockComposition.load = () => { mockComposition.load = () => {
mockComposition.emit('add', testTelemetryObject); mockComposition.emit('add', testTelemetryObject);
@ -645,10 +993,6 @@ describe("the plugin", function () {
}); });
}); });
afterEach(() => {
openmct.router.path = null;
});
describe('in view only mode', () => { describe('in view only mode', () => {
let browseOptionsEl; let browseOptionsEl;
let editOptionsEl; let editOptionsEl;
@ -752,24 +1096,5 @@ describe("the plugin", function () {
expect(colorSwatch).toBeDefined(); expect(colorSwatch).toBeDefined();
}); });
}); });
describe('limits', () => {
it('lines are not displayed by default', () => {
let limitEl = element.querySelectorAll(".js-limit-area .js-limit-line");
expect(limitEl.length).toBe(0);
});
xit('lines are displayed when configuration is set to true', (done) => {
config.series.models[0].set('limitLines', true);
Vue.nextTick(() => {
let limitEl = element.querySelectorAll(".js-limit-area .js-limit-line");
expect(limitEl.length).toBe(4);
done();
});
});
});
}); });
}); });

View File

@ -21,37 +21,21 @@
--> -->
<template> <template>
<div <div class="c-plot c-plot--stacked holder holder-plot has-control-bar">
v-if="loaded"
class="c-plot c-plot--stacked holder holder-plot has-control-bar"
:class="[plotLegendExpandedStateClass, plotLegendPositionClass]"
>
<plot-legend
:cursor-locked="!!lockHighlightPoint"
:series="seriesModels"
:highlights="highlights"
:legend="legend"
@legendHoverChanged="legendHoverChanged"
/>
<div class="l-view-section"> <div class="l-view-section">
<stacked-plot-item <stacked-plot-item
v-for="object in compositionObjects" v-for="object in compositionObjects"
:key="object.id" :key="object.id"
class="c-plot--stacked-container" class="c-plot--stacked-container"
:child-object="object" :object="object"
:options="options" :options="options"
:grid-lines="gridLines" :grid-lines="gridLines"
:color-palette="colorPalette"
:cursor-guide="cursorGuide" :cursor-guide="cursorGuide"
:show-limit-line-labels="showLimitLineLabels"
:plot-tick-width="maxTickWidth" :plot-tick-width="maxTickWidth"
@plotTickWidth="onTickWidthChange" @plotTickWidth="onTickWidthChange"
@loadingUpdated="loadingUpdated" @loadingUpdated="loadingUpdated"
@cursorGuide="onCursorGuideChange" @cursorGuide="onCursorGuideChange"
@gridLines="onGridLinesChange" @gridLines="onGridLinesChange"
@lockHighlightPoint="lockHighlightPointUpdated"
@highlights="highlightsUpdated"
@configLoaded="registerSeriesListeners"
/> />
</div> </div>
</div> </div>
@ -59,19 +43,12 @@
<script> <script>
import PlotConfigurationModel from '../configuration/PlotConfigurationModel';
import configStore from '../configuration/ConfigStore';
import ColorPalette from "@/ui/color/ColorPalette";
import PlotLegend from "../legend/PlotLegend.vue";
import StackedPlotItem from './StackedPlotItem.vue'; import StackedPlotItem from './StackedPlotItem.vue';
import ImageExporter from '../../../exporters/ImageExporter'; import ImageExporter from '../../../exporters/ImageExporter';
import eventHelpers from "@/plugins/plot/lib/eventHelpers";
export default { export default {
components: { components: {
StackedPlotItem, StackedPlotItem
PlotLegend
}, },
inject: ['openmct', 'domainObject', 'composition', 'path'], inject: ['openmct', 'domainObject', 'composition', 'path'],
props: { props: {
@ -83,35 +60,16 @@ export default {
} }
}, },
data() { data() {
this.seriesConfig = {};
return { return {
hideExportButtons: false, hideExportButtons: false,
cursorGuide: false, cursorGuide: false,
gridLines: true, gridLines: true,
loading: false, loading: false,
compositionObjects: [], compositionObjects: [],
tickWidthMap: {}, tickWidthMap: {}
legend: {},
loaded: false,
lockHighlightPoint: false,
highlights: [],
seriesModels: [],
showLimitLineLabels: undefined,
colorPalette: new ColorPalette()
}; };
}, },
computed: { computed: {
plotLegendPositionClass() {
return `plot-legend-${this.config.legend.get('position')}`;
},
plotLegendExpandedStateClass() {
if (this.config.legend.get('expanded')) {
return 'plot-legend-expanded';
} else {
return 'plot-legend-collapsed';
}
},
maxTickWidth() { maxTickWidth() {
return Math.max(...Object.values(this.tickWidthMap)); return Math.max(...Object.values(this.tickWidthMap));
} }
@ -120,13 +78,6 @@ export default {
this.destroy(); this.destroy();
}, },
mounted() { mounted() {
eventHelpers.extend(this);
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.config = this.getConfig(configId);
this.legend = this.config.legend;
this.loaded = true;
this.imageExporter = new ImageExporter(this.openmct); this.imageExporter = new ImageExporter(this.openmct);
this.composition.on('add', this.addChild); this.composition.on('add', this.addChild);
@ -135,29 +86,10 @@ export default {
this.composition.load(); this.composition.load();
}, },
methods: { methods: {
getConfig(configId) {
let config = configStore.get(configId);
if (!config) {
config = new PlotConfigurationModel({
id: configId,
domainObject: this.domainObject,
openmct: this.openmct,
callback: (data) => {
this.data = data;
}
});
configStore.add(configId, config);
}
return config;
},
loadingUpdated(loaded) { loadingUpdated(loaded) {
this.loading = loaded; this.loading = loaded;
}, },
destroy() { destroy() {
this.stopListening();
configStore.deleteStore(this.config.id);
this.composition.off('add', this.addChild); this.composition.off('add', this.addChild);
this.composition.off('remove', this.removeChild); this.composition.off('remove', this.removeChild);
this.composition.off('reorder', this.compositionReorder); this.composition.off('reorder', this.compositionReorder);
@ -167,19 +99,6 @@ export default {
const id = this.openmct.objects.makeKeyString(child.identifier); const id = this.openmct.objects.makeKeyString(child.identifier);
this.$set(this.tickWidthMap, id, 0); this.$set(this.tickWidthMap, id, 0);
const persistedConfig = this.domainObject.configuration.series && this.domainObject.configuration.series.find((seriesConfig) => {
return this.openmct.objects.areIdsEqual(seriesConfig.identifier, child.identifier);
});
if (persistedConfig === undefined) {
this.openmct.objects.mutate(
this.domainObject,
'configuration.series[' + this.compositionObjects.length + ']',
{
identifier: child.identifier
}
);
}
this.compositionObjects.push(child); this.compositionObjects.push(child);
}, },
@ -188,13 +107,6 @@ export default {
this.$delete(this.tickWidthMap, id); this.$delete(this.tickWidthMap, id);
const configIndex = this.domainObject.configuration.series.findIndex((seriesConfig) => {
return this.openmct.objects.areIdsEqual(seriesConfig.identifier, childIdentifier);
});
if (configIndex > -1) {
this.domainObject.configuration.series.splice(configIndex, 1);
}
const childObj = this.compositionObjects.filter((c) => { const childObj = this.compositionObjects.filter((c) => {
const identifier = this.openmct.objects.makeKeyString(c.identifier); const identifier = this.openmct.objects.makeKeyString(c.identifier);
@ -246,34 +158,6 @@ export default {
this.$set(this.tickWidthMap, plotId, width); this.$set(this.tickWidthMap, plotId, width);
}, },
legendHoverChanged(data) {
this.showLimitLineLabels = data;
},
lockHighlightPointUpdated(data) {
this.lockHighlightPoint = data;
},
highlightsUpdated(data) {
this.highlights = data;
},
registerSeriesListeners(configId) {
this.seriesConfig[configId] = this.getConfig(configId);
this.listenTo(this.seriesConfig[configId].series, 'add', this.addSeries, this);
this.listenTo(this.seriesConfig[configId].series, 'remove', this.removeSeries, this);
this.seriesConfig[configId].series.models.forEach(this.addSeries, this);
},
addSeries(series) {
const index = this.seriesModels.length;
this.$set(this.seriesModels, index, series);
},
removeSeries(plotSeries) {
const index = this.seriesModels.findIndex(seriesModel => this.openmct.objects.areIdsEqual(seriesModel.identifier, plotSeries.identifier));
if (index > -1) {
this.$delete(this.seriesModels, index);
}
this.stopListening(plotSeries);
},
onCursorGuideChange(cursorGuide) { onCursorGuideChange(cursorGuide) {
this.cursorGuide = cursorGuide === true; this.cursorGuide = cursorGuide === true;
}, },

View File

@ -27,14 +27,12 @@
import MctPlot from '../MctPlot.vue'; import MctPlot from '../MctPlot.vue';
import Vue from "vue"; import Vue from "vue";
import conditionalStylesMixin from "./mixins/objectStyles-mixin"; import conditionalStylesMixin from "./mixins/objectStyles-mixin";
import configStore from "@/plugins/plot/configuration/ConfigStore";
import PlotConfigurationModel from "@/plugins/plot/configuration/PlotConfigurationModel";
export default { export default {
mixins: [conditionalStylesMixin], mixins: [conditionalStylesMixin],
inject: ['openmct', 'domainObject', 'path'], inject: ['openmct', 'domainObject', 'path'],
props: { props: {
childObject: { object: {
type: Object, type: Object,
default() { default() {
return {}; return {};
@ -58,18 +56,6 @@ export default {
return true; return true;
} }
}, },
showLimitLineLabels: {
type: Object,
default() {
return {};
}
},
colorPalette: {
type: Object,
default() {
return undefined;
}
},
plotTickWidth: { plotTickWidth: {
type: Number, type: Number,
default() { default() {
@ -86,22 +72,12 @@ export default {
}, },
plotTickWidth(width) { plotTickWidth(width) {
this.updateComponentProp('plotTickWidth', width); this.updateComponentProp('plotTickWidth', width);
},
showLimitLineLabels: {
handler(data) {
this.updateComponentProp('limitLineLabels', data);
},
deep: true
} }
}, },
mounted() { mounted() {
this.updateView(); this.updateView();
}, },
beforeDestroy() { beforeDestroy() {
if (this.removeSelectable) {
this.removeSelectable();
}
if (this.component) { if (this.component) {
this.component.$destroy(); this.component.$destroy();
} }
@ -120,19 +96,15 @@ export default {
} }
const onTickWidthChange = this.onTickWidthChange; const onTickWidthChange = this.onTickWidthChange;
const onLockHighlightPointUpdated = this.onLockHighlightPointUpdated;
const onHighlightsUpdated = this.onHighlightsUpdated;
const onConfigLoaded = this.onConfigLoaded;
const onCursorGuideChange = this.onCursorGuideChange; const onCursorGuideChange = this.onCursorGuideChange;
const onGridLinesChange = this.onGridLinesChange; const onGridLinesChange = this.onGridLinesChange;
const loadingUpdated = this.loadingUpdated; const loadingUpdated = this.loadingUpdated;
const setStatus = this.setStatus; const setStatus = this.setStatus;
const openmct = this.openmct; const openmct = this.openmct;
const object = this.object;
const path = this.path; const path = this.path;
//If this object is not persistable, then package it with it's parent
const object = this.getPlotObject();
const getProps = this.getProps; const getProps = this.getProps;
let viewContainer = document.createElement('div'); let viewContainer = document.createElement('div');
this.$el.append(viewContainer); this.$el.append(viewContainer);
@ -151,28 +123,14 @@ export default {
return { return {
...getProps(), ...getProps(),
onTickWidthChange, onTickWidthChange,
onLockHighlightPointUpdated,
onHighlightsUpdated,
onConfigLoaded,
onCursorGuideChange, onCursorGuideChange,
onGridLinesChange, onGridLinesChange,
loadingUpdated, loadingUpdated,
setStatus setStatus
}; };
}, },
template: '<div ref="plotWrapper" class="l-view-section u-style-receiver js-style-receiver" :class="{\'s-status-timeconductor-unsynced\': status && status === \'timeconductor-unsynced\'}"><div v-show="!!loading" class="c-loading--overlay loading"></div><mct-plot :init-grid-lines="gridLines" :init-cursor-guide="cursorGuide" :plot-tick-width="plotTickWidth" :limit-line-labels="limitLineLabels" :color-palette="colorPalette" :options="options" @plotTickWidth="onTickWidthChange" @lockHighlightPoint="onLockHighlightPointUpdated" @highlights="onHighlightsUpdated" @configLoaded="onConfigLoaded" @cursorGuide="onCursorGuideChange" @gridLines="onGridLinesChange" @statusUpdated="setStatus" @loadingUpdated="loadingUpdated"/></div>' template: '<div ref="plotWrapper" class="l-view-section u-style-receiver js-style-receiver" :class="{\'s-status-timeconductor-unsynced\': status && status === \'timeconductor-unsynced\'}"><div v-show="!!loading" class="c-loading--overlay loading"></div><mct-plot :init-grid-lines="gridLines" :init-cursor-guide="cursorGuide" :plot-tick-width="plotTickWidth" :options="options" @plotTickWidth="onTickWidthChange" @cursorGuide="onCursorGuideChange" @gridLines="onGridLinesChange" @statusUpdated="setStatus" @loadingUpdated="loadingUpdated"/></div>'
}); });
this.setSelection();
},
onLockHighlightPointUpdated() {
this.$emit('lockHighlightPoint', ...arguments);
},
onHighlightsUpdated() {
this.$emit('highlights', ...arguments);
},
onConfigLoaded() {
this.$emit('configLoaded', ...arguments);
}, },
onTickWidthChange() { onTickWidthChange() {
this.$emit('plotTickWidth', ...arguments); this.$emit('plotTickWidth', ...arguments);
@ -187,73 +145,19 @@ export default {
this.status = status; this.status = status;
this.updateComponentProp('status', status); this.updateComponentProp('status', status);
}, },
setSelection() {
let childContext = {};
childContext.item = this.childObject;
this.context = childContext;
if (this.removeSelectable) {
this.removeSelectable();
}
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context);
},
loadingUpdated(loaded) { loadingUpdated(loaded) {
this.loading = loaded; this.loading = loaded;
this.updateComponentProp('loading', loaded); this.updateComponentProp('loading', loaded);
}, },
getProps() { getProps() {
return { return {
limitLineLabels: this.showLimitLineLabels,
gridLines: this.gridLines, gridLines: this.gridLines,
cursorGuide: this.cursorGuide, cursorGuide: this.cursorGuide,
plotTickWidth: this.plotTickWidth, plotTickWidth: this.plotTickWidth,
loading: this.loading, loading: this.loading,
options: this.options, options: this.options,
status: this.status, status: this.status
colorPalette: this.colorPalette
}; };
},
getPlotObject() {
if (this.childObject.configuration && this.childObject.configuration.series) {
//If the object has a configuration, allow initialization of the config from it's persisted config
return this.childObject;
} else {
// If the object does not have configuration, initialize the series config with the persisted config from the stacked plot
const configId = this.openmct.objects.makeKeyString(this.childObject.identifier);
let config = configStore.get(configId);
if (!config) {
const persistedConfig = this.domainObject.configuration.series.find((seriesConfig) => {
return this.openmct.objects.areIdsEqual(seriesConfig.identifier, this.childObject.identifier);
});
if (persistedConfig) {
config = new PlotConfigurationModel({
id: configId,
domainObject: {
...this.childObject,
configuration: {
series: [
{
identifier: this.childObject.identifier,
...persistedConfig.series
}
],
yAxis: persistedConfig.yAxis
}
},
openmct: this.openmct,
palette: this.colorPalette,
callback: (data) => {
this.data = data;
}
});
configStore.add(configId, config);
}
}
return this.childObject;
}
} }
} }
}; };

View File

@ -31,7 +31,7 @@ export default {
}; };
}, },
mounted() { mounted() {
this.objectStyles = this.getObjectStyleForItem(this.childObject.configuration); this.objectStyles = this.getObjectStyleForItem(this.object.configuration);
this.initObjectStyles(); this.initObjectStyles();
}, },
beforeDestroy() { beforeDestroy() {
@ -62,18 +62,18 @@ export default {
this.stopListeningStyles(); this.stopListeningStyles();
} }
this.stopListeningStyles = this.openmct.objects.observe(this.childObject, 'configuration.objectStyles', (newObjectStyle) => { this.stopListeningStyles = this.openmct.objects.observe(this.object, 'configuration.objectStyles', (newObjectStyle) => {
//Updating styles in the inspector view will trigger this so that the changes are reflected immediately //Updating styles in the inspector view will trigger this so that the changes are reflected immediately
this.styleRuleManager.updateObjectStyleConfig(newObjectStyle); this.styleRuleManager.updateObjectStyleConfig(newObjectStyle);
}); });
if (this.childObject && this.childObject.configuration && this.childObject.configuration.fontStyle) { if (this.object && this.object.configuration && this.object.configuration.fontStyle) {
const { fontSize, font } = this.childObject.configuration.fontStyle; const { fontSize, font } = this.object.configuration.fontStyle;
this.setFontSize(fontSize); this.setFontSize(fontSize);
this.setFont(font); this.setFont(font);
} }
this.stopListeningFontStyles = this.openmct.objects.observe(this.childObject, 'configuration.fontStyle', (newFontStyle) => { this.stopListeningFontStyles = this.openmct.objects.observe(this.object, 'configuration.fontStyle', (newFontStyle) => {
this.setFontSize(newFontStyle.fontSize); this.setFontSize(newFontStyle.fontSize);
this.setFont(newFontStyle.font); this.setFont(newFontStyle.font);
}); });

View File

@ -1,771 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, 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 {createMouseEvent, createOpenMct, resetApplicationState, spyOnBuiltins} from "utils/testing";
import PlotVuePlugin from "../plugin";
import Vue from "vue";
import StackedPlot from "./StackedPlot.vue";
import configStore from "../configuration/ConfigStore";
import EventEmitter from "EventEmitter";
import PlotConfigurationModel from "../configuration/PlotConfigurationModel";
import PlotOptions from "../inspector/PlotOptions.vue";
describe("the plugin", function () {
let element;
let child;
let openmct;
let telemetryPromise;
let telemetryPromiseResolve;
let mockObjectPath;
let stackedPlotObject = {
identifier: {
namespace: "",
key: "test-plot"
},
type: "telemetry.plot.stacked",
name: "Test Stacked Plot",
configuration: {
series: []
}
};
beforeEach((done) => {
mockObjectPath = [
{
name: 'mock folder',
type: 'fake-folder',
identifier: {
key: 'mock-folder',
namespace: ''
}
},
{
name: 'mock parent folder',
type: 'time-strip',
identifier: {
key: 'mock-parent-folder',
namespace: ''
}
}
];
const testTelemetry = [
{
'utc': 1,
'some-key': 'some-value 1',
'some-other-key': 'some-other-value 1'
},
{
'utc': 2,
'some-key': 'some-value 2',
'some-other-key': 'some-other-value 2'
},
{
'utc': 3,
'some-key': 'some-value 3',
'some-other-key': 'some-other-value 3'
}
];
const timeSystem = {
timeSystemKey: 'utc',
bounds: {
start: 0,
end: 4
}
};
openmct = createOpenMct(timeSystem);
telemetryPromise = new Promise((resolve) => {
telemetryPromiseResolve = resolve;
});
spyOn(openmct.telemetry, 'request').and.callFake(() => {
telemetryPromiseResolve(testTelemetry);
return telemetryPromise;
});
openmct.install(new PlotVuePlugin());
element = document.createElement("div");
element.style.width = "640px";
element.style.height = "480px";
child = document.createElement("div");
child.style.width = "640px";
child.style.height = "480px";
element.appendChild(child);
document.body.appendChild(element);
spyOn(window, 'ResizeObserver').and.returnValue({
observe() {},
unobserve() {},
disconnect() {}
});
openmct.types.addType("test-object", {
creatable: true
});
spyOnBuiltins(["requestAnimationFrame"]);
window.requestAnimationFrame.and.callFake((callBack) => {
callBack();
});
openmct.router.path = [stackedPlotObject];
openmct.on("start", done);
openmct.startHeadless();
});
afterEach((done) => {
openmct.time.timeSystem('utc', {
start: 0,
end: 1
});
configStore.deleteAll();
resetApplicationState(openmct).then(done).catch(done);
});
afterAll(() => {
openmct.router.path = null;
});
describe("the plot views", () => {
it("provides a stacked plot view for objects with telemetry", () => {
const testTelemetryObject = {
id: "test-object",
type: "telemetry.plot.stacked",
telemetry: {
values: [{
key: "some-key"
}]
}
};
const applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
let plotView = applicableViews.find((viewProvider) => viewProvider.key === "plot-stacked");
expect(plotView).toBeDefined();
});
});
describe("The stacked plot view", () => {
let testTelemetryObject;
let testTelemetryObject2;
let config;
let component;
let mockComposition;
let plotViewComponentObject;
afterAll(() => {
openmct.router.path = null;
});
beforeEach(() => {
testTelemetryObject = {
identifier: {
namespace: "",
key: "test-object"
},
type: "test-object",
name: "Test Object",
telemetry: {
values: [{
key: "utc",
format: "utc",
name: "Time",
hints: {
domain: 1
}
}, {
key: "some-key",
name: "Some attribute",
hints: {
range: 1
}
}, {
key: "some-other-key",
name: "Another attribute",
hints: {
range: 2
}
}]
},
configuration: {
objectStyles: {
staticStyle: {
style: {
backgroundColor: 'rgb(0, 200, 0)',
color: '',
border: ''
}
},
conditionSetIdentifier: {
namespace: '',
key: 'testConditionSetId'
},
selectedConditionId: 'conditionId1',
defaultConditionId: 'conditionId1',
styles: [
{
conditionId: 'conditionId1',
style: {
backgroundColor: 'rgb(0, 155, 0)',
color: '',
output: '',
border: ''
}
}
]
}
}
};
testTelemetryObject2 = {
identifier: {
namespace: "",
key: "test-object2"
},
type: "test-object",
name: "Test Object2",
telemetry: {
values: [{
key: "utc",
format: "utc",
name: "Time",
hints: {
domain: 1
}
}, {
key: "some-key2",
name: "Some attribute2",
hints: {
range: 1
}
}, {
key: "some-other-key2",
name: "Another attribute2",
hints: {
range: 2
}
}]
}
};
mockComposition = new EventEmitter();
mockComposition.load = () => {
mockComposition.emit('add', testTelemetryObject);
return [testTelemetryObject];
};
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
let viewContainer = document.createElement("div");
child.append(viewContainer);
component = new Vue({
el: viewContainer,
components: {
StackedPlot
},
provide: {
openmct: openmct,
domainObject: stackedPlotObject,
composition: openmct.composition.get(stackedPlotObject),
path: [stackedPlotObject]
},
template: "<stacked-plot></stacked-plot>"
});
return telemetryPromise
.then(Vue.nextTick())
.then(() => {
plotViewComponentObject = component.$root.$children[0];
const configId = openmct.objects.makeKeyString(testTelemetryObject.identifier);
config = configStore.get(configId);
});
});
it("Renders a collapsed legend for every telemetry", () => {
let legend = element.querySelectorAll(".plot-wrapper-collapsed-legend .plot-series-name");
expect(legend.length).toBe(1);
expect(legend[0].innerHTML).toEqual("Test Object");
});
it("Renders an expanded legend for every telemetry", () => {
let legendControl = element.querySelector(".c-plot-legend__view-control.gl-plot-legend__view-control.c-disclosure-triangle");
const clickEvent = createMouseEvent("click");
legendControl.dispatchEvent(clickEvent);
let legend = element.querySelectorAll(".plot-wrapper-expanded-legend .plot-legend-item td");
expect(legend.length).toBe(6);
});
it("Renders X-axis ticks for the telemetry object", (done) => {
let xAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-x .gl-plot-tick-wrapper");
expect(xAxisElement.length).toBe(1);
config.xAxis.set('displayRange', {
min: 0,
max: 4
});
Vue.nextTick(() => {
let ticks = xAxisElement[0].querySelectorAll(".gl-plot-tick");
expect(ticks.length).toBe(9);
done();
});
});
it("Renders Y-axis ticks for the telemetry object", (done) => {
config.yAxis.set('displayRange', {
min: 10,
max: 20
});
Vue.nextTick(() => {
let yAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-y .gl-plot-tick-wrapper");
expect(yAxisElement.length).toBe(1);
let ticks = yAxisElement[0].querySelectorAll(".gl-plot-tick");
expect(ticks.length).toBe(6);
done();
});
});
it("Renders Y-axis options for the telemetry object", () => {
let yAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-y .gl-plot-y-label__select");
expect(yAxisElement.length).toBe(1);
let options = yAxisElement[0].querySelectorAll("option");
expect(options.length).toBe(2);
expect(options[0].value).toBe("Some attribute");
expect(options[1].value).toBe("Another attribute");
});
it("turns on cursor Guides all telemetry objects", (done) => {
expect(plotViewComponentObject.cursorGuide).toBeFalse();
plotViewComponentObject.cursorGuide = true;
Vue.nextTick(() => {
let childCursorGuides = element.querySelectorAll(".c-cursor-guide--v");
expect(childCursorGuides.length).toBe(1);
done();
});
});
it("shows grid lines for all telemetry objects", () => {
expect(plotViewComponentObject.gridLines).toBeTrue();
let gridLinesContainer = element.querySelectorAll(".gl-plot-display-area .js-ticks");
let visible = 0;
gridLinesContainer.forEach(el => {
if (el.style.display !== "none") {
visible++;
}
});
expect(visible).toBe(2);
});
it("hides grid lines for all telemetry objects", (done) => {
expect(plotViewComponentObject.gridLines).toBeTrue();
plotViewComponentObject.gridLines = false;
Vue.nextTick(() => {
expect(plotViewComponentObject.gridLines).toBeFalse();
let gridLinesContainer = element.querySelectorAll(".gl-plot-display-area .js-ticks");
let visible = 0;
gridLinesContainer.forEach(el => {
if (el.style.display !== "none") {
visible++;
}
});
expect(visible).toBe(0);
done();
});
});
it('plots a new series when a new telemetry object is added', (done) => {
mockComposition.emit('add', testTelemetryObject2);
Vue.nextTick(() => {
let legend = element.querySelectorAll(".plot-wrapper-collapsed-legend .plot-series-name");
expect(legend.length).toBe(2);
expect(legend[1].innerHTML).toEqual("Test Object2");
done();
});
});
it('removes plots from series when a telemetry object is removed', (done) => {
mockComposition.emit('remove', testTelemetryObject.identifier);
Vue.nextTick(() => {
expect(plotViewComponentObject.compositionObjects.length).toBe(0);
done();
});
});
it("Changes the label of the y axis when the option changes", (done) => {
let selectEl = element.querySelector('.gl-plot-y-label__select');
selectEl.value = 'Another attribute';
selectEl.dispatchEvent(new Event("change"));
Vue.nextTick(() => {
expect(config.yAxis.get('label')).toEqual('Another attribute');
done();
});
});
it("Renders a new series when added to one of the plots", (done) => {
mockComposition.emit('add', testTelemetryObject2);
Vue.nextTick(() => {
let legend = element.querySelectorAll(".plot-wrapper-collapsed-legend .plot-series-name");
expect(legend.length).toBe(2);
expect(legend[1].innerHTML).toEqual("Test Object2");
done();
});
});
it("Adds a new point to the plot", (done) => {
let originalLength = config.series.models[0].getSeriesData().length;
config.series.models[0].add({
utc: 2,
'some-key': 1,
'some-other-key': 2
});
Vue.nextTick(() => {
const seriesData = config.series.models[0].getSeriesData();
expect(seriesData.length).toEqual(originalLength + 1);
done();
});
});
it("updates the xscale", (done) => {
config.xAxis.set('displayRange', {
min: 0,
max: 10
});
Vue.nextTick(() => {
expect(plotViewComponentObject.$children[1].component.$children[0].xScale.domain()).toEqual({
min: 0,
max: 10
});
done();
});
});
it("updates the yscale", (done) => {
config.yAxis.set('displayRange', {
min: 10,
max: 20
});
Vue.nextTick(() => {
expect(plotViewComponentObject.$children[1].component.$children[0].yScale.domain()).toEqual({
min: 10,
max: 20
});
done();
});
});
it("shows styles for telemetry objects if available", (done) => {
Vue.nextTick(() => {
let conditionalStylesContainer = element.querySelectorAll(".c-plot--stacked-container .js-style-receiver");
let hasStyles = 0;
conditionalStylesContainer.forEach(el => {
if (el.style.backgroundColor !== '') {
hasStyles++;
}
});
expect(hasStyles).toBe(1);
done();
});
});
});
describe('the stacked plot inspector view', () => {
let component;
let viewComponentObject;
let mockComposition;
let testTelemetryObject;
let selection;
let config;
beforeEach((done) => {
testTelemetryObject = {
identifier: {
namespace: "",
key: "test-object"
},
type: "test-object",
name: "Test Object",
telemetry: {
values: [{
key: "utc",
format: "utc",
name: "Time",
hints: {
domain: 1
}
}, {
key: "some-key",
name: "Some attribute",
hints: {
range: 1
}
}, {
key: "some-other-key",
name: "Another attribute",
hints: {
range: 2
}
}]
}
};
selection = [
[
{
context: {
item: {
type: 'telemetry.plot.stacked',
identifier: {
key: 'some-stacked-plot',
namespace: ''
},
configuration: {
series: []
}
}
}
}
]
];
openmct.router.path = [testTelemetryObject];
mockComposition = new EventEmitter();
mockComposition.load = () => {
mockComposition.emit('add', testTelemetryObject);
return [testTelemetryObject];
};
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
const configId = openmct.objects.makeKeyString(selection[0][0].context.item.identifier);
config = new PlotConfigurationModel({
id: configId,
domainObject: selection[0][0].context.item,
openmct: openmct
});
configStore.add(configId, config);
let viewContainer = document.createElement('div');
child.append(viewContainer);
component = new Vue({
el: viewContainer,
components: {
PlotOptions
},
provide: {
openmct: openmct,
domainObject: selection[0][0].context.item,
path: [selection[0][0].context.item]
},
template: '<plot-options/>'
});
Vue.nextTick(() => {
viewComponentObject = component.$root.$children[0];
done();
});
});
afterEach(() => {
openmct.router.path = null;
});
describe('in view only mode', () => {
let browseOptionsEl;
beforeEach(() => {
browseOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-browse');
});
it('shows legend properties', () => {
const legendPropertiesEl = browseOptionsEl.querySelector('.js-legend-properties');
expect(legendPropertiesEl).not.toBeNull();
});
it('does not show series properties', () => {
const seriesPropertiesEl = browseOptionsEl.querySelector('.c-tree');
expect(seriesPropertiesEl).toBeNull();
});
it('does not show yaxis properties', () => {
const yAxisPropertiesEl = browseOptionsEl.querySelector('.js-yaxis-properties');
expect(yAxisPropertiesEl).toBeNull();
});
});
});
describe('inspector view of stacked plot child', () => {
let component;
let viewComponentObject;
let mockComposition;
let testTelemetryObject;
let selection;
let config;
beforeEach((done) => {
testTelemetryObject = {
identifier: {
namespace: "",
key: "test-object"
},
type: "test-object",
name: "Test Object",
telemetry: {
values: [{
key: "utc",
format: "utc",
name: "Time",
hints: {
domain: 1
}
}, {
key: "some-key",
name: "Some attribute",
hints: {
range: 1
}
}, {
key: "some-other-key",
name: "Another attribute",
hints: {
range: 2
}
}]
}
};
selection = [
[
{
context: {
item: {
id: "test-object",
identifier: {
key: "test-object",
namespace: ''
},
type: "telemetry.plot.overlay",
configuration: {
series: [
{
identifier: {
key: "test-object",
namespace: ''
}
}
]
},
composition: []
}
}
},
{
context: {
item: {
type: 'telemetry.plot.stacked',
identifier: {
key: 'some-stacked-plot',
namespace: ''
},
configuration: {
series: []
}
}
}
}
]
];
openmct.router.path = [testTelemetryObject];
mockComposition = new EventEmitter();
mockComposition.load = () => {
mockComposition.emit('add', testTelemetryObject);
return [testTelemetryObject];
};
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
const configId = openmct.objects.makeKeyString(selection[0][0].context.item.identifier);
config = new PlotConfigurationModel({
id: configId,
domainObject: selection[0][0].context.item,
openmct: openmct
});
configStore.add(configId, config);
let viewContainer = document.createElement('div');
child.append(viewContainer);
component = new Vue({
el: viewContainer,
components: {
PlotOptions
},
provide: {
openmct: openmct,
domainObject: selection[0][0].context.item,
path: [selection[0][0].context.item, selection[0][1].context.item]
},
template: '<plot-options/>'
});
Vue.nextTick(() => {
viewComponentObject = component.$root.$children[0];
done();
});
});
afterEach(() => {
openmct.router.path = null;
});
describe('in view only mode', () => {
let browseOptionsEl;
beforeEach(() => {
browseOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-browse');
});
it('hides legend properties', () => {
const legendPropertiesEl = browseOptionsEl.querySelector('.js-legend-properties');
expect(legendPropertiesEl).toBeNull();
});
it('shows series properties', () => {
const seriesPropertiesEl = browseOptionsEl.querySelector('.c-tree');
expect(seriesPropertiesEl).not.toBeNull();
});
it('shows yaxis properties', () => {
const yAxisPropertiesEl = browseOptionsEl.querySelector('.js-yaxis-properties');
expect(yAxisPropertiesEl).not.toBeNull();
});
});
});
});

View File

@ -476,6 +476,7 @@ export default {
this.filterChanged = _.debounce(this.filterChanged, 500); this.filterChanged = _.debounce(this.filterChanged, 500);
}, },
mounted() { mounted() {
console.error('oh noes an error!');
this.csvExporter = new CSVExporter(); this.csvExporter = new CSVExporter();
this.rowsAdded = _.throttle(this.rowsAdded, 200); this.rowsAdded = _.throttle(this.rowsAdded, 200);
this.rowsRemoved = _.throttle(this.rowsRemoved, 200); this.rowsRemoved = _.throttle(this.rowsRemoved, 200);
@ -536,6 +537,7 @@ export default {
this.table.configuration.destroy(); this.table.configuration.destroy();
this.table.destroy(); this.table.destroy();
this.isNotARealFunction();
}, },
methods: { methods: {
updateVisibleRows() { updateVisibleRows() {

View File

@ -164,7 +164,7 @@ $borderMissing: 1px dashed $colorAlert !important;
$editUIColor: $uiColor; // Base color $editUIColor: $uiColor; // Base color
$editUIColorBg: $editUIColor; $editUIColorBg: $editUIColor;
$editUIColorFg: #fff; $editUIColorFg: #fff;
$editUIColorHov: pullForward(saturate($uiColor, 10%), 10%); // Hover color when $editUIColor is applied as a base color $editUIColorHov: pullForward(saturate($uiColor, 10%), 20%); // Hover color when $editUIColor is applied as a base color
$editUIBaseColor: #344b8d; // Base color, toolbar bg $editUIBaseColor: #344b8d; // Base color, toolbar bg
$editUIBaseColorHov: pullForward($editUIBaseColor, 20%); $editUIBaseColorHov: pullForward($editUIBaseColor, 20%);
$editUIBaseColorFg: #ffffff; // Toolbar button icon colors, etc. $editUIBaseColorFg: #ffffff; // Toolbar button icon colors, etc.
@ -178,10 +178,11 @@ $editFrameColor: $browseFrameColor; // Solid or dotted border applied to non-sel
$editFrameBorder: 1px dotted $editFrameColor; $editFrameBorder: 1px dotted $editFrameColor;
$editFrameColorHov: $editUIColor; // Solid border hover on frames; hover should not be applied to selected objects $editFrameColorHov: $editUIColor; // Solid border hover on frames; hover should not be applied to selected objects
$editFrameBorderHov: 1px solid $editFrameColorHov; // Hover on selectable frames $editFrameBorderHov: 1px solid $editFrameColorHov; // Hover on selectable frames
$editFrameColorSelected: #ffefc2; // Border of selected frames while editing $editFrameColorSelected: #ccc; // Border of selected frames
$editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout $editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout
$editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color $editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color
$editFrameSelectedShdw: rgba(black, 0.4) 0 1px 5px 1px; $editFrameSelectedShdw: rgba(black, 0.4) 0 1px 5px 1px;
$editFrameSelectedBorder: 1px solid $editFrameColorHov; // Selected frame element
$editFrameMovebarColorBg: $editFrameColor; // Movebar bg color $editFrameMovebarColorBg: $editFrameColor; // Movebar bg color
$editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%); // Grippy lines, container size text $editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%); // Grippy lines, container size text
$editFrameHovMovebarColorBg: pullForward($editFrameMovebarColorBg, 10%); // Hover style $editFrameHovMovebarColorBg: pullForward($editFrameMovebarColorBg, 10%); // Hover style
@ -190,7 +191,6 @@ $editFrameSelectedMovebarColorBg: pullForward($editFrameMovebarColorBg, 15%); //
$editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%); $editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%);
$editFrameMovebarH: 10px; // Height of move bar in layout frame $editFrameMovebarH: 10px; // Height of move bar in layout frame
$editMarqueeBorder: 1px dashed $editFrameColorSelected; $editMarqueeBorder: 1px dashed $editFrameColorSelected;
$editFrameSelectedBorder: $editMarqueeBorder; // Selected frame element
// Icons // Icons
$colorIconAlias: #4af6f3; $colorIconAlias: #4af6f3;

View File

@ -168,7 +168,7 @@ $borderMissing: 1px dashed $colorAlert !important;
$editUIColor: $uiColor; // Base color $editUIColor: $uiColor; // Base color
$editUIColorBg: $editUIColor; $editUIColorBg: $editUIColor;
$editUIColorFg: #fff; $editUIColorFg: #fff;
$editUIColorHov: pullForward(saturate($uiColor, 10%), 10%); // Hover color when $editUIColor is applied as a base color $editUIColorHov: pullForward(saturate($uiColor, 10%), 20%); // Hover color when $editUIColor is applied as a base color
$editUIBaseColor: #344b8d; // Base color, toolbar bg $editUIBaseColor: #344b8d; // Base color, toolbar bg
$editUIBaseColorHov: pullForward($editUIBaseColor, 20%); $editUIBaseColorHov: pullForward($editUIBaseColor, 20%);
$editUIBaseColorFg: #ffffff; // Toolbar button icon colors, etc. $editUIBaseColorFg: #ffffff; // Toolbar button icon colors, etc.
@ -182,10 +182,11 @@ $editFrameColor: $browseFrameColor; // Solid or dotted border applied to non-sel
$editFrameBorder: 1px dotted $editFrameColor; $editFrameBorder: 1px dotted $editFrameColor;
$editFrameColorHov: $editUIColor; // Solid border hover on frames; hover should not be applied to selected objects $editFrameColorHov: $editUIColor; // Solid border hover on frames; hover should not be applied to selected objects
$editFrameBorderHov: 1px solid $editFrameColorHov; // Hover on selectable frames $editFrameBorderHov: 1px solid $editFrameColorHov; // Hover on selectable frames
$editFrameColorSelected: #ffefc2; // Border of selected frames while editing $editFrameColorSelected: #ccc; // Border of selected frames
$editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout $editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout
$editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color $editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color
$editFrameSelectedShdw: rgba(black, 0.4) 0 1px 5px 1px; $editFrameSelectedShdw: rgba(black, 0.4) 0 1px 5px 1px;
$editFrameSelectedBorder: 1px solid $editFrameColorHov; // Selected frame element
$editFrameMovebarColorBg: $editFrameColor; // Movebar bg color $editFrameMovebarColorBg: $editFrameColor; // Movebar bg color
$editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%); // Grippy lines, container size text $editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%); // Grippy lines, container size text
$editFrameHovMovebarColorBg: pullForward($editFrameMovebarColorBg, 10%); // Hover style $editFrameHovMovebarColorBg: pullForward($editFrameMovebarColorBg, 10%); // Hover style
@ -194,7 +195,6 @@ $editFrameSelectedMovebarColorBg: pullForward($editFrameMovebarColorBg, 15%); //
$editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%); $editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%);
$editFrameMovebarH: 10px; // Height of move bar in layout frame $editFrameMovebarH: 10px; // Height of move bar in layout frame
$editMarqueeBorder: 1px dashed $editFrameColorSelected; $editMarqueeBorder: 1px dashed $editFrameColorSelected;
$editFrameSelectedBorder: $editMarqueeBorder; // Selected frame element
// Icons // Icons
$colorIconAlias: #4af6f3; $colorIconAlias: #4af6f3;

View File

@ -178,10 +178,11 @@ $editFrameColor: $browseFrameColor; // Solid or dotted border applied to non-sel
$editFrameBorder: 1px dotted $editFrameColor; $editFrameBorder: 1px dotted $editFrameColor;
$editFrameColorHov: $editUIColor; // Solid border hover on frames; hover should not be applied to selected objects $editFrameColorHov: $editUIColor; // Solid border hover on frames; hover should not be applied to selected objects
$editFrameBorderHov: 1px solid $editFrameColorHov; // Hover on selectable frames $editFrameBorderHov: 1px solid $editFrameColorHov; // Hover on selectable frames
$editFrameColorSelected: #ff7c00; // Border of selected frames $editFrameColorSelected: #333; // Border of selected frames
$editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout $editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout
$editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color $editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color
$editFrameSelectedShdw: rgba(black, 0.5) 0 1px 5px 2px; $editFrameSelectedShdw: rgba(black, 0.5) 0 1px 5px 2px;
$editFrameSelectedBorder: 1px dashed $editFrameColorSelected; // Selected frame element
$editFrameMovebarColorBg: $editFrameColor; // Movebar bg color $editFrameMovebarColorBg: $editFrameColor; // Movebar bg color
$editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%); // Grippy lines, container size text $editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%); // Grippy lines, container size text
$editFrameHovMovebarColorBg: pullForward($editFrameMovebarColorBg, 10%); // Hover style $editFrameHovMovebarColorBg: pullForward($editFrameMovebarColorBg, 10%); // Hover style
@ -190,7 +191,6 @@ $editFrameSelectedMovebarColorBg: pullForward($editFrameMovebarColorBg, 15%); //
$editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%); $editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%);
$editFrameMovebarH: 10px; // Height of move bar in layout frame $editFrameMovebarH: 10px; // Height of move bar in layout frame
$editMarqueeBorder: 1px dashed $editFrameColorSelected; $editMarqueeBorder: 1px dashed $editFrameColorSelected;
$editFrameSelectedBorder: 1px dashed $editMarqueeBorder; // Selected frame element
// Icons // Icons
$colorIconAlias: #4af6f3; $colorIconAlias: #4af6f3;

View File

@ -65,6 +65,7 @@ mct-plot {
.c-plot { .c-plot {
@include abs($mainViewPad); @include abs($mainViewPad);
display: flex; display: flex;
flex-direction: column;
overflow: hidden; overflow: hidden;
min-height: $plotMinH; min-height: $plotMinH;
@ -82,18 +83,11 @@ mct-plot {
} }
.c-plot--stacked-container { .c-plot--stacked-container {
border: 1px solid transparent;
display: flex; display: flex;
flex: 1 1 auto; flex: 1 1 auto;
flex-direction: column; flex-direction: column;
min-height: $plotMinH; min-height: $plotMinH;
overflow: hidden; overflow: hidden;
&[s-selected] {
.is-editing & {
border: $editMarqueeBorder;
}
}
} }
; ;

View File

@ -7,19 +7,12 @@ const webpack = require('webpack');
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const {VueLoaderPlugin} = require('vue-loader'); const {VueLoaderPlugin} = require('vue-loader');
let gitRevision = 'error-retrieving-revision'; const gitRevision = require('child_process')
let gitBranch = 'error-retrieving-branch'; .execSync('git rev-parse HEAD')
.toString().trim();
try { const gitBranch = require('child_process')
gitRevision = require('child_process') .execSync('git rev-parse --abbrev-ref HEAD')
.execSync('git rev-parse HEAD') .toString().trim();
.toString().trim();
gitBranch = require('child_process')
.execSync('git rev-parse --abbrev-ref HEAD')
.toString().trim();
} catch (err) {
console.warn(err);
}
/** @type {import('webpack').Configuration} */ /** @type {import('webpack').Configuration} */
const config = { const config = {
@ -33,10 +26,9 @@ const config = {
maelstromTheme: './src/plugins/themes/maelstrom-theme.scss' maelstromTheme: './src/plugins/themes/maelstrom-theme.scss'
}, },
output: { output: {
globalObject: 'this', globalObject: "this",
filename: '[name].js', filename: '[name].js',
path: path.resolve(__dirname, 'dist'), library: '[name]',
library: 'openmct',
libraryTarget: 'umd', libraryTarget: 'umd',
publicPath: '', publicPath: '',
hashFunction: 'xxhash64', hashFunction: 'xxhash64',

View File

@ -16,5 +16,5 @@ module.exports = merge(common, {
__OPENMCT_ROOT_RELATIVE__: '""' __OPENMCT_ROOT_RELATIVE__: '""'
}) })
], ],
devtool: 'eval-source-map' devtool: 'source-map'
}); });