Line data Source code
1 : import * as React from 'react';
2 :
3 : import { classNames } from '../../utils/classnames.js';
4 :
5 : import * as styles from './status_indicator.module.css';
6 : import { ProgressCounter } from '../../utils/progress.js';
7 :
8 1 : export enum IndicatorStatus {
9 1 : None,
10 1 : Running,
11 1 : Failed,
12 1 : Succeeded,
13 1 : Blocked,
14 1 : Skip,
15 1 : }
16 :
17 : // The status colors are theme tokens of @ankoh/dashql-ui/styles/tokens.css, flavors can override them
18 1 : export const STATUS_COLOR_IN_PROGRESS = 'var(--color_attention_fg)';
19 1 : export const STATUS_COLOR_SUCCESS = 'var(--color_success_fg)';
20 1 : export const STATUS_COLOR_FAILED = 'var(--color_danger_fg)';
21 1 : export const STATUS_COLOR_OTHER = 'var(--color_neutral_fg)';
22 :
23 1 : function getIndicatorColor(status: IndicatorStatus): string {
24 1 : switch (status) {
25 1 : case IndicatorStatus.Running:
26 1 : return STATUS_COLOR_IN_PROGRESS;
27 1 : case IndicatorStatus.Succeeded:
28 1 : return STATUS_COLOR_SUCCESS;
29 1 : case IndicatorStatus.Failed:
30 1 : return STATUS_COLOR_FAILED;
31 1 : default:
32 1 : return STATUS_COLOR_OTHER;
33 1 : }
34 1 : }
35 :
36 1 : export function getStatusFromProgressCounter(counter: ProgressCounter): IndicatorStatus {
37 1 : if (counter.total == null) {
38 1 : return IndicatorStatus.None;
39 : }
40 1 : if (counter.total == 0) {
41 1 : return IndicatorStatus.Skip;
42 : }
43 1 : if (counter.started == 0) {
44 1 : return IndicatorStatus.None;
45 : }
46 1 : if ((counter.failed + counter.skipped + counter.succeeded) >= counter.total) {
47 1 : let status = [
48 1 : [IndicatorStatus.Failed, counter.failed],
49 1 : [IndicatorStatus.Succeeded, counter.succeeded],
50 1 : [IndicatorStatus.Skip, counter.skipped],
51 1 : ];
52 1 : status.sort((l, r) => l[1] - r[1]);
53 1 : return status[2][0];
54 1 : }
55 1 : return IndicatorStatus.Running;
56 1 : }
57 :
58 : export function combineIndicatorStatus(l: IndicatorStatus, r: IndicatorStatus): IndicatorStatus {
59 : if (l == r) {
60 : return l;
61 : }
62 : if (l == IndicatorStatus.Running || r == IndicatorStatus.Running) {
63 : return IndicatorStatus.Running;
64 : }
65 : if (l == IndicatorStatus.Failed || r == IndicatorStatus.Failed) {
66 : return IndicatorStatus.Failed;
67 : }
68 : if (l == IndicatorStatus.Blocked || r == IndicatorStatus.Blocked) {
69 : return IndicatorStatus.Blocked;
70 : }
71 : if (l == IndicatorStatus.Succeeded || r == IndicatorStatus.Succeeded) {
72 : return IndicatorStatus.Succeeded;
73 : }
74 : return IndicatorStatus.None;
75 : }
76 :
77 :
78 : export interface StatusIndicatorProps {
79 : className?: string;
80 : status: IndicatorStatus;
81 : width?: string;
82 : height?: string;
83 : fill?: string;
84 : }
85 :
86 1 : export const StatusIndicator: React.FC<StatusIndicatorProps> = (props: StatusIndicatorProps) => {
87 : // SVG presentation attributes cannot resolve var(), the svg therefore takes the color and the shapes paint with currentColor
88 1 : const fill = props.fill || getIndicatorColor(props.status);
89 1 : let element = <div />;
90 1 : switch (props.status) {
91 1 : case IndicatorStatus.Running:
92 1 : element = (
93 1 : <svg
94 1 : className={classNames(props.className)}
95 1 : style={{ color: fill }}
96 1 : width={props.width || '24px'}
97 1 : height={props.height || '24px'}
98 1 : viewBox="-8 -8 16 16"
99 1 : fill="none"
100 1 : stroke="currentColor"
101 1 : strokeWidth="2"
102 : >
103 1 : <g fill="none" fillRule="evenodd">
104 1 : <circle cx="0" cy="0" r="7" opacity=".5"></circle>
105 1 : <circle cx="0" cy="0" r="4" strokeWidth="0" fill="currentColor"></circle>
106 1 : <circle cx="0" cy="0" r="7" strokeDasharray="12, 88" className={styles.status_running_spinner} />
107 1 : </g>
108 1 : </svg>
109 : );
110 1 : break;
111 1 : case IndicatorStatus.Blocked:
112 1 : case IndicatorStatus.None:
113 1 : element = (
114 1 : <svg
115 1 : className={classNames(props.className)}
116 1 : style={{ color: fill }}
117 1 : width={props.width || '24px'}
118 1 : height={props.height || '24px'}
119 1 : viewBox="-8 -8 16 16"
120 1 : fill="none"
121 1 : stroke="currentColor"
122 1 : strokeWidth="2"
123 : >
124 1 : <g fill="none" fillRule="evenodd">
125 1 : <circle cx="0" cy="0" r="4" opacity=".5" strokeWidth="0" fill="currentColor"></circle>
126 1 : </g>
127 1 : </svg>
128 : );
129 1 : break;
130 1 : case IndicatorStatus.Failed:
131 1 : element = (
132 1 : <svg
133 1 : className={classNames(props.className)}
134 1 : style={{ color: fill }}
135 1 : width={props.width || '24px'}
136 1 : height={props.height || '24px'}
137 1 : viewBox="-0.5 -0.5 17 17"
138 1 : fill="none"
139 1 : strokeWidth="2"
140 : >
141 1 : <g fill="none" fillRule="evenodd">
142 1 : <path
143 1 : fill="currentColor"
144 1 : fillRule="evenodd"
145 1 : d="M2.343 13.657A8 8 0 1113.657 2.343 8 8 0 012.343 13.657zM6.03 4.97a.75.75 0 00-1.06 1.06L6.94 8 4.97 9.97a.75.75 0 101.06 1.06L8 9.06l1.97 1.97a.75.75 0 101.06-1.06L9.06 8l1.97-1.97a.75.75 0 10-1.06-1.06L8 6.94 6.03 4.97z"
146 1 : ></path>
147 1 : </g>
148 1 : </svg>
149 : );
150 1 : break;
151 1 : case IndicatorStatus.Succeeded:
152 1 : element = (
153 1 : <svg
154 1 : className={classNames(props.className)}
155 1 : style={{ color: fill }}
156 1 : width={props.width || '24px'}
157 1 : height={props.height || '24px'}
158 1 : viewBox="-0.5 -0.5 17 17"
159 1 : fill="none"
160 1 : strokeWidth="2"
161 : >
162 1 : <g fill="none" fillRule="evenodd">
163 1 : <path
164 1 : fill="currentColor"
165 1 : fillRule="evenodd"
166 1 : d="M8 16A8 8 0 108 0a8 8 0 000 16zm3.78-9.72a.75.75 0 00-1.06-1.06L6.75 9.19 5.28 7.72a.75.75 0 00-1.06 1.06l2 2a.75.75 0 001.06 0l4.5-4.5z"
167 1 : ></path>
168 1 : </g>
169 1 : </svg>
170 : );
171 1 : break;
172 1 : case IndicatorStatus.Skip:
173 1 : element = (
174 1 : <svg
175 1 : className={classNames(props.className)}
176 1 : style={{ color: fill }}
177 1 : width={props.width || '24px'}
178 1 : height={props.height || '24px'}
179 1 : viewBox="-0.5 -0.5 17 17"
180 1 : fill="none"
181 1 : strokeWidth="2"
182 : >
183 1 : <g fill="none" fillRule="evenodd">
184 1 : <path
185 1 : fill="currentColor"
186 1 : fillRule="evenodd"
187 1 : d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm11.333-2.167a.825.825 0 0 0-1.166-1.166l-5.5 5.5a.825.825 0 0 0 1.166 1.166Z"
188 1 : ></path>
189 1 : </g>
190 1 : </svg>
191 : );
192 : break;
193 1 : }
194 1 : return element;
195 1 : };
196 :
197 : export interface BinaryStatusIndicatorProps {
198 : className?: string;
199 : online: boolean;
200 : width?: string;
201 : height?: string;
202 : fill?: string;
203 : }
204 :
205 1 : export const BinaryStatusIndicator: React.FC<BinaryStatusIndicatorProps> = (props: BinaryStatusIndicatorProps) => {
206 1 : const fill = props.fill || (props.online ? STATUS_COLOR_SUCCESS : STATUS_COLOR_OTHER);
207 1 : return (
208 1 : <svg
209 1 : className={classNames(props.className)}
210 1 : style={{ color: fill }}
211 1 : width={props.width || '24px'}
212 1 : height={props.height || '24px'}
213 1 : viewBox="-8 -8 16 16"
214 1 : fill="none"
215 1 : stroke="currentColor"
216 1 : strokeWidth="2"
217 : >
218 1 : <g fill="none" fillRule="evenodd">
219 1 : {props.online && (
220 1 : <circle
221 1 : cx="0"
222 1 : cy="0"
223 1 : r="4"
224 1 : strokeWidth="0"
225 1 : fill="currentColor"
226 1 : className={styles.status_binary_pulse}
227 1 : />
228 : )}
229 1 : <circle
230 1 : cx="0"
231 1 : cy="0"
232 1 : r="4"
233 1 : opacity=".5"
234 1 : strokeWidth="0"
235 1 : fill="currentColor"
236 1 : />
237 1 : </g>
238 1 : </svg>
239 : );
240 1 : };
|