Skip to content

Commit

Permalink
feat(react-components): timeseries support in rule based thresholds (#…
Browse files Browse the repository at this point in the history
…4414)

* introducing relationships to retrieve assets from the defined timeseries in the rules - wip

* include a hook to retrieve the timeseries from the timeseries endpoint - wip

* fetch timeseries from relationship and from the timeseries endpoint in order to get the assets linked and include it into the expressions - wip

* fix the support for timeseries in the numeric expression and combining asset data with timeseries data

* more refactoring and development to support timeseries and its assets against expressions

* refactoring and fix partially the timeseries and linked assets on trigger data

* refactoring to remove some inconsistencies on fetching relationship, timeseries and assets - wip

* fix filter when checking for duplicated linkage

* fix turning off rule styling

* refactoring on queries and fixing race condition when fetching assets and timeseries data

* cleanup

* move to a function to get linked relationship

* some clean up and request timeseries ids in chunks

* missing name optional

* Update react-components/src/components/RuleBasedOutputs/types.ts

Co-authored-by: Håkon Flatval <[email protected]>

* fix based on the lib updates

* fix react query arguments for the new version and some fixes from the review

* refactoring timeseries from relationship fetcher

* refactoring based on review

* cache contextualized asset nodes and timeseries ids from expressions when selecting a rule

* refactoring based on review: earlier returning and remove ternary

* linter fixes

* linter

* run promises in parallel

* replacing to await on forgotten places - cr

* not expose internal query keys

* add return type

* change to a const instead of function - cr

---------

Co-authored-by: Håkon Flatval <[email protected]>
  • Loading branch information
danpriori and haakonflatval-cognite authored May 13, 2024
1 parent 90ec258 commit 07a6c74
Show file tree
Hide file tree
Showing 19 changed files with 835 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ export const RuleBasedOutputsButton = ({

if (selectedRule !== undefined) {
selectedRule.isEnabled = data.target.checked;
} else {
if (onRuleSetStylingChanged !== undefined) onRuleSetStylingChanged(undefined);
}
setCurrentRuleSetEnabled(selectedRule);
setRuleInstances(ruleInstances);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
/*!
* Copyright 2024 Cognite AS
*/
import { useEffect, type ReactElement, useState } from 'react';
import { useEffect, type ReactElement, useState, useMemo } from 'react';

import { CogniteCadModel } from '@cognite/reveal';
import { useAllMappedEquipmentAssetMappings } from '../..';
import { type ModelMappingsWithAssets, useAllMappedEquipmentAssetMappings } from '../..';
import { type RuleOutputSet, type AssetStylingGroupAndStyleIndex } from './types';
import { generateRuleBasedOutputs } from './utils';
import { generateRuleBasedOutputs, traverseExpressionToGetTimeseries } from './utils';
import { use3dModels } from '../../hooks/use3dModels';
import { EMPTY_ARRAY } from '../../utilities/constants';
import { type Asset } from '@cognite/sdk';
import { type Datapoints, type Asset } from '@cognite/sdk';
import { isDefined } from '../../utilities/isDefined';
import { type InfiniteData } from '@tanstack/react-query';
import { type AssetIdsAndTimeseries } from '../../utilities/types';
import { useAssetsAndTimeseriesLinkageDataQuery } from '../../query/useAssetsAndTimeseriesLinkageDataQuery';

export type ColorOverlayProps = {
ruleSet: RuleOutputSet | undefined;
Expand All @@ -20,6 +24,8 @@ export function RuleBasedOutputsSelector({
ruleSet,
onRuleSetChanged
}: ColorOverlayProps): ReactElement | undefined {
if (ruleSet === undefined) return;

const models = use3dModels();

const [stylingGroups, setStylingsGroups] = useState<AssetStylingGroupAndStyleIndex[]>();
Expand All @@ -32,56 +38,95 @@ export function RuleBasedOutputsSelector({
} = useAllMappedEquipmentAssetMappings(models);

useEffect(() => {
if (!isFetching && hasNextPage) {
if (!isFetching && (hasNextPage ?? false)) {
void fetchNextPage();
}
}, [isFetching, hasNextPage, fetchNextPage]);

const contextualizedAssetNodes = useMemo(() => {
return (
assetMappings?.pages
.flat()
.flatMap((item) => item.assets)
.map(convertAssetMetadataKeysToLowerCase) ?? []
);
}, [assetMappings]);

const timeseriesExternalIds = useMemo(() => {
const expressions = ruleSet?.rulesWithOutputs
.map((ruleWithOutput) => ruleWithOutput.rule.expression)
.filter(isDefined);
return traverseExpressionToGetTimeseries(expressions) ?? [];
}, [ruleSet]);

const { isLoading: isLoadingAssetIdsAndTimeseriesData, data: assetIdsWithTimeseriesData } =
useAssetsAndTimeseriesLinkageDataQuery({
timeseriesExternalIds,
contextualizedAssetNodes
});

useEffect(() => {
if (onRuleSetChanged !== undefined) onRuleSetChanged(stylingGroups);
}, [stylingGroups]);

useEffect(() => {
if (assetMappings === undefined || models === undefined || isFetching) return;
if (timeseriesExternalIds.length > 0 && isLoadingAssetIdsAndTimeseriesData) return;

setStylingsGroups(EMPTY_ARRAY);

if (ruleSet === undefined) return;

const initializeRuleBasedOutputs = async (model: CogniteCadModel): Promise<void> => {
// parse assets and mappings
// TODO: refactor to be sure to filter only the mappings/assets for the current model within the pages
const flatAssetsMappingsList = assetMappings.pages
.flat()
.map((item) => item.mappings)
.flat();
const flatMappings = flatAssetsMappingsList.map((node) => node.items).flat();
const contextualizedAssetNodes = assetMappings.pages
.flat()
.flatMap((item) => item.assets)
.map(convertAssetMetadataKeysToLowerCase);

const collectionStylings = await generateRuleBasedOutputs(
model,
contextualizedAssetNodes,
flatMappings,
ruleSet
);

setStylingsGroups(collectionStylings);
};

models.forEach(async (model) => {
if (!(model instanceof CogniteCadModel)) {
return;
}
await initializeRuleBasedOutputs(model);
setStylingsGroups(
await initializeRuleBasedOutputs({
model,
assetMappings,
contextualizedAssetNodes,
ruleSet,
assetIdsAndTimeseries: assetIdsWithTimeseriesData?.assetIdsWithTimeseries ?? [],
timeseriesDatapoints: assetIdsWithTimeseriesData?.timeseriesDatapoints ?? []
})
);
});
}, [assetMappings, ruleSet]);
}, [isLoadingAssetIdsAndTimeseriesData, ruleSet]);

return <></>;
}

async function initializeRuleBasedOutputs({
model,
assetMappings,
contextualizedAssetNodes,
ruleSet,
assetIdsAndTimeseries,
timeseriesDatapoints
}: {
model: CogniteCadModel;
assetMappings: InfiniteData<ModelMappingsWithAssets[]>;
contextualizedAssetNodes: Asset[];
ruleSet: RuleOutputSet;
assetIdsAndTimeseries: AssetIdsAndTimeseries[];
timeseriesDatapoints: Datapoints[] | undefined;
}): Promise<AssetStylingGroupAndStyleIndex[]> {
const flatAssetsMappingsList = assetMappings.pages.flat().flatMap((item) => item.mappings);
const flatMappings = flatAssetsMappingsList.flatMap((node) => node.items);

const collectionStylings = await generateRuleBasedOutputs({
model,
contextualizedAssetNodes,
assetMappings: flatMappings,
ruleSet,
assetIdsAndTimeseries,
timeseriesDatapoints
});

return collectionStylings;
}

function convertAssetMetadataKeysToLowerCase(asset: Asset): Asset {
return {
...asset,
Expand Down
22 changes: 20 additions & 2 deletions react-components/src/components/RuleBasedOutputs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
import { type TreeIndexNodeCollection, type NumericRange } from '@cognite/reveal';
import { type FdmNode, type EdgeItem } from '../../utilities/FdmSDK';
import { type AssetStylingGroup, type FdmPropertyType } from '../Reveal3DResources/types';
import { type Datapoints, type Asset, type Timeseries, type ExternalId } from '@cognite/sdk';

// =========== RULE BASED OUTPUT DATA MODEL

export type TriggerType = 'timeseries' | 'metadata';

export type TimeseriesRuleTrigger = {
type: 'timeseries';
timeseriesId: number;
externalId: string;
};

export type MetadataRuleTrigger = {
Expand Down Expand Up @@ -88,6 +89,7 @@ export type Rule = {

export type BaseRuleOutput = {
externalId: string; // comes from FDM
name?: string;
// ruleId: string | undefined; // Transiently it can be left undefined
};

Expand Down Expand Up @@ -227,7 +229,6 @@ export type ViewQueryFilter = {
view: Source;
};

export type ExternalId = string;
export type Space = string;

export type ExternalIdsResultList<PropertyType> = {
Expand Down Expand Up @@ -261,3 +262,20 @@ export type NodeItem<PropertyType = Record<string, unknown>> = {
deletedTime: number;
properties: FdmPropertyType<PropertyType>;
};

export type TriggerTypeData = TriggerMetadataType | TriggerTimeseriesType;

export type TriggerMetadataType = {
type: 'metadata';
asset: Asset;
};

export type TriggerTimeseriesType = {
type: 'timeseries';
timeseries: {
timeseriesWithDatapoints: TimeseriesAndDatapoints[];
linkedAssets: Asset;
};
};

export type TimeseriesAndDatapoints = Timeseries & Datapoints;
Loading

0 comments on commit 07a6c74

Please sign in to comment.