Skip to content

Commit 2f6e29b

Browse files
committed
feat(FR-2667): add ReplicaStatusTag component with tooltip and Storybook story
1 parent 39bdb03 commit 2f6e29b

3 files changed

Lines changed: 333 additions & 0 deletions

File tree

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/**
2+
@license
3+
Copyright (c) 2015-2026 Lablup Inc. All rights reserved.
4+
*/
5+
import ReplicaStatusTag from './ReplicaStatusTag';
6+
import type { Meta, StoryObj } from '@storybook/react-vite';
7+
8+
/**
9+
* ReplicaStatusTag displays the health/lifecycle state of a deployment replica
10+
* using the BAI design system semantic colors and an optional tooltip.
11+
*
12+
* Health states: HEALTHY, UNHEALTHY, DEGRADED, NOT_CHECKED
13+
* Lifecycle states: PROVISIONING, TERMINATING, TERMINATED
14+
*/
15+
const meta: Meta<typeof ReplicaStatusTag> = {
16+
title: 'Components/ReplicaStatusTag',
17+
component: ReplicaStatusTag,
18+
tags: ['autodocs'],
19+
parameters: {
20+
layout: 'centered',
21+
reactDocgen: false,
22+
docs: {
23+
description: {
24+
component: `
25+
**ReplicaStatusTag** visualises the current state of a deployment replica.
26+
27+
## Status → color mapping
28+
29+
| Status | Color | Processing | Notes |
30+
|----------------|-------------|------------|--------------------------------------------|
31+
| HEALTHY | success | no | Replica is healthy and serving traffic. |
32+
| UNHEALTHY | error | no | Replica confirmed unhealthy. |
33+
| DEGRADED | warning | no | Health checker cannot reach replica. |
34+
| NOT_CHECKED | (outline) | no | Within initial delay period. |
35+
| PROVISIONING | info | yes | Replica is being provisioned. |
36+
| TERMINATING | warning | yes | Replica is being terminated. |
37+
| TERMINATED | default | no | Replica has been terminated. |
38+
39+
When \`showTooltip\` is \`true\` (default), a tooltip explaining the state is
40+
rendered on hover. Set \`showTooltip={false}\` to render just the badge.
41+
`,
42+
},
43+
},
44+
},
45+
argTypes: {
46+
status: {
47+
control: { type: 'select' },
48+
options: [
49+
'HEALTHY',
50+
'UNHEALTHY',
51+
'DEGRADED',
52+
'NOT_CHECKED',
53+
'PROVISIONING',
54+
'TERMINATING',
55+
'TERMINATED',
56+
],
57+
description: 'Replica health or lifecycle state.',
58+
},
59+
showTooltip: {
60+
control: { type: 'boolean' },
61+
description:
62+
'Whether to wrap the badge in a tooltip explaining the state.',
63+
table: {
64+
type: { summary: 'boolean' },
65+
defaultValue: { summary: 'true' },
66+
},
67+
},
68+
},
69+
};
70+
71+
export default meta;
72+
type Story = StoryObj<typeof ReplicaStatusTag>;
73+
74+
/**
75+
* HEALTHY — success (green) dot with tooltip.
76+
*/
77+
export const Healthy: Story = {
78+
name: 'Basic',
79+
args: {
80+
status: 'HEALTHY',
81+
},
82+
parameters: {
83+
docs: {
84+
description: {
85+
story:
86+
'Healthy replica: success (green) dot with an explanatory tooltip on hover.',
87+
},
88+
},
89+
},
90+
};
91+
92+
/**
93+
* UNHEALTHY — error (red) dot.
94+
*/
95+
export const Unhealthy: Story = {
96+
args: {
97+
status: 'UNHEALTHY',
98+
},
99+
parameters: {
100+
docs: {
101+
description: {
102+
story:
103+
'Unhealthy replica after consecutive health check failures. Excluded from the Active Pool.',
104+
},
105+
},
106+
},
107+
};
108+
109+
/**
110+
* DEGRADED — warning (orange) dot.
111+
*/
112+
export const Degraded: Story = {
113+
args: {
114+
status: 'DEGRADED',
115+
},
116+
parameters: {
117+
docs: {
118+
description: {
119+
story:
120+
'Degraded replica. The health checker cannot reach this replica and it is temporarily excluded from the Active Pool.',
121+
},
122+
},
123+
},
124+
};
125+
126+
/**
127+
* NOT_CHECKED — outline-only dot indicating an unknown/indeterminate state.
128+
*/
129+
export const NotChecked: Story = {
130+
args: {
131+
status: 'NOT_CHECKED',
132+
},
133+
parameters: {
134+
docs: {
135+
description: {
136+
story:
137+
'Replica within the initial delay period, awaiting the first health check. Renders as an outline-only dot to indicate an indeterminate state.',
138+
},
139+
},
140+
},
141+
};
142+
143+
/**
144+
* PROVISIONING — info (blue) dot with processing animation.
145+
*/
146+
export const Provisioning: Story = {
147+
args: {
148+
status: 'PROVISIONING',
149+
},
150+
parameters: {
151+
docs: {
152+
description: {
153+
story:
154+
'Replica is being provisioned. Uses info (blue) color with a ripple animation to signal an in-progress transition.',
155+
},
156+
},
157+
},
158+
};
159+
160+
/**
161+
* TERMINATING — warning (orange) dot with processing animation.
162+
*/
163+
export const Terminating: Story = {
164+
args: {
165+
status: 'TERMINATING',
166+
},
167+
parameters: {
168+
docs: {
169+
description: {
170+
story:
171+
'Replica is being terminated. Uses warning color with a ripple animation.',
172+
},
173+
},
174+
},
175+
};
176+
177+
/**
178+
* TERMINATED — default (grey) dot.
179+
*/
180+
export const Terminated: Story = {
181+
args: {
182+
status: 'TERMINATED',
183+
},
184+
parameters: {
185+
docs: {
186+
description: {
187+
story:
188+
'Replica has been terminated. Rendered with the neutral default (grey) color.',
189+
},
190+
},
191+
},
192+
};
193+
194+
/**
195+
* Same HEALTHY badge but with the tooltip disabled.
196+
*/
197+
export const WithoutTooltip: Story = {
198+
args: {
199+
status: 'HEALTHY',
200+
showTooltip: false,
201+
},
202+
parameters: {
203+
docs: {
204+
description: {
205+
story:
206+
'Renders the badge without the explanatory tooltip. Useful in dense tables where the status column header already describes the state.',
207+
},
208+
},
209+
},
210+
};
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/**
2+
@license
3+
Copyright (c) 2015-2026 Lablup Inc. All rights reserved.
4+
*/
5+
import { BadgeProps, Tooltip } from 'antd';
6+
import { BAIBadge, SemanticColor } from 'backend.ai-ui';
7+
import React from 'react';
8+
import { useTranslation } from 'react-i18next';
9+
10+
export type ReplicaStatus =
11+
| 'HEALTHY'
12+
| 'UNHEALTHY'
13+
| 'DEGRADED'
14+
| 'NOT_CHECKED'
15+
| 'PROVISIONING'
16+
| 'TERMINATING'
17+
| 'TERMINATED';
18+
19+
export interface ReplicaStatusTagProps extends Omit<
20+
BadgeProps,
21+
'color' | 'status'
22+
> {
23+
/**
24+
* Replica health/lifecycle state.
25+
* Health states: `HEALTHY`, `UNHEALTHY`, `DEGRADED`, `NOT_CHECKED`.
26+
* Lifecycle states: `PROVISIONING`, `TERMINATING`, `TERMINATED`.
27+
*/
28+
status: ReplicaStatus;
29+
/**
30+
* When true, wraps the badge in a tooltip explaining the state.
31+
* @default true
32+
*/
33+
showTooltip?: boolean;
34+
}
35+
36+
/**
37+
* Maps each replica status to a semantic color from the BAI design system.
38+
* `NOT_CHECKED` intentionally maps to `undefined` so `BAIBadge` renders an
39+
* outline-only (border) dot, matching the "unknown/indeterminate" convention.
40+
*/
41+
const replicaStatusSemanticMap: Record<
42+
ReplicaStatus,
43+
SemanticColor | undefined
44+
> = {
45+
HEALTHY: 'success',
46+
UNHEALTHY: 'error',
47+
DEGRADED: 'warning',
48+
NOT_CHECKED: undefined,
49+
PROVISIONING: 'info',
50+
TERMINATING: 'warning',
51+
TERMINATED: 'default',
52+
};
53+
54+
/**
55+
* Statuses that should render with a ripple/processing animation on the dot
56+
* to convey an in-progress lifecycle transition.
57+
*/
58+
const processingStatuses: ReadonlySet<ReplicaStatus> = new Set([
59+
'PROVISIONING',
60+
'TERMINATING',
61+
]);
62+
63+
/**
64+
* i18n key suffix (PascalCase) for each replica status. Matches the keys
65+
* registered under `replicaStatus.*` and `replicaStatus.tooltip.*` in
66+
* `resources/i18n/en.json` (see FR-2666).
67+
*/
68+
const replicaStatusI18nKey: Record<ReplicaStatus, string> = {
69+
HEALTHY: 'Healthy',
70+
UNHEALTHY: 'Unhealthy',
71+
DEGRADED: 'Degraded',
72+
NOT_CHECKED: 'NotChecked',
73+
PROVISIONING: 'Provisioning',
74+
TERMINATING: 'Terminating',
75+
TERMINATED: 'Terminated',
76+
};
77+
78+
/**
79+
* ReplicaStatusTag - Displays the health/lifecycle state of a deployment replica.
80+
*
81+
* Renders a `BAIBadge` whose dot color and processing animation map to the
82+
* new replica health state machine used by the Endpoint Deployment UI
83+
* (FR-1368 / FR-2658). When `showTooltip` is true (default), the badge is
84+
* wrapped in a `Tooltip` that explains the state to the user.
85+
*/
86+
const ReplicaStatusTag: React.FC<ReplicaStatusTagProps> = ({
87+
status,
88+
showTooltip = true,
89+
...badgeProps
90+
}) => {
91+
'use memo';
92+
const { t } = useTranslation();
93+
94+
const color = replicaStatusSemanticMap[status];
95+
const processing = processingStatuses.has(status);
96+
const i18nKey = replicaStatusI18nKey[status];
97+
const label = t(`replicaStatus.${i18nKey}`);
98+
const tooltipTitle = showTooltip
99+
? t(`replicaStatus.tooltip.${i18nKey}`, { defaultValue: '' })
100+
: undefined;
101+
102+
const badge = (
103+
<BAIBadge
104+
{...badgeProps}
105+
color={color}
106+
processing={processing}
107+
text={label}
108+
/>
109+
);
110+
111+
if (!showTooltip || !tooltipTitle) {
112+
return badge;
113+
}
114+
115+
return (
116+
<Tooltip title={tooltipTitle}>
117+
<span>{badge}</span>
118+
</Tooltip>
119+
);
120+
};
121+
122+
export default ReplicaStatusTag;

react/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
},
2525
"include": ["src", "../packages/backend.ai-ui/src"],
2626
"exclude": [
27+
"src/**/*.stories.*",
2728
"../packages/backend.ai-ui/src/**/*.test.*",
2829
"../packages/backend.ai-ui/src/**/*.stories.*",
2930
"../packages/backend.ai-ui/src/**/*.spec.*"

0 commit comments

Comments
 (0)