Skip to content

Commit 8395e2c

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

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
@@ -30,7 +30,6 @@ import {
3030
import type { BAITableProps, GraphQLFilter } from 'backend.ai-ui';
3131
import { default as dayjs } from 'dayjs';
3232
import * as _ from 'lodash-es';
33-
import { CircleArrowDownIcon, CircleArrowUpIcon } from 'lucide-react';
3433
import { parseAsJson, parseAsStringLiteral, useQueryStates } from 'nuqs';
3534
import React, { useDeferredValue, useState, useTransition } from 'react';
3635
import { useTranslation } from 'react-i18next';
@@ -52,12 +51,19 @@ type AutoScalingRuleNode = NonNullable<
5251
* - maxThreshold only → `[metric_name] < [maxThreshold]`
5352
* - minThreshold only → `[minThreshold] < [metric_name]`
5453
* - Both set → `[minThreshold] < [metric_name] < [maxThreshold]`
54+
*
55+
* For PROMETHEUS rules, the tag shows the preset name (from presetMap) instead of
56+
* the raw metricName, since users select a preset — not the metric directly.
5557
*/
5658
const renderCondition = (
5759
rule: AutoScalingRuleNode,
5860
t: (key: string) => string,
61+
presetMap?: Map<string, string>,
5962
) => {
60-
const metricName = rule.metricName;
63+
const tagLabel =
64+
rule.metricSource === 'PROMETHEUS' && rule.prometheusQueryPresetId
65+
? (presetMap?.get(rule.prometheusQueryPresetId) ?? rule.metricName)
66+
: rule.metricName;
6167
const minThreshold = rule.minThreshold;
6268
const maxThreshold = rule.maxThreshold;
6369
const suffix = rule.metricSource === 'KERNEL' ? '%' : '';
@@ -68,7 +74,7 @@ const renderCondition = (
6874
{minThreshold}
6975
{suffix}
7076
<Tooltip title={t('autoScalingRule.MinThreshold')}>{'<'}</Tooltip>
71-
<Tag>{metricName}</Tag>
77+
<Tag>{tagLabel}</Tag>
7278
<Tooltip title={t('autoScalingRule.MaxThreshold')}>{'<'}</Tooltip>
7379
{maxThreshold}
7480
{suffix}
@@ -79,7 +85,7 @@ const renderCondition = (
7985
if (maxThreshold != null) {
8086
return (
8187
<BAIFlex gap={'xs'}>
82-
<Tag>{metricName}</Tag>
88+
<Tag>{tagLabel}</Tag>
8389
<Tooltip title={t('autoScalingRule.MaxThreshold')}>{'<'}</Tooltip>
8490
{maxThreshold}
8591
{suffix}
@@ -93,7 +99,7 @@ const renderCondition = (
9399
{minThreshold}
94100
{suffix}
95101
<Tooltip title={t('autoScalingRule.MinThreshold')}>{'<'}</Tooltip>
96-
<Tag>{metricName}</Tag>
102+
<Tag>{tagLabel}</Tag>
97103
</BAIFlex>
98104
);
99105
}
@@ -195,7 +201,7 @@ const AutoScalingRuleListNodes: React.FC<AutoScalingRuleListNodesProps> = ({
195201
if (!row) return '-';
196202
return (
197203
<BAINameActionCell
198-
title={renderCondition(row, t)}
204+
title={renderCondition(row, t, presetMap)}
199205
showActions="always"
200206
actions={[
201207
{
@@ -218,22 +224,6 @@ const AutoScalingRuleListNodes: React.FC<AutoScalingRuleListNodesProps> = ({
218224
);
219225
},
220226
},
221-
{
222-
key: 'prometheusPreset',
223-
title: t('autoScalingRule.PrometheusPreset'),
224-
render: (_text, row) => {
225-
if (
226-
row?.metricSource !== 'PROMETHEUS' ||
227-
!row?.prometheusQueryPresetId
228-
) {
229-
return '-';
230-
}
231-
return (
232-
presetMap.get(row.prometheusQueryPresetId) ||
233-
row.prometheusQueryPresetId
234-
);
235-
},
236-
},
237227
{
238228
key: 'timeWindow',
239229
title: t('autoScalingRule.TimeWindow'),
@@ -248,40 +238,55 @@ const AutoScalingRuleListNodes: React.FC<AutoScalingRuleListNodesProps> = ({
248238
title: t('autoScalingRule.StepSize'),
249239
dataIndex: 'stepSize',
250240
render: (_text, row) => {
251-
if (row?.stepSize) {
252-
return (
253-
<BAIFlex gap={'xs'}>
254-
<Typography.Text>
255-
{row.stepSize > 0 ? (
256-
<CircleArrowUpIcon />
257-
) : (
258-
<CircleArrowDownIcon />
259-
)}
260-
</Typography.Text>
261-
<Typography.Text>{Math.abs(row.stepSize)}</Typography.Text>
262-
</BAIFlex>
263-
);
264-
} else {
265-
return '-';
266-
}
241+
if (!row?.stepSize) return '-';
242+
const hasMin = row.minThreshold != null;
243+
const hasMax = row.maxThreshold != null;
244+
const sign = hasMin && hasMax ? '±' : hasMax ? '+' : '−';
245+
return (
246+
<BAIFlex gap={'xs'}>
247+
<Typography.Text>{sign}</Typography.Text>
248+
<Typography.Text>{Math.abs(row.stepSize)}</Typography.Text>
249+
</BAIFlex>
250+
);
267251
},
268252
},
269253
{
270254
key: 'minMaxReplicas',
271255
title: t('autoScalingRule.MIN/MAXReplicas'),
272-
render: (_text, row) => (
273-
<span>
274-
{row?.stepSize
275-
? row.stepSize > 0
276-
? t('autoScalingRule.MaxReplicasValue', {
277-
value: row?.maxReplicas,
278-
})
279-
: t('autoScalingRule.MinReplicasValue', {
280-
value: row?.minReplicas,
281-
})
282-
: '-'}
283-
</span>
284-
),
256+
render: (_text, row) => {
257+
if (!row?.stepSize) return '-';
258+
const hasMin = row.minThreshold != null;
259+
const hasMax = row.maxThreshold != null;
260+
if (hasMin && hasMax) {
261+
return (
262+
<span>
263+
{t('autoScalingRule.MinReplicasValue', {
264+
value: row?.minReplicas,
265+
})}
266+
{' / '}
267+
{t('autoScalingRule.MaxReplicasValue', {
268+
value: row?.maxReplicas,
269+
})}
270+
</span>
271+
);
272+
}
273+
if (hasMax) {
274+
return (
275+
<span>
276+
{t('autoScalingRule.MaxReplicasValue', {
277+
value: row?.maxReplicas,
278+
})}
279+
</span>
280+
);
281+
}
282+
return (
283+
<span>
284+
{t('autoScalingRule.MinReplicasValue', {
285+
value: row?.minReplicas,
286+
})}
287+
</span>
288+
);
289+
},
285290
},
286291
{
287292
key: 'createdAt',

0 commit comments

Comments
 (0)