Skip to content

Commit

Permalink
feat(alarms): integrate alarms into KPI widget
Browse files Browse the repository at this point in the history
  • Loading branch information
ssjagad authored and corteggiano committed Sep 27, 2024
1 parent 80bc014 commit 95c2dde
Show file tree
Hide file tree
Showing 11 changed files with 348 additions and 52 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.alarm-state-text {
display: flex;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import React from 'react';
import Box from '@cloudscape-design/components/box';
import Icon from '@cloudscape-design/components/icon';
import {
colorBorderStatusError,
colorBorderStatusSuccess,
colorBorderStatusWarning,
colorTextStatusInactive,
spaceScaledXxs,
} from '@cloudscape-design/design-tokens';

import './alarm-state-text.css';
import { PascalCaseStateName } from '../../hooks/useAlarms/transformers';

type AlarmStateTextOptions = {
state?: PascalCaseStateName;
inheritFontColor?: boolean;
};

export const AlarmStateText = ({
state,
inheritFontColor,
}: AlarmStateTextOptions) => {
let icon = null;
let text = null;
let styles: React.CSSProperties = {
gap: spaceScaledXxs,
textDecoration: 'none',
};

let borderBottom = '1px dashed ';

switch (state) {
case 'Active':
icon = (
<Icon
name='notification'
variant={inheritFontColor ? 'normal' : 'error'}
/>
);
text = (
<Box color={inheritFontColor ? 'inherit' : 'text-status-error'}>
Active alarm
</Box>
);
borderBottom += inheritFontColor ? 'inherit' : colorBorderStatusError;
styles = {
...styles,
borderBottom: borderBottom,
};
break;
case 'Normal':
icon = (
<Icon
name='status-positive'
variant={inheritFontColor ? 'normal' : 'success'}
/>
);
text = (
<Box color={inheritFontColor ? 'inherit' : 'text-status-success'}>
Normal
</Box>
);
borderBottom += inheritFontColor ? 'inherit' : colorBorderStatusSuccess;
styles = {
...styles,
borderBottom: borderBottom,
};
break;
case 'Latched':
icon = (
<Icon
name='status-warning'
variant={inheritFontColor ? 'normal' : 'warning'}
/>
);
text = (
<Box color={inheritFontColor ? 'inherit' : 'text-status-warning'}>
Latched alarm
</Box>
);
borderBottom += inheritFontColor ? 'inherit' : colorBorderStatusWarning;
styles = {
...styles,
borderBottom: borderBottom,
};
break;
case 'Acknowledged':
icon = (
<Icon
name='status-in-progress'
variant={inheritFontColor ? 'normal' : 'subtle'}
/>
);
text = (
<Box color={inheritFontColor ? 'inherit' : 'text-status-inactive'}>
Acknowledged alarm
</Box>
);
borderBottom += inheritFontColor ? 'inherit' : colorTextStatusInactive;
styles = {
...styles,
borderBottom: borderBottom,
};
break;
case 'Disabled':
icon = (
<Icon
name='status-stopped'
variant={inheritFontColor ? 'normal' : 'subtle'}
/>
);
text = (
<Box color={inheritFontColor ? 'inherit' : 'text-status-inactive'}>
Disabled alarm
</Box>
);
borderBottom += inheritFontColor ? 'inherit' : colorTextStatusInactive;
styles = {
...styles,
borderBottom: borderBottom,
};
break;
case 'SnoozeDisabled':
icon = (
<Icon
name='status-pending'
variant={inheritFontColor ? 'normal' : 'subtle'}
/>
);
text = (
<Box color={inheritFontColor ? 'inherit' : 'text-status-inactive'}>
Snoozed alarm
</Box>
);
borderBottom += inheritFontColor ? 'inherit' : colorTextStatusInactive;
styles = {
...styles,
borderBottom: borderBottom,
};
break;
}

return (
<div
data-testid='alarm-state-text'
className='alarm-state alarm-state-text'
>
<div className='alarm-state-text' style={styles}>
{icon}
{text}
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,60 +1,48 @@
import React from 'react';
import { Quality } from '@aws-sdk/client-iotsitewise';
import Box from '@cloudscape-design/components/box';
import Icon from '@cloudscape-design/components/icon';
import {
colorBorderStatusError,
colorBorderStatusWarning,
spaceScaledXxs,
} from '@cloudscape-design/design-tokens';
import { spaceScaledXxs } from '@cloudscape-design/design-tokens';

import './data-quality-text.css';

type DataQualityTextOptions = {
quality?: Quality;
inheritFontColor?: boolean;
};

export const DataQualityText = ({ quality }: DataQualityTextOptions) => {
export const DataQualityText = ({
quality,
inheritFontColor,
}: DataQualityTextOptions) => {
// Don't show any special UX for good quality points
if (!quality || quality === 'GOOD') return null;

let icon = null;
let text = null;
let styles: React.CSSProperties = {
const styles: React.CSSProperties = {
gap: spaceScaledXxs,
textDecoration: 'none',
};

let borderBottom = '1px dashed ';

switch (quality) {
case 'BAD':
icon = <Icon name='status-negative' variant='error' />;
text = <Box color='text-status-error'>Bad Quality</Box>;
borderBottom += colorBorderStatusError;
styles = {
...styles,
borderBottom: borderBottom,
};
text = (
<Box color={inheritFontColor ? 'inherit' : 'text-label'}>
Bad Quality
</Box>
);
break;
case 'UNCERTAIN':
icon = <Icon name='status-warning' variant='warning' />;
text = <Box color='text-status-warning'>Uncertain Quality</Box>;
borderBottom += colorBorderStatusWarning;
styles = {
...styles,
borderBottom: borderBottom,
};
text = (
<Box color={inheritFontColor ? 'inherit' : 'text-label'}>
Uncertain Quality
</Box>
);
break;
}

return (
<div
data-testid='data-quality-text'
className='data-quality data-quality-text'
>
<div data-testid='data-quality-text'>
<div className='data-quality-text' style={styles}>
{icon}
{text}
</div>
</div>
Expand Down
5 changes: 2 additions & 3 deletions packages/react-components/src/components/kpi/kpi.css
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,10 @@

.kpi .timestamp-border {
height: 2px;
background-color: #e9ebed;
}

.kpi .timestamp {
padding: 6px;
padding: 8px;
height: 18px;
}

Expand All @@ -54,5 +53,5 @@
}

.aggregation {
padding: 6px;
padding: 8px;
}
52 changes: 39 additions & 13 deletions packages/react-components/src/components/kpi/kpi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import type {
import type { KPISettings } from './types';
import { KpiBase } from './kpiBase';
import type { ComponentQuery } from '../../common/chartTypes';
import { getTimeSeriesQueries } from '../../utils/queries';
import { getAlarmQueries, getTimeSeriesQueries } from '../../utils/queries';
import { convertAlarmQueryToAlarmRequest } from '../../queries/utils/convertAlarmQueryToAlarmRequest';
import { useAlarms } from '../../hooks/useAlarms';
import { buildTransformAlarmForSingleQueryWidgets } from '../../utils/buildTransformAlarmForSingleQueryWidgets';

export const KPI = ({
query,
Expand All @@ -31,23 +34,47 @@ export const KPI = ({
significantDigits?: number;
timeZone?: string;
}) => {
const { viewport } = useViewport();
const utilizedViewport = passedInViewport || viewport || DEFAULT_VIEWPORT; // explicitly passed in viewport overrides viewport group

const alarmQueries = getAlarmQueries([query]);
const timeSeriesQueries = getTimeSeriesQueries([query]);

const mapAlarmQueriesToRequests = alarmQueries.flatMap((query) =>
convertAlarmQueryToAlarmRequest(query)
);

const transformedAlarm = useAlarms({
iotSiteWiseClient: alarmQueries.at(0)?.iotSiteWiseClient,
iotEventsClient: alarmQueries.at(0)?.iotEventsClient,
requests: mapAlarmQueriesToRequests,
viewport: utilizedViewport,
settings: {
fetchThresholds: true,
refreshRate: alarmQueries.at(0)?.query.requestSettings?.refreshRate,
},
transform: buildTransformAlarmForSingleQueryWidgets({
iotSiteWiseClient: alarmQueries.at(0)?.iotSiteWiseClient,
iotEventsClient: alarmQueries.at(0)?.iotEventsClient,
}),
})
.filter((alarm) => !!alarm)
.at(0);

const { dataStreams, thresholds: queryThresholds } = useTimeSeriesData({
viewport: passedInViewport,
queries: getTimeSeriesQueries([query]),
queries: transformedAlarm
? transformedAlarm.timeSeriesDataQueries
: timeSeriesQueries,
settings: {
fetchMostRecentBeforeEnd: true,
},
styles,
});
const { viewport } = useViewport();

const utilizedViewport = passedInViewport || viewport || DEFAULT_VIEWPORT; // explicitly passed in viewport overrides viewport group

const {
propertyPoint,
alarmPoint,
propertyThreshold,
alarmStream,
propertyStream,
propertyResolution,
} = widgetPropertiesFromInputs({
Expand All @@ -56,17 +83,16 @@ export const KPI = ({
viewport: utilizedViewport,
});

const name = propertyStream?.name || alarmStream?.name;
const unit = propertyStream?.unit || alarmStream?.unit;
const name = propertyStream?.name;
const unit = propertyStream?.unit;
const backgroundColor = settings?.color || settings?.backgroundColor;
const isLoading =
alarmStream?.isLoading || propertyStream?.isLoading || false;
const error = alarmStream?.error || propertyStream?.error;
const isLoading = propertyStream?.isLoading || false;
const error = propertyStream?.error;

return (
<KpiBase
propertyPoint={propertyPoint}
alarmPoint={alarmPoint}
alarmState={transformedAlarm?.state}
settings={{ ...settings, backgroundColor }}
propertyThreshold={propertyThreshold}
aggregationType={dataStreams[0]?.aggregationType}
Expand Down
Loading

0 comments on commit 95c2dde

Please sign in to comment.