diff --git a/client/src/api/managment-service.ts b/client/src/api/managment-service.ts index 1a5b0d2f..79b72ca0 100644 --- a/client/src/api/managment-service.ts +++ b/client/src/api/managment-service.ts @@ -52,6 +52,7 @@ export interface ReportAwaitingGeneration { reportType: ReportType; sinceMs: number; toMs: number; + requestedAtMs: number; [key: string]: string | number; } @@ -70,13 +71,14 @@ export interface ReportDetails { id: string; clusterId: string; title: string; - urgency: UrgencyLevel; + urgency: UrgencyLevel | null; + requestedAtMs: number; + sinceMs: number; + toMs: number; totalApplicationEntries: number; totalNodeEntries: number; analyzedApplications: number; analyzedNodes: number; - sinceMs: number; - toMs: number; } export interface ClusterSummary { @@ -162,7 +164,8 @@ export interface ApplicationIncident { urgency: UrgencyLevel; accuracy: AccuracyLevel; recommendation: string; - sources: ApplicationIncidentSource[]; + sinceMs: number; + toMs: number; } export interface ApplicationIncidentSource { @@ -184,7 +187,8 @@ export interface NodeIncident { accuracy: AccuracyLevel; summary: string; recommendation: string; - sources: NodeIncidentSource[]; + sinceMs: number; + toMs: number; } export interface AllIncidentsFromReport { @@ -508,6 +512,16 @@ class ManagmentServiceApi { return response.data; } + public async getLatestReport(): Promise< + ReportDetails + > { + await this.refreshTokenIfExpired(); + const response = await this.axiosInstance.get( + '/api/v1/reports/latest', + ); + return response.data; + } + public async getApplicationIncident( id: string, ): Promise { diff --git a/client/src/assets/cycle-icon.svg b/client/src/assets/cycle-icon.svg new file mode 100644 index 00000000..07efdb2f --- /dev/null +++ b/client/src/assets/cycle-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/assets/hive-icon.svg b/client/src/assets/hive-icon.svg new file mode 100644 index 00000000..22d692f4 --- /dev/null +++ b/client/src/assets/hive-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/components/SVGIcon/SVGIcon.scss b/client/src/components/SVGIcon/SVGIcon.scss index 721670bc..c1a84c60 100644 --- a/client/src/components/SVGIcon/SVGIcon.scss +++ b/client/src/components/SVGIcon/SVGIcon.scss @@ -38,6 +38,16 @@ mask: url('/src/assets/clusters-icon.svg') no-repeat center; } +.cycle-icon { + @extend .svg-icon; + mask: url('/src/assets/cycle-icon.svg') no-repeat center; +} + +.hive-icon { + @extend .svg-icon; + mask: url('/src/assets/hive-icon.svg') no-repeat center; +} + .clusters-icon--white { @extend .svg-icon; mask: url('/src/assets/clusters-icon.svg') no-repeat center; diff --git a/client/src/hooks/useReportStats.tsx b/client/src/hooks/useReportStats.tsx index 340053de..b629d72b 100644 --- a/client/src/hooks/useReportStats.tsx +++ b/client/src/hooks/useReportStats.tsx @@ -1,8 +1,8 @@ import { + UrgencyLevel, AllIncidentsFromReport, ManagmentServiceApiInstance, ReportDetails, - UrgencyLevel, } from 'api/managment-service'; import { groupBy } from 'lib/arrays'; import { useEffect, useState } from 'react'; @@ -76,43 +76,25 @@ const groupIncidentsByNode = ( return groupBy(incidents, (incident) => incident.nodeName); }; -const useReportDetails = (reportId: string | null) => { - const [report, setReport] = useState(null); +export const useReportDetails = (report: ReportDetails | null) => { const [incidents, setIncidents] = useState( null, ); - const [isReportLoading, setIsReportLoading] = useState(true); const [areIncidentsLoading, setAreIncidentsLoading] = useState(true); const [incidentStats, setIncidentStats] = useState( null, ); useEffect(() => { - if (!reportId) return; - - const fetchReport = async (id: string) => { - try { - const reportData = await ManagmentServiceApiInstance.getReport(id); - setReport(reportData); - setIsReportLoading(false); - } catch (e: unknown) { - console.error('Failed to fetch report by id', id); - } - }; - - fetchReport(reportId); - }, [reportId]); - - useEffect(() => { - if (!report || !reportId) return; + if (!report) return; const fetchIncidents = async () => { try { const incidentsData = - await ManagmentServiceApiInstance.getIncidentsFromReport(reportId); + await ManagmentServiceApiInstance.getIncidentsFromReport(report.id); setIncidents(incidentsData); setAreIncidentsLoading(false); } catch (e: unknown) { - console.error('Failed to fetch report by id', reportId); + console.error('Failed to fetch report by id', report.id); } }; @@ -121,7 +103,7 @@ const useReportDetails = (reportId: string | null) => { } fetchIncidents(); - }, [reportId, report]); + }, [report]); useEffect(() => { if (!incidents) return; @@ -179,11 +161,9 @@ const useReportDetails = (reportId: string | null) => { return { incidents, - report: report, incidentStats, areIncidentsLoading, - isReportLoading, }; }; -export default useReportDetails; +export default useReportDetails; \ No newline at end of file diff --git a/client/src/pages/Clusters/Clusters.tsx b/client/src/pages/Clusters/Clusters.tsx index 1647675d..305b7634 100644 --- a/client/src/pages/Clusters/Clusters.tsx +++ b/client/src/pages/Clusters/Clusters.tsx @@ -1,10 +1,10 @@ import SectionComponent from 'components/SectionComponent/SectionComponent'; import PageTemplate from 'components/PageTemplate/PageTemplate'; import HeaderWithIcon from 'components/PageTemplate/components/HeaderWithIcon/HeaderWithIcon'; -import Table, { TableColumn } from 'components/Table/Table'; +import Table, {TableColumn} from 'components/Table/Table'; import './Clusters.scss'; import Channels from './components/NotificationChannelsColumn/NotificationChannelsColumn'; -import { useEffect, useState } from 'react'; +import {useEffect, useState} from 'react'; import { ClusterSummary, ManagmentServiceApiInstance, @@ -15,7 +15,7 @@ import SVGIcon from 'components/SVGIcon/SVGIcon'; import LinkComponent from 'components/LinkComponent/LinkComponent.tsx'; import ReportActionsCell from './ReportActionsCell'; import AccuracyBadge from 'components/AccuracyBadge/AccuracyBadge.tsx'; -import { dateTimeFromTimestampMs } from 'lib/date.ts'; +import {dateTimeFromTimestampMs} from 'lib/date.ts'; import CenteredSpinner from 'components/CenteredSpinner/CenteredSpinner'; interface ClusterDataRow { @@ -79,18 +79,18 @@ const columns: Array> = [ header: 'Notification', columnKey: 'notificationChannels', customComponent: ({ - notificationChannels, - }: { + notificationChannels, + }: { notificationChannels: NotificationChannelColumn[]; }) => { - return ; + return ; }, }, { header: 'Accuracy', columnKey: 'accuracy', customComponent: (row: ClusterDataRow) => { - return ; + return ; }, }, { @@ -101,7 +101,7 @@ const columns: Array> = [ header: 'Reports', columnKey: 'actions', customComponent: (row: ClusterDataRow) => { - return ; + return ; }, }, ]; @@ -135,17 +135,20 @@ const Clusters = () => { fetchClusters(); }, []); - const header = ; + const header = } + />; return ( } + icon={} > - {isLoading && } + {isLoading && } {!isLoading && clusters.length > 0 && ( - +
)} {!isLoading && clusters.length === 0 && (
No registered clusters yet
diff --git a/client/src/pages/Home/Home.tsx b/client/src/pages/Home/Home.tsx index c1966c5f..27f3f007 100644 --- a/client/src/pages/Home/Home.tsx +++ b/client/src/pages/Home/Home.tsx @@ -1,46 +1,40 @@ -import { ManagmentServiceApiInstance } from 'api/managment-service'; +import {ManagmentServiceApiInstance, ReportDetails} from 'api/managment-service'; import PageTemplate from 'components/PageTemplate/PageTemplate'; import HeaderWithIcon from 'components/PageTemplate/components/HeaderWithIcon/HeaderWithIcon'; -import useReportDetails from 'hooks/useReportStats'; - import { useEffect, useState } from 'react'; import ReportDetailsSection from './components/ReportDetailsSection/ReportDetailsSection'; +import useReportDetails from 'hooks/useReportStats'; const Home = () => { - const [lastReportId, setLastReportId] = useState(null); + const [lastReport, setLastReport] = useState(null); + const [isReportLoading, setIsReportLoading] = useState(true); useEffect(() => { - const fetchReports = async () => { + const getLatestReport = async () => { try { - const [onDemandReports, scheduledReports] = await Promise.all([ - ManagmentServiceApiInstance.getReports('ON_DEMAND'), - ManagmentServiceApiInstance.getReports('SCHEDULED'), - ]); - const reports = [...onDemandReports, ...scheduledReports]; - if (reports.length > 0) { - reports.sort((a, b) => b.requestedAtMs - a.requestedAtMs); - setLastReportId(reports[0].id); + const latestReport = await ManagmentServiceApiInstance.getLatestReport(); + if (latestReport) { + setLastReport(latestReport); } } catch (e: unknown) { - console.error('Failed to fetch reports'); + console.error('Failed to fetch the latest report', e); } }; - fetchReports(); + getLatestReport(); + setIsReportLoading(false); }, []); const { incidents, - report, incidentStats, areIncidentsLoading, - isReportLoading, - } = useReportDetails(lastReportId); + } = useReportDetails(lastReport); return ( }> ; } + return ( } + icon={} title={
- {areIncidentsLoading && } + {areIncidentsLoading && } {incidents && incidentStats && (
@@ -170,4 +171,4 @@ const ReportDetailsSection = ({ ); }; -export default ReportDetailsSection; +export default ReportDetailsSection; \ No newline at end of file diff --git a/client/src/pages/Incident/ApplicationIncident.tsx b/client/src/pages/Incident/ApplicationIncident.tsx index cce5442a..38520afc 100644 --- a/client/src/pages/Incident/ApplicationIncident.tsx +++ b/client/src/pages/Incident/ApplicationIncident.tsx @@ -14,7 +14,6 @@ import RecommendationSection from './components/RecommendationSection/Recommenda // eslint-disable-next-line import ApplicationSourceSection from './components/ApplicationSourceSection/ApplicationSourceSection'; import IncidentHeader from './components/IncidentHeader/IncidentHeader'; -import { getFirstAndLastDateFromTimestamps } from 'lib/date'; import ConfigurationSection from './components/ConfigurationSection/ConfigurationSection'; import { useTransition, animated } from '@react-spring/web'; import useInfiniteScroll from 'hooks/useInfiniteScroll'; @@ -117,14 +116,10 @@ const ApplicationIncidentPage = () => { ); } - const [startDate, endDate] = getFirstAndLastDateFromTimestamps( - incident.sources.map(({ timestamp }) => timestamp), - ); - return ( + } scrollRef={pageTemplateRef} > @@ -134,8 +129,8 @@ const ApplicationIncidentPage = () => { { ); } - const [startDate, endDate] = getFirstAndLastDateFromTimestamps( - incident.sources.map(({ timestamp }) => timestamp), - ); - return ( + } scrollRef={pageTemplateRef} > @@ -130,8 +125,8 @@ const NodeIncidentPage = () => {
= ({ return ( } + icon={} title="Applications" callback={() => handleOpenModal()} > diff --git a/client/src/pages/Report/NodesSection/NodesSection.tsx b/client/src/pages/Report/NodesSection/NodesSection.tsx index 9cce60e2..eed4ee35 100644 --- a/client/src/pages/Report/NodesSection/NodesSection.tsx +++ b/client/src/pages/Report/NodesSection/NodesSection.tsx @@ -149,7 +149,7 @@ const NodesSection: React.FC = ({ return ( } + icon={} title={'Nodes'} callback={() => handleOpenModal()} > diff --git a/client/src/pages/Report/StateSection/ReportGenerationType.tsx b/client/src/pages/Report/StateSection/ReportGenerationType.tsx index 7d4d2b45..bebc103b 100644 --- a/client/src/pages/Report/StateSection/ReportGenerationType.tsx +++ b/client/src/pages/Report/StateSection/ReportGenerationType.tsx @@ -17,7 +17,7 @@ const ReportGenerationType: React.FC = ({ setParentGe }; return ( - } title={'Generation type'}> + } title={'Generation type'}>
{ - const { id } = useParams(); - const { incidents, report, incidentStats, isReportLoading } = - useReportDetails(id!); + const {id} = useParams(); + const [report, setReport] = useState(null); + const [isReportLoading, setIsReportLoading] = useState(true); + const {incidents, incidentStats} = + useReportDetails(report); + + useEffect(() => { + if (!id) { + return; + } + const fetchReport = async (reportId: string) => { + try { + const reportData = await ManagmentServiceApiInstance.getReport(reportId); + setReport(reportData); + } catch (e: unknown) { + console.error('Failed to fetch report by id', id); + } + }; + + fetchReport(id); + setIsReportLoading(false); + }, [id]); const navigate = useNavigate(); if (isReportLoading || !report || !incidents || !incidentStats) { return ( - + ); } @@ -129,7 +149,7 @@ const ReportDetailsPage = () => { >
} + icon={} title={'Statistics'} > { /> } + icon={} title={'Application incidents'} > + onClick={({id: incidentId}) => navigate(`/application-incidents/${incidentId}`) } /> } + icon={} title={'Node incidents'} > + onClick={({id: incidentId}) => navigate(`/node-incidents/${incidentId}`) } /> diff --git a/client/src/pages/Reports/Reports.tsx b/client/src/pages/Reports/Reports.tsx index b9c70a67..ca67baf7 100644 --- a/client/src/pages/Reports/Reports.tsx +++ b/client/src/pages/Reports/Reports.tsx @@ -142,8 +142,7 @@ const Reports = () => { startDate: dateFromTimestampMs(report.sinceMs), endDate: dateFromTimestampMs(report.toMs), urgency: null, - requestedAtMs: Date.now(), - requestedAtDate: dateTimeWithoutSecondsFromTimestampMs(Date.now()), + requestedAtDate: dateTimeWithoutSecondsFromTimestampMs(report.requestedAtMs), })); const updateRows = ( diff --git a/client/src/types/incident.ts b/client/src/types/incident.ts index 82e6f68b..b8642c8c 100644 --- a/client/src/types/incident.ts +++ b/client/src/types/incident.ts @@ -31,7 +31,7 @@ export const genericIncidentsFromApplicationIncidents = ( category: incident.category, urgency: incident.urgency, title: incident.title, - timestamp: incident.sources[0].timestamp, + timestamp: incident.sinceMs, })); export const genericIncidentsFromNodeIncidents = ( @@ -43,5 +43,5 @@ export const genericIncidentsFromNodeIncidents = ( category: incident.category, urgency: incident.urgency, title: incident.title, - timestamp: incident.sources[0].timestamp, - })); + timestamp: incident.sinceMs, + })); \ No newline at end of file diff --git a/management-service/src/main/java/pl/pwr/zpi/reports/api/ReportsController.java b/management-service/src/main/java/pl/pwr/zpi/reports/api/ReportsController.java index 33624979..a632ec1b 100644 --- a/management-service/src/main/java/pl/pwr/zpi/reports/api/ReportsController.java +++ b/management-service/src/main/java/pl/pwr/zpi/reports/api/ReportsController.java @@ -7,7 +7,9 @@ import org.springframework.web.bind.annotation.*; import pl.pwr.zpi.reports.dto.report.*; import pl.pwr.zpi.reports.dto.report.application.ApplicationIncidentDTO; +import pl.pwr.zpi.reports.dto.report.application.ApplicationIncidentSimplifiedDTO; import pl.pwr.zpi.reports.dto.report.node.NodeIncidentDTO; +import pl.pwr.zpi.reports.dto.report.node.NodeIncidentSimplifiedDTO; import pl.pwr.zpi.reports.dto.request.CreateReportRequest; import pl.pwr.zpi.reports.dto.request.CreateReportScheduleRequest; import pl.pwr.zpi.reports.entity.report.application.ApplicationIncidentSource; @@ -53,7 +55,7 @@ public ResponseEntity retryFailedReportGenerationRequest(@PathVariable Str } @GetMapping - public ResponseEntity> getReportsOnDemand(@RequestParam String reportType) { + public ResponseEntity> getReportSummaries(@RequestParam String reportType) { return ResponseEntity.ok().body(reportsService.getReportSummaries(reportType)); } @@ -67,6 +69,11 @@ public ResponseEntity getReportById(@PathVariable Stri return ResponseEntity.of(reportsService.getReportDetailedSummaryById(id)); } + @GetMapping("/latest") + public ResponseEntity getNewestReport() { + return ResponseEntity.of(reportsService.getLatestReportDetailedSummary()); + } + @GetMapping("/{id}/incidents") public ResponseEntity getReportIncidents(@PathVariable String id) { return ResponseEntity.of(reportsService.getReportIncidents(id)); @@ -87,7 +94,7 @@ public ResponseEntity> getNodeIncid } @GetMapping("/application-incidents/{id}") - public ResponseEntity getApplicationIncidentById(@PathVariable String id) { + public ResponseEntity getApplicationIncidentById(@PathVariable String id) { return ResponseEntity.of(reportsService.getApplicationIncidentById(id)); } @@ -98,7 +105,7 @@ public ResponseEntity> ge } @GetMapping("/node-incidents/{id}") - public ResponseEntity getNodeIncidentById(@PathVariable String id) { + public ResponseEntity getNodeIncidentById(@PathVariable String id) { return ResponseEntity.of(reportsService.getNodeIncidentById(id)); } diff --git a/management-service/src/main/java/pl/pwr/zpi/reports/dto/report/ReportDetailedWithIncidentsDTO.java b/management-service/src/main/java/pl/pwr/zpi/reports/dto/report/ReportDetailedWithIncidentsDTO.java new file mode 100644 index 00000000..ab29636e --- /dev/null +++ b/management-service/src/main/java/pl/pwr/zpi/reports/dto/report/ReportDetailedWithIncidentsDTO.java @@ -0,0 +1,85 @@ +package pl.pwr.zpi.reports.dto.report; + +import lombok.Builder; +import pl.pwr.zpi.reports.dto.report.application.ApplicationIncidentSimplifiedDTO; +import pl.pwr.zpi.reports.dto.report.node.NodeIncidentSimplifiedDTO; +import pl.pwr.zpi.reports.entity.report.Report; +import pl.pwr.zpi.reports.entity.report.application.ApplicationIncidentSource; +import pl.pwr.zpi.reports.entity.report.node.NodeIncidentSource; + +import java.util.List; + +@Builder +public record ReportDetailedWithIncidentsDTO( + ReportDetailedSummaryDTO reportDetailedSummary, + List applicationIncidents, + List nodeIncidents +) { + public static ReportDetailedWithIncidentsDTO mapToReportDetailedWithIncidentsDTO(Report report) { + return ReportDetailedWithIncidentsDTO.builder() + .reportDetailedSummary( + ReportDetailedSummaryDTO.builder() + .id(report.getId()) + .clusterId(report.getClusterId()) + .title(report.getTitle()) + .urgency(report.getUrgency()) + .requestedAtMs(report.getRequestedAtMs()) + .sinceMs(report.getSinceMs()) + .toMs(report.getToMs()) + .totalApplicationEntries(report.getTotalApplicationEntries()) + .totalNodeEntries(report.getTotalNodeEntries()) + .analyzedApplications(report.getAnalyzedApplications()) + .analyzedNodes(report.getAnalyzedNodes()) + .build() + ) + .applicationIncidents(report.getApplicationIncidents().stream() + .map(appIncident -> ApplicationIncidentSimplifiedDTO.builder() + .id(appIncident.getId()) + .reportId(appIncident.getReportId()) + .title(appIncident.getTitle()) + .accuracy(appIncident.getAccuracy()) + .customPrompt(appIncident.getCustomPrompt()) + .clusterId(appIncident.getClusterId()) + .applicationName(appIncident.getApplicationName()) + .category(appIncident.getCategory()) + .summary(appIncident.getSummary()) + .recommendation(appIncident.getRecommendation()) + .urgency(appIncident.getUrgency()) + .sinceMs(appIncident.getSources().stream() + .map(ApplicationIncidentSource::getTimestamp) + .min(Long::compareTo) + .orElse(report.getSinceMs())) + .toMs(appIncident.getSources().stream() + .map(ApplicationIncidentSource::getTimestamp) + .max(Long::compareTo) + .orElse(report.getToMs())) + .build()) + .toList() + ) + .nodeIncidents(report.getNodeIncidents().stream() + .map(nodeIncident -> NodeIncidentSimplifiedDTO.builder() + .id(nodeIncident.getId()) + .reportId(nodeIncident.getReportId()) + .title(nodeIncident.getTitle()) + .accuracy(nodeIncident.getAccuracy()) + .customPrompt(nodeIncident.getCustomPrompt()) + .clusterId(nodeIncident.getClusterId()) + .nodeName(nodeIncident.getNodeName()) + .category(nodeIncident.getCategory()) + .summary(nodeIncident.getSummary()) + .recommendation(nodeIncident.getRecommendation()) + .urgency(nodeIncident.getUrgency()) + .sinceMs(nodeIncident.getSources().stream() + .map(NodeIncidentSource::getTimestamp) + .min(Long::compareTo) + .orElse(report.getSinceMs())) + .toMs(nodeIncident.getSources().stream() + .map(NodeIncidentSource::getTimestamp) + .max(Long::compareTo) + .orElse(report.getToMs())) + .build()) + .toList() + ) + .build(); + } +} \ No newline at end of file diff --git a/management-service/src/main/java/pl/pwr/zpi/reports/dto/report/ReportGeneratingDTO.java b/management-service/src/main/java/pl/pwr/zpi/reports/dto/report/ReportGeneratingDTO.java index d3538ed2..20bb3e5e 100644 --- a/management-service/src/main/java/pl/pwr/zpi/reports/dto/report/ReportGeneratingDTO.java +++ b/management-service/src/main/java/pl/pwr/zpi/reports/dto/report/ReportGeneratingDTO.java @@ -9,7 +9,8 @@ public record ReportGeneratingDTO( String clusterId, ReportType reportType, Long sinceMs, - Long toMs + Long toMs, + Long requestedAtMs ) { @@ -20,6 +21,7 @@ public static ReportGeneratingDTO ofReportGenerationRequestMetadata( .reportType(reportGenerationRequestMetadata.getReportType()) .sinceMs(reportGenerationRequestMetadata.getCreateReportRequest().sinceMs()) .toMs(reportGenerationRequestMetadata.getCreateReportRequest().toMs()) + .requestedAtMs(reportGenerationRequestMetadata.getRequestedAt()) .build(); } } diff --git a/management-service/src/main/java/pl/pwr/zpi/reports/dto/report/ReportIncidentsDTO.java b/management-service/src/main/java/pl/pwr/zpi/reports/dto/report/ReportIncidentsDTO.java index 6f64c811..a26eaba6 100644 --- a/management-service/src/main/java/pl/pwr/zpi/reports/dto/report/ReportIncidentsDTO.java +++ b/management-service/src/main/java/pl/pwr/zpi/reports/dto/report/ReportIncidentsDTO.java @@ -1,6 +1,8 @@ package pl.pwr.zpi.reports.dto.report; import lombok.Builder; +import pl.pwr.zpi.reports.dto.report.application.ApplicationIncidentSimplifiedDTO; +import pl.pwr.zpi.reports.dto.report.node.NodeIncidentSimplifiedDTO; import pl.pwr.zpi.reports.entity.report.application.ApplicationIncident; import pl.pwr.zpi.reports.entity.report.node.NodeIncident; @@ -8,7 +10,7 @@ @Builder public record ReportIncidentsDTO( - List applicationIncidents, - List nodeIncidents + List applicationIncidents, + List nodeIncidents ) { } diff --git a/management-service/src/main/java/pl/pwr/zpi/reports/dto/report/application/ApplicationIncidentSimplifiedDTO.java b/management-service/src/main/java/pl/pwr/zpi/reports/dto/report/application/ApplicationIncidentSimplifiedDTO.java new file mode 100644 index 00000000..e19982d0 --- /dev/null +++ b/management-service/src/main/java/pl/pwr/zpi/reports/dto/report/application/ApplicationIncidentSimplifiedDTO.java @@ -0,0 +1,48 @@ +package pl.pwr.zpi.reports.dto.report.application; + +import lombok.Builder; +import pl.pwr.zpi.reports.entity.report.application.ApplicationIncident; +import pl.pwr.zpi.reports.entity.report.application.ApplicationIncidentSource; +import pl.pwr.zpi.reports.enums.Accuracy; +import pl.pwr.zpi.reports.enums.Urgency; + +@Builder +public record ApplicationIncidentSimplifiedDTO( + String id, + String reportId, + String title, + Accuracy accuracy, + String customPrompt, + String clusterId, + String applicationName, + String category, + String summary, + String recommendation, + Urgency urgency, + long sinceMs, + long toMs +) { + public static ApplicationIncidentSimplifiedDTO fromApplicationIncident(ApplicationIncident incident) { + return ApplicationIncidentSimplifiedDTO.builder() + .id(incident.getId()) + .reportId(incident.getReportId()) + .title(incident.getTitle()) + .accuracy(incident.getAccuracy()) + .customPrompt(incident.getCustomPrompt()) + .clusterId(incident.getClusterId()) + .applicationName(incident.getApplicationName()) + .category(incident.getCategory()) + .summary(incident.getSummary()) + .recommendation(incident.getRecommendation()) + .urgency(incident.getUrgency()) + .sinceMs(incident.getSources().stream() + .map(ApplicationIncidentSource::getTimestamp) + .min(Long::compareTo) + .orElse(0L)) + .toMs(incident.getSources().stream() + .map(ApplicationIncidentSource::getTimestamp) + .max(Long::compareTo) + .orElse(0L)) + .build(); + } +} \ No newline at end of file diff --git a/management-service/src/main/java/pl/pwr/zpi/reports/dto/report/node/NodeIncidentSimplifiedDTO.java b/management-service/src/main/java/pl/pwr/zpi/reports/dto/report/node/NodeIncidentSimplifiedDTO.java new file mode 100644 index 00000000..20ff8fdf --- /dev/null +++ b/management-service/src/main/java/pl/pwr/zpi/reports/dto/report/node/NodeIncidentSimplifiedDTO.java @@ -0,0 +1,48 @@ +package pl.pwr.zpi.reports.dto.report.node; + +import lombok.Builder; +import pl.pwr.zpi.reports.entity.report.node.NodeIncident; +import pl.pwr.zpi.reports.entity.report.node.NodeIncidentSource; +import pl.pwr.zpi.reports.enums.Accuracy; +import pl.pwr.zpi.reports.enums.Urgency; + +@Builder +public record NodeIncidentSimplifiedDTO( + String id, + String reportId, + String title, + Accuracy accuracy, + String customPrompt, + String clusterId, + String nodeName, + String category, + String summary, + String recommendation, + Urgency urgency, + long sinceMs, + long toMs +) { + public static NodeIncidentSimplifiedDTO fromNodeIncident(NodeIncident incident) { + return NodeIncidentSimplifiedDTO.builder() + .id(incident.getId()) + .reportId(incident.getReportId()) + .title(incident.getTitle()) + .accuracy(incident.getAccuracy()) + .customPrompt(incident.getCustomPrompt()) + .clusterId(incident.getClusterId()) + .nodeName(incident.getNodeName()) + .category(incident.getCategory()) + .summary(incident.getSummary()) + .recommendation(incident.getRecommendation()) + .urgency(incident.getUrgency()) + .sinceMs(incident.getSources().stream() + .map(NodeIncidentSource::getTimestamp) + .min(Long::compareTo) + .orElse(0L)) + .toMs(incident.getSources().stream() + .map(NodeIncidentSource::getTimestamp) + .max(Long::compareTo) + .orElse(0L)) + .build(); + } +} \ No newline at end of file diff --git a/management-service/src/main/java/pl/pwr/zpi/reports/entity/report/request/ReportGenerationRequestMetadata.java b/management-service/src/main/java/pl/pwr/zpi/reports/entity/report/request/ReportGenerationRequestMetadata.java index 612153e5..f57bd835 100644 --- a/management-service/src/main/java/pl/pwr/zpi/reports/entity/report/request/ReportGenerationRequestMetadata.java +++ b/management-service/src/main/java/pl/pwr/zpi/reports/entity/report/request/ReportGenerationRequestMetadata.java @@ -21,6 +21,7 @@ public class ReportGenerationRequestMetadata { private ReportRequestFailed error; private CreateReportRequest createReportRequest; private ReportType reportType; + private long requestedAt; public static ReportGenerationRequestMetadata fromCreateReportRequest( String correlationId, @@ -32,6 +33,7 @@ public static ReportGenerationRequestMetadata fromCreateReportRequest( .status(ReportGenerationStatus.GENERATING) .createReportRequest(createReportRequest) .reportType(reportType) + .requestedAt(System.currentTimeMillis()) .build(); } diff --git a/management-service/src/main/java/pl/pwr/zpi/reports/repository/ReportRepository.java b/management-service/src/main/java/pl/pwr/zpi/reports/repository/ReportRepository.java index 55513256..7c838abb 100644 --- a/management-service/src/main/java/pl/pwr/zpi/reports/repository/ReportRepository.java +++ b/management-service/src/main/java/pl/pwr/zpi/reports/repository/ReportRepository.java @@ -18,4 +18,5 @@ public interface ReportRepository extends MongoRepository { Optional findProjectedDetailedById(String reportId); Optional findProjectedIncidentsById(String reportId); + Optional findFirstByOrderByRequestedAtMsDesc(); } diff --git a/management-service/src/main/java/pl/pwr/zpi/reports/service/ReportsService.java b/management-service/src/main/java/pl/pwr/zpi/reports/service/ReportsService.java index 9c456a12..e8e46494 100644 --- a/management-service/src/main/java/pl/pwr/zpi/reports/service/ReportsService.java +++ b/management-service/src/main/java/pl/pwr/zpi/reports/service/ReportsService.java @@ -6,7 +6,10 @@ import org.springframework.stereotype.Service; import pl.pwr.zpi.reports.dto.report.*; import pl.pwr.zpi.reports.dto.report.application.ApplicationIncidentDTO; +import pl.pwr.zpi.reports.dto.report.application.ApplicationIncidentSimplifiedDTO; import pl.pwr.zpi.reports.dto.report.node.NodeIncidentDTO; +import pl.pwr.zpi.reports.dto.report.ReportDetailedWithIncidentsDTO; +import pl.pwr.zpi.reports.dto.report.node.NodeIncidentSimplifiedDTO; import pl.pwr.zpi.reports.entity.report.application.ApplicationIncident; import pl.pwr.zpi.reports.entity.report.application.ApplicationIncidentSource; import pl.pwr.zpi.reports.entity.report.node.NodeIncident; @@ -57,11 +60,19 @@ public Optional getReportDetailedSummaryById(String re public Optional getReportIncidents(String reportId) { return reportRepository.findProjectedIncidentsById(reportId).map(incidentProjection -> ReportIncidentsDTO.builder() - .applicationIncidents(extractApplicationIncidents(incidentProjection)) - .nodeIncidents(extractNodeIncidents(incidentProjection)) + .applicationIncidents( + extractApplicationIncidents(incidentProjection).stream() + .map(ApplicationIncidentSimplifiedDTO::fromApplicationIncident) + .toList() + ) + .nodeIncidents( + extractNodeIncidents(incidentProjection).stream() + .map(NodeIncidentSimplifiedDTO::fromNodeIncident) + .toList()) .build()); } + public ReportPaginatedIncidentsDTO getReportApplicationIncidents( String reportId, Pageable pageable) { @@ -119,11 +130,16 @@ public ReportPaginatedIncidentsDTO getNodeIncidentSourcesByI } - public Optional getApplicationIncidentById(String incidentId) { - return applicationIncidentRepository.findById(incidentId).map(ApplicationIncidentDTO::fromApplicationIncident); + public Optional getApplicationIncidentById(String incidentId) { + return applicationIncidentRepository.findById(incidentId).map(ApplicationIncidentSimplifiedDTO::fromApplicationIncident); } - public Optional getNodeIncidentById(String incidentId) { - return nodeIncidentRepository.findById(incidentId).map(NodeIncidentDTO::fromNodeIncident); + public Optional getNodeIncidentById(String incidentId) { + return nodeIncidentRepository.findById(incidentId).map(NodeIncidentSimplifiedDTO::fromNodeIncident); + } + + public Optional getLatestReportDetailedSummary() { + return reportRepository.findFirstByOrderByRequestedAtMsDesc() + .map(ReportDetailedSummaryDTO::fromReportDetailedSummaryProjection); } }