LCOV - code coverage report
Current view: top level - dashql-app/src/ui/foundations - status_indicator.tsx Coverage Total Hit
Test: DashQL 915828b Lines: 100.0 % 173 173
Test Date: 2026-11-06 19:30:32

            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 : };
        

Generated by: LCOV version 2.0-1