From dac34e74e6bbb9572de60edec80dc5ab5ab6c259 Mon Sep 17 00:00:00 2001 From: Daniel Priori Date: Wed, 9 Oct 2024 17:58:00 +0200 Subject: [PATCH 01/13] update hooks to support execute parallel, chunks and argument name --- .../RuleBasedOutputsSelector.tsx | 2 +- react-components/src/data-providers/index.ts | 7 +++- .../network/getTimeseriesLatestDatapoints.ts | 17 +++++++++- .../useAssetsAndTimeseriesLinkageDataQuery.ts | 34 ++++++++++++++----- 4 files changed, 48 insertions(+), 12 deletions(-) diff --git a/react-components/src/components/RuleBasedOutputs/RuleBasedOutputsSelector.tsx b/react-components/src/components/RuleBasedOutputs/RuleBasedOutputsSelector.tsx index ab45eb5f637..e1b9831031e 100644 --- a/react-components/src/components/RuleBasedOutputs/RuleBasedOutputsSelector.tsx +++ b/react-components/src/components/RuleBasedOutputs/RuleBasedOutputsSelector.tsx @@ -96,7 +96,7 @@ export function RuleBasedOutputsSelector({ const { isLoading: isLoadingAssetIdsAndTimeseriesData, data: assetIdsWithTimeseriesData } = useAssetsAndTimeseriesLinkageDataQuery({ timeseriesExternalIds, - contextualizedAssetNodes + assetNodes: contextualizedAssetNodes }); const flatAssetsMappingsListPerModel = useCreateAssetMappingsMapPerModel(models, assetMappings); diff --git a/react-components/src/data-providers/index.ts b/react-components/src/data-providers/index.ts index 77fd2578512..52ee0f85481 100644 --- a/react-components/src/data-providers/index.ts +++ b/react-components/src/data-providers/index.ts @@ -2,5 +2,10 @@ * Copyright 2024 Cognite AS */ -export type { FdmInstanceWithView, InstanceReference, AssetInstanceReference } from './types'; +export type { + FdmInstanceWithView, + InstanceReference, + AssetInstanceReference, + AssetIdsAndTimeseriesData +} from './types'; export type { Source, DmsUniqueIdentifier, SimpleSource } from './FdmSDK'; diff --git a/react-components/src/hooks/network/getTimeseriesLatestDatapoints.ts b/react-components/src/hooks/network/getTimeseriesLatestDatapoints.ts index 8501661dd69..376beb5a27a 100644 --- a/react-components/src/hooks/network/getTimeseriesLatestDatapoints.ts +++ b/react-components/src/hooks/network/getTimeseriesLatestDatapoints.ts @@ -2,10 +2,25 @@ * Copyright 2024 Cognite AS */ import { type CogniteClient, type IdEither, type Datapoints } from '@cognite/sdk'; +import { executeParallel } from '../../utilities/executeParallel'; +import { isDefined } from '../../utilities/isDefined'; +import { chunk } from 'lodash'; + +const FETCH_CHUNK = 100; export const getTimeseriesLatestDatapoints = async ( sdk: CogniteClient, timeseriesIds: IdEither[] ): Promise => { - return await sdk.datapoints.retrieveLatest(timeseriesIds); + const timeseriesChunks = chunk(timeseriesIds, FETCH_CHUNK); + + const timeseriesDatapoints = await executeParallel( + timeseriesChunks.map( + (timeseriesIds) => async () => await sdk.datapoints.retrieveLatest(timeseriesIds) + ), + 2 + ); + + const cleanDatapoints = timeseriesDatapoints.filter(isDefined).flat(); + return cleanDatapoints; }; diff --git a/react-components/src/query/useAssetsAndTimeseriesLinkageDataQuery.ts b/react-components/src/query/useAssetsAndTimeseriesLinkageDataQuery.ts index e921afd544f..150ba2d7723 100644 --- a/react-components/src/query/useAssetsAndTimeseriesLinkageDataQuery.ts +++ b/react-components/src/query/useAssetsAndTimeseriesLinkageDataQuery.ts @@ -26,15 +26,19 @@ import { getTimeseriesByIds } from '../hooks/network/getTimeseriesByIds'; import { isDefined } from '../utilities/isDefined'; import { getAssetsByIds } from '../hooks/network/getAssetsByIds'; import { getTimeseriesLatestDatapoints } from '../hooks/network/getTimeseriesLatestDatapoints'; +import { chunk, uniqBy } from 'lodash'; +import { executeParallel } from '../utilities/executeParallel'; + +const FETCH_RELATIONSHIP_CHUNK = 1000; type Props = { timeseriesExternalIds: CogniteExternalId[]; - contextualizedAssetNodes: Asset[]; + assetNodes: Asset[]; }; export function useAssetsAndTimeseriesLinkageDataQuery({ timeseriesExternalIds, - contextualizedAssetNodes + assetNodes }: Props): UseQueryResult { const sdk = useSDK(); @@ -64,7 +68,7 @@ export function useAssetsAndTimeseriesLinkageDataQuery({ timeseries ?.map((timeseries) => { return getAssetIdsFromTimeseries( - contextualizedAssetNodes, + assetNodes, timeseries, assetAndTimeseriesIdsFromRelationship ); @@ -72,8 +76,10 @@ export function useAssetsAndTimeseriesLinkageDataQuery({ .flat() .filter(isDefined) ?? []; + const uniqueAssetIds = uniqBy(assetIdsFound, 'externalId'); + const assetFromTimeseries = - assetIdsFound.length > 0 ? await getAssetsByIds(sdk, assetIdsFound) : []; + assetIdsFound.length > 0 ? await getAssetsByIds(sdk, uniqueAssetIds) : []; const assetIdsWithTimeseries = timeseries @@ -102,13 +108,23 @@ const getLinkFromRelationships = async ( timeseriesExternalIds: string[], relationshipResourceTypes: RelationshipResourceType[] ): Promise => { - const dataRelationship = await getRelationships(sdk, { - resourceExternalIds: timeseriesExternalIds, - relationshipResourceTypes - }); + const timeseriesChunks = chunk(timeseriesExternalIds, FETCH_RELATIONSHIP_CHUNK); + + const dataRelationship = await executeParallel( + timeseriesChunks.map( + (timeseriesIds) => async () => + await getRelationships(sdk, { + resourceExternalIds: timeseriesIds, + relationshipResourceTypes + }) + ), + 2 + ); + + const cleanDataRelationship = dataRelationship.filter(isDefined).flat(); const assetAndTimeseriesIdsFromRelationship = - dataRelationship?.map((item) => { + cleanDataRelationship?.map((item) => { const assetAndTimeseriesIds: AssetAndTimeseriesIds = { assetIds: { externalId: '' }, timeseriesIds: { externalId: '' } From b2e61ab6c3e1af8acba9d44c33de0b35f6e441a8 Mon Sep 17 00:00:00 2001 From: Daniel Priori Date: Thu, 10 Oct 2024 23:26:16 +0200 Subject: [PATCH 02/13] add hook to get the resource from relationship --- react-components/src/data-providers/index.ts | 3 ++- react-components/src/query/index.ts | 2 ++ .../src/query/useGetResourceRelationship.ts | 27 +++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 react-components/src/query/useGetResourceRelationship.ts diff --git a/react-components/src/data-providers/index.ts b/react-components/src/data-providers/index.ts index 52ee0f85481..cbcf0c9ebdb 100644 --- a/react-components/src/data-providers/index.ts +++ b/react-components/src/data-providers/index.ts @@ -6,6 +6,7 @@ export type { FdmInstanceWithView, InstanceReference, AssetInstanceReference, - AssetIdsAndTimeseriesData + AssetIdsAndTimeseriesData, + ExtendedRelationship, } from './types'; export type { Source, DmsUniqueIdentifier, SimpleSource } from './FdmSDK'; diff --git a/react-components/src/query/index.ts b/react-components/src/query/index.ts index 8b422af8566..77e4511817a 100644 --- a/react-components/src/query/index.ts +++ b/react-components/src/query/index.ts @@ -30,6 +30,8 @@ export { export { useTimeseriesByIdsQuery } from './useTimeseriesByIdsQuery'; export { useTimeseriesLatestDatapointQuery } from './useTimeseriesLatestDatapointQuery'; +export { useGetResourceRelationship } from './useGetResourceRelationship'; + export type { ModelMappings, ModelMappingsWithAssets, diff --git a/react-components/src/query/useGetResourceRelationship.ts b/react-components/src/query/useGetResourceRelationship.ts new file mode 100644 index 00000000000..20e9884be85 --- /dev/null +++ b/react-components/src/query/useGetResourceRelationship.ts @@ -0,0 +1,27 @@ +/*! + * Copyright 2024 Cognite AS + */ +import { type RelationshipResourceType } from '@cognite/sdk'; +import { getRelationships } from '../hooks/network/getRelationships'; +import { type ExtendedRelationship } from '../data-providers/types'; +import { useSDK } from '../components/RevealCanvas/SDKProvider'; +import { useMemo } from 'react'; + +export const useGetResourceRelationship = async ( + resourceIds: string[], + resourceTypes: RelationshipResourceType[] +): Promise => { + const sdk = useSDK(); + + const result = useMemo(async () => { + if (resourceIds.length === 0 || resourceIds.filter((id) => id.length > 0).length === 0) { + return []; + } + return await getRelationships(sdk, { + resourceExternalIds: resourceIds, + relationshipResourceTypes: resourceTypes + }); + }, [resourceIds, resourceTypes]); + + return await result; +}; From 036c44d127fc84ea6f78b26eea9c7f924570bbed Mon Sep 17 00:00:00 2001 From: Daniel Priori Date: Fri, 11 Oct 2024 11:28:59 +0200 Subject: [PATCH 03/13] move get resource relationship and add timeseries data type on relationship returning --- .../src/components/RuleBasedOutputs/index.ts | 2 ++ .../src/components/RuleBasedOutputs/utils.ts | 16 +++++++++++++-- .../utils/getResourceRelationship.ts | 20 +++++++++++++++++++ .../RuleBasedOutputs/utils/index.ts | 4 ++++ react-components/src/data-providers/types.ts | 11 ++++++++++ .../network/getResourceRelationship.ts} | 4 ++-- react-components/src/query/index.ts | 2 -- 7 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 react-components/src/components/RuleBasedOutputs/utils/getResourceRelationship.ts create mode 100644 react-components/src/components/RuleBasedOutputs/utils/index.ts rename react-components/src/{query/useGetResourceRelationship.ts => hooks/network/getResourceRelationship.ts} (83%) diff --git a/react-components/src/components/RuleBasedOutputs/index.ts b/react-components/src/components/RuleBasedOutputs/index.ts index 5a8e9e84306..51a0ae14101 100644 --- a/react-components/src/components/RuleBasedOutputs/index.ts +++ b/react-components/src/components/RuleBasedOutputs/index.ts @@ -6,6 +6,8 @@ export * from './hooks'; export { RuleBasedOutputsPanel } from './RuleBasedOutputsPanel'; export { getRuleTriggerTypes } from './utils'; +export * from './utils/index'; + export type { RuleAndEnabled, TriggerType, diff --git a/react-components/src/components/RuleBasedOutputs/utils.ts b/react-components/src/components/RuleBasedOutputs/utils.ts index 77732e2cd31..effe7a96cb6 100644 --- a/react-components/src/components/RuleBasedOutputs/utils.ts +++ b/react-components/src/components/RuleBasedOutputs/utils.ts @@ -26,7 +26,13 @@ import { type FdmInstanceNodeWithConnectionAndProperties } from './types'; import { NumericRange, TreeIndexNodeCollection, type NodeAppearance } from '@cognite/reveal'; -import { type AssetMapping3D, type Asset, type Datapoints } from '@cognite/sdk'; +import { + type AssetMapping3D, + type Asset, + type Datapoints, + type RelationshipResourceType, + type CogniteClient +} from '@cognite/sdk'; import { type FdmAssetStylingGroup, type AssetStylingGroup, @@ -34,13 +40,19 @@ import { } from '../Reveal3DResources/types'; import { isDefined } from '../../utilities/isDefined'; import { assertNever } from '../../utilities/assertNever'; -import { type AssetIdsAndTimeseries } from '../../data-providers/types'; +import { + type ExtendedRelationshipWithSourceAndTarget, + type AssetIdsAndTimeseries +} from '../../data-providers/types'; import { uniq } from 'lodash'; import { type DmsUniqueIdentifier } from '../../data-providers/FdmSDK'; import { checkNumericExpressionStatement } from './core/checkNumericExpressionStatement'; import { checkStringExpressionStatement } from './core/checkStringExpressionStatement'; import { checkDatetimeExpressionStatement } from './core/checkDatetimeExpressionStatement'; import { checkBooleanExpressionStatement } from './core/checkBooleanExpressionStatement'; +import { useSDK } from '../RevealCanvas/SDKProvider'; +import { useMemo } from 'react'; +import { getRelationships } from '../../hooks/network/getRelationships'; export const getTriggerNumericData = ( triggerTypeData: TriggerTypeData[], diff --git a/react-components/src/components/RuleBasedOutputs/utils/getResourceRelationship.ts b/react-components/src/components/RuleBasedOutputs/utils/getResourceRelationship.ts new file mode 100644 index 00000000000..26878c759f7 --- /dev/null +++ b/react-components/src/components/RuleBasedOutputs/utils/getResourceRelationship.ts @@ -0,0 +1,20 @@ +/*! + * Copyright 2024 Cognite AS + */ +import { type RelationshipResourceType, type CogniteClient } from '@cognite/sdk/dist/src'; +import { type ExtendedRelationshipWithSourceAndTarget } from '../../../data-providers/types'; +import { getRelationships } from '../../../hooks/network/getRelationships'; + +export const getResourceRelationship = async ( + sdk: CogniteClient, + resourceIds: string[], + resourceTypes: RelationshipResourceType[] +): Promise => { + if (resourceIds.length === 0 || resourceIds.filter((id) => id.length > 0).length === 0) { + return []; + } + return await getRelationships(sdk, { + resourceExternalIds: resourceIds, + relationshipResourceTypes: resourceTypes + }); +}; diff --git a/react-components/src/components/RuleBasedOutputs/utils/index.ts b/react-components/src/components/RuleBasedOutputs/utils/index.ts new file mode 100644 index 00000000000..8798c208b3b --- /dev/null +++ b/react-components/src/components/RuleBasedOutputs/utils/index.ts @@ -0,0 +1,4 @@ +/*! + * Copyright 2024 Cognite AS + */ +export { getResourceRelationship } from './getResourceRelationship'; diff --git a/react-components/src/data-providers/types.ts b/react-components/src/data-providers/types.ts index fe7badfabb8..d55b431a0ee 100644 --- a/react-components/src/data-providers/types.ts +++ b/react-components/src/data-providers/types.ts @@ -32,10 +32,21 @@ export type ExtendedRelationship = { relation: 'Source' | 'Target'; } & Relationship; +export type ExtendedRelationshipWithSourceAndTarget = { + relation: 'Source' | 'Target'; +} & Relationship & + Partial; + +export type RelationshipSourceAndTargetTimeseries = { + source: Timeseries; + target: Timeseries; +}; + export type RelationshipSourceAndTarget = { source: RelationshipSourceAndTargetData; target: RelationshipSourceAndTargetData; }; + export type RelationshipSourceAndTargetData = { externalId?: string; id?: number; diff --git a/react-components/src/query/useGetResourceRelationship.ts b/react-components/src/hooks/network/getResourceRelationship.ts similarity index 83% rename from react-components/src/query/useGetResourceRelationship.ts rename to react-components/src/hooks/network/getResourceRelationship.ts index 20e9884be85..07d4b9164a6 100644 --- a/react-components/src/query/useGetResourceRelationship.ts +++ b/react-components/src/hooks/network/getResourceRelationship.ts @@ -3,14 +3,14 @@ */ import { type RelationshipResourceType } from '@cognite/sdk'; import { getRelationships } from '../hooks/network/getRelationships'; -import { type ExtendedRelationship } from '../data-providers/types'; +import { type ExtendedRelationshipWithSourceAndTarget } from '../data-providers/types'; import { useSDK } from '../components/RevealCanvas/SDKProvider'; import { useMemo } from 'react'; export const useGetResourceRelationship = async ( resourceIds: string[], resourceTypes: RelationshipResourceType[] -): Promise => { +): Promise => { const sdk = useSDK(); const result = useMemo(async () => { diff --git a/react-components/src/query/index.ts b/react-components/src/query/index.ts index 77e4511817a..8b422af8566 100644 --- a/react-components/src/query/index.ts +++ b/react-components/src/query/index.ts @@ -30,8 +30,6 @@ export { export { useTimeseriesByIdsQuery } from './useTimeseriesByIdsQuery'; export { useTimeseriesLatestDatapointQuery } from './useTimeseriesLatestDatapointQuery'; -export { useGetResourceRelationship } from './useGetResourceRelationship'; - export type { ModelMappings, ModelMappingsWithAssets, From 88bf908cae2d94da29438c2a43e7e70efb9f8eaf Mon Sep 17 00:00:00 2001 From: Daniel Priori Date: Fri, 11 Oct 2024 19:02:52 +0200 Subject: [PATCH 04/13] chore: remove unused function --- .../hooks/network/getResourceRelationship.ts | 27 ------------------- 1 file changed, 27 deletions(-) delete mode 100644 react-components/src/hooks/network/getResourceRelationship.ts diff --git a/react-components/src/hooks/network/getResourceRelationship.ts b/react-components/src/hooks/network/getResourceRelationship.ts deleted file mode 100644 index 07d4b9164a6..00000000000 --- a/react-components/src/hooks/network/getResourceRelationship.ts +++ /dev/null @@ -1,27 +0,0 @@ -/*! - * Copyright 2024 Cognite AS - */ -import { type RelationshipResourceType } from '@cognite/sdk'; -import { getRelationships } from '../hooks/network/getRelationships'; -import { type ExtendedRelationshipWithSourceAndTarget } from '../data-providers/types'; -import { useSDK } from '../components/RevealCanvas/SDKProvider'; -import { useMemo } from 'react'; - -export const useGetResourceRelationship = async ( - resourceIds: string[], - resourceTypes: RelationshipResourceType[] -): Promise => { - const sdk = useSDK(); - - const result = useMemo(async () => { - if (resourceIds.length === 0 || resourceIds.filter((id) => id.length > 0).length === 0) { - return []; - } - return await getRelationships(sdk, { - resourceExternalIds: resourceIds, - relationshipResourceTypes: resourceTypes - }); - }, [resourceIds, resourceTypes]); - - return await result; -}; From d331ba5bf571adf7c33c880e9bce9930538d7257 Mon Sep 17 00:00:00 2001 From: Daniel Priori Date: Fri, 11 Oct 2024 19:08:33 +0200 Subject: [PATCH 05/13] chore: lint fix --- .../RuleBasedOutputs/RuleBasedOutputsPanel.tsx | 2 +- .../src/components/RuleBasedOutputs/utils.ts | 16 ++-------------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/react-components/src/components/RuleBasedOutputs/RuleBasedOutputsPanel.tsx b/react-components/src/components/RuleBasedOutputs/RuleBasedOutputsPanel.tsx index 49bceb06213..ac23831d6c6 100644 --- a/react-components/src/components/RuleBasedOutputs/RuleBasedOutputsPanel.tsx +++ b/react-components/src/components/RuleBasedOutputs/RuleBasedOutputsPanel.tsx @@ -62,7 +62,7 @@ const StyledRuleBasedOutputsPanel = withSuppressRevealEvents(styled.div<{ $showM min-width: 200px; max-width: 400px; max-height: ${({ $showMore }: { $showMore?: boolean }) => - ($showMore ?? false) ? MAX_HEIGHT : 'inherit'}; + $showMore ?? false ? MAX_HEIGHT : 'inherit'}; border-radius: var(--cogs-border-radius--default); font-size: 13px; line-height: 18px; diff --git a/react-components/src/components/RuleBasedOutputs/utils.ts b/react-components/src/components/RuleBasedOutputs/utils.ts index effe7a96cb6..77732e2cd31 100644 --- a/react-components/src/components/RuleBasedOutputs/utils.ts +++ b/react-components/src/components/RuleBasedOutputs/utils.ts @@ -26,13 +26,7 @@ import { type FdmInstanceNodeWithConnectionAndProperties } from './types'; import { NumericRange, TreeIndexNodeCollection, type NodeAppearance } from '@cognite/reveal'; -import { - type AssetMapping3D, - type Asset, - type Datapoints, - type RelationshipResourceType, - type CogniteClient -} from '@cognite/sdk'; +import { type AssetMapping3D, type Asset, type Datapoints } from '@cognite/sdk'; import { type FdmAssetStylingGroup, type AssetStylingGroup, @@ -40,19 +34,13 @@ import { } from '../Reveal3DResources/types'; import { isDefined } from '../../utilities/isDefined'; import { assertNever } from '../../utilities/assertNever'; -import { - type ExtendedRelationshipWithSourceAndTarget, - type AssetIdsAndTimeseries -} from '../../data-providers/types'; +import { type AssetIdsAndTimeseries } from '../../data-providers/types'; import { uniq } from 'lodash'; import { type DmsUniqueIdentifier } from '../../data-providers/FdmSDK'; import { checkNumericExpressionStatement } from './core/checkNumericExpressionStatement'; import { checkStringExpressionStatement } from './core/checkStringExpressionStatement'; import { checkDatetimeExpressionStatement } from './core/checkDatetimeExpressionStatement'; import { checkBooleanExpressionStatement } from './core/checkBooleanExpressionStatement'; -import { useSDK } from '../RevealCanvas/SDKProvider'; -import { useMemo } from 'react'; -import { getRelationships } from '../../hooks/network/getRelationships'; export const getTriggerNumericData = ( triggerTypeData: TriggerTypeData[], From 727c8df4e8c5e5f719002536de2d951c0d1eda79 Mon Sep 17 00:00:00 2001 From: Daniel Priori Date: Fri, 11 Oct 2024 21:08:07 +0200 Subject: [PATCH 06/13] lint --- .../src/components/RuleBasedOutputs/RuleBasedOutputsPanel.tsx | 2 +- react-components/src/data-providers/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/react-components/src/components/RuleBasedOutputs/RuleBasedOutputsPanel.tsx b/react-components/src/components/RuleBasedOutputs/RuleBasedOutputsPanel.tsx index ac23831d6c6..49bceb06213 100644 --- a/react-components/src/components/RuleBasedOutputs/RuleBasedOutputsPanel.tsx +++ b/react-components/src/components/RuleBasedOutputs/RuleBasedOutputsPanel.tsx @@ -62,7 +62,7 @@ const StyledRuleBasedOutputsPanel = withSuppressRevealEvents(styled.div<{ $showM min-width: 200px; max-width: 400px; max-height: ${({ $showMore }: { $showMore?: boolean }) => - $showMore ?? false ? MAX_HEIGHT : 'inherit'}; + ($showMore ?? false) ? MAX_HEIGHT : 'inherit'}; border-radius: var(--cogs-border-radius--default); font-size: 13px; line-height: 18px; diff --git a/react-components/src/data-providers/index.ts b/react-components/src/data-providers/index.ts index cbcf0c9ebdb..1d95188316e 100644 --- a/react-components/src/data-providers/index.ts +++ b/react-components/src/data-providers/index.ts @@ -7,6 +7,6 @@ export type { InstanceReference, AssetInstanceReference, AssetIdsAndTimeseriesData, - ExtendedRelationship, + ExtendedRelationship } from './types'; export type { Source, DmsUniqueIdentifier, SimpleSource } from './FdmSDK'; From bbe2ff5eb1fa6034338c9b47445419e23f968c85 Mon Sep 17 00:00:00 2001 From: Daniel Priori Date: Mon, 14 Oct 2024 10:50:53 +0200 Subject: [PATCH 07/13] using a hook instead of exposing the get resource relationship --- .../src/components/RuleBasedOutputs/index.ts | 2 - .../RuleBasedOutputs/utils/index.ts | 4 -- react-components/src/hooks/index.ts | 1 + .../network}/getResourceRelationship.ts | 4 +- .../useFetchTimeseriesResourceRelationship.ts | 50 +++++++++++++++++++ 5 files changed, 53 insertions(+), 8 deletions(-) delete mode 100644 react-components/src/components/RuleBasedOutputs/utils/index.ts rename react-components/src/{components/RuleBasedOutputs/utils => hooks/network}/getResourceRelationship.ts (85%) create mode 100644 react-components/src/hooks/useFetchTimeseriesResourceRelationship.ts diff --git a/react-components/src/components/RuleBasedOutputs/index.ts b/react-components/src/components/RuleBasedOutputs/index.ts index 51a0ae14101..5a8e9e84306 100644 --- a/react-components/src/components/RuleBasedOutputs/index.ts +++ b/react-components/src/components/RuleBasedOutputs/index.ts @@ -6,8 +6,6 @@ export * from './hooks'; export { RuleBasedOutputsPanel } from './RuleBasedOutputsPanel'; export { getRuleTriggerTypes } from './utils'; -export * from './utils/index'; - export type { RuleAndEnabled, TriggerType, diff --git a/react-components/src/components/RuleBasedOutputs/utils/index.ts b/react-components/src/components/RuleBasedOutputs/utils/index.ts deleted file mode 100644 index 8798c208b3b..00000000000 --- a/react-components/src/components/RuleBasedOutputs/utils/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -/*! - * Copyright 2024 Cognite AS - */ -export { getResourceRelationship } from './getResourceRelationship'; diff --git a/react-components/src/hooks/index.ts b/react-components/src/hooks/index.ts index dc143267e54..1286b977f15 100644 --- a/react-components/src/hooks/index.ts +++ b/react-components/src/hooks/index.ts @@ -15,6 +15,7 @@ export { useSceneDefaultCamera } from './useSceneDefaultCamera'; export { useSkyboxFromScene } from './useSkyboxFromScene'; export { use3dScenes } from './scenes/use3dScenes'; export { useSceneConfig } from './scenes/useSceneConfig'; +export { useFetchTimeseriesResourceRelationship } from './useFetchTimeseriesResourceRelationship'; export type { CameraNavigationActions } from './useCameraNavigation'; export type { ClickedNodeData, FdmNodeDataResult } from './useClickedNode'; diff --git a/react-components/src/components/RuleBasedOutputs/utils/getResourceRelationship.ts b/react-components/src/hooks/network/getResourceRelationship.ts similarity index 85% rename from react-components/src/components/RuleBasedOutputs/utils/getResourceRelationship.ts rename to react-components/src/hooks/network/getResourceRelationship.ts index 26878c759f7..879b2476f04 100644 --- a/react-components/src/components/RuleBasedOutputs/utils/getResourceRelationship.ts +++ b/react-components/src/hooks/network/getResourceRelationship.ts @@ -2,8 +2,8 @@ * Copyright 2024 Cognite AS */ import { type RelationshipResourceType, type CogniteClient } from '@cognite/sdk/dist/src'; -import { type ExtendedRelationshipWithSourceAndTarget } from '../../../data-providers/types'; -import { getRelationships } from '../../../hooks/network/getRelationships'; +import { type ExtendedRelationshipWithSourceAndTarget } from '../../data-providers/types'; +import { getRelationships } from './getRelationships'; export const getResourceRelationship = async ( sdk: CogniteClient, diff --git a/react-components/src/hooks/useFetchTimeseriesResourceRelationship.ts b/react-components/src/hooks/useFetchTimeseriesResourceRelationship.ts new file mode 100644 index 00000000000..4d6730d8b7a --- /dev/null +++ b/react-components/src/hooks/useFetchTimeseriesResourceRelationship.ts @@ -0,0 +1,50 @@ +/*! + * Copyright 2024 Cognite AS + */ +import { type Asset, type Timeseries } from '@cognite/sdk'; +import { isUndefined } from 'lodash'; +import { useEffect } from 'react'; +import { useSDK } from '../components/RevealCanvas/SDKProvider'; +import { getResourceRelationship } from './network/getResourceRelationship'; + +export const useFetchTimeseriesResourceRelationship = ({ + asset, + filter, + callback +}: { + asset: Asset; + callback: (data: Timeseries[]) => void; + filter?: { isStep?: boolean; isString?: boolean }; +}): void => { + const sdk = useSDK(); + + useEffect(() => { + const getRelationship = async (): Promise => { + const relationshipsFound = await getResourceRelationship( + sdk, + [asset.externalId ?? ''], + ['timeSeries'] + ); + + if (relationshipsFound.length > 0) { + const timeseriesFromRelationshipFound = relationshipsFound + ?.map((rel) => { + if (rel.targetType === 'timeSeries' && rel.target !== undefined) { + const newTimeseries: Timeseries = rel.target; + if (filter?.isStep === false && newTimeseries.isStep) return undefined; + return newTimeseries; + } + if (rel.sourceType === 'timeSeries' && rel.source !== undefined) { + const newTimeseries: Timeseries = rel.source; + if (filter?.isStep === false && newTimeseries.isStep) return undefined; + return newTimeseries; + } + return undefined; + }) + .filter((ts) => !isUndefined(ts)); + callback(timeseriesFromRelationshipFound); + } + }; + void getRelationship(); + }, [asset.externalId, filter, filter?.isStep, filter?.isString, sdk]); +}; From 918e8d3dd97f43fb6047ebf9cb8f38e7dfd19014 Mon Sep 17 00:00:00 2001 From: Daniel Priori Date: Mon, 14 Oct 2024 10:59:48 +0200 Subject: [PATCH 08/13] chore: remove dist --- react-components/src/hooks/network/getResourceRelationship.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-components/src/hooks/network/getResourceRelationship.ts b/react-components/src/hooks/network/getResourceRelationship.ts index 879b2476f04..924511139a2 100644 --- a/react-components/src/hooks/network/getResourceRelationship.ts +++ b/react-components/src/hooks/network/getResourceRelationship.ts @@ -1,7 +1,7 @@ /*! * Copyright 2024 Cognite AS */ -import { type RelationshipResourceType, type CogniteClient } from '@cognite/sdk/dist/src'; +import { type RelationshipResourceType, type CogniteClient } from '@cognite/sdk'; import { type ExtendedRelationshipWithSourceAndTarget } from '../../data-providers/types'; import { getRelationships } from './getRelationships'; From cdbaaa721ffd61c0e2916e7d377be59516010121 Mon Sep 17 00:00:00 2001 From: Daniel Priori Date: Mon, 14 Oct 2024 22:26:37 +0200 Subject: [PATCH 09/13] change to useQuery --- react-components/src/hooks/index.ts | 1 - react-components/src/query/index.ts | 1 + .../useFetchTimeseriesResourceRelationship.ts | 28 +++++++++---------- 3 files changed, 15 insertions(+), 15 deletions(-) rename react-components/src/{hooks => query}/useFetchTimeseriesResourceRelationship.ts (65%) diff --git a/react-components/src/hooks/index.ts b/react-components/src/hooks/index.ts index 1286b977f15..dc143267e54 100644 --- a/react-components/src/hooks/index.ts +++ b/react-components/src/hooks/index.ts @@ -15,7 +15,6 @@ export { useSceneDefaultCamera } from './useSceneDefaultCamera'; export { useSkyboxFromScene } from './useSkyboxFromScene'; export { use3dScenes } from './scenes/use3dScenes'; export { useSceneConfig } from './scenes/useSceneConfig'; -export { useFetchTimeseriesResourceRelationship } from './useFetchTimeseriesResourceRelationship'; export type { CameraNavigationActions } from './useCameraNavigation'; export type { ClickedNodeData, FdmNodeDataResult } from './useClickedNode'; diff --git a/react-components/src/query/index.ts b/react-components/src/query/index.ts index 8b422af8566..0f6d5ed287c 100644 --- a/react-components/src/query/index.ts +++ b/react-components/src/query/index.ts @@ -29,6 +29,7 @@ export { } from './useSearchMappedEquipmentFDM'; export { useTimeseriesByIdsQuery } from './useTimeseriesByIdsQuery'; export { useTimeseriesLatestDatapointQuery } from './useTimeseriesLatestDatapointQuery'; +export { useFetchTimeseriesResourceRelationship } from './useFetchTimeseriesResourceRelationship'; export type { ModelMappings, diff --git a/react-components/src/hooks/useFetchTimeseriesResourceRelationship.ts b/react-components/src/query/useFetchTimeseriesResourceRelationship.ts similarity index 65% rename from react-components/src/hooks/useFetchTimeseriesResourceRelationship.ts rename to react-components/src/query/useFetchTimeseriesResourceRelationship.ts index 4d6730d8b7a..406e66ad93f 100644 --- a/react-components/src/hooks/useFetchTimeseriesResourceRelationship.ts +++ b/react-components/src/query/useFetchTimeseriesResourceRelationship.ts @@ -1,29 +1,30 @@ /*! * Copyright 2024 Cognite AS */ -import { type Asset, type Timeseries } from '@cognite/sdk'; +import { type RelationshipResourceType, type Asset, type Timeseries } from '@cognite/sdk'; import { isUndefined } from 'lodash'; -import { useEffect } from 'react'; import { useSDK } from '../components/RevealCanvas/SDKProvider'; -import { getResourceRelationship } from './network/getResourceRelationship'; +import { getResourceRelationship } from '../hooks/network/getResourceRelationship'; +import { useQuery, type UseQueryResult } from '@tanstack/react-query'; export const useFetchTimeseriesResourceRelationship = ({ asset, filter, - callback + resourceTypes }: { asset: Asset; - callback: (data: Timeseries[]) => void; - filter?: { isStep?: boolean; isString?: boolean }; -}): void => { + resourceTypes: RelationshipResourceType[]; + filter?: { isStep?: boolean }; +}): UseQueryResult => { const sdk = useSDK(); - useEffect(() => { - const getRelationship = async (): Promise => { + return useQuery({ + queryKey: ['reveal', 'react-components', asset.externalId, filter?.isStep, ...resourceTypes], + queryFn: async () => { const relationshipsFound = await getResourceRelationship( sdk, [asset.externalId ?? ''], - ['timeSeries'] + resourceTypes ); if (relationshipsFound.length > 0) { @@ -42,9 +43,8 @@ export const useFetchTimeseriesResourceRelationship = ({ return undefined; }) .filter((ts) => !isUndefined(ts)); - callback(timeseriesFromRelationshipFound); + return timeseriesFromRelationshipFound; } - }; - void getRelationship(); - }, [asset.externalId, filter, filter?.isStep, filter?.isString, sdk]); + } + }); }; From 86ae42c45aabb43520bde7b8f9a7edd73a5c186c Mon Sep 17 00:00:00 2001 From: Daniel Priori Date: Thu, 17 Oct 2024 17:18:04 +0200 Subject: [PATCH 10/13] fix: change the logic to get the time series first and search for the assets and relationships then --- react-components/src/data-providers/index.ts | 3 +- react-components/src/data-providers/types.ts | 8 +- .../src/hooks/network/getAssetsByIds.ts | 1 + .../src/hooks/network/getRelationships.ts | 4 +- .../hooks/network/getSourceRelationships.ts | 4 +- .../hooks/network/getTargetRelationships.ts | 4 +- react-components/src/query/index.ts | 1 + .../query/useAssetsAndTimeseriesLinkages.ts | 170 ++++++++++++++++++ 8 files changed, 187 insertions(+), 8 deletions(-) create mode 100644 react-components/src/query/useAssetsAndTimeseriesLinkages.ts diff --git a/react-components/src/data-providers/index.ts b/react-components/src/data-providers/index.ts index 1d95188316e..ed7a5639435 100644 --- a/react-components/src/data-providers/index.ts +++ b/react-components/src/data-providers/index.ts @@ -7,6 +7,7 @@ export type { InstanceReference, AssetInstanceReference, AssetIdsAndTimeseriesData, - ExtendedRelationship + ExtendedRelationship, + AssetAndTimeseries } from './types'; export type { Source, DmsUniqueIdentifier, SimpleSource } from './FdmSDK'; diff --git a/react-components/src/data-providers/types.ts b/react-components/src/data-providers/types.ts index d55b431a0ee..758bffa8cdc 100644 --- a/react-components/src/data-providers/types.ts +++ b/react-components/src/data-providers/types.ts @@ -7,7 +7,8 @@ import { type ExternalId, type InternalId, type Metadata, - type Relationship + type Relationship, + type Asset } from '@cognite/sdk'; import { type DmsUniqueIdentifier, type Source } from './FdmSDK'; @@ -60,6 +61,11 @@ export type AssetIdsAndTimeseries = { timeseries?: Timeseries | undefined; }; +export type AssetAndTimeseries = { + asset: Asset; + timeseries?: Timeseries[] | undefined; +}; + export type AssetAndTimeseriesIds = { assetIds: Partial; timeseriesIds: Partial; diff --git a/react-components/src/hooks/network/getAssetsByIds.ts b/react-components/src/hooks/network/getAssetsByIds.ts index 409948d83c6..35eebc19daa 100644 --- a/react-components/src/hooks/network/getAssetsByIds.ts +++ b/react-components/src/hooks/network/getAssetsByIds.ts @@ -7,5 +7,6 @@ export const getAssetsByIds = async ( sdk: CogniteClient, assetIds: IdEither[] ): Promise => { + if (assetIds.length === 0) return []; return await sdk.assets.retrieve(assetIds, { ignoreUnknownIds: true }); }; diff --git a/react-components/src/hooks/network/getRelationships.ts b/react-components/src/hooks/network/getRelationships.ts index 1a532016f4a..85acd9d7465 100644 --- a/react-components/src/hooks/network/getRelationships.ts +++ b/react-components/src/hooks/network/getRelationships.ts @@ -10,7 +10,7 @@ import { import { getSourceRelationships } from './getSourceRelationships'; import { getTargetRelationships } from './getTargetRelationships'; -import { type ExtendedRelationship } from '../../data-providers/types'; +import { type ExtendedRelationshipWithSourceAndTarget } from '../../data-providers/types'; type Payload = { resourceExternalIds: CogniteExternalId[]; @@ -23,7 +23,7 @@ type Payload = { export const getRelationships = async ( sdk: CogniteClient, payload: Payload -): Promise => { +): Promise => { const { resourceExternalIds, relationshipResourceTypes, ...rest } = payload; const [sourceRelationships, targetRelationships] = await Promise.all([ diff --git a/react-components/src/hooks/network/getSourceRelationships.ts b/react-components/src/hooks/network/getSourceRelationships.ts index 4bc6f12a8ad..0d9afa58cd4 100644 --- a/react-components/src/hooks/network/getSourceRelationships.ts +++ b/react-components/src/hooks/network/getSourceRelationships.ts @@ -9,7 +9,7 @@ import { } from '@cognite/sdk'; import { filterRelationships } from './filterRelationships'; -import { type ExtendedRelationship } from '../../data-providers/types'; +import { type ExtendedRelationshipWithSourceAndTarget } from '../../data-providers/types'; type Payload = { targetExternalIds: CogniteExternalId[]; @@ -19,7 +19,7 @@ type Payload = { export const getSourceRelationships = async ( sdk: CogniteClient, payload: Payload -): Promise => { +): Promise => { const relationships = await filterRelationships(sdk, payload); return relationships.map((relationship) => ({ ...relationship, diff --git a/react-components/src/hooks/network/getTargetRelationships.ts b/react-components/src/hooks/network/getTargetRelationships.ts index baeca78e13c..14ecf64eb83 100644 --- a/react-components/src/hooks/network/getTargetRelationships.ts +++ b/react-components/src/hooks/network/getTargetRelationships.ts @@ -9,7 +9,7 @@ import { } from '@cognite/sdk'; import { filterRelationships } from './filterRelationships'; -import { type ExtendedRelationship } from '../../data-providers/types'; +import { type ExtendedRelationshipWithSourceAndTarget } from '../../data-providers/types'; type Payload = { sourceExternalIds: CogniteExternalId[]; @@ -19,7 +19,7 @@ type Payload = { export const getTargetRelationships = async ( sdk: CogniteClient, payload: Payload -): Promise => { +): Promise => { const relationships = await filterRelationships(sdk, payload); return relationships.map((relationship) => ({ ...relationship, diff --git a/react-components/src/query/index.ts b/react-components/src/query/index.ts index 0f6d5ed287c..f42803470ef 100644 --- a/react-components/src/query/index.ts +++ b/react-components/src/query/index.ts @@ -30,6 +30,7 @@ export { export { useTimeseriesByIdsQuery } from './useTimeseriesByIdsQuery'; export { useTimeseriesLatestDatapointQuery } from './useTimeseriesLatestDatapointQuery'; export { useFetchTimeseriesResourceRelationship } from './useFetchTimeseriesResourceRelationship'; +export { useAssetsAndTimeseriesLinkages } from './useAssetsAndTimeseriesLinkages'; export type { ModelMappings, diff --git a/react-components/src/query/useAssetsAndTimeseriesLinkages.ts b/react-components/src/query/useAssetsAndTimeseriesLinkages.ts new file mode 100644 index 00000000000..201aba3bf99 --- /dev/null +++ b/react-components/src/query/useAssetsAndTimeseriesLinkages.ts @@ -0,0 +1,170 @@ +/*! + * Copyright 2024 Cognite AS + */ +import { useQuery, type UseQueryResult } from '@tanstack/react-query'; + +import { + type IdEither, + type InternalId, + type Timeseries, + type RelationshipResourceType, + type CogniteClient, + type Asset +} from '@cognite/sdk'; + +import { getRelationships } from '../hooks/network/getRelationships'; +import { useSDK } from '../components/RevealCanvas/SDKProvider'; +import { type AssetAndTimeseriesIds, type AssetAndTimeseries } from '../data-providers/types'; +import { queryKeys } from '../utilities/queryKeys'; +import { isDefined } from '../utilities/isDefined'; +import { getAssetsByIds } from '../hooks/network/getAssetsByIds'; +import { chunk, uniq, uniqBy } from 'lodash'; +import { executeParallel } from '../utilities/executeParallel'; +import { useMemo } from 'react'; + +const FETCH_RELATIONSHIP_CHUNK = 1000; + +export function useAssetsAndTimeseriesLinkages( + timeseriesData: Timeseries[] +): UseQueryResult { + const sdk = useSDK(); + + const timeseriesExternalIds = useMemo(() => { + return uniq(timeseriesData?.map((item) => item.externalId).filter(isDefined)); + }, [timeseriesData]); + + const assetIds = useMemo(() => { + return uniq( + timeseriesData + ?.map((item) => { + if (item.assetId === undefined) { + return undefined; + } + const assetId: InternalId = { id: item.assetId }; + return assetId; + }) + .filter(isDefined) + ); + }, [timeseriesData]); + + const relationshipResourceTypes: RelationshipResourceType[] = ['asset']; + + return useQuery({ + queryKey: [ + queryKeys.timeseriesLinkedToAssets(), + timeseriesExternalIds, + relationshipResourceTypes + ], + queryFn: async () => { + const assetAndTimeseriesIdsFromRelationship = await fetchLinkFromRelationships( + sdk, + timeseriesExternalIds, + relationshipResourceTypes + ); + + // extract the asset ids from the assetAndTimeseriesIdsFromRelationship + const assetIdsFromRelationship = assetAndTimeseriesIdsFromRelationship.map( + (item) => item.assetIds + ); + + const uniqueAssetInternalIdsFromTimeseries = uniqBy(assetIds, 'id'); + const uniqueAssetPartialExternalIdsFromTimeseries = uniqBy( + assetIdsFromRelationship, + 'externalId' + ); + + const uniqueAssetExternalIdsFromTimeseries = uniqueAssetPartialExternalIdsFromTimeseries + .map((item) => { + if (item.externalId === undefined) return undefined; + const id: IdEither = { externalId: item.externalId }; + return id; + }) + .filter(isDefined); + + const assetsInternalId = await getAssetsByIds(sdk, uniqueAssetInternalIdsFromTimeseries); + const assetsExternalId = await getAssetsByIds(sdk, uniqueAssetExternalIdsFromTimeseries); + + const allAssetsFound = assetsInternalId.concat(assetsExternalId); + + const uniqueAssetsFound = uniqBy(allAssetsFound, 'id'); + + const assetsAndTimeseriesAll: AssetAndTimeseries[] = uniqueAssetsFound.map((asset) => { + return { + asset, + timeseries: getLinkedTimeseriesForAsset( + asset, + timeseriesData, + assetAndTimeseriesIdsFromRelationship + ) + }; + }); + + return assetsAndTimeseriesAll; + }, + enabled: timeseriesExternalIds.length > 0 + }); +} + +const getLinkedTimeseriesForAsset = ( + asset: Asset, + timeseries: Timeseries[], + relationshipIds: AssetAndTimeseriesIds[] +): Timeseries[] => { + const timeseriesLinkedToAsset = timeseries.filter((item) => { + const relationshipFoundForThisAsset = relationshipIds.find((relationship) => { + return ( + relationship.assetIds.externalId === asset.externalId && + relationship.timeseriesIds.externalId === item.externalId + ); + }); + if (relationshipFoundForThisAsset !== undefined) return true; + return item.assetId === asset.id; + }); + + return timeseriesLinkedToAsset; +}; + +const fetchLinkFromRelationships = async ( + sdk: CogniteClient, + timeseriesExternalIds: string[], + relationshipResourceTypes: RelationshipResourceType[] +): Promise => { + const timeseriesChunks = chunk(timeseriesExternalIds, FETCH_RELATIONSHIP_CHUNK); + + const dataRelationship = await executeParallel( + timeseriesChunks.map( + (timeseriesIds) => async () => + await getRelationships(sdk, { + resourceExternalIds: timeseriesIds, + relationshipResourceTypes + }) + ), + 2 + ); + + const cleanDataRelationship = dataRelationship.filter(isDefined).flat(); + + const assetAndTimeseriesIdsFromRelationship = + cleanDataRelationship?.map((item) => { + const assetAndTimeseriesIds: AssetAndTimeseriesIds = { + assetIds: { externalId: '' }, + timeseriesIds: { externalId: '' } + }; + + if (item.sourceType === 'asset') { + assetAndTimeseriesIds.assetIds.externalId = item.sourceExternalId; + } else if (item.targetType === 'asset') { + assetAndTimeseriesIds.assetIds.externalId = item.targetExternalId; + } + + if (item.sourceType === 'timeSeries') { + assetAndTimeseriesIds.timeseriesIds.externalId = item.sourceExternalId; + } else if (item.targetType === 'timeSeries') { + assetAndTimeseriesIds.timeseriesIds.externalId = item.targetExternalId; + } + + return assetAndTimeseriesIds; + }) ?? []; + + return assetAndTimeseriesIdsFromRelationship; +}; From 32c112f844c659618e7ddef6d20f0837040191b7 Mon Sep 17 00:00:00 2001 From: Daniel Priori Date: Fri, 18 Oct 2024 15:00:32 +0200 Subject: [PATCH 11/13] add querykey - cr --- react-components/src/query/index.ts | 2 +- .../src/query/useAssetsAndTimeseriesLinkages.ts | 3 ++- ...ts => useFetchTimeseriesFromRelationshipByAsset.ts} | 10 ++++++++-- react-components/src/utilities/queryKeys.ts | 3 ++- 4 files changed, 13 insertions(+), 5 deletions(-) rename react-components/src/query/{useFetchTimeseriesResourceRelationship.ts => useFetchTimeseriesFromRelationshipByAsset.ts} (86%) diff --git a/react-components/src/query/index.ts b/react-components/src/query/index.ts index f42803470ef..9811d7f0115 100644 --- a/react-components/src/query/index.ts +++ b/react-components/src/query/index.ts @@ -29,7 +29,7 @@ export { } from './useSearchMappedEquipmentFDM'; export { useTimeseriesByIdsQuery } from './useTimeseriesByIdsQuery'; export { useTimeseriesLatestDatapointQuery } from './useTimeseriesLatestDatapointQuery'; -export { useFetchTimeseriesResourceRelationship } from './useFetchTimeseriesResourceRelationship'; +export { useFetchTimeseriesFromRelationshipByAsset } from './useFetchTimeseriesFromRelationshipByAsset'; export { useAssetsAndTimeseriesLinkages } from './useAssetsAndTimeseriesLinkages'; export type { diff --git a/react-components/src/query/useAssetsAndTimeseriesLinkages.ts b/react-components/src/query/useAssetsAndTimeseriesLinkages.ts index 201aba3bf99..e64a6a51769 100644 --- a/react-components/src/query/useAssetsAndTimeseriesLinkages.ts +++ b/react-components/src/query/useAssetsAndTimeseriesLinkages.ts @@ -52,8 +52,9 @@ export function useAssetsAndTimeseriesLinkages( return useQuery({ queryKey: [ queryKeys.timeseriesLinkedToAssets(), + relationshipResourceTypes, timeseriesExternalIds, - relationshipResourceTypes + assetIds.map((item) => item.id) ], queryFn: async () => { const assetAndTimeseriesIdsFromRelationship = await fetchLinkFromRelationships( diff --git a/react-components/src/query/useFetchTimeseriesResourceRelationship.ts b/react-components/src/query/useFetchTimeseriesFromRelationshipByAsset.ts similarity index 86% rename from react-components/src/query/useFetchTimeseriesResourceRelationship.ts rename to react-components/src/query/useFetchTimeseriesFromRelationshipByAsset.ts index 406e66ad93f..e6cb2748f2f 100644 --- a/react-components/src/query/useFetchTimeseriesResourceRelationship.ts +++ b/react-components/src/query/useFetchTimeseriesFromRelationshipByAsset.ts @@ -6,8 +6,9 @@ import { isUndefined } from 'lodash'; import { useSDK } from '../components/RevealCanvas/SDKProvider'; import { getResourceRelationship } from '../hooks/network/getResourceRelationship'; import { useQuery, type UseQueryResult } from '@tanstack/react-query'; +import { queryKeys } from '../utilities/queryKeys'; -export const useFetchTimeseriesResourceRelationship = ({ +export const useFetchTimeseriesFromRelationshipByAsset = ({ asset, filter, resourceTypes @@ -19,7 +20,12 @@ export const useFetchTimeseriesResourceRelationship = ({ const sdk = useSDK(); return useQuery({ - queryKey: ['reveal', 'react-components', asset.externalId, filter?.isStep, ...resourceTypes], + queryKey: [ + queryKeys.timeseriesFromRelationship(), + asset.externalId, + filter?.isStep, + ...resourceTypes + ], queryFn: async () => { const relationshipsFound = await getResourceRelationship( sdk, diff --git a/react-components/src/utilities/queryKeys.ts b/react-components/src/utilities/queryKeys.ts index eb7e1635b49..91153e04455 100644 --- a/react-components/src/utilities/queryKeys.ts +++ b/react-components/src/utilities/queryKeys.ts @@ -11,7 +11,8 @@ export const queryKeys = { timeseriesById: (ids: IdEither[]) => [...timeseries, ids] as const, timeseriesLatestDatapoint: () => [...timeseries, 'latest-datapoints'] as const, // TIMESERIES RELATIONSHIPS WITH ASSETS - timeseriesLinkedToAssets: () => [...timeseries, 'timeseries-linked-assets'] as const + timeseriesLinkedToAssets: () => [...timeseries, 'timeseries-linked-assets'] as const, + timeseriesFromRelationship: () => [...timeseries, 'timeseries-relationship'] as const } as const; const assets: string[] = [...queryKeys.all, 'assets']; From 43bd5f4e0227f9eed3f644c53a894e51089d2e88 Mon Sep 17 00:00:00 2001 From: Daniel Priori Date: Mon, 21 Oct 2024 12:51:10 +0200 Subject: [PATCH 12/13] add utests for relationship and time series and separate the hook for fetching relationships --- .../fetchLinkFromRelationshipsByTimeseries.ts | 57 ++++++ .../network/getLinkedTimeseriesForAsset.ts | 25 +++ .../query/useAssetsAndTimeseriesLinkages.ts | 82 +------- ...eFetchTimeseriesFromRelationshipByAsset.ts | 6 +- .../useAssetsAndTimeseriesLinkages.test.tsx | 124 ++++++++++++ ...TimeseriesFromRelationshipByAsset.test.tsx | 187 ++++++++++++++++++ 6 files changed, 404 insertions(+), 77 deletions(-) create mode 100644 react-components/src/hooks/network/fetchLinkFromRelationshipsByTimeseries.ts create mode 100644 react-components/src/hooks/network/getLinkedTimeseriesForAsset.ts create mode 100644 react-components/tests/unit-tests/hooks/useAssetsAndTimeseriesLinkages.test.tsx create mode 100644 react-components/tests/unit-tests/hooks/useFetchTimeseriesFromRelationshipByAsset.test.tsx diff --git a/react-components/src/hooks/network/fetchLinkFromRelationshipsByTimeseries.ts b/react-components/src/hooks/network/fetchLinkFromRelationshipsByTimeseries.ts new file mode 100644 index 00000000000..cade6f4062e --- /dev/null +++ b/react-components/src/hooks/network/fetchLinkFromRelationshipsByTimeseries.ts @@ -0,0 +1,57 @@ +/*! + * Copyright 2024 Cognite AS + */ + +import { type CogniteClient, type RelationshipResourceType } from '@cognite/sdk'; +import { type AssetAndTimeseriesIds } from '../../data-providers/types'; +import { chunk } from 'lodash'; +import { executeParallel } from '../../utilities/executeParallel'; +import { getRelationships } from './getRelationships'; +import { isDefined } from '../../utilities/isDefined'; + +const FETCH_RELATIONSHIP_CHUNK = 1000; + +export const fetchLinkFromRelationshipsByTimeseries = async ( + sdk: CogniteClient, + timeseriesExternalIds: string[], + relationshipResourceTypes: RelationshipResourceType[] +): Promise => { + const timeseriesChunks = chunk(timeseriesExternalIds, FETCH_RELATIONSHIP_CHUNK); + + const dataRelationship = await executeParallel( + timeseriesChunks.map( + (timeseriesIds) => async () => + await getRelationships(sdk, { + resourceExternalIds: timeseriesIds, + relationshipResourceTypes + }) + ), + 2 + ); + + const cleanDataRelationship = dataRelationship.filter(isDefined).flat(); + + const assetAndTimeseriesIdsFromRelationship = + cleanDataRelationship?.map((item) => { + const assetAndTimeseriesIds: AssetAndTimeseriesIds = { + assetIds: { externalId: '' }, + timeseriesIds: { externalId: '' } + }; + + if (item.sourceType === 'asset') { + assetAndTimeseriesIds.assetIds.externalId = item.sourceExternalId; + } else if (item.targetType === 'asset') { + assetAndTimeseriesIds.assetIds.externalId = item.targetExternalId; + } + + if (item.sourceType === 'timeSeries') { + assetAndTimeseriesIds.timeseriesIds.externalId = item.sourceExternalId; + } else if (item.targetType === 'timeSeries') { + assetAndTimeseriesIds.timeseriesIds.externalId = item.targetExternalId; + } + + return assetAndTimeseriesIds; + }) ?? []; + + return assetAndTimeseriesIdsFromRelationship; +}; diff --git a/react-components/src/hooks/network/getLinkedTimeseriesForAsset.ts b/react-components/src/hooks/network/getLinkedTimeseriesForAsset.ts new file mode 100644 index 00000000000..b2ffdbecef9 --- /dev/null +++ b/react-components/src/hooks/network/getLinkedTimeseriesForAsset.ts @@ -0,0 +1,25 @@ +/*! + * Copyright 2024 Cognite AS + */ + +import { type Asset, type Timeseries } from '@cognite/sdk'; +import { type AssetAndTimeseriesIds } from '../../data-providers/types'; + +export const getLinkedTimeseriesForAsset = ( + asset: Asset, + timeseries: Timeseries[], + relationshipIds: AssetAndTimeseriesIds[] +): Timeseries[] => { + const timeseriesLinkedToAsset = timeseries.filter((item) => { + const relationshipFoundForThisAsset = relationshipIds.find((relationship) => { + return ( + relationship.assetIds.externalId === asset.externalId && + relationship.timeseriesIds.externalId === item.externalId + ); + }); + if (relationshipFoundForThisAsset !== undefined) return true; + return item.assetId === asset.id; + }); + + return timeseriesLinkedToAsset; +}; diff --git a/react-components/src/query/useAssetsAndTimeseriesLinkages.ts b/react-components/src/query/useAssetsAndTimeseriesLinkages.ts index e64a6a51769..e1978652f49 100644 --- a/react-components/src/query/useAssetsAndTimeseriesLinkages.ts +++ b/react-components/src/query/useAssetsAndTimeseriesLinkages.ts @@ -7,22 +7,18 @@ import { type IdEither, type InternalId, type Timeseries, - type RelationshipResourceType, - type CogniteClient, - type Asset + type RelationshipResourceType } from '@cognite/sdk'; -import { getRelationships } from '../hooks/network/getRelationships'; import { useSDK } from '../components/RevealCanvas/SDKProvider'; -import { type AssetAndTimeseriesIds, type AssetAndTimeseries } from '../data-providers/types'; +import { type AssetAndTimeseries } from '../data-providers/types'; import { queryKeys } from '../utilities/queryKeys'; import { isDefined } from '../utilities/isDefined'; import { getAssetsByIds } from '../hooks/network/getAssetsByIds'; -import { chunk, uniq, uniqBy } from 'lodash'; -import { executeParallel } from '../utilities/executeParallel'; +import { uniq, uniqBy } from 'lodash'; import { useMemo } from 'react'; - -const FETCH_RELATIONSHIP_CHUNK = 1000; +import { getLinkedTimeseriesForAsset } from '../hooks/network/getLinkedTimeseriesForAsset'; +import { fetchLinkFromRelationshipsByTimeseries } from '../hooks/network/fetchLinkFromRelationshipsByTimeseries'; export function useAssetsAndTimeseriesLinkages( timeseriesData: Timeseries[] @@ -30,7 +26,7 @@ export function useAssetsAndTimeseriesLinkages( const sdk = useSDK(); const timeseriesExternalIds = useMemo(() => { - return uniq(timeseriesData?.map((item) => item.externalId).filter(isDefined)); + return uniq(timeseriesData.map((item) => item.externalId).filter(isDefined)); }, [timeseriesData]); const assetIds = useMemo(() => { @@ -57,7 +53,7 @@ export function useAssetsAndTimeseriesLinkages( assetIds.map((item) => item.id) ], queryFn: async () => { - const assetAndTimeseriesIdsFromRelationship = await fetchLinkFromRelationships( + const assetAndTimeseriesIdsFromRelationship = await fetchLinkFromRelationshipsByTimeseries( sdk, timeseriesExternalIds, relationshipResourceTypes @@ -105,67 +101,3 @@ export function useAssetsAndTimeseriesLinkages( enabled: timeseriesExternalIds.length > 0 }); } - -const getLinkedTimeseriesForAsset = ( - asset: Asset, - timeseries: Timeseries[], - relationshipIds: AssetAndTimeseriesIds[] -): Timeseries[] => { - const timeseriesLinkedToAsset = timeseries.filter((item) => { - const relationshipFoundForThisAsset = relationshipIds.find((relationship) => { - return ( - relationship.assetIds.externalId === asset.externalId && - relationship.timeseriesIds.externalId === item.externalId - ); - }); - if (relationshipFoundForThisAsset !== undefined) return true; - return item.assetId === asset.id; - }); - - return timeseriesLinkedToAsset; -}; - -const fetchLinkFromRelationships = async ( - sdk: CogniteClient, - timeseriesExternalIds: string[], - relationshipResourceTypes: RelationshipResourceType[] -): Promise => { - const timeseriesChunks = chunk(timeseriesExternalIds, FETCH_RELATIONSHIP_CHUNK); - - const dataRelationship = await executeParallel( - timeseriesChunks.map( - (timeseriesIds) => async () => - await getRelationships(sdk, { - resourceExternalIds: timeseriesIds, - relationshipResourceTypes - }) - ), - 2 - ); - - const cleanDataRelationship = dataRelationship.filter(isDefined).flat(); - - const assetAndTimeseriesIdsFromRelationship = - cleanDataRelationship?.map((item) => { - const assetAndTimeseriesIds: AssetAndTimeseriesIds = { - assetIds: { externalId: '' }, - timeseriesIds: { externalId: '' } - }; - - if (item.sourceType === 'asset') { - assetAndTimeseriesIds.assetIds.externalId = item.sourceExternalId; - } else if (item.targetType === 'asset') { - assetAndTimeseriesIds.assetIds.externalId = item.targetExternalId; - } - - if (item.sourceType === 'timeSeries') { - assetAndTimeseriesIds.timeseriesIds.externalId = item.sourceExternalId; - } else if (item.targetType === 'timeSeries') { - assetAndTimeseriesIds.timeseriesIds.externalId = item.targetExternalId; - } - - return assetAndTimeseriesIds; - }) ?? []; - - return assetAndTimeseriesIdsFromRelationship; -}; diff --git a/react-components/src/query/useFetchTimeseriesFromRelationshipByAsset.ts b/react-components/src/query/useFetchTimeseriesFromRelationshipByAsset.ts index e6cb2748f2f..9b81f59fc1b 100644 --- a/react-components/src/query/useFetchTimeseriesFromRelationshipByAsset.ts +++ b/react-components/src/query/useFetchTimeseriesFromRelationshipByAsset.ts @@ -38,18 +38,20 @@ export const useFetchTimeseriesFromRelationshipByAsset = ({ ?.map((rel) => { if (rel.targetType === 'timeSeries' && rel.target !== undefined) { const newTimeseries: Timeseries = rel.target; - if (filter?.isStep === false && newTimeseries.isStep) return undefined; + if ((filter?.isStep === false || filter?.isStep === undefined) && newTimeseries.isStep) return undefined; return newTimeseries; } if (rel.sourceType === 'timeSeries' && rel.source !== undefined) { const newTimeseries: Timeseries = rel.source; - if (filter?.isStep === false && newTimeseries.isStep) return undefined; + if ((filter?.isStep === false || filter?.isStep === undefined) && newTimeseries.isStep) return undefined; return newTimeseries; } return undefined; }) .filter((ts) => !isUndefined(ts)); return timeseriesFromRelationshipFound; + } else { + return []; } } }); diff --git a/react-components/tests/unit-tests/hooks/useAssetsAndTimeseriesLinkages.test.tsx b/react-components/tests/unit-tests/hooks/useAssetsAndTimeseriesLinkages.test.tsx new file mode 100644 index 00000000000..abc23d95c3b --- /dev/null +++ b/react-components/tests/unit-tests/hooks/useAssetsAndTimeseriesLinkages.test.tsx @@ -0,0 +1,124 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { renderHook, waitFor } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { useAssetsAndTimeseriesLinkages } from '../../../src/query/useAssetsAndTimeseriesLinkages'; +import { type CogniteClient, type Asset, type Timeseries } from '@cognite/sdk'; +import { fetchLinkFromRelationshipsByTimeseries } from '../../../src/hooks/network/fetchLinkFromRelationshipsByTimeseries'; +import { getAssetsByIds } from '../../../src/hooks/network/getAssetsByIds'; +import { + type AssetAndTimeseries, + type AssetAndTimeseriesIds +} from '../../../src/data-providers/types'; +import { useSDK } from '../../../src/components/RevealCanvas/SDKProvider'; + +const sdk = { + post: vi.fn().mockResolvedValue({ data: {} }), + project: 'project' +} as unknown as CogniteClient; + +const mockTimeseries: Timeseries[] = [ + { + id: 1, + externalId: 'timeseries-1', + assetId: 1, + isString: false, + isStep: false, + description: '', + lastUpdatedTime: new Date(), + createdTime: new Date() + }, + { + id: 2, + externalId: 'timeseries-2', + assetId: 2, + isString: false, + isStep: false, + description: '', + lastUpdatedTime: new Date(), + createdTime: new Date() + } +]; + +const mockAssetsAndTimeseries: AssetAndTimeseriesIds[] = [ + { + assetIds: { externalId: 'asset-1' }, + timeseriesIds: { id: 1, externalId: 'timeseries-1' } + }, + { + assetIds: { id: 2 }, + timeseriesIds: { id: 2, externalId: 'timeseries-2' } + } +]; + +const mockAssets: Asset[] = [ + { + id: 1, + externalId: 'asset-1', + name: 'Asset 1', + description: '', + lastUpdatedTime: new Date(), + createdTime: new Date(), + rootId: 0 + }, + { + id: 2, + externalId: 'asset-2', + name: 'Asset 2', + description: '', + lastUpdatedTime: new Date(), + createdTime: new Date(), + rootId: 0 + } +]; + +const assetsAndTimeseriesAll: AssetAndTimeseries[] = [ + { + asset: mockAssets[0], + timeseries: [mockTimeseries[0]] + }, + { + asset: mockAssets[1], + timeseries: [mockTimeseries[1]] + } +]; + +const queryClient = new QueryClient(); + +const wrapper = ({ children }: { children: any }): any => ( + {children} +); + +vi.mock('../../../src/hooks/network/fetchLinkFromRelationshipsByTimeseries'); +vi.mock('../../../src/hooks/network/getAssetsByIds'); +vi.mock('../../../src/components/RevealCanvas/SDKProvider'); + +vi.mocked(fetchLinkFromRelationshipsByTimeseries).mockResolvedValue(mockAssetsAndTimeseries); +vi.mocked(getAssetsByIds).mockResolvedValue(mockAssets); +vi.mocked(useSDK).mockReturnValue(sdk); + +describe('useAssetsAndTimeseriesLinkages', () => { + beforeEach(() => { + vi.clearAllMocks(); + queryClient.clear(); + }); + + it('should return assets with related time series data when the input is time series', async () => { + const { result } = renderHook(() => useAssetsAndTimeseriesLinkages(mockTimeseries), { + wrapper + }); + + await waitFor(() => { + expect(result.current.data).toEqual(assetsAndTimeseriesAll); + }); + }); + + it('should return undefined when the input is empty', async () => { + const { result } = renderHook(() => useAssetsAndTimeseriesLinkages([]), { + wrapper + }); + + await waitFor(() => { + expect(result.current.data).toEqual(undefined); + }); + }); +}); diff --git a/react-components/tests/unit-tests/hooks/useFetchTimeseriesFromRelationshipByAsset.test.tsx b/react-components/tests/unit-tests/hooks/useFetchTimeseriesFromRelationshipByAsset.test.tsx new file mode 100644 index 00000000000..fadf130ba48 --- /dev/null +++ b/react-components/tests/unit-tests/hooks/useFetchTimeseriesFromRelationshipByAsset.test.tsx @@ -0,0 +1,187 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { renderHook, waitFor } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { type CogniteClient, type Asset, type Timeseries } from '@cognite/sdk'; +import { type ExtendedRelationshipWithSourceAndTarget } from '../../../src/data-providers/types'; +import { getResourceRelationship } from '../../../src/hooks/network/getResourceRelationship'; +import { useFetchTimeseriesFromRelationshipByAsset } from '../../../src/query/useFetchTimeseriesFromRelationshipByAsset'; +import { useSDK } from '../../../src/components/RevealCanvas/SDKProvider'; + +const sdk = { + post: vi.fn().mockResolvedValue({ data: {} }), + project: 'project' +} as unknown as CogniteClient; + +const mockTimeseries: Timeseries[] = [ + { + id: 1, + externalId: 'timeseries-1', + assetId: 1, + isString: false, + isStep: true, + description: '', + lastUpdatedTime: new Date(), + createdTime: new Date() + }, + { + id: 2, + externalId: 'timeseries-2', + assetId: 1, + isString: false, + isStep: false, + description: '', + lastUpdatedTime: new Date(), + createdTime: new Date() + } +]; + +const mockAssets: Asset[] = [ + { + id: 1, + externalId: 'asset-1', + name: 'Asset 1', + description: '', + lastUpdatedTime: new Date(), + createdTime: new Date(), + rootId: 0 + }, + { + id: 2, + externalId: 'asset-2', + name: 'Asset 2', + description: '', + lastUpdatedTime: new Date(), + createdTime: new Date(), + rootId: 0 + } +]; + +const extendedRelationship: ExtendedRelationshipWithSourceAndTarget[] = [ + { + externalId: 'relationship-1', + sourceExternalId: 'asset-1', + targetExternalId: 'timeseries-1', + lastUpdatedTime: new Date(), + createdTime: new Date(), + source: { + id: 1, + externalId: 'asset-1', + isString: false, + isStep: false, + description: '', + lastUpdatedTime: new Date(), + createdTime: new Date(), + metadata: { + name: 'Asset 1' + } + }, + target: mockTimeseries[0], + sourceType: 'asset', + targetType: 'timeSeries', + relation: 'Source' + }, + { + externalId: 'relationship-2', + sourceExternalId: 'asset-1', + targetExternalId: 'timeseries-1', + lastUpdatedTime: new Date(), + createdTime: new Date(), + source: { + id: 1, + externalId: 'asset-1', + isString: false, + isStep: false, + description: '', + lastUpdatedTime: new Date(), + createdTime: new Date(), + metadata: { + name: 'Asset 1' + } + }, + target: mockTimeseries[1], + sourceType: 'asset', + targetType: 'timeSeries', + relation: 'Source' + } +]; + +const queryClient = new QueryClient(); + +const wrapper = ({ children }: { children: any }): any => ( + {children} +); + +vi.mock('../../../src/hooks/network/getResourceRelationship'); +vi.mock('../../../src/components/RevealCanvas/SDKProvider'); + +vi.mocked(useSDK).mockReturnValue(sdk); + +describe('useFetchTimeseriesFromRelationshipByAsset', () => { + beforeEach(() => { + vi.clearAllMocks(); + queryClient.clear(); + }); + + it('should return step and non-step time series with an asset as the input', async () => { + vi.mocked(getResourceRelationship).mockResolvedValue(extendedRelationship); + + const { result } = renderHook( + () => + useFetchTimeseriesFromRelationshipByAsset({ + asset: mockAssets[0], + filter: { isStep: true }, + resourceTypes: ['timeSeries'] + }), + { + wrapper + } + ); + + await waitFor(() => { + expect(result.current.data).toEqual([ + extendedRelationship[0].target, + extendedRelationship[1].target + ]); + }); + }); + + it('should return only non-step time series with an asset as the input', async () => { + vi.mocked(getResourceRelationship).mockResolvedValue(extendedRelationship); + + const { result } = renderHook( + () => + useFetchTimeseriesFromRelationshipByAsset({ + asset: mockAssets[0], + filter: { isStep: false }, + resourceTypes: ['timeSeries'] + }), + { + wrapper + } + ); + + await waitFor(() => { + expect(result.current.data).toEqual([extendedRelationship[1].target]); + }); + }); + + it('should return empty time series list when has no time series linked to the input asset', async () => { + vi.mocked(getResourceRelationship).mockResolvedValue([]); + + const { result } = renderHook( + () => + useFetchTimeseriesFromRelationshipByAsset({ + asset: mockAssets[1], + filter: { isStep: false }, + resourceTypes: ['timeSeries'] + }), + { + wrapper + } + ); + + await waitFor(() => { + expect(result.current.data).toEqual([]); + }); + }); +}); From 240fd111c184f98211eb412f9efe97467ac10e28 Mon Sep 17 00:00:00 2001 From: Daniel Priori Date: Mon, 21 Oct 2024 13:12:38 +0200 Subject: [PATCH 13/13] lint --- .../useFetchTimeseriesFromRelationshipByAsset.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/react-components/src/query/useFetchTimeseriesFromRelationshipByAsset.ts b/react-components/src/query/useFetchTimeseriesFromRelationshipByAsset.ts index 9b81f59fc1b..bfc9f3a9318 100644 --- a/react-components/src/query/useFetchTimeseriesFromRelationshipByAsset.ts +++ b/react-components/src/query/useFetchTimeseriesFromRelationshipByAsset.ts @@ -38,12 +38,20 @@ export const useFetchTimeseriesFromRelationshipByAsset = ({ ?.map((rel) => { if (rel.targetType === 'timeSeries' && rel.target !== undefined) { const newTimeseries: Timeseries = rel.target; - if ((filter?.isStep === false || filter?.isStep === undefined) && newTimeseries.isStep) return undefined; + if ( + (filter?.isStep === false || filter?.isStep === undefined) && + newTimeseries.isStep + ) + return undefined; return newTimeseries; } if (rel.sourceType === 'timeSeries' && rel.source !== undefined) { const newTimeseries: Timeseries = rel.source; - if ((filter?.isStep === false || filter?.isStep === undefined) && newTimeseries.isStep) return undefined; + if ( + (filter?.isStep === false || filter?.isStep === undefined) && + newTimeseries.isStep + ) + return undefined; return newTimeseries; } return undefined;