mirror of
https://github.com/nasa/openmct.git
synced 2025-05-21 17:57:39 +00:00
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 <nikhil.k.mandlik@nasa.gov> * 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 <nikhil.k.mandlik@nasa.gov> Co-authored-by: Jamie Vigliotta <jamie.j.vigliotta@nasa.gov> Co-authored-by: Andrew Henry <akhenry@gmail.com> * [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 <jchill2@gmail.com> * 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 <simplyrender@gmail.com> * 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 <contact@mhrogers.com> * 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 <jamie.j.vigliotta@nasa.gov> Co-authored-by: Michael Rogers <contact@mhrogers.com> Co-authored-by: David Tsay <3614296+davetsay@users.noreply.github.com> Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov> Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com> Co-authored-by: Andrew Henry <akhenry@gmail.com> Co-authored-by: unlikelyzero <jchill2@gmail.com>
This commit is contained in:
parent
370e6a0c37
commit
0f0c6a7b17
@ -173,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) {
|
||||||
|
@ -403,6 +403,9 @@ 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);
|
||||||
@ -905,8 +908,10 @@ 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 {
|
} else if (Number.isInteger(this.parsedSelectedTime)) {
|
||||||
this.numericDuration = currentTime - this.parsedSelectedTime;
|
this.numericDuration = currentTime - this.parsedSelectedTime;
|
||||||
|
} else {
|
||||||
|
this.numericDuration = undefined;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resetAgeCSS() {
|
resetAgeCSS() {
|
||||||
|
@ -72,6 +72,7 @@
|
|||||||
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
|
// Present to allow Save As... image
|
||||||
|
@ -70,22 +70,18 @@ export default {
|
|||||||
this.timeContext.off('timeSystem', this.timeSystemChange);
|
this.timeContext.off('timeSystem', this.timeSystemChange);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
datumIsNotValid(datum) {
|
isDatumValid(datum) {
|
||||||
if (this.imageHistory.length === 0) {
|
//TODO: Add a check to see if there are duplicate images (identical image timestamp and url subsequently)
|
||||||
|
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 historyTimeCheck = this.parseTime(this.imageHistory.slice(-1)[0]);
|
const bounds = this.timeContext.bounds();
|
||||||
const matchesLast = (datumTimeCheck === historyTimeCheck) && (datumURL === lastHistoryURL);
|
|
||||||
const isStale = datumTimeCheck < historyTimeCheck;
|
|
||||||
|
|
||||||
return matchesLast || isStale;
|
const isOutOfBounds = datumTimeCheck < bounds.start || datumTimeCheck > bounds.end;
|
||||||
|
|
||||||
|
return !isOutOfBounds;
|
||||||
},
|
},
|
||||||
formatImageUrl(datum) {
|
formatImageUrl(datum) {
|
||||||
if (!datum) {
|
if (!datum) {
|
||||||
@ -132,25 +128,19 @@ 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;
|
||||||
this.imageHistory = [];
|
const bounds = this.timeContext.bounds();
|
||||||
|
|
||||||
let data = await this.openmct.telemetry
|
const 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) {
|
if (this.requestCount !== requestId) {
|
||||||
let imagery = [];
|
return this.imageHistory = [];
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const imagery = data.filter(this.isDatumValid).map(this.normalizeDatum);
|
||||||
|
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
|
||||||
@ -180,27 +170,29 @@ 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 (parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end) {
|
if (this.isDatumValid(datum)) {
|
||||||
let image = this.normalizeDatum(datum);
|
this.imageHistory.push(this.normalizeDatum(datum));
|
||||||
if (image) {
|
|
||||||
this.imageHistory.push(image);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
normalizeDatum(datum) {
|
normalizeDatum(datum) {
|
||||||
if (this.datumIsNotValid(datum)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let image = { ...datum };
|
const formattedTime = this.formatTime(datum);
|
||||||
image.formattedTime = this.formatTime(datum);
|
const url = this.formatImageUrl(datum);
|
||||||
image.url = this.formatImageUrl(datum);
|
const time = this.parseTime(formattedTime);
|
||||||
image.time = this.parseTime(image.formattedTime);
|
const imageDownloadName = this.getImageDownloadName(datum);
|
||||||
image.imageDownloadName = this.getImageDownloadName(datum);
|
|
||||||
|
|
||||||
return image;
|
return {
|
||||||
|
...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 };
|
||||||
|
@ -84,7 +84,6 @@ 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;
|
||||||
@ -205,20 +204,12 @@ 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;
|
||||||
@ -337,44 +328,93 @@ describe("The Imagery View Layouts", () => {
|
|||||||
expect(imageryView).toBeDefined();
|
expect(imageryView).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("imagery view", () => {
|
describe("Clear data action for imagery", () => {
|
||||||
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", (done) => {
|
it("on mount should show the the most recent image", () => {
|
||||||
//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
|
||||||
Vue.nextTick(() => {
|
return 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();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -422,7 +462,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[2].url;
|
const target = imageTelemetry[4].url;
|
||||||
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
@ -544,25 +584,6 @@ 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", () => {
|
||||||
|
@ -26,9 +26,10 @@ 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',
|
||||||
library: '[name]',
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
library: 'openmct',
|
||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
publicPath: '',
|
publicPath: '',
|
||||||
hashFunction: 'xxhash64',
|
hashFunction: 'xxhash64',
|
||||||
|
@ -16,5 +16,5 @@ module.exports = merge(common, {
|
|||||||
__OPENMCT_ROOT_RELATIVE__: '""'
|
__OPENMCT_ROOT_RELATIVE__: '""'
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
devtool: 'source-map'
|
devtool: 'eval-source-map'
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user