diff --git a/src/components/report-viewer-tab.jsx b/src/components/report-viewer-tab.jsx index 3edd34b91f..b201693135 100644 --- a/src/components/report-viewer-tab.jsx +++ b/src/components/report-viewer-tab.jsx @@ -14,7 +14,7 @@ import { useIntl } from 'react-intl'; import { useSelector } from 'react-redux'; import { useReportFetcher } from '../hooks/use-report-fetcher'; -import { COMPUTING_AND_NETWORK_MODIFICATION_TYPE } from '../utils/report/report.constant'; +import { COMPUTING_AND_NETWORK_MODIFICATION_TYPE, GLOBAL_REPORT_NODE_LABEL } from '../utils/report/report.constant'; import { ROOT_NODE_LABEL } from '../constants/node.constant'; import { ReportType } from 'utils/report/report.type'; import { sortSeverityList } from 'utils/report/report-severity'; @@ -87,7 +87,7 @@ export const ReportViewerTab = ({ visible, currentNode, disabled }) => { fetchReport(nodeOnlyReport).then((r) => { if (r !== undefined) { setReport(r); - fetchReportSeverities(r.id, r.parentId ? ReportType.NODE : ReportType.GLOBAL).then((severities) => { + fetchReportSeverities(r.id, r.message !== GLOBAL_REPORT_NODE_LABEL ? ReportType.NODE : ReportType.GLOBAL).then((severities) => { setSeverities(sortSeverityList(severities)); }); } diff --git a/src/components/report-viewer/log-table.tsx b/src/components/report-viewer/log-table.tsx index fc432e3ddc..758338c827 100644 --- a/src/components/report-viewer/log-table.tsx +++ b/src/components/report-viewer/log-table.tsx @@ -149,7 +149,7 @@ const LogTable = ({ setRowData([]); return; } - fetchLogs(selectedReport.id, severityFilter, messageFilter, selectedReport.type, page, rowsPerPage)?.then( + fetchLogs(selectedReport.reportId, severityFilter, messageFilter, selectedReport.type, page, rowsPerPage)?.then( (pagedLogs) => { const { content, totalElements, totalPages } = pagedLogs; if (totalPages - 1 < page) { @@ -163,7 +163,7 @@ const LogTable = ({ }, [ severityFilter, fetchLogs, - selectedReport.id, + selectedReport.reportId, selectedReport.type, messageFilter, page, @@ -327,7 +327,7 @@ const LogTable = ({ setSearchTerm(searchTerm); fetchLogMatches( - selectedReport.id, + selectedReport.reportId, severityFilter, messageFilter, selectedReport.type, @@ -348,7 +348,7 @@ const LogTable = ({ messageFilter, resetSearch, rowsPerPage, - selectedReport.id, + selectedReport.reportId, selectedReport.type, setPagination, severityFilter, diff --git a/src/components/report-viewer/report-viewer.tsx b/src/components/report-viewer/report-viewer.tsx index 3e8be84a4e..63ecd7f3de 100644 --- a/src/components/report-viewer/report-viewer.tsx +++ b/src/components/report-viewer/report-viewer.tsx @@ -18,7 +18,7 @@ import { SelectedReportLog, SeverityLevel, } from 'utils/report/report.type'; -import { GLOBAL_REPORT_NODE_LABEL } from '../../utils/report/report.constant'; +import { GLOBAL_REPORT_NODE_LABEL, makeNodeKey } from '../../utils/report/report.constant'; import { ImperativePanelGroupHandle, Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; import DragIndicatorIcon from '@mui/icons-material/DragIndicator'; import { Box } from '@mui/material'; @@ -48,9 +48,13 @@ export default function ReportViewer({ const [expandedTreeReports, setExpandedTreeReports] = useState([]); const [highlightedReportId, setHighlightedReportId] = useState(); + const isGlobal = report.message === GLOBAL_REPORT_NODE_LABEL; + const initialNodeKey = isGlobal ? GLOBAL_REPORT_NODE_LABEL : makeNodeKey(report.id, report.order); + const [selectedReport, setSelectedReport] = useState({ - id: report.id, - type: report.message === GLOBAL_REPORT_NODE_LABEL ? ReportType.GLOBAL : ReportType.NODE, + id: initialNodeKey, + reportId: report.id, + type: isGlobal ? ReportType.GLOBAL : ReportType.NODE, }); const reportTree = useMemo(() => mapReportsTree(report), [report]); @@ -70,7 +74,7 @@ export default function ReportViewer({ setSelectedReport((currentSelected) => { if (currentSelected.id !== reportTree.id || currentSelected.type !== newType) { setExpandedTreeReports([reportTree.id]); - return { id: reportTree.id, type: newType }; + return { id: reportTree.id, reportId: reportTreeMap[reportTree.id]?.reportId ?? reportTree.id, type: newType }; } return currentSelected; }); @@ -89,7 +93,7 @@ export default function ReportViewer({ } return Array.from(treeReportsToExpand); }); - setHighlightedReportId(data?.parentId); + setHighlightedReportId(data?.parentId ?? undefined); }, [reportTreeMap] ); @@ -102,7 +106,8 @@ export default function ReportViewer({ (report: ReportItem) => { setSelectedReport((prevSelectedReport) => { if (prevSelectedReport.id !== report.id) { - return { id: report.id, type: reportTreeMap[report.id].type }; + const treeNode = reportTreeMap[report.id]; + return { id: report.id, reportId: treeNode?.reportId ?? report.id, type: treeNode?.type ?? ReportType.NODE }; } return prevSelectedReport; }); diff --git a/src/hooks/use-report-fetcher.tsx b/src/hooks/use-report-fetcher.tsx index 3955cf362d..749ddb7fca 100644 --- a/src/hooks/use-report-fetcher.tsx +++ b/src/hooks/use-report-fetcher.tsx @@ -35,6 +35,8 @@ function makeSingleReportAndMapNames(report: Report | Report[], nodesNames: Map< return { message: GLOBAL_REPORT_NODE_LABEL, id: GLOBAL_REPORT_NODE_LABEL, + order: -1, + parentOrder: null, severity: getHighestSeverity(report), subReports: report.map((r) => setNodeName(r, nodesNames)), } as Report; @@ -59,13 +61,12 @@ function setNodeName(report: Report, nodesNames: Map) { if (report.message !== ROOT_NODE_LABEL) { report.message = nodesNames?.get(report.message) ?? report.message; } - report.parentId = GLOBAL_REPORT_NODE_LABEL; return report; } function prettifyReportLogMessage(reports: ReportLog[], nodesNames: Map) { reports.forEach((report) => { - if (report.parentId == null) { + if (report.parentOrder == null) { if (report.message !== ROOT_NODE_LABEL) { report.message = nodesNames?.get(report.message) ?? report.message; } @@ -200,9 +201,10 @@ export const useReportFetcher = ( page, size ).then((r: PagedReportLogs) => { + const logReportId = reportType === ReportType.GLOBAL ? null : reportId; return { ...r, - content: mapReportLogs(prettifyReportLogMessage(r.content, nodesNames)), + content: mapReportLogs(prettifyReportLogMessage(r.content, nodesNames), logReportId), }; }); }, diff --git a/src/utils/report/report-log.mapper.ts b/src/utils/report/report-log.mapper.ts index 7fcffddac6..a7d2bfb193 100644 --- a/src/utils/report/report-log.mapper.ts +++ b/src/utils/report/report-log.mapper.ts @@ -7,24 +7,27 @@ import { REPORT_SEVERITY } from './report-severity'; import { Log, ReportLog } from './report.type'; +import { makeNodeKey } from './report.constant'; -export const mapReportLogs = (reportLogs: ReportLog[]) => { +export const mapReportLogs = (reportLogs: ReportLog[], reportId?: string | null) => { const formattedLogs: Log[] = []; const minDepth = Math.min(...reportLogs.map((log) => log.depth ?? 0)); reportLogs.forEach((reportLog) => { - formatLog(minDepth, reportLog, formattedLogs); + formatLog(minDepth, reportLog, formattedLogs, reportId); }); return formattedLogs; }; -const formatLog = (minDepth: number, reportLog: ReportLog, formattedLogs: Log[]) => { +const formatLog = (minDepth: number, reportLog: ReportLog, formattedLogs: Log[], reportId?: string | null) => { const severity = Object.values(REPORT_SEVERITY).find((s) => reportLog.severity === s.name) ?? REPORT_SEVERITY.UNKNOWN; + const parentId = + reportLog.parentOrder != null && reportId ? makeNodeKey(reportId, reportLog.parentOrder) : null; formattedLogs.push({ message: reportLog.message, severity: severity.name, backgroundColor: severity.colorName, depth: (reportLog.depth ?? 0) - minDepth, - parentId: reportLog.parentId, + parentId, }); }; diff --git a/src/utils/report/report-tree.mapper.ts b/src/utils/report/report-tree.mapper.ts index 96c699733a..b6c1ff4e0c 100644 --- a/src/utils/report/report-tree.mapper.ts +++ b/src/utils/report/report-tree.mapper.ts @@ -6,18 +6,23 @@ */ import { Report, ReportTree, ReportType } from './report.type'; -import { GLOBAL_REPORT_NODE_LABEL } from './report.constant'; +import { GLOBAL_REPORT_NODE_LABEL, makeNodeKey } from './report.constant'; import { REPORT_SEVERITY } from './report-severity'; -export function mapReportsTree(report: Report, reportType?: ReportType): ReportTree { +export function mapReportsTree(report: Report, reportType?: ReportType, parentNodeKey?: string | null): ReportTree { + const isGlobal = report.message === GLOBAL_REPORT_NODE_LABEL; + const nodeKey = isGlobal ? GLOBAL_REPORT_NODE_LABEL : makeNodeKey(report.id, report.order); + return { - type: reportType ?? (report.message === GLOBAL_REPORT_NODE_LABEL ? ReportType.GLOBAL : ReportType.NODE), - id: report.id, + type: reportType ?? (isGlobal ? ReportType.GLOBAL : ReportType.NODE), + id: nodeKey, + reportId: report.id, + order: report.order, message: report.message, - parentId: report.parentId, + parentId: parentNodeKey ?? null, severity: Object.values(REPORT_SEVERITY).find((s) => report.severity === s.name) ?? REPORT_SEVERITY.UNKNOWN, subReports: report.subReports .filter((subReport) => subReport.subReports.length > 0 || subReport.id) - .map((subReport) => mapReportsTree(subReport, ReportType.NODE)), + .map((subReport) => mapReportsTree(subReport, ReportType.NODE, nodeKey)), } satisfies ReportTree; } diff --git a/src/utils/report/report.constant.ts b/src/utils/report/report.constant.ts index bfdcf4f7f5..b13e6c3a91 100644 --- a/src/utils/report/report.constant.ts +++ b/src/utils/report/report.constant.ts @@ -14,3 +14,11 @@ export const COMPUTING_AND_NETWORK_MODIFICATION_TYPE = { ...ComputingType, NETWORK_MODIFICATION: NETWORK_MODIFICATION, }; + +/** + * Build a unique node key for the report tree from the report UUID and the node's order. + * All nodes within a single report share the same UUID; order makes them unique. + */ +export function makeNodeKey(reportId: string, order: number): string { + return `${reportId}_${order}`; +} diff --git a/src/utils/report/report.type.ts b/src/utils/report/report.type.ts index 33c7d0adc7..e94b6ac457 100644 --- a/src/utils/report/report.type.ts +++ b/src/utils/report/report.type.ts @@ -29,14 +29,17 @@ export enum ReportType { interface BaseReport { message: string; severity: SeverityLevel; - parentId: string | null; + parentOrder: number | null; id: string; + order: number; subReports: T[]; } -export interface ReportTree extends Omit, 'severity'> { +export interface ReportTree extends Omit, 'severity' | 'parentOrder'> { type: ReportType; severity: ReportSeverity; + reportId: string; + parentId: string | null; } export interface Report extends BaseReport {} @@ -46,19 +49,20 @@ export type Log = { severity: string; backgroundColor: string; depth: number; - parentId: string; + parentId: string | null; }; export type ReportLog = { message: string; severity: SeverityLevel; depth: number; - parentId: string; + parentOrder: number | null; backgroundColor?: string; }; export type SelectedReportLog = { id: string; + reportId: string; type: ReportType; };