From f720c334d6674b607d8672a17751574cf70b4b16 Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Sun, 26 Apr 2026 05:49:09 +0530 Subject: [PATCH 1/5] Parse descriptions/bugfixes and improve PDF UI Add parsing utilities for update descriptions and bug fixes (parseDescriptionSections, parseBugFixes) and use them in both the modal UI and PDF generator. The modal now renders structured sections (General Description, Implementation Details, Impact), formats bug-fix URLs as links, and preserves instructions/security advisories layout. PDF generation was updated to consume the parsed sections and bug-fix list. Also add a download state with a CircularProgress icon and disabled Download button while the PDF is being generated. --- .../all-updates/UpdateLevelsReportModal.tsx | 222 +++++++++++------- .../updates/utils/updateLevelsReportPdf.ts | 90 +++++-- 2 files changed, 210 insertions(+), 102 deletions(-) diff --git a/apps/customer-portal/webapp/src/features/updates/components/all-updates/UpdateLevelsReportModal.tsx b/apps/customer-portal/webapp/src/features/updates/components/all-updates/UpdateLevelsReportModal.tsx index dc5266c980..109ebaa543 100644 --- a/apps/customer-portal/webapp/src/features/updates/components/all-updates/UpdateLevelsReportModal.tsx +++ b/apps/customer-portal/webapp/src/features/updates/components/all-updates/UpdateLevelsReportModal.tsx @@ -17,6 +17,7 @@ import { Box, Button, + CircularProgress, Dialog, DialogActions, DialogContent, @@ -26,8 +27,12 @@ import { Typography, } from "@wso2/oxygen-ui"; import { Printer, X } from "@wso2/oxygen-ui-icons-react"; -import { useCallback, type JSX } from "react"; -import { generateUpdateLevelsReportPdf } from "@features/updates/utils/updateLevelsReportPdf"; +import { useCallback, useState, type JSX } from "react"; +import { + generateUpdateLevelsReportPdf, + parseBugFixes, + parseDescriptionSections, +} from "@features/updates/utils/updateLevelsReportPdf"; import type { UpdateLevelsReportModalProps, UpdateDescriptionLevel } from "@features/updates/types/updates"; function isInstructionsNonEmpty(text: string | null | undefined): boolean { @@ -43,16 +48,25 @@ function formatTimestamp(ts: number): string { } function UpdateDescriptionItem({ item }: { item: UpdateDescriptionLevel }): JSX.Element { + const parsed = parseDescriptionSections(item.description); + const displayDesc = parsed.generalDescription || parsed.implementationDetails || parsed.impact; + const bugFixUrls = parseBugFixes(item.bugFixes); return ( - {item.description?.trim() && ( + {displayDesc && ( - {item.description.trim()} + {displayDesc} )} - {item.bugFixes?.trim() && ( + {bugFixUrls.length > 0 && ( - Bug fixes: {item.bugFixes.trim()} + Bug fixes:{" "} + {bugFixUrls.map((url, i) => ( + + {i > 0 && ", "} + {url} + + ))} )} @@ -70,9 +84,15 @@ export default function UpdateLevelsReportModal({ onView, rawData, }: UpdateLevelsReportModalProps): JSX.Element | null { + const [isDownloading, setIsDownloading] = useState(false); + const handleDownloadPdf = useCallback(() => { if (!reportData) return; - generateUpdateLevelsReportPdf(reportData); + setIsDownloading(true); + setTimeout(() => { + generateUpdateLevelsReportPdf(reportData); + setIsDownloading(false); + }, 0); }, [reportData]); const handleLevelClick = useCallback( @@ -278,91 +298,129 @@ export default function UpdateLevelsReportModal({ Update Details - {reportData.allEntries.map((entry, i) => ( - - - - {entry.formattedDate} - Update No: {entry.updateNumber} - - - {entry.productName}-{entry.productVersion} ({entry.channel}) - - - {entry.description?.trim() && ( - - {entry.description.trim()} - - )} - {entry.bugFixes?.trim() && ( - - Bug Fixes: {entry.bugFixes.trim()} - - )} - {isInstructionsNonEmpty(entry.instructions) && ( - - - Instructions: + {reportData.allEntries.map((entry, i) => { + const parsedDesc = parseDescriptionSections(entry.description); + const bugFixUrls = parseBugFixes(entry.bugFixes); + return ( + + + + {entry.formattedDate} - Update No: {entry.updateNumber} - - {entry.instructions?.trim()} - - - )} - {(entry.securityAdvisories?.length ?? 0) > 0 && ( - - - Security Advisories: + + {entry.productName}-{entry.productVersion} ({entry.channel}) - - {entry.securityAdvisories.map((adv, j) => ( - - - {adv.id} ({adv.severity}): {adv.overview} + + {parsedDesc.generalDescription && ( + + + General Description: + + {parsedDesc.generalDescription} + + )} + {parsedDesc.implementationDetails && ( + + + Implementation Details: + + {parsedDesc.implementationDetails} + + )} + {parsedDesc.impact && ( + + + Impact: + + {parsedDesc.impact} + + )} + {bugFixUrls.length > 0 && ( + + + Bug Fixes: + + + {bugFixUrls.map((url, j) => ( + + {url} - - ))} + ))} + - - )} - - ))} + )} + {isInstructionsNonEmpty(entry.instructions) && ( + + + Instructions: + + + {entry.instructions?.trim()} + + + )} + {(entry.securityAdvisories?.length ?? 0) > 0 && ( + + + Security Advisories: + + + {entry.securityAdvisories.map((adv, j) => ( + + + {adv.id} ({adv.severity}): {adv.overview} + + + ))} + + + )} + + ); + })} -