Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions react/src/components/AutoScalingRuleEditorModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
InputNumber,
Segmented,
Select,
Spin,
Typography,
theme,
} from 'antd';
Expand Down Expand Up @@ -180,10 +181,7 @@ const PrometheusPresetPreview: React.FC<{
{t('autoScalingRule.CurrentValue')}:{' '}
</Typography.Text>
<ErrorBoundaryWithNullFallback>
{/* null fallback: initial load shows nothing until data arrives.
On refresh via startTransition, React keeps the previous value
visible and never commits this fallback. */}
<React.Suspense fallback={null}>
<React.Suspense fallback={<Spin size="small" />}>
<PreviewValue presetRawId={presetRawId} fetchKey={fetchKey} />
</React.Suspense>
</ErrorBoundaryWithNullFallback>
Expand Down Expand Up @@ -644,7 +642,7 @@ const AutoScalingRuleEditorModal: React.FC<AutoScalingRuleEditorModalProps> = ({
{isPrometheus && (
<>
<Form.Item
label={t('autoScalingRule.PrometheusPreset')}
label={`${t('autoScalingRule.MetricName')} (${t('autoScalingRule.PrometheusPreset')})`}
Comment thread
agatha197 marked this conversation as resolved.
name="prometheusQueryPresetId"
rules={[
{
Expand Down Expand Up @@ -680,6 +678,12 @@ const AutoScalingRuleEditorModal: React.FC<AutoScalingRuleEditorModalProps> = ({
}
}}
placeholder={t('autoScalingRule.SelectPrometheusPreset')}
showSearch
filterOption={(input, option) =>
String(option?.label ?? '')
.toLowerCase()
.includes(input.toLowerCase())
}
options={presetOptions}
optionRender={(option) => (
<BAIFlex direction="column" align="start">
Expand Down
108 changes: 57 additions & 51 deletions react/src/components/AutoScalingRuleList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import {
import type { BAITableProps, GraphQLFilter } from 'backend.ai-ui';
import { default as dayjs } from 'dayjs';
import * as _ from 'lodash-es';
import { CircleArrowDownIcon, CircleArrowUpIcon } from 'lucide-react';
import { parseAsJson, parseAsStringLiteral, useQueryStates } from 'nuqs';
import React, { useDeferredValue, useState, useTransition } from 'react';
import { useTranslation } from 'react-i18next';
Expand All @@ -48,12 +47,19 @@ type AutoScalingRuleNode = NonNullable<
* - maxThreshold only → `[metric_name] < [maxThreshold]`
* - minThreshold only → `[minThreshold] < [metric_name]`
* - Both set → `[minThreshold] < [metric_name] < [maxThreshold]`
*
* For PROMETHEUS rules, the tag shows the preset name (from presetMap) instead of
* the raw metricName, since users select a preset — not the metric directly.
*/
const renderCondition = (
rule: AutoScalingRuleNode,
t: (key: string) => string,
presetMap?: Map<string, string>,
) => {
const metricName = rule.metricName;
const tagLabel =
rule.metricSource === 'PROMETHEUS' && rule.prometheusQueryPresetId
? (presetMap?.get(rule.prometheusQueryPresetId) ?? rule.metricName)
: rule.metricName;
const minThreshold = rule.minThreshold;
const maxThreshold = rule.maxThreshold;
const suffix = rule.metricSource === 'KERNEL' ? '%' : '';
Expand All @@ -64,7 +70,7 @@ const renderCondition = (
{minThreshold}
{suffix}
<Tooltip title={t('autoScalingRule.MinThreshold')}>{'<'}</Tooltip>
<Tag>{metricName}</Tag>
<Tag>{tagLabel}</Tag>
<Tooltip title={t('autoScalingRule.MaxThreshold')}>{'<'}</Tooltip>
{maxThreshold}
{suffix}
Expand All @@ -75,7 +81,7 @@ const renderCondition = (
if (maxThreshold != null) {
return (
<BAIFlex gap={'xs'}>
<Tag>{metricName}</Tag>
<Tag>{tagLabel}</Tag>
<Tooltip title={t('autoScalingRule.MaxThreshold')}>{'<'}</Tooltip>
{maxThreshold}
{suffix}
Expand All @@ -89,7 +95,7 @@ const renderCondition = (
{minThreshold}
{suffix}
<Tooltip title={t('autoScalingRule.MinThreshold')}>{'<'}</Tooltip>
<Tag>{metricName}</Tag>
<Tag>{tagLabel}</Tag>
</BAIFlex>
);
}
Expand Down Expand Up @@ -191,7 +197,7 @@ const AutoScalingRuleListNodes: React.FC<AutoScalingRuleListNodesProps> = ({
if (!row) return '-';
return (
<BAINameActionCell
title={renderCondition(row, t)}
title={renderCondition(row, t, presetMap)}
showActions="always"
actions={[
{
Expand All @@ -214,22 +220,6 @@ const AutoScalingRuleListNodes: React.FC<AutoScalingRuleListNodesProps> = ({
);
},
},
{
key: 'prometheusPreset',
title: t('autoScalingRule.PrometheusPreset'),
render: (_text, row) => {
if (
row?.metricSource !== 'PROMETHEUS' ||
!row?.prometheusQueryPresetId
) {
return '-';
}
return (
presetMap.get(row.prometheusQueryPresetId) ||
row.prometheusQueryPresetId
);
},
},
{
key: 'timeWindow',
title: t('autoScalingRule.TimeWindow'),
Expand All @@ -244,40 +234,56 @@ const AutoScalingRuleListNodes: React.FC<AutoScalingRuleListNodesProps> = ({
title: t('autoScalingRule.StepSize'),
dataIndex: 'stepSize',
render: (_text, row) => {
if (row?.stepSize) {
return (
<BAIFlex gap={'xs'}>
<Typography.Text>
{row.stepSize > 0 ? (
<CircleArrowUpIcon />
) : (
<CircleArrowDownIcon />
)}
</Typography.Text>
<Typography.Text>{Math.abs(row.stepSize)}</Typography.Text>
</BAIFlex>
);
} else {
return '-';
}
if (!row?.stepSize) return '-';
const hasMin = row.minThreshold != null;
const hasMax = row.maxThreshold != null;
Comment thread
agatha197 marked this conversation as resolved.
if (!hasMin && !hasMax) return '-';
const sign = hasMin && hasMax ? '±' : hasMax ? '+' : '−';
return (
<BAIFlex gap={'xs'}>
<Typography.Text>{sign}</Typography.Text>
<Typography.Text>{Math.abs(row.stepSize)}</Typography.Text>
</BAIFlex>
);
},
},
{
key: 'minMaxReplicas',
title: t('autoScalingRule.MIN/MAXReplicas'),
render: (_text, row) => (
<span>
{row?.stepSize
? row.stepSize > 0
? t('autoScalingRule.MaxReplicasValue', {
value: row?.maxReplicas,
})
: t('autoScalingRule.MinReplicasValue', {
value: row?.minReplicas,
})
: '-'}
</span>
),
render: (_text, row) => {
if (!row?.stepSize) return '-';
const hasMin = row.minThreshold != null;
const hasMax = row.maxThreshold != null;
if (hasMin && hasMax) {
return (
<span>
{t('autoScalingRule.MinReplicasValue', {
value: row?.minReplicas,
})}
{' / '}
{t('autoScalingRule.MaxReplicasValue', {
value: row?.maxReplicas,
})}
</span>
);
}
if (hasMax) {
return (
<span>
{t('autoScalingRule.MaxReplicasValue', {
value: row?.maxReplicas,
})}
</span>
);
}
return (
<span>
{t('autoScalingRule.MinReplicasValue', {
value: row?.minReplicas,
})}
</span>
);
},
},
{
key: 'createdAt',
Expand Down
Loading