From 72e7581924f265b0f6077e5b471a2906bc849d14 Mon Sep 17 00:00:00 2001 From: Christopher Chiche Date: Wed, 16 Aug 2023 01:15:43 +0200 Subject: [PATCH] Report viewer (#8) - [x] Create a report structure that reflects the UI structure - [x] Routing to go through capability children - [x] Display table for a given capability Screenshot 2023-08-14 at 16 45 02 TODO: - [ ] Parse the report to create the internal report structure --- reports/.gitignore | 3 + reports/index.html | 5 +- reports/package.json | 4 +- reports/src/App.css | 49 +++---- reports/src/App.tsx | 43 ++----- reports/src/capability/CapabilityTable.tsx | 114 +++++++++++++++++ reports/src/capability/capabilityTypes.ts | 134 ++++++++++++++++++++ reports/src/capability/reportNavigation.tsx | 20 +++ reports/src/capability/reportParser.ts | 27 ++++ reports/src/capability/useReport.tsx | 31 +++++ reports/src/index.css | 1 + reports/yarn.lock | 20 +++ 12 files changed, 394 insertions(+), 57 deletions(-) create mode 100644 reports/src/capability/CapabilityTable.tsx create mode 100644 reports/src/capability/capabilityTypes.ts create mode 100644 reports/src/capability/reportNavigation.tsx create mode 100644 reports/src/capability/reportParser.ts create mode 100644 reports/src/capability/useReport.tsx diff --git a/reports/.gitignore b/reports/.gitignore index a547bf3..3761c30 100644 --- a/reports/.gitignore +++ b/reports/.gitignore @@ -1,3 +1,6 @@ +# Report examples +public/monitoring-tests-reports + # Logs logs *.log diff --git a/reports/index.html b/reports/index.html index e939ed4..23714b7 100644 --- a/reports/index.html +++ b/reports/index.html @@ -1,9 +1,9 @@ - + - Vite + React + TS + Report -
diff --git a/reports/package.json b/reports/package.json index a4682a1..6278be0 100644 --- a/reports/package.json +++ b/reports/package.json @@ -4,6 +4,7 @@ "version": "0.0.0", "type": "module", "scripts": { + "start": "vite", "dev": "vite", "prebuild": "cd ../tools && yarn generate-types", "build": "tsc && vite build", @@ -12,7 +13,8 @@ }, "dependencies": { "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-router-dom": "^6.15.0" }, "devDependencies": { "@types/react": "^18.2.15", diff --git a/reports/src/App.css b/reports/src/App.css index b9d355d..9d47df5 100644 --- a/reports/src/App.css +++ b/reports/src/App.css @@ -5,38 +5,39 @@ text-align: center; } -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; +table { + border-collapse: collapse; } -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); + +table, th, td { + border: 1px solid black; } -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); + +th, td { + padding: 4px; + text-align: left; } -@keyframes logo-spin { - from { - transform: rotate(0deg); +td { + &.fail { + background-color: red; + color: white; + font-weight: 600; } - to { - transform: rotate(360deg); + &.pass { + background-color: green; + color: white; + font-weight: 600; } } -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} -.card { - padding: 2em; +th { + background-color: lightgray; } -.read-the-docs { - color: #888; -} +@media (prefers-color-scheme: dark) { + th { + background-color: dimgray; + } +} \ No newline at end of file diff --git a/reports/src/App.tsx b/reports/src/App.tsx index d31c7f9..5a7b192 100644 --- a/reports/src/App.tsx +++ b/reports/src/App.tsx @@ -1,34 +1,19 @@ -import './App.css' - -import { ReportsReportTestRunReport } from './types/TestRunReport' - -// TODO: Replace with actual JSON report. Placeholder to verify proper typing. -const report: ReportsReportTestRunReport = -{ - baseline_signature: '', - codebase_version: '', - commit_hash: '', - report: {}, - configuration: { - action: {}, - resources: { - resource_declarations: {} - } - }, - file_signatures: {} -} +import { RouterProvider, createBrowserRouter } from "react-router-dom"; +import { useReport } from "./capability/useReport"; +import "./App.css"; function App() { - // FIXME + // FIXME use the report from the config // eslint-disable-next-line @typescript-eslint/no-explicit-any - const configuration = JSON.stringify((window as any)["interuss"]) - console.log("Configuration:", configuration) - return ( - <> -

Report

- {JSON.stringify(report, null, 2)} - - ) + const configuration = JSON.stringify((window as any)["interuss"]); + console.log("Configuration:", configuration); + + const { report, nav } = useReport(); + if (!report) { + return
Report not found
; + } + const router = createBrowserRouter(nav); + return ; } -export default App +export default App; diff --git a/reports/src/capability/CapabilityTable.tsx b/reports/src/capability/CapabilityTable.tsx new file mode 100644 index 0000000..af0d658 --- /dev/null +++ b/reports/src/capability/CapabilityTable.tsx @@ -0,0 +1,114 @@ +import { + Capability, + Check, + Requirement, + exampleReport, +} from "./capabilityTypes"; + +type CapabilityTableProps = { + capability?: Capability; +}; + +export const CheckRow = ({ check }: { check: Check }) => { + return ( + + {check.name} + + {check.result === "pass" ? "PASS" : "FAIL"} + + + Link + + + ); +}; + +const requirementRow = (requirement: Requirement) => { + const checks = requirement.checks.map((c) => ); + const requirementHeader = ( + + {requirement.name} + {!checks.length && Not tested} + + ); + + if (checks.length) { + return [requirementHeader, ...checks]; + } else { + return [requirementHeader]; + } +}; + +export const ChildCapabilityRow = ({ + capability, + path, +}: { + capability: Capability; + path: string; +}) => { + return ( + + + {capability.name} + + + {capability.result === "pass" ? "PASS" : "FAIL"} + + + ); +}; + +const ChildCapabilityHeader = () => { + return ( + + Child Capability + Result + + ); +}; + +export const CapabilityRows = ({ capability }: { capability: Capability }) => { + const requirements = capability.requirements + .flatMap((r) => requirementRow(r)) + .flat(); + const childCapabilities = capability.childCapabilities.map((c, i) => ( + + )); + const childTable = childCapabilities + ? [, ...childCapabilities] + : []; + + const allRows = [...requirements, ...childTable]; + return [ + + {capability.name} + , + ...allRows, + ]; +}; + +export const CapabilityTable = ({ + capability = exampleReport.capability, +}: CapabilityTableProps) => { + return ( + <> + + + + + + + + + + + + + +
CapabilityRequirementTest CheckResultDetails
+ + ); +}; diff --git a/reports/src/capability/capabilityTypes.ts b/reports/src/capability/capabilityTypes.ts new file mode 100644 index 0000000..a46eea1 --- /dev/null +++ b/reports/src/capability/capabilityTypes.ts @@ -0,0 +1,134 @@ +type CapabilityResult = "pass" | "fail" | "missing"; +type CheckResult = "pass" | "fail"; + +export type Check = { + result: CheckResult; + name: string; + detailLink: string; +}; + +export type Requirement = { + name: string; + checks: Check[]; +}; + +export type Capability = { + name: string; + result: CapabilityResult; + requirements: Requirement[]; + childCapabilities: Capability[]; +}; + +export type Report = { + capability: Capability; +}; + +export const exampleReport: Report = { + capability: { + name: "ASTM Service Provider", + result: "fail", + requirements: [ + { + name: "astm.f3548.v21.GEN0310", + checks: [ + { + name: "Nominal behavior :: Setup :: Clear", + result: "pass", + detailLink: "Link to test details", + }, + { + name: "SP polling :: Poll :: Valid data", + result: "pass", + detailLink: "Link to test details", + }, + ], + }, + { + name: "astm.f3548.v21.OPIN0030", + checks: [ + { + name: "...", + result: "fail", + detailLink: "Link to test details", + }, + { + name: "...", + result: "pass", + detailLink: "Link to test details", + }, + ], + }, + { + name: "astm.f3548.v21.SDC0015", + checks: [ + // Should display "Not tested" + ], + }, + { + name: "astm.f3548.v21.GEN0200", + checks: [ + { + name: "...", + result: "pass", + detailLink: "Link to test details", + }, + ], + }, + ], + childCapabilities: [ + { + name: "ASTM SP Operator ID Provider", + result: "pass", + requirements: [ + { + name: "child 1", + checks: [ + { + name: "Nominal behavior :: Setup :: Clear", + result: "pass", + detailLink: "Link to test details", + }, + { + name: "SP polling :: Poll :: Valid data", + result: "pass", + detailLink: "Link to test details", + }, + ], + }, + { + name: "child 2", + checks: [ + { + name: "...", + result: "fail", + detailLink: "Link to test details", + }, + { + name: "...", + result: "pass", + detailLink: "Link to test details", + }, + ], + }, + { + name: "child 3", + checks: [ + // Should display "Not tested" + ], + }, + { + name: "child 4", + checks: [ + { + name: "...", + result: "pass", + detailLink: "Link to test details", + }, + ], + }, + ], + childCapabilities: [], + }, + ], + }, +}; diff --git a/reports/src/capability/reportNavigation.tsx b/reports/src/capability/reportNavigation.tsx new file mode 100644 index 0000000..505f917 --- /dev/null +++ b/reports/src/capability/reportNavigation.tsx @@ -0,0 +1,20 @@ +import { RouteObject } from "react-router-dom"; +import { Capability } from "./capabilityTypes"; +import { CapabilityTable } from "./CapabilityTable"; + +export const getNavFromCapability = ( + capability: Capability, + path: string = "" +): RouteObject[] => { + const children = + capability.childCapabilities.flatMap((c, i) => + getNavFromCapability(c, `${path}/${i}`) + ) || []; + return [ + { + path: path, + element: , + }, + ...children, + ]; +}; diff --git a/reports/src/capability/reportParser.ts b/reports/src/capability/reportParser.ts new file mode 100644 index 0000000..9f0354a --- /dev/null +++ b/reports/src/capability/reportParser.ts @@ -0,0 +1,27 @@ +import { + ReportsReportTestRunReport, + ReportsReportTestSuiteActionReport, +} from "../types/TestRunReport"; +import { Capability, Report, exampleReport } from "./capabilityTypes"; + +const parseTestSuite = (testSuite: ReportsReportTestSuiteActionReport) => { + console.log("Parse test suite", testSuite); + if (!testSuite.test_suite) return null; + const capabilities = testSuite.test_suite.capability_evaluations.map( + (c): Capability => ({ + name: `${c.capability_id} - ${c.participant_id}`, + requirements: [], + childCapabilities: [], + result: c.verified ? "pass" : "fail", + }) + ); + console.log("Parsed capbilities", capabilities); +}; + +export const parseReport = (report: ReportsReportTestRunReport): Report => { + // FixMe: Actually parse the report + console.log("Input Report", report); + const parsed = parseTestSuite(report.report); + console.log("Parsed report", parsed); + return exampleReport; +}; diff --git a/reports/src/capability/useReport.tsx b/reports/src/capability/useReport.tsx new file mode 100644 index 0000000..bbb1f3c --- /dev/null +++ b/reports/src/capability/useReport.tsx @@ -0,0 +1,31 @@ +import { useState, useEffect } from "react"; +import { ReportsReportTestRunReport } from "../types/TestRunReport"; +import { RouteObject } from "react-router-dom"; +import { parseReport } from "./reportParser"; +import { getNavFromCapability } from "./reportNavigation"; + +const reportUrl = + "/monitoring-tests-reports/uss_qualifier/output/report_uspace.json"; + +type UseReportReturn = { + report?: ReportsReportTestRunReport; + nav: RouteObject[]; +}; + +export const useReport = (): UseReportReturn => { + const [report, setReport] = useState(); + + useEffect(() => { + const fetchReport = async () => { + const res = await fetch(reportUrl); + const json = await res.json(); + setReport(json as ReportsReportTestRunReport); + }; + fetchReport(); + }, []); + + const parsedReport = report ? parseReport(report) : undefined; + const nav = parsedReport ? getNavFromCapability(parsedReport.capability) : []; + + return { report, nav }; +}; diff --git a/reports/src/index.css b/reports/src/index.css index 2c3fac6..df44f4d 100644 --- a/reports/src/index.css +++ b/reports/src/index.css @@ -67,3 +67,4 @@ button:focus-visible { background-color: #f9f9f9; } } + diff --git a/reports/yarn.lock b/reports/yarn.lock index 2008b80..a66fdca 100644 --- a/reports/yarn.lock +++ b/reports/yarn.lock @@ -427,6 +427,11 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@remix-run/router@1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.8.0.tgz#e848d2f669f601544df15ce2a313955e4bf0bafc" + integrity sha512-mrfKqIHnSZRyIzBcanNJmVQELTnX+qagEDlcKO90RgRBVOZGSGvZKeDihTRfWcqoDn5N/NkUcwWTccnpN18Tfg== + "@types/json-schema@^7.0.12": version "7.0.12" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" @@ -1317,6 +1322,21 @@ react-refresh@^0.14.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== +react-router-dom@^6.15.0: + version "6.15.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.15.0.tgz#6da7db61e56797266fbbef0d5e324d6ac443ee40" + integrity sha512-aR42t0fs7brintwBGAv2+mGlCtgtFQeOzK0BM1/OiqEzRejOZtpMZepvgkscpMUnKb8YO84G7s3LsHnnDNonbQ== + dependencies: + "@remix-run/router" "1.8.0" + react-router "6.15.0" + +react-router@6.15.0: + version "6.15.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.15.0.tgz#bf2cb5a4a7ed57f074d4ea88db0d95033f39cac8" + integrity sha512-NIytlzvzLwJkCQj2HLefmeakxxWHWAP+02EGqWEZy+DgfHHKQMUoBBjUQLOtFInBMhWtb3hiUy6MfFgwLjXhqg== + dependencies: + "@remix-run/router" "1.8.0" + react@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"