Skip to content

Commit 371c91b

Browse files
committed
refactor(FR-2494): improve auto scaling rule v2 UI — step size sign, condition tag, preset searchable
1 parent fdabd22 commit 371c91b

2 files changed

Lines changed: 65 additions & 56 deletions

File tree

react/src/components/AutoScalingRuleEditorModal.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
InputNumber,
2222
Segmented,
2323
Select,
24+
Spin,
2425
Typography,
2526
theme,
2627
} from 'antd';
@@ -180,10 +181,7 @@ const PrometheusPresetPreview: React.FC<{
180181
{t('autoScalingRule.CurrentValue')}:{' '}
181182
</Typography.Text>
182183
<ErrorBoundaryWithNullFallback>
183-
{/* null fallback: initial load shows nothing until data arrives.
184-
On refresh via startTransition, React keeps the previous value
185-
visible and never commits this fallback. */}
186-
<React.Suspense fallback={null}>
184+
<React.Suspense fallback={<Spin size="small" />}>
187185
<PreviewValue presetRawId={presetRawId} fetchKey={fetchKey} />
188186
</React.Suspense>
189187
</ErrorBoundaryWithNullFallback>
@@ -644,7 +642,7 @@ const AutoScalingRuleEditorModal: React.FC<AutoScalingRuleEditorModalProps> = ({
644642
{isPrometheus && (
645643
<>
646644
<Form.Item
647-
label={t('autoScalingRule.PrometheusPreset')}
645+
label={`${t('autoScalingRule.MetricName')} (${t('autoScalingRule.PrometheusPreset')})`}
648646
name="prometheusQueryPresetId"
649647
rules={[
650648
{
@@ -680,6 +678,12 @@ const AutoScalingRuleEditorModal: React.FC<AutoScalingRuleEditorModalProps> = ({
680678
}
681679
}}
682680
placeholder={t('autoScalingRule.SelectPrometheusPreset')}
681+
showSearch
682+
filterOption={(input, option) =>
683+
(option?.label ?? '')
684+
.toLowerCase()
685+
.includes(input.toLowerCase())
686+
}
683687
options={presetOptions}
684688
optionRender={(option) => (
685689
<BAIFlex direction="column" align="start">

react/src/components/AutoScalingRuleList.tsx

Lines changed: 56 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import {
3131
import type { BAITableProps, GraphQLFilter } from 'backend.ai-ui';
3232
import { default as dayjs } from 'dayjs';
3333
import * as _ from 'lodash-es';
34-
import { CircleArrowDownIcon, CircleArrowUpIcon } from 'lucide-react';
3534
import { parseAsJson, parseAsStringLiteral, useQueryStates } from 'nuqs';
3635
import React, { useDeferredValue, useState, useTransition } from 'react';
3736
import { useTranslation } from 'react-i18next';
@@ -48,12 +47,19 @@ type AutoScalingRuleNode = NonNullable<
4847
* - maxThreshold only → `[metric_name] < [maxThreshold]`
4948
* - minThreshold only → `[minThreshold] < [metric_name]`
5049
* - Both set → `[minThreshold] < [metric_name] < [maxThreshold]`
50+
*
51+
* For PROMETHEUS rules, the tag shows the preset name (from presetMap) instead of
52+
* the raw metricName, since users select a preset — not the metric directly.
5153
*/
5254
const renderCondition = (
5355
rule: AutoScalingRuleNode,
5456
t: (key: string) => string,
57+
presetMap?: Map<string, string>,
5558
) => {
56-
const metricName = rule.metricName;
59+
const tagLabel =
60+
rule.metricSource === 'PROMETHEUS' && rule.prometheusQueryPresetId
61+
? (presetMap?.get(rule.prometheusQueryPresetId) ?? rule.metricName)
62+
: rule.metricName;
5763
const minThreshold = rule.minThreshold;
5864
const maxThreshold = rule.maxThreshold;
5965
const suffix = rule.metricSource === 'KERNEL' ? '%' : '';
@@ -64,7 +70,7 @@ const renderCondition = (
6470
{minThreshold}
6571
{suffix}
6672
<Tooltip title={t('autoScalingRule.MinThreshold')}>{'<'}</Tooltip>
67-
<Tag>{metricName}</Tag>
73+
<Tag>{tagLabel}</Tag>
6874
<Tooltip title={t('autoScalingRule.MaxThreshold')}>{'<'}</Tooltip>
6975
{maxThreshold}
7076
{suffix}
@@ -75,7 +81,7 @@ const renderCondition = (
7581
if (maxThreshold != null) {
7682
return (
7783
<BAIFlex gap={'xs'}>
78-
<Tag>{metricName}</Tag>
84+
<Tag>{tagLabel}</Tag>
7985
<Tooltip title={t('autoScalingRule.MaxThreshold')}>{'<'}</Tooltip>
8086
{maxThreshold}
8187
{suffix}
@@ -89,7 +95,7 @@ const renderCondition = (
8995
{minThreshold}
9096
{suffix}
9197
<Tooltip title={t('autoScalingRule.MinThreshold')}>{'<'}</Tooltip>
92-
<Tag>{metricName}</Tag>
98+
<Tag>{tagLabel}</Tag>
9399
</BAIFlex>
94100
);
95101
}
@@ -191,7 +197,7 @@ const AutoScalingRuleListNodes: React.FC<AutoScalingRuleListNodesProps> = ({
191197
if (!row) return '-';
192198
return (
193199
<BAINameActionCell
194-
title={renderCondition(row, t)}
200+
title={renderCondition(row, t, presetMap)}
195201
showActions="always"
196202
actions={[
197203
{
@@ -214,22 +220,6 @@ const AutoScalingRuleListNodes: React.FC<AutoScalingRuleListNodesProps> = ({
214220
);
215221
},
216222
},
217-
{
218-
key: 'prometheusPreset',
219-
title: t('autoScalingRule.PrometheusPreset'),
220-
render: (_text, row) => {
221-
if (
222-
row?.metricSource !== 'PROMETHEUS' ||
223-
!row?.prometheusQueryPresetId
224-
) {
225-
return '-';
226-
}
227-
return (
228-
presetMap.get(row.prometheusQueryPresetId) ||
229-
row.prometheusQueryPresetId
230-
);
231-
},
232-
},
233223
{
234224
key: 'timeWindow',
235225
title: t('autoScalingRule.TimeWindow'),
@@ -244,40 +234,55 @@ const AutoScalingRuleListNodes: React.FC<AutoScalingRuleListNodesProps> = ({
244234
title: t('autoScalingRule.StepSize'),
245235
dataIndex: 'stepSize',
246236
render: (_text, row) => {
247-
if (row?.stepSize) {
248-
return (
249-
<BAIFlex gap={'xs'}>
250-
<Typography.Text>
251-
{row.stepSize > 0 ? (
252-
<CircleArrowUpIcon />
253-
) : (
254-
<CircleArrowDownIcon />
255-
)}
256-
</Typography.Text>
257-
<Typography.Text>{Math.abs(row.stepSize)}</Typography.Text>
258-
</BAIFlex>
259-
);
260-
} else {
261-
return '-';
262-
}
237+
if (!row?.stepSize) return '-';
238+
const hasMin = row.minThreshold != null;
239+
const hasMax = row.maxThreshold != null;
240+
const sign = hasMin && hasMax ? '±' : hasMax ? '+' : '−';
241+
return (
242+
<BAIFlex gap={'xs'}>
243+
<Typography.Text>{sign}</Typography.Text>
244+
<Typography.Text>{Math.abs(row.stepSize)}</Typography.Text>
245+
</BAIFlex>
246+
);
263247
},
264248
},
265249
{
266250
key: 'minMaxReplicas',
267251
title: t('autoScalingRule.MIN/MAXReplicas'),
268-
render: (_text, row) => (
269-
<span>
270-
{row?.stepSize
271-
? row.stepSize > 0
272-
? t('autoScalingRule.MaxReplicasValue', {
273-
value: row?.maxReplicas,
274-
})
275-
: t('autoScalingRule.MinReplicasValue', {
276-
value: row?.minReplicas,
277-
})
278-
: '-'}
279-
</span>
280-
),
252+
render: (_text, row) => {
253+
if (!row?.stepSize) return '-';
254+
const hasMin = row.minThreshold != null;
255+
const hasMax = row.maxThreshold != null;
256+
if (hasMin && hasMax) {
257+
return (
258+
<span>
259+
{t('autoScalingRule.MinReplicasValue', {
260+
value: row?.minReplicas,
261+
})}
262+
{' / '}
263+
{t('autoScalingRule.MaxReplicasValue', {
264+
value: row?.maxReplicas,
265+
})}
266+
</span>
267+
);
268+
}
269+
if (hasMax) {
270+
return (
271+
<span>
272+
{t('autoScalingRule.MaxReplicasValue', {
273+
value: row?.maxReplicas,
274+
})}
275+
</span>
276+
);
277+
}
278+
return (
279+
<span>
280+
{t('autoScalingRule.MinReplicasValue', {
281+
value: row?.minReplicas,
282+
})}
283+
</span>
284+
);
285+
},
281286
},
282287
{
283288
key: 'createdAt',

0 commit comments

Comments
 (0)