feat(platform): expose missing sources per vehicle
This commit is contained in:
@@ -92,6 +92,18 @@ func TestHandlerVehicleCoverage(t *testing.T) {
|
|||||||
if body.Data.Items[0].ServiceStatus.Status != "degraded" {
|
if body.Data.Items[0].ServiceStatus.Status != "degraded" {
|
||||||
t.Fatalf("coverage row should expose degraded status, got %+v", body.Data.Items[0].ServiceStatus)
|
t.Fatalf("coverage row should expose degraded status, got %+v", body.Data.Items[0].ServiceStatus)
|
||||||
}
|
}
|
||||||
|
var rawBody struct {
|
||||||
|
Data Page[struct {
|
||||||
|
VIN string `json:"vin"`
|
||||||
|
MissingProtocols []string `json:"missingProtocols"`
|
||||||
|
}] `json:"data"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(rec.Body.Bytes(), &rawBody); err != nil {
|
||||||
|
t.Fatalf("response JSON should decode missing source evidence: %v body=%s", err, rec.Body.String())
|
||||||
|
}
|
||||||
|
if len(rawBody.Data.Items) == 0 || !containsString(rawBody.Data.Items[0].MissingProtocols, "YUTONG_MQTT") {
|
||||||
|
t.Fatalf("coverage row should expose missing canonical protocols, got %+v body=%s", rawBody.Data.Items, rec.Body.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandlerVehicleCoverageFiltersServiceStatus(t *testing.T) {
|
func TestHandlerVehicleCoverageFiltersServiceStatus(t *testing.T) {
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ func (m *MockStore) VehicleCoverage(_ context.Context, query url.Values) (Page[V
|
|||||||
}
|
}
|
||||||
items := make([]VehicleCoverageRow, 0, len(byVIN))
|
items := make([]VehicleCoverageRow, 0, len(byVIN))
|
||||||
for _, row := range byVIN {
|
for _, row := range byVIN {
|
||||||
|
row.MissingProtocols = missingCanonicalProtocols(row.Protocols)
|
||||||
row.ServiceStatus = buildVehicleCoverageServiceStatus(*row)
|
row.ServiceStatus = buildVehicleCoverageServiceStatus(*row)
|
||||||
if keepCoverageRow(*row, query) {
|
if keepCoverageRow(*row, query) {
|
||||||
items = append(items, *row)
|
items = append(items, *row)
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ type VehicleCoverageRow struct {
|
|||||||
Phone string `json:"phone"`
|
Phone string `json:"phone"`
|
||||||
OEM string `json:"oem"`
|
OEM string `json:"oem"`
|
||||||
Protocols []string `json:"protocols"`
|
Protocols []string `json:"protocols"`
|
||||||
|
MissingProtocols []string `json:"missingProtocols"`
|
||||||
SourceCount int `json:"sourceCount"`
|
SourceCount int `json:"sourceCount"`
|
||||||
OnlineSourceCount int `json:"onlineSourceCount"`
|
OnlineSourceCount int `json:"onlineSourceCount"`
|
||||||
Online bool `json:"online"`
|
Online bool `json:"online"`
|
||||||
|
|||||||
@@ -268,6 +268,7 @@ func (s *ProductionStore) VehicleCoverage(ctx context.Context, query url.Values)
|
|||||||
return Page[VehicleCoverageRow]{}, err
|
return Page[VehicleCoverageRow]{}, err
|
||||||
}
|
}
|
||||||
row.Protocols = splitCSV(protocols)
|
row.Protocols = splitCSV(protocols)
|
||||||
|
row.MissingProtocols = missingCanonicalProtocols(row.Protocols)
|
||||||
row.Online = online == 1
|
row.Online = online == 1
|
||||||
row.ServiceStatus = buildVehicleCoverageServiceStatus(row)
|
row.ServiceStatus = buildVehicleCoverageServiceStatus(row)
|
||||||
items = append(items, row)
|
items = append(items, row)
|
||||||
|
|||||||
@@ -58,6 +58,16 @@ type Service struct {
|
|||||||
|
|
||||||
var canonicalVehicleProtocols = []string{"GB32960", "JT808", "YUTONG_MQTT"}
|
var canonicalVehicleProtocols = []string{"GB32960", "JT808", "YUTONG_MQTT"}
|
||||||
|
|
||||||
|
func missingCanonicalProtocols(protocols []string) []string {
|
||||||
|
missing := make([]string, 0, len(canonicalVehicleProtocols))
|
||||||
|
for _, protocol := range canonicalVehicleProtocols {
|
||||||
|
if !containsString(protocols, protocol) {
|
||||||
|
missing = append(missing, protocol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return missing
|
||||||
|
}
|
||||||
|
|
||||||
func NewService(store Store) *Service {
|
func NewService(store Store) *Service {
|
||||||
return &Service{store: store}
|
return &Service{store: store}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export interface VehicleCoverageRow {
|
|||||||
phone: string;
|
phone: string;
|
||||||
oem: string;
|
oem: string;
|
||||||
protocols: string[];
|
protocols: string[];
|
||||||
|
missingProtocols: string[];
|
||||||
sourceCount: number;
|
sourceCount: number;
|
||||||
onlineSourceCount: number;
|
onlineSourceCount: number;
|
||||||
online: boolean;
|
online: boolean;
|
||||||
|
|||||||
@@ -125,6 +125,17 @@ export function Vehicles({
|
|||||||
</Space>
|
</Space>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '缺失来源',
|
||||||
|
width: 220,
|
||||||
|
render: (_: unknown, row: VehicleCoverageRow) => (
|
||||||
|
<Space spacing={4} wrap>
|
||||||
|
{(row.missingProtocols ?? []).length > 0
|
||||||
|
? row.missingProtocols.map((protocol) => <Tag key={protocol} color="orange">缺 {protocol}</Tag>)
|
||||||
|
: <Tag color="green">完整</Tag>}
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
|
},
|
||||||
{ title: '证据覆盖', width: 120, render: (_: unknown, row: VehicleCoverageRow) => sourceEvidenceText(row) },
|
{ title: '证据覆盖', width: 120, render: (_: unknown, row: VehicleCoverageRow) => sourceEvidenceText(row) },
|
||||||
{ title: '在线', width: 90, render: (_: unknown, row: VehicleCoverageRow) => <StatusTag status={row.online ? 'ok' : 'offline'} /> },
|
{ title: '在线', width: 90, render: (_: unknown, row: VehicleCoverageRow) => <StatusTag status={row.online ? 'ok' : 'offline'} /> },
|
||||||
{ title: '最后在线', dataIndex: 'lastSeen', width: 170 },
|
{ title: '最后在线', dataIndex: 'lastSeen', width: 170 },
|
||||||
|
|||||||
@@ -445,6 +445,7 @@ test('filters vehicle list by missing source evidence', async () => {
|
|||||||
phone: '13307795425',
|
phone: '13307795425',
|
||||||
oem: 'G7s',
|
oem: 'G7s',
|
||||||
protocols: ['GB32960', 'JT808'],
|
protocols: ['GB32960', 'JT808'],
|
||||||
|
missingProtocols: ['YUTONG_MQTT'],
|
||||||
sourceCount: 2,
|
sourceCount: 2,
|
||||||
onlineSourceCount: 0,
|
onlineSourceCount: 0,
|
||||||
online: false,
|
online: false,
|
||||||
@@ -472,9 +473,13 @@ test('filters vehicle list by missing source evidence', async () => {
|
|||||||
|
|
||||||
render(<App />);
|
render(<App />);
|
||||||
|
|
||||||
expect(await screen.findByText('缺失来源')).toBeInTheDocument();
|
await waitFor(() => {
|
||||||
|
expect(screen.getAllByText('缺失来源').length).toBeGreaterThanOrEqual(2);
|
||||||
|
});
|
||||||
|
expect(screen.getAllByText('缺 YUTONG_MQTT').length).toBeGreaterThanOrEqual(1);
|
||||||
fireEvent.click(screen.getByTestId('missing-protocol-filter'));
|
fireEvent.click(screen.getByTestId('missing-protocol-filter'));
|
||||||
fireEvent.click(await screen.findByText('缺 YUTONG_MQTT'));
|
const mqttOptions = await screen.findAllByText('缺 YUTONG_MQTT');
|
||||||
|
fireEvent.click(mqttOptions[mqttOptions.length - 1]);
|
||||||
fireEvent.click(screen.getByRole('button', { name: '查询' }));
|
fireEvent.click(screen.getByRole('button', { name: '查询' }));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user