Skip to content

Commit 383a8a2

Browse files
feat(react-components): hook for creating DM-filtering function (#5156)
* feat(react-components): hook for creating DM-filtering function * chore: remove console logs * test: add test for new hook * chore: comment on why we don't provide typings to query objects * chore: fix tests * chore: remove unused function * chore: add extra typing in test * chore: lint fix
1 parent 5eeaea7 commit 383a8a2

11 files changed

+144
-57
lines changed

react-components/src/data-providers/Fdm3dDataProvider.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ export type Fdm3dDataProvider = {
4949
filterNodesByMappedTo3d: (
5050
nodes: InstancesWithViewDefinition[],
5151
models: Array<AddModelOptions<DataSourceType> | AddImage360CollectionDatamodelsOptions>,
52-
spacesToSearch: string[]
52+
spacesToSearch: string[],
53+
includeIndirectRelations: boolean
5354
) => Promise<InstancesWithView[]>;
5455

5556
getCadModelsForInstance: (instance: DmsUniqueIdentifier) => Promise<TaggedAddResourceOptions[]>;

react-components/src/data-providers/core-dm-provider/CoreDm3dDataProvider.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,18 @@ export class CoreDm3dFdm3dDataProvider implements Fdm3dDataProvider {
172172
async filterNodesByMappedTo3d(
173173
nodes: InstancesWithViewDefinition[],
174174
models: Array<AddModelOptions<DataSourceType> | AddImage360CollectionDatamodelsOptions>,
175-
spacesToSearch: string[]
175+
spacesToSearch: string[],
176+
includeIndirectRelations: boolean
176177
): Promise<InstancesWithView[]> {
177178
const revisionRefs = await this.getRevisionRefs(models);
178179

179-
return await filterNodesByMappedTo3d(nodes, revisionRefs, spacesToSearch, this._fdmSdk);
180+
return await filterNodesByMappedTo3d(
181+
nodes,
182+
revisionRefs,
183+
spacesToSearch,
184+
this._fdmSdk,
185+
includeIndirectRelations
186+
);
180187
}
181188

182189
async getCadModelsForInstance(

react-components/src/data-providers/core-dm-provider/check3dConnectedEquipmentQuery.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ function getObject3dRelation(visualizableTableName: string): QueryTableExpressio
117117
};
118118
}
119119

120+
// We want to preserve most of the typing of the object returned here,
121+
// as it gives us better types in the result of the query call later on
122+
// Specifying a type here would either mean losing valuable type information
123+
// or an enourmous type
120124
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
121125
export function createCheck3dConnectedEquipmentQuery(
122126
initialNodes: DmsUniqueIdentifier[],

react-components/src/data-providers/core-dm-provider/filterNodesByMappedTo3d.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,14 @@ describe(filterNodesByMappedTo3d.name, () => {
9494
it('returns empty when no input nodes are provided', async () => {
9595
const fdmSdkMock = new Mock<FdmSDK>().object();
9696

97-
const result = await filterNodesByMappedTo3d([], [modelIdentifier], [], fdmSdkMock);
97+
const result = await filterNodesByMappedTo3d([], [modelIdentifier], [], fdmSdkMock, true);
9898

9999
expect(result).toEqual([]);
100100
});
101101

102102
it('returns empty when no input revisions are provided', async () => {
103103
const fdmSdkMock = new Mock<FdmSDK>().object();
104-
const result = await filterNodesByMappedTo3d(instancesWithView, [], [], fdmSdkMock);
104+
const result = await filterNodesByMappedTo3d(instancesWithView, [], [], fdmSdkMock, true);
105105
expect(result).toEqual([]);
106106
});
107107

@@ -155,7 +155,8 @@ describe(filterNodesByMappedTo3d.name, () => {
155155
instancesWithView,
156156
[modelIdentifier],
157157
[],
158-
fdmSdkMock
158+
fdmSdkMock,
159+
true
159160
);
160161

161162
expect(result).toEqual([

react-components/src/data-providers/core-dm-provider/filterNodesByMappedTo3d.ts

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ export async function filterNodesByMappedTo3d(
3131
nodes: InstancesWithViewDefinition[],
3232
revisionRefs: DmsUniqueIdentifier[],
3333
_spacesToSearch: string[],
34-
fdmSdk: FdmSDK
34+
fdmSdk: FdmSDK,
35+
includeIndirectRelations: boolean
3536
): Promise<InstancesWithView[]> {
3637
if (nodes.length === 0 || revisionRefs.length === 0) {
3738
return [];
@@ -40,7 +41,8 @@ export async function filterNodesByMappedTo3d(
4041
const object3dKeys = await getObject3dsConnectedToAssetAndModelRevisions(
4142
nodes,
4243
revisionRefs,
43-
fdmSdk
44+
fdmSdk,
45+
includeIndirectRelations
4446
);
4547

4648
const result = nodes.map(async (viewWithNodes) => {
@@ -51,12 +53,11 @@ export async function filterNodesByMappedTo3d(
5153
return {
5254
view: viewWithNodes.view,
5355
instances: viewWithNodes.instances.filter((instance) => {
54-
if (
55-
!isDmsInstance(instance.properties[spaceFromView]?.[assetExternalIdWithVersion]?.object3D)
56-
) {
56+
const object3dId =
57+
instance.properties[spaceFromView]?.[assetExternalIdWithVersion]?.object3D;
58+
if (!isDmsInstance(object3dId)) {
5759
return false;
5860
}
59-
const object3dId = instance.properties[spaceFromView][assetExternalIdWithVersion]?.object3D;
6061
return object3dKeys.has(createFdmKey(object3dId));
6162
})
6263
};
@@ -88,7 +89,8 @@ type SelectSourcesType = [
8889
async function getObject3dsConnectedToAssetAndModelRevisions(
8990
nodes: InstancesWithViewDefinition[],
9091
revisionRefs: DmsUniqueIdentifier[],
91-
fdmSdk: FdmSDK
92+
fdmSdk: FdmSDK,
93+
includeIndirectRelations: boolean
9294
): Promise<Set<FdmKey>> {
9395
const initialIds = nodes.flatMap((node) => node.instances.map(restrictToDmsId));
9496

@@ -98,7 +100,7 @@ async function getObject3dsConnectedToAssetAndModelRevisions(
98100
getCogniteAssetDirectRelationProperties(instance, node.view)
99101
)
100102
)
101-
.filter(isDefined);
103+
.filter((node) => isDefined(node) && node.externalId !== undefined && node.space !== undefined);
102104

103105
const mergedIds = concat(initialIds, directlyConnectedIds);
104106
const uniqueIds = uniqBy(mergedIds, createFdmKey);
@@ -118,7 +120,9 @@ async function getObject3dsConnectedToAssetAndModelRevisions(
118120
};
119121

120122
const result = await fdmSdk.queryAllNodesAndEdges<typeof query, SelectSourcesType>(query);
121-
object3dConnectedToEquipmentAndModel.push(...getRelevantObject3ds(result));
123+
object3dConnectedToEquipmentAndModel.push(
124+
...getRelevantObject3ds(result, includeIndirectRelations)
125+
);
122126
}
123127

124128
return new Set<FdmKey>(object3dConnectedToEquipmentAndModel);
@@ -128,16 +132,17 @@ function getRelevantObject3ds(
128132
connectionData: QueryResult<
129133
ReturnType<typeof createCheck3dConnectedEquipmentQuery>,
130134
SelectSourcesType
131-
>
135+
>,
136+
includeIndirectRelations: boolean
132137
): FdmKey[] {
133138
const cadObject3dList = [...connectionData.items.initial_nodes_cad_nodes]
134-
.concat(connectionData.items.indirect_nodes_cad_nodes)
139+
.concat(includeIndirectRelations ? connectionData.items.indirect_nodes_cad_nodes : [])
135140
.map((node) =>
136141
createFdmKey(node.properties[CORE_DM_SPACE][COGNITE_CAD_NODE_VIEW_VERSION_KEY].object3D)
137142
);
138143

139144
const pointCloudObject3dList = [...connectionData.items.initial_nodes_point_cloud_volumes]
140-
.concat(connectionData.items.indirect_nodes_point_cloud_volumes)
145+
.concat(includeIndirectRelations ? connectionData.items.indirect_nodes_point_cloud_volumes : [])
141146
.map((pointCloudVolume) =>
142147
createFdmKey(
143148
pointCloudVolume.properties[CORE_DM_SPACE][COGNITE_POINT_CLOUD_VOLUME_VIEW_VERSION_KEY]
@@ -148,13 +153,13 @@ function getRelevantObject3ds(
148153
const relevant360NodeKeys = new Set<FdmKey>(
149154
[
150155
...connectionData.items.initial_nodes_360_images,
151-
...connectionData.items.indirect_nodes_360_images
156+
...(includeIndirectRelations ? connectionData.items.indirect_nodes_360_images : [])
152157
].map(createFdmKey)
153158
);
154159

155160
const relevant360AnnotationEdges = [
156161
...connectionData.items.initial_edges_360_image_annotations,
157-
...connectionData.items.indirect_edges_360_image_annotations
162+
...(includeIndirectRelations ? connectionData.items.indirect_edges_360_image_annotations : [])
158163
].filter((edge) => relevant360NodeKeys.has(createFdmKey(edge.endNode)));
159164

160165
const image360Object3dList = relevant360AnnotationEdges.map((edge) =>

react-components/src/data-providers/legacy-fdm-provider/LegacyFdm3dDataProvider.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ export class LegacyFdm3dDataProvider implements Fdm3dDataProvider {
102102
async filterNodesByMappedTo3d(
103103
nodes: InstancesWithViewDefinition[],
104104
models: Array<AddModelOptions<DataSourceType> | AddImage360CollectionDatamodelsOptions>,
105-
spacesToSearch: string[]
105+
spacesToSearch: string[],
106+
includeIndirectRelations: boolean
106107
): Promise<InstancesWithView[]> {
107108
const classicModels = models.filter((model) => isClassicIdentifier(model));
108109

@@ -121,7 +122,8 @@ export class LegacyFdm3dDataProvider implements Fdm3dDataProvider {
121122
this._fdmSdk,
122123
transformedNodes,
123124
classicModels,
124-
spacesToSearch
125+
spacesToSearch,
126+
includeIndirectRelations
125127
);
126128
}
127129

react-components/src/data-providers/legacy-fdm-provider/filterNodesByMappedTo3d.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ export async function filterNodesByMappedTo3d(
2525
fdmSdk: FdmSDK,
2626
nodesWithViews: InstancesWithView[],
2727
models: AddModelOptions[],
28-
spacesToSearch: string[]
28+
spacesToSearch: string[],
29+
includeIndirectRelations: boolean
2930
): Promise<InstancesWithView[]> {
3031
const nodes = nodesWithViews.flatMap((result) => result.instances);
3132
const views = nodesWithViews.map((result) => result.view);
@@ -61,7 +62,8 @@ export async function filterNodesByMappedTo3d(
6162
checkInstanceWithMappedEquipmentMaps(
6263
mappedEquipmentFirstLevelMap,
6364
equipmentSecondLevelMap,
64-
instance
65+
instance,
66+
includeIndirectRelations
6567
)
6668
);
6769

@@ -188,7 +190,8 @@ async function createModelsMap(
188190
function checkInstanceWithMappedEquipmentMaps(
189191
mappedEquipmentFirstLevelMap: Record<FdmKey, EdgeItem[]>,
190192
equipmentSecondLevelMap: Record<FdmKey, EdgeItem[]>,
191-
instance: NodeItem
193+
instance: NodeItem,
194+
includeIndirectRelations: boolean
192195
): boolean {
193196
const key: FdmKey = `${instance.space}/${instance.externalId}`;
194197
const directRelationProperties = getDirectRelationProperties(instance);
@@ -200,7 +203,7 @@ function checkInstanceWithMappedEquipmentMaps(
200203
return true;
201204
}
202205

203-
if (isSecondLevelWithEdge) {
206+
if (isSecondLevelWithEdge && includeIndirectRelations) {
204207
return equipmentSecondLevelMap[key].some((edge) => {
205208
const { space, externalId } = edge.endNode;
206209

@@ -211,7 +214,7 @@ function checkInstanceWithMappedEquipmentMaps(
211214
});
212215
}
213216

214-
if (isSecondLevelWithDirectRelation) {
217+
if (isSecondLevelWithDirectRelation && includeIndirectRelations) {
215218
const isMappedWithDirectRelation = directRelationProperties.some(
216219
({ externalId, space }) =>
217220
mappedEquipmentFirstLevelMap[`${space}/${externalId}`] !== undefined

react-components/src/query/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ export {
2525
} from './useSearchMappedEquipmentAssetMappings';
2626
export {
2727
useAllMappedEquipmentFDM,
28-
useSearchMappedEquipmentFDM
28+
useSearchMappedEquipmentFDM,
29+
useFilterNodesByMappedToModelsCallback
2930
} from './useSearchMappedEquipmentFDM';
3031
export { useTimeseriesByIdsQuery } from './useTimeseriesByIdsQuery';
3132
export { useTimeseriesLatestDatapointQuery } from './useTimeseriesLatestDatapointQuery';

react-components/src/query/useFilterOnClassicAssetsInScene.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
* Copyright 2023 Cognite AS
33
*/
44
import type { AddModelOptions } from '@cognite/reveal';
5-
import { useContext, useEffect, useMemo } from 'react';
5+
import { useCallback, useContext, useEffect, useMemo } from 'react';
66
import type { Asset, CogniteClient, DirectRelationReference } from '@cognite/sdk';
7+
78
import {
89
type AddCadResourceOptions,
910
type AddImage360CollectionOptions,
@@ -116,9 +117,14 @@ export const useFilterOnClassicAssetsInScene = (
116117
isLoadingAllPointCloudAssets
117118
]);
118119

120+
const filterAssetsCallback = useCallback(
121+
(assets: Asset[]) => assets.filter((asset) => allAssetsMap?.has(asset.id) === true),
122+
[allAssetsMap]
123+
);
124+
119125
if (allAssetsMap === undefined) {
120126
return undefined;
121127
}
122128

123-
return (assets: Asset[]) => assets.filter((asset) => allAssetsMap.has(asset.id));
129+
return filterAssetsCallback;
124130
};

react-components/src/query/useSearchMappedEquipmentFDM.test.tsx

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import { type AddModelOptions } from '@cognite/reveal';
44
import {
55
useSearchMappedEquipmentFDM,
66
useAllMappedEquipmentFDM,
7-
type InstancesWithView
7+
type InstancesWithView,
8+
useFilterNodesByMappedToModelsCallback
89
} from '../query/useSearchMappedEquipmentFDM';
910
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
1011
import { cadNodesFixtures } from '#test-utils/fixtures/dm/nodeItems';
11-
import { type FdmSDK } from '../data-providers/FdmSDK';
12+
import { type Source, type FdmSDK } from '../data-providers/FdmSDK';
1213
import { Mock } from 'moq.ts';
1314
import { type RevealRenderTarget } from '../architecture';
1415
import type { FC, PropsWithChildren } from 'react';
@@ -17,6 +18,8 @@ import { ViewerContext } from '../components/RevealCanvas/ViewerContext';
1718
import { FdmSdkContext } from '../components/RevealCanvas/FdmDataProviderContext';
1819
import { getMockViewByIdResponse } from '#test-utils/fixtures/dm/getMockViewByIdResponse';
1920
import { getMockViewItemFromSimpleSource } from '#test-utils/fixtures/dm/getMockViewItemFromSimpleSource';
21+
import { COGNITE_ASSET_SOURCE } from '../data-providers/core-dm-provider/dataModels';
22+
import { type TableExpressionFilterDefinition } from '@cognite/sdk';
2023

2124
const queryClient = new QueryClient();
2225

@@ -60,25 +63,27 @@ const wrapper: FC<PropsWithChildren> = ({ children }) => (
6063
</QueryClientProvider>
6164
);
6265

63-
describe(useSearchMappedEquipmentFDM.name, () => {
64-
const mockModels: AddModelOptions[] = [
65-
{ modelId: 456, revisionId: 789 },
66-
{ modelId: 123, revisionId: 456 }
67-
];
66+
const mockModels: AddModelOptions[] = [
67+
{ modelId: 456, revisionId: 789 },
68+
{ modelId: 123, revisionId: 456 }
69+
];
6870

69-
const mockInstancesFilter = { equals: { property: ['key'], value: 'value' } };
71+
const mockInstancesFilter: TableExpressionFilterDefinition = {
72+
equals: { property: ['key'], value: 'value' }
73+
};
7074

71-
const mockViewsToSearch = [
72-
{ externalId: 'CogniteCADNode', space: 'cdf_cdm', version: 'v1', type: 'view' as const },
73-
{ externalId: 'CogniteCADNode', space: 'cdf_cdm', version: 'v1', type: 'view' as const }
74-
];
75+
const mockViewsToSearch: Source[] = [
76+
{ externalId: 'CogniteCADNode', space: 'cdf_cdm', version: 'v1', type: 'view' as const },
77+
{ externalId: 'CogniteCADNode', space: 'cdf_cdm', version: 'v1', type: 'view' as const }
78+
];
7579

76-
const mockInstancesWithView: InstancesWithView[] = [
77-
{ view: mockViewsToSearch[0], instances: cadNodesFixtures },
80+
const mockInstancesWithView: InstancesWithView[] = [
81+
{ view: mockViewsToSearch[0], instances: cadNodesFixtures },
7882

79-
{ view: mockViewsToSearch[1], instances: cadNodesFixtures }
80-
];
83+
{ view: mockViewsToSearch[1], instances: cadNodesFixtures }
84+
];
8185

86+
describe(useSearchMappedEquipmentFDM.name, () => {
8287
beforeEach(() => {
8388
vi.clearAllMocks();
8489
queryClient.clear();
@@ -183,17 +188,42 @@ describe(useSearchMappedEquipmentFDM.name, () => {
183188
});
184189
});
185190

186-
describe(useAllMappedEquipmentFDM.name, () => {
187-
const mockViewsToSearch = [
188-
{ externalId: 'view1', space: 'space1', version: 'v1' },
189-
{ externalId: 'view2', space: 'space2', version: 'v1' }
190-
];
191+
describe(useFilterNodesByMappedToModelsCallback.name, () => {
192+
beforeEach(() => {
193+
vi.clearAllMocks();
194+
queryClient.clear();
195+
196+
mockListMappedFdmNodes.mockResolvedValue(cadNodesFixtures);
197+
mockFilterNodesByMappedTo3d.mockResolvedValue(mockInstancesWithView);
198+
});
199+
200+
it('returned function should call filterNodesByMappedTo3D and return its result', async () => {
201+
const viewMock = getMockViewByIdResponse(mockViewsToSearch);
202+
vi.mocked(mockFdmSdk.getViewsByIds).mockResolvedValueOnce(viewMock);
203+
204+
const { result } = renderHook(
205+
() => useFilterNodesByMappedToModelsCallback(mockModels, COGNITE_ASSET_SOURCE, false),
206+
{ wrapper }
207+
);
208+
209+
const returnValue = await result.current([]);
210+
211+
expect(mockFilterNodesByMappedTo3d).toHaveBeenCalledWith(
212+
[{ instances: [], view: viewMock.items[0] }],
213+
mockModels,
214+
[COGNITE_ASSET_SOURCE.space],
215+
false
216+
);
217+
218+
const filterReturnValue = await mockFilterNodesByMappedTo3d.mock.results[0].value;
191219

192-
const mockModels: AddModelOptions[] = [
193-
{ modelId: 456, revisionId: 789 },
194-
{ modelId: 123, revisionId: 456 }
195-
];
220+
expect(returnValue).toEqual(
221+
filterReturnValue.flatMap((res: InstancesWithView) => res.instances)
222+
);
223+
});
224+
});
196225

226+
describe(useAllMappedEquipmentFDM.name, () => {
197227
beforeEach(() => {
198228
vi.clearAllMocks();
199229
queryClient.clear();

0 commit comments

Comments
 (0)