Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add null/NaN support #3108

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ jobs:
data-mocked,
dev-env,
doc-site,
helpers,
react-components,
scene-composer,
source-iotsitewise,
Expand Down
1 change: 0 additions & 1 deletion apps/dev-env/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
7 changes: 3 additions & 4 deletions apps/dev-env/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -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.
*/
Expand Down
4 changes: 2 additions & 2 deletions apps/dev-env/stories/dashboard/sitewise-dashboard.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down
2 changes: 1 addition & 1 deletion configuration/eslint-config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
14 changes: 0 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/core/src/data-module/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

export type Resolution = number;

export type Primitive = string | number | boolean;
export type Primitive = string | number | boolean | null;

export type DataStreamId = string;

Expand Down Expand Up @@ -154,7 +154,7 @@
cacheSettings?: CacheSettings;
};

export type AnyDataStreamQuery = DataStreamQuery & any;

Check warning on line 157 in packages/core/src/data-module/types.ts

View workflow job for this annotation

GitHub Actions / unit (core)

Unexpected any. Specify a different type

export type ErrorCallback = ({
id,
Expand Down
1 change: 0 additions & 1 deletion packages/helpers/.eslintignore

This file was deleted.

4 changes: 0 additions & 4 deletions packages/helpers/.eslintrc.cjs

This file was deleted.

20 changes: 0 additions & 20 deletions packages/helpers/package.json

This file was deleted.

2 changes: 0 additions & 2 deletions packages/helpers/src/constants/time.ts

This file was deleted.

5 changes: 0 additions & 5 deletions packages/helpers/tsconfig.json

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -41,11 +42,10 @@ function createDataStreamWithLatestValue<DataStream extends DataStreamResource>(
requestEntry: DataStreamRequestEntry<DataStream>,
successEntry?: SuccessEntry
): DataStreamResourceWithLatestValue<DataStream> {
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,
};
Expand Down
Original file line number Diff line number Diff line change
@@ -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!'
// );
};
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,6 @@ export type TimeSeriesResourceWithLatestValue =

export type DataStreamResourceWithLatestValue<DataStreamResource> =
DataStreamResource & {
latestValue?: number | string | boolean;
latestValue?: number | string | boolean | null;
latestValueTimestamp?: number;
};
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -84,7 +85,7 @@ export type AssetPropertyValuesRequest =
| AssetPropertyValueHistoryRequest
| AssetPropertyAggregatesRequest;

export type AssetPropertyValuesData = DataPoint[];
export type AssetPropertyValuesData = DataPoint<Primitive>[];

export type AssetPropertyValuesRequestFunctions = {
getAssetPropertyValueHistory?: GetAssetPropertyValueHistoryRequestFunction;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
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,
Variant,
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 =>
Expand All @@ -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');
}
Expand All @@ -47,17 +48,15 @@ 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;
};

/**
* Converts a SiteWise response for data into a data point understood by IoT App Kit.
*/
export const toDataPoint = (
assetPropertyValue: AssetPropertyValue | undefined
): DataPoint | undefined => {
): DataPoint<Primitive> | undefined => {
if (assetPropertyValue == null) {
return undefined;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/react-components/src/utils/getPreciseValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
6 changes: 5 additions & 1 deletion packages/react-components/src/utils/thresholdUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
Loading
Loading