diff --git a/package.json b/package.json index fcfded6fb..a10e11072 100755 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "fix:eslint": "eslint --fix --ext .js,.ts,.tsx .", "fix:stylelint": "stylelint '**/*.css' --fix", "test": "npm-run-all -p test:unit test:eslint test:stylelint test:git", - "test:eslint": "eslint --ext .js,.ts,.tsx . --max-warnings=47", + "test:eslint": "eslint --ext .js,.ts,.tsx . --max-warnings=37", "test:stylelint": "stylelint '**/*.css' --max-warnings 0", "test:unit": "lerna run test --stream --concurrency 1", "test:git": "git diff --exit-code", diff --git a/packages/core/src/__mocks__/data-source.ts b/packages/core/src/__mocks__/data-source.ts index 2225cb174..d2e4fc5fe 100644 --- a/packages/core/src/__mocks__/data-source.ts +++ b/packages/core/src/__mocks__/data-source.ts @@ -39,7 +39,7 @@ export const createMockSiteWiseDataSource = ( } ), getRequestsFromQuery: ({ query }) => - query.assets + Promise.resolve(query.assets .map(({ assetId, properties }) => properties.map(({ propertyId, refId }) => ({ id: toDataStreamId({ assetId, propertyId }), @@ -47,5 +47,5 @@ export const createMockSiteWiseDataSource = ( resolution: '0', })) ) - .flat(), + .flat()), }); diff --git a/packages/core/src/data-module/TimeSeriesDataModule.spec.ts b/packages/core/src/data-module/TimeSeriesDataModule.spec.ts index a6eacb862..6df6171e9 100644 --- a/packages/core/src/data-module/TimeSeriesDataModule.spec.ts +++ b/packages/core/src/data-module/TimeSeriesDataModule.spec.ts @@ -77,6 +77,8 @@ describe('update subscription', () => { timeSeriesCallback ); + await flushPromises(); + timeSeriesCallback.mockClear(); (dataSource.initiateRequest as Mock).mockClear(); @@ -94,7 +96,7 @@ describe('initial request', () => { it('does not load request data streams which are not provided from a data-source', async () => { const dataSource: DataSource = { initiateRequest: jest.fn(), - getRequestsFromQuery: () => [], + getRequestsFromQuery: () => Promise.resolve([]), } as DataSource; const dataModule = new TimeSeriesDataModule(dataSource); @@ -113,11 +115,13 @@ describe('initial request', () => { timeSeriesCallback ); + await flushPromises(); + expect(timeSeriesCallback).not.toBeCalled(); expect(dataSource.initiateRequest).not.toBeCalled(); }); - it('passes back associated refId', () => { + it('passes back associated refId', async () => { const REF_ID = 'ref-id'; const query: SiteWiseDataStreamQuery = { assets: [ @@ -146,6 +150,9 @@ describe('initial request', () => { }, timeSeriesCallback ); + + await flushPromises(); + expect(timeSeriesCallback).toBeCalledWith({ dataStreams: [ expect.objectContaining({ @@ -192,6 +199,9 @@ describe('initial request', () => { }, timeSeriesCallback ); + + await flushPromises(); + jest.advanceTimersByTime(100); await flushPromises(); jest.advanceTimersByTime(100); @@ -213,7 +223,7 @@ describe('initial request', () => { }); }); - it('initiates a request for a data stream', () => { + it('initiates a request for a data stream', async () => { const START = new Date(2000, 0, 0); const END = new Date(); @@ -233,6 +243,8 @@ describe('initial request', () => { timeSeriesCallback ); + await flushPromises(); + expect(timeSeriesCallback).toBeCalledWith({ dataStreams: [ expect.objectContaining({ @@ -293,7 +305,7 @@ it('subscribes to a single data stream', async () => { timeSeriesCallback ); - jest.advanceTimersByTime(1); + await flushPromises(); expect(timeSeriesCallback).toBeCalledWith({ dataStreams: [ @@ -309,7 +321,7 @@ it('subscribes to a single data stream', async () => { }); }); -it('requests data from a custom data source', () => { +it('requests data from a custom data source', async () => { const customSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); const { propertyId, assetId } = toSiteWiseAssetProperty(DATA_STREAM.id); @@ -336,6 +348,8 @@ it('requests data from a custom data source', () => { onSuccess ); + await flushPromises(); + expect(onSuccess).toBeCalledWith({ dataStreams: [expect.objectContaining({ id: DATA_STREAM.id })], viewport: { @@ -345,7 +359,7 @@ it('requests data from a custom data source', () => { }); }); -it('subscribes to multiple data streams', () => { +it('subscribes to multiple data streams', async () => { const onRequestData = jest.fn(); const source = createMockSiteWiseDataSource({ onRequestData }); @@ -374,6 +388,8 @@ it('subscribes to multiple data streams', () => { onSuccess ); + await flushPromises(); + expect(onRequestData).toHaveBeenNthCalledWith( 1, expect.objectContaining({ @@ -390,7 +406,7 @@ it('subscribes to multiple data streams', () => { ); }); -it('subscribes to multiple queries on the same data source', () => { +it('subscribes to multiple queries on the same data source', async () => { const onRequestData = jest.fn(); const source = createMockSiteWiseDataSource({ onRequestData }); @@ -431,6 +447,8 @@ it('subscribes to multiple queries on the same data source', () => { onSuccess ); + await flushPromises(); + expect(onRequestData).toHaveBeenNthCalledWith( 1, expect.objectContaining({ @@ -468,7 +486,7 @@ it('subscribes to multiple queries on the same data source', () => { }); }); -it('only requests latest value', () => { +it('only requests latest value', async () => { const onRequestData = jest.fn(); const source = createMockSiteWiseDataSource({ onRequestData }); @@ -497,6 +515,8 @@ it('only requests latest value', () => { onSuccess ); + await flushPromises(); + expect(onRequestData).toBeCalledWith( expect.objectContaining({ request: expect.objectContaining({ @@ -539,7 +559,7 @@ describe('error handling', () => { }, }; - it('provides a data stream which has an error associated with it on initial subscription', () => { + it('provides a data stream which has an error associated with it on initial subscription', async () => { const customSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); const dataModule = new TimeSeriesDataModule(customSource, { initialDataCache: CACHE_WITH_ERROR }); @@ -559,6 +579,8 @@ describe('error handling', () => { timeSeriesCallback ); + await flushPromises(); + expect(timeSeriesCallback).toBeCalledTimes(1); expect(timeSeriesCallback).toBeCalledWith({ dataStreams: [expect.objectContaining({ error: ERR })], @@ -589,12 +611,14 @@ describe('error handling', () => { timeSeriesCallback ); + await flushPromises(); + timeSeriesCallback.mockClear(); jest.advanceTimersByTime(SECOND_IN_MS * 0.11); expect(timeSeriesCallback).not.toBeCalled(); }); - it('does request a data stream which has no error associated with it', () => { + it('does request a data stream which has no error associated with it', async () => { const customSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); const dataModule = new TimeSeriesDataModule(customSource, { initialDataCache: CACHE_WITHOUT_ERROR }); @@ -615,6 +639,8 @@ describe('error handling', () => { timeSeriesCallback ); + await flushPromises(); + expect(timeSeriesCallback).toBeCalledTimes(1); expect(timeSeriesCallback).toBeCalledWith({ dataStreams: [expect.objectContaining({ error: undefined })], @@ -627,7 +653,7 @@ describe('error handling', () => { }); describe('caching', () => { - it('does not request already cached data', () => { + it('does not request already cached data', async () => { const dataSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); const dataModule = new TimeSeriesDataModule(dataSource); @@ -646,11 +672,13 @@ describe('caching', () => { timeSeriesCallback ); - update({ request: { viewport: { start: START_2, end: END_2 }, settings: { fetchFromStartToEnd: true } } }); + await flushPromises(); + + await update({ request: { viewport: { start: START_2, end: END_2 }, settings: { fetchFromStartToEnd: true } } }); (dataSource.initiateRequest as Mock).mockClear(); - update({ request: { viewport: { start: START_1, end: END_1 }, settings: { fetchFromStartToEnd: true } } }); + await update({ request: { viewport: { start: START_1, end: END_1 }, settings: { fetchFromStartToEnd: true } } }); expect(dataSource.initiateRequest).not.toBeCalled(); }); @@ -677,6 +705,8 @@ describe('caching', () => { timeSeriesCallback ); + await flushPromises(); + expect(dataSource.initiateRequest).toHaveBeenCalledTimes(1); expect(dataSource.initiateRequest).toHaveBeenCalledWith( @@ -696,7 +726,7 @@ describe('caching', () => { (dataSource.initiateRequest as Mock).mockClear(); - update({ request: { viewport: { start: START_2, end: END_2 }, settings: { fetchFromStartToEnd: true } } }); + await update({ request: { viewport: { start: START_2, end: END_2 }, settings: { fetchFromStartToEnd: true } } }); await flushPromises(); @@ -724,7 +754,7 @@ describe('caching', () => { ); }); - it('immediately request when subscribed to an entirely new time interval not previously requested', () => { + it('immediately request when subscribed to an entirely new time interval not previously requested', async () => { const dataSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); const dataModule = new TimeSeriesDataModule(dataSource); @@ -745,9 +775,11 @@ describe('caching', () => { timeSeriesCallback ); + await flushPromises(); + (dataSource.initiateRequest as Mock).mockClear(); - update({ + await update({ request: { viewport: { start: START_2, end: END_2 }, settings: { fetchFromStartToEnd: true } }, }); @@ -786,9 +818,11 @@ describe('caching', () => { timeSeriesCallback ); + await flushPromises(); (dataSource.initiateRequest as Mock).mockClear(); jest.advanceTimersByTime(MINUTE_IN_MS); + await flushPromises(); expect(dataSource.initiateRequest).toBeCalledWith( expect.objectContaining({ @@ -834,6 +868,8 @@ describe('caching', () => { timeSeriesCallback ); + await flushPromises(); + (dataSource.initiateRequest as Mock).mockClear(); jest.advanceTimersByTime(MINUTE_IN_MS); @@ -891,6 +927,8 @@ it.skip('overrides module-level cache TTL if query-level cache TTL is provided', timeSeriesCallback ); + await flushPromises(); + (dataSource.initiateRequest as Mock).mockClear(); jest.advanceTimersByTime(MINUTE_IN_MS); @@ -939,15 +977,19 @@ describe('request scheduler', () => { timeSeriesCallback ); + await flushPromises(); timeSeriesCallback.mockClear(); jest.advanceTimersByTime(SECOND_IN_MS * 0.11); + await flushPromises(); expect(timeSeriesCallback).toBeCalledTimes(2); timeSeriesCallback.mockClear(); jest.advanceTimersByTime(SECOND_IN_MS * 0.11); + await flushPromises(); expect(timeSeriesCallback).toBeCalledTimes(2); + unsubscribe(); }); @@ -976,13 +1018,17 @@ describe('request scheduler', () => { timeSeriesCallback ); + await flushPromises(); + timeSeriesCallback.mockClear(); jest.advanceTimersByTime(SECOND_IN_MS * 0.11); + await flushPromises(); expect(timeSeriesCallback).toBeCalledTimes(2); timeSeriesCallback.mockClear(); jest.advanceTimersByTime(SECOND_IN_MS * 0.11); + await flushPromises(); expect(timeSeriesCallback).toBeCalledTimes(2); @@ -991,6 +1037,7 @@ describe('request scheduler', () => { timeSeriesCallback.mockClear(); jest.advanceTimersByTime(SECOND_IN_MS * 0.11); + await flushPromises(); expect(timeSeriesCallback).toBeCalledTimes(0); @@ -1016,11 +1063,14 @@ describe('request scheduler', () => { timeSeriesCallback ); + await flushPromises(); + unsubscribe(); await flushPromises(); timeSeriesCallback.mockClear(); jest.advanceTimersByTime(SECOND_IN_MS * 0.11); + await flushPromises(); expect(timeSeriesCallback).not.toHaveBeenCalled(); }); @@ -1046,7 +1096,9 @@ describe('request scheduler', () => { timeSeriesCallback ); - update({ + await flushPromises(); + + await update({ request: { viewport: { duration: MINUTE_IN_MS }, settings: { @@ -1058,10 +1110,15 @@ describe('request scheduler', () => { timeSeriesCallback.mockClear(); jest.advanceTimersByTime(SECOND_IN_MS * 0.11); + await flushPromises(); expect(timeSeriesCallback).toBeCalledTimes(2); + + await flushPromises(); timeSeriesCallback.mockClear(); jest.advanceTimersByTime(SECOND_IN_MS * 0.11); + await flushPromises(); + expect(timeSeriesCallback).toBeCalledTimes(2); unsubscribe(); @@ -1086,8 +1143,10 @@ describe('request scheduler', () => { timeSeriesCallback ); + await flushPromises(); + // Update the request info to trigger the live mode - update({ + await update({ request: { viewport: { duration: SECOND_IN_MS }, settings: { @@ -1101,6 +1160,8 @@ describe('request scheduler', () => { timeSeriesCallback.mockClear(); jest.advanceTimersByTime(SECOND_IN_MS * 0.11); + await flushPromises(); + expect(timeSeriesCallback).not.toHaveBeenCalled(); }); @@ -1117,7 +1178,9 @@ describe('request scheduler', () => { timeSeriesCallback ); - update({ + await flushPromises(); + + await update({ request: { viewport: { start: new Date(2000, 0, 0), end: new Date(2001, 0, 0) }, settings: { @@ -1133,7 +1196,7 @@ describe('request scheduler', () => { expect(timeSeriesCallback).not.toBeCalled(); }); - it('continues the schedule requests when request info gets updated with static viewport that intersects with TTL intervals', async () => { + it('continues the scheduled requests when request info gets updated with static viewport that intersects with TTL intervals', async () => { const dataSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); const dataModule = new TimeSeriesDataModule(dataSource); @@ -1149,7 +1212,9 @@ describe('request scheduler', () => { const END = new Date(); const START = new Date(END.getTime() - HOUR_IN_MS); - update({ + await flushPromises(); + + await update({ request: { viewport: { start: START, end: END }, settings: { refreshRate: SECOND_IN_MS * 0.1, fetchFromStartToEnd: true }, @@ -1158,12 +1223,13 @@ describe('request scheduler', () => { timeSeriesCallback.mockClear(); jest.advanceTimersByTime(SECOND_IN_MS * 0.11); + await flushPromises(); expect(timeSeriesCallback).toBeCalledTimes(2); }); }); -it('when data is requested from the viewport start to end with a buffer, include a buffer', () => { +it('when data is requested from the viewport start to end with a buffer, include a buffer', async () => { const dataSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); const dataModule = new TimeSeriesDataModule(dataSource); @@ -1182,6 +1248,8 @@ it('when data is requested from the viewport start to end with a buffer, include timeSeriesCallback ); + await flushPromises(); + expect(dataSource.initiateRequest).toBeCalledWith( expect.objectContaining({ request: { diff --git a/packages/core/src/data-module/TimeSeriesDataModule.ts b/packages/core/src/data-module/TimeSeriesDataModule.ts index e6e542242..4426b336d 100644 --- a/packages/core/src/data-module/TimeSeriesDataModule.ts +++ b/packages/core/src/data-module/TimeSeriesDataModule.ts @@ -69,7 +69,7 @@ export class TimeSeriesDataModule { * Takes into account the current state of the cache, to determine which data has already been requested, or has expired * segments within the cache. */ - private fulfillQueries = ({ + private fulfillQueries = async ({ viewport, request, queries, @@ -78,7 +78,7 @@ export class TimeSeriesDataModule { request: TimeSeriesDataRequest; queries: Query[]; }) => { - const requestedStreams = this.dataSourceStore.getRequestsFromQueries({ queries, request }); + const requestedStreams = await this.dataSourceStore.getRequestsFromQueries({ queries, request }); const isRequestedDataStream = ({ id, resolution }: RequestInformation) => this.dataCache.shouldRequestDataStream({ dataStreamId: id, resolution: parseDuration(resolution) }); @@ -151,20 +151,18 @@ export class TimeSeriesDataModule { this.unsubscribe(subscriptionId); }; - const update = (subscriptionUpdate: SubscriptionUpdate) => { - this.update(subscriptionId, subscriptionUpdate); - }; + const update = (subscriptionUpdate: SubscriptionUpdate) => this.update(subscriptionId, subscriptionUpdate); return { unsubscribe, update }; }; - private update = (subscriptionId: string, subscriptionUpdate: SubscriptionUpdate): void => { + private update = async (subscriptionId: string, subscriptionUpdate: SubscriptionUpdate): Promise => { const subscription = this.subscriptions.getSubscription(subscriptionId); const updatedSubscription = { ...subscription, ...subscriptionUpdate }; if ('queries' in updatedSubscription) { - this.subscriptions.updateSubscription(subscriptionId, { + return this.subscriptions.updateSubscription(subscriptionId, { ...updatedSubscription, fulfill: () => { this.fulfillQueries({ diff --git a/packages/core/src/data-module/data-source-store/dataSourceStore.spec.ts b/packages/core/src/data-module/data-source-store/dataSourceStore.spec.ts index 1153966cc..d70bb1e65 100644 --- a/packages/core/src/data-module/data-source-store/dataSourceStore.spec.ts +++ b/packages/core/src/data-module/data-source-store/dataSourceStore.spec.ts @@ -2,7 +2,7 @@ import DataSourceStore from './dataSourceStore'; import { DataSource } from '../types'; it('initiate a request on a registered data source', () => { - const customSource: DataSource = { initiateRequest: jest.fn(), getRequestsFromQuery: () => [] }; + const customSource: DataSource = { initiateRequest: jest.fn(), getRequestsFromQuery: () => Promise.resolve([]) }; const dataSourceStore = new DataSourceStore(customSource); const query = { source: 'custom' }; diff --git a/packages/core/src/data-module/data-source-store/dataSourceStore.ts b/packages/core/src/data-module/data-source-store/dataSourceStore.ts index 18ba11728..ebf436c70 100644 --- a/packages/core/src/data-module/data-source-store/dataSourceStore.ts +++ b/packages/core/src/data-module/data-source-store/dataSourceStore.ts @@ -14,24 +14,31 @@ export default class DataSourceStore { this.dataSource = dataSource; } - public getRequestsFromQueries = ({ + public getRequestsFromQueries = async ({ queries, request, }: { queries: Query[]; request: TimeSeriesDataRequest; - }): RequestInformation[] => queries.map((query) => this.getRequestsFromQuery({ query, request })).flat(); + }): Promise => { + const requestInformations = await Promise.all( + queries.map((query) => this.getRequestsFromQuery({ query, request })) + ); + return requestInformations.flat(); + }; - public getRequestsFromQuery = ({ + private getRequestsFromQuery = ({ query, request, }: { query: Query; request: TimeSeriesDataRequest; - }): RequestInformation[] => { + }): Promise => { return this.dataSource .getRequestsFromQuery({ query, request }) - .map((request) => ({ ...request, cacheSettings: query.cacheSettings })); + .then((requestInformations) => + requestInformations.map((requestInfo) => ({ ...requestInfo, cacheSettings: query.cacheSettings })) + ); }; public initiateRequest = (request: DataSourceRequest, requestInformations: RequestInformationAndRange[]) => { diff --git a/packages/core/src/data-module/subscription-store/subscriptionStore.spec.ts b/packages/core/src/data-module/subscription-store/subscriptionStore.spec.ts index c50a10496..b21b04f1c 100644 --- a/packages/core/src/data-module/subscription-store/subscriptionStore.spec.ts +++ b/packages/core/src/data-module/subscription-store/subscriptionStore.spec.ts @@ -7,7 +7,7 @@ import { DEFAULT_CACHE_SETTINGS } from '../TimeSeriesDataModule'; const createSubscriptionStore = () => { const store = new DataSourceStore({ initiateRequest: () => {}, - getRequestsFromQuery: () => [], + getRequestsFromQuery: () => Promise.resolve([]), } as DataSource); return new SubscriptionStore({ @@ -30,16 +30,16 @@ const MOCK_SUBSCRIPTION: Subscription = { fulfill: () => {}, }; -it('adds subscription', () => { +it('adds subscription', async () => { const subscriptionStore = createSubscriptionStore(); - subscriptionStore.addSubscription('some-id', MOCK_SUBSCRIPTION); + await subscriptionStore.addSubscription('some-id', MOCK_SUBSCRIPTION); expect(subscriptionStore.getSubscriptions()).toEqual([MOCK_SUBSCRIPTION]); subscriptionStore.removeSubscription('some-id'); }); -it('updates subscription', () => { +it('updates subscription', async () => { const SUBSCRIPTION_ID = 'some-id'; const subscriptionStore = createSubscriptionStore(); @@ -49,35 +49,35 @@ it('updates subscription', () => { }, ]; - subscriptionStore.addSubscription(SUBSCRIPTION_ID, MOCK_SUBSCRIPTION); - subscriptionStore.updateSubscription(SUBSCRIPTION_ID, { queries }); + await subscriptionStore.addSubscription(SUBSCRIPTION_ID, MOCK_SUBSCRIPTION); + await subscriptionStore.updateSubscription(SUBSCRIPTION_ID, { queries }); expect(subscriptionStore.getSubscriptions()).toEqual([{ ...MOCK_SUBSCRIPTION, queries }]); subscriptionStore.removeSubscription(SUBSCRIPTION_ID); }); -it('removes subscription', () => { +it('removes subscription', async () => { const SUBSCRIPTION_ID = 'some-id'; const subscriptionStore = createSubscriptionStore(); - subscriptionStore.addSubscription(SUBSCRIPTION_ID, MOCK_SUBSCRIPTION); + await subscriptionStore.addSubscription(SUBSCRIPTION_ID, MOCK_SUBSCRIPTION); subscriptionStore.removeSubscription(SUBSCRIPTION_ID); expect(subscriptionStore.getSubscriptions()).toBeEmpty(); }); -it('gets subscription by subscriptionId', () => { +it('gets subscription by subscriptionId', async () => { const subscriptionStore = createSubscriptionStore(); - subscriptionStore.addSubscription('some-id', MOCK_SUBSCRIPTION); + await subscriptionStore.addSubscription('some-id', MOCK_SUBSCRIPTION); expect(subscriptionStore.getSubscription('some-id')).toEqual(MOCK_SUBSCRIPTION); subscriptionStore.removeSubscription('some-id'); }); describe('throws errors when', () => { - it('throws error when trying to update non-existent subscription', () => { + it('throws error when trying to update non-existent subscription', async () => { const subscriptionStore = createSubscriptionStore(); - expect(() => subscriptionStore.updateSubscription('some-id', {})).toThrowError(/some-id/); + await expect(subscriptionStore.updateSubscription('some-id', {})).rejects.toThrowError(/some-id/); }); it('throws error when trying to remove non-existent subscription', () => { @@ -85,12 +85,12 @@ describe('throws errors when', () => { expect(() => subscriptionStore.removeSubscription('some-id')).toThrowError(/some-id/); }); - it('throws error when trying to add the same subscription id twice', () => { + it('throws error when trying to add the same subscription id twice', async () => { const SUBSCRIPTION_ID = 'some-id'; const subscriptionStore = createSubscriptionStore(); - subscriptionStore.addSubscription(SUBSCRIPTION_ID, MOCK_SUBSCRIPTION); - expect(() => subscriptionStore.addSubscription(SUBSCRIPTION_ID, MOCK_SUBSCRIPTION)).toThrowError(/some-id/); + await subscriptionStore.addSubscription(SUBSCRIPTION_ID, MOCK_SUBSCRIPTION); + await expect(subscriptionStore.addSubscription(SUBSCRIPTION_ID, MOCK_SUBSCRIPTION)).rejects.toThrowError(/some-id/); subscriptionStore.removeSubscription('some-id'); }); diff --git a/packages/core/src/data-module/subscription-store/subscriptionStore.ts b/packages/core/src/data-module/subscription-store/subscriptionStore.ts index a06e6e4ce..ff71ba6ed 100644 --- a/packages/core/src/data-module/subscription-store/subscriptionStore.ts +++ b/packages/core/src/data-module/subscription-store/subscriptionStore.ts @@ -33,7 +33,7 @@ export default class SubscriptionStore { this.cacheSettings = cacheSettings; } - addSubscription(subscriptionId: string, subscription: Subscription): void { + async addSubscription(subscriptionId: string, subscription: Subscription): Promise { if (this.subscriptions[subscriptionId] == null) { /** * If the subscription is query based @@ -66,10 +66,11 @@ export default class SubscriptionStore { const { queries, request } = subscription; + const requestInfos = await this.dataSourceStore.getRequestsFromQueries({ queries, request }); + // Subscribe to changes from the data cache - const unsubscribe = this.dataCache.subscribe( - this.dataSourceStore.getRequestsFromQueries({ queries, request }), - (dataStreams) => subscription.emit({ dataStreams, viewport: subscription.request.viewport }) + const unsubscribe = this.dataCache.subscribe(requestInfos, (dataStreams) => + subscription.emit({ dataStreams, viewport: subscription.request.viewport }) ); this.unsubscribeMap[subscriptionId] = () => { @@ -91,7 +92,7 @@ export default class SubscriptionStore { } } - updateSubscription(subscriptionId: string, subscriptionUpdate: SubscriptionUpdate): void { + async updateSubscription(subscriptionId: string, subscriptionUpdate: SubscriptionUpdate): Promise { if (this.subscriptions[subscriptionId] == null) { throw new Error( `Attempted to update a subscription with an id of "${subscriptionId}", but the requested subscription does not exist.` @@ -105,7 +106,7 @@ export default class SubscriptionStore { this.removeSubscription(subscriptionId); - this.addSubscription(subscriptionId, updatedSubscription); + await this.addSubscription(subscriptionId, updatedSubscription); } removeSubscription = (subscriptionId: string): void => { diff --git a/packages/core/src/data-module/types.ts b/packages/core/src/data-module/types.ts index ba6f54025..019dbecb3 100644 --- a/packages/core/src/data-module/types.ts +++ b/packages/core/src/data-module/types.ts @@ -53,7 +53,13 @@ export interface DataStream { export type DataSource = { initiateRequest: (request: DataSourceRequest, requestInformations: RequestInformationAndRange[]) => void; - getRequestsFromQuery: ({ query, request }: { query: Query; request: TimeSeriesDataRequest }) => RequestInformation[]; + getRequestsFromQuery: ({ + query, + request, + }: { + query: Query; + request: TimeSeriesDataRequest; + }) => Promise; }; export type DataStreamCallback = (dataStreams: DataStream[], requestInformation: RequestInformationAndRange) => void; @@ -118,7 +124,7 @@ export type SubscriptionResponse = { unsubscribe: () => void; /** Update the subscription. This will immediately evaluate if a new query must be requested */ - update: (subscriptionUpdate: SubscriptionUpdate) => void; + update: (subscriptionUpdate: SubscriptionUpdate) => Promise; }; // SiteWise specific types - eventually remove these from here diff --git a/packages/related-table/src/Hooks/useTreeCollection.ts b/packages/related-table/src/Hooks/useTreeCollection.ts index eac253de3..ee641651c 100644 --- a/packages/related-table/src/Hooks/useTreeCollection.ts +++ b/packages/related-table/src/Hooks/useTreeCollection.ts @@ -47,10 +47,12 @@ export const useTreeCollection = ( const expandNode = (node: ITreeNode) => { if (node) { const key = (node as any)[keyPropertyName]; - const internalNode = nodes.find((n) => (n as any)[keyPropertyName] === key)!; - internalNode.toggleExpandCollapse(); - expandOrCollapseChildren(internalNode, treeMap, keyPropertyName); - treeMap.set(key, internalNode); + const internalNode = nodes.find((n) => (n as any)[keyPropertyName] === key); + if (internalNode) { + internalNode.toggleExpandCollapse(); + expandOrCollapseChildren(internalNode, treeMap, keyPropertyName); + treeMap.set(key, internalNode); + } const updatedNodes = nodes.concat([]); setNodes(updatedNodes); setTreeMap(treeMap); diff --git a/packages/related-table/src/RelatedTable/RelatedTable.test.tsx b/packages/related-table/src/RelatedTable/RelatedTable.test.tsx index a630dfaac..cc981629d 100644 --- a/packages/related-table/src/RelatedTable/RelatedTable.test.tsx +++ b/packages/related-table/src/RelatedTable/RelatedTable.test.tsx @@ -62,7 +62,7 @@ it('expand button correctly', () => { expect(buttons.length).toEqual(1); act(() => { - buttons[0]!.click(); + buttons[0]?.click(); }); expect(onExpandChildren).toHaveBeenCalled(); diff --git a/packages/related-table/src/utils/build.ts b/packages/related-table/src/utils/build.ts index 3acbacc1b..1936ac6ae 100644 --- a/packages/related-table/src/utils/build.ts +++ b/packages/related-table/src/utils/build.ts @@ -47,7 +47,8 @@ const createNode = ( const prepareNode = (node: ITreeNode, treeMap: TreeMap, keyPropertyName: string): ITreeNode => { const key = (node as any)[keyPropertyName]; - const isVisible = node.getParent() ? node.getParent()!.isExpanded() && node.getParent()!.isVisible() : true; + const parent = node.getParent(); + const isVisible = parent ? parent.isExpanded() && parent.isVisible() : true; node.setVisible(isVisible); node.setStatus( node.hasChildren || node.getChildren().length > 0 @@ -79,7 +80,8 @@ export const buildTreeNodes = ( }; export const recursiveBuildTreePrefix = (node: ITreeNode, index: number, parentLastChildPath: boolean[]) => { - const isLastChild = node.getParent() ? node.getParent()!.getChildren().length - 1 === index : true; + const parent = node.getParent(); + const isLastChild = parent ? parent.getChildren().length - 1 === index : true; node.buildPrefix(isLastChild, parentLastChildPath); node .getChildren() diff --git a/packages/related-table/src/utils/cleanup.ts b/packages/related-table/src/utils/cleanup.ts index 2219fc068..fb7676d74 100644 --- a/packages/related-table/src/utils/cleanup.ts +++ b/packages/related-table/src/utils/cleanup.ts @@ -4,9 +4,11 @@ const removeNode = (node: ITreeNode, keyPropertyName: string, treeMap: Tre const key = (node as any)[keyPropertyName]; if (node.getParent()) { - const parentChildren = node.getParent()!.getChildren(); - const childIndex = parentChildren.findIndex((child) => child === node); - parentChildren.splice(childIndex, 1); + const parentChildren = node.getParent()?.getChildren(); + const childIndex = parentChildren?.findIndex((child) => child === node); + if (childIndex != null) { + parentChildren?.splice(childIndex, 1); + } node.setParentNode(undefined); } diff --git a/packages/related-table/src/utils/sort.ts b/packages/related-table/src/utils/sort.ts index 1bf822a31..168fe640e 100644 --- a/packages/related-table/src/utils/sort.ts +++ b/packages/related-table/src/utils/sort.ts @@ -27,10 +27,10 @@ export const sortTree = ( ) => { const { sortingColumn } = sortState; if (sortingColumn && sortingColumn.sortingField) { - const columnDefinition = columnsDefinitions.find((column) => column.sortingField === sortingColumn.sortingField)!; + const columnDefinition = columnsDefinitions.find((column) => column.sortingField === sortingColumn.sortingField); const direction = sortState.isDescending ? -1 : 1; const comparator = - columnDefinition.sortingComparator || defaultComparator(sortState.sortingColumn.sortingField as keyof T); + columnDefinition?.sortingComparator || defaultComparator(sortState.sortingColumn.sortingField as keyof T); tree .sort((a: T, b: T) => comparator(a, b) * direction) diff --git a/packages/related-table/src/utils/utils.test.ts b/packages/related-table/src/utils/utils.test.ts index 8f6f376cd..cb33e4144 100644 --- a/packages/related-table/src/utils/utils.test.ts +++ b/packages/related-table/src/utils/utils.test.ts @@ -71,7 +71,7 @@ describe('TreeUtility', () => { nodes.forEach((node) => treeMap.set(node.id, node)); const newItems = [...items]; - const parentIndex = newItems.findIndex((item) => item.name === 'Parent')!; + const parentIndex = newItems.findIndex((item) => item.name === 'Parent'); const newParentItem = { ...items[parentIndex], priority: 99, @@ -81,7 +81,7 @@ describe('TreeUtility', () => { const newNodes = flatTree(buildTreeNodes(newItems, treeMap, 'id', 'parentId')); const parentNode = newNodes.find((item) => item.name === 'Parent'); expect(parentNode).toBeDefined(); - expect(parentNode!.priority).toBe(newParentItem.priority); + expect(parentNode?.priority).toBe(newParentItem.priority); }); it('delete nodes', () => { @@ -89,7 +89,7 @@ describe('TreeUtility', () => { nodes.forEach((node) => treeMap.set(node.id, node)); const newItems = [...items]; - const parentIndex = newItems.findIndex((item) => item.name === 'Parent')!; + const parentIndex = newItems.findIndex((item) => item.name === 'Parent'); newItems.splice(parentIndex, 1); const newNodes = flatTree(buildTreeNodes(newItems, treeMap, 'id', 'parentId')); diff --git a/packages/source-iotsitewise/src/time-series-data/data-source.spec.ts b/packages/source-iotsitewise/src/time-series-data/data-source.spec.ts index 4e0faf102..ca847b188 100644 --- a/packages/source-iotsitewise/src/time-series-data/data-source.spec.ts +++ b/packages/source-iotsitewise/src/time-series-data/data-source.spec.ts @@ -1007,7 +1007,7 @@ describe.skip('aggregated data', () => { }); describe('gets requests from query', () => { - it("appends refId's to the requests from the query", () => { + it("appends refId's to the requests from the query", async () => { const mockSDK = createMockSiteWiseSDK({}); const dataSource = createDataSource(mockSDK); @@ -1033,7 +1033,9 @@ describe('gets requests from query', () => { }, }; - expect(dataSource.getRequestsFromQuery({ query, request })).toEqual([expect.objectContaining({ refId: REF_ID })]); + const requestInfos = await dataSource.getRequestsFromQuery({ query, request }); + + expect(requestInfos).toEqual([expect.objectContaining({ refId: REF_ID })]); }); }); @@ -1092,7 +1094,7 @@ it.skip('only fetches uncached data for multiple properties', async () => { ], }; - update({ queries: [updatedQuery], request: { viewport: { start: START_2, end: END_2 } } }); + await update({ queries: [updatedQuery], request: { viewport: { start: START_2, end: END_2 } } }); await flushPromises(); diff --git a/packages/source-iotsitewise/src/time-series-data/data-source.ts b/packages/source-iotsitewise/src/time-series-data/data-source.ts index dcf71203c..3e590b044 100644 --- a/packages/source-iotsitewise/src/time-series-data/data-source.ts +++ b/packages/source-iotsitewise/src/time-series-data/data-source.ts @@ -75,7 +75,7 @@ export const createDataSource = ( }), client.getHistoricalPropertyDataPoints({ requestInformations, onSuccess, onError }), ]), - getRequestsFromQuery: ({ query, request }) => { + getRequestsFromQuery: async ({ query, request }) => { const resolution = determineResolution({ resolution: request.settings?.resolution, start: viewportStartDate(request.viewport), diff --git a/packages/source-iotsitewise/src/time-series-data/provider.spec.ts b/packages/source-iotsitewise/src/time-series-data/provider.spec.ts index 2e27e857a..20d36e22d 100644 --- a/packages/source-iotsitewise/src/time-series-data/provider.spec.ts +++ b/packages/source-iotsitewise/src/time-series-data/provider.spec.ts @@ -18,7 +18,7 @@ const createMockSource = (dataStreams: DataStream[]): DataSource onSuccess(dataStreams, { start: new Date(), resolution: '1m', end: new Date(), id: '123' }, new Date(), new Date()) ), - getRequestsFromQuery: () => dataStreams.map((dataStream) => ({ id: dataStream.id, resolution: '0' })), + getRequestsFromQuery: async () => dataStreams.map((dataStream) => ({ id: dataStream.id, resolution: '0' })), }); const dataSource = createMockSource([DATA_STREAM]); diff --git a/packages/source-iotsitewise/src/time-series-data/subscribeToTimeSeriesData.spec.ts b/packages/source-iotsitewise/src/time-series-data/subscribeToTimeSeriesData.spec.ts index 97c9b08ec..d5bb65e53 100644 --- a/packages/source-iotsitewise/src/time-series-data/subscribeToTimeSeriesData.spec.ts +++ b/packages/source-iotsitewise/src/time-series-data/subscribeToTimeSeriesData.spec.ts @@ -39,7 +39,7 @@ it('unsubscribes', () => { const unsubscribeSpy = jest.fn(); jest.spyOn(dataModule, 'subscribeToDataStreams').mockImplementation(() => ({ unsubscribe: unsubscribeSpy, - update: () => {}, + update: async () => {}, })); const subscribe = subscribeToTimeSeriesData(dataModule, siteWiseAssetModuleSession);