Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nasa/openmct.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShefali <simplyrender@gmail.com>2022-08-24 19:23:37 +0300
committerShefali <simplyrender@gmail.com>2022-08-24 19:23:37 +0300
commit7bccb73729d8375d44a9ece9d39b8d1963fc3acf (patch)
tree2a040cd4bb85015d2048cd97e5aea5ed2e8f677e
parent5a4dd1195530461cce6bac28dc3979b76583ea21 (diff)
parent78df5fc2a5f0323e4742860f6c032cbcc23f21f6 (diff)
Merge branch 'release/2.0.8' of https://github.com/nasa/openmct into release/2.0.8v2.0.8
-rw-r--r--e2e/tests/functional/plugins/timeConductor/timeConductor.e2e.spec.js20
-rw-r--r--e2e/tests/functional/search.e2e.spec.js13
-rw-r--r--src/api/objects/InMemorySearchProvider.js6
-rw-r--r--src/plugins/charts/scatter/ScatterPlotView.vue9
-rw-r--r--src/plugins/timeConductor/ConductorHistory.vue32
-rw-r--r--src/plugins/timeConductor/pluginSpec.js8
-rw-r--r--src/ui/layout/search/GrandSearchSpec.js76
-rw-r--r--src/utils/duration.js16
8 files changed, 144 insertions, 36 deletions
diff --git a/e2e/tests/functional/plugins/timeConductor/timeConductor.e2e.spec.js b/e2e/tests/functional/plugins/timeConductor/timeConductor.e2e.spec.js
index 59d317170..ea50e8530 100644
--- a/e2e/tests/functional/plugins/timeConductor/timeConductor.e2e.spec.js
+++ b/e2e/tests/functional/plugins/timeConductor/timeConductor.e2e.spec.js
@@ -147,4 +147,24 @@ test.describe('Time conductor input fields real-time mode', () => {
expect(page.url()).toContain(`startDelta=${startDelta}`);
expect(page.url()).toContain(`endDelta=${endDelta}`);
});
+
+ test.fixme('time conductor history in fixed time mode will track changing start and end times', async ({ page }) => {
+ // change start time, verify it's tracked in history
+ // change end time, verify it's tracked in history
+ });
+
+ test.fixme('time conductor history in realtime mode will track changing start and end times', async ({ page }) => {
+ // change start offset, verify it's tracked in history
+ // change end offset, verify it's tracked in history
+ });
+
+ test.fixme('time conductor history allows you to set a historical timeframe', async ({ page }) => {
+ // make sure there are historical history options
+ // select an option and make sure the time conductor start and end bounds are updated correctly
+ });
+
+ test.fixme('time conductor history allows you to set a realtime offsets', async ({ page }) => {
+ // make sure there are realtime history options
+ // select an option and verify the offsets are updated correctly
+ });
});
diff --git a/e2e/tests/functional/search.e2e.spec.js b/e2e/tests/functional/search.e2e.spec.js
index 0daa1b3f9..38e06d5e5 100644
--- a/e2e/tests/functional/search.e2e.spec.js
+++ b/e2e/tests/functional/search.e2e.spec.js
@@ -24,6 +24,8 @@
*/
const { test, expect } = require('../../pluginFixtures');
+const { createDomainObjectWithDefaults } = require('../../appActions');
+const { v4: uuid } = require('uuid');
test.describe('Grand Search', () => {
test('Can search for objects, and subsequent search dropdown behaves properly', async ({ page, openmctConfig }) => {
@@ -112,13 +114,16 @@ test.describe("Search Tests @unstable", () => {
await expect(page.locator('text=No matching results.')).toBeVisible();
});
- test('Validate single object in search result', async ({ page }) => {
+ test('Validate single object in search result @couchdb', async ({ page }) => {
//Go to baseURL
await page.goto("./", { waitUntil: "networkidle" });
// Create a folder object
- const folderName = 'testFolder';
- await createFolderObject(page, folderName);
+ const folderName = uuid();
+ await createDomainObjectWithDefaults(page, {
+ type: 'folder',
+ name: folderName
+ });
// Full search for object
await page.type("input[type=search]", folderName);
@@ -127,7 +132,7 @@ test.describe("Search Tests @unstable", () => {
await waitForSearchCompletion(page);
// Get the search results
- const searchResults = await page.locator(searchResultSelector);
+ const searchResults = page.locator(searchResultSelector);
// Verify that one result is found
expect(await searchResults.count()).toBe(1);
diff --git a/src/api/objects/InMemorySearchProvider.js b/src/api/objects/InMemorySearchProvider.js
index f6d4fb04e..6feadaf44 100644
--- a/src/api/objects/InMemorySearchProvider.js
+++ b/src/api/objects/InMemorySearchProvider.js
@@ -295,7 +295,11 @@ class InMemorySearchProvider {
// using structuredClone. Thus we're using JSON.parse/JSON.stringify to discard
// those functions.
const nonMutableDomainObject = JSON.parse(JSON.stringify(newDomainObjectToIndex));
- provider.index(nonMutableDomainObject);
+
+ const objectProvider = this.openmct.objects.getProvider(nonMutableDomainObject.identifier);
+ if (objectProvider === undefined || objectProvider.search === undefined) {
+ provider.index(nonMutableDomainObject);
+ }
}
onCompositionRemoved(domainObjectToRemoveIdentifier) {
diff --git a/src/plugins/charts/scatter/ScatterPlotView.vue b/src/plugins/charts/scatter/ScatterPlotView.vue
index f6a69228e..129a3bca9 100644
--- a/src/plugins/charts/scatter/ScatterPlotView.vue
+++ b/src/plugins/charts/scatter/ScatterPlotView.vue
@@ -97,11 +97,11 @@ export default {
},
followTimeContext() {
- this.timeContext.on('bounds', this.reloadTelemetry);
+ this.timeContext.on('bounds', this.reloadTelemetryOnBoundsChange);
},
stopFollowingTimeContext() {
if (this.timeContext) {
- this.timeContext.off('bounds', this.reloadTelemetry);
+ this.timeContext.off('bounds', this.reloadTelemetryOnBoundsChange);
}
},
addToComposition(telemetryObject) {
@@ -181,6 +181,11 @@ export default {
this.composition.on('remove', this.removeTelemetryObject);
this.composition.load();
},
+ reloadTelemetryOnBoundsChange(bounds, isTick) {
+ if (!isTick) {
+ this.reloadTelemetry();
+ }
+ },
reloadTelemetry() {
this.valuesByTimestamp = {};
diff --git a/src/plugins/timeConductor/ConductorHistory.vue b/src/plugins/timeConductor/ConductorHistory.vue
index a91accfa2..8df1925e8 100644
--- a/src/plugins/timeConductor/ConductorHistory.vue
+++ b/src/plugins/timeConductor/ConductorHistory.vue
@@ -39,7 +39,7 @@
const DEFAULT_DURATION_FORMATTER = 'duration';
const LOCAL_STORAGE_HISTORY_KEY_FIXED = 'tcHistory';
const LOCAL_STORAGE_HISTORY_KEY_REALTIME = 'tcHistoryRealtime';
-const DEFAULT_RECORDS = 10;
+const DEFAULT_RECORDS_LENGTH = 10;
import { millisecondsToDHMS } from "utils/duration";
import UTCTimeFormat from "../utcTimeSystem/UTCTimeFormat.js";
@@ -79,16 +79,14 @@ export default {
* @timespans {start, end} number representing timestamp
*/
fixedHistory: {},
- presets: []
+ presets: [],
+ isFixed: this.openmct.time.clock() === undefined
};
},
computed: {
currentHistory() {
return this.mode + 'History';
},
- isFixed() {
- return this.openmct.time.clock() === undefined;
- },
historyForCurrentTimeSystem() {
const history = this[this.currentHistory][this.timeSystem.key];
@@ -96,7 +94,7 @@ export default {
},
storageKey() {
let key = LOCAL_STORAGE_HISTORY_KEY_FIXED;
- if (this.mode !== 'fixed') {
+ if (!this.isFixed) {
key = LOCAL_STORAGE_HISTORY_KEY_REALTIME;
}
@@ -108,6 +106,7 @@ export default {
handler() {
// only for fixed time since we track offsets for realtime
if (this.isFixed) {
+ this.updateMode();
this.addTimespan();
}
},
@@ -115,28 +114,35 @@ export default {
},
offsets: {
handler() {
+ this.updateMode();
this.addTimespan();
},
deep: true
},
timeSystem: {
handler(ts) {
+ this.updateMode();
this.loadConfiguration();
this.addTimespan();
},
deep: true
},
mode: function () {
- this.getHistoryFromLocalStorage();
- this.initializeHistoryIfNoHistory();
+ this.updateMode();
this.loadConfiguration();
}
},
mounted() {
+ this.updateMode();
this.getHistoryFromLocalStorage();
this.initializeHistoryIfNoHistory();
},
methods: {
+ updateMode() {
+ this.isFixed = this.openmct.time.clock() === undefined;
+ this.getHistoryFromLocalStorage();
+ this.initializeHistoryIfNoHistory();
+ },
getHistoryMenuItems() {
const history = this.historyForCurrentTimeSystem.map(timespan => {
let name;
@@ -203,8 +209,8 @@ export default {
currentHistory = currentHistory.filter(ts => !(ts.start === timespan.start && ts.end === timespan.end));
currentHistory.unshift(timespan); // add to front
- if (currentHistory.length > this.records) {
- currentHistory.length = this.records;
+ if (currentHistory.length > this.MAX_RECORDS_LENGTH) {
+ currentHistory.length = this.MAX_RECORDS_LENGTH;
}
this.$set(this[this.currentHistory], key, currentHistory);
@@ -231,7 +237,7 @@ export default {
.filter(option => option.timeSystem === this.timeSystem.key);
this.presets = this.loadPresets(configurations);
- this.records = this.loadRecords(configurations);
+ this.MAX_RECORDS_LENGTH = this.loadRecords(configurations);
},
loadPresets(configurations) {
const configuration = configurations.find(option => {
@@ -243,9 +249,9 @@ export default {
},
loadRecords(configurations) {
const configuration = configurations.find(option => option.records);
- const records = configuration ? configuration.records : DEFAULT_RECORDS;
+ const maxRecordsLength = configuration ? configuration.records : DEFAULT_RECORDS_LENGTH;
- return records;
+ return maxRecordsLength;
},
formatTime(time) {
let format = this.timeSystem.timeFormat;
diff --git a/src/plugins/timeConductor/pluginSpec.js b/src/plugins/timeConductor/pluginSpec.js
index d39478413..3d12594fe 100644
--- a/src/plugins/timeConductor/pluginSpec.js
+++ b/src/plugins/timeConductor/pluginSpec.js
@@ -131,15 +131,15 @@ describe('time conductor', () => {
describe('duration functions', () => {
it('should transform milliseconds to DHMS', () => {
const functionResults = [millisecondsToDHMS(0), millisecondsToDHMS(86400000),
- millisecondsToDHMS(129600000), millisecondsToDHMS(661824000)];
- const validResults = [' ', '+ 1d', '+ 1d 12h', '+ 7d 15h 50m 24s'];
+ millisecondsToDHMS(129600000), millisecondsToDHMS(661824000), millisecondsToDHMS(213927028)];
+ const validResults = [' ', '+ 1d', '+ 1d 12h', '+ 7d 15h 50m 24s', '+ 2d 11h 25m 27s 28ms'];
expect(validResults).toEqual(functionResults);
});
it('should get precise duration', () => {
const functionResults = [getPreciseDuration(0), getPreciseDuration(643680000),
- getPreciseDuration(1605312000)];
- const validResults = ['00:00:00:00', '07:10:48:00', '18:13:55:12'];
+ getPreciseDuration(1605312000), getPreciseDuration(213927028)];
+ const validResults = ['00:00:00:00:000', '07:10:48:00:000', '18:13:55:12:000', '02:11:25:27:028'];
expect(validResults).toEqual(functionResults);
});
});
diff --git a/src/ui/layout/search/GrandSearchSpec.js b/src/ui/layout/search/GrandSearchSpec.js
index 32d719b11..5235fc2b3 100644
--- a/src/ui/layout/search/GrandSearchSpec.js
+++ b/src/ui/layout/search/GrandSearchSpec.js
@@ -42,6 +42,8 @@ describe("GrandSearch", () => {
let mockAnotherFolderObject;
let mockTopObject;
let originalRouterPath;
+ let mockNewObject;
+ let mockObjectProvider;
beforeEach((done) => {
openmct = createOpenMct();
@@ -55,6 +57,7 @@ describe("GrandSearch", () => {
mockDomainObject = {
type: 'notebook',
name: 'fooRabbitNotebook',
+ location: 'fooNameSpace:topObject',
identifier: {
key: 'some-object',
namespace: 'fooNameSpace'
@@ -75,6 +78,7 @@ describe("GrandSearch", () => {
mockTopObject = {
type: 'root',
name: 'Top Folder',
+ composition: [],
identifier: {
key: 'topObject',
namespace: 'fooNameSpace'
@@ -83,6 +87,7 @@ describe("GrandSearch", () => {
mockAnotherFolderObject = {
type: 'folder',
name: 'Another Test Folder',
+ composition: [],
location: 'fooNameSpace:topObject',
identifier: {
key: 'someParent',
@@ -92,6 +97,7 @@ describe("GrandSearch", () => {
mockFolderObject = {
type: 'folder',
name: 'Test Folder',
+ composition: [],
location: 'fooNameSpace:someParent',
identifier: {
key: 'someFolder',
@@ -101,6 +107,7 @@ describe("GrandSearch", () => {
mockDisplayLayout = {
type: 'layout',
name: 'Bar Layout',
+ composition: [],
identifier: {
key: 'some-layout',
namespace: 'fooNameSpace'
@@ -125,9 +132,19 @@ describe("GrandSearch", () => {
}
}
};
+ mockNewObject = {
+ type: 'folder',
+ name: 'New Apple Test Folder',
+ composition: [],
+ location: 'fooNameSpace:topObject',
+ identifier: {
+ key: 'newApple',
+ namespace: 'fooNameSpace'
+ }
+ };
openmct.router.isNavigatedObject = jasmine.createSpy().and.returnValue(false);
- const mockObjectProvider = jasmine.createSpyObj("mock object provider", [
+ mockObjectProvider = jasmine.createSpyObj("mock object provider", [
"create",
"update",
"get"
@@ -146,6 +163,8 @@ describe("GrandSearch", () => {
return mockAnotherFolderObject;
} else if (identifier.key === mockTopObject.identifier.key) {
return mockTopObject;
+ } else if (identifier.key === mockNewObject.identifier.key) {
+ return mockNewObject;
} else {
return null;
}
@@ -168,6 +187,7 @@ describe("GrandSearch", () => {
// use local worker
sharedWorkerToRestore = openmct.objects.inMemorySearchProvider.worker;
openmct.objects.inMemorySearchProvider.worker = null;
+ await openmct.objects.inMemorySearchProvider.index(mockTopObject);
await openmct.objects.inMemorySearchProvider.index(mockDomainObject);
await openmct.objects.inMemorySearchProvider.index(mockDisplayLayout);
await openmct.objects.inMemorySearchProvider.index(mockFolderObject);
@@ -196,6 +216,7 @@ describe("GrandSearch", () => {
openmct.objects.inMemorySearchProvider.worker = sharedWorkerToRestore;
openmct.router.path = originalRouterPath;
grandSearchComponent.$destroy();
+ document.body.removeChild(parent);
return resetApplicationState(openmct);
});
@@ -203,25 +224,62 @@ describe("GrandSearch", () => {
it("should render an object search result", async () => {
await grandSearchComponent.$children[0].searchEverything('foo');
await Vue.nextTick();
- const searchResult = document.querySelector('[aria-label="fooRabbitNotebook notebook result"]');
- expect(searchResult).toBeDefined();
+ const searchResults = document.querySelectorAll('[aria-label="fooRabbitNotebook notebook result"]');
+ expect(searchResults.length).toBe(1);
+ expect(searchResults[0].innerText).toContain('Rabbit');
+ });
+
+ it("should render an object search result if new object added", async () => {
+ const composition = openmct.composition.get(mockFolderObject);
+ composition.add(mockNewObject);
+ await grandSearchComponent.$children[0].searchEverything('apple');
+ await Vue.nextTick();
+ const searchResults = document.querySelectorAll('[aria-label="New Apple Test Folder folder result"]');
+ expect(searchResults.length).toBe(1);
+ expect(searchResults[0].innerText).toContain('Apple');
+ });
+
+ it("should not use InMemorySearch provider if object provider provides search", async () => {
+ // eslint-disable-next-line require-await
+ mockObjectProvider.search = async (query, abortSignal, searchType) => {
+ if (searchType === openmct.objects.SEARCH_TYPES.OBJECTS) {
+ return mockNewObject;
+ } else {
+ return [];
+ }
+ };
+
+ mockObjectProvider.supportsSearchType = (someType) => {
+ return true;
+ };
+
+ const composition = openmct.composition.get(mockFolderObject);
+ composition.add(mockNewObject);
+ await grandSearchComponent.$children[0].searchEverything('apple');
+ await Vue.nextTick();
+ const searchResults = document.querySelectorAll('[aria-label="New Apple Test Folder folder result"]');
+ // This will be of length 2 (doubles) if we're incorrectly searching with InMemorySearchProvider as well
+ expect(searchResults.length).toBe(1);
+ expect(searchResults[0].innerText).toContain('Apple');
});
it("should render an annotation search result", async () => {
await grandSearchComponent.$children[0].searchEverything('S');
await Vue.nextTick();
- const annotationResult = document.querySelector('[aria-label="Search Result"]');
- expect(annotationResult).toBeDefined();
+ const annotationResults = document.querySelectorAll('[aria-label="Search Result"]');
+ expect(annotationResults.length).toBe(2);
+ expect(annotationResults[1].innerText).toContain('Driving');
});
it("should preview object search results in edit mode if object clicked", async () => {
await grandSearchComponent.$children[0].searchEverything('Folder');
grandSearchComponent._provided.openmct.router.path = [mockDisplayLayout];
await Vue.nextTick();
- const searchResult = document.querySelector('[name="Test Folder"]');
- expect(searchResult).toBeDefined();
- searchResult.click();
+ const searchResults = document.querySelectorAll('[name="Test Folder"]');
+ expect(searchResults.length).toBe(1);
+ expect(searchResults[0].innerText).toContain('Folder');
+ searchResults[0].click();
const previewWindow = document.querySelector('.js-preview-window');
- expect(previewWindow).toBeDefined();
+ expect(previewWindow.innerText).toContain('Snapshot');
});
});
diff --git a/src/utils/duration.js b/src/utils/duration.js
index 708d4b786..70b98378b 100644
--- a/src/utils/duration.js
+++ b/src/utils/duration.js
@@ -32,8 +32,16 @@ function normalizeAge(num) {
return isWhole ? hundredtized / 100 : num;
}
+function padLeadingZeros(num, numOfLeadingZeros) {
+ return num.toString().padStart(numOfLeadingZeros, '0');
+}
+
function toDoubleDigits(num) {
- return num >= 10 ? num : `0${num}`;
+ return padLeadingZeros(num, 2);
+}
+
+function toTripleDigits(num) {
+ return padLeadingZeros(num, 3);
}
function addTimeSuffix(value, suffix) {
@@ -46,7 +54,8 @@ export function millisecondsToDHMS(numericDuration) {
addTimeSuffix(Math.floor(normalizeAge(ms / ONE_DAY)), 'd'),
addTimeSuffix(Math.floor(normalizeAge((ms % ONE_DAY) / ONE_HOUR)), 'h'),
addTimeSuffix(Math.floor(normalizeAge((ms % ONE_HOUR) / ONE_MINUTE)), 'm'),
- addTimeSuffix(Math.floor(normalizeAge((ms % ONE_MINUTE) / ONE_SECOND)), 's')
+ addTimeSuffix(Math.floor(normalizeAge((ms % ONE_MINUTE) / ONE_SECOND)), 's'),
+ addTimeSuffix(Math.floor(normalizeAge(ms % ONE_SECOND)), "ms")
].filter(Boolean).join(' ');
return `${ dhms ? '+' : ''} ${dhms}`;
@@ -59,7 +68,8 @@ export function getPreciseDuration(value) {
toDoubleDigits(Math.floor(normalizeAge(ms / ONE_DAY))),
toDoubleDigits(Math.floor(normalizeAge((ms % ONE_DAY) / ONE_HOUR))),
toDoubleDigits(Math.floor(normalizeAge((ms % ONE_HOUR) / ONE_MINUTE))),
- toDoubleDigits(Math.floor(normalizeAge((ms % ONE_MINUTE) / ONE_SECOND)))
+ toDoubleDigits(Math.floor(normalizeAge((ms % ONE_MINUTE) / ONE_SECOND))),
+ toTripleDigits(Math.floor(normalizeAge(ms % ONE_SECOND)))
].join(":");
}