diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 15cc60586..24d082f13 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -62,7 +62,6 @@ jobs: data-mocked, dev-env, doc-site, - helpers, react-components, scene-composer, source-iotsitewise, diff --git a/apps/dev-env/package.json b/apps/dev-env/package.json index 56a2b9e43..3fdcaf587 100644 --- a/apps/dev-env/package.json +++ b/apps/dev-env/package.json @@ -22,7 +22,6 @@ "@iot-app-kit/core-util": "*", "@iot-app-kit/dashboard": "*", "@iot-app-kit/data-mocked": "*", - "@iot-app-kit/helpers": "*", "@iot-app-kit/ts-config": "*", "@playwright/test": "^1.48.2", "@storybook/addon-essentials": "^8.4.5", diff --git a/apps/dev-env/playwright.config.ts b/apps/dev-env/playwright.config.ts index b94292d15..d1e9c1e4b 100644 --- a/apps/dev-env/playwright.config.ts +++ b/apps/dev-env/playwright.config.ts @@ -1,9 +1,8 @@ -import { - MINUTE_IN_MS, - SECOND_IN_MS, -} from '@iot-app-kit/helpers/constants/time'; import { defineConfig, devices } from '@playwright/test'; +const SECOND_IN_MS = 1000; +const MINUTE_IN_MS = 60_000; + /** * See https://playwright.dev/docs/test-configuration. */ diff --git a/apps/dev-env/stories/dashboard/sitewise-dashboard.stories.tsx b/apps/dev-env/stories/dashboard/sitewise-dashboard.stories.tsx index 9f07d9aac..75e759025 100644 --- a/apps/dev-env/stories/dashboard/sitewise-dashboard.stories.tsx +++ b/apps/dev-env/stories/dashboard/sitewise-dashboard.stories.tsx @@ -11,8 +11,8 @@ const DASHBOARD_STORAGE_NAMESPACE = 'connected-dashboard'; const DEFAULT_DASHBOARD_CONFIG = { displaySettings: { - numColumns: 100, - numRows: 100, + numColumns: 200, + numRows: 200, }, widgets: [], defaultViewport: { duration: '10m' }, diff --git a/configuration/eslint-config/index.js b/configuration/eslint-config/index.js index a3fd8f783..89df8a604 100644 --- a/configuration/eslint-config/index.js +++ b/configuration/eslint-config/index.js @@ -20,7 +20,7 @@ module.exports = { 'plugin:import/recommended', 'plugin:import/typescript', ], - plugins: ['prettier', 'react', 'jest', 'import', 'unused-imports'], + plugins: ['prettier', 'react', "react-hooks", 'jest', 'import', 'unused-imports'], globals: { module: true, process: true, diff --git a/package-lock.json b/package-lock.json index f46b61081..3b3c04634 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,6 @@ "@iot-app-kit/core-util": "*", "@iot-app-kit/dashboard": "*", "@iot-app-kit/data-mocked": "*", - "@iot-app-kit/helpers": "*", "@iot-app-kit/ts-config": "*", "@playwright/test": "^1.48.2", "@storybook/addon-essentials": "^8.4.5", @@ -9899,10 +9898,6 @@ "resolved": "apps/doc-site", "link": true }, - "node_modules/@iot-app-kit/helpers": { - "resolved": "packages/helpers", - "link": true - }, "node_modules/@iot-app-kit/react-components": { "resolved": "packages/react-components", "link": true @@ -36952,15 +36947,6 @@ "uuid": "^9.0.0" } }, - "packages/helpers": { - "name": "@iot-app-kit/helpers", - "dependencies": { - "@iot-app-kit/ts-config": "*", - "eslint-config-iot-app-kit": "*", - "rimraf": "^5.0.1", - "typescript": "^5.5.4" - } - }, "packages/react-components": { "name": "@iot-app-kit/react-components", "version": "12.3.0", diff --git a/packages/core/src/data-module/types.ts b/packages/core/src/data-module/types.ts index 390b7f18a..1da52ae1f 100644 --- a/packages/core/src/data-module/types.ts +++ b/packages/core/src/data-module/types.ts @@ -27,7 +27,7 @@ export interface DataPoint export type Resolution = number; -export type Primitive = string | number | boolean; +export type Primitive = string | number | boolean | null; export type DataStreamId = string; diff --git a/packages/helpers/.eslintignore b/packages/helpers/.eslintignore deleted file mode 100644 index b512c09d4..000000000 --- a/packages/helpers/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -node_modules \ No newline at end of file diff --git a/packages/helpers/.eslintrc.cjs b/packages/helpers/.eslintrc.cjs deleted file mode 100644 index 5b89c5273..000000000 --- a/packages/helpers/.eslintrc.cjs +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - root: true, - extends: ['iot-app-kit'], -}; diff --git a/packages/helpers/package.json b/packages/helpers/package.json deleted file mode 100644 index 13d64a22e..000000000 --- a/packages/helpers/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "@iot-app-kit/helpers", - "private": true, - "type": "module", - "exports": { - "./constants/*": "./src/constants/*.ts" - }, - "scripts": { - "clean": "rimraf .turbo .cache", - "clean:nuke": "npm run clean && rimraf node_modules", - "lint": "eslint . --max-warnings=0 --cache --cache-location .cache/eslint/", - "fix": "eslint --fix . --cache --cache-location ./cache/eslint/" - }, - "dependencies": { - "@iot-app-kit/ts-config": "*", - "eslint-config-iot-app-kit": "*", - "rimraf": "^5.0.1", - "typescript": "^5.5.4" - } -} diff --git a/packages/helpers/src/constants/time.ts b/packages/helpers/src/constants/time.ts deleted file mode 100644 index 3527184b3..000000000 --- a/packages/helpers/src/constants/time.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const SECOND_IN_MS = 1000; -export const MINUTE_IN_MS = 60_000; diff --git a/packages/helpers/tsconfig.json b/packages/helpers/tsconfig.json deleted file mode 100644 index 2ee437d64..000000000 --- a/packages/helpers/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "extends": "@iot-app-kit/ts-config/tsconfig.base.json", - "include": ["src"] -} diff --git a/packages/react-components/src/components/chart/chartOptions/tooltip/value.tsx b/packages/react-components/src/components/chart/chartOptions/tooltip/value.tsx index 349e6f91a..d8f7acd5c 100644 --- a/packages/react-components/src/components/chart/chartOptions/tooltip/value.tsx +++ b/packages/react-components/src/components/chart/chartOptions/tooltip/value.tsx @@ -4,7 +4,7 @@ import { isNumeric, round } from '@iot-app-kit/core-util'; export const formatValue = (significantDigits = 4) => (value: Primitive) => - isNumeric(value) ? `${round(value, significantDigits)}` : value.toString(); + isNumeric(value) ? `${round(value, significantDigits)}` : value?.toString(); export type XYPlotTooltipValueOptions = { value?: Primitive; diff --git a/packages/react-components/src/components/chart/multiYAxis/yAxisMenu.tsx b/packages/react-components/src/components/chart/multiYAxis/yAxisMenu.tsx index 3f2d2e1eb..c5f2e7e3a 100644 --- a/packages/react-components/src/components/chart/multiYAxis/yAxisMenu.tsx +++ b/packages/react-components/src/components/chart/multiYAxis/yAxisMenu.tsx @@ -24,7 +24,7 @@ import { useHighlightedDataStreams } from '../hooks/useHighlightedDataStreams'; import './yAxisMenu.css'; const getValue = (value: Primitive, significantDigits = 4) => - isNumeric(value) ? `${round(value, significantDigits)}` : value.toString(); + isNumeric(value) ? `${round(value, significantDigits)}` : value?.toString(); const MENU_OFFSET = 5; const MENU_FONT_SIZE = 14; diff --git a/packages/react-components/src/components/resource-explorers/requests/use-latest-values/create-data-streams-with-latest-value.ts b/packages/react-components/src/components/resource-explorers/requests/use-latest-values/create-data-streams-with-latest-value.ts index b3313953c..6a79b8104 100644 --- a/packages/react-components/src/components/resource-explorers/requests/use-latest-values/create-data-streams-with-latest-value.ts +++ b/packages/react-components/src/components/resource-explorers/requests/use-latest-values/create-data-streams-with-latest-value.ts @@ -1,6 +1,7 @@ import type { BatchGetAssetPropertyValueSuccessEntry } from '@aws-sdk/client-iotsitewise'; import type { DataStreamRequestEntry, DataStreamResource } from './types'; import type { DataStreamResourceWithLatestValue } from '../../types/resources'; +import { toValue } from './toValue'; type SuccessEntry = BatchGetAssetPropertyValueSuccessEntry; @@ -41,11 +42,10 @@ function createDataStreamWithLatestValue( requestEntry: DataStreamRequestEntry, successEntry?: SuccessEntry ): DataStreamResourceWithLatestValue { + const variant = successEntry?.assetPropertyValue?.value; const dataStreamWithLatestValue = { ...requestEntry.dataStream, - latestValue: Object.values( - successEntry?.assetPropertyValue?.value ?? {} - ).at(0), + latestValue: variant && toValue(variant), latestValueTimestamp: successEntry?.assetPropertyValue?.timestamp?.timeInSeconds, }; diff --git a/packages/react-components/src/components/resource-explorers/requests/use-latest-values/toValue.ts b/packages/react-components/src/components/resource-explorers/requests/use-latest-values/toValue.ts new file mode 100644 index 000000000..75edf4d40 --- /dev/null +++ b/packages/react-components/src/components/resource-explorers/requests/use-latest-values/toValue.ts @@ -0,0 +1,45 @@ +import type { Primitive } from '@iot-app-kit/core'; +import type { Variant } from '@aws-sdk/client-iotsitewise'; + +export const toValue = (variant: Variant | undefined): Primitive => { + if (variant == null) { + throw new Error('variant is undefined'); + } + + const { doubleValue, integerValue, stringValue, booleanValue } = variant; + + if (doubleValue != null) { + return doubleValue; + } + + if (integerValue != null) { + return integerValue; + } + + if (stringValue != null) { + return stringValue; + } + + if (booleanValue != null) { + return booleanValue.toString(); + } + + if ('nullValue' in variant && variant.nullValue != null) { + /** + * nullValue is not a nullish value + * it's an object with the string type of + * what the value should be. + */ + return null; + } + + /** + * A variant with no properties is treated + * as null data so that datastreams do not + * break when the sdk updates + */ + return null; + // throw new Error( + // 'Expected value to have at least one property value, but instead it has none!' + // ); +}; diff --git a/packages/react-components/src/components/resource-explorers/types/resources.ts b/packages/react-components/src/components/resource-explorers/types/resources.ts index e495f1db4..e855ef560 100644 --- a/packages/react-components/src/components/resource-explorers/types/resources.ts +++ b/packages/react-components/src/components/resource-explorers/types/resources.ts @@ -59,6 +59,6 @@ export type TimeSeriesResourceWithLatestValue = export type DataStreamResourceWithLatestValue = DataStreamResource & { - latestValue?: number | string | boolean; + latestValue?: number | string | boolean | null; latestValueTimestamp?: number; }; diff --git a/packages/react-components/src/components/table/createTableItems.ts b/packages/react-components/src/components/table/createTableItems.ts index b57514079..1ad623d45 100644 --- a/packages/react-components/src/components/table/createTableItems.ts +++ b/packages/react-components/src/components/table/createTableItems.ts @@ -40,7 +40,7 @@ export const createTableItems: ( const alarmItemsWithData = alarms.map((alarm) => { const isLoading = alarm.isLoading; return { - id: alarm.id as Primitive, + id: alarm.id as string, assetId: createCellItem( { value: alarm.assetId, @@ -189,7 +189,7 @@ export const createTableItems: ( const [first] = keyDataPairs; return { - id: first.data.value as Primitive, + id: first.data.value as string | number | boolean, ...keyDataPairs.reduce( (previous, { key, data }) => ({ ...previous, diff --git a/packages/react-components/src/queries/useAssetPropertyValues/types.ts b/packages/react-components/src/queries/useAssetPropertyValues/types.ts index 82982b951..3a8169733 100644 --- a/packages/react-components/src/queries/useAssetPropertyValues/types.ts +++ b/packages/react-components/src/queries/useAssetPropertyValues/types.ts @@ -8,6 +8,7 @@ import { type GetAssetPropertyValueHistoryRequest, type GetAssetPropertyValueHistoryResponse, } from '@aws-sdk/client-iotsitewise'; +import { type Primitive } from '@iot-app-kit/charts-core'; import { type RequestFunction, type RequestParameters, @@ -84,7 +85,7 @@ export type AssetPropertyValuesRequest = | AssetPropertyValueHistoryRequest | AssetPropertyAggregatesRequest; -export type AssetPropertyValuesData = DataPoint[]; +export type AssetPropertyValuesData = DataPoint[]; export type AssetPropertyValuesRequestFunctions = { getAssetPropertyValueHistory?: GetAssetPropertyValueHistoryRequestFunction; diff --git a/packages/react-components/src/queries/useAssetPropertyValues/utils/toDataPoint.spec.ts b/packages/react-components/src/queries/useAssetPropertyValues/utils/toDataPoint.spec.ts index 14ac13246..3e805580b 100644 --- a/packages/react-components/src/queries/useAssetPropertyValues/utils/toDataPoint.spec.ts +++ b/packages/react-components/src/queries/useAssetPropertyValues/utils/toDataPoint.spec.ts @@ -70,13 +70,13 @@ describe('toDataPoint', () => { }); }); - it('throws error when no property values passed in', () => { + it('does not throw error when no property values passed in', () => { expect(() => toDataPoint({ timestamp: { timeInSeconds: SECONDS }, value: {}, }) - ).toThrowError(); + ).not.toThrowError(); }); it('converts correctly for a string value', () => { diff --git a/packages/react-components/src/queries/useAssetPropertyValues/utils/toDataPoint.ts b/packages/react-components/src/queries/useAssetPropertyValues/utils/toDataPoint.ts index cbd7c30bd..807fb55e0 100644 --- a/packages/react-components/src/queries/useAssetPropertyValues/utils/toDataPoint.ts +++ b/packages/react-components/src/queries/useAssetPropertyValues/utils/toDataPoint.ts @@ -1,5 +1,5 @@ import { NANO_SECOND_IN_MS, SECOND_IN_MS } from './timeConstants'; -import type { DataPoint, Primitive } from '@iot-app-kit/core'; +import type { DataPoint } from '@iot-app-kit/core'; import type { AssetPropertyValue, TimeInNanos, @@ -7,6 +7,7 @@ import type { Aggregates, AggregatedValue, } from '@aws-sdk/client-iotsitewise'; +import { type Primitive } from '@iot-app-kit/charts-core'; /** converts the TimeInNanos to milliseconds */ export const toTimestamp = (time: TimeInNanos | undefined): number => @@ -24,7 +25,7 @@ export const toTimestamp = (time: TimeInNanos | undefined): number => * * NOTE: Currently we treat booleans as strings. */ -export const toValue = (variant: Variant | undefined): Primitive => { +export const toValue = (variant: Variant | undefined): Primitive | null => { if (variant == null) { throw new Error('variant is undefined'); } @@ -47,9 +48,7 @@ export const toValue = (variant: Variant | undefined): Primitive => { return booleanValue.toString(); } - throw new Error( - 'Expected value to have at least one property value, but instead it has none!' - ); + return null; }; /** @@ -57,7 +56,7 @@ export const toValue = (variant: Variant | undefined): Primitive => { */ export const toDataPoint = ( assetPropertyValue: AssetPropertyValue | undefined -): DataPoint | undefined => { +): DataPoint | undefined => { if (assetPropertyValue == null) { return undefined; } diff --git a/packages/react-components/src/utils/getPreciseValue.ts b/packages/react-components/src/utils/getPreciseValue.ts index eccc8200e..68aee9499 100644 --- a/packages/react-components/src/utils/getPreciseValue.ts +++ b/packages/react-components/src/utils/getPreciseValue.ts @@ -2,4 +2,4 @@ import { type Primitive } from '@iot-app-kit/core'; import { isNumeric, round } from '@iot-app-kit/core-util'; export const getPreciseValue = (value: Primitive, significantDigits = 4) => - isNumeric(value) ? `${round(value, significantDigits)}` : value.toString(); + isNumeric(value) ? `${round(value, significantDigits)}` : value?.toString(); diff --git a/packages/react-components/src/utils/thresholdUtils.ts b/packages/react-components/src/utils/thresholdUtils.ts index c29061148..291d6d950 100644 --- a/packages/react-components/src/utils/thresholdUtils.ts +++ b/packages/react-components/src/utils/thresholdUtils.ts @@ -226,7 +226,11 @@ export const getBreachedThreshold = ( return undefined; } - if (typeof value === 'string' || typeof value === 'boolean') { + if ( + typeof value === 'string' || + typeof value === 'boolean' || + value === null + ) { return ( thresholds.find((threshold) => isThresholdBreached(value, threshold)) || undefined diff --git a/packages/source-iotsitewise/src/alarms/iotevents/siteWiseAlarmModule.ts b/packages/source-iotsitewise/src/alarms/iotevents/siteWiseAlarmModule.ts index 14a03533c..96d337e4a 100644 --- a/packages/source-iotsitewise/src/alarms/iotevents/siteWiseAlarmModule.ts +++ b/packages/source-iotsitewise/src/alarms/iotevents/siteWiseAlarmModule.ts @@ -110,7 +110,8 @@ export class SiteWiseAlarmModule { inputPropertyId, thresholdPropertyId, comparisonOperator, - threshold, + // old synchro charts type does not support null + threshold: threshold === null ? '' : threshold, severity, rule, state, diff --git a/packages/source-iotsitewise/src/alarms/iotevents/util/completeAlarmStream.ts b/packages/source-iotsitewise/src/alarms/iotevents/util/completeAlarmStream.ts index 51392b632..834186a1d 100644 --- a/packages/source-iotsitewise/src/alarms/iotevents/util/completeAlarmStream.ts +++ b/packages/source-iotsitewise/src/alarms/iotevents/util/completeAlarmStream.ts @@ -35,7 +35,7 @@ export const completeAlarmStream = ({ if (!assetModel) { if ( isIoTEventsAlarmStateProperty( - dataStream.data[dataStream.data?.length - 1]?.y + dataStream.data[dataStream.data?.length - 1]?.y ?? undefined ) ) { return { diff --git a/packages/source-iotsitewise/src/time-series-data/util/toDataPoint.spec.ts b/packages/source-iotsitewise/src/time-series-data/util/toDataPoint.spec.ts index 14ac13246..860153785 100644 --- a/packages/source-iotsitewise/src/time-series-data/util/toDataPoint.spec.ts +++ b/packages/source-iotsitewise/src/time-series-data/util/toDataPoint.spec.ts @@ -70,13 +70,16 @@ describe('toDataPoint', () => { }); }); - it('throws error when no property values passed in', () => { + it('does not throw error when no property values passed in', () => { + /** + * changing this functionality to patch support null / nan data + */ expect(() => toDataPoint({ timestamp: { timeInSeconds: SECONDS }, value: {}, }) - ).toThrowError(); + ).not.toThrowError(); }); it('converts correctly for a string value', () => { diff --git a/packages/source-iotsitewise/src/time-series-data/util/toDataPoint.ts b/packages/source-iotsitewise/src/time-series-data/util/toDataPoint.ts index a0923cb7f..46760c9f7 100644 --- a/packages/source-iotsitewise/src/time-series-data/util/toDataPoint.ts +++ b/packages/source-iotsitewise/src/time-series-data/util/toDataPoint.ts @@ -47,9 +47,24 @@ export const toValue = (variant: Variant | undefined): Primitive => { return booleanValue.toString(); } - throw new Error( - 'Expected value to have at least one property value, but instead it has none!' - ); + if ('nullValue' in variant && variant.nullValue != null) { + /** + * nullValue is not a nullish value + * it's an object with the string type of + * what the value should be. + */ + return null; + } + + /** + * A variant with no properties is treated + * as null data so that datastreams do not + * break when the sdk updates + */ + return null; + // throw new Error( + // 'Expected value to have at least one property value, but instead it has none!' + // ); }; /**