fix(ui): пояснять отсутствие отдельного направления трафика
This commit is contained in:
1 parent
ec8e28d15d
commit
ccbc1aa029
3 files changed
+36
-7
No files matched your search
@@ -82,11 +82,14 @@ export function TrafficRxTxChart({
|
|||||||
const data = toChartData(rx, tx, rangeMinutes)
|
const data = toChartData(rx, tx, rangeMinutes)
|
||||||
const tickEvery = Math.max(1, Math.ceil(data.length / 6))
|
const tickEvery = Math.max(1, Math.ceil(data.length / 6))
|
||||||
|
|
||||||
const hasData = data.some(point => point.rx !== null || point.tx !== null)
|
const hasRx = data.some(point => point.rx !== null)
|
||||||
|
const hasTx = data.some(point => point.tx !== null)
|
||||||
|
const hasData = hasRx || hasTx
|
||||||
const chart = !hasData ? (
|
const chart = !hasData ? (
|
||||||
<p role="status" className="flex min-h-[220px] items-center justify-center text-sm text-muted-foreground">Нет исторических измерений за выбранный период</p>
|
<p role="status" className="flex min-h-[220px] items-center justify-center text-sm text-muted-foreground">Нет исторических измерений за выбранный период</p>
|
||||||
) : (
|
) : (
|
||||||
<div role="region" aria-label="История RX/TX" className="flex flex-col gap-4">
|
<div role="region" aria-label="История RX/TX" className="flex flex-col gap-4">
|
||||||
|
{(!hasRx || !hasTx) && <p role="status" className="text-sm text-muted-foreground">{!hasRx ? "Нет измерений RX за выбранный период" : "Нет измерений TX за выбранный период"}</p>}
|
||||||
<ChartContainer config={chartConfig} className="-ms-4 aspect-auto h-[220px] w-full">
|
<ChartContainer config={chartConfig} className="-ms-4 aspect-auto h-[220px] w-full">
|
||||||
<LineChart data={data} margin={{ top: 5, right: 5, left: 5, bottom: 5 }}>
|
<LineChart data={data} margin={{ top: 5, right: 5, left: 5, bottom: 5 }}>
|
||||||
<CartesianGrid
|
<CartesianGrid
|
||||||
@@ -111,20 +114,20 @@ export function TrafficRxTxChart({
|
|||||||
width={72}
|
width={72}
|
||||||
/>
|
/>
|
||||||
<ChartTooltip content={<CustomTooltip />} />
|
<ChartTooltip content={<CustomTooltip />} />
|
||||||
<Line
|
{hasRx && <Line
|
||||||
dataKey="rx"
|
dataKey="rx"
|
||||||
type="monotone"
|
type="monotone"
|
||||||
stroke="var(--chart-rx)"
|
stroke="var(--chart-rx)"
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
dot={false}
|
dot={false}
|
||||||
/>
|
/>}
|
||||||
<Line
|
{hasTx && <Line
|
||||||
dataKey="tx"
|
dataKey="tx"
|
||||||
type="monotone"
|
type="monotone"
|
||||||
stroke="var(--chart-tx)"
|
stroke="var(--chart-tx)"
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
dot={false}
|
dot={false}
|
||||||
/>
|
/>}
|
||||||
</LineChart>
|
</LineChart>
|
||||||
</ChartContainer>
|
</ChartContainer>
|
||||||
<div className="mb-1 flex items-center justify-center gap-6">
|
<div className="mb-1 flex items-center justify-center gap-6">
|
||||||
|
|||||||
@@ -29,6 +29,13 @@
|
|||||||
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат |
|
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат |
|
||||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат |
|
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат |
|
||||||
|
|
||||||
|
## Блок 8.4Е — отдельные отсутствующие направления RX/TX (2026-09-24)
|
||||||
|
|
||||||
|
- Общий график поясняет отсутствие RX или TX, если второе направление содержит измерения. Линия отсутствующего направления не создаётся; подсказка содержит только доступное направление.
|
||||||
|
- 7 E2E деталей прошли, включая два новых сценария: одна серия отсутствует, остаётся одна линия, при наведении нет отсутствующего значения и виден доступный показатель. Это проверка полностью отсутствующего направления; разрывы внутри ряда ещё не проверялись отдельным E2E.
|
||||||
|
- Дополнительно прошли 24 E2E дашборда/flow, покрывающие потребителей общего графика. Lint графика/теста и diff-check чистые. Production build/TypeScript прошли (34 страницы).
|
||||||
|
- Далее: мини-графики карточек, разрывы внутри исторического ряда и свежесть; смена backend/сервера и поздний SSE, затем приёмка этапа 8. Реальные IPFIX/RouterOS не проверены. Использовано 20% пятичасового и 81% недельного лимита; локальный коммит без push.
|
||||||
|
|
||||||
## Блок 8.4Д — пустая история RX/TX (2026-09-24)
|
## Блок 8.4Д — пустая история RX/TX (2026-09-24)
|
||||||
|
|
||||||
- Live-преобразования серверов, клиентов и интерфейсов сохраняют пустые массивы истории вместо заполнения 60 нулями. Общий TrafficRxTxChart показывает «Нет исторических измерений за выбранный период», если обе серии не содержат валидных точек.
|
- Live-преобразования серверов, клиентов и интерфейсов сохраняют пустые массивы истории вместо заполнения 60 нулями. Общий TrafficRxTxChart показывает «Нет исторических измерений за выбранный период», если обе серии не содержат валидных точек.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { test, expect, type Page } from '@playwright/test'
|
import { test, expect, type Page } from '@playwright/test'
|
||||||
|
|
||||||
async function setup(page: Page) {
|
async function setup(page: Page) {
|
||||||
const control = { holdRange: '', failIface: false, series: undefined as number[] | undefined, release: () => {} }
|
const control = { holdRange: '', failIface: false, series: undefined as number[] | undefined, txSeries: undefined as number[] | undefined, release: () => {} }
|
||||||
const gate = new Promise<void>(resolve => { control.release = resolve })
|
const gate = new Promise<void>(resolve => { control.release = resolve })
|
||||||
const server = { id: '1', name: 'Первый сервер', site: 'TEST', country: 'RU', status: 'online', rxNow: 2, txNow: 3, rxPeak: 5, txPeak: 6, rxTotal: 100, txTotal: 200, sessions: 1, rxSeries: [1, 2], txSeries: [2, 3] }
|
const server = { id: '1', name: 'Первый сервер', site: 'TEST', country: 'RU', status: 'online', rxNow: 2, txNow: 3, rxPeak: 5, txPeak: 6, rxTotal: 100, txTotal: 200, sessions: 1, rxSeries: [1, 2], txSeries: [2, 3] }
|
||||||
await page.route('**/health', route => route.fulfill({ json: { ok: true } }))
|
await page.route('**/health', route => route.fulfill({ json: { ok: true } }))
|
||||||
@@ -12,7 +12,7 @@ async function setup(page: Page) {
|
|||||||
const range = url.searchParams.get('range'), iface = url.searchParams.get('iface')
|
const range = url.searchParams.get('range'), iface = url.searchParams.get('iface')
|
||||||
if (control.holdRange && range === control.holdRange) await gate
|
if (control.holdRange && range === control.holdRange) await gate
|
||||||
if (control.failIface && iface === 'ether2') return route.fulfill({ status: 500, json: { error: 'Интерфейс временно недоступен' } })
|
if (control.failIface && iface === 'ether2') return route.fulfill({ status: 500, json: { error: 'Интерфейс временно недоступен' } })
|
||||||
return route.fulfill({ json: { server: { ...server, rxSeries: control.series ?? server.rxSeries, txSeries: control.series ?? server.txSeries, name: `Детали ${range} ${iface}`, rxNow: range === '24h' ? 24 : 15 } } })
|
return route.fulfill({ json: { server: { ...server, rxSeries: control.series ?? server.rxSeries, txSeries: control.txSeries ?? control.series ?? server.txSeries, name: `Детали ${range} ${iface}`, rxNow: range === '24h' ? 24 : 15 } } })
|
||||||
}
|
}
|
||||||
const json = path === '/api/auth/config' ? { required: false }
|
const json = path === '/api/auth/config' ? { required: false }
|
||||||
: path === '/api/traffic/servers' ? { servers: [server] }
|
: path === '/api/traffic/servers' ? { servers: [server] }
|
||||||
@@ -85,3 +85,22 @@ test('Трафик: пустая история отличается от нул
|
|||||||
await expect(page.getByRole('region', { name: 'История RX/TX' })).toBeVisible()
|
await expect(page.getByRole('region', { name: 'История RX/TX' })).toBeVisible()
|
||||||
await expect(page.getByText('Нет исторических измерений за выбранный период', { exact: true })).toHaveCount(0)
|
await expect(page.getByText('Нет исторических измерений за выбранный период', { exact: true })).toHaveCount(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
for (const missing of ['RX', 'TX'] as const) {
|
||||||
|
test('Трафик: отсутствие ' + missing + ' не подменяется нулевой линией', async ({ page }) => {
|
||||||
|
const control = await setup(page)
|
||||||
|
control.series = missing === 'RX' ? [] : [2, 3]
|
||||||
|
control.txSeries = missing === 'TX' ? [] : [2, 3]
|
||||||
|
await page.getByRole('button', { name: '24ч', exact: true }).click()
|
||||||
|
const chart = page.getByRole('region', { name: 'История RX/TX' })
|
||||||
|
await expect(chart.getByRole('status')).toHaveText('Нет измерений ' + missing + ' за выбранный период')
|
||||||
|
await expect(chart.locator('.recharts-line-curve')).toHaveCount(1)
|
||||||
|
const plot = chart.locator('.recharts-wrapper')
|
||||||
|
await plot.scrollIntoViewIfNeeded()
|
||||||
|
const box = await plot.boundingBox()
|
||||||
|
await page.mouse.move(box!.x + box!.width - 12, box!.y + box!.height / 2)
|
||||||
|
await expect(chart.getByText(missing + ':', { exact: true })).toHaveCount(0)
|
||||||
|
await expect(chart.getByText((missing === 'RX' ? 'TX' : 'RX') + ':', { exact: true })).toBeVisible()
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in new issue
Block a user