diff options
author | Shefali <simplyrender@gmail.com> | 2022-08-24 19:23:37 +0300 |
---|---|---|
committer | Shefali <simplyrender@gmail.com> | 2022-08-24 19:23:37 +0300 |
commit | 7bccb73729d8375d44a9ece9d39b8d1963fc3acf (patch) | |
tree | 2a040cd4bb85015d2048cd97e5aea5ed2e8f677e | |
parent | 5a4dd1195530461cce6bac28dc3979b76583ea21 (diff) | |
parent | 78df5fc2a5f0323e4742860f6c032cbcc23f21f6 (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.js | 20 | ||||
-rw-r--r-- | e2e/tests/functional/search.e2e.spec.js | 13 | ||||
-rw-r--r-- | src/api/objects/InMemorySearchProvider.js | 6 | ||||
-rw-r--r-- | src/plugins/charts/scatter/ScatterPlotView.vue | 9 | ||||
-rw-r--r-- | src/plugins/timeConductor/ConductorHistory.vue | 32 | ||||
-rw-r--r-- | src/plugins/timeConductor/pluginSpec.js | 8 | ||||
-rw-r--r-- | src/ui/layout/search/GrandSearchSpec.js | 76 | ||||
-rw-r--r-- | src/utils/duration.js | 16 |
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(":"); } |