From 0da35a44b05925944aa27745f578b8af2aee9fbe Mon Sep 17 00:00:00 2001 From: Shefali Joshi Date: Mon, 3 May 2021 12:33:19 -0700 Subject: [PATCH] Plots inspector using Vue (#3781) --- .../views/TypeInspectorViewProvider.js | 7 + .../plot/vue/inspector/PlotOptions.vue | 64 ++++ .../plot/vue/inspector/PlotOptionsBrowse.vue | 198 ++++++++++ .../plot/vue/inspector/PlotOptionsEdit.vue | 100 +++++ .../plot/vue/inspector/PlotOptionsItem.vue | 155 ++++++++ .../inspector/PlotsInspectorViewProvider.js | 51 +++ .../plot/vue/inspector/forms/LegendForm.vue | 207 +++++++++++ .../plot/vue/inspector/forms/SeriesForm.vue | 347 ++++++++++++++++++ .../plot/vue/inspector/forms/YAxisForm.vue | 226 ++++++++++++ .../plot/vue/inspector/forms/formUtil.js | 29 ++ src/plugins/plot/vue/single/plugin.js | 2 + src/plugins/plot/vue/single/pluginSpec.js | 245 +++++++++++++ src/plugins/timeline/TimelineObjectView.vue | 113 ++++++ src/plugins/timeline/TimelineViewLayout.vue | 25 +- src/ui/components/swim-lane/SwimLane.vue | 1 - 15 files changed, 1751 insertions(+), 19 deletions(-) create mode 100644 src/plugins/plot/vue/inspector/PlotOptions.vue create mode 100644 src/plugins/plot/vue/inspector/PlotOptionsBrowse.vue create mode 100644 src/plugins/plot/vue/inspector/PlotOptionsEdit.vue create mode 100644 src/plugins/plot/vue/inspector/PlotOptionsItem.vue create mode 100644 src/plugins/plot/vue/inspector/PlotsInspectorViewProvider.js create mode 100644 src/plugins/plot/vue/inspector/forms/LegendForm.vue create mode 100644 src/plugins/plot/vue/inspector/forms/SeriesForm.vue create mode 100644 src/plugins/plot/vue/inspector/forms/YAxisForm.vue create mode 100644 src/plugins/plot/vue/inspector/forms/formUtil.js create mode 100644 src/plugins/timeline/TimelineObjectView.vue diff --git a/src/adapter/views/TypeInspectorViewProvider.js b/src/adapter/views/TypeInspectorViewProvider.js index 6ff740e703..86aa9f2f9f 100644 --- a/src/adapter/views/TypeInspectorViewProvider.js +++ b/src/adapter/views/TypeInspectorViewProvider.js @@ -24,6 +24,13 @@ define([ return false; } + //TODO: Remove this when plots Angular implementation is deprecated + let parent = selection[0].length > 1 && selection[0][1].context.item; + if (parent && parent.type === 'time-strip') { + return (selectionContext.item.type === typeDefinition.key) + && (typeDefinition.key !== 'telemetry.plot.overlay'); + } + return selectionContext.item.type === typeDefinition.key; }, view: function (selection) { diff --git a/src/plugins/plot/vue/inspector/PlotOptions.vue b/src/plugins/plot/vue/inspector/PlotOptions.vue new file mode 100644 index 0000000000..20fd584197 --- /dev/null +++ b/src/plugins/plot/vue/inspector/PlotOptions.vue @@ -0,0 +1,64 @@ + + + + diff --git a/src/plugins/plot/vue/inspector/PlotOptionsBrowse.vue b/src/plugins/plot/vue/inspector/PlotOptionsBrowse.vue new file mode 100644 index 0000000000..7dfbecd202 --- /dev/null +++ b/src/plugins/plot/vue/inspector/PlotOptionsBrowse.vue @@ -0,0 +1,198 @@ + + + + diff --git a/src/plugins/plot/vue/inspector/PlotOptionsEdit.vue b/src/plugins/plot/vue/inspector/PlotOptionsEdit.vue new file mode 100644 index 0000000000..c96cc638ff --- /dev/null +++ b/src/plugins/plot/vue/inspector/PlotOptionsEdit.vue @@ -0,0 +1,100 @@ + + + diff --git a/src/plugins/plot/vue/inspector/PlotOptionsItem.vue b/src/plugins/plot/vue/inspector/PlotOptionsItem.vue new file mode 100644 index 0000000000..3db34a43db --- /dev/null +++ b/src/plugins/plot/vue/inspector/PlotOptionsItem.vue @@ -0,0 +1,155 @@ + + + diff --git a/src/plugins/plot/vue/inspector/PlotsInspectorViewProvider.js b/src/plugins/plot/vue/inspector/PlotsInspectorViewProvider.js new file mode 100644 index 0000000000..1a22cbdee6 --- /dev/null +++ b/src/plugins/plot/vue/inspector/PlotsInspectorViewProvider.js @@ -0,0 +1,51 @@ + +import PlotOptions from "./PlotOptions.vue"; +import Vue from 'vue'; + +export default function PlotsInspectorViewProvider(openmct) { + return { + key: 'plots-inspector', + name: 'Plots Inspector View', + canView: function (selection) { + if (selection.length === 0 || selection[0].length === 0) { + return false; + } + + let parent = selection[0].length > 1 && selection[0][1].context.item; + let object = selection[0][0].context.item; + + return parent + && parent.type === 'time-strip' + && object + && object.type === 'telemetry.plot.overlay'; + }, + view: function (selection) { + let component; + + return { + show: function (element) { + component = new Vue({ + el: element, + components: { + PlotOptions: PlotOptions + }, + provide: { + openmct, + domainObject: openmct.selection.get()[0][0].context.item + }, + template: '' + }); + }, + destroy: function () { + if (component) { + component.$destroy(); + component = undefined; + } + } + }; + }, + priority: function () { + return 1; + } + }; +} diff --git a/src/plugins/plot/vue/inspector/forms/LegendForm.vue b/src/plugins/plot/vue/inspector/forms/LegendForm.vue new file mode 100644 index 0000000000..d7c385d07c --- /dev/null +++ b/src/plugins/plot/vue/inspector/forms/LegendForm.vue @@ -0,0 +1,207 @@ + + diff --git a/src/plugins/plot/vue/inspector/forms/SeriesForm.vue b/src/plugins/plot/vue/inspector/forms/SeriesForm.vue new file mode 100644 index 0000000000..0f949ce984 --- /dev/null +++ b/src/plugins/plot/vue/inspector/forms/SeriesForm.vue @@ -0,0 +1,347 @@ + + + diff --git a/src/plugins/plot/vue/inspector/forms/YAxisForm.vue b/src/plugins/plot/vue/inspector/forms/YAxisForm.vue new file mode 100644 index 0000000000..9dd521387c --- /dev/null +++ b/src/plugins/plot/vue/inspector/forms/YAxisForm.vue @@ -0,0 +1,226 @@ + + + diff --git a/src/plugins/plot/vue/inspector/forms/formUtil.js b/src/plugins/plot/vue/inspector/forms/formUtil.js new file mode 100644 index 0000000000..9231fa5c6b --- /dev/null +++ b/src/plugins/plot/vue/inspector/forms/formUtil.js @@ -0,0 +1,29 @@ +export function coerce(value, coerceFunc) { + if (coerceFunc) { + return coerceFunc(value); + } + + return value; +} + +export function validate(value, model, validateFunc) { + if (validateFunc) { + return validateFunc(value, model); + } + + return true; +} + +export function objectPath(path) { + if (path) { + if (typeof path !== "function") { + const staticObjectPath = path; + + return function (object, model) { + return staticObjectPath; + }; + } + + return path; + } +} diff --git a/src/plugins/plot/vue/single/plugin.js b/src/plugins/plot/vue/single/plugin.js index 88278ce9fd..b3a0248fe3 100644 --- a/src/plugins/plot/vue/single/plugin.js +++ b/src/plugins/plot/vue/single/plugin.js @@ -23,12 +23,14 @@ import PlotViewProvider from './PlotViewProvider'; import OverlayPlotViewProvider from '../overlayPlot/OverlayPlotViewProvider'; import StackedPlotViewProvider from '../stackedPlot/StackedPlotViewProvider'; +import PlotsInspectorViewProvider from '../inspector/PlotsInspectorViewProvider'; export default function () { return function install(openmct) { openmct.objectViews.addProvider(new StackedPlotViewProvider(openmct)); openmct.objectViews.addProvider(new OverlayPlotViewProvider(openmct)); openmct.objectViews.addProvider(new PlotViewProvider(openmct)); + openmct.inspectorViews.addProvider(new PlotsInspectorViewProvider(openmct)); }; } diff --git a/src/plugins/plot/vue/single/pluginSpec.js b/src/plugins/plot/vue/single/pluginSpec.js index 25b7c23da8..aebf002cfd 100644 --- a/src/plugins/plot/vue/single/pluginSpec.js +++ b/src/plugins/plot/vue/single/pluginSpec.js @@ -26,6 +26,8 @@ import Vue from "vue"; import StackedPlot from "../stackedPlot/StackedPlot.vue"; import configStore from "@/plugins/plot/vue/single/configuration/configStore"; import EventEmitter from "EventEmitter"; +import PlotOptions from "../inspector/PlotOptions.vue"; +import PlotConfigurationModel from "@/plugins/plot/vue/single/configuration/PlotConfigurationModel"; describe("the plugin", function () { let element; @@ -174,6 +176,35 @@ describe("the plugin", function () { expect(plotView).toBeDefined(); }); + it('provides an inspector view for overlay plots', () => { + let selection = [ + [ + { + context: { + item: { + id: "test-object", + type: "telemetry.plot.overlay", + telemetry: { + values: [{ + key: "some-key" + }] + } + } + } + }, + { + context: { + item: { + type: 'time-strip' + } + } + } + ] + ]; + const plotInspectorView = openmct.inspectorViews.get(selection); + expect(plotInspectorView.length).toEqual(1); + }); + it("provides a stacked plot view for objects with telemetry", () => { const testTelemetryObject = { id: "test-object", @@ -578,4 +609,218 @@ describe("the plugin", function () { }); }); + describe('the 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: { + id: "test-object", + identifier: { + key: "test-object", + namespace: '' + }, + type: "telemetry.plot.overlay", + configuration: { + series: [ + { + identifier: { + key: "test-object", + namespace: '' + } + } + ] + }, + composition: [] + } + } + }, + { + context: { + item: { + type: 'time-strip', + identifier: { + key: 'some-other-key', + namespace: '' + } + } + } + } + ] + ]; + + 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: '' + }); + + Vue.nextTick(() => { + viewComponentObject = component.$root.$children[0]; + done(); + }); + }); + + describe('in view only mode', () => { + let browseOptionsEl; + let editOptionsEl; + beforeEach(() => { + browseOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-browse'); + editOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-edit'); + }); + + it('does not show the edit options', () => { + expect(editOptionsEl).toBeNull(); + }); + + it('shows the name', () => { + const seriesEl = browseOptionsEl.querySelector('.c-object-label__name'); + expect(seriesEl.innerHTML).toEqual(testTelemetryObject.name); + }); + + it('shows in collapsed mode', () => { + const seriesEl = browseOptionsEl.querySelectorAll('.c-disclosure-triangle--expanded'); + expect(seriesEl.length).toEqual(0); + }); + + it('shows in expanded mode', () => { + let expandControl = browseOptionsEl.querySelector(".c-disclosure-triangle"); + const clickEvent = createMouseEvent("click"); + expandControl.dispatchEvent(clickEvent); + + const plotOptionsProperties = browseOptionsEl.querySelectorAll('.js-plot-options-browse-properties .grid-row'); + expect(plotOptionsProperties.length).toEqual(5); + }); + }); + + describe('in edit mode', () => { + let editOptionsEl; + let browseOptionsEl; + + beforeEach((done) => { + viewComponentObject.setEditState(true); + Vue.nextTick(() => { + editOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-edit'); + browseOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-browse'); + done(); + }); + }); + + it('does not show the browse options', () => { + expect(browseOptionsEl).toBeNull(); + }); + + it('shows the name', () => { + const seriesEl = editOptionsEl.querySelector('.c-object-label__name'); + expect(seriesEl.innerHTML).toEqual(testTelemetryObject.name); + }); + + it('shows in collapsed mode', () => { + const seriesEl = editOptionsEl.querySelectorAll('.c-disclosure-triangle--expanded'); + expect(seriesEl.length).toEqual(0); + }); + + it('shows in collapsed mode', () => { + const seriesEl = editOptionsEl.querySelectorAll('.c-disclosure-triangle--expanded'); + expect(seriesEl.length).toEqual(0); + }); + + it('renders expanded', () => { + const expandControl = editOptionsEl.querySelector(".c-disclosure-triangle"); + const clickEvent = createMouseEvent("click"); + expandControl.dispatchEvent(clickEvent); + + const plotOptionsProperties = editOptionsEl.querySelectorAll(".js-plot-options-edit-properties .grid-row"); + expect(plotOptionsProperties.length).toEqual(6); + }); + + it('shows yKeyOptions', () => { + const expandControl = editOptionsEl.querySelector(".c-disclosure-triangle"); + const clickEvent = createMouseEvent("click"); + expandControl.dispatchEvent(clickEvent); + + const plotOptionsProperties = editOptionsEl.querySelectorAll(".js-plot-options-edit-properties .grid-row"); + + const yKeySelection = plotOptionsProperties[0].querySelector('select'); + const options = Array.from(yKeySelection.options).map((option) => { + return option.value; + }); + expect(options).toEqual([testTelemetryObject.telemetry.values[1].key, testTelemetryObject.telemetry.values[2].key]); + }); + + it('shows yAxis options', () => { + const expandControl = editOptionsEl.querySelector(".c-disclosure-triangle"); + const clickEvent = createMouseEvent("click"); + expandControl.dispatchEvent(clickEvent); + + const yAxisProperties = editOptionsEl.querySelectorAll("div.grid-properties:first-of-type .l-inspector-part"); + expect(yAxisProperties.length).toEqual(3); + }); + }); + }); + }); diff --git a/src/plugins/timeline/TimelineObjectView.vue b/src/plugins/timeline/TimelineObjectView.vue new file mode 100644 index 0000000000..27d8f92de0 --- /dev/null +++ b/src/plugins/timeline/TimelineObjectView.vue @@ -0,0 +1,113 @@ +/***************************************************************************** +* Open MCT, Copyright (c) 2014-2021, 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. +*****************************************************************************/ + + + diff --git a/src/plugins/timeline/TimelineViewLayout.vue b/src/plugins/timeline/TimelineViewLayout.vue index 671d204625..190e888eb1 100644 --- a/src/plugins/timeline/TimelineViewLayout.vue +++ b/src/plugins/timeline/TimelineViewLayout.vue @@ -52,22 +52,10 @@ :key="item.keyString" class="u-contents c-timeline__content" > - - - - + @@ -75,7 +63,7 @@