mirror of
https://github.com/apache/superset.git
synced 2026-05-11 10:55:43 +00:00
173 lines
4.7 KiB
TypeScript
173 lines
4.7 KiB
TypeScript
/**
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
import { FC, useMemo, useRef, useEffect, useState } from 'react';
|
|
import { css, useTheme } from '@apache-superset/core/theme';
|
|
import { AutoRefreshStatus } from '../../types/autoRefresh';
|
|
|
|
export interface StatusIndicatorDotProps {
|
|
/** Current status to display */
|
|
status: AutoRefreshStatus;
|
|
/** Size of the dot in pixels */
|
|
size?: number;
|
|
}
|
|
|
|
/**
|
|
* Status indicator configuration mapping.
|
|
*
|
|
* - Green dot: Refreshed on schedule
|
|
* - Blue dot: Fetching data or waiting for first refresh
|
|
* - Yellow/warning dot: Delayed
|
|
* - Red dot: Error
|
|
* - White dot: Paused
|
|
*/
|
|
interface StatusConfig {
|
|
color: string;
|
|
needsBorder: boolean;
|
|
outlineColor?: string;
|
|
}
|
|
|
|
export const getStatusConfig = (
|
|
theme: ReturnType<typeof useTheme>,
|
|
status: AutoRefreshStatus,
|
|
): StatusConfig => {
|
|
switch (status) {
|
|
case AutoRefreshStatus.Success:
|
|
return {
|
|
color: theme.colorSuccess,
|
|
needsBorder: false,
|
|
};
|
|
case AutoRefreshStatus.Idle:
|
|
return {
|
|
color: theme.colorInfo,
|
|
needsBorder: false,
|
|
};
|
|
case AutoRefreshStatus.Fetching:
|
|
return {
|
|
color: theme.colorInfo,
|
|
needsBorder: false,
|
|
};
|
|
case AutoRefreshStatus.Delayed:
|
|
return {
|
|
color: theme.colorWarning,
|
|
needsBorder: false,
|
|
};
|
|
case AutoRefreshStatus.Error:
|
|
return {
|
|
color: theme.colorError,
|
|
needsBorder: false,
|
|
};
|
|
case AutoRefreshStatus.Paused:
|
|
return {
|
|
color: theme.colorBgContainer,
|
|
needsBorder: true,
|
|
outlineColor: 'currentColor',
|
|
};
|
|
default:
|
|
return {
|
|
color: theme.colorTextSecondary,
|
|
needsBorder: false,
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* A colored dot indicator that shows the auto-refresh status.
|
|
*
|
|
* Uses CSS transitions to prevent flickering between states.
|
|
* The color change is animated smoothly rather than instantly.
|
|
*/
|
|
export const StatusIndicatorDot: FC<StatusIndicatorDotProps> = ({
|
|
status,
|
|
size = 10,
|
|
}) => {
|
|
const theme = useTheme();
|
|
|
|
// Debounce rapid status changes to prevent flickering
|
|
const [displayStatus, setDisplayStatus] = useState(status);
|
|
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
|
|
useEffect(() => {
|
|
// Clear any pending timer
|
|
if (timerRef.current) {
|
|
clearTimeout(timerRef.current);
|
|
}
|
|
|
|
// For fetching state, update immediately to show user something is happening
|
|
if (status === AutoRefreshStatus.Fetching) {
|
|
setDisplayStatus(status);
|
|
} else {
|
|
// For other states, debounce to prevent flickering
|
|
timerRef.current = setTimeout(() => {
|
|
setDisplayStatus(status);
|
|
}, 100);
|
|
}
|
|
|
|
return () => {
|
|
if (timerRef.current) {
|
|
clearTimeout(timerRef.current);
|
|
}
|
|
};
|
|
}, [status]);
|
|
|
|
const statusConfig = useMemo(
|
|
() => getStatusConfig(theme, displayStatus),
|
|
[theme, displayStatus],
|
|
);
|
|
|
|
const dotStyles = useMemo(
|
|
() => css`
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: ${size}px;
|
|
height: ${size}px;
|
|
border-radius: 50%;
|
|
color: ${theme.colorTextSecondary};
|
|
background-color: ${statusConfig.color};
|
|
transition:
|
|
background-color ${theme.motionDurationMid} ease-in-out,
|
|
border-color ${theme.motionDurationMid} ease-in-out;
|
|
border: ${statusConfig.needsBorder ? '1px solid' : 'none'};
|
|
border-color: ${statusConfig.needsBorder
|
|
? statusConfig.outlineColor
|
|
: 'transparent'};
|
|
box-shadow: ${statusConfig.needsBorder
|
|
? 'none'
|
|
: `0 0 0 2px ${theme.colorBgContainer}`};
|
|
margin-left: ${theme.marginXS}px;
|
|
margin-right: ${theme.marginXS}px;
|
|
cursor: help;
|
|
`,
|
|
[statusConfig, size, theme],
|
|
);
|
|
|
|
return (
|
|
<span
|
|
css={dotStyles}
|
|
role="status"
|
|
aria-label={`Auto-refresh status: ${displayStatus}`}
|
|
data-test="status-indicator-dot"
|
|
data-status={displayStatus}
|
|
data-size={size}
|
|
/>
|
|
);
|
|
};
|
|
|
|
export default StatusIndicatorDot;
|