Add metric sub-team intermediate drill-down view
Clicking a metric now shows a sub-team breakdown page with totals per team (compliant, non-compliant, total, %) instead of jumping directly to a flat device list. Clicking a sub-team then shows the device list filtered to that team only. Navigation flow: Overview → Vertical → Metric (sub-team totals) → Team (devices) Backend: added optional ?team= query param to the device list endpoint for filtered queries. Frontend: added MetricSubTeamView component with metric-level stats bar and clickable sub-team table. Updated navigation state to include selectedTeam. Also updated design brief to reflect the new drill-down hierarchy.
This commit is contained in:
@@ -796,16 +796,19 @@ function createVCLMultiVerticalRouter(upload) {
|
||||
/**
|
||||
* GET /vertical/:code/metric/:metricId/devices
|
||||
* Returns the list of non-compliant devices for a specific vertical + metric.
|
||||
* Optionally filters by team when the query parameter is provided.
|
||||
*
|
||||
* @method GET
|
||||
* @route /vertical/:code/metric/:metricId/devices
|
||||
* @param {string} code — vertical code (e.g., "NTS_AEO")
|
||||
* @param {string} metricId — metric identifier (e.g., "VM-001")
|
||||
* @query {string} [team] — optional team name to filter devices (e.g., "STEAM", "ACCESS-ENG")
|
||||
*
|
||||
* @response 200
|
||||
* {
|
||||
* vertical: string,
|
||||
* metric_id: string,
|
||||
* team: string|null,
|
||||
* devices: Array<{
|
||||
* hostname: string,
|
||||
* ip_address: string,
|
||||
@@ -828,19 +831,26 @@ function createVCLMultiVerticalRouter(upload) {
|
||||
if (!metricId || metricId.length > 50) return res.status(400).json({ error: 'Invalid metric ID' });
|
||||
|
||||
try {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT ci.hostname, ci.ip_address, ci.device_type, ci.team, ci.seen_count,
|
||||
const team = req.query.team || null;
|
||||
let query = `SELECT ci.hostname, ci.ip_address, ci.device_type, ci.team, ci.seen_count,
|
||||
ci.resolution_date, ci.remediation_plan,
|
||||
fu.report_date AS first_seen, lu.report_date AS last_seen
|
||||
FROM compliance_items ci
|
||||
LEFT JOIN compliance_uploads fu ON ci.first_seen_upload_id = fu.id
|
||||
LEFT JOIN compliance_uploads lu ON ci.upload_id = lu.id
|
||||
WHERE ci.vertical = $1 AND ci.metric_id = $2 AND ci.status = 'active'
|
||||
ORDER BY ci.hostname`,
|
||||
[vertical, metricId]
|
||||
);
|
||||
WHERE ci.vertical = $1 AND ci.metric_id = $2 AND ci.status = 'active'`;
|
||||
const params = [vertical, metricId];
|
||||
|
||||
res.json({ vertical, metric_id: metricId, devices: rows });
|
||||
if (team) {
|
||||
query += ` AND ci.team = $3`;
|
||||
params.push(team);
|
||||
}
|
||||
|
||||
query += ` ORDER BY ci.hostname`;
|
||||
|
||||
const { rows } = await pool.query(query, params);
|
||||
|
||||
res.json({ vertical, metric_id: metricId, team: team || null, devices: rows });
|
||||
} catch (err) {
|
||||
console.error('[VCL Multi] GET /vertical/:code/metric/:metricId/devices error:', err.message);
|
||||
res.status(500).json({ error: 'Database error' });
|
||||
|
||||
Reference in New Issue
Block a user