feat: unify alert operations queue
This commit is contained in:
@@ -141,7 +141,7 @@ test('clears old alert rows and inspector evidence when the event scope changes'
|
|||||||
const view = render(<QueryClientProvider client={client}><MemoryRouter future={ROUTER_FUTURE} initialEntries={['/alerts?keyword=OLDVIN']}><AlertsPage /></MemoryRouter></QueryClientProvider>);
|
const view = render(<QueryClientProvider client={client}><MemoryRouter future={ROUTER_FUTURE} initialEntries={['/alerts?keyword=OLDVIN']}><AlertsPage /></MemoryRouter></QueryClientProvider>);
|
||||||
|
|
||||||
expect((await screen.findAllByText('旧告警车牌')).length).toBeGreaterThan(0);
|
expect((await screen.findAllByText('旧告警车牌')).length).toBeGreaterThan(0);
|
||||||
for (const className of ['v2-alert-filter-card', 'v2-alert-kpis-card', 'v2-alert-context-card', 'v2-alert-table-card']) {
|
for (const className of ['v2-alert-filter-card', 'v2-alert-metric-rail', 'v2-alert-table-card']) {
|
||||||
expect(view.container.querySelector(`.${className}.semi-card`)).toBeInTheDocument();
|
expect(view.container.querySelector(`.${className}.semi-card`)).toBeInTheDocument();
|
||||||
}
|
}
|
||||||
expect(view.container.querySelector('.v2-alert-navigation')).toBeInTheDocument();
|
expect(view.container.querySelector('.v2-alert-navigation')).toBeInTheDocument();
|
||||||
@@ -152,9 +152,9 @@ test('clears old alert rows and inspector evidence when the event scope changes'
|
|||||||
expect(screen.queryByRole('heading', { level: 2, name: '告警中心' })).not.toBeInTheDocument();
|
expect(screen.queryByRole('heading', { level: 2, name: '告警中心' })).not.toBeInTheDocument();
|
||||||
expect(view.container.querySelector('.v2-alert-inspector')).not.toBeInTheDocument();
|
expect(view.container.querySelector('.v2-alert-inspector')).not.toBeInTheDocument();
|
||||||
expect(view.container.querySelector('.v2-alert-workspace')).not.toHaveClass('is-inspector-open');
|
expect(view.container.querySelector('.v2-alert-workspace')).not.toHaveClass('is-inspector-open');
|
||||||
expect(view.container.querySelectorAll('.v2-alert-kpis .v2-metric-action')).toHaveLength(4);
|
expect(view.container.querySelectorAll('.v2-alert-metric-rail .v2-workspace-metric-action.semi-button')).toHaveLength(4);
|
||||||
expect(view.container.querySelectorAll('.v2-alert-secondary-states .semi-button')).toHaveLength(3);
|
expect(view.container.querySelectorAll('.v2-alert-metric-context-actions .semi-button')).toHaveLength(3);
|
||||||
expect(screen.getByRole('button', { name: '查看未读通知,共 0 条' })).toHaveClass('is-notice');
|
expect(screen.getByRole('button', { name: '查看未读通知,共 0 条' }).closest('[role="listitem"]')).toHaveClass('is-warning', 'is-secondary');
|
||||||
expect(view.container.querySelector('.v2-alert-event-table.semi-table-wrapper')).toBeInTheDocument();
|
expect(view.container.querySelector('.v2-alert-event-table.semi-table-wrapper')).toBeInTheDocument();
|
||||||
expect(view.container.querySelector('.v2-alert-event-table.semi-table-wrapper')).not.toHaveClass('is-compact');
|
expect(view.container.querySelector('.v2-alert-event-table.semi-table-wrapper')).not.toHaveClass('is-compact');
|
||||||
expect(screen.getByRole('columnheader', { name: '触发 / 阈值' })).toBeInTheDocument();
|
expect(screen.getByRole('columnheader', { name: '触发 / 阈值' })).toBeInTheDocument();
|
||||||
@@ -254,11 +254,11 @@ test('renders only selectable Semi alert cards on mobile', async () => {
|
|||||||
expect(view.container.querySelector('.v2-alert-table-scroll.is-mobile-scroll')).toHaveAttribute('tabindex', '0');
|
expect(view.container.querySelector('.v2-alert-table-scroll.is-mobile-scroll')).toHaveAttribute('tabindex', '0');
|
||||||
expect(view.container.querySelector('.v2-alert-table-scroll.is-mobile-scroll')).toHaveAttribute('aria-label', '告警事件列表,可上下滚动');
|
expect(view.container.querySelector('.v2-alert-table-scroll.is-mobile-scroll')).toHaveAttribute('aria-label', '告警事件列表,可上下滚动');
|
||||||
expect(view.container.querySelector('.v2-alert-event-table')).not.toBeInTheDocument();
|
expect(view.container.querySelector('.v2-alert-event-table')).not.toBeInTheDocument();
|
||||||
const mobileMetrics = Array.from(view.container.querySelectorAll('.v2-alert-kpis .v2-metric-action'));
|
const mobileMetrics = Array.from(view.container.querySelectorAll('.v2-alert-metric-rail .v2-workspace-metric-action'));
|
||||||
expect(mobileMetrics).toHaveLength(3);
|
expect(mobileMetrics).toHaveLength(2);
|
||||||
expect(mobileMetrics.map((metric) => metric.textContent)).toEqual(['待处置1', '处理中0', '全部事件1']);
|
expect(mobileMetrics.map((metric) => metric.textContent)).toEqual(['待处置1需要核验', '全部事件1当前范围']);
|
||||||
expect(screen.getByRole('button', { name: '筛选待处置,共 1 条' })).toHaveAttribute('aria-pressed', 'false');
|
expect(screen.getByRole('button', { name: '筛选待处置,共 1 条' })).toHaveAttribute('aria-pressed', 'false');
|
||||||
expect(view.container.querySelector('.v2-alert-secondary-states')).not.toBeInTheDocument();
|
expect(view.container.querySelector('.v2-alert-metric-context-actions')).not.toBeInTheDocument();
|
||||||
expect(screen.queryByRole('button', { name: /查看未读通知/ })).not.toBeInTheDocument();
|
expect(screen.queryByRole('button', { name: /查看未读通知/ })).not.toBeInTheDocument();
|
||||||
expect(screen.getByRole('tab', { name: /站内通知/ })).toBeInTheDocument();
|
expect(screen.getByRole('tab', { name: /站内通知/ })).toBeInTheDocument();
|
||||||
expect(action).toHaveAttribute('aria-expanded', 'false');
|
expect(action).toHaveAttribute('aria-expanded', 'false');
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { api } from '../../api/client';
|
|||||||
import type { AlertEvent, AlertQuery, AlertRule, AlertRuleInput, AlertStatus, MetricDefinition, Page } from '../../api/types';
|
import type { AlertEvent, AlertQuery, AlertRule, AlertRuleInput, AlertStatus, MetricDefinition, Page } from '../../api/types';
|
||||||
import { actionLabels, alertDeltaText, alertValue, canAct, formatAlertTime, operatorLabels, ruleCondition, severityLabels, statusLabels, thresholdText } from '../domain/alert';
|
import { actionLabels, alertDeltaText, alertValue, canAct, formatAlertTime, operatorLabels, ruleCondition, severityLabels, statusLabels, thresholdText } from '../domain/alert';
|
||||||
import { InlineError, PanelEmpty, PanelLoading } from '../shared/AsyncState';
|
import { InlineError, PanelEmpty, PanelLoading } from '../shared/AsyncState';
|
||||||
import { MetricActionButton } from '../shared/MetricActionButton';
|
|
||||||
import { MobileFilterToggle } from '../shared/MobileFilterToggle';
|
import { MobileFilterToggle } from '../shared/MobileFilterToggle';
|
||||||
import { MobileFilterSheet, MobileFilterSheetSection } from '../shared/MobileFilterSheet';
|
import { MobileFilterSheet, MobileFilterSheetSection } from '../shared/MobileFilterSheet';
|
||||||
import { ProtocolTag } from '../shared/ProtocolTag';
|
import { ProtocolTag } from '../shared/ProtocolTag';
|
||||||
@@ -17,6 +16,7 @@ import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar';
|
|||||||
import { WorkspaceDetailSideSheet } from '../shared/WorkspaceDetailSideSheet';
|
import { WorkspaceDetailSideSheet } from '../shared/WorkspaceDetailSideSheet';
|
||||||
import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
|
import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
|
||||||
import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel';
|
import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel';
|
||||||
|
import { WorkspaceMetricRail, type WorkspaceQueueMetricRailItem } from '../shared/WorkspaceMetricRail';
|
||||||
import { WorkspaceSideSheet } from '../shared/WorkspaceSideSheet';
|
import { WorkspaceSideSheet } from '../shared/WorkspaceSideSheet';
|
||||||
import { detailTriggerRow } from '../shared/detailTriggerRow';
|
import { detailTriggerRow } from '../shared/detailTriggerRow';
|
||||||
import { usePlatformSession } from '../auth/AuthGate';
|
import { usePlatformSession } from '../auth/AuthGate';
|
||||||
@@ -229,17 +229,33 @@ function EventWorkspace({ filters, draft, setDraft, setFilters, rules, unread, e
|
|||||||
<label><span>起始时间</span><Input aria-label="告警起始时间" type="datetime-local" value={draft.dateFrom} onChange={(value) => setDraft({ ...draft, dateFrom: value })} /></label>
|
<label><span>起始时间</span><Input aria-label="告警起始时间" type="datetime-local" value={draft.dateFrom} onChange={(value) => setDraft({ ...draft, dateFrom: value })} /></label>
|
||||||
<label><span>结束时间</span><Input aria-label="告警结束时间" type="datetime-local" value={draft.dateTo} onChange={(value) => setDraft({ ...draft, dateTo: value })} /></label>
|
<label><span>结束时间</span><Input aria-label="告警结束时间" type="datetime-local" value={draft.dateTo} onChange={(value) => setDraft({ ...draft, dateTo: value })} /></label>
|
||||||
</>;
|
</>;
|
||||||
const primaryMetrics = mobileLayout
|
const metricItem = (
|
||||||
|
label: string,
|
||||||
|
value: number,
|
||||||
|
status: string,
|
||||||
|
options: Pick<WorkspaceQueueMetricRailItem, 'note' | 'tone' | 'emphasis'>
|
||||||
|
): WorkspaceQueueMetricRailItem => {
|
||||||
|
const formattedValue = value.toLocaleString('zh-CN');
|
||||||
|
const notice = status === 'notice';
|
||||||
|
return {
|
||||||
|
label,
|
||||||
|
value: formattedValue,
|
||||||
|
...options,
|
||||||
|
active: !notice && filters.status === status,
|
||||||
|
ariaLabel: notice ? `查看未读通知,共 ${formattedValue} 条` : `筛选${label},共 ${formattedValue} 条`,
|
||||||
|
onClick: () => notice ? onTab('notifications') : quickStatus(status)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const primaryMetrics: WorkspaceQueueMetricRailItem[] = mobileLayout
|
||||||
? [
|
? [
|
||||||
{ label: '待处置', value: sums?.unprocessed, tone: 'unprocessed', status: 'unprocessed' },
|
metricItem('待处置', Number(sums?.unprocessed ?? 0), 'unprocessed', { note: '需要核验', tone: 'danger', emphasis: 'primary' }),
|
||||||
{ label: '处理中', value: sums?.processing, tone: 'processing', status: 'processing' },
|
metricItem('全部事件', allEventCount, '', { note: '当前范围', tone: 'neutral', emphasis: 'primary' })
|
||||||
{ label: '全部事件', value: allEventCount, tone: 'active', status: '' }
|
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
{ label: '全部事件', value: allEventCount, tone: 'active', status: '' },
|
metricItem('未处理', Number(sums?.unprocessed ?? 0), 'unprocessed', { note: '需要核验与处置', tone: 'danger', emphasis: 'primary' }),
|
||||||
{ label: '未处理', value: sums?.unprocessed, tone: 'unprocessed', status: 'unprocessed' },
|
metricItem('全部事件', allEventCount, '', { note: '当前查询范围', tone: 'neutral', emphasis: 'primary' }),
|
||||||
{ label: '处理中', value: sums?.processing, tone: 'processing', status: 'processing' },
|
metricItem('处理中', Number(sums?.processing ?? 0), 'processing', { note: '处置进行中', tone: 'primary', emphasis: 'secondary' }),
|
||||||
{ label: '未读通知', value: unread, tone: 'notice', status: 'notice' }
|
metricItem('未读通知', unread, 'notice', { note: '进入通知队列', tone: 'warning', emphasis: 'secondary' })
|
||||||
];
|
];
|
||||||
return <>
|
return <>
|
||||||
{mobileLayout ? <div className="v2-alert-mobile-discovery">
|
{mobileLayout ? <div className="v2-alert-mobile-discovery">
|
||||||
@@ -292,19 +308,19 @@ function EventWorkspace({ filters, draft, setDraft, setFilters, rules, unread, e
|
|||||||
</form>
|
</form>
|
||||||
</WorkspaceFilterPanel>}
|
</WorkspaceFilterPanel>}
|
||||||
{summary.isError ? <InlineError message={summary.error instanceof Error ? summary.error.message : '告警汇总读取失败'} onRetry={() => summary.refetch()} /> : null}
|
{summary.isError ? <InlineError message={summary.error instanceof Error ? summary.error.message : '告警汇总读取失败'} onRetry={() => summary.refetch()} /> : null}
|
||||||
<Card className="v2-alert-kpis-card v2-alert-context-card" bodyStyle={{ padding: 0 }}>
|
<WorkspaceMetricRail
|
||||||
<section className="v2-alert-kpis" aria-label="活跃告警概览">{primaryMetrics.map((item) => {
|
variant="queue"
|
||||||
const value = Number(item.value ?? 0).toLocaleString('zh-CN');
|
className="v2-alert-metric-rail"
|
||||||
return <MetricActionButton key={item.label} label={item.label} value={value} tone={item.tone} active={item.status !== 'notice' && filters.status === item.status} ariaLabel={item.status === 'notice' ? `查看未读通知,共 ${value} 条` : `筛选${item.label},共 ${value} 条`} onClick={() => item.status === 'notice' ? onTab('notifications') : quickStatus(item.status)} />;
|
ariaLabel="活跃告警概览"
|
||||||
})}</section>
|
items={primaryMetrics}
|
||||||
{!mobileLayout ? <nav className="v2-alert-secondary-states" aria-label="已结束告警状态">{
|
context={!mobileLayout ? <nav className="v2-alert-metric-context-actions" aria-label="已结束告警状态">{
|
||||||
[
|
[
|
||||||
{ label: '已恢复', value: sums?.recovered, status: 'recovered' },
|
{ label: '已恢复', value: sums?.recovered, status: 'recovered' },
|
||||||
{ label: '已关闭', value: sums?.closed, status: 'closed' },
|
{ label: '已关闭', value: sums?.closed, status: 'closed' },
|
||||||
{ label: '已忽略', value: sums?.ignored, status: 'ignored' }
|
{ label: '已忽略', value: sums?.ignored, status: 'ignored' }
|
||||||
].map((item) => <Button key={item.status} theme="borderless" type="tertiary" aria-pressed={filters.status === item.status} onClick={() => quickStatus(item.status)}><span>{item.label}</span><b>{Number(item.value ?? 0).toLocaleString('zh-CN')}</b></Button>)
|
].map((item) => <Button key={item.status} theme="borderless" type="tertiary" aria-pressed={filters.status === item.status} onClick={() => quickStatus(item.status)}><span>{item.label}</span><strong>{Number(item.value ?? 0).toLocaleString('zh-CN')}</strong></Button>)
|
||||||
}</nav> : null}
|
}</nav> : undefined}
|
||||||
</Card>
|
/>
|
||||||
{events.isError ? <InlineError message={events.error instanceof Error ? events.error.message : '告警事件读取失败'} onRetry={() => events.refetch()} /> : null}
|
{events.isError ? <InlineError message={events.error instanceof Error ? events.error.message : '告警事件读取失败'} onRetry={() => events.refetch()} /> : null}
|
||||||
<div className={`v2-alert-workspace${selectedID && !mobileLayout ? ' is-inspector-open' : ''}`}>
|
<div className={`v2-alert-workspace${selectedID && !mobileLayout ? ' is-inspector-open' : ''}`}>
|
||||||
<Card className="v2-alert-table-card" bodyStyle={{ padding: 0 }}>
|
<Card className="v2-alert-table-card" bodyStyle={{ padding: 0 }}>
|
||||||
|
|||||||
@@ -188,7 +188,9 @@ describe('V2 production entry', () => {
|
|||||||
expect(corePageSources.AlertsPage).toContain('<TextArea');
|
expect(corePageSources.AlertsPage).toContain('<TextArea');
|
||||||
expect(corePageSources.AlertsPage).toContain('<Radio');
|
expect(corePageSources.AlertsPage).toContain('<Radio');
|
||||||
expect(corePageSources.AlertsPage).toContain('<Card key={event.id} className={`v2-alert-mobile-card');
|
expect(corePageSources.AlertsPage).toContain('<Card key={event.id} className={`v2-alert-mobile-card');
|
||||||
expect(corePageSources.AlertsPage).toContain('<Card className="v2-alert-kpis-card v2-alert-context-card"');
|
expect(corePageSources.AlertsPage).toContain('<WorkspaceMetricRail');
|
||||||
|
expect(corePageSources.AlertsPage).toContain('className="v2-alert-metric-rail"');
|
||||||
|
expect(corePageSources.AlertsPage).not.toContain('v2-alert-kpis-card v2-alert-context-card');
|
||||||
expect(corePageSources.AlertsPage).toContain('<Card className="v2-alert-table-card"');
|
expect(corePageSources.AlertsPage).toContain('<Card className="v2-alert-table-card"');
|
||||||
expect(corePageSources.AlertsPage).toContain('<Card className={`v2-alert-inspector');
|
expect(corePageSources.AlertsPage).toContain('<Card className={`v2-alert-inspector');
|
||||||
expect(corePageSources.AlertsPage).toContain('<WorkspaceCommandBar');
|
expect(corePageSources.AlertsPage).toContain('<WorkspaceCommandBar');
|
||||||
@@ -459,11 +461,12 @@ describe('V2 production entry', () => {
|
|||||||
expect(corePageSources.StatisticsPage).toContain('<PanelEmpty className="v2-mileage-empty"');
|
expect(corePageSources.StatisticsPage).toContain('<PanelEmpty className="v2-mileage-empty"');
|
||||||
expect(corePageSources.StatisticsPage).not.toContain('<table className="v2-mileage-table"');
|
expect(corePageSources.StatisticsPage).not.toContain('<table className="v2-mileage-table"');
|
||||||
expect(corePageSources.StatisticsPage).not.toContain('<input');
|
expect(corePageSources.StatisticsPage).not.toContain('<input');
|
||||||
expect(workspaceMetricRailSource).toContain("import { Card, CardGroup } from '@douyinfe/semi-ui'");
|
expect(workspaceMetricRailSource).toContain("import { Button, Card, CardGroup } from '@douyinfe/semi-ui'");
|
||||||
expect(workspaceMetricRailSource).toContain('<Card className={className}');
|
expect(workspaceMetricRailSource).toContain('<Card className={className}');
|
||||||
expect(workspaceMetricRailSource).toContain('<CardGroup className={secondaryClassName}');
|
expect(workspaceMetricRailSource).toContain('<CardGroup className={secondaryClassName}');
|
||||||
expect(workspaceMetricRailSource).toContain("variant: 'queue'");
|
expect(workspaceMetricRailSource).toContain("variant: 'queue'");
|
||||||
expect(workspaceMetricRailSource).toContain('className={`v2-workspace-metric-rail is-queue');
|
expect(workspaceMetricRailSource).toContain('className={`v2-workspace-metric-rail is-queue');
|
||||||
|
expect(workspaceMetricRailSource).toContain('className="v2-workspace-metric-action"');
|
||||||
expect(reconciliationSource).toContain('<WorkspaceMetricRail');
|
expect(reconciliationSource).toContain('<WorkspaceMetricRail');
|
||||||
expect(reconciliationSource).toContain('variant="queue"');
|
expect(reconciliationSource).toContain('variant="queue"');
|
||||||
expect(reconciliationSource).not.toContain('v2-reconcile-kpi-card');
|
expect(reconciliationSource).not.toContain('v2-reconcile-kpi-card');
|
||||||
@@ -570,10 +573,10 @@ describe('V2 production entry', () => {
|
|||||||
expect(source).toContain('<MapRetryAction');
|
expect(source).toContain('<MapRetryAction');
|
||||||
expect(source).not.toContain('><IconRefresh />重新加载地图</button>');
|
expect(source).not.toContain('><IconRefresh />重新加载地图</button>');
|
||||||
}
|
}
|
||||||
for (const name of ['AlertsPage', 'AccessPage']) {
|
expect(corePageSources.AccessPage).toContain("from '../shared/MetricActionButton'");
|
||||||
expect(corePageSources[name]).toContain("from '../shared/MetricActionButton'");
|
expect(corePageSources.AccessPage).toContain('<MetricActionButton');
|
||||||
expect(corePageSources[name]).toContain('<MetricActionButton');
|
expect(corePageSources.AlertsPage).toContain("from '../shared/WorkspaceMetricRail'");
|
||||||
}
|
expect(corePageSources.AlertsPage).toContain('<WorkspaceMetricRail');
|
||||||
expect(corePageSources.HistoryPage).toContain('className="v2-history-result-meta"');
|
expect(corePageSources.HistoryPage).toContain('className="v2-history-result-meta"');
|
||||||
expect(corePageSources.AlertsPage).toContain('<Tag className={`v2-alert-severity');
|
expect(corePageSources.AlertsPage).toContain('<Tag className={`v2-alert-severity');
|
||||||
expect(corePageSources.AlertsPage).toContain('<Tag className={`v2-alert-status');
|
expect(corePageSources.AlertsPage).toContain('<Tag className={`v2-alert-status');
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { render, screen, within } from '@testing-library/react';
|
import { fireEvent, render, screen, within } from '@testing-library/react';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
import { WorkspaceMetricRail } from './WorkspaceMetricRail';
|
import { WorkspaceMetricRail } from './WorkspaceMetricRail';
|
||||||
|
|
||||||
describe('WorkspaceMetricRail', () => {
|
describe('WorkspaceMetricRail', () => {
|
||||||
@@ -48,4 +48,33 @@ describe('WorkspaceMetricRail', () => {
|
|||||||
expect(view.container.querySelector('.v2-workspace-metric-context')).toHaveTextContent('当前筛选 1,347 条');
|
expect(view.container.querySelector('.v2-workspace-metric-context')).toHaveTextContent('当前筛选 1,347 条');
|
||||||
expect(view.container.querySelectorAll('.semi-card')).toHaveLength(1);
|
expect(view.container.querySelectorAll('.semi-card')).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('keeps interactive queue metrics inside accessible Semi buttons', () => {
|
||||||
|
const onOpen = vi.fn();
|
||||||
|
render(<WorkspaceMetricRail
|
||||||
|
variant="queue"
|
||||||
|
ariaLabel="告警队列概览"
|
||||||
|
className="alert-metrics"
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
label: '未处理',
|
||||||
|
value: '687',
|
||||||
|
note: '需要核验',
|
||||||
|
tone: 'danger',
|
||||||
|
active: true,
|
||||||
|
ariaLabel: '筛选未处理,共 687 条',
|
||||||
|
onClick: onOpen
|
||||||
|
},
|
||||||
|
{ label: '全部事件', value: '2,848', note: '当前范围' }
|
||||||
|
]}
|
||||||
|
/>);
|
||||||
|
|
||||||
|
const list = screen.getByRole('list', { name: '告警队列概览' });
|
||||||
|
const action = within(list).getByRole('button', { name: '筛选未处理,共 687 条' });
|
||||||
|
expect(action).toHaveClass('semi-button', 'v2-workspace-metric-action');
|
||||||
|
expect(action).toHaveAttribute('aria-pressed', 'true');
|
||||||
|
expect(action.closest('[role="listitem"]')).toHaveClass('is-action', 'is-active', 'is-danger');
|
||||||
|
fireEvent.click(action);
|
||||||
|
expect(onOpen).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Card, CardGroup } from '@douyinfe/semi-ui';
|
import { Button, Card, CardGroup } from '@douyinfe/semi-ui';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
export type WorkspaceMetricRailItem = {
|
export type WorkspaceMetricRailItem = {
|
||||||
@@ -14,6 +14,9 @@ export type WorkspaceQueueMetricRailItem = {
|
|||||||
note?: ReactNode;
|
note?: ReactNode;
|
||||||
tone?: 'neutral' | 'primary' | 'success' | 'warning' | 'danger';
|
tone?: 'neutral' | 'primary' | 'success' | 'warning' | 'danger';
|
||||||
emphasis?: 'primary' | 'secondary';
|
emphasis?: 'primary' | 'secondary';
|
||||||
|
active?: boolean;
|
||||||
|
ariaLabel?: string;
|
||||||
|
onClick?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type WorkspaceSummaryMetricRailProps = {
|
type WorkspaceSummaryMetricRailProps = {
|
||||||
@@ -41,14 +44,31 @@ export function WorkspaceMetricRail(props: WorkspaceSummaryMetricRailProps | Wor
|
|||||||
{props.items.map((item, index) => {
|
{props.items.map((item, index) => {
|
||||||
const toneClassName = `is-${item.tone ?? 'neutral'}`;
|
const toneClassName = `is-${item.tone ?? 'neutral'}`;
|
||||||
const emphasisClassName = `is-${item.emphasis ?? (index < 2 ? 'primary' : 'secondary')}`;
|
const emphasisClassName = `is-${item.emphasis ?? (index < 2 ? 'primary' : 'secondary')}`;
|
||||||
return <span
|
const classes = [
|
||||||
className={toneClassName === emphasisClassName ? toneClassName : `${toneClassName} ${emphasisClassName}`}
|
toneClassName,
|
||||||
role="listitem"
|
emphasisClassName !== toneClassName ? emphasisClassName : '',
|
||||||
key={item.label}
|
item.onClick ? 'is-action' : '',
|
||||||
>
|
item.active ? 'is-active' : ''
|
||||||
|
].filter(Boolean).join(' ');
|
||||||
|
const content = <>
|
||||||
<small>{item.label}</small>
|
<small>{item.label}</small>
|
||||||
<strong>{item.value}</strong>
|
<strong>{item.value}</strong>
|
||||||
{item.note ? <em>{item.note}</em> : null}
|
{item.note ? <em>{item.note}</em> : null}
|
||||||
|
</>;
|
||||||
|
return <span
|
||||||
|
className={classes}
|
||||||
|
role="listitem"
|
||||||
|
key={item.label}
|
||||||
|
>
|
||||||
|
{item.onClick ? <Button
|
||||||
|
className="v2-workspace-metric-action"
|
||||||
|
theme="borderless"
|
||||||
|
type="tertiary"
|
||||||
|
htmlType="button"
|
||||||
|
aria-label={item.ariaLabel}
|
||||||
|
aria-pressed={item.active}
|
||||||
|
onClick={item.onClick}
|
||||||
|
>{content}</Button> : content}
|
||||||
</span>;
|
</span>;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16217,11 +16217,47 @@
|
|||||||
padding: 7px 13px;
|
padding: 7px 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.v2-workspace-metric-list > span.is-action {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-list > span + span {
|
.v2-workspace-metric-list > span + span {
|
||||||
border-left: 1px solid #e5ebf2;
|
border-left: 1px solid #e5ebf2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-list > span > small {
|
.v2-workspace-metric-action.semi-button {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 58px;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-workspace-metric-action.semi-button:hover {
|
||||||
|
background: rgba(31, 111, 235, .045);
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-workspace-metric-action.semi-button:focus-visible {
|
||||||
|
outline: 2px solid rgba(31, 111, 235, .42);
|
||||||
|
outline-offset: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-workspace-metric-action .semi-button-content {
|
||||||
|
display: grid;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 58px;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
|
grid-template-rows: auto auto;
|
||||||
|
align-content: center;
|
||||||
|
column-gap: 10px;
|
||||||
|
padding: 7px 13px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-workspace-metric-list > span > small,
|
||||||
|
.v2-workspace-metric-action .semi-button-content > small {
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
color: #78879a;
|
color: #78879a;
|
||||||
font-size: 9px;
|
font-size: 9px;
|
||||||
@@ -16230,7 +16266,8 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-list > span > strong {
|
.v2-workspace-metric-list > span > strong,
|
||||||
|
.v2-workspace-metric-action .semi-button-content > strong {
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
color: #243950;
|
color: #243950;
|
||||||
font-size: 21px;
|
font-size: 21px;
|
||||||
@@ -16239,7 +16276,8 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-list > span > em {
|
.v2-workspace-metric-list > span > em,
|
||||||
|
.v2-workspace-metric-action .semi-button-content > em {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
grid-column: 2;
|
grid-column: 2;
|
||||||
grid-row: 1 / 3;
|
grid-row: 1 / 3;
|
||||||
@@ -16252,7 +16290,8 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-list > span.is-secondary > strong {
|
.v2-workspace-metric-list > span.is-secondary > strong,
|
||||||
|
.v2-workspace-metric-list > span.is-secondary .v2-workspace-metric-action strong {
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16261,7 +16300,8 @@
|
|||||||
box-shadow: inset 0 -3px #1f6feb;
|
box-shadow: inset 0 -3px #1f6feb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-list > span.is-primary > :is(small, strong) {
|
.v2-workspace-metric-list > span.is-primary > :is(small, strong),
|
||||||
|
.v2-workspace-metric-list > span.is-primary .v2-workspace-metric-action :is(small, strong) {
|
||||||
color: #155fc5;
|
color: #155fc5;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16270,18 +16310,44 @@
|
|||||||
box-shadow: inset 0 -3px #e2554f;
|
box-shadow: inset 0 -3px #e2554f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-list > span.is-danger > :is(small, strong) {
|
.v2-workspace-metric-list > span.is-danger > :is(small, strong),
|
||||||
|
.v2-workspace-metric-list > span.is-danger .v2-workspace-metric-action :is(small, strong) {
|
||||||
color: #c2413d;
|
color: #c2413d;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-list > span.is-success > :is(small, strong) {
|
.v2-workspace-metric-list > span.is-success > :is(small, strong),
|
||||||
|
.v2-workspace-metric-list > span.is-success .v2-workspace-metric-action :is(small, strong) {
|
||||||
color: #148459;
|
color: #148459;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-list > span.is-warning > :is(small, strong) {
|
.v2-workspace-metric-list > span.is-warning > :is(small, strong),
|
||||||
|
.v2-workspace-metric-list > span.is-warning .v2-workspace-metric-action :is(small, strong) {
|
||||||
color: #a76512;
|
color: #a76512;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.v2-workspace-metric-list > span.is-action {
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-workspace-metric-list > span.is-action.is-danger {
|
||||||
|
background: linear-gradient(145deg, #fff 0%, #fff8f7 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-workspace-metric-list > span.is-action.is-warning {
|
||||||
|
background: linear-gradient(145deg, #fff 0%, #fffcf5 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-workspace-metric-list > span.is-action.is-active {
|
||||||
|
background: linear-gradient(145deg, #edf5ff 0%, #f8fbff 100%);
|
||||||
|
box-shadow: inset 0 -3px #1f6feb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-workspace-metric-list > span.is-action.is-active.is-danger {
|
||||||
|
background: linear-gradient(145deg, #fff1ef 0%, #fff8f7 100%);
|
||||||
|
box-shadow: inset 0 -3px #e2554f;
|
||||||
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-context {
|
.v2-workspace-metric-context {
|
||||||
display: flex;
|
display: flex;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@@ -16320,6 +16386,68 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.v2-alert-metric-rail.is-queue.semi-card {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
border: 1px solid #dce4ed;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 12px rgba(38, 56, 82, .035);
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-alert-metric-rail.is-queue > .semi-card-body {
|
||||||
|
grid-template-columns: minmax(0, 1fr) 286px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-alert-metric-rail .v2-workspace-metric-context {
|
||||||
|
padding: 5px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-alert-metric-context-actions {
|
||||||
|
display: grid;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-alert-metric-context-actions > .semi-button {
|
||||||
|
height: 46px;
|
||||||
|
min-width: 0;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 7px;
|
||||||
|
padding: 4px 5px;
|
||||||
|
color: #718096;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-alert-metric-context-actions > .semi-button:hover,
|
||||||
|
.v2-alert-metric-context-actions > .semi-button[aria-pressed="true"] {
|
||||||
|
border-color: #d5e3f8;
|
||||||
|
background: #eef5ff;
|
||||||
|
color: #155fc5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-alert-metric-context-actions .semi-button-content {
|
||||||
|
display: grid;
|
||||||
|
justify-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-alert-metric-context-actions span {
|
||||||
|
font-size: 9px;
|
||||||
|
line-height: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-alert-metric-context-actions strong {
|
||||||
|
color: #34455c;
|
||||||
|
font-size: 14px;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
line-height: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-alert-metric-context-actions > .semi-button[aria-pressed="true"] strong {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
.v2-reconcile-metric-rail {
|
.v2-reconcile-metric-rail {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
@@ -16344,6 +16472,11 @@
|
|||||||
column-gap: 6px;
|
column-gap: 6px;
|
||||||
padding-inline: 9px;
|
padding-inline: 9px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.v2-workspace-metric-action .semi-button-content {
|
||||||
|
column-gap: 6px;
|
||||||
|
padding-inline: 9px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 680px) {
|
@media (max-width: 680px) {
|
||||||
@@ -16363,22 +16496,44 @@
|
|||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.v2-workspace-metric-action.semi-button,
|
||||||
|
.v2-workspace-metric-action .semi-button-content {
|
||||||
|
min-height: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-workspace-metric-action .semi-button-content {
|
||||||
|
column-gap: 8px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-list > span.is-secondary {
|
.v2-workspace-metric-list > span.is-secondary {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-list > span > strong {
|
.v2-workspace-metric-list > span > strong,
|
||||||
|
.v2-workspace-metric-action .semi-button-content > strong {
|
||||||
font-size: 19px;
|
font-size: 19px;
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-list > span > em {
|
.v2-workspace-metric-list > span > em,
|
||||||
|
.v2-workspace-metric-action .semi-button-content > em {
|
||||||
font-size: 8px;
|
font-size: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-workspace-metric-context {
|
.v2-workspace-metric-context {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.v2-alert-metric-rail.is-queue.semi-card {
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 681px) and (max-width: 900px) {
|
||||||
|
.v2-alert-metric-rail.is-queue > .semi-card-body {
|
||||||
|
grid-template-columns: minmax(0, 1fr) 230px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-reconcile-issue-cell > span {
|
.v2-reconcile-issue-cell > span {
|
||||||
|
|||||||
Reference in New Issue
Block a user