diff --git a/packages/react-components/src/hooks/useAlarms/hookHelpers/index.ts b/packages/react-components/src/hooks/useAlarms/hookHelpers/index.ts index a37274da4..c04d38137 100644 --- a/packages/react-components/src/hooks/useAlarms/hookHelpers/index.ts +++ b/packages/react-components/src/hooks/useAlarms/hookHelpers/index.ts @@ -1,3 +1,3 @@ export * from './useAlarmAssets'; -export * from './useLatestAlarmPropertyValue'; +export * from './useLatestAlarmPropertyValues'; export * from './useAlarmModels'; diff --git a/packages/react-components/src/hooks/useAlarms/hookHelpers/predicates.spec.ts b/packages/react-components/src/hooks/useAlarms/hookHelpers/predicates.spec.ts index a50140ec0..a8e964d51 100644 --- a/packages/react-components/src/hooks/useAlarms/hookHelpers/predicates.spec.ts +++ b/packages/react-components/src/hooks/useAlarms/hookHelpers/predicates.spec.ts @@ -5,7 +5,7 @@ import { MOCK_COMPOSITE_MODEL_ID, mockStateAssetProperty, } from '../../../testing/alarms'; -import { AlarmProperty, AlarmRequest } from '../types'; +import type { AlarmProperty, AlarmRequest } from '../types'; import { isAlarmProperty, isAssetModelRequest, diff --git a/packages/react-components/src/hooks/useAlarms/hookHelpers/predicates.ts b/packages/react-components/src/hooks/useAlarms/hookHelpers/predicates.ts index f70a202a3..91d595f80 100644 --- a/packages/react-components/src/hooks/useAlarms/hookHelpers/predicates.ts +++ b/packages/react-components/src/hooks/useAlarms/hookHelpers/predicates.ts @@ -1,5 +1,5 @@ import { AssetPropertyValue } from '@aws-sdk/client-iotsitewise'; -import { +import type { AlarmAssetModelRequest, AlarmAssetRequest, AlarmCompositeModelRequest, diff --git a/packages/react-components/src/hooks/useAlarms/hookHelpers/useAlarmAssets.spec.ts b/packages/react-components/src/hooks/useAlarms/hookHelpers/useAlarmAssets.spec.ts index 99cb0703d..b053d3b2b 100644 --- a/packages/react-components/src/hooks/useAlarms/hookHelpers/useAlarmAssets.spec.ts +++ b/packages/react-components/src/hooks/useAlarms/hookHelpers/useAlarmAssets.spec.ts @@ -8,11 +8,11 @@ import { DescribeAssetResponse, } from '@aws-sdk/client-iotsitewise'; import { useAlarmAssets } from './useAlarmAssets'; -import { +import type { AlarmAssetModelRequest, AlarmAssetRequest, AlarmCompositeModelRequest, - AlarmData, + AlarmDataInternal, AlarmInputPropertyRequest, } from '../types'; import { queryClient } from '../../../queries'; @@ -32,13 +32,15 @@ import { mockAlarmDataDescribeAssetModel, mockAlarmModelCompositeModel, mockAlarmModelCompositeModel2, + mockAssetModelProperties, + mockAssetProperties, mockInputProperty, } from '../../../testing/alarms'; const mockDescribeAssetResponse = ({ assetId = MOCK_ASSET_ID, compositeModels = [], - assetProperties = [], + assetProperties = mockAssetProperties, }: { assetId?: string; compositeModels?: AssetCompositeModel[]; @@ -61,7 +63,7 @@ const mockDescribeAssetResponse = ({ const mockDescribeAssetModelResponse = ({ assetModelId = MOCK_ASSET_MODEL_ID, compositeModels = [], - assetModelProperties = [], + assetModelProperties = mockAssetModelProperties, }: { assetModelId?: string; compositeModels?: AssetModelCompositeModel[]; @@ -124,10 +126,16 @@ describe('useAlarmAssets', () => { mockDescribeAssetResponse({ compositeModels: [mockAlarmCompositeModel] }) ); - const alarmCompositeModelRequest: AlarmCompositeModelRequest = { + const alarmCompositeModelRequest = { assetId: MOCK_ASSET_ID, assetCompositeModelId: MOCK_COMPOSITE_MODEL_ID, - }; + } satisfies AlarmCompositeModelRequest; + + const mockAlarmDataInternal = { + ...mockAlarmDataDescribeAsset, + request: alarmCompositeModelRequest, + properties: mockAssetProperties, + } satisfies AlarmDataInternal; const { result: alarmDataResults } = renderHook(() => useAlarmAssets({ @@ -138,9 +146,7 @@ describe('useAlarmAssets', () => { await waitFor(() => { expect(alarmDataResults.current.length).toBe(1); - expect(alarmDataResults.current[0]).toMatchObject( - mockAlarmDataDescribeAsset - ); + expect(alarmDataResults.current[0]).toMatchObject(mockAlarmDataInternal); }); expect(describeAssetMock).toBeCalled(); @@ -148,22 +154,24 @@ describe('useAlarmAssets', () => { }); it('should return AlarmData with content for one alarm in an alarm input property request', async () => { + const mockProperties = [mockInputProperty]; describeAssetMock.mockResolvedValue( mockDescribeAssetResponse({ compositeModels: [mockAlarmCompositeModel], - assetProperties: [mockInputProperty], + assetProperties: mockProperties, }) ); - const alarmInputPropertyRequest: AlarmInputPropertyRequest = { + const alarmInputPropertyRequest = { assetId: MOCK_ASSET_ID, inputPropertyId: MOCK_ALARM_INPUT_PROPERTY_ID, - }; + } satisfies AlarmInputPropertyRequest; - const expectedAlarmData: AlarmData = { + const expectedAlarmData = { ...mockAlarmDataDescribeAsset, - inputProperty: [mockInputProperty], - }; + request: alarmInputPropertyRequest, + properties: mockProperties, + } satisfies AlarmDataInternal; const { result: alarmDataResults } = renderHook(() => useAlarmAssets({ @@ -188,15 +196,27 @@ describe('useAlarmAssets', () => { }) ); - const mockAlarmDataDescribeAsset2: AlarmData = { + const mockAlarmDataDescribeAsset2 = { ...mockAlarmDataDescribeAsset, compositeModelId: MOCK_COMPOSITE_MODEL_ID_2, compositeModelName: MOCK_COMPOSITE_MODEL_NAME_2, }; - const alarmAssetRequest: AlarmAssetRequest = { + const alarmAssetRequest = { assetId: MOCK_ASSET_ID, - }; + } satisfies AlarmAssetRequest; + + const mockAlarmDataInternal = { + ...mockAlarmDataDescribeAsset, + request: alarmAssetRequest, + properties: mockAssetProperties, + } satisfies AlarmDataInternal; + + const mockAlarmDataInternal2 = { + ...mockAlarmDataDescribeAsset2, + request: alarmAssetRequest, + properties: mockAssetProperties, + } satisfies AlarmDataInternal; const { result: alarmDataResults } = renderHook(() => useAlarmAssets({ @@ -207,12 +227,8 @@ describe('useAlarmAssets', () => { await waitFor(() => { expect(alarmDataResults.current.length).toBe(2); - expect(alarmDataResults.current[0]).toMatchObject( - mockAlarmDataDescribeAsset - ); - expect(alarmDataResults.current[1]).toMatchObject( - mockAlarmDataDescribeAsset2 - ); + expect(alarmDataResults.current[0]).toMatchObject(mockAlarmDataInternal); + expect(alarmDataResults.current[1]).toMatchObject(mockAlarmDataInternal2); }); expect(describeAssetMock).toBeCalled(); @@ -229,16 +245,28 @@ describe('useAlarmAssets', () => { }) ); - const alarmAssetModelRequest: AlarmAssetModelRequest = { + const alarmAssetModelRequest = { assetModelId: MOCK_ASSET_MODEL_ID, - }; + } satisfies AlarmAssetModelRequest; - const mockAlarmDataDescribeAssetModel2: AlarmData = { + const mockAlarmDataDescribeAssetModel2 = { ...mockAlarmDataDescribeAssetModel, compositeModelId: MOCK_COMPOSITE_MODEL_ID_2, compositeModelName: MOCK_COMPOSITE_MODEL_NAME_2, }; + const mockAlarmDataInternal = { + ...mockAlarmDataDescribeAssetModel, + request: alarmAssetModelRequest, + properties: mockAssetModelProperties, + } satisfies AlarmDataInternal; + + const mockAlarmDataInternal2 = { + ...mockAlarmDataDescribeAssetModel2, + request: alarmAssetModelRequest, + properties: mockAssetModelProperties, + } satisfies AlarmDataInternal; + const { result: alarmDataResults } = renderHook(() => useAlarmAssets({ iotSiteWiseClient: iotSiteWiseClientMock, @@ -248,12 +276,8 @@ describe('useAlarmAssets', () => { await waitFor(() => { expect(alarmDataResults.current.length).toBe(2); - expect(alarmDataResults.current[0]).toMatchObject( - mockAlarmDataDescribeAssetModel - ); - expect(alarmDataResults.current[1]).toMatchObject( - mockAlarmDataDescribeAssetModel2 - ); + expect(alarmDataResults.current[0]).toMatchObject(mockAlarmDataInternal); + expect(alarmDataResults.current[1]).toMatchObject(mockAlarmDataInternal2); }); expect(describeAssetMock).not.toBeCalled(); @@ -263,9 +287,9 @@ describe('useAlarmAssets', () => { it('should return no AlarmData when there are no alarms on an asset', async () => { describeAssetMock.mockResolvedValue(mockDescribeAssetResponse({})); - const alarmAssetRequest: AlarmAssetRequest = { + const alarmAssetRequest = { assetId: MOCK_ASSET_ID, - }; + } satisfies AlarmAssetRequest; const { result: alarmDataResults } = renderHook(() => useAlarmAssets({ @@ -285,9 +309,9 @@ describe('useAlarmAssets', () => { mockDescribeAssetModelResponse({}) ); - const alarmAssetModelRequest: AlarmAssetModelRequest = { + const alarmAssetModelRequest = { assetModelId: MOCK_ASSET_MODEL_ID, - }; + } satisfies AlarmAssetModelRequest; const { result: alarmDataResults } = renderHook(() => useAlarmAssets({ @@ -309,22 +333,34 @@ describe('useAlarmAssets', () => { }) ); - const alarmCompositeModelRequest1: AlarmCompositeModelRequest = { + const alarmCompositeModelRequest1 = { assetId: MOCK_ASSET_ID, assetCompositeModelId: MOCK_COMPOSITE_MODEL_ID, - }; + } satisfies AlarmCompositeModelRequest; - const alarmCompositeModelRequest2: AlarmCompositeModelRequest = { + const alarmCompositeModelRequest2 = { assetId: MOCK_ASSET_ID, assetCompositeModelId: MOCK_COMPOSITE_MODEL_ID_2, - }; + } satisfies AlarmCompositeModelRequest; - const mockAlarmDataDescribeAsset2: AlarmData = { + const mockAlarmDataDescribeAsset2 = { ...mockAlarmDataDescribeAsset, compositeModelId: MOCK_COMPOSITE_MODEL_ID_2, compositeModelName: MOCK_COMPOSITE_MODEL_NAME_2, }; + const mockAlarmDataInternal1 = { + ...mockAlarmDataDescribeAsset, + request: alarmCompositeModelRequest1, + properties: mockAssetProperties, + } satisfies AlarmDataInternal; + + const mockAlarmDataInternal2 = { + ...mockAlarmDataDescribeAsset2, + request: alarmCompositeModelRequest2, + properties: mockAssetProperties, + } satisfies AlarmDataInternal; + const { result: alarmDataResults } = renderHook(() => useAlarmAssets({ iotSiteWiseClient: iotSiteWiseClientMock, @@ -334,15 +370,42 @@ describe('useAlarmAssets', () => { await waitFor(() => { expect(alarmDataResults.current.length).toBe(2); - expect(alarmDataResults.current[0]).toMatchObject( - mockAlarmDataDescribeAsset - ); - expect(alarmDataResults.current[1]).toMatchObject( - mockAlarmDataDescribeAsset2 - ); + expect(alarmDataResults.current[0]).toMatchObject(mockAlarmDataInternal1); + expect(alarmDataResults.current[1]).toMatchObject(mockAlarmDataInternal2); }); expect(describeAssetMock).toBeCalledTimes(1); expect(describeAssetModelMock).not.toBeCalled(); }); + + it('should return AlarmData with just request and status when query fails', async () => { + describeAssetMock.mockRejectedValue('Failure calling DescribeAsset'); + + const mockRequest = { assetId: MOCK_ASSET_ID }; + const expectedAlarmData = { + assetId: MOCK_ASSET_ID, + status: { + isLoading: false, + isRefetching: false, + isSuccess: false, + isError: true, + }, + request: mockRequest, + } satisfies AlarmDataInternal; + + const { result: alarmDataResults } = renderHook(() => + useAlarmAssets({ + iotSiteWiseClient: iotSiteWiseClientMock, + requests: [mockRequest], + retry: false, + }) + ); + + await waitFor(() => { + expect(alarmDataResults.current.length).toBe(1); + expect(alarmDataResults.current[0]).toMatchObject(expectedAlarmData); + }); + + expect(describeAssetMock).toBeCalledTimes(1); + }); }); diff --git a/packages/react-components/src/hooks/useAlarms/hookHelpers/useAlarmAssets.ts b/packages/react-components/src/hooks/useAlarms/hookHelpers/useAlarmAssets.ts index 973278542..ca9bc725d 100644 --- a/packages/react-components/src/hooks/useAlarms/hookHelpers/useAlarmAssets.ts +++ b/packages/react-components/src/hooks/useAlarms/hookHelpers/useAlarmAssets.ts @@ -1,18 +1,19 @@ import { useMemo } from 'react'; import { IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; -import { AlarmData, AlarmRequest } from '../types'; +import type { AlarmDataInternal, AlarmRequest } from '../types'; import { useDescribeAssetModels, useDescribeAssets } from '../../../queries'; import { - buildFromAssetModelResponse, - buildFromAssetResponse, -} from '../utils/alarmDataUtils'; -import { getStatusForQuery } from '../utils/queryUtils'; + createFromAssetModelResponse, + createFromAssetResponse, +} from '../utils/createAlarmData'; +import { getStatusForQuery } from '../utils/queryStatus'; import { isAssetModelRequest, isAssetRequest } from './predicates'; +import type { QueryOptionsGlobal } from '../../../queries/common/types'; -export interface UseAlarmAssetsOptions { +export type UseAlarmAssetsOptions = { iotSiteWiseClient?: IoTSiteWiseClient; requests?: AlarmRequest[]; -} +} & QueryOptionsGlobal; /** * useAlarmAssets is a hook used to describe the asset or assetModel @@ -25,12 +26,14 @@ export interface UseAlarmAssetsOptions { export function useAlarmAssets({ iotSiteWiseClient, requests = [], -}: UseAlarmAssetsOptions): AlarmData[] { + retry, +}: UseAlarmAssetsOptions): AlarmDataInternal[] { // Fetch an asset model for request with an assetModelId const assetModelRequests = requests.filter(isAssetModelRequest); const assetModelQueries = useDescribeAssetModels({ iotSiteWiseClient, requests: assetModelRequests, + retry, }); // Fetch an asset for each request with an assetId @@ -38,15 +41,16 @@ export function useAlarmAssets({ const assetQueries = useDescribeAssets({ iotSiteWiseClient, requests: assetRequests, + retry, }); - // Build AlarmData for all alarms from assetModels + // Initialize AlarmData for all alarms from assetModels const assetModelAlarms = useMemo( () => assetModelRequests .map((request, index) => { const status = getStatusForQuery(assetModelQueries[index]); - return buildFromAssetModelResponse({ + return createFromAssetModelResponse({ request, status, assetModelResponse: assetModelQueries[index].data, @@ -56,13 +60,13 @@ export function useAlarmAssets({ [assetModelRequests, assetModelQueries] ); - // Build AlarmData for all alarms from assets + // Initialize AlarmData for all alarms from assets const assetAlarms = useMemo( () => assetRequests .map((request, index) => { const status = getStatusForQuery(assetQueries[index]); - return buildFromAssetResponse({ + return createFromAssetResponse({ request, status, assetResponse: assetQueries[index].data, diff --git a/packages/react-components/src/hooks/useAlarms/hookHelpers/useAlarmModels.spec.ts b/packages/react-components/src/hooks/useAlarms/hookHelpers/useAlarmModels.spec.ts index 3d141789e..db77367a3 100644 --- a/packages/react-components/src/hooks/useAlarms/hookHelpers/useAlarmModels.spec.ts +++ b/packages/react-components/src/hooks/useAlarms/hookHelpers/useAlarmModels.spec.ts @@ -9,7 +9,7 @@ import { mockAlarmModel, mockAlarmModel2, } from '../../../testing/alarms'; -import { AlarmData } from '../types'; +import type { AlarmDataInternal } from '../types'; import { useAlarmModels } from './useAlarmModels'; describe('useAlarmModels', () => { @@ -21,10 +21,10 @@ describe('useAlarmModels', () => { it('should inject alarm model into AlarmData for one alarm', async () => { describeAlarmModelMock.mockResolvedValue(mockAlarmModel); - const expectedAlarmData: AlarmData = { + const expectedAlarmData = { ...mockAlarmDataGetAssetPropertyValue, models: [mockAlarmModel], - }; + } satisfies AlarmDataInternal; const { result: alarmDataResults } = renderHook(() => useAlarmModels({ @@ -42,10 +42,10 @@ describe('useAlarmModels', () => { }); it('should not change AlarmData for external alarm without a source property', async () => { - const externalAlarmData: AlarmData = { + const externalAlarmData = { ...mockAlarmDataGetAssetPropertyValue, source: undefined, - }; + } satisfies AlarmDataInternal; const { result: alarmDataResults } = renderHook(() => useAlarmModels({ @@ -84,15 +84,15 @@ describe('useAlarmModels', () => { describeAlarmModelMock.mockResolvedValueOnce(mockAlarmModel); describeAlarmModelMock.mockResolvedValueOnce(mockAlarmModel2); - const expectedAlarmData1: AlarmData = { + const expectedAlarmData1 = { ...mockAlarmDataGetAssetPropertyValue, models: [mockAlarmModel], - }; + } satisfies AlarmDataInternal; - const expectedAlarmData2: AlarmData = { + const expectedAlarmData2 = { ...mockAlarmDataGetAssetPropertyValue2, models: [mockAlarmModel2], - }; + } satisfies AlarmDataInternal; const { result: alarmDataResults } = renderHook(() => useAlarmModels({ @@ -112,4 +112,34 @@ describe('useAlarmModels', () => { expect(describeAlarmModelMock).toBeCalledTimes(2); }); + + it('should overwrite the status of AlarmData when queries fail', async () => { + describeAlarmModelMock.mockRejectedValue( + 'Failure calling DescribeAlarmModel' + ); + const expectedAlarmData = { + ...mockAlarmDataGetAssetPropertyValue, + status: { + isLoading: false, + isRefetching: false, + isSuccess: false, + isError: true, + }, + } satisfies AlarmDataInternal; + + const { result: alarmDataResults } = renderHook(() => + useAlarmModels({ + iotEventsClient: iotEventsClientMock, + alarmDataList: [mockAlarmDataGetAssetPropertyValue], + retry: false, + }) + ); + + await waitFor(() => { + expect(alarmDataResults.current.length).toBe(1); + expect(alarmDataResults.current[0]).toMatchObject(expectedAlarmData); + }); + + expect(describeAlarmModelMock).toBeCalledTimes(1); + }); }); diff --git a/packages/react-components/src/hooks/useAlarms/hookHelpers/useAlarmModels.ts b/packages/react-components/src/hooks/useAlarms/hookHelpers/useAlarmModels.ts index 97f11940a..3c5927fe9 100644 --- a/packages/react-components/src/hooks/useAlarms/hookHelpers/useAlarmModels.ts +++ b/packages/react-components/src/hooks/useAlarms/hookHelpers/useAlarmModels.ts @@ -1,14 +1,15 @@ import { useMemo } from 'react'; import { IoTEventsClient } from '@aws-sdk/client-iot-events'; -import { getAlarmModelNameFromAlarmSourceProperty } from '../utils/alarmModelUtils'; -import { AlarmData, AlarmProperty } from '../types'; +import { getAlarmModelNameFromAlarmSourceProperty } from '../utils/parseAlarmModels'; +import type { AlarmDataInternal, AlarmProperty } from '../types'; import { useDescribeAlarmModels } from '../../../queries'; -import { getStatusForQuery } from '../utils/queryUtils'; +import { getStatusForQuery } from '../utils/queryStatus'; +import type { QueryOptionsGlobal } from '../../../queries/common/types'; -export interface UseAlarmModelsOptions { +export type UseAlarmModelsOptions = { iotEventsClient?: IoTEventsClient; - alarmDataList?: AlarmData[]; -} + alarmDataList?: AlarmDataInternal[]; +} & QueryOptionsGlobal; /** * useAlarmModels is a hook used to describe the IoT Events alarm model @@ -21,7 +22,8 @@ export interface UseAlarmModelsOptions { export function useAlarmModels({ iotEventsClient, alarmDataList = [], -}: UseAlarmModelsOptions): AlarmData[] { + retry, +}: UseAlarmModelsOptions): AlarmDataInternal[] { // Filter AlarmData with a source property and data const alarmModelRequests = alarmDataList .filter( @@ -39,6 +41,7 @@ export function useAlarmModels({ const alarmModelQueries = useDescribeAlarmModels({ iotEventsClient, requests: alarmModelRequests, + retry, }); return useMemo(() => { diff --git a/packages/react-components/src/hooks/useAlarms/hookHelpers/useAlarmState/updateStatus.ts b/packages/react-components/src/hooks/useAlarms/hookHelpers/useAlarmState/updateStatus.ts index fcd3d2b9a..7482557d5 100644 --- a/packages/react-components/src/hooks/useAlarms/hookHelpers/useAlarmState/updateStatus.ts +++ b/packages/react-components/src/hooks/useAlarms/hookHelpers/useAlarmState/updateStatus.ts @@ -4,7 +4,7 @@ import { AlarmData } from '../../types'; import { combineStatusForQueries, isQueryDisabled, -} from '../../utils/queryUtils'; +} from '../../utils/queryStatus'; export const updateAlarmStatusForAlarmStateQueries = ( alarm: AlarmData, diff --git a/packages/react-components/src/hooks/useAlarms/hookHelpers/useLatestAlarmPropertyValue.spec.ts b/packages/react-components/src/hooks/useAlarms/hookHelpers/useLatestAlarmPropertyValues.spec.ts similarity index 93% rename from packages/react-components/src/hooks/useAlarms/hookHelpers/useLatestAlarmPropertyValue.spec.ts rename to packages/react-components/src/hooks/useAlarms/hookHelpers/useLatestAlarmPropertyValues.spec.ts index e963b9c85..287779da6 100644 --- a/packages/react-components/src/hooks/useAlarms/hookHelpers/useLatestAlarmPropertyValue.spec.ts +++ b/packages/react-components/src/hooks/useAlarms/hookHelpers/useLatestAlarmPropertyValues.spec.ts @@ -16,8 +16,8 @@ import { mockStateAssetPropertyValue2, mockTypeAssetPropertyValue, } from '../../../testing/alarms'; -import { AlarmData } from '../types'; -import { useLatestAlarmPropertyValue } from './useLatestAlarmPropertyValue'; +import type { AlarmDataInternal } from '../types'; +import { useLatestAlarmPropertyValues } from './useLatestAlarmPropertyValues'; const mockBatchGetAssetPropertyValue = ({ errorEntries = [], @@ -31,7 +31,7 @@ const mockBatchGetAssetPropertyValue = ({ skippedEntries: [], }); -describe('useLatestAlarmPropertyValue', () => { +describe('useLatestAlarmPropertyValues', () => { beforeEach(() => { jest.resetAllMocks(); queryClient.clear(); @@ -60,7 +60,7 @@ describe('useLatestAlarmPropertyValue', () => { }; const { result: alarmDataResults } = renderHook(() => - useLatestAlarmPropertyValue({ + useLatestAlarmPropertyValues({ iotSiteWiseClient: iotSiteWiseClientMock, alarmDataList: [mockAlarmDataDescribeAsset], alarmPropertyFieldName: 'state', @@ -98,7 +98,7 @@ describe('useLatestAlarmPropertyValue', () => { }; const { result: alarmDataResults } = renderHook(() => - useLatestAlarmPropertyValue({ + useLatestAlarmPropertyValues({ iotSiteWiseClient: iotSiteWiseClientMock, alarmDataList: [mockAlarmDataDescribeAsset], alarmPropertyFieldName: 'type', @@ -136,7 +136,7 @@ describe('useLatestAlarmPropertyValue', () => { }; const { result: alarmDataResults } = renderHook(() => - useLatestAlarmPropertyValue({ + useLatestAlarmPropertyValues({ iotSiteWiseClient: iotSiteWiseClientMock, alarmDataList: [mockAlarmDataDescribeAsset], alarmPropertyFieldName: 'source', @@ -152,13 +152,13 @@ describe('useLatestAlarmPropertyValue', () => { }); it('should return same AlarmData for external alarm without a source property', async () => { - const externalAlarmData: AlarmData = { + const externalAlarmData = { ...mockAlarmDataDescribeAsset, source: undefined, - }; + } satisfies AlarmDataInternal; const { result: alarmDataResults } = renderHook(() => - useLatestAlarmPropertyValue({ + useLatestAlarmPropertyValues({ iotSiteWiseClient: iotSiteWiseClientMock, alarmDataList: [externalAlarmData], alarmPropertyFieldName: 'source', @@ -175,7 +175,7 @@ describe('useLatestAlarmPropertyValue', () => { it('should return same AlarmData when alarm field is not an AlarmProperty', async () => { const { result: alarmDataResults } = renderHook(() => - useLatestAlarmPropertyValue({ + useLatestAlarmPropertyValues({ iotSiteWiseClient: iotSiteWiseClientMock, alarmDataList: [mockAlarmDataDescribeAsset], alarmPropertyFieldName: 'assetId', @@ -227,7 +227,7 @@ describe('useLatestAlarmPropertyValue', () => { }; const { result: alarmDataResults } = renderHook(() => - useLatestAlarmPropertyValue({ + useLatestAlarmPropertyValues({ iotSiteWiseClient: iotSiteWiseClientMock, alarmDataList: [ mockAlarmDataDescribeAsset, diff --git a/packages/react-components/src/hooks/useAlarms/hookHelpers/useLatestAlarmPropertyValue.ts b/packages/react-components/src/hooks/useAlarms/hookHelpers/useLatestAlarmPropertyValues.ts similarity index 81% rename from packages/react-components/src/hooks/useAlarms/hookHelpers/useLatestAlarmPropertyValue.ts rename to packages/react-components/src/hooks/useAlarms/hookHelpers/useLatestAlarmPropertyValues.ts index 6f68f11be..bb4f9bf48 100644 --- a/packages/react-components/src/hooks/useAlarms/hookHelpers/useLatestAlarmPropertyValue.ts +++ b/packages/react-components/src/hooks/useAlarms/hookHelpers/useLatestAlarmPropertyValues.ts @@ -1,33 +1,36 @@ import { useMemo } from 'react'; import { IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; -import { AlarmData, AlarmProperty } from '../types'; +import type { AlarmDataInternal, AlarmProperty } from '../types'; import { useLatestAssetPropertyValues } from '../../../queries'; -import { getStatusForQuery } from '../utils/queryUtils'; +import { getStatusForQuery } from '../utils/queryStatus'; import { isAlarmProperty } from './predicates'; -import { constructAlarmAssetModelProperty } from '../utils/compositeModelUtils'; +import { constructAlarmProperty } from '../utils/constructAlarmProperty'; -export interface UseAlarmCompositePropertyOptions { - alarmPropertyFieldName: keyof AlarmData; +export interface UseLatestAlarmPropertyValuesOptions { + alarmPropertyFieldName: keyof AlarmDataInternal; iotSiteWiseClient?: IoTSiteWiseClient; - alarmDataList?: AlarmData[]; + alarmDataList?: AlarmDataInternal[]; + enabled?: boolean; } /** - * useLatestAlarmPropertyValue is a hook used to fetch the latest + * useLatestAlarmPropertyValues is a hook used to fetch the latest * asset property value for a SiteWise alarm composite property * * @param alarmPropertyFieldName is the name of an alarm property field * 'state' | 'type' | 'source' * @param iotSiteWiseClient is an AWS SDK IoT SiteWise client * @param alarmDataList is a list of AlarmData + * @param enabled will manually disable the hook * @returns a list of AlarmData with the latest property value injected * into the associated property field */ -export function useLatestAlarmPropertyValue({ +export function useLatestAlarmPropertyValues({ alarmPropertyFieldName, iotSiteWiseClient, alarmDataList = [], -}: UseAlarmCompositePropertyOptions): AlarmData[] { + enabled, +}: UseLatestAlarmPropertyValuesOptions): AlarmDataInternal[] { // Filter AlarmData with an existing property for alarmPropertyFieldName const alarmPropertyRequests = alarmDataList .filter((alarmData) => @@ -43,6 +46,7 @@ export function useLatestAlarmPropertyValue({ const alarmPropertyQueries = useLatestAssetPropertyValues({ iotSiteWiseClient, requests: alarmPropertyRequests, + enabled, }); return useMemo(() => { @@ -71,7 +75,7 @@ export function useLatestAlarmPropertyValue({ alarmPropertyQueries[filteredIndex], alarmData.status ); - const newAlarmProperty = constructAlarmAssetModelProperty( + const newAlarmProperty = constructAlarmProperty( alarmProperty.property, alarmPropertyQueries[filteredIndex].data?.propertyValue ); diff --git a/packages/react-components/src/hooks/useAlarms/types.ts b/packages/react-components/src/hooks/useAlarms/types.ts index 1de19c6ec..3d6a546d0 100644 --- a/packages/react-components/src/hooks/useAlarms/types.ts +++ b/packages/react-components/src/hooks/useAlarms/types.ts @@ -9,7 +9,7 @@ import { IoTEventsClient, } from '@aws-sdk/client-iot-events'; -import { Viewport } from '@iot-app-kit/core'; +import type { Viewport } from '@iot-app-kit/core'; /** * Execution status of the alarms queries @@ -111,6 +111,22 @@ export type AlarmData = { status: AlarmDataStatus; }; +/** + * AlarmData with additional fields for internal processing + */ +export type AlarmDataInternal = { + /** + * The alarm request which spawned the alarm. + */ + request?: AlarmRequest; + + /** + * The list of asset or assetModel properties on the alarm's asset. + * Used to assign a property object to the inputProperty field. + */ + properties?: (AssetProperty | AssetModelProperty)[]; +} & AlarmData; + /** * Request data for a single alarm by its composite model id. * Results in a 1 to 1 - request to alarm model. diff --git a/packages/react-components/src/hooks/useAlarms/useAlarms.spec.ts b/packages/react-components/src/hooks/useAlarms/useAlarms.spec.ts index 0e4981e54..49faf5d8f 100644 --- a/packages/react-components/src/hooks/useAlarms/useAlarms.spec.ts +++ b/packages/react-components/src/hooks/useAlarms/useAlarms.spec.ts @@ -1,8 +1,9 @@ import { renderHook, waitFor } from '@testing-library/react'; import { useAlarms } from './useAlarms'; -import { AlarmData, AlarmProperty } from './types'; +import type { AlarmData, AlarmDataInternal, AlarmProperty } from './types'; import { MOCK_ALARM_INPUT_PROPERTY_ID, + MOCK_ALARM_INPUT_PROPERTY_ID_2, MOCK_ASSET_ID, MOCK_COMPOSITE_MODEL_ID, iotSiteWiseClientMock, @@ -10,24 +11,36 @@ import { mockAlarmDataDescribeAlarmModel2, mockAlarmDataDescribeAsset, mockAlarmDataGetAssetPropertyValue, - mockInputProperty, + mockAssetProperties, mockStateAssetPropertyValue, } from '../../testing/alarms'; import * as alarmAssetHook from './hookHelpers/useAlarmAssets'; -import * as alarmCompositeProp from './hookHelpers/useLatestAlarmPropertyValue'; +import * as alarmCompositeProp from './hookHelpers/useLatestAlarmPropertyValues'; import * as alarmModelHook from './hookHelpers/useAlarmModels'; +import { AssetProperty } from '@aws-sdk/client-iotsitewise'; jest.mock('./hookHelpers/useAlarmAssets'); -jest.mock('./hookHelpers/useLatestAlarmPropertyValue'); +jest.mock('./hookHelpers/useLatestAlarmPropertyValues'); jest.mock('./hookHelpers/useAlarmModels'); const useAlarmAssetsMock = jest.spyOn(alarmAssetHook, 'useAlarmAssets'); -const useLatestAlarmPropertyValueMock = jest.spyOn( +const useLatestAlarmPropertyValuesMock = jest.spyOn( alarmCompositeProp, - 'useLatestAlarmPropertyValue' + 'useLatestAlarmPropertyValues' ); const useAlarmModelsMock = jest.spyOn(alarmModelHook, 'useAlarmModels'); +const mockRequest = { + assetId: MOCK_ASSET_ID, + assetCompositeModelId: MOCK_COMPOSITE_MODEL_ID, +}; + +const mockAlarmDataInternal = { + ...mockAlarmDataDescribeAlarmModel, + request: mockRequest, + properties: mockAssetProperties, +} satisfies AlarmDataInternal; + /** * TODO: need to update tests so that they test * actual AlarmData. Skpping for now. @@ -39,40 +52,35 @@ describe.skip('useAlarms', () => { it('should not transform AlarmData when no transform function supplied', async () => { useAlarmAssetsMock.mockReturnValue([mockAlarmDataDescribeAsset]); - useLatestAlarmPropertyValueMock.mockReturnValue([ + useLatestAlarmPropertyValuesMock.mockReturnValue([ mockAlarmDataGetAssetPropertyValue, ]); - useAlarmModelsMock.mockReturnValue([mockAlarmDataDescribeAlarmModel]); + useAlarmModelsMock.mockReturnValue([mockAlarmDataInternal]); const { result: alarmResults } = renderHook(() => useAlarms({ iotSiteWiseClient: iotSiteWiseClientMock, - requests: [ - { - assetId: MOCK_ASSET_ID, - assetCompositeModelId: MOCK_COMPOSITE_MODEL_ID, - }, - ], + requests: [mockRequest], }) ); await waitFor(() => expect(alarmResults.current).toMatchObject([ - mockAlarmDataDescribeAlarmModel, + mockAlarmDataDescribeAlarmModel as AlarmData, ]) ); expect(useAlarmAssetsMock).toBeCalledTimes(1); - expect(useLatestAlarmPropertyValueMock).toBeCalledTimes(2); + expect(useLatestAlarmPropertyValuesMock).toBeCalledTimes(2); expect(useAlarmModelsMock).toBeCalledTimes(1); }); it('should transform AlarmData according to supplied transform function', async () => { useAlarmAssetsMock.mockReturnValue([mockAlarmDataDescribeAsset]); - useLatestAlarmPropertyValueMock.mockReturnValue([ + useLatestAlarmPropertyValuesMock.mockReturnValue([ mockAlarmDataGetAssetPropertyValue, ]); - useAlarmModelsMock.mockReturnValue([mockAlarmDataDescribeAlarmModel]); + useAlarmModelsMock.mockReturnValue([mockAlarmDataInternal]); const transform = (alarmData: AlarmData): AlarmProperty | undefined => alarmData.state; @@ -85,12 +93,7 @@ describe.skip('useAlarms', () => { const { result: alarmResults } = renderHook(() => useAlarms({ iotSiteWiseClient: iotSiteWiseClientMock, - requests: [ - { - assetId: MOCK_ASSET_ID, - assetCompositeModelId: MOCK_COMPOSITE_MODEL_ID, - }, - ], + requests: [mockRequest], transform, }) ); @@ -100,50 +103,126 @@ describe.skip('useAlarms', () => { ); expect(useAlarmAssetsMock).toBeCalledTimes(1); - expect(useLatestAlarmPropertyValueMock).toBeCalledTimes(2); + expect(useLatestAlarmPropertyValuesMock).toBeCalledTimes(2); expect(useAlarmModelsMock).toBeCalledTimes(1); }); - it('should return AlarmData with associated inputProperty', async () => { + it('should return AlarmData with associated inputProperty from request', async () => { + const mockInputPropertyRequest = { + assetId: MOCK_ASSET_ID, + inputPropertyId: MOCK_ALARM_INPUT_PROPERTY_ID, + }; + + const mockAssetProperty = { + id: MOCK_ALARM_INPUT_PROPERTY_ID, + name: 'alarmInputName', + dataType: 'STRING', + } satisfies AssetProperty; + + const mockAlarmDataInternalAlarmModel = { + ...mockAlarmDataDescribeAlarmModel, + request: mockInputPropertyRequest, + properties: [mockAssetProperty], + } satisfies AlarmDataInternal; + + const mockAlarmDataInternalAlarmModel2 = { + ...mockAlarmDataDescribeAlarmModel2, + request: mockInputPropertyRequest, + properties: [mockAssetProperty], + } satisfies AlarmDataInternal; + + useAlarmAssetsMock.mockReturnValue([mockAlarmDataDescribeAsset]); + useLatestAlarmPropertyValuesMock.mockReturnValue([ + mockAlarmDataGetAssetPropertyValue, + ]); // Case where multiple alarms from the same asset have different alarm models - const mockAlarmDataInputProperty: AlarmData = { + useAlarmModelsMock.mockReturnValue([ + mockAlarmDataInternalAlarmModel, + mockAlarmDataInternalAlarmModel2, + ]); + + const expectedAlarmData: AlarmData = { ...mockAlarmDataDescribeAlarmModel, - inputProperty: [mockInputProperty], + inputProperty: [mockAssetProperty], }; - const mockAlarmDataInputProperty2: AlarmData = { + const { result: alarmResults } = renderHook(() => + useAlarms({ + iotSiteWiseClient: iotSiteWiseClientMock, + requests: [mockInputPropertyRequest], + }) + ); + + await waitFor(() => + expect(alarmResults.current).toMatchObject([expectedAlarmData]) + ); + + expect(useAlarmAssetsMock).toBeCalledTimes(1); + expect(useLatestAlarmPropertyValuesMock).toBeCalledTimes(2); + expect(useAlarmModelsMock).toBeCalledTimes(1); + }); + + it('should return AlarmData with inputProperty from alarm model', async () => { + const mockAssetProperty1 = { + id: MOCK_ALARM_INPUT_PROPERTY_ID, + name: 'alarmInputName', + dataType: 'STRING', + } satisfies AssetProperty; + + const mockAssetProperty2 = { + id: MOCK_ALARM_INPUT_PROPERTY_ID_2, + name: 'alarmInputName2', + dataType: 'STRING', + } satisfies AssetProperty; + + const mockAlarmDataInternalAlarmModel = { ...mockAlarmDataDescribeAlarmModel, - models: mockAlarmDataDescribeAlarmModel2.models, - inputProperty: [mockInputProperty], + request: mockRequest, + properties: [mockAssetProperty1, mockAssetProperty2], + } satisfies AlarmDataInternal; + + const mockAlarmDataInternalAlarmModel2 = { + ...mockAlarmDataDescribeAlarmModel2, + request: mockRequest, + properties: [mockAssetProperty1, mockAssetProperty2], + } satisfies AlarmDataInternal; + + const expectedAlarmData1: AlarmData = { + ...mockAlarmDataDescribeAlarmModel, + inputProperty: [mockAssetProperty1], + }; + + const expectedAlarmData2: AlarmData = { + ...mockAlarmDataDescribeAlarmModel2, + inputProperty: [mockAssetProperty2], }; useAlarmAssetsMock.mockReturnValue([mockAlarmDataDescribeAsset]); - useLatestAlarmPropertyValueMock.mockReturnValue([ + useLatestAlarmPropertyValuesMock.mockReturnValue([ mockAlarmDataGetAssetPropertyValue, ]); + // Case where multiple alarms from the same asset have different alarm models useAlarmModelsMock.mockReturnValue([ - mockAlarmDataInputProperty, - mockAlarmDataInputProperty2, + mockAlarmDataInternalAlarmModel, + mockAlarmDataInternalAlarmModel2, ]); const { result: alarmResults } = renderHook(() => useAlarms({ iotSiteWiseClient: iotSiteWiseClientMock, - requests: [ - { - assetId: MOCK_ASSET_ID, - inputPropertyId: MOCK_ALARM_INPUT_PROPERTY_ID, - }, - ], + requests: [mockRequest], }) ); await waitFor(() => - expect(alarmResults.current).toMatchObject([mockAlarmDataInputProperty]) + expect(alarmResults.current).toMatchObject([ + expectedAlarmData1, + expectedAlarmData2, + ]) ); expect(useAlarmAssetsMock).toBeCalledTimes(1); - expect(useLatestAlarmPropertyValueMock).toBeCalledTimes(2); + expect(useLatestAlarmPropertyValuesMock).toBeCalledTimes(2); expect(useAlarmModelsMock).toBeCalledTimes(1); }); }); diff --git a/packages/react-components/src/hooks/useAlarms/useAlarms.ts b/packages/react-components/src/hooks/useAlarms/useAlarms.ts index 3d7bb1653..08dd49333 100644 --- a/packages/react-components/src/hooks/useAlarms/useAlarms.ts +++ b/packages/react-components/src/hooks/useAlarms/useAlarms.ts @@ -1,6 +1,6 @@ import { useMemo } from 'react'; -import { SetRequired } from 'type-fest'; -import { +import type { SetRequired } from 'type-fest'; +import type { AlarmData, UseAlarmsOptions, UseAlarmOptionsWithoutTransform, @@ -8,10 +8,10 @@ import { import { useAlarmAssets, useAlarmModels, - useLatestAlarmPropertyValue, + useLatestAlarmPropertyValues, } from './hookHelpers'; -import { filterAlarmsMatchingInputProperties } from './utils/alarmModelUtils'; import { useAlarmState } from './hookHelpers/useAlarmState/useAlarmState'; +import { filterAlarmInputProperties } from './utils/filterAlarmInputProperties'; /** * Identify function that returns the input AlarmData. @@ -75,7 +75,7 @@ function useAlarms(options?: UseAlarmsOptions): (T | AlarmData)[] { * Fetch latest asset property values for alarms with a type property. * Data should be available for all alarms fetched for an asset. */ - const typePropertyAlarmData = useLatestAlarmPropertyValue({ + const typePropertyAlarmData = useLatestAlarmPropertyValues({ iotSiteWiseClient, alarmDataList: statePropertyAlarmData, alarmPropertyFieldName: 'type', @@ -86,7 +86,7 @@ function useAlarms(options?: UseAlarmsOptions): (T | AlarmData)[] { * Data should be available for all alarms fetched for an asset, where * the alarm type is "IOT_EVENTS". */ - const sourcePropertyAlarmData = useLatestAlarmPropertyValue({ + const sourcePropertyAlarmData = useLatestAlarmPropertyValues({ iotSiteWiseClient, alarmDataList: typePropertyAlarmData, alarmPropertyFieldName: 'source', @@ -103,23 +103,33 @@ function useAlarms(options?: UseAlarmsOptions): (T | AlarmData)[] { }); /** - * If an inputProperty is in an AlarmRequest then all alarms for - * the property's asset is fetched. After the IoT Events alarm models - * are fetched for each alarm we can verify which alarm on the asset - * is actually associated with an inputProperty. + * For an input property request filter out alarms + * with alarm models that don't match the request inputPropertyId. + * + * For all other requests find the inputProperty from + * an alarm's alarm model. */ - const filterAlarmInputProperties = useMemo( - () => filterAlarmsMatchingInputProperties(alarmModelAlarmData), + const inputPropertiesAlarmData = useMemo( + () => filterAlarmInputProperties(alarmModelAlarmData), [alarmModelAlarmData] ); + // Remove internal properties on AlarmDataInternal + const alarmData: AlarmData[] = inputPropertiesAlarmData.map( + ({ + request: _unusedRequest, + properties: _unusedProperties, + ...alarmData + }) => ({ + ...alarmData, + }) + ); + // Apply the transform callback if it exists, otherwise return the AlarmData return useMemo( () => - transform - ? filterAlarmInputProperties?.map(transform) - : filterAlarmInputProperties?.map(alarmDataIdentity), - [transform, filterAlarmInputProperties] + transform ? alarmData?.map(transform) : alarmData?.map(alarmDataIdentity), + [transform, alarmData] ); } diff --git a/packages/react-components/src/hooks/useAlarms/utils/alarmDataUtils.ts b/packages/react-components/src/hooks/useAlarms/utils/alarmDataUtils.ts deleted file mode 100644 index 3813eb31e..000000000 --- a/packages/react-components/src/hooks/useAlarms/utils/alarmDataUtils.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { - AssetProperty, - DescribeAssetModelResponse, - DescribeAssetResponse, -} from '@aws-sdk/client-iotsitewise'; -import { AlarmData, AlarmDataStatus, AlarmRequest } from '../types'; -import { - buildFromCompositeModel, - isAlarmCompositeModel, -} from './compositeModelUtils'; - -/** - * This function builds an AlarmData object for each alarm composite model on a SiteWise asset. - * The content of AlarmData depends on the AlarmRequest argument. - * - * @param request is an AlarmRequest that may have an assetId - * @param status is the status of the DescribeAssetResponse query - * @param assetResponse is the DescribeAssetResponse - * @returns a list of AlarmData for all alarms on an asset - */ -export const buildFromAssetResponse = ({ - request, - status, - assetResponse, -}: { - request: AlarmRequest; - status: AlarmDataStatus; - assetResponse?: DescribeAssetResponse; -}): AlarmData[] => { - const { assetId, assetCompositeModelId, inputPropertyId, assetModelId } = - request; - - if (!assetResponse || !assetId) { - return [ - { - assetModelId, - assetId, - compositeModelId: assetCompositeModelId, - status, - }, - ]; - } - - if (assetResponse && assetId && assetResponse?.assetId !== assetId) { - throw new Error( - 'AlarmDataFactory.buildFromAssetResponse expects there to be an assetId in the request that matches the DescribeAssetResponse' - ); - } - - // Find all alarms - const alarmCompositeModels = assetResponse?.assetCompositeModels?.filter( - isAlarmCompositeModel - ); - - /** - * Find inputProperty from asset summary if requested. - * - * Used as an indicator in useAlarms to filter out alarms - * when the inputProperty from IoT Events alarmModels don't match. - */ - let inputPropertyList: AssetProperty[] | undefined; - if (inputPropertyId) { - const inputProperty = assetResponse?.assetProperties?.find( - (property) => property.id === inputPropertyId - ); - if (inputProperty) { - inputPropertyList = [inputProperty]; - } - } - - // If request is for one alarm, only build AlarmData for the given composite model id - if (assetCompositeModelId) { - const foundCompositeModel = alarmCompositeModels?.find( - (compositeModel) => compositeModel.id === assetCompositeModelId - ); - if (foundCompositeModel) { - const alarmData = buildFromCompositeModel(foundCompositeModel, status); - return [ - { - ...alarmData, - assetModelId: assetResponse?.assetModelId, - assetId: assetResponse?.assetId, - }, - ]; - } - } else { - // Build AlarmData for all alarm composite models on the asset - return ( - alarmCompositeModels?.map((compositeModel) => { - const alarmData = buildFromCompositeModel(compositeModel, status); - return { - ...alarmData, - assetModelId: assetResponse?.assetModelId, - assetId: assetResponse?.assetId, - inputProperty: inputPropertyList, - }; - }) ?? [] - ); - } - - // Return empty list of there are no alarms that meet the AlarmRequest criteria - return []; -}; - -/** - * - * @param request is an AlarmRequest that may have an assetModelId - * @param status is the status of the DescribeAssetModelResponse query - * @param assetModelResponse is the DescribeAssetModelResponse - * @returns a list of AlarmData for all alarms on an assetModel - */ -export const buildFromAssetModelResponse = ({ - request, - status, - assetModelResponse, -}: { - request: AlarmRequest; - status: AlarmDataStatus; - assetModelResponse?: DescribeAssetModelResponse; -}): AlarmData[] => { - const { assetModelId } = request; - - if (!assetModelResponse || !assetModelId) { - return [ - { - assetModelId, - status, - }, - ]; - } - - if (assetModelId && assetModelResponse?.assetModelId !== assetModelId) { - throw new Error( - 'AlarmDataFactory.buildFromAssetModelResponse expects there to be an assetModelId in the request that matches the DescribeAssetModelResponse' - ); - } - - // Find all alarms - const alarmModelCompositeModels = - assetModelResponse?.assetModelCompositeModels?.filter( - isAlarmCompositeModel - ); - - // Build AlarmData for all alarm composite models on the assetModel - return ( - alarmModelCompositeModels?.map((compositeModel) => { - const alarmData = buildFromCompositeModel(compositeModel, status); - return { - ...alarmData, - assetModelId: assetModelResponse?.assetModelId, - }; - }) ?? [] - ); -}; diff --git a/packages/react-components/src/hooks/useAlarms/utils/alarmModelUtils.ts b/packages/react-components/src/hooks/useAlarms/utils/alarmModelUtils.ts deleted file mode 100644 index 78e2d9816..000000000 --- a/packages/react-components/src/hooks/useAlarms/utils/alarmModelUtils.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { DescribeAlarmModelResponse } from '@aws-sdk/client-iot-events'; -import { SITE_WISE_BACKED_PROPERTY_PREFIX } from '../constants'; -import { AlarmData, AlarmProperty } from '../types'; - -/** - * Expecting the alarm model name to be at the end of the ARN after the last '/' - * - * @param alarmModelArn is the alarm model ARN of the format: - * arn::iotevents:::alarmModel/ - * @returns the alarm model name or undefined if the ARN is malformed - */ -const nameFromAlarmModelArn = (alarmModelArn: string): string | undefined => { - const arnSplit = alarmModelArn.split('/'); - if (arnSplit.length === 2 && arnSplit[1] !== '') { - return arnSplit[1]; - } -}; - -/** - * Function extracts an IoT Events alarm model name from an IoT SiteWise alarm source property. - * An alarm source property stores an associated IoT Events alarm model ARN as a property value. - * This is only supported for "IOT_EVENTS" alarm types. - * - * @param alarmSourceProperty is an AlarmProperty as defined on an AlarmData object - * @returns the IoT Events alarm model name if available - */ -export const getAlarmModelNameFromAlarmSourceProperty = ( - alarmSourceProperty?: AlarmProperty -): string | undefined => { - let alarmModelArn: string | undefined; - if (alarmSourceProperty?.data && alarmSourceProperty?.data.length > 0) { - const dataLength = alarmSourceProperty?.data.length; - // Get latest property value - alarmModelArn = alarmSourceProperty.data[dataLength - 1].value?.stringValue; - } - return alarmModelArn && nameFromAlarmModelArn(alarmModelArn); -}; - -const isBackedBySiteWiseAssetProperty = (inputProperty: string): boolean => - inputProperty.startsWith(SITE_WISE_BACKED_PROPERTY_PREFIX); - -const removeBackticks = (value: string) => { - return value.replace(/^`|`$/g, ''); -}; - -/** - * Expecting the propertyId to be the 3rd string after $sitewise prefix. - * - * @param inputProperty - an expression with the propertyId e.g. - * $sitewise.assetModel.`f6dca270-d4b9-4de0-9722-d57d3f260cb8`.`00b36ed5-7640-4635-b9d3-60c2db229568`.propertyValue.value - * here the propertyId is 00b36ed5-7640-4635-b9d3-60c2db229568 - * @returns the propertyId or undefined if we are not provided with a model propertyId that is backed by SiteWise - */ -const extractAssetPropertyId = ( - inputProperty: string | undefined -): string | undefined => { - if (inputProperty && isBackedBySiteWiseAssetProperty(inputProperty)) { - const splitInputProperty = inputProperty.split('.'); - return removeBackticks(splitInputProperty[3]); - } -}; - -/** - * Function filters out AlarmData where the inputProperty field does not match - * any of its IoT Events alarm models. - * - * useAlarms fills in the inputProperty field if the original AlarmRequest has - * an inputPropertyId specified. Need to fetch all alarms from an asset and - * check them against all IoT Events alarm models to find which alarms have - * the inputPropertyId. - * - * @param alarmDataList is the list of AlarmData with fetched assets and alarm models - * @returns a filtered list of AlarmData where inputPropertyId matches the inputProperty - * from the alarm model - */ -export const filterAlarmsMatchingInputProperties = ( - alarmDataList: AlarmData[] -) => { - return alarmDataList.filter((alarmData) => { - if (alarmData.inputProperty) { - let matchingAlarmModel: DescribeAlarmModelResponse | undefined; - for (const property of alarmData.inputProperty) { - matchingAlarmModel = alarmData.models?.find((alarmModel) => { - const inputPropertyId = extractAssetPropertyId( - alarmModel.alarmRule?.simpleRule?.inputProperty - ); - return inputPropertyId === property.id; - }); - } - return matchingAlarmModel ? true : false; - } else { - return true; - } - }); -}; diff --git a/packages/react-components/src/hooks/useAlarms/utils/compositeModelUtils.ts b/packages/react-components/src/hooks/useAlarms/utils/compositeModelUtils.ts deleted file mode 100644 index fde8113d2..000000000 --- a/packages/react-components/src/hooks/useAlarms/utils/compositeModelUtils.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { - AssetCompositeModel, - AssetModelCompositeModel, - AssetModelProperty, - AssetProperty, - AssetPropertyValue, -} from '@aws-sdk/client-iotsitewise'; -import { - ALARM_COMPOSITE_MODEL_TYPE, - ALARM_SOURCE_PROPERTY_NAME, - ALARM_STATE_PROPERTY_NAME, - ALARM_TYPE_PROPERTY_NAME, -} from '../constants'; -import { AlarmData, AlarmDataStatus, AlarmProperty } from '../types'; - -/** - * This function finds the alarm state, type, and source properties on a composite model if they exist. - * A compositeModel from an asset or assetModel share the same shape for searching properties so this - * function is generic for both. - * - * @param compositeModel is the asset or assetModel compositeModel - * @returns the state, type, and source asset or assetModel properties - */ -export const getAlarmPropertiesFromCompositeModel = ( - compositeModel?: AssetCompositeModel | AssetModelCompositeModel -): - | { - state?: AssetProperty | AssetModelProperty; - type?: AssetProperty | AssetModelProperty; - source?: AssetProperty | AssetModelProperty; - } - | undefined => { - if (isAlarmCompositeModel(compositeModel)) { - let stateProperty: AssetProperty | AssetModelProperty | undefined; - let typeProperty: AssetProperty | AssetModelProperty | undefined; - let sourceProperty: AssetProperty | AssetModelProperty | undefined; - - compositeModel?.properties?.forEach((property) => { - if (property.name === ALARM_STATE_PROPERTY_NAME) { - stateProperty = property; - } else if (property.name === ALARM_TYPE_PROPERTY_NAME) { - typeProperty = property; - } else if (property.name === ALARM_SOURCE_PROPERTY_NAME) { - sourceProperty = property; - } - }); - - return { - state: stateProperty, - type: typeProperty, - source: sourceProperty, - }; - } -}; - -/** - * Checks whether a composite model is an AWS/ALARM type - */ -export const isAlarmCompositeModel = ( - compositeModel?: AssetCompositeModel | AssetModelCompositeModel -): boolean => { - return compositeModel?.type === ALARM_COMPOSITE_MODEL_TYPE; -}; - -/** - * Builds AlarmData for a provided compositeModel - * - * @param compositeModel is the asset or assetModel compositeModel - * @param status is the query status for fetching the compositeModel - * @returns an AlarmData object with content from the compositeModel - */ -export const buildFromCompositeModel = ( - compositeModel: AssetCompositeModel | AssetModelCompositeModel, - status: AlarmDataStatus -) => { - const alarmProperties = getAlarmPropertiesFromCompositeModel(compositeModel); - const alarmData: AlarmData = { - compositeModelId: compositeModel.id, - compositeModelName: compositeModel.name, - state: constructAlarmAssetModelProperty(alarmProperties?.state), - type: constructAlarmAssetModelProperty(alarmProperties?.type), - source: constructAlarmAssetModelProperty(alarmProperties?.source), - status, - }; - return alarmData; -}; - -/** - * Builds an AlarmProperty with property and data values - * - * @param property is an asset or assetModel property - * @param assetPropertyValue is an asset property value - * @returns an AlarmProperty with property and data values - */ -export const constructAlarmAssetModelProperty = ( - property?: AssetModelProperty | AssetProperty, - assetPropertyValue?: AssetPropertyValue -): AlarmProperty | undefined => { - if (!property) return undefined; - - let data: AssetPropertyValue[] | undefined; - if ((property as AssetModelProperty)?.type?.attribute?.defaultValue) { - data = [ - { - value: { - /** - * The only available data without an asset reference - * for an AssetModelProperty is an attribute default value - */ - stringValue: (property as AssetModelProperty)?.type?.attribute - ?.defaultValue, - }, - timestamp: undefined, - }, - ]; - } else if (assetPropertyValue) { - data = [assetPropertyValue]; - } - - return { - property, - data, - }; -}; diff --git a/packages/react-components/src/hooks/useAlarms/utils/constructAlarmProperty.ts b/packages/react-components/src/hooks/useAlarms/utils/constructAlarmProperty.ts new file mode 100644 index 000000000..93173a97f --- /dev/null +++ b/packages/react-components/src/hooks/useAlarms/utils/constructAlarmProperty.ts @@ -0,0 +1,44 @@ +import { + AssetModelProperty, + AssetProperty, + AssetPropertyValue, +} from '@aws-sdk/client-iotsitewise'; +import type { AlarmProperty } from '../types'; + +/** + * Creates an AlarmProperty with property and data values + * + * @param property is an asset or assetModel property + * @param assetPropertyValue is an asset property value + * @returns an AlarmProperty with property and data values + */ +export const constructAlarmProperty = ( + property?: AssetModelProperty | AssetProperty, + assetPropertyValue?: AssetPropertyValue +): AlarmProperty | undefined => { + if (!property) return undefined; + + let data: AssetPropertyValue[] | undefined; + if ((property as AssetModelProperty)?.type?.attribute?.defaultValue) { + data = [ + { + value: { + /** + * The only available data without an asset reference + * for an AssetModelProperty is an attribute default value + */ + stringValue: (property as AssetModelProperty)?.type?.attribute + ?.defaultValue, + }, + timestamp: undefined, + }, + ]; + } else if (assetPropertyValue) { + data = [assetPropertyValue]; + } + + return { + property, + data, + }; +}; diff --git a/packages/react-components/src/hooks/useAlarms/utils/createAlarmData.ts b/packages/react-components/src/hooks/useAlarms/utils/createAlarmData.ts new file mode 100644 index 000000000..92d21d3d8 --- /dev/null +++ b/packages/react-components/src/hooks/useAlarms/utils/createAlarmData.ts @@ -0,0 +1,180 @@ +import { + AssetCompositeModel, + AssetModelCompositeModel, + DescribeAssetModelResponse, + DescribeAssetResponse, +} from '@aws-sdk/client-iotsitewise'; +import type { + AlarmData, + AlarmDataInternal, + AlarmDataStatus, + AlarmRequest, +} from '../types'; +import { + getAlarmPropertiesFromCompositeModel, + isAlarmCompositeModel, +} from './parseCompositeModels'; +import { constructAlarmProperty } from './constructAlarmProperty'; + +/** + * Creates an AlarmData object for each alarm composite model on a SiteWise asset. + * The content of AlarmData depends on the AlarmRequest argument. + * + * @param request is an AlarmRequest that may have an assetId + * @param status is the status of the DescribeAssetResponse query + * @param assetResponse is the DescribeAssetResponse + * @returns a list of AlarmData for all alarms on an asset + */ +export const createFromAssetResponse = ({ + request, + status, + assetResponse, +}: { + request: AlarmRequest; + status: AlarmDataStatus; + assetResponse?: DescribeAssetResponse; +}): AlarmDataInternal[] => { + const { assetId, assetCompositeModelId } = request; + + const defaultAlarmDataResponse = [ + { + request, + assetId, + compositeModelId: assetCompositeModelId, + status, + }, + ] satisfies AlarmDataInternal[]; + + if (!assetResponse || !assetId) { + return defaultAlarmDataResponse; + } + + if (assetResponse && assetId && assetResponse?.assetId !== assetId) { + console.error( + 'createFromAssetResponse expects there to be an assetId in the request that matches the DescribeAssetResponse' + ); + return defaultAlarmDataResponse; + } + + // Find all alarms + const alarmCompositeModels = assetResponse?.assetCompositeModels?.filter( + isAlarmCompositeModel + ); + + // If request is for one alarm, only build AlarmData for the given composite model id + if (assetCompositeModelId) { + const foundCompositeModel = alarmCompositeModels?.find( + (compositeModel) => compositeModel.id === assetCompositeModelId + ); + if (foundCompositeModel) { + const alarmData = createFromCompositeModel(foundCompositeModel, status); + return [ + { + ...alarmData, + assetModelId: assetResponse?.assetModelId, + assetId: assetResponse?.assetId, + request, + properties: assetResponse.assetProperties, + }, + ] satisfies AlarmDataInternal[]; + } + } else { + // Build AlarmData for all alarm composite models on the asset + return ( + alarmCompositeModels?.map((compositeModel) => { + const alarmData = createFromCompositeModel(compositeModel, status); + return { + ...alarmData, + assetModelId: assetResponse?.assetModelId, + assetId: assetResponse?.assetId, + request, + properties: assetResponse.assetProperties, + } satisfies AlarmDataInternal; + }) ?? [] + ); + } + + // Return empty list of there are no alarms that meet the AlarmRequest criteria + return []; +}; + +/** + * Creates an AlarmData object for each alarm assetModel composite model on a SiteWise assetModel. + * + * @param request is an AlarmRequest that may have an assetModelId + * @param status is the status of the DescribeAssetModelResponse query + * @param assetModelResponse is the DescribeAssetModelResponse + * @returns a list of AlarmData for all alarms on an assetModel + */ +export const createFromAssetModelResponse = ({ + request, + status, + assetModelResponse, +}: { + request: AlarmRequest; + status: AlarmDataStatus; + assetModelResponse?: DescribeAssetModelResponse; +}): AlarmDataInternal[] => { + const { assetModelId } = request; + + const defaultAlarmDataResponse = [ + { + request, + assetModelId, + status, + }, + ] satisfies AlarmDataInternal[]; + + if (!assetModelResponse || !assetModelId) { + return defaultAlarmDataResponse; + } + + if (assetModelId && assetModelResponse?.assetModelId !== assetModelId) { + console.error( + 'createFromAssetModelResponse expects there to be an assetModelId in the request that matches the DescribeAssetModelResponse' + ); + return defaultAlarmDataResponse; + } + + // Find all alarms + const alarmModelCompositeModels = + assetModelResponse?.assetModelCompositeModels?.filter( + isAlarmCompositeModel + ); + + // Build AlarmData for all alarm composite models on the assetModel + return ( + alarmModelCompositeModels?.map((compositeModel) => { + const alarmData = createFromCompositeModel(compositeModel, status); + return { + ...alarmData, + assetModelId: assetModelResponse?.assetModelId, + request, + properties: assetModelResponse?.assetModelProperties, + }; + }) ?? [] + ); +}; + +/** + * Creates AlarmData for a provided compositeModel + * + * @param compositeModel is the asset or assetModel compositeModel + * @param status is the query status for fetching the compositeModel + * @returns an AlarmData object with content from the compositeModel + */ +export const createFromCompositeModel = ( + compositeModel: AssetCompositeModel | AssetModelCompositeModel, + status: AlarmDataStatus +) => { + const alarmProperties = getAlarmPropertiesFromCompositeModel(compositeModel); + const alarmData: AlarmData = { + compositeModelId: compositeModel.id, + compositeModelName: compositeModel.name, + state: constructAlarmProperty(alarmProperties?.state), + type: constructAlarmProperty(alarmProperties?.type), + source: constructAlarmProperty(alarmProperties?.source), + status, + }; + return alarmData; +}; diff --git a/packages/react-components/src/hooks/useAlarms/utils/filterAlarmInputProperties.ts b/packages/react-components/src/hooks/useAlarms/utils/filterAlarmInputProperties.ts new file mode 100644 index 000000000..2666ca862 --- /dev/null +++ b/packages/react-components/src/hooks/useAlarms/utils/filterAlarmInputProperties.ts @@ -0,0 +1,54 @@ +import type { AlarmDataInternal } from '../types'; +import { extractAssetPropertyId } from './parseAlarmModels'; +import { AssetModelProperty, AssetProperty } from '@aws-sdk/client-iotsitewise'; + +/** + * Finds the input properties for an alarm based on the original alarm request. + * + * Case 1: request has an inputPropertyId + * Filter out any alarms that don't have an alarm model input property + * that matches the inputPropertyId. + * + * Case 2: request does not have an inputPropertyId + * Find the input property on the alarm model and assign the inputProperty field + * with the associated property object. + * + * AlarmDataInternal stores the AlarmRequest and asset/assetModel properties + * which enables resolving the inputProperty. + * + * @param alarmDataList is the list of AlarmData with fetched assets and alarm models + * @returns a filtered list of AlarmData where inputPropertyId matches the inputProperty + * from the alarm model + */ +export const filterAlarmInputProperties = ( + alarmDataList: AlarmDataInternal[] +) => { + const filterForInputPropertyRequests = alarmDataList.filter((alarmData) => { + if (alarmData.request?.inputPropertyId) { + const matchingAlarmModel = alarmData.models?.find((alarmModel) => { + const inputPropertyId = extractAssetPropertyId( + alarmModel.alarmRule?.simpleRule?.inputProperty + ); + return inputPropertyId === alarmData.request?.inputPropertyId; + }); + return Boolean(matchingAlarmModel); + } + return true; + }); + + return filterForInputPropertyRequests.map((alarmData) => { + let inputProperty: AssetProperty | AssetModelProperty | undefined; + if (alarmData.models) { + const inputPropertyId = extractAssetPropertyId( + alarmData.models[0].alarmRule?.simpleRule?.inputProperty + ); + inputProperty = alarmData.properties?.find( + (property) => property.id === inputPropertyId + ); + } + return { + ...alarmData, + inputProperty: inputProperty ? [inputProperty] : undefined, + }; + }); +}; diff --git a/packages/react-components/src/hooks/useAlarms/utils/parseAlarmModels.ts b/packages/react-components/src/hooks/useAlarms/utils/parseAlarmModels.ts new file mode 100644 index 000000000..89169075d --- /dev/null +++ b/packages/react-components/src/hooks/useAlarms/utils/parseAlarmModels.ts @@ -0,0 +1,65 @@ +import { SITE_WISE_BACKED_PROPERTY_PREFIX } from '../constants'; +import type { AlarmProperty } from '../types'; + +/** + * Extracts the alarm model name from its ARN. + * Expecting the alarm model name to be at the end of the ARN after the last '/'. + * + * @param alarmModelArn is the alarm model ARN of the format: + * arn::iotevents:::alarmModel/ + * @returns the alarm model name or undefined if the ARN is malformed + */ +const nameFromAlarmModelArn = (alarmModelArn: string): string | undefined => { + const arnSplit = alarmModelArn.split('/'); + if (arnSplit.length === 2 && arnSplit[1] !== '') { + return arnSplit[1]; + } +}; + +/** + * Extracts an IoT Events alarm model name from an IoT SiteWise alarm source property. + * An alarm source property stores an associated IoT Events alarm model ARN as a property value. + * This is only supported for "IOT_EVENTS" alarm types. + * + * @param alarmSourceProperty is an AlarmProperty as defined on an AlarmData object + * @returns the IoT Events alarm model name if available + */ +export const getAlarmModelNameFromAlarmSourceProperty = ( + alarmSourceProperty?: AlarmProperty +): string | undefined => { + let alarmModelArn: string | undefined; + if (alarmSourceProperty?.data && alarmSourceProperty?.data.length > 0) { + const dataLength = alarmSourceProperty?.data.length; + // Get latest property value + alarmModelArn = alarmSourceProperty.data[dataLength - 1].value?.stringValue; + } + return alarmModelArn && nameFromAlarmModelArn(alarmModelArn); +}; + +const isBackedBySiteWiseAssetProperty = (propertyExpression: string): boolean => + propertyExpression.startsWith(SITE_WISE_BACKED_PROPERTY_PREFIX); + +const removeBackticks = (value: string) => { + return value.replace(/^`|`$/g, ''); +}; + +/** + * Extracts the propertyId from an alarm model expression. + * Expect to find the property as the 3rd string after $sitewise prefix. + * + * @param propertyExpression - an expression with the propertyId e.g. + * $sitewise.assetModel.`f6dca270-d4b9-4de0-9722-d57d3f260cb8`.`00b36ed5-7640-4635-b9d3-60c2db229568`.propertyValue.value + * here the propertyId is 00b36ed5-7640-4635-b9d3-60c2db229568 + * @returns the propertyId or undefined if we are not provided with a model propertyId that is backed by SiteWise + */ +export const extractAssetPropertyId = ( + propertyExpression?: string +): string | undefined => { + if ( + propertyExpression && + isBackedBySiteWiseAssetProperty(propertyExpression) + ) { + const splitInputProperty = propertyExpression.split('.'); + return removeBackticks(splitInputProperty[3]); + } +}; diff --git a/packages/react-components/src/hooks/useAlarms/utils/parseCompositeModels.ts b/packages/react-components/src/hooks/useAlarms/utils/parseCompositeModels.ts new file mode 100644 index 000000000..962f00d17 --- /dev/null +++ b/packages/react-components/src/hooks/useAlarms/utils/parseCompositeModels.ts @@ -0,0 +1,61 @@ +import { + AssetCompositeModel, + AssetModelCompositeModel, + AssetModelProperty, + AssetProperty, +} from '@aws-sdk/client-iotsitewise'; +import { + ALARM_COMPOSITE_MODEL_TYPE, + ALARM_SOURCE_PROPERTY_NAME, + ALARM_STATE_PROPERTY_NAME, + ALARM_TYPE_PROPERTY_NAME, +} from '../constants'; + +/** + * Finds the alarm state, type, and source properties on a composite model if they exist. + * A compositeModel from an asset or assetModel share the same shape for searching properties so this + * function is generic for both. + * + * @param compositeModel is the asset or assetModel compositeModel + * @returns the state, type, and source asset or assetModel properties + */ +export const getAlarmPropertiesFromCompositeModel = ( + compositeModel?: AssetCompositeModel | AssetModelCompositeModel +): + | { + state?: AssetProperty | AssetModelProperty; + type?: AssetProperty | AssetModelProperty; + source?: AssetProperty | AssetModelProperty; + } + | undefined => { + if (isAlarmCompositeModel(compositeModel)) { + let stateProperty: AssetProperty | AssetModelProperty | undefined; + let typeProperty: AssetProperty | AssetModelProperty | undefined; + let sourceProperty: AssetProperty | AssetModelProperty | undefined; + + compositeModel?.properties?.forEach((property) => { + if (property.name === ALARM_STATE_PROPERTY_NAME) { + stateProperty = property; + } else if (property.name === ALARM_TYPE_PROPERTY_NAME) { + typeProperty = property; + } else if (property.name === ALARM_SOURCE_PROPERTY_NAME) { + sourceProperty = property; + } + }); + + return { + state: stateProperty, + type: typeProperty, + source: sourceProperty, + }; + } +}; + +/** + * Checks whether a composite model is an AWS/ALARM type + */ +export const isAlarmCompositeModel = ( + compositeModel?: AssetCompositeModel | AssetModelCompositeModel +): boolean => { + return compositeModel?.type === ALARM_COMPOSITE_MODEL_TYPE; +}; diff --git a/packages/react-components/src/hooks/useAlarms/utils/queryUtils.ts b/packages/react-components/src/hooks/useAlarms/utils/queryStatus.ts similarity index 79% rename from packages/react-components/src/hooks/useAlarms/utils/queryUtils.ts rename to packages/react-components/src/hooks/useAlarms/utils/queryStatus.ts index 30e5ecb02..c44e95a7e 100644 --- a/packages/react-components/src/hooks/useAlarms/utils/queryUtils.ts +++ b/packages/react-components/src/hooks/useAlarms/utils/queryStatus.ts @@ -1,14 +1,12 @@ import { UseQueryResult } from '@tanstack/react-query'; -import { AlarmDataStatus } from '../types'; +import type { AlarmDataStatus } from '../types'; /** - * Combine two query statuses + * Combine two query statuses. * * If any one status isLoading/Refetching/Error is true then the running status is also true * If any one status isSuccess is false then running status is also false - * Combine all errors into a running list */ -// Combine two query statuses const combineStatuses = ({ oldStatus, newStatus, @@ -20,12 +18,12 @@ const combineStatuses = ({ isRefetching: oldStatus.isRefetching || newStatus.isRefetching, isSuccess: oldStatus.isSuccess && newStatus.isSuccess, isError: oldStatus.isError || newStatus.isError, - errors: [...(oldStatus.errors ?? []), ...(newStatus.errors ?? [])], }); /** - * getStatusForQuery builds the AlarmDataStatus for a tanstack query result. - * Since AlarmData is built on multiple queries the status can be combined with a previous query. + * Create the AlarmDataStatus for a tanstack query result. + * Since AlarmData is built on multiple queries the status + * can be combined with a previous query. * * @param query is the tanstack query result with a status * @param oldStatus is the status from a previous query, which is combined with the given query @@ -35,14 +33,11 @@ export const getStatusForQuery = ( query: UseQueryResult, oldStatus?: AlarmDataStatus ): AlarmDataStatus => { - const errors: Error[] | undefined = - query.error !== null ? [query.error] : undefined; let status: AlarmDataStatus = { isLoading: query.isLoading, isRefetching: query.isRefetching, isSuccess: query.isSuccess, isError: query.isError, - errors, }; if (oldStatus) { status = combineStatuses({ oldStatus, newStatus: status }); @@ -52,7 +47,7 @@ export const getStatusForQuery = ( }; /** - * combineStatusForQueries resolves a single status between multiple queries and + * Resolves a single status between multiple queries and * the status of a previous query * * @param queries are the tanstack query results each with their own status @@ -75,7 +70,6 @@ export const combineStatusForQueries = ( isRefetching: queries.some(({ isRefetching }) => isRefetching), isSuccess: queries.every(({ isSuccess }) => isSuccess), isError: queries.some(({ isError }) => isError), - errors, }; if (oldStatus) { diff --git a/packages/react-components/src/queries/index.ts b/packages/react-components/src/queries/index.ts index e8a56d1b2..d94679cda 100644 --- a/packages/react-components/src/queries/index.ts +++ b/packages/react-components/src/queries/index.ts @@ -7,4 +7,5 @@ export * from './useTimeSeriesData'; export * from './useDescribeAssets'; export * from './useDescribeAssetModels'; export * from './useLatestAssetPropertyValues'; +export * from './useHistoricalAssetPropertyValues'; export * from './useDescribeAlarmModels'; diff --git a/packages/react-components/src/queries/useHistoricalAssetPropertyValues/index.ts b/packages/react-components/src/queries/useHistoricalAssetPropertyValues/index.ts new file mode 100644 index 000000000..432243d20 --- /dev/null +++ b/packages/react-components/src/queries/useHistoricalAssetPropertyValues/index.ts @@ -0,0 +1,2 @@ +export * from './types'; +export * from './useHistoricalAssetPropertyValues'; diff --git a/packages/react-components/src/queries/useHistoricalAssetPropertyValues/types.ts b/packages/react-components/src/queries/useHistoricalAssetPropertyValues/types.ts index 962473d62..9c853d9c3 100644 --- a/packages/react-components/src/queries/useHistoricalAssetPropertyValues/types.ts +++ b/packages/react-components/src/queries/useHistoricalAssetPropertyValues/types.ts @@ -8,7 +8,7 @@ import { import { UseIoTSiteWiseClientOptions } from '../../hooks/requestFunctions/useIoTSiteWiseClient'; import { QueryOptionsGlobal } from '../common/types'; -export type QueryFnClient = { +export type HistoricalValueQueryFnClient = { getAssetPropertyValueHistory?: GetAssetPropertyValueHistory; batchGetAssetPropertyValueHistory?: BatchGetAssetPropertyValueHistory; }; diff --git a/packages/react-components/src/queries/useHistoricalAssetPropertyValues/useHistoricalAssetPropertyValues.ts b/packages/react-components/src/queries/useHistoricalAssetPropertyValues/useHistoricalAssetPropertyValues.ts index 8e90897e9..de649d493 100644 --- a/packages/react-components/src/queries/useHistoricalAssetPropertyValues/useHistoricalAssetPropertyValues.ts +++ b/packages/react-components/src/queries/useHistoricalAssetPropertyValues/useHistoricalAssetPropertyValues.ts @@ -13,7 +13,7 @@ import { import { HistoricalAssetPropertyValueRequest, - QueryFnClient, + HistoricalValueQueryFnClient, UseHistoricalAssetPropertyValuesOptions, } from './types'; import { HistoricalAssetPropertyValueKeyFactory } from './historicalAssetPropertyValueKeyFactory'; @@ -44,7 +44,7 @@ const requestIsValid = ({ const clientIsValid = ({ getAssetPropertyValueHistory, batchGetAssetPropertyValueHistory, -}: QueryFnClient = {}) => +}: HistoricalValueQueryFnClient = {}) => hasRequestFunction( getAssetPropertyValueHistory ) || @@ -154,7 +154,7 @@ export const useHistoricalAssetPropertyValues = ({ }; export const createHistoricalAssetPropertyValueQueryFn = ( - client?: QueryFnClient + client?: HistoricalValueQueryFnClient ) => { return async ({ queryKey: [ diff --git a/packages/react-components/src/queries/useLatestAssetPropertyValues/types.ts b/packages/react-components/src/queries/useLatestAssetPropertyValues/types.ts index 1c61949a5..500221da9 100644 --- a/packages/react-components/src/queries/useLatestAssetPropertyValues/types.ts +++ b/packages/react-components/src/queries/useLatestAssetPropertyValues/types.ts @@ -7,7 +7,7 @@ import { import { UseIoTSiteWiseClientOptions } from '../../hooks/requestFunctions/useIoTSiteWiseClient'; import { QueryOptionsGlobal } from '../common/types'; -export type QueryFnClient = { +export type LatestValueQueryFnClient = { getAssetPropertyValue?: GetAssetPropertyValue; batchGetAssetPropertyValue?: BatchGetAssetPropertyValue; }; diff --git a/packages/react-components/src/queries/useLatestAssetPropertyValues/useLatestAssetPropertyValues.ts b/packages/react-components/src/queries/useLatestAssetPropertyValues/useLatestAssetPropertyValues.ts index ace03f669..e2c391fde 100644 --- a/packages/react-components/src/queries/useLatestAssetPropertyValues/useLatestAssetPropertyValues.ts +++ b/packages/react-components/src/queries/useLatestAssetPropertyValues/useLatestAssetPropertyValues.ts @@ -13,7 +13,7 @@ import { import { LatestAssetPropertyValueRequest, - QueryFnClient, + LatestValueQueryFnClient, UseLatestAssetPropertyValuesOptions, } from './types'; import { LatestAssetPropertyValueKeyFactory } from './latestAssetPropertyValueKeyFactory'; @@ -40,7 +40,7 @@ const requestIsValid = ({ const clientIsValid = ({ getAssetPropertyValue, batchGetAssetPropertyValue, -}: QueryFnClient = {}) => +}: LatestValueQueryFnClient = {}) => hasRequestFunction(getAssetPropertyValue) || hasRequestFunction(batchGetAssetPropertyValue); @@ -121,7 +121,7 @@ export const useLatestAssetPropertyValues = ({ }; export const createLatestAssetPropertyValueQueryFn = ( - client?: QueryFnClient + client?: LatestValueQueryFnClient ) => { return async ({ queryKey: [{ assetId, propertyId, propertyAlias }], diff --git a/packages/react-components/src/testing/alarms/mockAlarmData.ts b/packages/react-components/src/testing/alarms/mockAlarmData.ts index 3ee073561..221e03510 100644 --- a/packages/react-components/src/testing/alarms/mockAlarmData.ts +++ b/packages/react-components/src/testing/alarms/mockAlarmData.ts @@ -1,4 +1,4 @@ -import { AlarmData } from '../../hooks/useAlarms'; +import type { AlarmDataInternal } from '../../hooks/useAlarms'; import { mockAlarmModel, mockAlarmModel2, @@ -31,7 +31,7 @@ import { mockTypeAssetPropertyValue, } from './mockProperties'; -export const mockAlarmDataDescribeAsset: AlarmData = { +export const mockAlarmDataDescribeAsset = { assetModelId: MOCK_ASSET_MODEL_ID, assetId: MOCK_ASSET_ID, compositeModelId: MOCK_COMPOSITE_MODEL_ID, @@ -51,9 +51,9 @@ export const mockAlarmDataDescribeAsset: AlarmData = { isRefetching: false, isSuccess: true, }, -}; +} satisfies AlarmDataInternal; -export const mockAlarmDataDescribeAsset2: AlarmData = { +export const mockAlarmDataDescribeAsset2 = { assetModelId: MOCK_ASSET_MODEL_ID, assetId: MOCK_ASSET_ID, compositeModelId: MOCK_COMPOSITE_MODEL_ID_2, @@ -73,9 +73,9 @@ export const mockAlarmDataDescribeAsset2: AlarmData = { isRefetching: false, isSuccess: true, }, -}; +} satisfies AlarmDataInternal; -export const mockAlarmDataDescribeAssetModel: AlarmData = { +export const mockAlarmDataDescribeAssetModel = { assetModelId: MOCK_ASSET_MODEL_ID, compositeModelId: MOCK_COMPOSITE_MODEL_ID, compositeModelName: MOCK_COMPOSITE_MODEL_NAME, @@ -118,9 +118,9 @@ export const mockAlarmDataDescribeAssetModel: AlarmData = { isRefetching: false, isSuccess: true, }, -}; +} satisfies AlarmDataInternal; -export const mockAlarmDataGetAssetPropertyValue: AlarmData = { +export const mockAlarmDataGetAssetPropertyValue = { ...mockAlarmDataDescribeAsset, state: { property: mockStateAssetProperty, @@ -134,9 +134,9 @@ export const mockAlarmDataGetAssetPropertyValue: AlarmData = { property: mockSourceAssetProperty, data: [mockSourceAssetPropertyValue], }, -}; +} satisfies AlarmDataInternal; -export const mockAlarmDataGetAssetPropertyValue2: AlarmData = { +export const mockAlarmDataGetAssetPropertyValue2 = { ...mockAlarmDataDescribeAsset2, state: { property: mockStateAssetProperty2, @@ -150,13 +150,13 @@ export const mockAlarmDataGetAssetPropertyValue2: AlarmData = { property: mockSourceAssetProperty2, data: [mockSourceAssetPropertyValue2], }, -}; +} satisfies AlarmDataInternal; -export const mockAlarmDataDescribeAlarmModel: AlarmData = { +export const mockAlarmDataDescribeAlarmModel = { ...mockAlarmDataGetAssetPropertyValue, models: [mockAlarmModel], -}; -export const mockAlarmDataDescribeAlarmModel2: AlarmData = { +} satisfies AlarmDataInternal; +export const mockAlarmDataDescribeAlarmModel2 = { ...mockAlarmDataGetAssetPropertyValue2, models: [mockAlarmModel2], -}; +} satisfies AlarmDataInternal; diff --git a/packages/react-components/src/testing/alarms/mockAlarmModel.ts b/packages/react-components/src/testing/alarms/mockAlarmModel.ts index 27c04d301..972e39a47 100644 --- a/packages/react-components/src/testing/alarms/mockAlarmModel.ts +++ b/packages/react-components/src/testing/alarms/mockAlarmModel.ts @@ -10,7 +10,7 @@ export const MOCK_ALARM_MODEL_NAME_2 = 'alarmModelName2'; export const mockAlarmModelArn = `arn:aws:iotevents:us-east-1:123456789012:alarmModel/${MOCK_ALARM_MODEL_NAME}`; export const mockAlarmModelArn2 = `arn:aws:iotevents:us-east-1:123456789012:alarmModel/${MOCK_ALARM_MODEL_NAME_2}`; -export const mockAlarmModel: DescribeAlarmModelResponse = { +export const mockAlarmModel = { alarmModelArn: mockAlarmModelArn, alarmModelVersion: '1', creationTime: new Date(), @@ -25,9 +25,9 @@ export const mockAlarmModel: DescribeAlarmModelResponse = { threshold: '30', }, }, -}; +} satisfies DescribeAlarmModelResponse; -export const mockAlarmModel2: DescribeAlarmModelResponse = { +export const mockAlarmModel2 = { alarmModelArn: mockAlarmModelArn2, alarmModelVersion: '1', creationTime: new Date(), @@ -42,4 +42,4 @@ export const mockAlarmModel2: DescribeAlarmModelResponse = { threshold: '30', }, }, -}; +} satisfies DescribeAlarmModelResponse; diff --git a/packages/react-components/src/testing/alarms/mockCompositeModels.ts b/packages/react-components/src/testing/alarms/mockCompositeModels.ts index 12240881f..63feb3f46 100644 --- a/packages/react-components/src/testing/alarms/mockCompositeModels.ts +++ b/packages/react-components/src/testing/alarms/mockCompositeModels.ts @@ -18,7 +18,7 @@ import { MOCK_COMPOSITE_MODEL_NAME_2, } from './mockIds'; -export const mockAlarmCompositeModel: AssetCompositeModel = { +export const mockAlarmCompositeModel = { name: MOCK_COMPOSITE_MODEL_NAME, type: ALARM_COMPOSITE_MODEL_TYPE, properties: [ @@ -27,9 +27,9 @@ export const mockAlarmCompositeModel: AssetCompositeModel = { mockSourceAssetProperty, ], id: MOCK_COMPOSITE_MODEL_ID, -}; +} satisfies AssetCompositeModel; -export const mockAlarmCompositeModel2: AssetCompositeModel = { +export const mockAlarmCompositeModel2 = { name: MOCK_COMPOSITE_MODEL_NAME_2, type: ALARM_COMPOSITE_MODEL_TYPE, properties: [ @@ -38,9 +38,9 @@ export const mockAlarmCompositeModel2: AssetCompositeModel = { mockSourceAssetProperty, ], id: MOCK_COMPOSITE_MODEL_ID_2, -}; +} satisfies AssetCompositeModel; -export const mockAlarmModelCompositeModel: AssetModelCompositeModel = { +export const mockAlarmModelCompositeModel = { name: MOCK_COMPOSITE_MODEL_NAME, type: ALARM_COMPOSITE_MODEL_TYPE, properties: [ @@ -49,9 +49,9 @@ export const mockAlarmModelCompositeModel: AssetModelCompositeModel = { mockSourceAssetModelProperty, ], id: MOCK_COMPOSITE_MODEL_ID, -}; +} satisfies AssetModelCompositeModel; -export const mockAlarmModelCompositeModel2: AssetModelCompositeModel = { +export const mockAlarmModelCompositeModel2 = { name: MOCK_COMPOSITE_MODEL_NAME_2, type: ALARM_COMPOSITE_MODEL_TYPE, properties: [ @@ -60,4 +60,4 @@ export const mockAlarmModelCompositeModel2: AssetModelCompositeModel = { mockSourceAssetModelProperty, ], id: MOCK_COMPOSITE_MODEL_ID_2, -}; +} satisfies AssetModelCompositeModel; diff --git a/packages/react-components/src/testing/alarms/mockProperties.ts b/packages/react-components/src/testing/alarms/mockProperties.ts index 54436d1c0..8b9bc4c62 100644 --- a/packages/react-components/src/testing/alarms/mockProperties.ts +++ b/packages/react-components/src/testing/alarms/mockProperties.ts @@ -14,41 +14,69 @@ import { } from './mockIds'; import { mockAlarmModelArn, mockAlarmModelArn2 } from './mockAlarmModel'; -export const mockStateAssetProperty: AssetProperty = { +export const mockAssetProperties = [ + { + id: 'property1', + name: 'propertyName1', + dataType: 'STRING', + }, + { + id: 'property1', + name: 'propertyName1', + dataType: 'STRING', + }, +] satisfies AssetProperty[]; + +export const mockAssetModelProperties = [ + { + id: 'property1', + name: 'propertyName1', + dataType: 'STRING', + type: {}, + }, + { + id: 'property1', + name: 'propertyName1', + dataType: 'STRING', + type: {}, + }, +] satisfies AssetModelProperty[]; + +export const mockStateAssetProperty = { id: 'stateId', name: ALARM_STATE_PROPERTY_NAME, dataType: 'STRING', -}; +} satisfies AssetProperty; -export const mockStateAssetProperty2: AssetProperty = { +export const mockStateAssetProperty2 = { id: 'stateId2', name: ALARM_STATE_PROPERTY_NAME, dataType: 'STRING', -}; +} satisfies AssetProperty; -export const mockTypeAssetProperty: AssetProperty = { +export const mockTypeAssetProperty = { id: 'typeId', name: ALARM_TYPE_PROPERTY_NAME, dataType: 'STRING', -}; +} satisfies AssetProperty; -export const mockTypeAssetProperty2: AssetProperty = { +export const mockTypeAssetProperty2 = { id: 'typeId2', name: ALARM_STATE_PROPERTY_NAME, dataType: 'STRING', -}; +} satisfies AssetProperty; -export const mockSourceAssetProperty: AssetProperty = { +export const mockSourceAssetProperty = { id: 'sourceId', name: ALARM_SOURCE_PROPERTY_NAME, dataType: 'STRING', -}; +} satisfies AssetProperty; -export const mockSourceAssetProperty2: AssetProperty = { +export const mockSourceAssetProperty2 = { id: 'sourceId2', name: ALARM_STATE_PROPERTY_NAME, dataType: 'STRING', -}; +} satisfies AssetProperty; export const mockDefaultAlarmState = JSON.stringify({ stateName: 'NORMAL', @@ -60,6 +88,7 @@ export const mockDefaultAlarmState = JSON.stringify({ }, }, }); + export const mockDefaultAlarmState2 = JSON.stringify({ stateName: 'ACTIVE', ruleEvaluation: { @@ -70,45 +99,46 @@ export const mockDefaultAlarmState2 = JSON.stringify({ }, }, }); -export const mockStateAssetModelProperty: AssetModelProperty = { + +export const mockStateAssetModelProperty = { ...mockStateAssetProperty, type: { attribute: { defaultValue: mockDefaultAlarmState, }, }, -}; +} satisfies AssetModelProperty; export const mockDefaultAlarmType = 'IOT_EVENTS'; -export const mockTypeAssetModelProperty: AssetModelProperty = { +export const mockTypeAssetModelProperty = { ...mockTypeAssetProperty, type: { attribute: { defaultValue: mockDefaultAlarmType, }, }, -}; +} satisfies AssetModelProperty; -export const mockSourceAssetModelProperty: AssetModelProperty = { +export const mockSourceAssetModelProperty = { ...mockSourceAssetProperty, type: { attribute: { defaultValue: mockAlarmModelArn, }, }, -}; +} satisfies AssetModelProperty; -export const mockInputProperty: AssetProperty = { +export const mockInputProperty = { id: MOCK_ALARM_INPUT_PROPERTY_ID, name: 'inputPropertyName', dataType: 'STRING', -}; +} satisfies AssetProperty; -export const mockInputProperty2: AssetProperty = { +export const mockInputProperty2 = { id: MOCK_ALARM_INPUT_PROPERTY_ID_2, name: 'inputPropertyName2', dataType: 'STRING', -}; +} satisfies AssetProperty; export const mockStringAssetPropertyValue = ( value: string, @@ -124,14 +154,19 @@ export const mockStringAssetPropertyValue = ( quality: 'GOOD', }); -export const mockStateAssetPropertyValue: AssetPropertyValue = - mockStringAssetPropertyValue(mockDefaultAlarmState); -export const mockTypeAssetPropertyValue: AssetPropertyValue = - mockStringAssetPropertyValue(mockDefaultAlarmType); -export const mockSourceAssetPropertyValue: AssetPropertyValue = - mockStringAssetPropertyValue(mockAlarmModelArn); - -export const mockStateAssetPropertyValue2: AssetPropertyValue = - mockStringAssetPropertyValue(mockDefaultAlarmState2); -export const mockSourceAssetPropertyValue2: AssetPropertyValue = - mockStringAssetPropertyValue(mockAlarmModelArn2); +export const mockStateAssetPropertyValue = mockStringAssetPropertyValue( + mockDefaultAlarmState +) satisfies AssetPropertyValue; +export const mockTypeAssetPropertyValue = mockStringAssetPropertyValue( + mockDefaultAlarmType +) satisfies AssetPropertyValue; +export const mockSourceAssetPropertyValue = mockStringAssetPropertyValue( + mockAlarmModelArn +) satisfies AssetPropertyValue; + +export const mockStateAssetPropertyValue2 = mockStringAssetPropertyValue( + mockDefaultAlarmState2 +) satisfies AssetPropertyValue; +export const mockSourceAssetPropertyValue2 = mockStringAssetPropertyValue( + mockAlarmModelArn2 +) satisfies AssetPropertyValue; diff --git a/packages/react-components/stories/queries/queriesBase.stories.tsx b/packages/react-components/stories/queries/queriesBase.stories.tsx index 828a98776..7310d54df 100644 --- a/packages/react-components/stories/queries/queriesBase.stories.tsx +++ b/packages/react-components/stories/queries/queriesBase.stories.tsx @@ -79,7 +79,6 @@ export const UseAlarms: ComponentStory = () => { }, ], }); - console.log(alarmDataList); return ; };