Skip to content

Commit

Permalink
feat: add thresholds assistant and make use of base threshold for lights
Browse files Browse the repository at this point in the history
  • Loading branch information
jackw committed Feb 5, 2024
1 parent e2b29cc commit b2644b3
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 66 deletions.
70 changes: 13 additions & 57 deletions provisioning/dashboards/panels.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,6 @@
"color": "red",
"value": null
},
{
"color": "red",
"value": 0
},
{
"color": "#EAB839",
"value": 33
Expand Down Expand Up @@ -105,17 +101,13 @@
"color": "red",
"value": null
},
{
"color": "red",
"value": 0
},
{
"color": "orange",
"value": 33
"value": 0
},
{
"color": "green",
"value": 66
"value": 33
}
]
}
Expand Down Expand Up @@ -168,10 +160,6 @@
"color": "red",
"value": null
},
{
"color": "red",
"value": 0
},
{
"color": "orange",
"value": 33
Expand Down Expand Up @@ -227,13 +215,9 @@
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 0
"value": null
},
{
"color": "#EAB839",
Expand Down Expand Up @@ -297,10 +281,6 @@
"color": "red",
"value": null
},
{
"color": "red",
"value": 0
},
{
"color": "#EAB839",
"value": 50
Expand Down Expand Up @@ -361,10 +341,6 @@
"color": "red",
"value": null
},
{
"color": "red",
"value": 0
},
{
"color": "#EAB839",
"value": 50
Expand Down Expand Up @@ -579,7 +555,8 @@
"mode": "percentage",
"steps": [
{
"color": "red"
"color": "red",
"value": null
},
{
"color": "red",
Expand Down Expand Up @@ -609,12 +586,9 @@
"value": {
"mode": "percentage",
"steps": [
{
"color": "red"
},
{
"color": "super-light-red",
"value": 0
"value": null
},
{
"color": "red",
Expand All @@ -640,12 +614,9 @@
"value": {
"mode": "percentage",
"steps": [
{
"color": "red"
},
{
"color": "super-light-orange",
"value": 0
"value": null
},
{
"color": "orange",
Expand All @@ -671,12 +642,9 @@
"value": {
"mode": "percentage",
"steps": [
{
"color": "red"
},
{
"color": "super-light-yellow",
"value": 0
"value": null
},
{
"color": "yellow",
Expand All @@ -702,12 +670,9 @@
"value": {
"mode": "percentage",
"steps": [
{
"color": "red"
},
{
"color": "super-light-green",
"value": 0
"value": null
},
{
"color": "green",
Expand All @@ -733,12 +698,9 @@
"value": {
"mode": "percentage",
"steps": [
{
"color": "red"
},
{
"color": "super-light-blue",
"value": 0
"value": null
},
{
"color": "blue",
Expand All @@ -764,12 +726,9 @@
"value": {
"mode": "percentage",
"steps": [
{
"color": "red"
},
{
"color": "super-light-purple",
"value": 0
"value": null
},
{
"color": "purple",
Expand Down Expand Up @@ -841,12 +800,9 @@
"thresholds": {
"mode": "percentage",
"steps": [
{
"color": "red"
},
{
"color": "red",
"value": 0
"value": null
},
{
"color": "#EAB839",
Expand Down Expand Up @@ -931,6 +887,6 @@
"timezone": "",
"title": "Traffic Lights Example",
"uid": "O4tc_E6Gz",
"version": 8,
"version": 4,
"weekStart": ""
}
88 changes: 88 additions & 0 deletions src/components/ThresholdsAssistant.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { css, cx } from '@emotion/css';
import { GrafanaTheme2, ThresholdsConfig } from '@grafana/data';
import { Alert, Icon, useStyles2 } from '@grafana/ui';
import React from 'react';

export function ThresholdsAssistant({ thresholds }: { thresholds?: ThresholdsConfig }) {
const validSteps = validateThresholds(thresholds!);
const styles = useStyles2(getStyles);
return (
<Alert title="Invalid thresholds" severity="warning" style={{ maxWidth: 500 }}>
<p>Please configure thresholds for all three lights:</p>
<div className={styles.grid}>
<div className={styles.trafficLight} />
{validSteps.map((step, i) => (
<ThresholdFeedback isValid={step.isValid} index={i} />

Check failure on line 15 in src/components/ThresholdsAssistant.tsx

View workflow job for this annotation

GitHub Actions / build

Missing "key" prop for element in iterator
))}
</div>
</Alert>
);
}

const BASE_LIGHT_GRID_AREA = [2, 1, 3, 2];
const BASE_LABEL_GRID_AREA = [2, 2, 3, 3];

function ThresholdFeedback({ isValid, index }: { isValid: boolean; index: number }) {
const iconName = isValid ? 'check' : 'times';
const label = isValid ? 'Threshold configured' : 'Threshold not configured';
const styles = useStyles2(getStyles);
const lightGridArea = BASE_LIGHT_GRID_AREA.map((n, i) => (i % 2 === 0 ? n + index : n)).join(' / ');
const labelGridArea = BASE_LABEL_GRID_AREA.map((n, i) => (i % 2 === 0 ? n + index : n)).join(' / ');
return (
<>
<div style={{ gridArea: lightGridArea }} className={cx(styles.light, isValid ? styles.valid : styles.invalid)}>
<Icon name={iconName} size="lg" />
</div>
<span style={{ gridArea: labelGridArea }} className={styles.label}>
{label}
</span>
</>
);
}

function validateThresholds(thresholds: ThresholdsConfig) {
const { steps } = thresholds;
const validNumberOfSteps = Array(3).fill(null);
const result = validNumberOfSteps.map((_, i) => {
return {
isValid: Boolean(steps[i]),
};
});

return result;
}

const getStyles = (theme: GrafanaTheme2) => ({
grid: css({
display: 'grid',
gridTemplateColumns: '40px 1fr',
gridTemplateRows: '4px repeat(3, 1fr) 4px',
gap: theme.spacing(),
}),
trafficLight: css({
backgroundColor: theme.colors.background.canvas,
borderRadius: 50,
gridArea: '1 / 1 / 6 / 2',
}),
light: css({
alignItems: 'center',
backgroundColor: theme.colors.background.secondary,
borderRadius: 100,
display: 'flex',
justifyContent: 'center',
height: 26,
placeSelf: 'center',
width: 26,
}),
label: css({
display: 'flex',
alignItems: 'center',
fontSize: theme.typography.size.sm,

Check warning on line 80 in src/components/ThresholdsAssistant.tsx

View workflow job for this annotation

GitHub Actions / build

'size' is deprecated. from legacy old theme
}),
valid: css({
backgroundColor: theme.colors.success.main,
}),
invalid: css({
backgroundColor: theme.colors.error.main,
}),
});
9 changes: 5 additions & 4 deletions src/components/TrafficLightPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { DataLinksContextMenu, useTheme2 } from '@grafana/ui';
import { LightsDataResultStatus, useLightsData } from 'hooks/useLightsData';
import { calculateRowsAndColumns } from 'utils';
import { TrafficLight } from './TrafficLight';
import { ThresholdsAssistant } from './ThresholdsAssistant';

interface TrafficLightPanelProps extends PanelProps<TrafficLightOptions> {}

Expand All @@ -23,7 +24,7 @@ export function TrafficLightPanel({
const theme = useTheme2();
const { rows, cols } = calculateRowsAndColumns(width, minLightWidth, data.series.length);
const styles = getStyles({ rows, cols, singleRow, minLightWidth, theme });
const { values, status } = useLightsData({
const { values, status, invalidThresholds } = useLightsData({
fieldConfig,
replaceVariables,
theme,
Expand Down Expand Up @@ -51,7 +52,7 @@ export function TrafficLightPanel({
if (status === LightsDataResultStatus.incorrectThresholds) {
return (
<div style={styles.centeredContent}>
<h4>Thresholds are incorrectly set.</h4>
<ThresholdsAssistant thresholds={invalidThresholds} />
</div>
);
}
Expand All @@ -63,10 +64,10 @@ export function TrafficLightPanel({
height,
}}
>
{/* @ts-ignore TODO: fix up styles. */}
{/* @ts-ignore TODO: fix conditional styles errors. */}
<div style={styles.containerStyle}>
{values.map((light) => (
// @ts-ignore TODO: fix up styles.
// @ts-ignore TODO: fix conditional styles errors.
<div key={light.title} style={styles.itemStyle}>
{light.hasLinks && light.getLinks !== undefined ? (
<DataLinksContextMenu links={light.getLinks} style={{ flexGrow: 1 }}>
Expand Down
15 changes: 10 additions & 5 deletions src/hooks/useLightsData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export type LightsDataValues = {
export type LightsDataResult = {
values: LightsDataValues[];
status: LightsDataResultStatus;
invalidThresholds?: ThresholdsConfig;
};

type UseLightsData = Omit<GetFieldDisplayValuesOptions, 'reduceOptions'> & { sortLights: SortOptions };
Expand All @@ -47,6 +48,8 @@ export function useLightsData(options: UseLightsData): LightsDataResult {

return useMemo(() => {
let status = LightsDataResultStatus.nodata;
let invalidThresholds = undefined;

if (noData(data)) {
return {
values: [
Expand Down Expand Up @@ -88,11 +91,10 @@ export function useLightsData(options: UseLightsData): LightsDataResult {
const thresholdsValid = validateThresholds(displayValue.field.thresholds);
const activeThreshold = getActiveThreshold(displayValue.display.numeric, displayValue.field.thresholds?.steps);
const { title, text, suffix, prefix } = displayValue.display;
const colors = displayValue.field.thresholds?.steps.slice(1).map((threshold, i) => {
const isNegative = displayValue.display.numeric < 0 && i === 0;
const colors = displayValue.field.thresholds?.steps.map((threshold, i) => {
return {
color: theme.visualization.getColorByName(threshold.color),
active: isNegative ? true : threshold.value === activeThreshold.value,
active: threshold.value === activeThreshold.value,
};
});

Expand All @@ -101,6 +103,7 @@ export function useLightsData(options: UseLightsData): LightsDataResult {

if (!thresholdsValid) {
status = LightsDataResultStatus.incorrectThresholds;
invalidThresholds = displayValue.field.thresholds;
} else {
status = LightsDataResultStatus.success;
}
Expand All @@ -119,9 +122,11 @@ export function useLightsData(options: UseLightsData): LightsDataResult {
getLinks: displayValue.getLinks,
};
});

return {
values: sortLights === SortOptions.None ? values : sortByValue(values, sortLights),
status: status,
invalidThresholds,
status,
};
}, [theme, data, fieldConfig, replaceVariables, timeZone, sortLights]);
}
Expand Down Expand Up @@ -167,7 +172,7 @@ function getTrendColor(value: number) {

function validateThresholds(thresholds?: ThresholdsConfig) {
const numberOfSteps = thresholds?.steps.length;
if (!numberOfSteps || numberOfSteps < 4) {
if (!numberOfSteps || numberOfSteps < 3) {
return false;
}

Expand Down

0 comments on commit b2644b3

Please sign in to comment.