Skip to content

Commit d5c0406

Browse files
committed
fix(FR-2494): replace hardcoded style values with design tokens
- marginRight: 4 → token.marginXXS - marginBottom: 12 → token.marginSM - gap: 8 → token.marginXS - Remove lineHeight: '32px', use alignItems: center on flex row instead - capitalize Metric in direction selector and range condition labels
1 parent f23f1d6 commit d5c0406

24 files changed

Lines changed: 741 additions & 201 deletions

react/src/components/AutoScalingRuleEditorModal.tsx

Lines changed: 81 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,23 @@ import { ReloadOutlined } from '@ant-design/icons';
1616
import {
1717
App,
1818
AutoComplete,
19-
Button,
2019
Form,
2120
FormInstance,
2221
InputNumber,
2322
Segmented,
2423
Select,
25-
Spin,
2624
Typography,
25+
theme,
2726
} from 'antd';
2827
import {
28+
BAIButton,
2929
BAIModal,
3030
BAIModalProps,
3131
toLocalId,
3232
useBAILogger,
3333
} from 'backend.ai-ui';
3434
import * as _ from 'lodash-es';
35-
import React, { useRef, useState } from 'react';
35+
import React, { useRef, useState, useTransition } from 'react';
3636
import { useTranslation } from 'react-i18next';
3737
import {
3838
graphql,
@@ -77,18 +77,16 @@ const METRIC_NAMES_MAP: Partial<
7777
};
7878

7979
/**
80-
* Inline preview component that fetches and displays the current Prometheus
81-
* metric value for a selected preset. Uses useLazyLoadQuery with fetchKey
82-
* to support manual refresh without useEffect + setState.
80+
* Inner component: fetches and renders only the metric value text.
81+
* Isolated so that React.Suspense covers just this text node during refresh,
82+
* leaving the "Current value:" label and refresh button always visible.
8383
*/
84-
const PrometheusPresetPreview: React.FC<{
85-
presetGlobalId: string;
86-
}> = ({ presetGlobalId }) => {
84+
const PreviewValue: React.FC<{
85+
presetRawId: string;
86+
fetchKey: number;
87+
}> = ({ presetRawId, fetchKey }) => {
8788
'use memo';
8889
const { t } = useTranslation();
89-
const [fetchKey, setFetchKey] = useState(0);
90-
91-
const presetRawId = toLocalId(presetGlobalId);
9290

9391
const data = useLazyLoadQuery<AutoScalingRuleEditorModalPresetResultQuery>(
9492
graphql`
@@ -125,12 +123,10 @@ const PrometheusPresetPreview: React.FC<{
125123
const results = data.prometheusQueryPresetResult.result;
126124

127125
let displayValue: string | null = null;
128-
if (results.length === 0) {
129-
displayValue = null;
130-
} else if (results.length === 1) {
126+
if (results.length === 1) {
131127
const values = results[0].values;
132128
displayValue = values.length > 0 ? values[values.length - 1].value : null;
133-
} else {
129+
} else if (results.length > 1) {
134130
const firstValues = results[0].values;
135131
const latestValue =
136132
firstValues.length > 0 ? firstValues[firstValues.length - 1].value : null;
@@ -143,20 +139,54 @@ const PrometheusPresetPreview: React.FC<{
143139
: null;
144140
}
145141

142+
return displayValue != null ? (
143+
<Typography.Text strong>{displayValue}</Typography.Text>
144+
) : (
145+
<Typography.Text type="secondary">
146+
{t('autoScalingRule.NoDataAvailable')}
147+
</Typography.Text>
148+
);
149+
};
150+
151+
/**
152+
* Inline preview component for a selected Prometheus preset.
153+
* The label and refresh button are always visible; only the value area
154+
* shows a loading spinner during fetch/refresh.
155+
*/
156+
const PrometheusPresetPreview: React.FC<{
157+
presetGlobalId: string;
158+
}> = ({ presetGlobalId }) => {
159+
'use memo';
160+
const { t } = useTranslation();
161+
const { token } = theme.useToken();
162+
const [fetchKey, setFetchKey] = useState(0);
163+
const [isPending, startTransition] = useTransition();
164+
const presetRawId = toLocalId(presetGlobalId);
165+
146166
return (
147167
<span>
148-
<Typography.Text type="secondary" style={{ marginRight: 4 }}>
168+
<Typography.Text
169+
type="secondary"
170+
style={{ marginRight: token.marginXXS }}
171+
>
149172
{t('autoScalingRule.CurrentValue')}:{' '}
150173
</Typography.Text>
151-
<Typography.Text strong>
152-
{displayValue ?? t('autoScalingRule.NoDataAvailable')}
153-
</Typography.Text>
154-
<Button
174+
<ErrorBoundaryWithNullFallback>
175+
{/* null fallback: initial load shows nothing until data arrives.
176+
On refresh via startTransition, React keeps the previous value
177+
visible and never commits this fallback. */}
178+
<React.Suspense fallback={null}>
179+
<PreviewValue presetRawId={presetRawId} fetchKey={fetchKey} />
180+
</React.Suspense>
181+
</ErrorBoundaryWithNullFallback>
182+
<BAIButton
155183
type="link"
156184
size="small"
157185
icon={<ReloadOutlined />}
158-
onClick={() => setFetchKey((k) => k + 1)}
186+
loading={isPending}
187+
onClick={() => startTransition(() => setFetchKey((k) => k + 1))}
159188
title={t('autoScalingRule.RefreshPreview')}
189+
aria-label={t('autoScalingRule.RefreshPreview')}
160190
/>
161191
</span>
162192
);
@@ -189,6 +219,7 @@ const AutoScalingRuleEditorModal: React.FC<AutoScalingRuleEditorModalProps> = ({
189219
}) => {
190220
'use memo';
191221
const { t } = useTranslation();
222+
const { token } = theme.useToken();
192223
const { message } = App.useApp();
193224
const { logger } = useBAILogger();
194225

@@ -585,6 +616,11 @@ const AutoScalingRuleEditorModal: React.FC<AutoScalingRuleEditorModalProps> = ({
585616
},
586617
},
587618
]}
619+
extra={
620+
selectedPreset ? (
621+
<PrometheusPresetPreview presetGlobalId={selectedPreset.id} />
622+
) : undefined
623+
}
588624
>
589625
<Select
590626
value={selectedPresetId}
@@ -616,35 +652,6 @@ const AutoScalingRuleEditorModal: React.FC<AutoScalingRuleEditorModalProps> = ({
616652
onClear={() => setSelectedPresetId(undefined)}
617653
/>
618654
</Form.Item>
619-
{selectedPreset && (
620-
<Form.Item
621-
label={t('autoScalingRule.QueryTemplate')}
622-
extra={
623-
<ErrorBoundaryWithNullFallback>
624-
<React.Suspense
625-
fallback={
626-
<Spin size="small" style={{ marginRight: 8 }} />
627-
}
628-
>
629-
<PrometheusPresetPreview
630-
presetGlobalId={selectedPreset.id}
631-
/>
632-
</React.Suspense>
633-
</ErrorBoundaryWithNullFallback>
634-
}
635-
>
636-
<Typography.Text
637-
code
638-
style={{
639-
display: 'block',
640-
whiteSpace: 'pre-wrap',
641-
wordBreak: 'break-all',
642-
}}
643-
>
644-
{selectedPreset.queryTemplate}
645-
</Typography.Text>
646-
</Form.Item>
647-
)}
648655
</>
649656
)}
650657

@@ -669,26 +676,32 @@ const AutoScalingRuleEditorModal: React.FC<AutoScalingRuleEditorModalProps> = ({
669676
onChange={(value) => {
670677
setConditionMode(value as ConditionMode);
671678
}}
672-
style={{ marginBottom: 12 }}
679+
style={{ marginBottom: token.marginSM }}
673680
/>
674681
</Form.Item>
675682

676683
{conditionMode === 'single' ? (
677-
<div style={{ display: 'flex', gap: 8, alignItems: 'start' }}>
684+
<div
685+
style={{
686+
display: 'flex',
687+
gap: token.marginXS,
688+
alignItems: 'center',
689+
}}
690+
>
678691
<Form.Item
679692
name={'direction'}
680693
noStyle
681694
rules={[{ required: true }]}
682695
>
683696
<Select
684-
style={{ width: 120 }}
697+
style={{ width: 100 }}
685698
options={[
686699
{
687-
label: t('autoScalingRule.Upper'),
700+
label: 'Metric >',
688701
value: 'upper',
689702
},
690703
{
691-
label: t('autoScalingRule.Lower'),
704+
label: 'Metric <',
692705
value: 'lower',
693706
},
694707
]}
@@ -705,7 +718,7 @@ const AutoScalingRuleEditorModal: React.FC<AutoScalingRuleEditorModalProps> = ({
705718
{
706719
type: 'number',
707720
min: 0,
708-
message: t('autoScalingRule.ThresholdMustBePositive'),
721+
message: t('autoScalingRule.ThresholdMustBeNonNegative'),
709722
},
710723
]}
711724
>
@@ -717,7 +730,13 @@ const AutoScalingRuleEditorModal: React.FC<AutoScalingRuleEditorModalProps> = ({
717730
</Form.Item>
718731
</div>
719732
) : (
720-
<div style={{ display: 'flex', gap: 8, alignItems: 'start' }}>
733+
<div
734+
style={{
735+
display: 'flex',
736+
gap: token.marginXS,
737+
alignItems: 'center',
738+
}}
739+
>
721740
<Form.Item
722741
name={'minThreshold'}
723742
noStyle
@@ -729,7 +748,7 @@ const AutoScalingRuleEditorModal: React.FC<AutoScalingRuleEditorModalProps> = ({
729748
{
730749
type: 'number',
731750
min: 0,
732-
message: t('autoScalingRule.ThresholdMustBePositive'),
751+
message: t('autoScalingRule.ThresholdMustBeNonNegative'),
733752
},
734753
]}
735754
>
@@ -739,8 +758,8 @@ const AutoScalingRuleEditorModal: React.FC<AutoScalingRuleEditorModalProps> = ({
739758
min={0}
740759
/>
741760
</Form.Item>
742-
<Typography.Text style={{ lineHeight: '32px', flexShrink: 0 }}>
743-
{'<'} metric {'<'}
761+
<Typography.Text style={{ flexShrink: 0 }}>
762+
{'<'} Metric {'<'}
744763
</Typography.Text>
745764
<Form.Item
746765
name={'maxThreshold'}
@@ -754,7 +773,7 @@ const AutoScalingRuleEditorModal: React.FC<AutoScalingRuleEditorModalProps> = ({
754773
{
755774
type: 'number',
756775
min: 0,
757-
message: t('autoScalingRule.ThresholdMustBePositive'),
776+
message: t('autoScalingRule.ThresholdMustBeNonNegative'),
758777
},
759778
({ getFieldValue }) => ({
760779
validator(_, value) {

0 commit comments

Comments
 (0)