From 48bd5d0e1dbf9697c5b9d9fa837cf50019299ee9 Mon Sep 17 00:00:00 2001 From: Laura Barcziova Date: Sun, 21 Jan 2024 09:46:42 +0100 Subject: [PATCH] Show Bodhi updates in jobs and pipelines Fixes #215 --- frontend/src/app/Jobs/BodhiUpdatesTable.tsx | 145 +++++++++++++++++ frontend/src/app/Jobs/Jobs.tsx | 3 + frontend/src/app/Pipelines/PipelinesTable.tsx | 6 + .../app/Results/ResultsPageBodhiUpdate.tsx | 150 ++++++++++++++++++ frontend/src/app/routes.tsx | 14 ++ frontend/tsconfig.json | 4 +- 6 files changed, 320 insertions(+), 2 deletions(-) create mode 100644 frontend/src/app/Jobs/BodhiUpdatesTable.tsx create mode 100644 frontend/src/app/Results/ResultsPageBodhiUpdate.tsx diff --git a/frontend/src/app/Jobs/BodhiUpdatesTable.tsx b/frontend/src/app/Jobs/BodhiUpdatesTable.tsx new file mode 100644 index 00000000..67fb8857 --- /dev/null +++ b/frontend/src/app/Jobs/BodhiUpdatesTable.tsx @@ -0,0 +1,145 @@ +import React, { useMemo } from "react"; + +import { TableVariant, cellWidth, IRow } from "@patternfly/react-table"; +import { + Table, + TableHeader, + TableBody, +} from "@patternfly/react-table/deprecated"; + +import { Button } from "@patternfly/react-core"; +import { TriggerLink } from "../Trigger/TriggerLink"; +import { ErrorConnection } from "../Errors/ErrorConnection"; +import { Preloader } from "../Preloader/Preloader"; +import { ForgeIcon } from "../Forge/ForgeIcon"; +import { StatusLabel } from "../StatusLabel/StatusLabel"; +import { Timestamp } from "../utils/Timestamp"; +import { useInfiniteQuery } from "@tanstack/react-query"; + +export interface BodhiUpdate { + packit_id: number; + status: string; + alias: string | null; + web_url: string | null; + branch: string; + submitted_time: number; + update_creation_time: number | null; + pr_id: number | null; + branch_name: string | null; + release: string | null; + project_url: string; + repo_namespace: string; + repo_name: string; +} +const BodhiUpdatesTable = () => { + // Headings + const columns = [ + { + title: Forge, + }, // space for forge icon + { title: "Trigger", transforms: [cellWidth(35)] }, + { title: "Branch", transforms: [cellWidth(20)] }, + { title: "Time Processed", transforms: [cellWidth(20)] }, + { title: "Bodhi Update", transforms: [cellWidth(20)] }, + ]; + + // Fetch data from dashboard backend (or if we want, directly from the API) + const fetchData = ({ pageParam = 1 }) => + fetch( + `${ + import.meta.env.VITE_API_URL + }/bodhi-updates?page=${pageParam}&per_page=20`, + ) + .then((response) => response.json()) + .then((data) => jsonToRow(data)); + + const { isInitialLoading, isError, fetchNextPage, data, isFetching } = + useInfiniteQuery(["bodhi"], fetchData, { + getNextPageParam: (_, allPages) => allPages.length + 1, + }); + + // Convert fetched json into row format that the table can read + function jsonToRow(bodhi_updates: BodhiUpdate[]) { + let rowsList: IRow[] = []; + + bodhi_updates.forEach((bodhi_update) => { + let singleRow = { + cells: [ + { + title: , + }, + { + title: ( + + + + ), + }, + { + title: ( + + ), + }, + { + title: , + }, + { + title: ( + + + {bodhi_update.alias} + + + ), + }, + ], + }; + rowsList.push(singleRow); + }); + return rowsList; + } + + // Create a memoization of all the data when we flatten it out. Ideally one should render all the pages separately so that rendering will be done faster + const rows = useMemo(() => (data ? data.pages.flat() : []), [data]); + + // If backend API is down + if (isError) { + return ; + } + + // Show preloader if waiting for API data + // TODO(SpyTec): Replace with skeleton loader, we know the data will look like + if (isInitialLoading) { + return ; + } + + return ( +
+ + + +
+
+
+ +
+
+ ); +}; + +export { BodhiUpdatesTable }; diff --git a/frontend/src/app/Jobs/Jobs.tsx b/frontend/src/app/Jobs/Jobs.tsx index c14d4e1f..a20d8ab4 100644 --- a/frontend/src/app/Jobs/Jobs.tsx +++ b/frontend/src/app/Jobs/Jobs.tsx @@ -69,6 +69,9 @@ const Jobs = () => { Downstream Koji Builds + + Bodhi Updates + diff --git a/frontend/src/app/Pipelines/PipelinesTable.tsx b/frontend/src/app/Pipelines/PipelinesTable.tsx index 5334b1a9..f535adfb 100644 --- a/frontend/src/app/Pipelines/PipelinesTable.tsx +++ b/frontend/src/app/Pipelines/PipelinesTable.tsx @@ -192,6 +192,12 @@ const PipelinesTable = () => { statusClass={SyncReleaseTargetStatusLabel} entries={run.pull_from_upstream} /> + ), }, diff --git a/frontend/src/app/Results/ResultsPageBodhiUpdate.tsx b/frontend/src/app/Results/ResultsPageBodhiUpdate.tsx new file mode 100644 index 00000000..93d5eb0f --- /dev/null +++ b/frontend/src/app/Results/ResultsPageBodhiUpdate.tsx @@ -0,0 +1,150 @@ +import React from "react"; +import { + PageSection, + Card, + CardBody, + PageSectionVariants, + TextContent, + Text, + Title, + Label, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, +} from "@patternfly/react-core"; +import { TableHeader, TableBody } from "@patternfly/react-table/deprecated"; +import { Table, Tbody, Td, Th, Thead, Tr } from "@patternfly/react-table"; +import { ErrorConnection } from "../Errors/ErrorConnection"; +import { Preloader } from "../Preloader/Preloader"; +import { TriggerLink } from "../Trigger/TriggerLink"; +import { StatusLabel } from "../StatusLabel/StatusLabel"; +import { Timestamp } from "../utils/Timestamp"; +import { useParams } from "react-router-dom"; +import { useTitle } from "../utils/useTitle"; +import { getCommitLink } from "../utils/forgeUrls"; +import { useQuery } from "@tanstack/react-query"; +import { SHACopy } from "../utils/SHACopy"; + +interface BodhiUpdate { + packit_id: number; + status: string; + alias: string | null; + web_url: string | null; + branch: string; + submitted_time: number; + update_creation_time: number | null; + pr_id: number | null; + branch_name: string | null; + release: string | null; + project_url: string; + repo_namespace: string; + repo_name: string; +} + +const fetchBodhiUpdates = (url: string) => + fetch(url).then((response) => { + if (!response.ok && response.status !== 404) { + throw Promise.reject(response); + } + return response.json(); + }); + +const ResultsPageBodhiUpdate = () => { + useTitle("Bodhi Updates"); + let { id } = useParams(); + + const URL = `${import.meta.env.VITE_API_URL}/bodhi-updates/${id}`; + const { data, isError, isInitialLoading } = useQuery< + BodhiUpdate | { error: string } + >([URL], () => fetchBodhiUpdates(URL)); + + // If backend API is down + if (isError) { + return ; + } + + // Show preloader if waiting for API data + if (isInitialLoading || data === undefined) { + return ; + } + + if ("error" in data) { + return ( + + + + + Not Found. + + + + + ); + } + + return ( + <> + + + Bodhi Update Results + + + + + +
+
+
+
+ + + + + + + Status + + {" "} + + Alias + + {" "} + {data.alias !== null ? data.alias : not provided} + + Koji NVR + + {" "} + {data.koji_nvr} + + + + + Update Processing Time + + + + + Update Creation Time + + + + + + + + + + ); +}; + +export { ResultsPageBodhiUpdate }; diff --git a/frontend/src/app/routes.tsx b/frontend/src/app/routes.tsx index 3ffc2fa0..cc61d136 100644 --- a/frontend/src/app/routes.tsx +++ b/frontend/src/app/routes.tsx @@ -14,11 +14,13 @@ import { ResultsPageKoji } from "./Results/ResultsPageKoji"; import { ResultsPageSyncReleaseRuns } from "./Results/ResultsPageSyncReleaseRuns"; import { ResultsPageSRPM } from "./Results/ResultsPageSRPM"; import { ResultsPageTestingFarm } from "./Results/ResultsPageTestingFarm"; +import { ResultsPageBodhiUpdate } from "./Results/ResultsPageBodhiUpdate"; import { CoprBuildsTable } from "./Jobs/CoprBuildsTable"; import { KojiBuildsTable } from "./Jobs/KojiBuildsTable"; import { SyncReleaseTable } from "./Jobs/SyncReleaseStatuses"; import { SRPMBuildsTable } from "./Jobs/SRPMBuildsTable"; import { TestingFarmResultsTable } from "./Jobs/TestingFarmResultsTable"; +import { BodhiUpdatesTable } from "./Jobs/BodhiUpdatesTable"; import { Usage } from "./Usage/Usage"; import { ErrorApp } from "./Errors/ErrorApp"; @@ -113,6 +115,14 @@ const routes: RouteObject[] = [ label: "Downstream (production) Koji builds", }, }, + { + element: , + id: "bodhi-updates", + path: "bodhi-updates", + handle: { + label: "Bodhi Updates", + }, + }, ], }, { @@ -159,6 +169,10 @@ const routes: RouteObject[] = [ path: "/results/pull-from-upstream/:id", element: , }, + { + path: "/results/bodhi-updates/:id", + element: , + }, { element: , path: "/usage", diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 3f0ed268..d9429069 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -12,8 +12,8 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "preserve" + "jsx": "preserve", }, "include": ["src"], - "exclude": ["node_modules"] + "exclude": ["node_modules"], }