From 0f0c6a7b1772c5024e735f21afef0ab9ad153a14 Mon Sep 17 00:00:00 2001 From: Shefali Joshi Date: Wed, 8 Jun 2022 18:06:31 -0700 Subject: [PATCH] 2.0.4 merge into master (#5297) * Release 2.0.3 * Fix tick values for plots ticks in log mode and null check (#5119) * [2297] When there is no display range or range, skip setting the range value when auto scale is turned off. * If the formatted value is a number and a float, set precision to 2 decimal points. * Fix value assignment * Use whole numbers in log mode * Revert whole numbers fix - need floats for values between 0 and 1. * Handle scrolling to focused image on resize/new data (#5121) * Scroll to focused image when view resizes - this will force scrolling to focused image when going to/from view large mode * Scroll to the right if there is no paused focused image * [LAD Tables] Use Telemetry Collections (#5127) * Use telemetry collections to handle bounds checks * added telemetry collection to alphanumeric telemetry view (#5131) * Added animation styling for POS and CAM; adjusted cutoff for isNewImage (#5116) * Added animation styling for POS and CAM; adjusted cutoff for isNewImage * Remove animation from POS and CAM * Fix transactions overwriting latest objects with stale objects on save (#5132) * use object (map) instead of set to track dirty objects * fix tests due to internals change Co-authored-by: Nikhil * Gauge edit enabled 2.0.3 (#5133) * Gauge plugin #4896, add edit mode * Dynamic dial-type Gauge sizing by height and width (#5129) * Improve sizing strategy for gauges. * Do not install gauge by default for now Co-authored-by: Nikhil Co-authored-by: Jamie Vigliotta Co-authored-by: Andrew Henry * [Telemetry Collections] Include data with start and end bounds (#5145) * Reverts forced precision for log plots axis labels (#5147) * Condition Widgets trigger hundreds of persistence calls (#5146) Co-authored-by: unlikelyzero * Update version for 2.0.4 (#5255) * Eliminate NaN conditions and clear stale duration (#5248) * Temp source map fix 2.0.4 (#5267) * use dev mode for production * mode -> production * added extra devtool options * wip * Imagery Fixes for release/2.0.4 (#5282) * Fallback for height * Remove duplicated requestHistory call since setDataTimeContext already invokes it on mount * Inverted datumIsNotValid and refactored requestHistory * Remove old datumIsNotValid func * Return false if datum is falsy * Corrected brightness/contrast input * Clone default values to avoid mutation * Changed index of imageTelemetry to an item within bounds * Implement clearData test for imagery differently * x-out clearData tests Co-authored-by: Joshi * Imagery test fixes (#5293) * Fallback for height * Remove duplicated requestHistory call since setDataTimeContext already invokes it on mount * Inverted datumIsNotValid and refactored requestHistory * Remove old datumIsNotValid func * Return false if datum is falsy * Corrected brightness/contrast input * Clone default values to avoid mutation * Changed index of imageTelemetry to an item within bounds * Implement clearData test for imagery differently * x-out clearData tests * Set bounds on each test rather than the wrapper Co-authored-by: Michael Rogers * Imagery validation fix (#5295) * Remove check for duplicate images * Remove commented out code and add TODO * lint fix * Add missing tests * Use the master version and ignore release/2.0.4 changes Co-authored-by: Jamie V Co-authored-by: Michael Rogers Co-authored-by: David Tsay <3614296+davetsay@users.noreply.github.com> Co-authored-by: Nikhil Co-authored-by: Charles Hacskaylo Co-authored-by: Andrew Henry Co-authored-by: unlikelyzero --- e2e/tests/recycled_storage.json | 2 +- .../imagery/components/ImageControls.vue | 2 +- .../imagery/components/ImageryView.vue | 7 +- .../imagery/components/imagery-view.scss | 1 + src/plugins/imagery/mixins/imageryData.js | 70 ++++++------- src/plugins/imagery/pluginSpec.js | 99 +++++++++++-------- webpack.common.js | 5 +- webpack.prod.js | 2 +- 8 files changed, 104 insertions(+), 84 deletions(-) diff --git a/e2e/tests/recycled_storage.json b/e2e/tests/recycled_storage.json index c20b7ee73d..53c7695977 100644 --- a/e2e/tests/recycled_storage.json +++ b/e2e/tests/recycled_storage.json @@ -19,4 +19,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/src/plugins/imagery/components/ImageControls.vue b/src/plugins/imagery/components/ImageControls.vue index b14c13f8b2..eae50f6b49 100644 --- a/src/plugins/imagery/components/ImageControls.vue +++ b/src/plugins/imagery/components/ImageControls.vue @@ -173,7 +173,7 @@ export default { this.$emit('filtersUpdated', this.filters); }, handleResetFilters() { - this.filters = DEFAULT_FILTER_VALUES; + this.filters = {...DEFAULT_FILTER_VALUES}; this.notifyFiltersChanged(); }, limitZoomRange(factor) { diff --git a/src/plugins/imagery/components/ImageryView.vue b/src/plugins/imagery/components/ImageryView.vue index 8f011cf8ef..66d53669b3 100644 --- a/src/plugins/imagery/components/ImageryView.vue +++ b/src/plugins/imagery/components/ImageryView.vue @@ -403,6 +403,9 @@ export default { formattedDuration() { let result = 'N/A'; let negativeAge = -1; + if (!Number.isInteger(this.numericDuration)) { + return result; + } if (this.numericDuration > TWENTYFOUR_HOURS) { negativeAge *= (this.numericDuration / TWENTYFOUR_HOURS); @@ -905,8 +908,10 @@ export default { let currentTime = this.timeContext.clock() && this.timeContext.clock().currentValue(); if (currentTime === undefined) { this.numericDuration = currentTime; - } else { + } else if (Number.isInteger(this.parsedSelectedTime)) { this.numericDuration = currentTime - this.parsedSelectedTime; + } else { + this.numericDuration = undefined; } }, resetAgeCSS() { diff --git a/src/plugins/imagery/components/imagery-view.scss b/src/plugins/imagery/components/imagery-view.scss index a2bcd7ad15..50895d7e32 100644 --- a/src/plugins/imagery/components/imagery-view.scss +++ b/src/plugins/imagery/components/imagery-view.scss @@ -72,6 +72,7 @@ background-position: center; background-repeat: no-repeat; background-size: contain; + height: 100%; //fallback value } &__image { // Present to allow Save As... image diff --git a/src/plugins/imagery/mixins/imageryData.js b/src/plugins/imagery/mixins/imageryData.js index 64137d46d9..c5251d1218 100644 --- a/src/plugins/imagery/mixins/imageryData.js +++ b/src/plugins/imagery/mixins/imageryData.js @@ -70,22 +70,18 @@ export default { this.timeContext.off('timeSystem', this.timeSystemChange); } }, - datumIsNotValid(datum) { - if (this.imageHistory.length === 0) { + isDatumValid(datum) { + //TODO: Add a check to see if there are duplicate images (identical image timestamp and url subsequently) + if (!datum) { 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 historyTimeCheck = this.parseTime(this.imageHistory.slice(-1)[0]); - const matchesLast = (datumTimeCheck === historyTimeCheck) && (datumURL === lastHistoryURL); - const isStale = datumTimeCheck < historyTimeCheck; + const bounds = this.timeContext.bounds(); - return matchesLast || isStale; + const isOutOfBounds = datumTimeCheck < bounds.start || datumTimeCheck > bounds.end; + + return !isOutOfBounds; }, formatImageUrl(datum) { if (!datum) { @@ -132,25 +128,19 @@ export default { return this.requestHistory(); }, async requestHistory() { - let bounds = this.timeContext.bounds(); this.requestCount++; const requestId = this.requestCount; - this.imageHistory = []; + const bounds = this.timeContext.bounds(); - let data = await this.openmct.telemetry + const data = await this.openmct.telemetry .request(this.domainObject, bounds) || []; - - if (this.requestCount === requestId) { - 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; + // wait until new request resolves to do comparison + if (this.requestCount !== requestId) { + return this.imageHistory = []; } + + const imagery = data.filter(this.isDatumValid).map(this.normalizeDatum); + this.imageHistory = imagery; }, clearData(domainObjectToClear) { // global clearData button is accepted therefore no truthy check on inputted param @@ -180,27 +170,29 @@ export default { .subscribe(this.domainObject, (datum) => { let parsedTimestamp = this.parseTime(datum); let bounds = this.timeContext.bounds(); + if (!(parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end)) { + return; + } - if (parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end) { - let image = this.normalizeDatum(datum); - if (image) { - this.imageHistory.push(image); - } + if (this.isDatumValid(datum)) { + this.imageHistory.push(this.normalizeDatum(datum)); } }); }, normalizeDatum(datum) { - if (this.datumIsNotValid(datum)) { - return; - } - let image = { ...datum }; - image.formattedTime = this.formatTime(datum); - image.url = this.formatImageUrl(datum); - image.time = this.parseTime(image.formattedTime); - image.imageDownloadName = this.getImageDownloadName(datum); + const formattedTime = this.formatTime(datum); + const url = this.formatImageUrl(datum); + const time = this.parseTime(formattedTime); + const imageDownloadName = this.getImageDownloadName(datum); - return image; + return { + ...datum, + formattedTime, + url, + time, + imageDownloadName + }; }, getFormatter(key) { let metadataValue = this.metadata.value(key) || { format: key }; diff --git a/src/plugins/imagery/pluginSpec.js b/src/plugins/imagery/pluginSpec.js index aa2a284d43..44167b3b0a 100644 --- a/src/plugins/imagery/pluginSpec.js +++ b/src/plugins/imagery/pluginSpec.js @@ -84,7 +84,6 @@ describe("The Imagery View Layouts", () => { let telemetryPromise; let telemetryPromiseResolve; let cleanupFirst; - let isClearDataTriggered; let openmct; let parent; @@ -205,20 +204,12 @@ describe("The Imagery View Layouts", () => { cleanupFirst = []; openmct = createOpenMct(); - openmct.time.timeSystem('utc', { - start: START - (5 * ONE_MINUTE), - end: START + (5 * ONE_MINUTE) - }); telemetryPromise = new Promise((resolve) => { telemetryPromiseResolve = resolve; }); spyOn(openmct.telemetry, 'request').and.callFake(() => { - if (isClearDataTriggered) { - return []; - } - telemetryPromiseResolve(imageTelemetry); return telemetryPromise; @@ -337,44 +328,93 @@ describe("The Imagery View Layouts", () => { expect(imageryView).toBeDefined(); }); - describe("imagery view", () => { + describe("Clear data action for imagery", () => { let applicableViews; let imageryViewProvider; let imageryView; + let componentView; let clearDataPlugin; let clearDataAction; 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); + componentView = imageryView._getInstance().$children[0]; + clearDataPlugin = new ClearDataPlugin( ['example.imagery'], {indicator: true} ); openmct.install(clearDataPlugin); 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 + 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; 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", (done) => { + it("on mount should show the the most recent image", () => { //Looks like we need Vue.nextTick here so that computed properties settle down - Vue.nextTick(() => { + return Vue.nextTick(() => { const imageInfo = getImageInfo(parent); expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1); - done(); }); }); @@ -422,7 +462,7 @@ describe("The Imagery View Layouts", () => { it("should show that an image is not new", (done) => { Vue.nextTick(() => { - const target = imageTelemetry[2].url; + const target = imageTelemetry[4].url; parent.querySelectorAll(`img[src='${target}']`)[0].click(); Vue.nextTick(() => { @@ -544,25 +584,6 @@ describe("The Imagery View Layouts", () => { expect(imageSizeAfter.width).toBeLessThan(imageSizeBefore.width); 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", () => { diff --git a/webpack.common.js b/webpack.common.js index 3400ade160..38e9e8644d 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -26,9 +26,10 @@ const config = { maelstromTheme: './src/plugins/themes/maelstrom-theme.scss' }, output: { - globalObject: "this", + globalObject: 'this', filename: '[name].js', - library: '[name]', + path: path.resolve(__dirname, 'dist'), + library: 'openmct', libraryTarget: 'umd', publicPath: '', hashFunction: 'xxhash64', diff --git a/webpack.prod.js b/webpack.prod.js index a64d4ce548..362b4eb505 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -16,5 +16,5 @@ module.exports = merge(common, { __OPENMCT_ROOT_RELATIVE__: '""' }) ], - devtool: 'source-map' + devtool: 'eval-source-map' });