From d593930ff11cf4d1edf1050fddf3f8b3dd4cf0ce Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 20:47:38 +0000 Subject: [PATCH] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- frontend/.storybook/main.ts | 30 +- frontend/.storybook/preview.ts | 8 +- frontend/src/App.tsx | 10 +- frontend/src/app/AppLayout/AppLayout.tsx | 445 ++++----- frontend/src/app/Dashboard/Dashboard.tsx | 241 +++-- frontend/src/app/Errors/ErrorApp.stories.tsx | 6 +- frontend/src/app/Errors/ErrorApp.tsx | 154 ++- .../app/Errors/ErrorConnection.stories.tsx | 4 +- frontend/src/app/Errors/ErrorConnection.tsx | 26 +- frontend/src/app/Forge/ForgeIcon.stories.tsx | 26 +- frontend/src/app/Forge/ForgeIcon.tsx | 40 +- .../Jobs/.fixtures/CoprBuildsData.fixture.tsx | 942 +++++++++--------- .../src/app/Jobs/CoprBuildsTable.stories.tsx | 38 +- frontend/src/app/Jobs/CoprBuildsTable.tsx | 312 +++--- frontend/src/app/Jobs/Jobs.stories.tsx | 24 +- frontend/src/app/Jobs/Jobs.tsx | 160 ++- frontend/src/app/Jobs/KojiBuildsTable.tsx | 246 +++-- frontend/src/app/Jobs/SRPMBuildsTable.tsx | 210 ++-- frontend/src/app/Jobs/SyncReleaseStatuses.tsx | 300 +++--- .../src/app/Jobs/TestingFarmResultsTable.tsx | 216 ++-- .../src/app/NotFound/NotFound.stories.tsx | 4 +- frontend/src/app/NotFound/NotFound.tsx | 9 +- frontend/src/app/Pipelines/Pipelines.tsx | 36 +- frontend/src/app/Pipelines/PipelinesTable.tsx | 408 ++++---- frontend/src/app/Preloader/Preloader.tsx | 10 +- frontend/src/app/Projects/BranchList.tsx | 218 ++-- frontend/src/app/Projects/Forge.stories.tsx | 36 +- frontend/src/app/Projects/Forge.tsx | 40 +- frontend/src/app/Projects/IssuesList.tsx | 80 +- frontend/src/app/Projects/Namespace.tsx | 51 +- frontend/src/app/Projects/ProjectInfo.tsx | 252 +++-- frontend/src/app/Projects/ProjectSearch.tsx | 224 ++--- frontend/src/app/Projects/Projects.tsx | 48 +- frontend/src/app/Projects/ProjectsList.tsx | 315 +++--- frontend/src/app/Projects/PullRequestList.tsx | 236 ++--- frontend/src/app/Projects/ReleasesList.tsx | 126 +-- frontend/src/app/Results/ResultsPageCopr.tsx | 300 +++--- .../app/Results/ResultsPageCoprDetails.tsx | 114 +-- frontend/src/app/Results/ResultsPageKoji.tsx | 336 +++---- frontend/src/app/Results/ResultsPageSRPM.tsx | 385 ++++--- .../Results/ResultsPageSyncReleaseRuns.tsx | 507 +++++----- .../app/Results/ResultsPageTestingFarm.tsx | 500 +++++----- .../src/app/StatusLabel/BaseStatusLabel.tsx | 98 +- frontend/src/app/StatusLabel/StatusLabel.tsx | 72 +- .../SyncReleaseTargetStatusLabel.tsx | 80 +- frontend/src/app/Support/Support.tsx | 28 +- frontend/src/app/Trigger/TriggerInfo.tsx | 323 +++--- frontend/src/app/Trigger/TriggerLink.tsx | 104 +- frontend/src/app/Usage/Usage.tsx | 118 +-- frontend/src/app/Usage/UsageInterval.tsx | 481 +++++---- frontend/src/app/Usage/UsageList.tsx | 565 +++++------ frontend/src/app/Usage/UsageListData.ts | 112 +-- frontend/src/app/routes.tsx | 278 +++--- frontend/src/app/utils/LabelLink.tsx | 32 +- frontend/src/app/utils/SHACopy.tsx | 56 +- frontend/src/app/utils/Timestamp.tsx | 106 +- frontend/src/app/utils/forgeUrls.tsx | 126 +-- .../app/utils/storybook/withQueryClient.jsx | 44 +- frontend/src/app/utils/useTitle.tsx | 2 +- frontend/src/global.d.ts | 8 +- frontend/src/index.tsx | 6 +- frontend/vite.config.ts | 18 +- 62 files changed, 5008 insertions(+), 5322 deletions(-) diff --git a/frontend/.storybook/main.ts b/frontend/.storybook/main.ts index 3dd6630e..7c5ee1f2 100644 --- a/frontend/.storybook/main.ts +++ b/frontend/.storybook/main.ts @@ -1,21 +1,21 @@ import type { StorybookConfig } from "@storybook/react-vite"; const config: StorybookConfig = { - stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], - addons: [ - "@storybook/addon-links", - "@storybook/addon-essentials", - "@storybook/addon-interactions", - "storybook-addon-react-router-v6", - "@storybook/addon-a11y", - ], - framework: { - name: "@storybook/react-vite", - options: {}, - }, - docs: { - autodocs: "tag", - }, + stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], + addons: [ + "@storybook/addon-links", + "@storybook/addon-essentials", + "@storybook/addon-interactions", + "storybook-addon-react-router-v6", + "@storybook/addon-a11y", + ], + framework: { + name: "@storybook/react-vite", + options: {}, + }, + docs: { + autodocs: "tag", + }, }; export default config; diff --git a/frontend/.storybook/preview.ts b/frontend/.storybook/preview.ts index 93aadbc0..3c0d79f6 100644 --- a/frontend/.storybook/preview.ts +++ b/frontend/.storybook/preview.ts @@ -5,10 +5,10 @@ import "@patternfly/react-core/dist/styles/base.css"; initialize(); const preview = { - parameters: { - actions: { argTypesRegex: "^on[A-Z].*" }, - }, - loaders: [mswLoader], + parameters: { + actions: { argTypesRegex: "^on[A-Z].*" }, + }, + loaders: [mswLoader], }; // Provide the MSW addon decorator globally so it works for every Storybook export default preview; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 42b825a2..1c9aad31 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -9,10 +9,10 @@ const queryClient = new QueryClient(); const router = createBrowserRouter(routes); const App = () => { - return ( - - - - ); + return ( + + + + ); }; export { App }; diff --git a/frontend/src/app/AppLayout/AppLayout.tsx b/frontend/src/app/AppLayout/AppLayout.tsx index 54eceb09..d988af42 100644 --- a/frontend/src/app/AppLayout/AppLayout.tsx +++ b/frontend/src/app/AppLayout/AppLayout.tsx @@ -1,253 +1,232 @@ import { - NavLink, - Outlet, - RouteObject, - matchRoutes, - useLocation, + NavLink, + Outlet, + RouteObject, + matchRoutes, + useLocation, } from "react-router-dom"; import { - Nav, - NavList, - NavItem, - Page, - PageSidebar, - Button, - SkipToContent, - Text, - TextContent, - Popover, - Masthead, - MastheadBrand, - MastheadContent, - MastheadMain, - MastheadToggle, - PageToggleButton, - Toolbar, - ToolbarContent, - ToolbarItem, - NavExpandable, - PageSidebarBody, + Nav, + NavList, + NavItem, + Page, + PageSidebar, + Button, + SkipToContent, + Text, + TextContent, + Popover, + Masthead, + MastheadBrand, + MastheadContent, + MastheadMain, + MastheadToggle, + PageToggleButton, + Toolbar, + ToolbarContent, + ToolbarItem, + NavExpandable, + PageSidebarBody, } from "@patternfly/react-core"; import { routes } from "../routes"; import packitLogo from "../../static/logo.png"; import React, { useState, useEffect } from "react"; import { - ExternalLinkAltIcon, - CodeBranchIcon, - BarsIcon, + ExternalLinkAltIcon, + CodeBranchIcon, + BarsIcon, } from "@patternfly/react-icons"; const AppLayout = () => { - const location = useLocation(); - const [activeLocationLabel, setActiveLocationLabel] = useState(""); - const [activeLocationCategory, setActiveLocationCategory] = useState(""); - const currentRouteTree = matchRoutes(routes, location); - // Dynamically set page title and update currently active page in sidebar - useEffect(() => { - if (!currentRouteTree) { - return; - } - // it matches everything that has to do with the current URL. Start from the leaf and work up to set title if at all. - for (let index = currentRouteTree.length - 1; index >= 0; index--) { - const element = currentRouteTree[index]; - if (element.route.handle?.label) { - setActiveLocationLabel(element.route.handle.label); - setActiveLocationCategory(element.route.handle.category || ""); - } - } - }, [currentRouteTree]); - - const [isNavOpen, setIsNavOpen] = useState(true); - const [isMobileView, setIsMobileView] = useState(true); - const [isNavOpenMobile, setIsNavOpenMobile] = useState(false); - const onNavToggleMobile = () => { - setIsNavOpenMobile(!isNavOpenMobile); - }; - const onNavToggle = () => { - setIsNavOpen(!isNavOpen); - }; - const onPageResize = (props: any) => { - setIsMobileView(props.mobileView); - }; + const location = useLocation(); + const [activeLocationLabel, setActiveLocationLabel] = useState(""); + const [activeLocationCategory, setActiveLocationCategory] = useState(""); + const currentRouteTree = matchRoutes(routes, location); + // Dynamically set page title and update currently active page in sidebar + useEffect(() => { + if (!currentRouteTree) { + return; + } + // it matches everything that has to do with the current URL. Start from the leaf and work up to set title if at all. + for (let index = currentRouteTree.length - 1; index >= 0; index--) { + const element = currentRouteTree[index]; + if (element.route.handle?.label) { + setActiveLocationLabel(element.route.handle.label); + setActiveLocationCategory(element.route.handle.category || ""); + } + } + }, [currentRouteTree]); - const headerToolbar = ( - - - - - - This service is open source, so all of its - code is inspectable. Explore repositories to - view and contribute to the source code. - - - - } - > - - - - - - ); + const [isNavOpen, setIsNavOpen] = useState(true); + const [isMobileView, setIsMobileView] = useState(true); + const [isNavOpenMobile, setIsNavOpenMobile] = useState(false); + const onNavToggleMobile = () => { + setIsNavOpenMobile(!isNavOpenMobile); + }; + const onNavToggle = () => { + setIsNavOpen(!isNavOpen); + }; + const onPageResize = (props: any) => { + setIsMobileView(props.mobileView); + }; - const Header = ( - - - + + + + + This service is open source, so all of its code is + inspectable. Explore repositories to view and contribute to + the source code. + + + + } + > + + + + + + ); - const NavigationRoute: React.FC<{ route: RouteObject; index: string }> = ({ - route, - index, - }) => ( - + + - - {route.handle.label} - - - ); - const addedCategories: string[] = []; - const Navigation = ( - - ); - const Sidebar = ( - - {Navigation} - - ); - const PageSkipToContent = ( - - Skip to Content - - ); - return ( - onPageResize(props)} - skipToContent={PageSkipToContent} + + + + + + Packit Logo + + + {headerToolbar} + + ); + + const NavigationRoute: React.FC<{ route: RouteObject; index: string }> = ({ + route, + index, + }) => ( + + + {route.handle.label} + + + ); + const addedCategories: string[] = []; + const Navigation = ( + + ); + const Sidebar = ( + + {Navigation} + + ); + const PageSkipToContent = ( + Skip to Content + ); + return ( + onPageResize(props)} + skipToContent={PageSkipToContent} + > + + + ); }; export { AppLayout }; diff --git a/frontend/src/app/Dashboard/Dashboard.tsx b/frontend/src/app/Dashboard/Dashboard.tsx index 2b731a37..24167116 100644 --- a/frontend/src/app/Dashboard/Dashboard.tsx +++ b/frontend/src/app/Dashboard/Dashboard.tsx @@ -1,151 +1,134 @@ import { - Card, - CardHeader, - CardBody, - Grid, - GridItem, - Page, - PageSection, - Title, + Card, + CardHeader, + CardBody, + Grid, + GridItem, + Page, + PageSection, + Title, } from "@patternfly/react-core"; import { - BugIcon, - InfoCircleIcon, - CodeIcon, - BellIcon, - CatalogIcon, - OutlinedCommentsIcon, + BugIcon, + InfoCircleIcon, + CodeIcon, + BellIcon, + CatalogIcon, + OutlinedCommentsIcon, } from "@patternfly/react-icons"; import { useTitle } from "../utils/useTitle"; const Dashboard = () => { - useTitle("Homes"); - return ( - - - Packit Dashboard - + useTitle("Homes"); + return ( + + + Packit Dashboard + - - - - - - -  Documentation - + + + + + + +  Documentation + - - For documentation related to Packit tooling and - Packit Service itself, see our{" "} - homepage. - - - + + For documentation related to Packit tooling and Packit Service + itself, see our homepage. + + + - - - - -  Blog posts - + + + + +  Blog posts + - - If you want to know about latest features - introduced to Packit and Packit Service, bug - fixes and related work being done, follow our{" "} - - blog posts - - . - - - + + If you want to know about latest features introduced to Packit + and Packit Service, bug fixes and related work being done, + follow our blog posts. + + + - - - - -  Issues - + + + + +  Issues + - - If you ran into any issue, either using Packit - Service or the tooling locally, try to find the - project that is as closely related to your issue - as possible (if you don't get it right, we can - always fix it) and follow our guidelines - regarding{" "} - - reporting issues - - . - - - + + If you ran into any issue, either using Packit Service or the + tooling locally, try to find the project that is as closely + related to your issue as possible (if you don't get it right, we + can always fix it) and follow our guidelines regarding{" "} + + reporting issues + + . + + + - - - - -  Source code on GitHub - + + + + +  Source code on GitHub + - - In case you would like to contribute to our code - base, please refer to our{" "} - - GitHub namespace - - , choose a project of your liking and don't - forget to follow our{" "} - - contribution guidelines - - . - - - + + In case you would like to contribute to our code base, please + refer to our{" "} + GitHub namespace, choose + a project of your liking and don't forget to follow our{" "} + + contribution guidelines + + . + + + - - - - -  Status page - + + + + +  Status page + - - For current status of Packit Service have a look - at{" "} - - our status page - - . - - - + + For current status of Packit Service have a look at{" "} + our status page. + + + - - - - -  Contact us - + + + + +  Contact us + - - If you want to contact us, choose one of the - ways{" "} - - from our website - - . All feedback is welcome! - - - - - - - ); + + If you want to contact us, choose one of the ways{" "} + from our website. All + feedback is welcome! + + + + + + + ); }; export { Dashboard }; diff --git a/frontend/src/app/Errors/ErrorApp.stories.tsx b/frontend/src/app/Errors/ErrorApp.stories.tsx index 145707de..4b785ee0 100644 --- a/frontend/src/app/Errors/ErrorApp.stories.tsx +++ b/frontend/src/app/Errors/ErrorApp.stories.tsx @@ -3,9 +3,9 @@ import type { Meta, StoryObj } from "@storybook/react"; import { ErrorApp } from "./ErrorApp"; const meta: Meta = { - title: "Error/App", - component: ErrorApp, - decorators: [withRouter], + title: "Error/App", + component: ErrorApp, + decorators: [withRouter], }; export default meta; diff --git a/frontend/src/app/Errors/ErrorApp.tsx b/frontend/src/app/Errors/ErrorApp.tsx index 801ba2d5..8b3b17a7 100644 --- a/frontend/src/app/Errors/ErrorApp.tsx +++ b/frontend/src/app/Errors/ErrorApp.tsx @@ -1,15 +1,15 @@ import { - Button, - ClipboardCopyButton, - CodeBlock, - CodeBlockAction, - CodeBlockCode, - EmptyState, - EmptyStateBody, - EmptyStateIcon, - EmptyStateActions, - EmptyStateHeader, - EmptyStateFooter, + Button, + ClipboardCopyButton, + CodeBlock, + CodeBlockAction, + CodeBlockCode, + EmptyState, + EmptyStateBody, + EmptyStateIcon, + EmptyStateActions, + EmptyStateHeader, + EmptyStateFooter, } from "@patternfly/react-core"; import React from "react"; @@ -17,80 +17,72 @@ import { ExclamationCircleIcon } from "@patternfly/react-icons"; import { useRouteError } from "react-router-dom"; const ErrorApp = () => { - const error = useRouteError(); - console.error(error); - const [copied, setCopied] = React.useState(false); + const error = useRouteError(); + console.error(error); + const [copied, setCopied] = React.useState(false); - const clipboardCopyFunc = (event: React.MouseEvent, text: string) => { - navigator.clipboard.writeText(text); - }; + const clipboardCopyFunc = (event: React.MouseEvent, text: string) => { + navigator.clipboard.writeText(text); + }; - const onClick = (event: React.MouseEvent, error: unknown) => { - clipboardCopyFunc(event, error?.toString() || "Unknown error"); - setCopied(true); - }; + const onClick = (event: React.MouseEvent, error: unknown) => { + clipboardCopyFunc(event, error?.toString() || "Unknown error"); + setCopied(true); + }; - const actions = ( - - onClick(e, error)} - exitDelay={copied ? 1500 : 600} - maxWidth="110px" - variant="plain" - onTooltipHidden={() => setCopied(false)} - > - {copied - ? "Successfully copied to clipboard!" - : "Copy to clipboard"} - - - ); + const actions = ( + + onClick(e, error)} + exitDelay={copied ? 1500 : 600} + maxWidth="110px" + variant="plain" + onTooltipHidden={() => setCopied(false)} + > + {copied ? "Successfully copied to clipboard!" : "Copy to clipboard"} + + + ); - return ( - - - } - headingLevel="h1" - /> - - If possible, take this callstack and report it upstream so it - can be fixed. When reporting, specify the reproducer on how the - bug happened. - - - - - - - - - - {error?.toString() ?? "Unknown error"} - - - - - - ); + return ( + + } + headingLevel="h1" + /> + + If possible, take this callstack and report it upstream so it can be + fixed. When reporting, specify the reproducer on how the bug happened. + + + + + + + + + + {error?.toString() ?? "Unknown error"} + + + + + + ); }; export { ErrorApp }; diff --git a/frontend/src/app/Errors/ErrorConnection.stories.tsx b/frontend/src/app/Errors/ErrorConnection.stories.tsx index 9fc27b1b..313cca5c 100644 --- a/frontend/src/app/Errors/ErrorConnection.stories.tsx +++ b/frontend/src/app/Errors/ErrorConnection.stories.tsx @@ -3,8 +3,8 @@ import type { Meta, StoryObj } from "@storybook/react"; import { ErrorConnection } from "./ErrorConnection"; const meta: Meta = { - title: "Error/Connection", - component: ErrorConnection, + title: "Error/Connection", + component: ErrorConnection, }; export default meta; diff --git a/frontend/src/app/Errors/ErrorConnection.tsx b/frontend/src/app/Errors/ErrorConnection.tsx index 6ead3dad..efda6b84 100644 --- a/frontend/src/app/Errors/ErrorConnection.tsx +++ b/frontend/src/app/Errors/ErrorConnection.tsx @@ -1,22 +1,22 @@ import { - EmptyState, - EmptyStateIcon, - EmptyStateVariant, - EmptyStateBody, - Icon, - EmptyStateHeader, + EmptyState, + EmptyStateIcon, + EmptyStateVariant, + EmptyStateBody, + Icon, + EmptyStateHeader, } from "@patternfly/react-core"; import { ExclamationTriangleIcon } from "@patternfly/react-icons"; const ErrorConnection = () => ( - - - - - - There was an error retrieving data. - + + + + + + There was an error retrieving data. + ); export { ErrorConnection }; diff --git a/frontend/src/app/Forge/ForgeIcon.stories.tsx b/frontend/src/app/Forge/ForgeIcon.stories.tsx index 4aaba786..3afa684e 100644 --- a/frontend/src/app/Forge/ForgeIcon.stories.tsx +++ b/frontend/src/app/Forge/ForgeIcon.stories.tsx @@ -5,28 +5,28 @@ import type { Meta, StoryObj } from "@storybook/react"; import { ForgeIcon } from "./ForgeIcon"; const meta: Meta = { - title: "Forge", - component: ForgeIcon, - tags: ["autodocs"], - decorators: [withRouter, withQueryClient], + title: "Forge", + component: ForgeIcon, + tags: ["autodocs"], + decorators: [withRouter, withQueryClient], }; export default meta; type Story = StoryObj; export const Primary: Story = { - args: { - url: "https://pagure.io", - }, + args: { + url: "https://pagure.io", + }, }; export const Github: Story = { - args: { - url: "https://github.com", - }, + args: { + url: "https://github.com", + }, }; export const Gitlab: Story = { - args: { - url: "https://gitlab.com", - }, + args: { + url: "https://gitlab.com", + }, }; diff --git a/frontend/src/app/Forge/ForgeIcon.tsx b/frontend/src/app/Forge/ForgeIcon.tsx index cacc472d..8739b495 100644 --- a/frontend/src/app/Forge/ForgeIcon.tsx +++ b/frontend/src/app/Forge/ForgeIcon.tsx @@ -6,29 +6,29 @@ import { getHostName } from "../utils/forgeUrls"; import { GithubIcon, GitlabIcon, GitIcon } from "@patternfly/react-icons"; export interface ForgeIconProps { - url: string; + url: string; } export const ForgeIcon: React.FC = ({ url }) => { - const forge = getHostName(url); + const forge = getHostName(url); - let forgeIcon; - switch (forge) { - case "github.com": - forgeIcon = ; - break; - case "gitlab.com": - forgeIcon = ; - break; - default: - // patternfly doesn't have an icon for pagure - forgeIcon = ; - break; - } + let forgeIcon; + switch (forge) { + case "github.com": + forgeIcon = ; + break; + case "gitlab.com": + forgeIcon = ; + break; + default: + // patternfly doesn't have an icon for pagure + forgeIcon = ; + break; + } - return ( - - {forgeIcon} - - ); + return ( + + {forgeIcon} + + ); }; diff --git a/frontend/src/app/Jobs/.fixtures/CoprBuildsData.fixture.tsx b/frontend/src/app/Jobs/.fixtures/CoprBuildsData.fixture.tsx index f17f59a3..888b286b 100644 --- a/frontend/src/app/Jobs/.fixtures/CoprBuildsData.fixture.tsx +++ b/frontend/src/app/Jobs/.fixtures/CoprBuildsData.fixture.tsx @@ -1,474 +1,474 @@ export const CoprBuildData = [ - { - packit_id: 25732, - project: "packit-ogr-779", - build_id: "5705488", - status_per_chroot: { - "fedora-36-x86_64": "success", - "fedora-37-x86_64": "success", - "fedora-rawhide-x86_64": "success", - "fedora-38-x86_64": "success", - "epel-9-x86_64": "success", - }, - packit_id_per_chroot: { - "fedora-36-x86_64": 25732, - "fedora-37-x86_64": 25733, - "fedora-rawhide-x86_64": 25729, - "fedora-38-x86_64": 25730, - "epel-9-x86_64": 25731, - }, - build_submitted_time: 1679672392, - web_url: "https://copr.fedorainfracloud.org/coprs/build/5705488/", - ref: "8c9610fede6a85b3cbe852ad14805e62426a2c90", - pr_id: 779, - branch_name: null, - repo_namespace: "packit", - repo_name: "ogr", - project_url: "https://github.com/packit/ogr", - }, - { - packit_id: 25726, - project: "packit-ogr-778", - build_id: "5705478", - status_per_chroot: { - "epel-9-x86_64": "success", - "fedora-38-x86_64": "success", - "fedora-37-x86_64": "success", - "fedora-36-x86_64": "success", - "fedora-rawhide-x86_64": "success", - }, - packit_id_per_chroot: { - "epel-9-x86_64": 25726, - "fedora-38-x86_64": 25725, - "fedora-37-x86_64": 25728, - "fedora-36-x86_64": 25727, - "fedora-rawhide-x86_64": 25724, - }, - build_submitted_time: 1679672209, - web_url: "https://copr.fedorainfracloud.org/coprs/build/5705478/", - ref: "c960d9adf105a09002e84ac375683e59e8d28a8b", - pr_id: 778, - branch_name: null, - repo_namespace: "packit", - repo_name: "ogr", - project_url: "https://github.com/packit/ogr", - }, - { - packit_id: 25721, - project: "packit-hello-world-1338", - build_id: "5702702", - status_per_chroot: { - "fedora-rawhide-x86_64": "success", - "fedora-36-x86_64": "success", - "fedora-37-x86_64": "success", - }, - packit_id_per_chroot: { - "fedora-rawhide-x86_64": 25721, - "fedora-36-x86_64": 25722, - "fedora-37-x86_64": 25723, - }, - build_submitted_time: 1679636438, - web_url: "https://copr.fedorainfracloud.org/coprs/build/5702702/", - ref: "7a5bf5a11661fc704c101fea7ae4ebe432f56b44", - pr_id: 1338, - branch_name: null, - repo_namespace: "packit", - repo_name: "hello-world", - project_url: "https://github.com/packit/hello-world", - }, - { - packit_id: 25718, - project: "packit-hello-world-1337", - build_id: "5702699", - status_per_chroot: { - "fedora-rawhide-x86_64": "success", - "fedora-36-x86_64": "success", - "fedora-37-x86_64": "success", - }, - packit_id_per_chroot: { - "fedora-rawhide-x86_64": 25718, - "fedora-36-x86_64": 25719, - "fedora-37-x86_64": 25720, - }, - build_submitted_time: 1679636378, - web_url: "https://copr.fedorainfracloud.org/coprs/build/5702699/", - ref: "12a0beab3217344b4be0b398f09240fe026cf6f4", - pr_id: 1337, - branch_name: null, - repo_namespace: "packit", - repo_name: "hello-world", - project_url: "https://github.com/packit/hello-world", - }, - { - packit_id: 25715, - project: "packit-hello-world-1298", - build_id: "5702698", - status_per_chroot: { - "fedora-rawhide-x86_64": "success", - "fedora-36-x86_64": "success", - "fedora-37-x86_64": "success", - }, - packit_id_per_chroot: { - "fedora-rawhide-x86_64": 25715, - "fedora-36-x86_64": 25716, - "fedora-37-x86_64": 25717, - }, - build_submitted_time: 1679636005, - web_url: "https://copr.fedorainfracloud.org/coprs/build/5702698/", - ref: "824b9cb6304a087b5e4dc72c44b5d53ccfd60ec7", - pr_id: 1298, - branch_name: null, - repo_namespace: "packit", - repo_name: "hello-world", - project_url: "https://github.com/packit/hello-world", - }, - { - packit_id: 25713, - project: "packit-hello-world-1295", - build_id: "5702692", - status_per_chroot: { - "fedora-36-x86_64": "success", - "fedora-38-x86_64": "success", - "fedora-rawhide-x86_64": "success", - "fedora-37-x86_64": "success", - }, - packit_id_per_chroot: { - "fedora-36-x86_64": 25713, - "fedora-38-x86_64": 25712, - "fedora-rawhide-x86_64": 25711, - "fedora-37-x86_64": 25714, - }, - build_submitted_time: 1679635608, - web_url: "https://copr.fedorainfracloud.org/coprs/build/5702692/", - ref: "ba11b61ac11609da625d92f4f71439efed9e6c7e", - pr_id: 1295, - branch_name: null, - repo_namespace: "packit", - repo_name: "hello-world", - project_url: "https://github.com/packit/hello-world", - }, - { - packit_id: 25707, - project: "packit-hello-world-1290", - build_id: "5702688", - status_per_chroot: { - "fedora-rawhide-x86_64": "success", - "fedora-38-x86_64": "success", - "fedora-36-x86_64": "success", - "fedora-37-x86_64": "success", - }, - packit_id_per_chroot: { - "fedora-rawhide-x86_64": 25707, - "fedora-38-x86_64": 25708, - "fedora-36-x86_64": 25709, - "fedora-37-x86_64": 25710, - }, - build_submitted_time: 1679635010, - web_url: "https://copr.fedorainfracloud.org/coprs/build/5702688/", - ref: "cdaf038ef4493ade7d672caad0b77466e352935b", - pr_id: 1290, - branch_name: null, - repo_namespace: "packit", - repo_name: "hello-world", - project_url: "https://github.com/packit/hello-world", - }, - { - packit_id: 25706, - project: "packit-hello-world-1291", - build_id: "5702678", - status_per_chroot: { "centos-stream-8-x86_64": "success" }, - packit_id_per_chroot: { "centos-stream-8-x86_64": 25706 }, - build_submitted_time: 1679634494, - web_url: "https://copr.fedorainfracloud.org/coprs/build/5702678/", - ref: "9dc0c44d382687a5309fe8573f236cc8ab160653", - pr_id: 1291, - branch_name: null, - repo_namespace: "packit", - repo_name: "hello-world", - project_url: "https://github.com/packit/hello-world", - }, - { - packit_id: 25704, - project: "packit-hello-world-1292", - build_id: "5702660", - status_per_chroot: { - "epel-7-x86_64": "success", - "epel-8-x86_64": "success", - }, - packit_id_per_chroot: { - "epel-7-x86_64": 25704, - "epel-8-x86_64": 25705, - }, - build_submitted_time: 1679634080, - web_url: "https://copr.fedorainfracloud.org/coprs/build/5702660/", - ref: "53d9385e1e7c262231f8c348b901ac33ee66f45b", - pr_id: 1292, - branch_name: null, - repo_namespace: "packit", - repo_name: "hello-world", - project_url: "https://github.com/packit/hello-world", - }, - { - packit_id: 25703, - project: "packit-hello-world-1293", - build_id: "5702646", - status_per_chroot: { "fedora-rawhide-x86_64": "success" }, - packit_id_per_chroot: { "fedora-rawhide-x86_64": 25703 }, - build_submitted_time: 1679633402, - web_url: "https://copr.fedorainfracloud.org/coprs/build/5702646/", - ref: "c64a22a416699449b1f057c2092b1fe9d170ad18", - pr_id: 1293, - branch_name: null, - repo_namespace: "packit", - repo_name: "hello-world", - project_url: "https://github.com/packit/hello-world", - }, - { - packit_id: 25699, - project: "packit-hello-world-1294", - build_id: "5702638", - status_per_chroot: { - "fedora-rawhide-x86_64": "success", - "fedora-36-x86_64": "success", - "fedora-37-x86_64": "success", - "fedora-38-x86_64": "success", - }, - packit_id_per_chroot: { - "fedora-rawhide-x86_64": 25699, - "fedora-36-x86_64": 25701, - "fedora-37-x86_64": 25702, - "fedora-38-x86_64": 25700, - }, - build_submitted_time: 1679632721, - web_url: "https://copr.fedorainfracloud.org/coprs/build/5702638/", - ref: "d7340789f9f5e0a08820c46b4c042edb96fc2d7b", - pr_id: 1294, - branch_name: null, - repo_namespace: "packit", - repo_name: "hello-world", - project_url: "https://github.com/packit/hello-world", - }, - { - packit_id: 25695, - project: "packit-hello-world-1296", - build_id: "5702625", - status_per_chroot: { - "fedora-rawhide-x86_64": "success", - "fedora-37-x86_64": "success", - "fedora-36-x86_64": "success", - "fedora-38-x86_64": "success", - }, - packit_id_per_chroot: { - "fedora-rawhide-x86_64": 25695, - "fedora-37-x86_64": 25698, - "fedora-36-x86_64": 25697, - "fedora-38-x86_64": 25696, - }, - build_submitted_time: 1679632018, - web_url: "https://copr.fedorainfracloud.org/coprs/build/5702625/", - ref: "016aebdc844706608c90213c34e44d4df5121645", - pr_id: 1296, - branch_name: null, - repo_namespace: "packit", - repo_name: "hello-world", - project_url: "https://github.com/packit/hello-world", - }, - { - packit_id: 25692, - project: "gitlab.com-packit-service-hello-world-108", - build_id: "5702615", - status_per_chroot: { - "fedora-rawhide-x86_64": "success", - "fedora-36-x86_64": "success", - "fedora-37-x86_64": "success", - }, - packit_id_per_chroot: { - "fedora-rawhide-x86_64": 25692, - "fedora-36-x86_64": 25693, - "fedora-37-x86_64": 25694, - }, - build_submitted_time: 1679631347, - web_url: "https://copr.fedorainfracloud.org/coprs/build/5702615/", - ref: "ff30d5383caff77cd0fef541c1610fa415acf11f", - pr_id: 108, - branch_name: null, - repo_namespace: "packit-service", - repo_name: "hello-world", - project_url: "https://gitlab.com/packit-service/hello-world", - }, - { - packit_id: 25689, - project: "gitlab.com-packit-service-hello-world-27", - build_id: "5702602", - status_per_chroot: { - "fedora-rawhide-x86_64": "success", - "fedora-36-x86_64": "success", - "fedora-37-x86_64": "success", - }, - packit_id_per_chroot: { - "fedora-rawhide-x86_64": 25689, - "fedora-36-x86_64": 25690, - "fedora-37-x86_64": 25691, - }, - build_submitted_time: 1679630868, - web_url: "https://copr.fedorainfracloud.org/coprs/build/5702602/", - ref: "264cb2b7d09dce029cb0f83df39cb762c26159ca", - pr_id: 27, - branch_name: null, - repo_namespace: "packit-service", - repo_name: "hello-world", - project_url: "https://gitlab.com/packit-service/hello-world", - }, - { - packit_id: 25685, - project: "gitlab.com-packit-service-hello-world-22", - build_id: "5702556", - status_per_chroot: { - "fedora-rawhide-x86_64": "success", - "fedora-38-x86_64": "success", - "fedora-36-x86_64": "success", - "fedora-37-x86_64": "success", - }, - packit_id_per_chroot: { - "fedora-rawhide-x86_64": 25685, - "fedora-38-x86_64": 25686, - "fedora-36-x86_64": 25687, - "fedora-37-x86_64": 25688, - }, - build_submitted_time: 1679630432, - web_url: "https://copr.fedorainfracloud.org/coprs/build/5702556/", - ref: "31766c1c8890bec095fda9fd5bd0d04267093e31", - pr_id: 22, - branch_name: null, - repo_namespace: "packit-service", - repo_name: "hello-world", - project_url: "https://gitlab.com/packit-service/hello-world", - }, - { - packit_id: 25682, - project: "packit-dev", - build_id: "5698637", - status_per_chroot: { - "epel-9-x86_64": "success", - "fedora-37-x86_64": "success", - "fedora-36-x86_64": "success", - "fedora-38-x86_64": "success", - "fedora-rawhide-x86_64": "success", - }, - packit_id_per_chroot: { - "epel-9-x86_64": 25682, - "fedora-37-x86_64": 25683, - "fedora-36-x86_64": 25684, - "fedora-38-x86_64": 25680, - "fedora-rawhide-x86_64": 25681, - }, - build_submitted_time: 1679579002, - web_url: "https://copr.fedorainfracloud.org/coprs/build/5698637/", - ref: "21fb5bb31a0deb56c52f7c5664e46d2ca76d02ae", - pr_id: null, - branch_name: "main", - repo_namespace: "packit", - repo_name: "packit", - project_url: "https://github.com/packit/packit", - }, - { - packit_id: 25676, - project: "packit-packit-1897", - build_id: "5698616", - status_per_chroot: { - "fedora-rawhide-x86_64": "success", - "fedora-36-x86_64": "success", - "epel-9-x86_64": "success", - "fedora-37-x86_64": "success", - "fedora-38-x86_64": "success", - }, - packit_id_per_chroot: { - "fedora-rawhide-x86_64": 25676, - "fedora-36-x86_64": 25679, - "epel-9-x86_64": 25677, - "fedora-37-x86_64": 25678, - "fedora-38-x86_64": 25675, - }, - build_submitted_time: 1679577383, - web_url: "https://copr.fedorainfracloud.org/coprs/build/5698616/", - ref: "1ed4f97292ccf316db037b8cc795c8e8efb3a5fe", - pr_id: 1897, - branch_name: null, - repo_namespace: "packit", - repo_name: "packit", - project_url: "https://github.com/packit/packit", - }, - { - packit_id: 25670, - project: "packit-packit-1864", - build_id: "5697830", - status_per_chroot: { - "fedora-38-x86_64": "success", - "fedora-rawhide-x86_64": "success", - "epel-9-x86_64": "success", - "fedora-37-x86_64": "success", - "fedora-36-x86_64": "success", - }, - packit_id_per_chroot: { - "fedora-38-x86_64": 25670, - "fedora-rawhide-x86_64": 25671, - "epel-9-x86_64": 25672, - "fedora-37-x86_64": 25673, - "fedora-36-x86_64": 25674, - }, - build_submitted_time: 1679560765, - web_url: "https://copr.fedorainfracloud.org/coprs/build/5697830/", - ref: "c24b2ca403fcc96b50f4450c3c01d145a20e1ae3", - pr_id: 1864, - branch_name: null, - repo_namespace: "packit", - repo_name: "packit", - project_url: "https://github.com/packit/packit", - }, - { - packit_id: 25667, - project: "packit-hello-world-1336", - build_id: "5697372", - status_per_chroot: { - "fedora-36-x86_64": "success", - "fedora-37-x86_64": "success", - "fedora-rawhide-x86_64": "success", - }, - packit_id_per_chroot: { - "fedora-36-x86_64": 25667, - "fedora-37-x86_64": 25668, - "fedora-rawhide-x86_64": 25669, - }, - build_submitted_time: 1679549803, - web_url: "https://copr.fedorainfracloud.org/coprs/build/5697372/", - ref: "1ef1222b9cebb56e925d05b0a39914bde671d7ad", - pr_id: 1336, - branch_name: null, - repo_namespace: "packit", - repo_name: "hello-world", - project_url: "https://github.com/packit/hello-world", - }, - { - packit_id: 25664, - project: "packit-hello-world-1335", - build_id: "5697370", - status_per_chroot: { - "fedora-36-x86_64": "success", - "fedora-37-x86_64": "success", - "fedora-rawhide-x86_64": "success", - }, - packit_id_per_chroot: { - "fedora-36-x86_64": 25664, - "fedora-37-x86_64": 25665, - "fedora-rawhide-x86_64": 25666, - }, - build_submitted_time: 1679549598, - web_url: "https://copr.fedorainfracloud.org/coprs/build/5697370/", - ref: "41bc5e187778a51b9db69e2c28010464b0483c14", - pr_id: 1335, - branch_name: null, - repo_namespace: "packit", - repo_name: "hello-world", - project_url: "https://github.com/packit/hello-world", + { + packit_id: 25732, + project: "packit-ogr-779", + build_id: "5705488", + status_per_chroot: { + "fedora-36-x86_64": "success", + "fedora-37-x86_64": "success", + "fedora-rawhide-x86_64": "success", + "fedora-38-x86_64": "success", + "epel-9-x86_64": "success", }, + packit_id_per_chroot: { + "fedora-36-x86_64": 25732, + "fedora-37-x86_64": 25733, + "fedora-rawhide-x86_64": 25729, + "fedora-38-x86_64": 25730, + "epel-9-x86_64": 25731, + }, + build_submitted_time: 1679672392, + web_url: "https://copr.fedorainfracloud.org/coprs/build/5705488/", + ref: "8c9610fede6a85b3cbe852ad14805e62426a2c90", + pr_id: 779, + branch_name: null, + repo_namespace: "packit", + repo_name: "ogr", + project_url: "https://github.com/packit/ogr", + }, + { + packit_id: 25726, + project: "packit-ogr-778", + build_id: "5705478", + status_per_chroot: { + "epel-9-x86_64": "success", + "fedora-38-x86_64": "success", + "fedora-37-x86_64": "success", + "fedora-36-x86_64": "success", + "fedora-rawhide-x86_64": "success", + }, + packit_id_per_chroot: { + "epel-9-x86_64": 25726, + "fedora-38-x86_64": 25725, + "fedora-37-x86_64": 25728, + "fedora-36-x86_64": 25727, + "fedora-rawhide-x86_64": 25724, + }, + build_submitted_time: 1679672209, + web_url: "https://copr.fedorainfracloud.org/coprs/build/5705478/", + ref: "c960d9adf105a09002e84ac375683e59e8d28a8b", + pr_id: 778, + branch_name: null, + repo_namespace: "packit", + repo_name: "ogr", + project_url: "https://github.com/packit/ogr", + }, + { + packit_id: 25721, + project: "packit-hello-world-1338", + build_id: "5702702", + status_per_chroot: { + "fedora-rawhide-x86_64": "success", + "fedora-36-x86_64": "success", + "fedora-37-x86_64": "success", + }, + packit_id_per_chroot: { + "fedora-rawhide-x86_64": 25721, + "fedora-36-x86_64": 25722, + "fedora-37-x86_64": 25723, + }, + build_submitted_time: 1679636438, + web_url: "https://copr.fedorainfracloud.org/coprs/build/5702702/", + ref: "7a5bf5a11661fc704c101fea7ae4ebe432f56b44", + pr_id: 1338, + branch_name: null, + repo_namespace: "packit", + repo_name: "hello-world", + project_url: "https://github.com/packit/hello-world", + }, + { + packit_id: 25718, + project: "packit-hello-world-1337", + build_id: "5702699", + status_per_chroot: { + "fedora-rawhide-x86_64": "success", + "fedora-36-x86_64": "success", + "fedora-37-x86_64": "success", + }, + packit_id_per_chroot: { + "fedora-rawhide-x86_64": 25718, + "fedora-36-x86_64": 25719, + "fedora-37-x86_64": 25720, + }, + build_submitted_time: 1679636378, + web_url: "https://copr.fedorainfracloud.org/coprs/build/5702699/", + ref: "12a0beab3217344b4be0b398f09240fe026cf6f4", + pr_id: 1337, + branch_name: null, + repo_namespace: "packit", + repo_name: "hello-world", + project_url: "https://github.com/packit/hello-world", + }, + { + packit_id: 25715, + project: "packit-hello-world-1298", + build_id: "5702698", + status_per_chroot: { + "fedora-rawhide-x86_64": "success", + "fedora-36-x86_64": "success", + "fedora-37-x86_64": "success", + }, + packit_id_per_chroot: { + "fedora-rawhide-x86_64": 25715, + "fedora-36-x86_64": 25716, + "fedora-37-x86_64": 25717, + }, + build_submitted_time: 1679636005, + web_url: "https://copr.fedorainfracloud.org/coprs/build/5702698/", + ref: "824b9cb6304a087b5e4dc72c44b5d53ccfd60ec7", + pr_id: 1298, + branch_name: null, + repo_namespace: "packit", + repo_name: "hello-world", + project_url: "https://github.com/packit/hello-world", + }, + { + packit_id: 25713, + project: "packit-hello-world-1295", + build_id: "5702692", + status_per_chroot: { + "fedora-36-x86_64": "success", + "fedora-38-x86_64": "success", + "fedora-rawhide-x86_64": "success", + "fedora-37-x86_64": "success", + }, + packit_id_per_chroot: { + "fedora-36-x86_64": 25713, + "fedora-38-x86_64": 25712, + "fedora-rawhide-x86_64": 25711, + "fedora-37-x86_64": 25714, + }, + build_submitted_time: 1679635608, + web_url: "https://copr.fedorainfracloud.org/coprs/build/5702692/", + ref: "ba11b61ac11609da625d92f4f71439efed9e6c7e", + pr_id: 1295, + branch_name: null, + repo_namespace: "packit", + repo_name: "hello-world", + project_url: "https://github.com/packit/hello-world", + }, + { + packit_id: 25707, + project: "packit-hello-world-1290", + build_id: "5702688", + status_per_chroot: { + "fedora-rawhide-x86_64": "success", + "fedora-38-x86_64": "success", + "fedora-36-x86_64": "success", + "fedora-37-x86_64": "success", + }, + packit_id_per_chroot: { + "fedora-rawhide-x86_64": 25707, + "fedora-38-x86_64": 25708, + "fedora-36-x86_64": 25709, + "fedora-37-x86_64": 25710, + }, + build_submitted_time: 1679635010, + web_url: "https://copr.fedorainfracloud.org/coprs/build/5702688/", + ref: "cdaf038ef4493ade7d672caad0b77466e352935b", + pr_id: 1290, + branch_name: null, + repo_namespace: "packit", + repo_name: "hello-world", + project_url: "https://github.com/packit/hello-world", + }, + { + packit_id: 25706, + project: "packit-hello-world-1291", + build_id: "5702678", + status_per_chroot: { "centos-stream-8-x86_64": "success" }, + packit_id_per_chroot: { "centos-stream-8-x86_64": 25706 }, + build_submitted_time: 1679634494, + web_url: "https://copr.fedorainfracloud.org/coprs/build/5702678/", + ref: "9dc0c44d382687a5309fe8573f236cc8ab160653", + pr_id: 1291, + branch_name: null, + repo_namespace: "packit", + repo_name: "hello-world", + project_url: "https://github.com/packit/hello-world", + }, + { + packit_id: 25704, + project: "packit-hello-world-1292", + build_id: "5702660", + status_per_chroot: { + "epel-7-x86_64": "success", + "epel-8-x86_64": "success", + }, + packit_id_per_chroot: { + "epel-7-x86_64": 25704, + "epel-8-x86_64": 25705, + }, + build_submitted_time: 1679634080, + web_url: "https://copr.fedorainfracloud.org/coprs/build/5702660/", + ref: "53d9385e1e7c262231f8c348b901ac33ee66f45b", + pr_id: 1292, + branch_name: null, + repo_namespace: "packit", + repo_name: "hello-world", + project_url: "https://github.com/packit/hello-world", + }, + { + packit_id: 25703, + project: "packit-hello-world-1293", + build_id: "5702646", + status_per_chroot: { "fedora-rawhide-x86_64": "success" }, + packit_id_per_chroot: { "fedora-rawhide-x86_64": 25703 }, + build_submitted_time: 1679633402, + web_url: "https://copr.fedorainfracloud.org/coprs/build/5702646/", + ref: "c64a22a416699449b1f057c2092b1fe9d170ad18", + pr_id: 1293, + branch_name: null, + repo_namespace: "packit", + repo_name: "hello-world", + project_url: "https://github.com/packit/hello-world", + }, + { + packit_id: 25699, + project: "packit-hello-world-1294", + build_id: "5702638", + status_per_chroot: { + "fedora-rawhide-x86_64": "success", + "fedora-36-x86_64": "success", + "fedora-37-x86_64": "success", + "fedora-38-x86_64": "success", + }, + packit_id_per_chroot: { + "fedora-rawhide-x86_64": 25699, + "fedora-36-x86_64": 25701, + "fedora-37-x86_64": 25702, + "fedora-38-x86_64": 25700, + }, + build_submitted_time: 1679632721, + web_url: "https://copr.fedorainfracloud.org/coprs/build/5702638/", + ref: "d7340789f9f5e0a08820c46b4c042edb96fc2d7b", + pr_id: 1294, + branch_name: null, + repo_namespace: "packit", + repo_name: "hello-world", + project_url: "https://github.com/packit/hello-world", + }, + { + packit_id: 25695, + project: "packit-hello-world-1296", + build_id: "5702625", + status_per_chroot: { + "fedora-rawhide-x86_64": "success", + "fedora-37-x86_64": "success", + "fedora-36-x86_64": "success", + "fedora-38-x86_64": "success", + }, + packit_id_per_chroot: { + "fedora-rawhide-x86_64": 25695, + "fedora-37-x86_64": 25698, + "fedora-36-x86_64": 25697, + "fedora-38-x86_64": 25696, + }, + build_submitted_time: 1679632018, + web_url: "https://copr.fedorainfracloud.org/coprs/build/5702625/", + ref: "016aebdc844706608c90213c34e44d4df5121645", + pr_id: 1296, + branch_name: null, + repo_namespace: "packit", + repo_name: "hello-world", + project_url: "https://github.com/packit/hello-world", + }, + { + packit_id: 25692, + project: "gitlab.com-packit-service-hello-world-108", + build_id: "5702615", + status_per_chroot: { + "fedora-rawhide-x86_64": "success", + "fedora-36-x86_64": "success", + "fedora-37-x86_64": "success", + }, + packit_id_per_chroot: { + "fedora-rawhide-x86_64": 25692, + "fedora-36-x86_64": 25693, + "fedora-37-x86_64": 25694, + }, + build_submitted_time: 1679631347, + web_url: "https://copr.fedorainfracloud.org/coprs/build/5702615/", + ref: "ff30d5383caff77cd0fef541c1610fa415acf11f", + pr_id: 108, + branch_name: null, + repo_namespace: "packit-service", + repo_name: "hello-world", + project_url: "https://gitlab.com/packit-service/hello-world", + }, + { + packit_id: 25689, + project: "gitlab.com-packit-service-hello-world-27", + build_id: "5702602", + status_per_chroot: { + "fedora-rawhide-x86_64": "success", + "fedora-36-x86_64": "success", + "fedora-37-x86_64": "success", + }, + packit_id_per_chroot: { + "fedora-rawhide-x86_64": 25689, + "fedora-36-x86_64": 25690, + "fedora-37-x86_64": 25691, + }, + build_submitted_time: 1679630868, + web_url: "https://copr.fedorainfracloud.org/coprs/build/5702602/", + ref: "264cb2b7d09dce029cb0f83df39cb762c26159ca", + pr_id: 27, + branch_name: null, + repo_namespace: "packit-service", + repo_name: "hello-world", + project_url: "https://gitlab.com/packit-service/hello-world", + }, + { + packit_id: 25685, + project: "gitlab.com-packit-service-hello-world-22", + build_id: "5702556", + status_per_chroot: { + "fedora-rawhide-x86_64": "success", + "fedora-38-x86_64": "success", + "fedora-36-x86_64": "success", + "fedora-37-x86_64": "success", + }, + packit_id_per_chroot: { + "fedora-rawhide-x86_64": 25685, + "fedora-38-x86_64": 25686, + "fedora-36-x86_64": 25687, + "fedora-37-x86_64": 25688, + }, + build_submitted_time: 1679630432, + web_url: "https://copr.fedorainfracloud.org/coprs/build/5702556/", + ref: "31766c1c8890bec095fda9fd5bd0d04267093e31", + pr_id: 22, + branch_name: null, + repo_namespace: "packit-service", + repo_name: "hello-world", + project_url: "https://gitlab.com/packit-service/hello-world", + }, + { + packit_id: 25682, + project: "packit-dev", + build_id: "5698637", + status_per_chroot: { + "epel-9-x86_64": "success", + "fedora-37-x86_64": "success", + "fedora-36-x86_64": "success", + "fedora-38-x86_64": "success", + "fedora-rawhide-x86_64": "success", + }, + packit_id_per_chroot: { + "epel-9-x86_64": 25682, + "fedora-37-x86_64": 25683, + "fedora-36-x86_64": 25684, + "fedora-38-x86_64": 25680, + "fedora-rawhide-x86_64": 25681, + }, + build_submitted_time: 1679579002, + web_url: "https://copr.fedorainfracloud.org/coprs/build/5698637/", + ref: "21fb5bb31a0deb56c52f7c5664e46d2ca76d02ae", + pr_id: null, + branch_name: "main", + repo_namespace: "packit", + repo_name: "packit", + project_url: "https://github.com/packit/packit", + }, + { + packit_id: 25676, + project: "packit-packit-1897", + build_id: "5698616", + status_per_chroot: { + "fedora-rawhide-x86_64": "success", + "fedora-36-x86_64": "success", + "epel-9-x86_64": "success", + "fedora-37-x86_64": "success", + "fedora-38-x86_64": "success", + }, + packit_id_per_chroot: { + "fedora-rawhide-x86_64": 25676, + "fedora-36-x86_64": 25679, + "epel-9-x86_64": 25677, + "fedora-37-x86_64": 25678, + "fedora-38-x86_64": 25675, + }, + build_submitted_time: 1679577383, + web_url: "https://copr.fedorainfracloud.org/coprs/build/5698616/", + ref: "1ed4f97292ccf316db037b8cc795c8e8efb3a5fe", + pr_id: 1897, + branch_name: null, + repo_namespace: "packit", + repo_name: "packit", + project_url: "https://github.com/packit/packit", + }, + { + packit_id: 25670, + project: "packit-packit-1864", + build_id: "5697830", + status_per_chroot: { + "fedora-38-x86_64": "success", + "fedora-rawhide-x86_64": "success", + "epel-9-x86_64": "success", + "fedora-37-x86_64": "success", + "fedora-36-x86_64": "success", + }, + packit_id_per_chroot: { + "fedora-38-x86_64": 25670, + "fedora-rawhide-x86_64": 25671, + "epel-9-x86_64": 25672, + "fedora-37-x86_64": 25673, + "fedora-36-x86_64": 25674, + }, + build_submitted_time: 1679560765, + web_url: "https://copr.fedorainfracloud.org/coprs/build/5697830/", + ref: "c24b2ca403fcc96b50f4450c3c01d145a20e1ae3", + pr_id: 1864, + branch_name: null, + repo_namespace: "packit", + repo_name: "packit", + project_url: "https://github.com/packit/packit", + }, + { + packit_id: 25667, + project: "packit-hello-world-1336", + build_id: "5697372", + status_per_chroot: { + "fedora-36-x86_64": "success", + "fedora-37-x86_64": "success", + "fedora-rawhide-x86_64": "success", + }, + packit_id_per_chroot: { + "fedora-36-x86_64": 25667, + "fedora-37-x86_64": 25668, + "fedora-rawhide-x86_64": 25669, + }, + build_submitted_time: 1679549803, + web_url: "https://copr.fedorainfracloud.org/coprs/build/5697372/", + ref: "1ef1222b9cebb56e925d05b0a39914bde671d7ad", + pr_id: 1336, + branch_name: null, + repo_namespace: "packit", + repo_name: "hello-world", + project_url: "https://github.com/packit/hello-world", + }, + { + packit_id: 25664, + project: "packit-hello-world-1335", + build_id: "5697370", + status_per_chroot: { + "fedora-36-x86_64": "success", + "fedora-37-x86_64": "success", + "fedora-rawhide-x86_64": "success", + }, + packit_id_per_chroot: { + "fedora-36-x86_64": 25664, + "fedora-37-x86_64": 25665, + "fedora-rawhide-x86_64": 25666, + }, + build_submitted_time: 1679549598, + web_url: "https://copr.fedorainfracloud.org/coprs/build/5697370/", + ref: "41bc5e187778a51b9db69e2c28010464b0483c14", + pr_id: 1335, + branch_name: null, + repo_namespace: "packit", + repo_name: "hello-world", + project_url: "https://github.com/packit/hello-world", + }, ]; diff --git a/frontend/src/app/Jobs/CoprBuildsTable.stories.tsx b/frontend/src/app/Jobs/CoprBuildsTable.stories.tsx index 32c7be75..45ecebd7 100644 --- a/frontend/src/app/Jobs/CoprBuildsTable.stories.tsx +++ b/frontend/src/app/Jobs/CoprBuildsTable.stories.tsx @@ -6,18 +6,18 @@ import { CoprBuildData } from "./.fixtures/CoprBuildsData.fixture"; import type { Meta, StoryObj } from "@storybook/react"; const meta: Meta = { - title: "Jobs/CoprBuildsTable", - component: CoprBuildsTable, - decorators: [withRouter, withQueryClient], - parameters: { - msw: { - handlers: { - common: rest.get("*/copr-builds", (req, res, ctx) => { - return res(ctx.json(CoprBuildData)); - }), - }, - }, + title: "Jobs/CoprBuildsTable", + component: CoprBuildsTable, + decorators: [withRouter, withQueryClient], + parameters: { + msw: { + handlers: { + common: rest.get("*/copr-builds", (req, res, ctx) => { + return res(ctx.json(CoprBuildData)); + }), + }, }, + }, }; export default meta; @@ -26,13 +26,13 @@ type Story = StoryObj; export const ResultsFound: Story = {}; export const ServerFails: Story = { - parameters: { - msw: { - handlers: { - common: rest.get("*/copr-builds", (req, res, ctx) => { - return res(ctx.delay(800), ctx.status(503)); - }), - }, - }, + parameters: { + msw: { + handlers: { + common: rest.get("*/copr-builds", (req, res, ctx) => { + return res(ctx.delay(800), ctx.status(503)); + }), + }, }, + }, }; diff --git a/frontend/src/app/Jobs/CoprBuildsTable.tsx b/frontend/src/app/Jobs/CoprBuildsTable.tsx index 47bd9760..2e75e8bf 100644 --- a/frontend/src/app/Jobs/CoprBuildsTable.tsx +++ b/frontend/src/app/Jobs/CoprBuildsTable.tsx @@ -2,9 +2,9 @@ import React, { useMemo } from "react"; import { TableVariant, cellWidth, IRow } from "@patternfly/react-table"; import { - Table, - TableHeader, - TableBody, + Table, + TableHeader, + TableBody, } from "@patternfly/react-table/deprecated"; import { Button } from "@patternfly/react-core"; @@ -17,174 +17,166 @@ import { Timestamp } from "../utils/Timestamp"; import { useInfiniteQuery } from "@tanstack/react-query"; interface ChrootStatusesProps { - statuses: { - [key: string]: string; - }; - ids: { - [key: string]: number; - }; + statuses: { + [key: string]: string; + }; + ids: { + [key: string]: number; + }; } // Add every target to the chroots column and color code according to status const ChrootStatuses: React.FC = (props) => { - let labels = []; - - for (let chroot in props.ids) { - const id = props.ids[chroot]; - const status = props.statuses[chroot]; - - labels.push( - , - ); - } - - return <>{labels}; + let labels = []; + + for (let chroot in props.ids) { + const id = props.ids[chroot]; + const status = props.statuses[chroot]; + + labels.push( + , + ); + } + + return <>{labels}; }; // TODO(SpyTec): If needed elsewhere move out to a new folder for API bindings or something export interface CoprBuild { - packit_id: number; - project: string; - build_id: number; - status_per_chroot: { - [key: string]: string; - }; - packit_id_per_chroot: { - [key: string]: number; - }; - build_submitted_time: number; - web_url: string; - ref: string; - // TODO(SpyTec): change interface depending on status of pr_id or branch_item. - // They seem to be mutually exclusive so can be sure one is null and other is string - pr_id: number | null; - branch_name: string | null; - repo_namespace: string; - repo_name: string; - project_url: string; + packit_id: number; + project: string; + build_id: number; + status_per_chroot: { + [key: string]: string; + }; + packit_id_per_chroot: { + [key: string]: number; + }; + build_submitted_time: number; + web_url: string; + ref: string; + // TODO(SpyTec): change interface depending on status of pr_id or branch_item. + // They seem to be mutually exclusive so can be sure one is null and other is string + pr_id: number | null; + branch_name: string | null; + repo_namespace: string; + repo_name: string; + project_url: string; } const CoprBuildsTable = () => { - // Headings - const columns = [ - { - title: Forge, - }, // space for forge icon - { title: "Trigger", transforms: [cellWidth(15)] }, - { title: "Chroots", transforms: [cellWidth(60)] }, - { title: "Time Submitted", transforms: [cellWidth(10)] }, - { title: "Copr Build", transforms: [cellWidth(10)] }, - ]; - - // Fetch data from dashboard backend (or if we want, directly from the API) - const fetchData = ({ pageParam = 1 }) => - fetch( - `${ - import.meta.env.VITE_API_URL - }/copr-builds?page=${pageParam}&per_page=20`, - ) - .then((response) => response.json()) - .then((data) => jsonToRow(data)); - - const { isInitialLoading, isError, fetchNextPage, data, isFetching } = - useInfiniteQuery(["copr"], fetchData, { - getNextPageParam: (_, allPages) => allPages.length + 1, - }); - - // Convert fetched json into row format that the table can read - function jsonToRow(copr_builds: CoprBuild[]) { - let rowsList: IRow[] = []; - - copr_builds.forEach((copr_build) => { - let singleRow = { - cells: [ - { - title: , - }, - { - title: ( - - - - ), - }, - { - title: ( - - ), - }, - { - title: ( - - ), - }, - { - title: ( - - - {copr_build.build_id} - - - ), - }, - ], - }; - 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 ( -
- - - -
-
-
- -
-
- ); + // Headings + const columns = [ + { + title: Forge, + }, // space for forge icon + { title: "Trigger", transforms: [cellWidth(15)] }, + { title: "Chroots", transforms: [cellWidth(60)] }, + { title: "Time Submitted", transforms: [cellWidth(10)] }, + { title: "Copr Build", transforms: [cellWidth(10)] }, + ]; + + // Fetch data from dashboard backend (or if we want, directly from the API) + const fetchData = ({ pageParam = 1 }) => + fetch( + `${ + import.meta.env.VITE_API_URL + }/copr-builds?page=${pageParam}&per_page=20`, + ) + .then((response) => response.json()) + .then((data) => jsonToRow(data)); + + const { isInitialLoading, isError, fetchNextPage, data, isFetching } = + useInfiniteQuery(["copr"], fetchData, { + getNextPageParam: (_, allPages) => allPages.length + 1, + }); + + // Convert fetched json into row format that the table can read + function jsonToRow(copr_builds: CoprBuild[]) { + let rowsList: IRow[] = []; + + copr_builds.forEach((copr_build) => { + let singleRow = { + cells: [ + { + title: , + }, + { + title: ( + + + + ), + }, + { + title: ( + + ), + }, + { + title: , + }, + { + title: ( + + + {copr_build.build_id} + + + ), + }, + ], + }; + 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 { CoprBuildsTable }; diff --git a/frontend/src/app/Jobs/Jobs.stories.tsx b/frontend/src/app/Jobs/Jobs.stories.tsx index f2d1594d..3b3c3f7f 100644 --- a/frontend/src/app/Jobs/Jobs.stories.tsx +++ b/frontend/src/app/Jobs/Jobs.stories.tsx @@ -6,23 +6,23 @@ import { Jobs } from "./Jobs"; import { CoprBuildsTable } from "./CoprBuildsTable"; const meta: Meta = { - title: "Jobs", - component: Jobs, - decorators: [withRouter, withQueryClient], + title: "Jobs", + component: Jobs, + decorators: [withRouter, withQueryClient], }; export default meta; type Story = StoryObj; export const ResultsFound: Story = { - parameters: { - reactRouter: { - routePath: "/jobs/copr-builds", - outlet: { - element: , - handle: "Copr Builds", - path: "copr-builds", - }, - }, + parameters: { + reactRouter: { + routePath: "/jobs/copr-builds", + outlet: { + element: , + handle: "Copr Builds", + path: "copr-builds", + }, }, + }, }; diff --git a/frontend/src/app/Jobs/Jobs.tsx b/frontend/src/app/Jobs/Jobs.tsx index 69c2936d..c14d4e1f 100644 --- a/frontend/src/app/Jobs/Jobs.tsx +++ b/frontend/src/app/Jobs/Jobs.tsx @@ -1,104 +1,82 @@ import { - Nav, - NavItem, - NavList, - PageGroup, - PageNavigation, - PageSection, - PageSectionVariants, - Text, - TextContent, + Nav, + NavItem, + NavList, + PageGroup, + PageNavigation, + PageSection, + PageSectionVariants, + Text, + TextContent, } from "@patternfly/react-core"; import { useEffect } from "react"; import { - NavLink, - Outlet, - useLocation, - useMatches, - useNavigate, + NavLink, + Outlet, + useLocation, + useMatches, + useNavigate, } from "react-router-dom"; import { useTitle } from "../utils/useTitle"; const Jobs = () => { - useTitle("Jobs"); - const location = useLocation(); - const matches = useMatches(); - const currentMatch = matches.find( - (match) => match.pathname === location.pathname, - ); + useTitle("Jobs"); + const location = useLocation(); + const matches = useMatches(); + const currentMatch = matches.find( + (match) => match.pathname === location.pathname, + ); - // if we're not inside a specific route, default to copr-builds and redirect - const navigate = useNavigate(); - useEffect(() => { - if (matches[matches.length - 1].id === "jobs") { - navigate("/jobs/copr-builds"); - } - }, [navigate]); + // if we're not inside a specific route, default to copr-builds and redirect + const navigate = useNavigate(); + useEffect(() => { + if (matches[matches.length - 1].id === "jobs") { + navigate("/jobs/copr-builds"); + } + }, [navigate]); - return ( - - - - Jobs - List of jobs by Packit Service - - - - - - - - - - ); + return ( + + + + Jobs + List of jobs by Packit Service + + + + + + + + + + ); }; export { Jobs }; diff --git a/frontend/src/app/Jobs/KojiBuildsTable.tsx b/frontend/src/app/Jobs/KojiBuildsTable.tsx index ec602790..d1c10346 100644 --- a/frontend/src/app/Jobs/KojiBuildsTable.tsx +++ b/frontend/src/app/Jobs/KojiBuildsTable.tsx @@ -2,9 +2,9 @@ import React, { useMemo } from "react"; import { TableVariant, cellWidth, IRow } from "@patternfly/react-table"; import { - Table, - TableHeader, - TableBody, + Table, + TableHeader, + TableBody, } from "@patternfly/react-table/deprecated"; import { Button } from "@patternfly/react-core"; @@ -17,142 +17,134 @@ import { Timestamp } from "../utils/Timestamp"; import { useInfiniteQuery } from "@tanstack/react-query"; export interface KojiBuild { - packit_id: number; - task_id: string; - status: string; // TODO(SpyTec): Probably an enum right? Change to be one if so - build_submitted_time: number; - chroot: string; - web_url: string; - build_logs_urls: string; - // TODO(SpyTec): change interface depending on status of pr_id or branch_item. - // They seem to be mutually exclusive so can be sure one is null and other is string - pr_id: number | null; - branch_name: string | null; - release: string | null; - project_url: string; - repo_namespace: string; - repo_name: string; + packit_id: number; + task_id: string; + status: string; // TODO(SpyTec): Probably an enum right? Change to be one if so + build_submitted_time: number; + chroot: string; + web_url: string; + build_logs_urls: string; + // TODO(SpyTec): change interface depending on status of pr_id or branch_item. + // They seem to be mutually exclusive so can be sure one is null and other is string + pr_id: number | null; + branch_name: string | null; + release: string | null; + project_url: string; + repo_namespace: string; + repo_name: string; } interface KojiBuildTableProps { - scratch: "true" | "false"; + scratch: "true" | "false"; } const KojiBuildsTable: React.FC = ({ scratch }) => { - // Headings - const columns = [ - { - title: Forge, - }, // space for forge icon - { title: "Trigger", transforms: [cellWidth(35)] }, - { title: "Target", transforms: [cellWidth(20)] }, - { title: "Time Submitted", transforms: [cellWidth(20)] }, - { title: "Koji Build Task", transforms: [cellWidth(20)] }, - ]; + // Headings + const columns = [ + { + title: Forge, + }, // space for forge icon + { title: "Trigger", transforms: [cellWidth(35)] }, + { title: "Target", transforms: [cellWidth(20)] }, + { title: "Time Submitted", transforms: [cellWidth(20)] }, + { title: "Koji Build Task", 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 - }/koji-builds?scratch=${scratch}&page=${pageParam}&per_page=20`, - ) - .then((response) => response.json()) - .then((data) => jsonToRow(data)); + // Fetch data from dashboard backend (or if we want, directly from the API) + const fetchData = ({ pageParam = 1 }) => + fetch( + `${ + import.meta.env.VITE_API_URL + }/koji-builds?scratch=${scratch}&page=${pageParam}&per_page=20`, + ) + .then((response) => response.json()) + .then((data) => jsonToRow(data)); - const { isInitialLoading, isError, fetchNextPage, data, isFetching } = - useInfiniteQuery(["koji"], fetchData, { - getNextPageParam: (_, allPages) => allPages.length + 1, - }); + const { isInitialLoading, isError, fetchNextPage, data, isFetching } = + useInfiniteQuery(["koji"], fetchData, { + getNextPageParam: (_, allPages) => allPages.length + 1, + }); - // Convert fetched json into row format that the table can read - function jsonToRow(koji_builds: KojiBuild[]) { - let rowsList: IRow[] = []; + // Convert fetched json into row format that the table can read + function jsonToRow(koji_builds: KojiBuild[]) { + let rowsList: IRow[] = []; - koji_builds.forEach((koji_build) => { - let singleRow = { - cells: [ - { - title: , - }, - { - title: ( - - - - ), - }, - { - title: ( - - ), - }, - { - title: ( - - ), - }, - { - title: ( - - - {koji_build.task_id} - - - ), - }, - ], - }; - rowsList.push(singleRow); - }); - return rowsList; - } + koji_builds.forEach((koji_build) => { + let singleRow = { + cells: [ + { + title: , + }, + { + title: ( + + + + ), + }, + { + title: ( + + ), + }, + { + title: , + }, + { + title: ( + + + {koji_build.task_id} + + + ), + }, + ], + }; + 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]); + // 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 ; - } + // 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 ; - } + // Show preloader if waiting for API data + // TODO(SpyTec): Replace with skeleton loader, we know the data will look like + if (isInitialLoading) { + return ; + } - return ( -
- - - -
-
-
- -
-
- ); + return ( +
+ + + +
+
+
+ +
+
+ ); }; export { KojiBuildsTable }; diff --git a/frontend/src/app/Jobs/SRPMBuildsTable.tsx b/frontend/src/app/Jobs/SRPMBuildsTable.tsx index ee77026f..dee00616 100644 --- a/frontend/src/app/Jobs/SRPMBuildsTable.tsx +++ b/frontend/src/app/Jobs/SRPMBuildsTable.tsx @@ -2,9 +2,9 @@ import { useMemo } from "react"; import { TableVariant, cellWidth, IRow } from "@patternfly/react-table"; import { - Table, - TableHeader, - TableBody, + Table, + TableHeader, + TableBody, } from "@patternfly/react-table/deprecated"; import { Button } from "@patternfly/react-core"; @@ -18,121 +18,117 @@ import { Timestamp } from "../utils/Timestamp"; import { useInfiniteQuery } from "@tanstack/react-query"; export interface SRPMBuild { - srpm_build_id: number; - status: string; // TODO(SpyTec): Probably an enum right? Change to be one if so - log_url: string; - build_submitted_time: number; - repo_namespace: string; - repo_name: string; - project_url: string; - // TODO(SpyTec): change interface depending on status of pr_id or branch_item. - // They seem to be mutually exclusive so can be sure one is null and other is string - pr_id: number | null; - branch_name: string | null; + srpm_build_id: number; + status: string; // TODO(SpyTec): Probably an enum right? Change to be one if so + log_url: string; + build_submitted_time: number; + repo_namespace: string; + repo_name: string; + project_url: string; + // TODO(SpyTec): change interface depending on status of pr_id or branch_item. + // They seem to be mutually exclusive so can be sure one is null and other is string + pr_id: number | null; + branch_name: string | null; } const SRPMBuildsTable = () => { - // Headings - const columns = [ - { - title: Forge, - }, // space for forge icon - { title: "Trigger", transforms: [cellWidth(50)] }, - { title: "Results", transforms: [cellWidth(20)] }, - { title: "Time Submitted", transforms: [cellWidth(20)] }, - ]; + // Headings + const columns = [ + { + title: Forge, + }, // space for forge icon + { title: "Trigger", transforms: [cellWidth(50)] }, + { title: "Results", transforms: [cellWidth(20)] }, + { title: "Time Submitted", 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 - }/srpm-builds?page=${pageParam}&per_page=20`, - ) - .then((response) => response.json()) - .then((data) => jsonToRow(data)); + // Fetch data from dashboard backend (or if we want, directly from the API) + const fetchData = ({ pageParam = 1 }) => + fetch( + `${ + import.meta.env.VITE_API_URL + }/srpm-builds?page=${pageParam}&per_page=20`, + ) + .then((response) => response.json()) + .then((data) => jsonToRow(data)); - const { isInitialLoading, isError, fetchNextPage, data, isFetching } = - useInfiniteQuery(["srpm"], fetchData, { - getNextPageParam: (_, allPages) => allPages.length + 1, - }); + const { isInitialLoading, isError, fetchNextPage, data, isFetching } = + useInfiniteQuery(["srpm"], fetchData, { + getNextPageParam: (_, allPages) => allPages.length + 1, + }); - // Convert fetched json into row format that the table can read - function jsonToRow(srpm_builds: SRPMBuild[]) { - let rowsList: IRow[] = []; + // Convert fetched json into row format that the table can read + function jsonToRow(srpm_builds: SRPMBuild[]) { + let rowsList: IRow[] = []; - srpm_builds.forEach((srpm_build) => { - let singleRow = { - cells: [ - { - title: , - }, - { - title: ( - - - - ), - }, - { - title: ( - - ), - }, - { - title: ( - - ), - }, - ], - }; - rowsList.push(singleRow); - }); - return rowsList; - } + srpm_builds.forEach((srpm_build) => { + let singleRow = { + cells: [ + { + title: , + }, + { + title: ( + + + + ), + }, + { + title: ( + + ), + }, + { + title: , + }, + ], + }; + 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]); + // 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 ; - } + // 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 ; - } + // Show preloader if waiting for API data + // TODO(SpyTec): Replace with skeleton loader, we know the data will look like + if (isInitialLoading) { + return ; + } - return ( -
- - - -
-
-
- -
-
- ); + return ( +
+ + + +
+
+
+ +
+
+ ); }; export { SRPMBuildsTable }; diff --git a/frontend/src/app/Jobs/SyncReleaseStatuses.tsx b/frontend/src/app/Jobs/SyncReleaseStatuses.tsx index 6360b4f2..04ddc463 100644 --- a/frontend/src/app/Jobs/SyncReleaseStatuses.tsx +++ b/frontend/src/app/Jobs/SyncReleaseStatuses.tsx @@ -2,9 +2,9 @@ import React, { useMemo } from "react"; import { TableVariant, cellWidth, IRow } from "@patternfly/react-table"; import { - Table, - TableHeader, - TableBody, + Table, + TableHeader, + TableBody, } from "@patternfly/react-table/deprecated"; import { Button } from "@patternfly/react-core"; @@ -17,170 +17,156 @@ import { Timestamp } from "../utils/Timestamp"; import { useInfiniteQuery } from "@tanstack/react-query"; export interface UpstreamDownstreamJobs { - packit_id: number; - status: string; - submitted_time: number; - status_per_downstream_pr: { - [key: string]: string; - }; - packit_id_per_downstream_pr: { - [key: string]: number; - }; - pr_id: number | null; - issue_id: number | null; - release: string | null; - repo_namespace: string; - repo_name: string; - project_url: string; + packit_id: number; + status: string; + submitted_time: number; + status_per_downstream_pr: { + [key: string]: string; + }; + packit_id_per_downstream_pr: { + [key: string]: number; + }; + pr_id: number | null; + issue_id: number | null; + release: string | null; + repo_namespace: string; + repo_name: string; + project_url: string; } interface SyncReleaseStatusesProps { - ids: { - [key: string]: number; - }; - statuses: { - [key: string]: string; - }; - job: SyncReleaseTableProps["job"]; + ids: { + [key: string]: number; + }; + statuses: { + [key: string]: string; + }; + job: SyncReleaseTableProps["job"]; } const SyncReleaseStatuses: React.FC = (props) => { - let labels = []; - - for (let target in props.ids) { - const id = props.ids[target]; - const status = props.statuses[target]; - - labels.push( - , - ); - } - - return
{labels}
; + let labels = []; + + for (let target in props.ids) { + const id = props.ids[target]; + const status = props.statuses[target]; + + labels.push( + , + ); + } + + return
{labels}
; }; interface SyncReleaseTableProps { - job: "propose-downstream" | "pull-from-upstream"; + job: "propose-downstream" | "pull-from-upstream"; } const SyncReleaseTable: React.FC = ({ job }) => { - // Headings - const columns = [ - { - title: Forge, - }, // space for forge icon - { title: "Trigger", transforms: [cellWidth(25)] }, - { title: "Targets", transforms: [cellWidth(60)] }, - { title: "Time Submitted", 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 - }/${job}?page=${pageParam}&per_page=20`, - ) - .then((response) => response.json()) - .then((data) => jsonToRow(data)); - - const { isInitialLoading, isError, fetchNextPage, data, isFetching } = - useInfiniteQuery([job], fetchData, { - getNextPageParam: (_, allPages) => allPages.length + 1, - }); - - // Convert fetched json into row format that the table can read - function jsonToRow(upstream_downstream_jobs: UpstreamDownstreamJobs[]) { - let rowsList: IRow[] = []; - - upstream_downstream_jobs.forEach((upstream_downstream_job) => { - let singleRow = { - cells: [ - { - title: ( - - ), - }, - { - title: ( - - - - ), - }, - { - title: ( - - ), - }, - { - title: ( - - ), - }, - ], - }; - 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 ( -
- - - -
-
-
- -
-
- ); + // Headings + const columns = [ + { + title: Forge, + }, // space for forge icon + { title: "Trigger", transforms: [cellWidth(25)] }, + { title: "Targets", transforms: [cellWidth(60)] }, + { title: "Time Submitted", 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}/${job}?page=${pageParam}&per_page=20`, + ) + .then((response) => response.json()) + .then((data) => jsonToRow(data)); + + const { isInitialLoading, isError, fetchNextPage, data, isFetching } = + useInfiniteQuery([job], fetchData, { + getNextPageParam: (_, allPages) => allPages.length + 1, + }); + + // Convert fetched json into row format that the table can read + function jsonToRow(upstream_downstream_jobs: UpstreamDownstreamJobs[]) { + let rowsList: IRow[] = []; + + upstream_downstream_jobs.forEach((upstream_downstream_job) => { + let singleRow = { + cells: [ + { + title: , + }, + { + title: ( + + + + ), + }, + { + title: ( + + ), + }, + { + title: , + }, + ], + }; + 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 { SyncReleaseTable }; diff --git a/frontend/src/app/Jobs/TestingFarmResultsTable.tsx b/frontend/src/app/Jobs/TestingFarmResultsTable.tsx index a6bcf581..b9570769 100644 --- a/frontend/src/app/Jobs/TestingFarmResultsTable.tsx +++ b/frontend/src/app/Jobs/TestingFarmResultsTable.tsx @@ -2,9 +2,9 @@ import React, { useMemo } from "react"; import { TableVariant, cellWidth, IRow } from "@patternfly/react-table"; import { - Table, - TableHeader, - TableBody, + Table, + TableHeader, + TableBody, } from "@patternfly/react-table/deprecated"; import { Button } from "@patternfly/react-core"; @@ -18,122 +18,120 @@ import { Timestamp } from "../utils/Timestamp"; import { useInfiniteQuery } from "@tanstack/react-query"; export interface TestingFarmResult { - packit_id: number; - pipeline_id: string; - ref: string; - status: string; - target: string; - web_url: string; - pr_id: number; - submitted_time: number; - repo_namespace: string; - repo_name: string; - project_url: string; + packit_id: number; + pipeline_id: string; + ref: string; + status: string; + target: string; + web_url: string; + pr_id: number; + submitted_time: number; + repo_namespace: string; + repo_name: string; + project_url: string; } const TestingFarmResultsTable = () => { - const columns = [ - { title: "" }, // space for forge icon - { title: "Trigger", transforms: [cellWidth(35)] }, - { title: "Target", transforms: [cellWidth(20)] }, - { title: "Time Submitted", transforms: [cellWidth(20)] }, - { title: "Test Results", transforms: [cellWidth(20)] }, - ]; + const columns = [ + { title: "" }, // space for forge icon + { title: "Trigger", transforms: [cellWidth(35)] }, + { title: "Target", transforms: [cellWidth(20)] }, + { title: "Time Submitted", transforms: [cellWidth(20)] }, + { title: "Test Results", 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 - }/testing-farm/results?page=${pageParam}&per_page=50`, - ) - .then((response) => response.json()) - .then((data) => jsonToRow(data)); + // Fetch data from dashboard backend (or if we want, directly from the API) + const fetchData = ({ pageParam = 1 }) => + fetch( + `${ + import.meta.env.VITE_API_URL + }/testing-farm/results?page=${pageParam}&per_page=50`, + ) + .then((response) => response.json()) + .then((data) => jsonToRow(data)); - const { isInitialLoading, isError, fetchNextPage, data, isFetching } = - useInfiniteQuery(["copr"], fetchData, { - getNextPageParam: (_, allPages) => allPages.length + 1, - }); + const { isInitialLoading, isError, fetchNextPage, data, isFetching } = + useInfiniteQuery(["copr"], fetchData, { + getNextPageParam: (_, allPages) => allPages.length + 1, + }); - function jsonToRow(test_results: TestingFarmResult[]) { - let rowsList: IRow[] = []; - test_results.forEach((test_result) => { - let singleRow = { - cells: [ - { - title: , - }, - { - title: ( - - - - ), - }, - { - title: ( - - ), - }, - { - title: , - }, - { - title: ( - - - {test_result.pipeline_id} - - - ), - }, - ], - }; - rowsList.push(singleRow); - }); - return rowsList; - } + function jsonToRow(test_results: TestingFarmResult[]) { + let rowsList: IRow[] = []; + test_results.forEach((test_result) => { + let singleRow = { + cells: [ + { + title: , + }, + { + title: ( + + + + ), + }, + { + title: ( + + ), + }, + { + title: , + }, + { + title: ( + + {test_result.pipeline_id} + + ), + }, + ], + }; + 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]); + // 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 ; - } + // If backend API is down + if (isError) { + return ; + } - // Show preloader if waiting for API data - if (isInitialLoading) { - return ; - } + // Show preloader if waiting for API data + if (isInitialLoading) { + return ; + } - return ( -
- - - -
-
-
- -
-
- ); + return ( +
+ + + +
+
+
+ +
+
+ ); }; export { TestingFarmResultsTable }; diff --git a/frontend/src/app/NotFound/NotFound.stories.tsx b/frontend/src/app/NotFound/NotFound.stories.tsx index e02cb6c1..13a02456 100644 --- a/frontend/src/app/NotFound/NotFound.stories.tsx +++ b/frontend/src/app/NotFound/NotFound.stories.tsx @@ -3,8 +3,8 @@ import type { Meta, StoryObj } from "@storybook/react"; import { NotFound } from "./NotFound"; const meta: Meta = { - title: "NotFound", - component: NotFound, + title: "NotFound", + component: NotFound, }; export default meta; diff --git a/frontend/src/app/NotFound/NotFound.tsx b/frontend/src/app/NotFound/NotFound.tsx index f6c51286..bcffd69a 100644 --- a/frontend/src/app/NotFound/NotFound.tsx +++ b/frontend/src/app/NotFound/NotFound.tsx @@ -2,12 +2,9 @@ import * as React from "react"; import { Alert, PageSection } from "@patternfly/react-core"; const NotFound = () => ( - - - + + + ); export { NotFound }; diff --git a/frontend/src/app/Pipelines/Pipelines.tsx b/frontend/src/app/Pipelines/Pipelines.tsx index 0335d637..ef0266eb 100644 --- a/frontend/src/app/Pipelines/Pipelines.tsx +++ b/frontend/src/app/Pipelines/Pipelines.tsx @@ -1,29 +1,29 @@ import React from "react"; import { - PageSection, - PageSectionVariants, - TextContent, - Text, + PageSection, + PageSectionVariants, + TextContent, + Text, } from "@patternfly/react-core"; import { PipelinesTable } from "./PipelinesTable"; import { useTitle } from "../utils/useTitle"; const Pipelines = () => { - useTitle("Pipelines"); - return ( -
- - - Pipelines - Pipelines run by Packit Service. - - - - - -
- ); + useTitle("Pipelines"); + return ( +
+ + + Pipelines + Pipelines run by Packit Service. + + + + + +
+ ); }; export { Pipelines }; diff --git a/frontend/src/app/Pipelines/PipelinesTable.tsx b/frontend/src/app/Pipelines/PipelinesTable.tsx index a02e423b..5334b1a9 100644 --- a/frontend/src/app/Pipelines/PipelinesTable.tsx +++ b/frontend/src/app/Pipelines/PipelinesTable.tsx @@ -2,9 +2,9 @@ import React, { useMemo } from "react"; import { TableVariant, cellWidth, IRow } from "@patternfly/react-table"; import { - Table, - TableHeader, - TableBody, + Table, + TableHeader, + TableBody, } from "@patternfly/react-table/deprecated"; import { Button, LabelGroup } from "@patternfly/react-core"; @@ -20,231 +20,225 @@ import kojiLogo from "../../static/koji.ico"; import { useInfiniteQuery } from "@tanstack/react-query"; interface StatusItem { - packit_id: number; - status?: string; - target: string; + packit_id: number; + status?: string; + target: string; } interface StatusItemSRPM { - packit_id: number; - status: string; - target?: string; + packit_id: number; + status: string; + target?: string; } interface StatusesProps { - route: string; - name: string | React.ReactNode; - entries: (StatusItem | StatusItemSRPM)[]; - statusClass: typeof StatusLabel; + route: string; + name: string | React.ReactNode; + entries: (StatusItem | StatusItemSRPM)[]; + statusClass: typeof StatusLabel; } const Statuses: React.FC = (props) => { - const labels = useMemo(() => { - const labelled: React.ReactNode[] = []; - props.entries.forEach((entry, i) => { - labelled.push( - , - ); - }); - return labelled; - }, [props]); - - // Technically LabelGroup doesn't accept elements, only string. But the way it currently uses them works for us - // Should be tested as part of a visual test - return ( - {labels} - ); + const labels = useMemo(() => { + const labelled: React.ReactNode[] = []; + props.entries.forEach((entry, i) => { + labelled.push( + , + ); + }); + return labelled; + }, [props]); + + // Technically LabelGroup doesn't accept elements, only string. But the way it currently uses them works for us + // Should be tested as part of a visual test + return {labels}; }; interface PipelineItem { - packit_id: number; - target: string; - status: string; + packit_id: number; + target: string; + status: string; } interface PipelineRun { - merged_run_id: number; - srpm: { - packit_id: number; - status: string; - }; - copr: PipelineItem[]; - koji: PipelineItem[]; - test_run: PipelineItem[]; - propose_downstream: PipelineItem[]; - pull_from_upstream: PipelineItem[]; - time_submitted: number; - trigger: { - repo_namespace: string; - repo_name: string; - git_repo: string; - pr_id: number | null; - issue_id: number | null; - branch_name: string | null; - release: string | null; - }; + merged_run_id: number; + srpm: { + packit_id: number; + status: string; + }; + copr: PipelineItem[]; + koji: PipelineItem[]; + test_run: PipelineItem[]; + propose_downstream: PipelineItem[]; + pull_from_upstream: PipelineItem[]; + time_submitted: number; + trigger: { + repo_namespace: string; + repo_name: string; + git_repo: string; + pr_id: number | null; + issue_id: number | null; + branch_name: string | null; + release: string | null; + }; } function getBuilderLabel(run: PipelineRun) { - const iconStyle = { - minWidth: "14px", - minHeight: "14px", - width: "14px", - height: "14px", - }; - - let text = "none"; - let icon = undefined; - - if (run.copr.length > 0) { - icon = Copr logo; - text = "Copr"; - } else if (run.koji.length > 0) { - icon = Koji logo; - text = "Koji"; - } - - return ( - <> - {icon} {text} - - ); + const iconStyle = { + minWidth: "14px", + minHeight: "14px", + width: "14px", + height: "14px", + }; + + let text = "none"; + let icon = undefined; + + if (run.copr.length > 0) { + icon = Copr logo; + text = "Copr"; + } else if (run.koji.length > 0) { + icon = Koji logo; + text = "Koji"; + } + + return ( + <> + {icon} {text} + + ); } const PipelinesTable = () => { - // Headings - const columns = [ - { title: "" }, // space for forge icon - { title: "Trigger", transforms: [cellWidth(15)] }, - { title: "Time Submitted", transforms: [cellWidth(10)] }, - { title: "Jobs", transforms: [cellWidth(70)] }, - ]; - - // Fetch data from dashboard backend (or if we want, directly from the API) - const fetchData = ({ pageParam = 1 }) => - fetch( - `${ - import.meta.env.VITE_API_URL - }/runs?page=${pageParam}&per_page=20`, - ) - .then((response) => response.json()) - .then((data: PipelineRun[]) => jsonToRow(data)); - - const { isInitialLoading, isError, fetchNextPage, data, isFetching } = - useInfiniteQuery(["pipelines"], fetchData, { - getNextPageParam: (_, allPages) => allPages.length + 1, - keepPreviousData: true, - }); - - // Convert fetched json into row format that the table can read - function jsonToRow(res: PipelineRun[]) { - const rowsList: (IRow | string[])[] = []; - - res.forEach((run) => { - let singleRow = { - cells: [ - { - title: , - }, - { - title: ( - - - - ), - }, - { title: }, - { - title: ( - <> - - - - - - - - ), - }, - ], - }; - 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 ( -
- - - -
-
-
- -
-
- ); + // Headings + const columns = [ + { title: "" }, // space for forge icon + { title: "Trigger", transforms: [cellWidth(15)] }, + { title: "Time Submitted", transforms: [cellWidth(10)] }, + { title: "Jobs", transforms: [cellWidth(70)] }, + ]; + + // Fetch data from dashboard backend (or if we want, directly from the API) + const fetchData = ({ pageParam = 1 }) => + fetch(`${import.meta.env.VITE_API_URL}/runs?page=${pageParam}&per_page=20`) + .then((response) => response.json()) + .then((data: PipelineRun[]) => jsonToRow(data)); + + const { isInitialLoading, isError, fetchNextPage, data, isFetching } = + useInfiniteQuery(["pipelines"], fetchData, { + getNextPageParam: (_, allPages) => allPages.length + 1, + keepPreviousData: true, + }); + + // Convert fetched json into row format that the table can read + function jsonToRow(res: PipelineRun[]) { + const rowsList: (IRow | string[])[] = []; + + res.forEach((run) => { + let singleRow = { + cells: [ + { + title: , + }, + { + title: ( + + + + ), + }, + { title: }, + { + title: ( + <> + + + + + + + + ), + }, + ], + }; + 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 { PipelinesTable }; diff --git a/frontend/src/app/Preloader/Preloader.tsx b/frontend/src/app/Preloader/Preloader.tsx index 910383a4..b02b328e 100644 --- a/frontend/src/app/Preloader/Preloader.tsx +++ b/frontend/src/app/Preloader/Preloader.tsx @@ -2,11 +2,11 @@ import React from "react"; import { Spinner } from "@patternfly/react-core"; const Preloader = () => ( -
-
- -
-
+
+
+ +
+
); export { Preloader }; diff --git a/frontend/src/app/Projects/BranchList.tsx b/frontend/src/app/Projects/BranchList.tsx index 59c7e99a..696b1a90 100644 --- a/frontend/src/app/Projects/BranchList.tsx +++ b/frontend/src/app/Projects/BranchList.tsx @@ -5,146 +5,146 @@ import { Preloader } from "../Preloader/Preloader"; import { TriggerInfo } from "../Trigger/TriggerInfo"; import { - DataList, - DataListToggle, - DataListCell, - DataListItem, - DataListContent, - DataListItemCells, - DataListItemRow, + DataList, + DataListToggle, + DataListCell, + DataListItem, + DataListContent, + DataListItemCells, + DataListItemRow, } from "@patternfly/react-core"; import { useQuery } from "@tanstack/react-query"; import { getBranchLink } from "../utils/forgeUrls"; interface CoprBuild { - build_id: string; - chroot: string; - status: string; - web_url: string; + build_id: string; + chroot: string; + status: string; + web_url: string; } interface KojiBuild { - build_id: string; - status: string; - chroot: string; - web_url: string; + build_id: string; + status: string; + chroot: string; + web_url: string; } interface SRPMBuild { - srpm_build_id: number; - status: string; - log_url: string; + srpm_build_id: number; + status: string; + log_url: string; } interface TestingFarmRun { - pipeline_id: string; - chroot: string; - status: string; - web_url: string; + pipeline_id: string; + chroot: string; + status: string; + web_url: string; } interface ProjectBranchInfo { - branch: string; - builds: CoprBuild[]; - koji_builds: KojiBuild[]; - srpm_builds: SRPMBuild[]; - tests: TestingFarmRun[]; + branch: string; + builds: CoprBuild[]; + koji_builds: KojiBuild[]; + srpm_builds: SRPMBuild[]; + tests: TestingFarmRun[]; } const fetchBranchList = async (url: string): Promise => { - const res = await fetch(url); - return await res.json(); + const res = await fetch(url); + return await res.json(); }; interface BranchListProps { - forge: string; - namespace: string; - repoName: string; + forge: string; + namespace: string; + repoName: string; } const BranchList: React.FC = (props) => { - // Local State - const [expanded, setExpanded] = useState<{ [key: string]: boolean }>({}); + // Local State + const [expanded, setExpanded] = useState<{ [key: string]: boolean }>({}); - const id = useId(); + const id = useId(); - // Repo Info - const forge = props.forge; - const namespace = props.namespace; - const repoName = props.repoName; - const URL = `${ - import.meta.env.VITE_API_URL - }/projects/${forge}/${namespace}/${repoName}/branches`; + // Repo Info + const forge = props.forge; + const namespace = props.namespace; + const repoName = props.repoName; + const URL = `${ + import.meta.env.VITE_API_URL + }/projects/${forge}/${namespace}/${repoName}/branches`; - const { data, isError, isInitialLoading } = useQuery([URL], () => - fetchBranchList(URL), - ); + const { data, isError, isInitialLoading } = useQuery([URL], () => + fetchBranchList(URL), + ); - function onToggle(branchName: string) { - // We cant just invert the previous state here - // because its undefined for the first time - if (expanded[branchName]) { - let copyExpanded = { ...expanded }; - copyExpanded[branchName] = false; - setExpanded(copyExpanded); - } else { - let copyExpanded = { ...expanded }; - copyExpanded[branchName] = true; - setExpanded(copyExpanded); - } + function onToggle(branchName: string) { + // We cant just invert the previous state here + // because its undefined for the first time + if (expanded[branchName]) { + let copyExpanded = { ...expanded }; + copyExpanded[branchName] = false; + setExpanded(copyExpanded); + } else { + let copyExpanded = { ...expanded }; + copyExpanded[branchName] = true; + setExpanded(copyExpanded); } + } - // If backend API is down - if (isError) { - return ; - } + // If backend API is down + if (isError) { + return ; + } - // Show preloader if waiting for API data - if (isInitialLoading) { - return ; - } + // Show preloader if waiting for API data + if (isInitialLoading) { + return ; + } - return ( -
- - {data?.map((branch, index) => ( - + + {data?.map((branch, index) => ( + + + onToggle(branch.branch)} + isExpanded={expanded[branch.branch]} + id={`${id}-branch-${branch.branch}`} + aria-controls={`${id}-ex-expand-${branch.branch}`} + /> + + - - onToggle(branch.branch)} - isExpanded={expanded[branch.branch]} - id={`${id}-branch-${branch.branch}`} - aria-controls={`${id}-ex-expand-${branch.branch}`} - /> - - - {branch.branch} - - , - ]} - /> - - - - - - ))} - -
- ); + {branch.branch} + + , + ]} + /> + + + + + + ))} + + + ); }; export { BranchList }; diff --git a/frontend/src/app/Projects/Forge.stories.tsx b/frontend/src/app/Projects/Forge.stories.tsx index 785eee44..0f011349 100644 --- a/frontend/src/app/Projects/Forge.stories.tsx +++ b/frontend/src/app/Projects/Forge.stories.tsx @@ -5,30 +5,30 @@ import type { Meta, StoryObj } from "@storybook/react"; import { Forge } from "./Forge"; const meta: Meta = { - title: "Projects/Forge", - component: Forge, - decorators: [withRouter, withQueryClient], - parameters: { - reactRouter: { - routePath: "/projects/:forge", - routeParams: { - forge: "github.com", - }, - }, + title: "Projects/Forge", + component: Forge, + decorators: [withRouter, withQueryClient], + parameters: { + reactRouter: { + routePath: "/projects/:forge", + routeParams: { + forge: "github.com", + }, }, + }, }; export default meta; type Story = StoryObj; export const ResultsFound: Story = { - parameters: { - msw: { - handlers: [ - rest.get("*/projects", (req, res, ctx) => { - return res(ctx.status(503)); - }), - ], - }, + parameters: { + msw: { + handlers: [ + rest.get("*/projects", (req, res, ctx) => { + return res(ctx.status(503)); + }), + ], }, + }, }; diff --git a/frontend/src/app/Projects/Forge.tsx b/frontend/src/app/Projects/Forge.tsx index 6b5db9df..f7e06a75 100644 --- a/frontend/src/app/Projects/Forge.tsx +++ b/frontend/src/app/Projects/Forge.tsx @@ -1,8 +1,8 @@ import { - PageSection, - PageSectionVariants, - Text, - TextContent, + PageSection, + PageSectionVariants, + Text, + TextContent, } from "@patternfly/react-core"; import { useParams } from "react-router-dom"; @@ -11,23 +11,23 @@ import { ProjectsList } from "../Projects/ProjectsList"; import { useTitle } from "../utils/useTitle"; const Forge = () => { - useTitle("Project Forge"); - let { forge } = useParams(); + useTitle("Project Forge"); + let { forge } = useParams(); - return ( - <> - - - - {forge} - - - - - - - - ); + return ( + <> + + + + {forge} + + + + + + + + ); }; export { Forge }; diff --git a/frontend/src/app/Projects/IssuesList.tsx b/frontend/src/app/Projects/IssuesList.tsx index 2b25adb9..388e496a 100644 --- a/frontend/src/app/Projects/IssuesList.tsx +++ b/frontend/src/app/Projects/IssuesList.tsx @@ -7,55 +7,55 @@ import { List, ListItem } from "@patternfly/react-core"; import { useQuery } from "@tanstack/react-query"; const fetchData = (url: string) => { - return fetch(url).then((response) => response.json()); + return fetch(url).then((response) => response.json()); }; interface IssuesListProps { - forge: string; - namespace: string; - repoName: string; + forge: string; + namespace: string; + repoName: string; } const IssuesList: React.FC = ({ - forge, - namespace, - repoName, + forge, + namespace, + repoName, }) => { - const URL = `${ - import.meta.env.VITE_API_URL - }/projects/${forge}/${namespace}/${repoName}/issues`; - // TODO: Setup interface or type for issues endpoint - const { data, isInitialLoading, isError } = useQuery([URL], () => - fetchData(URL), - ); + const URL = `${ + import.meta.env.VITE_API_URL + }/projects/${forge}/${namespace}/${repoName}/issues`; + // TODO: Setup interface or type for issues endpoint + const { data, isInitialLoading, isError } = useQuery([URL], () => + fetchData(URL), + ); - // If backend API is down - if (isError) { - return ; - } + // If backend API is down + if (isError) { + return ; + } - // Show preloader if waiting for API data - if (isInitialLoading) { - return ; - } + // Show preloader if waiting for API data + if (isInitialLoading) { + return ; + } - return ( - - {data?.map((issue) => ( - - - #{issue} - - - ))} - - ); + return ( + + {data?.map((issue) => ( + + + #{issue} + + + ))} + + ); }; export { IssuesList }; diff --git a/frontend/src/app/Projects/Namespace.tsx b/frontend/src/app/Projects/Namespace.tsx index 64b6f9b3..316f17a0 100644 --- a/frontend/src/app/Projects/Namespace.tsx +++ b/frontend/src/app/Projects/Namespace.tsx @@ -1,10 +1,10 @@ import React from "react"; import { - PageSection, - PageSectionVariants, - TextContent, - Text, - Label, + PageSection, + PageSectionVariants, + TextContent, + Text, + Label, } from "@patternfly/react-core"; import { ForgeIcon } from "../Forge/ForgeIcon"; @@ -13,30 +13,27 @@ import { useParams } from "react-router-dom"; import { useTitle } from "../utils/useTitle"; const Namespace = () => { - useTitle("Project Namespace"); + useTitle("Project Namespace"); - let { forge, namespace } = useParams(); + let { forge, namespace } = useParams(); - return ( - <> - - - {namespace} - - - - - - - - - - ); + return ( + <> + + + {namespace} + + + + + + + + + + ); }; export { Namespace }; diff --git a/frontend/src/app/Projects/ProjectInfo.tsx b/frontend/src/app/Projects/ProjectInfo.tsx index 6f311e23..c9a132a7 100644 --- a/frontend/src/app/Projects/ProjectInfo.tsx +++ b/frontend/src/app/Projects/ProjectInfo.tsx @@ -1,16 +1,16 @@ import React from "react"; import { - PageSection, - PageSectionVariants, - Text, - Tabs, - Tab, - Title, - TabTitleText, - Card, - CardBody, - TextContent, - Label, + PageSection, + PageSectionVariants, + Text, + Tabs, + Tab, + Title, + TabTitleText, + Card, + CardBody, + TextContent, + Label, } from "@patternfly/react-core"; import { PullRequestList } from "./PullRequestList"; @@ -27,147 +27,127 @@ import { useTitle } from "../utils/useTitle"; import { useQuery } from "@tanstack/react-query"; interface ProjectDetails { - namespace: string; - repo_name: string; - project_url: string; - prs_handled: number; - branches_handled: number; - releases_handled: number; - issues_handled: number; + namespace: string; + repo_name: string; + project_url: string; + prs_handled: number; + branches_handled: number; + releases_handled: number; + issues_handled: number; } const fetchProjectInfo = async (url: string): Promise => { - const response = await fetch(url); - if (!response.ok && response.status !== 404) { - throw Promise.reject(response); - } - return await response.json(); + const response = await fetch(url); + if (!response.ok && response.status !== 404) { + throw Promise.reject(response); + } + return await response.json(); }; export const ProjectInfo = () => { - useTitle("Project"); - let { forge, namespace, repoName } = useParams(); + useTitle("Project"); + let { forge, namespace, repoName } = useParams(); - // TODO: Change tabs around so we can update URL instead with Outlet - const [activeTabKey, setActiveTabKey] = React.useState(0); - const handleTabClick = ( - _event: React.MouseEvent, - eventKey: number | string, - ) => { - setActiveTabKey(eventKey); - }; + // TODO: Change tabs around so we can update URL instead with Outlet + const [activeTabKey, setActiveTabKey] = React.useState(0); + const handleTabClick = ( + _event: React.MouseEvent, + eventKey: number | string, + ) => { + setActiveTabKey(eventKey); + }; - const URL = `${ - import.meta.env.VITE_API_URL - }/projects/${forge}/${namespace}/${repoName}`; + const URL = `${ + import.meta.env.VITE_API_URL + }/projects/${forge}/${namespace}/${repoName}`; - const { data, isError, isInitialLoading } = useQuery( - [forge, namespace, repoName], - () => fetchProjectInfo(URL), - ); - - if (isError || (data === undefined && !isInitialLoading)) { - return ; - } + const { data, isError, isInitialLoading } = useQuery( + [forge, namespace, repoName], + () => fetchProjectInfo(URL), + ); - let content = ; - if (data && "error" in data) { - content = ( - - Not Found. - - ); - } else if (!isInitialLoading && repoName && namespace && forge) { - content = ( - - PRs Handled} - > - - - Releases Handled} - > - - - Branches Handled} - > - - - Issues Handled} - > - - - - ); - } + if (isError || (data === undefined && !isInitialLoading)) { + return ; + } - // TODO: Project URL from response? - return ( - <> - - - {`${namespace}/${repoName}`} - - - - {data && } - - - - - - - {content} - - - + let content = ; + if (data && "error" in data) { + content = ( + + Not Found. + + ); + } else if (!isInitialLoading && repoName && namespace && forge) { + content = ( + + PRs Handled}> + + + Releases Handled}> + + + Branches Handled}> + + + Issues Handled}> + + + ); + } + + // TODO: Project URL from response? + return ( + <> + + + {`${namespace}/${repoName}`} + + + + {data && } + + + + + + + {content} + + + + ); }; interface ProjectLinkProps { - link: string; + link: string; } const ProjectLink: React.FC = (props) => { - if (props.link === "") { - return <>; - } - return ( - - - - ); + if (props.link === "") { + return <>; + } + return ( + + + + ); }; diff --git a/frontend/src/app/Projects/ProjectSearch.tsx b/frontend/src/app/Projects/ProjectSearch.tsx index 87dede8b..1baadad6 100644 --- a/frontend/src/app/Projects/ProjectSearch.tsx +++ b/frontend/src/app/Projects/ProjectSearch.tsx @@ -1,14 +1,14 @@ import React, { useState } from "react"; import { - Alert, - Form, - Button, - TextInput, - Panel, - InputGroup, - PanelMain, - PanelMainBody, - InputGroupItem, + Alert, + Form, + Button, + TextInput, + Panel, + InputGroup, + PanelMain, + PanelMainBody, + InputGroupItem, } from "@patternfly/react-core"; import { SearchIcon } from "@patternfly/react-icons"; @@ -16,119 +16,115 @@ import { SearchIcon } from "@patternfly/react-icons"; import { useNavigate } from "react-router-dom"; const ProjectSearch = () => { - const [namespace, setNamespace] = useState(""); - const [repoName, setRepoName] = useState(""); - const [forge, setForge] = useState(""); - const [showWarning, setWarning] = useState(false); + const [namespace, setNamespace] = useState(""); + const [repoName, setRepoName] = useState(""); + const [forge, setForge] = useState(""); + const [showWarning, setWarning] = useState(false); - // Name refers to HTML 5 History API - // We use this to go to a dynamic link (/projects//../.. ) - const navigate = useNavigate(); + // Name refers to HTML 5 History API + // We use this to go to a dynamic link (/projects//../.. ) + const navigate = useNavigate(); - function goToProjectDetails() { - if (forge && namespace && repoName) { - navigate(`/projects/${forge}/${namespace}/${repoName}`); - } else if (forge && namespace) { - navigate(`/projects/${forge}/${namespace}`); - } else if (forge && !namespace && !repoName) { - navigate(`/projects/${forge}`); - } else { - setWarning(true); - } + function goToProjectDetails() { + if (forge && namespace && repoName) { + navigate(`/projects/${forge}/${namespace}/${repoName}`); + } else if (forge && namespace) { + navigate(`/projects/${forge}/${namespace}`); + } else if (forge && !namespace && !repoName) { + navigate(`/projects/${forge}`); + } else { + setWarning(true); } + } - function handleForgeChange(url: string) { - // Remove protocol if any - url = url.replace("http://", "").replace("https://", ""); + function handleForgeChange(url: string) { + // Remove protocol if any + url = url.replace("http://", "").replace("https://", ""); - // Split to forge - namespace - repo_name and update states - var parts = url.split("/"); - setForge(parts[0]); - switch (parts.length) { - case 1: - break; - case 2: - setNamespace(parts[1]); - break; - case 3: - setNamespace(parts[1]); - setRepoName(parts[2]); - break; - default: - // GitLab namespace can contain slash - setNamespace(`${parts[1]}/${parts[2]}`); - setRepoName(parts[3]); - break; - } + // Split to forge - namespace - repo_name and update states + var parts = url.split("/"); + setForge(parts[0]); + switch (parts.length) { + case 1: + break; + case 2: + setNamespace(parts[1]); + break; + case 3: + setNamespace(parts[1]); + setRepoName(parts[2]); + break; + default: + // GitLab namespace can contain slash + setNamespace(`${parts[1]}/${parts[2]}`); + setRepoName(parts[3]); + break; } + } - let invalidFormWarning; - if (showWarning) { - invalidFormWarning = ; - } + let invalidFormWarning; + if (showWarning) { + invalidFormWarning = ; + } - return ( - - - -
- - - - handleForgeChange(val) - } - /> - - - - setNamespace(val) - } - /> - - - setRepoName(val)} - /> - - - - - - {invalidFormWarning} -
-
-
-
- ); + return ( + + + +
+ + + handleForgeChange(val)} + /> + + + setNamespace(val)} + /> + + + setRepoName(val)} + /> + + + + + + {invalidFormWarning} +
+
+
+
+ ); }; export { ProjectSearch }; diff --git a/frontend/src/app/Projects/Projects.tsx b/frontend/src/app/Projects/Projects.tsx index ee93187d..f99bb264 100644 --- a/frontend/src/app/Projects/Projects.tsx +++ b/frontend/src/app/Projects/Projects.tsx @@ -1,10 +1,10 @@ import React from "react"; import { - PageSection, - PageSectionVariants, - TextContent, - Text, - PageGroup, + PageSection, + PageSectionVariants, + TextContent, + Text, + PageGroup, } from "@patternfly/react-core"; import { ProjectSearch } from "./ProjectSearch"; @@ -12,25 +12,25 @@ import { ProjectsList } from "./ProjectsList"; import { useTitle } from "../utils/useTitle"; const Projects = () => { - useTitle("Projects"); - return ( - <> - - - Projects - - List of repositories with Packit Service enabled - - - - - - - - - - - ); + useTitle("Projects"); + return ( + <> + + + Projects + + List of repositories with Packit Service enabled + + + + + + + + + + + ); }; export { Projects }; diff --git a/frontend/src/app/Projects/ProjectsList.tsx b/frontend/src/app/Projects/ProjectsList.tsx index d1966156..f8ef09af 100644 --- a/frontend/src/app/Projects/ProjectsList.tsx +++ b/frontend/src/app/Projects/ProjectsList.tsx @@ -2,10 +2,10 @@ import React, { useMemo } from "react"; import { Button } from "@patternfly/react-core"; import { - CodeBranchIcon, - SecurityIcon, - BuildIcon, - BlueprintIcon, + CodeBranchIcon, + SecurityIcon, + BuildIcon, + BlueprintIcon, } from "@patternfly/react-icons"; import { ErrorConnection } from "../Errors/ErrorConnection"; @@ -13,187 +13,180 @@ import { Preloader } from "../Preloader/Preloader"; import { Link } from "react-router-dom"; import { useInfiniteQuery } from "@tanstack/react-query"; import { - Table /* data-codemods */, - Tbody, - Td, - Th, - Thead, - Tr, + Table /* data-codemods */, + Tbody, + Td, + Th, + Thead, + Tr, } from "@patternfly/react-table"; interface Project { - namespace: string; - repo_name: string; - project_url: string; - prs_handled: number; - branches_handled: number; - releases_handled: number; - issues_handled: number; + namespace: string; + repo_name: string; + project_url: string; + prs_handled: number; + branches_handled: number; + releases_handled: number; + issues_handled: number; } function getProjectInfoURL(project: Project) { - const urlArray = project.project_url?.split("/"); - const forge = urlArray[2]; - return `/projects/${forge}/${project.namespace}/${project.repo_name}`; + const urlArray = project.project_url?.split("/"); + const forge = urlArray[2]; + return `/projects/${forge}/${project.namespace}/${project.repo_name}`; } interface ProjectsListProps { - forge?: string; - namespace?: string; - project_url?: string; - repo_name?: string; + forge?: string; + namespace?: string; + project_url?: string; + repo_name?: string; } const columnNames = { - name: "Repositories", - branches: "Branches", - issues: "Issues", - releases: "Releases", - prs: "Pull requests", - workspaces: "Workspaces", - lastCommit: "Last commit", + name: "Repositories", + branches: "Branches", + issues: "Issues", + releases: "Releases", + prs: "Pull requests", + workspaces: "Workspaces", + lastCommit: "Last commit", }; type ColumnKey = keyof typeof columnNames; // TODO: Move data fetching to parent components const ProjectsList: React.FC = (props) => { - const [expandedCells, setExpandedCells] = - React.useState>(); - // Fetch data from dashboard backend (or if we want, directly from the API) - const fetchData = ({ pageParam = 1 }): Promise => - fetch( - `${import.meta.env.VITE_API_URL}/projects${ - props.forge ? "/" + props.forge : "" - }${props.namespace ? "/" + props.namespace : ""}?page=${pageParam}`, - ).then((response) => response.json()); + const [expandedCells, setExpandedCells] = + React.useState>(); + // Fetch data from dashboard backend (or if we want, directly from the API) + const fetchData = ({ pageParam = 1 }): Promise => + fetch( + `${import.meta.env.VITE_API_URL}/projects${ + props.forge ? "/" + props.forge : "" + }${props.namespace ? "/" + props.namespace : ""}?page=${pageParam}`, + ).then((response) => response.json()); - const { isInitialLoading, isError, fetchNextPage, data } = useInfiniteQuery( - ["ProjectsList"], - fetchData, - { - getNextPageParam: (_, allPages) => allPages.length + 1, - keepPreviousData: true, - }, - ); + const { isInitialLoading, isError, fetchNextPage, data } = useInfiniteQuery( + ["ProjectsList"], + fetchData, + { + getNextPageParam: (_, allPages) => allPages.length + 1, + keepPreviousData: true, + }, + ); - const flatPages = useMemo(() => data?.pages.flat() ?? [], [data?.pages]); + const flatPages = useMemo(() => data?.pages.flat() ?? [], [data?.pages]); - // // Hide the Load More Button if we're displaying projects of one namespace only - // if (props.forge && props.namespace) { - // loadButton = ""; - // } + // // Hide the Load More Button if we're displaying projects of one namespace only + // if (props.forge && props.namespace) { + // loadButton = ""; + // } - // If backend API is down - if (isError) { - return ; - } + // If backend API is down + if (isError) { + return ; + } - // Show preloader if waiting for API data - if (isInitialLoading) { - return ; - } + // Show preloader if waiting for API data + if (isInitialLoading) { + return ; + } - return ( - <> - - - - - - - - - - - {flatPages.map((project, index) => { - const expandedCellKey = expandedCells - ? expandedCells[project.repo_name] - : null; - const isRowExpanded = !!expandedCellKey; - return ( - - - - - - - - - - - ); - })} -
RepositoriesBranches HandledIssues HandledReleases HandledPull Requests Handled -
- - {`${project.namespace}/${project.repo_name}`} - - - - -   - {project.branches_handled} - - - - -   - {project.issues_handled} - - - - -   - {project.releases_handled} - - - - -   - {project.prs_handled} - - - - Open external link - -
-
-
- -
- - ); + return ( + <> + + + + + + + + + + + {flatPages.map((project, index) => { + const expandedCellKey = expandedCells + ? expandedCells[project.repo_name] + : null; + const isRowExpanded = !!expandedCellKey; + return ( + + + + + + + + + + + ); + })} +
RepositoriesBranches HandledIssues HandledReleases HandledPull Requests Handled +
+ + {`${project.namespace}/${project.repo_name}`} + + + + +   + {project.branches_handled} + + + + +   + {project.issues_handled} + + + + +   + {project.releases_handled} + + + + +   + {project.prs_handled} + + + + Open external link + +
+
+
+ +
+ + ); }; export { ProjectsList }; diff --git a/frontend/src/app/Projects/PullRequestList.tsx b/frontend/src/app/Projects/PullRequestList.tsx index 2e4a55c5..875a2d82 100644 --- a/frontend/src/app/Projects/PullRequestList.tsx +++ b/frontend/src/app/Projects/PullRequestList.tsx @@ -5,153 +5,153 @@ import { Preloader } from "../Preloader/Preloader"; import { TriggerInfo } from "../Trigger/TriggerInfo"; import { - Button, - DataList, - DataListToggle, - DataListCell, - DataListItem, - DataListContent, - DataListItemCells, - DataListItemRow, + Button, + DataList, + DataListToggle, + DataListCell, + DataListItem, + DataListContent, + DataListItemCells, + DataListItemRow, } from "@patternfly/react-core"; import { getPRLink } from "../utils/forgeUrls"; import { useInfiniteQuery } from "@tanstack/react-query"; interface CoprBuild { - build_id: string; - chroot: string; - status: string; - web_url: string; + build_id: string; + chroot: string; + status: string; + web_url: string; } interface SRPMBuild { - srpm_build_id: number; - status: string; - log_url: string; + srpm_build_id: number; + status: string; + log_url: string; } interface TestingFarmRun { - pipeline_id: string; - chroot: string; - status: string; - web_url: string; + pipeline_id: string; + chroot: string; + status: string; + web_url: string; } interface PullRequestInfo { - pr_id: number; - builds: CoprBuild[]; - koji_builds: []; // TODO - srpm_builds: SRPMBuild[]; - tests: TestingFarmRun[]; + pr_id: number; + builds: CoprBuild[]; + koji_builds: []; // TODO + srpm_builds: SRPMBuild[]; + tests: TestingFarmRun[]; } async function fetchData( - URL: string, - page: string, + URL: string, + page: string, ): Promise { - const response = await fetch(`${URL}?page=${page}&per_page=10`); - return await response.json(); + const response = await fetch(`${URL}?page=${page}&per_page=10`); + return await response.json(); } interface PullRequestListProps { - forge: string; - namespace: string; - repoName: string; + forge: string; + namespace: string; + repoName: string; } const PullRequestList: React.FC = ({ - forge, - namespace, - repoName, + forge, + namespace, + repoName, }) => { - const [expanded, setExpanded] = useState<{ [key: string]: boolean }>({}); + const [expanded, setExpanded] = useState<{ [key: string]: boolean }>({}); - const URL = `${ - import.meta.env.VITE_API_URL - }/projects/${forge}/${namespace}/${repoName}/prs`; - // Fetch data from dashboard backend (or if we want, directly from the API) - const { data, isInitialLoading, isError, fetchNextPage } = useInfiniteQuery( - [URL], - ({ pageParam = 1 }) => fetchData(URL, pageParam), - { - getNextPageParam: (_, allPages) => allPages.length + 1, - keepPreviousData: true, - }, - ); + const URL = `${ + import.meta.env.VITE_API_URL + }/projects/${forge}/${namespace}/${repoName}/prs`; + // Fetch data from dashboard backend (or if we want, directly from the API) + const { data, isInitialLoading, isError, fetchNextPage } = useInfiniteQuery( + [URL], + ({ pageParam = 1 }) => fetchData(URL, pageParam), + { + getNextPageParam: (_, allPages) => allPages.length + 1, + keepPreviousData: true, + }, + ); - const flatPages = useMemo(() => data?.pages.flat(), [data?.pages]); + const flatPages = useMemo(() => data?.pages.flat(), [data?.pages]); - function onToggle(prID: number) { - // We cant just invert the previous state here - // because its undefined for the first time - if (expanded[prID]) { - let copyExpanded = { ...expanded }; - copyExpanded[prID] = false; - setExpanded(copyExpanded); - } else { - let copyExpanded = { ...expanded }; - copyExpanded[prID] = true; - setExpanded(copyExpanded); - } + function onToggle(prID: number) { + // We cant just invert the previous state here + // because its undefined for the first time + if (expanded[prID]) { + let copyExpanded = { ...expanded }; + copyExpanded[prID] = false; + setExpanded(copyExpanded); + } else { + let copyExpanded = { ...expanded }; + copyExpanded[prID] = true; + setExpanded(copyExpanded); } + } - // If backend API is down - if (isError) { - return ; - } + // If backend API is down + if (isError) { + return ; + } - // Show preloader if waiting for API data - if (isInitialLoading) { - return ; - } + // Show preloader if waiting for API data + if (isInitialLoading) { + return ; + } - return ( - <> - - {flatPages?.map((pr, index) => ( - + + {flatPages?.map((pr, index) => ( + + + onToggle(pr.pr_id)} + isExpanded={expanded[pr.pr_id]} + id={`pull-request-${pr.pr_id}`} + aria-controls="ex-expand1" + /> + + - - onToggle(pr.pr_id)} - isExpanded={expanded[pr.pr_id]} - id={`pull-request-${pr.pr_id}`} - aria-controls="ex-expand1" - /> - - - #{pr.pr_id} - - , - ]} - /> - - - - - - ))} - -
-
- -
- - ); + #{pr.pr_id} + + , + ]} + /> + + + + +
+ ))} +
+
+
+ +
+ + ); }; export { PullRequestList }; diff --git a/frontend/src/app/Projects/ReleasesList.tsx b/frontend/src/app/Projects/ReleasesList.tsx index 21f4d840..adcbc599 100644 --- a/frontend/src/app/Projects/ReleasesList.tsx +++ b/frontend/src/app/Projects/ReleasesList.tsx @@ -8,82 +8,82 @@ import { Table, Tbody, Td, Th, Thead, Tr } from "@patternfly/react-table"; // Fetch data from dashboard backend (or if we want, directly from the API) async function fetchData(URL: string): Promise { - return fetch(URL).then((response) => response.json()); + return fetch(URL).then((response) => response.json()); } interface ProjectRelease { - commit_hash: string; - tag_name: string; + commit_hash: string; + tag_name: string; } interface ReleasesListProps { - forge: string; - namespace: string; - repoName: string; + forge: string; + namespace: string; + repoName: string; } const ReleasesList: React.FC = ({ - forge, - namespace, - repoName, + forge, + namespace, + repoName, }) => { - const URL = `${ - import.meta.env.VITE_API_URL - }/projects/${forge}/${namespace}/${repoName}/releases`; - const { data, isError, isInitialLoading } = useQuery([URL], () => - fetchData(URL), - ); + const URL = `${ + import.meta.env.VITE_API_URL + }/projects/${forge}/${namespace}/${repoName}/releases`; + const { data, isError, isInitialLoading } = useQuery([URL], () => + fetchData(URL), + ); - // If backend API is down - if (isError) { - return ; - } + // If backend API is down + if (isError) { + return ; + } - // Show preloader if waiting for API data - if (isInitialLoading) { - return ; - } + // Show preloader if waiting for API data + if (isInitialLoading) { + return ; + } - return ( - - - - - - - - - {data?.map((release, index) => ( - - - - - ))} - -
TagCommit Hash
- - {release.tag_name} - - - - {release.commit_hash} - -
- ); + return ( + + + + + + + + + {data?.map((release, index) => ( + + + + + ))} + +
TagCommit Hash
+ + {release.tag_name} + + + + {release.commit_hash} + +
+ ); }; export { ReleasesList }; diff --git a/frontend/src/app/Results/ResultsPageCopr.tsx b/frontend/src/app/Results/ResultsPageCopr.tsx index 0ee59106..075de5e0 100644 --- a/frontend/src/app/Results/ResultsPageCopr.tsx +++ b/frontend/src/app/Results/ResultsPageCopr.tsx @@ -1,13 +1,13 @@ import { - PageSection, - Card, - CardBody, - PageSectionVariants, - TextContent, - Text, - Title, - List, - ListItem, + PageSection, + Card, + CardBody, + PageSectionVariants, + TextContent, + Text, + Title, + List, + ListItem, } from "@patternfly/react-core"; import { ErrorConnection } from "../Errors/ErrorConnection"; @@ -23,174 +23,162 @@ import { ResultsPageCoprDetails } from "./ResultsPageCoprDetails"; import { SHACopy } from "../utils/SHACopy"; interface BuildPackage { - arch: string; - epoch: number; - name: string; - release: string; - version: string; + arch: string; + epoch: number; + name: string; + release: string; + version: string; } export interface CoprResult { - build_id: string; - status: string; - chroot: string; - build_submitted_time: number; - build_start_time: number; - build_finished_time: number; - commit_sha: string; - web_url: string; - build_logs_url: string; - copr_project: string; - copr_owner: string; - srpm_build_id: number; - run_ids: number[]; - built_packages: BuildPackage[]; - repo_namespace: string; - repo_name: string; - git_repo: string; - pr_id: number | null; - issue_id: number | null; - branch_name: string | null; - release: string | null; + build_id: string; + status: string; + chroot: string; + build_submitted_time: number; + build_start_time: number; + build_finished_time: number; + commit_sha: string; + web_url: string; + build_logs_url: string; + copr_project: string; + copr_owner: string; + srpm_build_id: number; + run_ids: number[]; + built_packages: BuildPackage[]; + repo_namespace: string; + repo_name: string; + git_repo: string; + pr_id: number | null; + issue_id: number | null; + branch_name: string | null; + release: string | null; } function getPackagesToInstall(built_packages: BuildPackage[]) { - let packagesToInstall = []; + let packagesToInstall = []; - for (let packageDict of built_packages) { - if (packageDict.arch !== "src") { - const packageString = - packageDict.name + - "-" + - (packageDict.epoch ? packageDict.epoch + ":" : "") + - packageDict.version + - "-" + - packageDict.release + - "." + - packageDict.arch; - packagesToInstall.push(packageString); - } + for (let packageDict of built_packages) { + if (packageDict.arch !== "src") { + const packageString = + packageDict.name + + "-" + + (packageDict.epoch ? packageDict.epoch + ":" : "") + + packageDict.version + + "-" + + packageDict.release + + "." + + packageDict.arch; + packagesToInstall.push(packageString); } - return packagesToInstall; + } + return packagesToInstall; } export const fetchSyncRelease = (url: string) => - fetch(url).then((response) => { - if (!response.ok && response.status !== 404) { - throw Promise.reject(response); - } - return response.json(); - }); + fetch(url).then((response) => { + if (!response.ok && response.status !== 404) { + throw Promise.reject(response); + } + return response.json(); + }); export const API_COPR_BUILDS = `${import.meta.env.VITE_API_URL}/copr-builds/`; const ResultsPageCopr = () => { - useTitle("Copr Results"); - let { id } = useParams(); - const URL = API_COPR_BUILDS + id; - - const { data, isError, isInitialLoading } = useQuery< - CoprResult | { error: string } - >([URL], () => fetchSyncRelease(URL)); + useTitle("Copr Results"); + let { id } = useParams(); + const URL = API_COPR_BUILDS + id; - // If backend API is down - if (isError) { - return ; - } + const { data, isError, isInitialLoading } = useQuery< + CoprResult | { error: string } + >([URL], () => fetchSyncRelease(URL)); - // Show preloader if waiting for API data - if (isInitialLoading || data === undefined) { - return ; - } + // If backend API is down + if (isError) { + return ; + } - if ("error" in data) { - return ( - - - - - Not Found. - - - - - ); - } + // Show preloader if waiting for API data + if (isInitialLoading || data === undefined) { + return ; + } + if ("error" in data) { return ( - <> - - - Copr Build Results - - - - - - - - - - - - - - - - - - - - You can install the built RPMs by following - these steps: - - -
- - - - sudo yum install -y dnf-plugins-core - {" "} - on RHEL 8 or CentOS Stream - - - - sudo dnf install -y dnf-plugins-core - {" "} - on Fedora - - - - sudo dnf copr enable {data.copr_owner}/ - {data.copr_project} - - - {data.built_packages ? ( - - - sudo dnf install -y{" "} - {getPackagesToInstall( - data.built_packages, - ).join(" ")} - - - ) : ( - <> - )} - - -
- Please note that the RPMs should be used only in a - testing environment. -
-
-
-
- + + + + + Not Found. + + + + ); + } + + return ( + <> + + + Copr Build Results + + + + + + + + + + + + + + + + + + + + You can install the built RPMs by following these steps: + + +
+ + + sudo yum install -y dnf-plugins-core on RHEL 8 or + CentOS Stream + + + sudo dnf install -y dnf-plugins-core on Fedora + + + + sudo dnf copr enable {data.copr_owner}/{data.copr_project} + + + {data.built_packages ? ( + + + sudo dnf install -y{" "} + {getPackagesToInstall(data.built_packages).join(" ")} + + + ) : ( + <> + )} + + +
+ Please note that the RPMs should be used only in a testing + environment. +
+
+
+
+ + ); }; export { ResultsPageCopr }; diff --git a/frontend/src/app/Results/ResultsPageCoprDetails.tsx b/frontend/src/app/Results/ResultsPageCoprDetails.tsx index 5e484f97..8b35b23e 100644 --- a/frontend/src/app/Results/ResultsPageCoprDetails.tsx +++ b/frontend/src/app/Results/ResultsPageCoprDetails.tsx @@ -1,9 +1,9 @@ import { - Label, - DescriptionList, - DescriptionListDescription, - DescriptionListGroup, - DescriptionListTerm, + Label, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, } from "@patternfly/react-core"; import { Timestamp } from "../utils/Timestamp"; import { getCommitLink } from "../utils/forgeUrls"; @@ -14,66 +14,54 @@ import { Link, NavLink } from "react-router-dom"; import { LabelLink } from "../utils/LabelLink"; export interface ResultsPageCoprDetailsProps { - data: CoprResult; + data: CoprResult; } export const ResultsPageCoprDetails: React.FC = ({ - data, + data, }) => { - return ( - - - SRPM Build - - - Details - - - Copr build - - - ( - - Logs - - ) - - - - Build Submitted Time - - - - Build Start Time - - - - Build Finish Time - - - - - - ); + return ( + + + SRPM Build + + + Details + + + Copr build + + + ( + + Logs + + ) + + + + Build Submitted Time + + + + Build Start Time + + + + Build Finish Time + + + + + + ); }; diff --git a/frontend/src/app/Results/ResultsPageKoji.tsx b/frontend/src/app/Results/ResultsPageKoji.tsx index ebd2a872..7787b605 100644 --- a/frontend/src/app/Results/ResultsPageKoji.tsx +++ b/frontend/src/app/Results/ResultsPageKoji.tsx @@ -1,17 +1,17 @@ import React from "react"; import { - PageSection, - Card, - CardBody, - PageSectionVariants, - TextContent, - Text, - Title, - Label, - DescriptionList, - DescriptionListDescription, - DescriptionListGroup, - DescriptionListTerm, + 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"; @@ -27,188 +27,158 @@ import { useQuery } from "@tanstack/react-query"; import { SHACopy } from "../utils/SHACopy"; interface KojiBuild { - scratch: boolean; - task_id: string; - status: string; - chroot: string; - build_start_time: number; - build_finished_time: number; - build_submitted_time: number; - commit_sha: string; - web_url: string; - build_logs_urls: string; - srpm_build_id: number; - run_ids: number[]; - repo_namespace: string; - repo_name: string; - git_repo: string; - pr_id: number | null; - issue_id: number | null; - branch_name: string | null; - release: string | null; + scratch: boolean; + task_id: string; + status: string; + chroot: string; + build_start_time: number; + build_finished_time: number; + build_submitted_time: number; + commit_sha: string; + web_url: string; + build_logs_urls: string; + srpm_build_id: number; + run_ids: number[]; + repo_namespace: string; + repo_name: string; + git_repo: string; + pr_id: number | null; + issue_id: number | null; + branch_name: string | null; + release: string | null; } const fetchKojiBuilds = (url: string) => - fetch(url).then((response) => { - if (!response.ok && response.status !== 404) { - throw Promise.reject(response); - } - return response.json(); - }); + fetch(url).then((response) => { + if (!response.ok && response.status !== 404) { + throw Promise.reject(response); + } + return response.json(); + }); const ResultsPageKoji = () => { - useTitle("Koji Results"); - let { id } = useParams(); - - const URL = `${import.meta.env.VITE_API_URL}/koji-builds/${id}`; - const { data, isError, isInitialLoading } = useQuery< - KojiBuild | { error: string } - >([URL], () => fetchKojiBuilds(URL)); + useTitle("Koji Results"); + let { id } = useParams(); - // If backend API is down - if (isError) { - return ; - } + const URL = `${import.meta.env.VITE_API_URL}/koji-builds/${id}`; + const { data, isError, isInitialLoading } = useQuery< + KojiBuild | { error: string } + >([URL], () => fetchKojiBuilds(URL)); - // Show preloader if waiting for API data - if (isInitialLoading || data === undefined) { - return ; - } + // If backend API is down + if (isError) { + return ; + } - if ("error" in data) { - return ( - - - - - Not Found. - - - - - ); - } + // Show preloader if waiting for API data + if (isInitialLoading || data === undefined) { + return ; + } + if ("error" in data) { return ( - <> - - - Koji Build Results - - - - - -
-
-
-
- - - - - - - {data.srpm_build_id ? ( - <> - - SRPM Build - - - - - - ) : null} - - Koji Build - - - {" "} - ({data.scratch ? "scratch" : "production"}) - - - Build logs - - - {data.build_logs_urls !== null && - Object.keys(data.build_logs_urls).length !== - 0 ? ( - - - {data.build_logs_urls - ? Object.entries( - data.build_logs_urls, - ).map(([arch, url]) => ( - - - - )) - : null} - -
- - {arch} - -
- ) : ( - not provided - )} -
-
- - - Build Submitted Time - - - - - - Build Start Time - - - - - - Build Finish Time - - - - - -
-
-
-
- + + + + + Not Found. + + + + ); + } + + return ( + <> + + + Koji Build Results + + + + + +
+
+
+
+ + + + + + + {data.srpm_build_id ? ( + <> + SRPM Build + + + + + ) : null} + Koji Build + + {" "} + ({data.scratch ? "scratch" : "production"}) + + Build logs + + {data.build_logs_urls !== null && + Object.keys(data.build_logs_urls).length !== 0 ? ( + + + {data.build_logs_urls + ? Object.entries(data.build_logs_urls).map( + ([arch, url]) => ( + + + + ), + ) + : null} + +
+ {arch} +
+ ) : ( + not provided + )} +
+
+ + Build Submitted Time + + + + Build Start Time + + + + Build Finish Time + + + + +
+
+
+
+ + ); }; export { ResultsPageKoji }; diff --git a/frontend/src/app/Results/ResultsPageSRPM.tsx b/frontend/src/app/Results/ResultsPageSRPM.tsx index 81346b13..6888f6c5 100644 --- a/frontend/src/app/Results/ResultsPageSRPM.tsx +++ b/frontend/src/app/Results/ResultsPageSRPM.tsx @@ -1,20 +1,20 @@ import React from "react"; import { - PageSection, - Card, - CardBody, - PageSectionVariants, - TextContent, - Text, - Title, - Toolbar, - ToolbarContent, - ToolbarItem, - DescriptionList, - DescriptionListGroup, - DescriptionListTerm, - DescriptionListDescription, - Divider, + PageSection, + Card, + CardBody, + PageSectionVariants, + TextContent, + Text, + Title, + Toolbar, + ToolbarContent, + ToolbarItem, + DescriptionList, + DescriptionListGroup, + DescriptionListTerm, + DescriptionListDescription, + Divider, } from "@patternfly/react-core"; import { LogViewer, LogViewerSearch } from "@patternfly/react-log-viewer"; @@ -28,202 +28,187 @@ import { useTitle } from "../utils/useTitle"; import { useQuery } from "@tanstack/react-query"; interface SRPMBuild { - status: string; - build_start_time: number; - build_finished_time: number; - build_submitted_time: number; - url: string; - logs: string | null; - logs_url: string; - copr_build_id: string; - copr_web_url: string; - run_ids: number[]; - repo_namespace: string; - repo_name: string; - git_repo: string; - pr_id: number | null; - issue_id: number | null; - branch_name: string | null; - release: string | null; + status: string; + build_start_time: number; + build_finished_time: number; + build_submitted_time: number; + url: string; + logs: string | null; + logs_url: string; + copr_build_id: string; + copr_web_url: string; + run_ids: number[]; + repo_namespace: string; + repo_name: string; + git_repo: string; + pr_id: number | null; + issue_id: number | null; + branch_name: string | null; + release: string | null; } const fetchSRPMBuild = (url: string) => - fetch(url).then((response) => { - if (!response.ok && response.status !== 404) { - throw Promise.reject(response); - } - return response.json(); - }); - -const ResultsPageSRPM = () => { - useTitle("SRPM Results"); - let { id } = useParams(); - - const URL = `${import.meta.env.VITE_API_URL}/srpm-builds/${id}`; - const { data, isError, isInitialLoading } = useQuery< - SRPMBuild | { error: string } - >([URL], () => fetchSRPMBuild(URL)); - - // If backend API is down - if (isError) { - return ; + fetch(url).then((response) => { + if (!response.ok && response.status !== 404) { + throw Promise.reject(response); } + return response.json(); + }); - // Show preloader if waiting for API data - if (isInitialLoading || data === undefined) { - return ; - } - - if ("error" in data) { - return ( - - - - - Not Found. - - - - - ); - } - - const srpmURL = data.url ? ( - Link to download - ) : ( - "Not available to download" - ); - - const submittedAt = data.build_submitted_time ? ( - - ) : ( - "Not available" - ); +const ResultsPageSRPM = () => { + useTitle("SRPM Results"); + let { id } = useParams(); - const startedAt = data.build_start_time ? ( - - ) : ( - "Not available" - ); + const URL = `${import.meta.env.VITE_API_URL}/srpm-builds/${id}`; + const { data, isError, isInitialLoading } = useQuery< + SRPMBuild | { error: string } + >([URL], () => fetchSRPMBuild(URL)); - const finishedAt = data.build_finished_time ? ( - - ) : ( - "Not available" - ); + // If backend API is down + if (isError) { + return ; + } - const logs = data.copr_build_id ? ( - "" - ) : ( - - - - - - - - - - - } - hasLineNumbers={false} - /> - - - - ); + // Show preloader if waiting for API data + if (isInitialLoading || data === undefined) { + return ; + } + if ("error" in data) { return ( - <> - - - SRPM Build - - - - - - - - - - - - - - - - Copr build - - - {" "} - {data.url ? ( - <> - {" "} - ( - - Logs - - ) ( - - Results - - ) - - ) : ( - <> - )} - - - - - Build Submitted Time - - - {submittedAt} - - - Build Start Time - - - {startedAt} - - - Build Finish Time - - - {finishedAt} - - - - - - - {logs} - + + + + + Not Found. + + + + ); + } + + const srpmURL = data.url ? ( + Link to download + ) : ( + "Not available to download" + ); + + const submittedAt = data.build_submitted_time ? ( + + ) : ( + "Not available" + ); + + const startedAt = data.build_start_time ? ( + + ) : ( + "Not available" + ); + + const finishedAt = data.build_finished_time ? ( + + ) : ( + "Not available" + ); + + const logs = data.copr_build_id ? ( + "" + ) : ( + + + + + + + + + + + } + hasLineNumbers={false} + /> + + + + ); + + return ( + <> + + + SRPM Build + + + + + + + + + + + + + + + Copr build + + {" "} + {data.url ? ( + <> + {" "} + ( + + Logs + + ) ( + + Results + + ) + + ) : ( + <> + )} + + + + Build Submitted Time + + {submittedAt} + + Build Start Time + + {startedAt} + + Build Finish Time + + {finishedAt} + + + + + + + {logs} + + ); }; export { ResultsPageSRPM }; diff --git a/frontend/src/app/Results/ResultsPageSyncReleaseRuns.tsx b/frontend/src/app/Results/ResultsPageSyncReleaseRuns.tsx index 9adf0ea8..aa0831ab 100644 --- a/frontend/src/app/Results/ResultsPageSyncReleaseRuns.tsx +++ b/frontend/src/app/Results/ResultsPageSyncReleaseRuns.tsx @@ -1,23 +1,23 @@ import React, { useState } from "react"; import { - PageSection, - Card, - CardBody, - PageSectionVariants, - TextContent, - Text, - Title, - Toolbar, - ToolbarContent, - ToolbarItem, - Checkbox, - Button, - ToolbarGroup, - Tooltip, - DescriptionList, - DescriptionListDescription, - DescriptionListGroup, - DescriptionListTerm, + PageSection, + Card, + CardBody, + PageSectionVariants, + TextContent, + Text, + Title, + Toolbar, + ToolbarContent, + ToolbarItem, + Checkbox, + Button, + ToolbarGroup, + Tooltip, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, } from "@patternfly/react-core"; import { ErrorConnection } from "../Errors/ErrorConnection"; @@ -32,278 +32,249 @@ import { useQuery } from "@tanstack/react-query"; import { DownloadIcon, ExpandIcon } from "@patternfly/react-icons"; interface ResultsPageSyncReleaseRunsProps { - job: "propose-downstream" | "pull-from-upstream"; + job: "propose-downstream" | "pull-from-upstream"; } interface SyncReleaseRun { - status: string; - branch: string; - downstream_pr_url: string; - submitted_time: number; - start_time: number; - finished_time: number; - logs: string; - repo_namespace: string; - repo_name: string; - git_repo: string; - pr_id: number | null; - issue_id: number | null; - branch_name: string | null; - release: string | null; + status: string; + branch: string; + downstream_pr_url: string; + submitted_time: number; + start_time: number; + finished_time: number; + logs: string; + repo_namespace: string; + repo_name: string; + git_repo: string; + pr_id: number | null; + issue_id: number | null; + branch_name: string | null; + release: string | null; } const fetchSyncRelease = (url: string) => - fetch(url).then((response) => { - if (!response.ok && response.status !== 404) { - throw Promise.reject(response); - } - return response.json(); - }); + fetch(url).then((response) => { + if (!response.ok && response.status !== 404) { + throw Promise.reject(response); + } + return response.json(); + }); const ResultsPageSyncReleaseRuns: React.FC = ({ - job, + job, }) => { - const displayText = - job === "pull-from-upstream" - ? "Pull from upstream results" - : "Propose downstream results"; - useTitle(displayText); - let { id } = useParams(); - - const [isTextWrapped, setIsTextWrapped] = useState(true); - const [isLineNumbersShown, setIsLineNumbersShown] = useState(false); - // TODO(spytec): Not sure what the ref type is supposed to be - const logViewerRef = React.useRef(null); - const [isFullScreen, setIsFullScreen] = React.useState(false); + const displayText = + job === "pull-from-upstream" + ? "Pull from upstream results" + : "Propose downstream results"; + useTitle(displayText); + let { id } = useParams(); - const API_URL = `${import.meta.env.VITE_API_URL}/${job}/${id}`; - const { data, isError, isInitialLoading } = useQuery< - SyncReleaseRun | { error: string } - >([API_URL], () => fetchSyncRelease(API_URL)); + const [isTextWrapped, setIsTextWrapped] = useState(true); + const [isLineNumbersShown, setIsLineNumbersShown] = useState(false); + // TODO(spytec): Not sure what the ref type is supposed to be + const logViewerRef = React.useRef(null); + const [isFullScreen, setIsFullScreen] = React.useState(false); - // If backend API is down - if (isError) { - return ; - } + const API_URL = `${import.meta.env.VITE_API_URL}/${job}/${id}`; + const { data, isError, isInitialLoading } = useQuery< + SyncReleaseRun | { error: string } + >([API_URL], () => fetchSyncRelease(API_URL)); - // Show preloader if waiting for API data - if (isInitialLoading || data === undefined) { - return ; - } + // If backend API is down + if (isError) { + return ; + } - if ("error" in data) { - return ( - - - - - Not Found. - - - - - ); - } + // Show preloader if waiting for API data + if (isInitialLoading || data === undefined) { + return ; + } - const linkToDownstreamPR = data.downstream_pr_url ? ( - - Click here - - ) : ( - <>Link will be available after successful downstream PR submission. + if ("error" in data) { + return ( + + + + + Not Found. + + + + ); + } - const FooterButton = () => { - const handleClick = () => { - if (logViewerRef.current) logViewerRef.current.scrollToBottom(); - }; - return ; - }; - - const onDownloadClick = () => { - if (!data.logs) return; - const element = document.createElement("a"); - const file = new Blob([data.logs], { type: "text/plain" }); - element.href = URL.createObjectURL(file); - element.download = `${data.repo_namespace}-${data.repo_name}-${data.start_time}.txt`; - document.body.appendChild(element); - element.click(); - document.body.removeChild(element); - }; + const linkToDownstreamPR = data.downstream_pr_url ? ( + + Click here + + ) : ( + <>Link will be available after successful downstream PR submission. + ); - const onExpandClick = (_: any) => { - const element: any = document.querySelector("#logviewer"); - if (!isFullScreen && element) { - element.requestFullscreen(); - setIsFullScreen(true); - } else { - document.exitFullscreen(); - setIsFullScreen(false); - } + const FooterButton = () => { + const handleClick = () => { + if (logViewerRef.current) logViewerRef.current.scrollToBottom(); }; + return ; + }; - // TODO(SpyTec): Move to its own component - const logs = ( - - - - - - - - - - - - setIsTextWrapped(val) - } - /> - - - - setIsLineNumbersShown(val) - } - /> - - - - - Download} - > - - - - - Expand} - > - - - - - - - } - footer={} - /> - - - - ); + const onDownloadClick = () => { + if (!data.logs) return; + const element = document.createElement("a"); + const file = new Blob([data.logs], { type: "text/plain" }); + element.href = URL.createObjectURL(file); + element.download = `${data.repo_namespace}-${data.repo_name}-${data.start_time}.txt`; + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); + }; - return ( - <> - - - {displayText} - - - - -
-
-
-
+ const onExpandClick = (_: any) => { + const element: any = document.querySelector("#logviewer"); + if (!isFullScreen && element) { + element.requestFullscreen(); + setIsFullScreen(true); + } else { + document.exitFullscreen(); + setIsFullScreen(false); + } + }; - - - - + + + + + + + + + + setIsTextWrapped(val)} + /> + + + setIsLineNumbersShown(val)} + /> + + + + + Download}> + + + + + Expand}> + + + + + + + } + footer={} + /> + + + + ); + + return ( + <> + + + {displayText} + + + + +
+
+
+
+ + + + + + + Status + + + + + + Submitted Time + + + + Start Time + + + + Finish Time + + + + + + + + + {logs} + + ); }; export { ResultsPageSyncReleaseRuns }; diff --git a/frontend/src/app/Results/ResultsPageTestingFarm.tsx b/frontend/src/app/Results/ResultsPageTestingFarm.tsx index eeb26047..e15f5f4e 100644 --- a/frontend/src/app/Results/ResultsPageTestingFarm.tsx +++ b/frontend/src/app/Results/ResultsPageTestingFarm.tsx @@ -1,24 +1,24 @@ import React, { useEffect, useState } from "react"; import { - PageSection, - Card, - CardBody, - PageSectionVariants, - TextContent, - Text, - Title, - DescriptionList, - DescriptionListDescription, - DescriptionListGroup, - DescriptionListTerm, - DataList, - DataListItemRow, - DataListItem, - DataListContent, - DataListItemCells, - DataListCell, - DataListToggle, - ClipboardCopy, + PageSection, + Card, + CardBody, + PageSectionVariants, + TextContent, + Text, + Title, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + DataList, + DataListItemRow, + DataListItem, + DataListContent, + DataListItemCells, + DataListCell, + DataListToggle, + ClipboardCopy, } from "@patternfly/react-core"; import { ErrorConnection } from "../Errors/ErrorConnection"; @@ -29,36 +29,36 @@ import { useTitle } from "../utils/useTitle"; import { Timestamp } from "../utils/Timestamp"; import { getCommitLink } from "../utils/forgeUrls"; import { - QueriesOptions, - UseQueryResult, - useQueries, - useQuery, + QueriesOptions, + UseQueryResult, + useQueries, + useQuery, } from "@tanstack/react-query"; import { - API_COPR_BUILDS, - CoprResult, - fetchSyncRelease, + API_COPR_BUILDS, + CoprResult, + fetchSyncRelease, } from "./ResultsPageCopr"; import { Preloader } from "../Preloader/Preloader"; import { ResultsPageCoprDetails } from "./ResultsPageCoprDetails"; import { SHACopy } from "../utils/SHACopy"; export interface TestingFarmOverview { - pipeline_id: string; // UUID - status: string; - chroot: string; - commit_sha: string; - web_url: string; - copr_build_ids: number[]; - run_ids: number[]; - submitted_time: number; - repo_namespace: string; - repo_name: string; - git_repo: string; - pr_id: number | null; - issue_id: number | null; - branch_name: string | null; - release: string | null; + pipeline_id: string; // UUID + status: string; + chroot: string; + commit_sha: string; + web_url: string; + copr_build_ids: number[]; + run_ids: number[]; + submitted_time: number; + repo_namespace: string; + repo_name: string; + git_repo: string; + pr_id: number | null; + issue_id: number | null; + branch_name: string | null; + release: string | null; } /** @@ -66,243 +66,225 @@ export interface TestingFarmOverview { * @returns Expandable Copr list that fetches detail on expansion */ function useCoprBuilds(copr_build_ids: number[]): React.JSX.Element[] { - let coprBuilds = []; + let coprBuilds = []; - const [expandedBuilds, setExpandedBuilds] = useState([]); - const [coprQueries, setCoprQueries] = useState<[...QueriesOptions]>( - [], - ); - type QueryResult = UseQueryResult; - const results = useQueries<(CoprResult | { error: string })[]>({ - queries: coprQueries, - }) as QueryResult[]; - useEffect(() => { - const queries: typeof coprQueries = []; - copr_build_ids.forEach((id) => { - const URL = API_COPR_BUILDS + id; - queries.push({ - queryKey: [URL], - queryFn: () => fetchSyncRelease(URL), - enabled: expandedBuilds.includes(id), - staleTime: Infinity, - }); - }); - setCoprQueries(queries); - }, [copr_build_ids, expandedBuilds]); + const [expandedBuilds, setExpandedBuilds] = useState([]); + const [coprQueries, setCoprQueries] = useState<[...QueriesOptions]>([]); + type QueryResult = UseQueryResult; + const results = useQueries<(CoprResult | { error: string })[]>({ + queries: coprQueries, + }) as QueryResult[]; + useEffect(() => { + const queries: typeof coprQueries = []; + copr_build_ids.forEach((id) => { + const URL = API_COPR_BUILDS + id; + queries.push({ + queryKey: [URL], + queryFn: () => fetchSyncRelease(URL), + enabled: expandedBuilds.includes(id), + staleTime: Infinity, + }); + }); + setCoprQueries(queries); + }, [copr_build_ids, expandedBuilds]); - const toggleExpand = (id: number) => { - if (expandedBuilds.includes(id)) { - setExpandedBuilds( - expandedBuilds.filter((expandedBuild) => id !== expandedBuild), - ); - } else { - setExpandedBuilds([...expandedBuilds, id]); - } - }; + const toggleExpand = (id: number) => { + if (expandedBuilds.includes(id)) { + setExpandedBuilds( + expandedBuilds.filter((expandedBuild) => id !== expandedBuild), + ); + } else { + setExpandedBuilds([...expandedBuilds, id]); + } + }; - const coprDetails = results.map((result) => { - if (!result.data || typeof result.data !== "object") - return <>Loading; - if ("error" in result.data) { - return <>Error {result.data["error"]}; - } else if ("build_id" in result.data) { - return ; - } - }); - for (let i = 0; i < copr_build_ids.length; i++) { - let coprBuildId = copr_build_ids[i]; - let isExpanded = expandedBuilds.includes(coprBuildId); - coprBuilds.push( - - - toggleExpand(coprBuildId)} - /> - - - {coprBuildId} - - , - ]} - > - - - {coprDetails} - - , - ); + const coprDetails = results.map((result) => { + if (!result.data || typeof result.data !== "object") return <>Loading; + if ("error" in result.data) { + return <>Error {result.data["error"]}; + } else if ("build_id" in result.data) { + return ; } - return coprBuilds; + }); + for (let i = 0; i < copr_build_ids.length; i++) { + let coprBuildId = copr_build_ids[i]; + let isExpanded = expandedBuilds.includes(coprBuildId); + coprBuilds.push( + + + toggleExpand(coprBuildId)} + /> + + + {coprBuildId} + + , + ]} + > + + + {coprDetails} + + , + ); + } + return coprBuilds; } const fetchTestingFarm = ( - url: string, + url: string, ): Promise => - fetch(url).then((response) => { - if (!response.ok && response.status !== 404) { - throw Promise.reject(response); - } - return response.json(); - }); + fetch(url).then((response) => { + if (!response.ok && response.status !== 404) { + throw Promise.reject(response); + } + return response.json(); + }); const ResultsPageTestingFarm = () => { - useTitle("Testing Farm Results"); - let { id } = useParams(); + useTitle("Testing Farm Results"); + let { id } = useParams(); - const URL = `${import.meta.env.VITE_API_URL}/testing-farm/${id}`; - const { data, isError, isInitialLoading } = useQuery([URL], () => - fetchTestingFarm(URL), - ); - const [coprBuildIds, setCoprBuildIds] = useState([]); - const coprBuilds = useCoprBuilds(coprBuildIds); - useEffect(() => { - if (data && "copr_build_ids" in data) { - setCoprBuildIds( - data?.copr_build_ids.filter((copr) => copr !== null), - ); - } - }, [data]); - // If backend API is down - if (isError) { - return ; + const URL = `${import.meta.env.VITE_API_URL}/testing-farm/${id}`; + const { data, isError, isInitialLoading } = useQuery([URL], () => + fetchTestingFarm(URL), + ); + const [coprBuildIds, setCoprBuildIds] = useState([]); + const coprBuilds = useCoprBuilds(coprBuildIds); + useEffect(() => { + if (data && "copr_build_ids" in data) { + setCoprBuildIds(data?.copr_build_ids.filter((copr) => copr !== null)); } + }, [data]); + // If backend API is down + if (isError) { + return ; + } - // // Show preloader if waiting for API data - // if (isInitialLoading || data === undefined) { - // return ; - // } + // // Show preloader if waiting for API data + // if (isInitialLoading || data === undefined) { + // return ; + // } - if (data && "error" in data) { - return; - } + if (data && "error" in data) { + return; + } - const statusWithLink = data?.web_url ? ( - - {data.status} - - ) : ( - <>{data?.status} - ); + const statusWithLink = data?.web_url ? ( + + {data.status} + + ) : ( + <>{data?.status} + ); - return ( - <> - - - Testing Farm Results + return ( + <> + + + Testing Farm Results - - - {data ? ( - <> - - - - ) : ( - <> - )} - -
-
-
-
- + + + {data ? ( + <> + + + + ) : ( + <> + )} + +
+
+
+
+ + + {!data ? ( + isInitialLoading || data === undefined ? ( + + ) : ( + - {!data ? ( - isInitialLoading || data === undefined ? ( - - ) : ( - - - - - Not Found. - - - - - ) - ) : ( - <> - - - - - Status - - - - - - Pipeline ID - - - - {data.pipeline_id} - - - - - - Run Submitted Time - - - - - - - - - - - - Copr Build(s) - - - - {coprBuilds} - - - - - - - )} + + + Not Found. + + - - - ); + + ) + ) : ( + <> + + + + Status + + + + Pipeline ID + + + {data.pipeline_id} + + + + + + Run Submitted Time + + + + + + + + + + + Copr Build(s) + + {coprBuilds} + + + + + + )} + + + + ); }; export { ResultsPageTestingFarm }; diff --git a/frontend/src/app/StatusLabel/BaseStatusLabel.tsx b/frontend/src/app/StatusLabel/BaseStatusLabel.tsx index 85c7c99f..f4e816e7 100644 --- a/frontend/src/app/StatusLabel/BaseStatusLabel.tsx +++ b/frontend/src/app/StatusLabel/BaseStatusLabel.tsx @@ -3,20 +3,20 @@ import { Label, Tooltip } from "@patternfly/react-core"; import { Link } from "react-router-dom"; export interface BaseStatusLabelProps { - link?: string; - tooltipText: string; - icon: React.ReactNode; - color: - | "blue" - | "cyan" - | "green" - | "orange" - | "purple" - | "red" - | "grey" - | "gold" - | undefined; - label: React.ReactNode; + link?: string; + tooltipText: string; + icon: React.ReactNode; + color: + | "blue" + | "cyan" + | "green" + | "orange" + | "purple" + | "red" + | "grey" + | "gold" + | undefined; + label: React.ReactNode; } /** @@ -24,42 +24,36 @@ export interface BaseStatusLabelProps { * properties that are given to almost every status label, e.g. link and tooltip. */ export const BaseStatusLabel: React.FC = (props) => { - return ( - - - - - - ); + return ( + + + + + + ); }; diff --git a/frontend/src/app/StatusLabel/StatusLabel.tsx b/frontend/src/app/StatusLabel/StatusLabel.tsx index ffd7e8c2..4be27d35 100644 --- a/frontend/src/app/StatusLabel/StatusLabel.tsx +++ b/frontend/src/app/StatusLabel/StatusLabel.tsx @@ -1,51 +1,51 @@ import React, { useEffect, useState } from "react"; import { - CheckCircleIcon, - ExclamationCircleIcon, - ExclamationTriangleIcon, - InfoCircleIcon, + CheckCircleIcon, + ExclamationCircleIcon, + ExclamationTriangleIcon, + InfoCircleIcon, } from "@patternfly/react-icons"; import { BaseStatusLabel, BaseStatusLabelProps } from "./BaseStatusLabel"; export interface StatusLabelProps { - link: string; - target?: string; - status: string; + link: string; + target?: string; + status: string; } /** * Status label component that is used from other components. */ export const StatusLabel: React.FC = (props) => { - const [color, setColor] = useState("purple"); - const [icon, setIcon] = useState(); + const [color, setColor] = useState("purple"); + const [icon, setIcon] = useState(); - useEffect(() => { - switch (props.status) { - case "success": - case "passed": - setColor("green"); - setIcon(); - break; - case "failure": - case "failed": - setColor("red"); - setIcon(); - break; - case "error": - setColor("orange"); - setIcon(); - break; - } - }, [props.status]); + useEffect(() => { + switch (props.status) { + case "success": + case "passed": + setColor("green"); + setIcon(); + break; + case "failure": + case "failed": + setColor("red"); + setIcon(); + break; + case "error": + setColor("orange"); + setIcon(); + break; + } + }, [props.status]); - return ( - - ); + return ( + + ); }; diff --git a/frontend/src/app/StatusLabel/SyncReleaseTargetStatusLabel.tsx b/frontend/src/app/StatusLabel/SyncReleaseTargetStatusLabel.tsx index d48dcc96..47e6d7ee 100644 --- a/frontend/src/app/StatusLabel/SyncReleaseTargetStatusLabel.tsx +++ b/frontend/src/app/StatusLabel/SyncReleaseTargetStatusLabel.tsx @@ -1,10 +1,10 @@ import React, { useEffect, useState } from "react"; import { Spinner } from "@patternfly/react-core"; import { - CheckCircleIcon, - ExclamationCircleIcon, - InfoCircleIcon, - RedoIcon, + CheckCircleIcon, + ExclamationCircleIcon, + InfoCircleIcon, + RedoIcon, } from "@patternfly/react-icons"; import { BaseStatusLabel, BaseStatusLabelProps } from "./BaseStatusLabel"; import { StatusLabelProps } from "./StatusLabel"; @@ -15,43 +15,43 @@ import { StatusLabelProps } from "./StatusLabel"; */ export const SyncReleaseTargetStatusLabel: React.FC = ( - props, + props, ) => { - const [color, setColor] = useState("purple"); - const [icon, setIcon] = useState(); + const [color, setColor] = useState("purple"); + const [icon, setIcon] = useState(); - useEffect(() => { - switch (props.status) { - case "running": - setColor("blue"); - setIcon(); - break; - case "error": - setColor("red"); - setIcon(); - break; - case "retry": - setColor("orange"); - setIcon(); - break; - case "submitted": - setColor("green"); - setIcon(); - break; - case "skipped": - setColor("gray"); - setIcon(); - break; - } - }, [props.status]); + useEffect(() => { + switch (props.status) { + case "running": + setColor("blue"); + setIcon(); + break; + case "error": + setColor("red"); + setIcon(); + break; + case "retry": + setColor("orange"); + setIcon(); + break; + case "submitted": + setColor("green"); + setIcon(); + break; + case "skipped": + setColor("gray"); + setIcon(); + break; + } + }, [props.status]); - return ( - - ); + return ( + + ); }; diff --git a/frontend/src/app/Support/Support.tsx b/frontend/src/app/Support/Support.tsx index 58b4e0d9..fb94f428 100644 --- a/frontend/src/app/Support/Support.tsx +++ b/frontend/src/app/Support/Support.tsx @@ -1,23 +1,23 @@ import * as React from "react"; import { CubesIcon } from "@patternfly/react-icons"; import { - PageSection, - EmptyState, - EmptyStateVariant, - EmptyStateIcon, - EmptyStateHeader, + PageSection, + EmptyState, + EmptyStateVariant, + EmptyStateIcon, + EmptyStateHeader, } from "@patternfly/react-core"; const Support = () => ( - - - } - headingLevel="h1" - /> - - + + + } + headingLevel="h1" + /> + + ); export { Support }; diff --git a/frontend/src/app/Trigger/TriggerInfo.tsx b/frontend/src/app/Trigger/TriggerInfo.tsx index b27ad033..5b7e70e2 100644 --- a/frontend/src/app/Trigger/TriggerInfo.tsx +++ b/frontend/src/app/Trigger/TriggerInfo.tsx @@ -2,193 +2,182 @@ import React, { useState } from "react"; import { Label } from "@patternfly/react-core"; import { - Dropdown, - DropdownToggle, - DropdownItem, + Dropdown, + DropdownToggle, + DropdownItem, } from "@patternfly/react-core/deprecated"; import { CaretDownIcon, ExternalLinkAltIcon } from "@patternfly/react-icons"; import { Table, Tbody, Td, Th, Thead, Tr } from "@patternfly/react-table"; interface TriggerInfoProps { - trigger: { - builds: { - build_id: string; - chroot: string; - status: string; - web_url: string; - }[]; - koji_builds: { - build_id: string; - status: string; - chroot: string; - web_url: string; - }[]; - srpm_builds: { - srpm_build_id: number; - status: string; - log_url: string; - }[]; - tests: { - pipeline_id: string; - chroot: string; - status: string; - web_url: string; - }[]; - }; + trigger: { + builds: { + build_id: string; + chroot: string; + status: string; + web_url: string; + }[]; + koji_builds: { + build_id: string; + status: string; + chroot: string; + web_url: string; + }[]; + srpm_builds: { + srpm_build_id: number; + status: string; + log_url: string; + }[]; + tests: { + pipeline_id: string; + chroot: string; + status: string; + web_url: string; + }[]; + }; } // Trigger here refers to one unique pull request or one unique branch push const TriggerInfo: React.FC = (props) => { - const [isOpen, setOpen] = useState(false); - const [activeView, setActiveView] = useState("Builds"); + const [isOpen, setOpen] = useState(false); + const [activeView, setActiveView] = useState("Builds"); - // Open/close the dropdown - // This is called in two cases - // a) when the toggle button is pressed - // b) when someone chooses an entry - function onToggle() { - setOpen((isOpen) => !isOpen); - } + // Open/close the dropdown + // This is called in two cases + // a) when the toggle button is pressed + // b) when someone chooses an entry + function onToggle() { + setOpen((isOpen) => !isOpen); + } - // NOTE: Since we did not need any advanced table features, - // I used a regular html table instead of Patternfly React's table component - let activeViewContent; - if (activeView === "Builds") { - activeViewContent = ( - - - - - - - - - - - {props.trigger.builds.map((build, index) => ( - - - - - - - ))} - -
Build IDChrootSuccessWeb URL
{build.build_id} - - {build.status} - -
- ); - } - if (activeView === "SRPM Builds") { - activeViewContent = ( - - - - - - - - - - {props.trigger.srpm_builds.map((build, index) => ( - - - - - - ))} - -
SRPM Build IDSuccessLogs
- {build.srpm_build_id} - {build.status} - -
- ); - } - if (activeView === "Test Runs") { - activeViewContent = ( - - - - - - - - - - - {props.trigger.tests.map((test) => ( - - - - - - - ))} - -
Pipeline IDChrootStatusWeb URL
{test.pipeline_id} - - {test.status} - -
- ); - } + // NOTE: Since we did not need any advanced table features, + // I used a regular html table instead of Patternfly React's table component + let activeViewContent; + if (activeView === "Builds") { + activeViewContent = ( + + + + + + + + + + + {props.trigger.builds.map((build, index) => ( + + + + + + + ))} + +
Build IDChrootSuccessWeb URL
{build.build_id} + + {build.status} + +
+ ); + } + if (activeView === "SRPM Builds") { + activeViewContent = ( + + + + + + + + + + {props.trigger.srpm_builds.map((build, index) => ( + + + + + + ))} + +
SRPM Build IDSuccessLogs
{build.srpm_build_id}{build.status} + +
+ ); + } + if (activeView === "Test Runs") { + activeViewContent = ( + + + + + + + + + + + {props.trigger.tests.map((test) => ( + + + + + + + ))} + +
Pipeline IDChrootStatusWeb URL
{test.pipeline_id} + + {test.status} + +
+ ); + } - return ( - <> - - {activeView} - - } - isOpen={isOpen} - dropdownItems={[ - setActiveView("Builds")} - > - Builds - , - setActiveView("SRPM Builds")} - > - SRPM Builds - , - setActiveView("Test Runs")} - > - Test Runs - , - ]} - /> -
-
+ return ( + <> + + {activeView} + + } + isOpen={isOpen} + dropdownItems={[ + setActiveView("Builds")}> + Builds + , + setActiveView("SRPM Builds")} + > + SRPM Builds + , + setActiveView("Test Runs")}> + Test Runs + , + ]} + /> +
+
- {activeViewContent} - - ); + {activeViewContent} + + ); }; interface WebUrlIconProps { - link: string; + link: string; } const WebUrlIcon: React.FC = (props) => { - return ( - - - - ); + return ( + + + + ); }; export { TriggerInfo }; diff --git a/frontend/src/app/Trigger/TriggerLink.tsx b/frontend/src/app/Trigger/TriggerLink.tsx index 932513a2..e180a5c7 100644 --- a/frontend/src/app/Trigger/TriggerLink.tsx +++ b/frontend/src/app/Trigger/TriggerLink.tsx @@ -1,70 +1,70 @@ import React from "react"; import { - getPRLink, - getBranchLink, - getIssueLink, - getReleaseLink, + getPRLink, + getBranchLink, + getIssueLink, + getReleaseLink, } from "../utils/forgeUrls"; interface TriggerLinkProps { - builds: { - project_url?: string | null; - repo_namespace: string; - repo_name: string; - git_repo?: string | null; - pr_id?: number | null; - issue_id?: number | null; - branch_name?: string | null; - release?: string | null; - }; + builds: { + project_url?: string | null; + repo_namespace: string; + repo_name: string; + git_repo?: string | null; + pr_id?: number | null; + issue_id?: number | null; + branch_name?: string | null; + release?: string | null; + }; } const TriggerLink: React.FC = (props) => { - let link = ""; - // set suffix to be either PR ID or Branch Name depending on trigger - let jobSuffix = ""; + let link = ""; + // set suffix to be either PR ID or Branch Name depending on trigger + let jobSuffix = ""; - // different endpoints use "git_repo" or "project_url" to refer to the same thing - let gitRepo = ""; - if (props.builds.project_url) { - gitRepo = props.builds.project_url; - } else if (props.builds.git_repo) { - gitRepo = props.builds.git_repo; - } + // different endpoints use "git_repo" or "project_url" to refer to the same thing + let gitRepo = ""; + if (props.builds.project_url) { + gitRepo = props.builds.project_url; + } else if (props.builds.git_repo) { + gitRepo = props.builds.git_repo; + } - if (props.builds.pr_id) { - jobSuffix = `#${props.builds.pr_id}`; + if (props.builds.pr_id) { + jobSuffix = `#${props.builds.pr_id}`; - link = getPRLink(gitRepo, props.builds.pr_id); - } else if (props.builds.issue_id) { - jobSuffix = `#${props.builds.issue_id}`; + link = getPRLink(gitRepo, props.builds.pr_id); + } else if (props.builds.issue_id) { + jobSuffix = `#${props.builds.issue_id}`; - link = getIssueLink(gitRepo, props.builds.issue_id); - } else if (props.builds.branch_name) { - jobSuffix = `:${props.builds.branch_name}`; + link = getIssueLink(gitRepo, props.builds.issue_id); + } else if (props.builds.branch_name) { + jobSuffix = `:${props.builds.branch_name}`; - link = getBranchLink(gitRepo, props.builds.branch_name); - } else if (props.builds.release) { - jobSuffix = `#release:${props.builds.release}`; + link = getBranchLink(gitRepo, props.builds.branch_name); + } else if (props.builds.release) { + jobSuffix = `#release:${props.builds.release}`; - link = getReleaseLink(gitRepo, props.builds.release); - } + link = getReleaseLink(gitRepo, props.builds.release); + } - if (link !== "") { - return ( - - {props.builds.repo_namespace}/{props.builds.repo_name} - {jobSuffix} - - ); - } else { - return ( - <> - {props.builds.repo_namespace}/{props.builds.repo_name} - {jobSuffix} - - ); - } + if (link !== "") { + return ( + + {props.builds.repo_namespace}/{props.builds.repo_name} + {jobSuffix} + + ); + } else { + return ( + <> + {props.builds.repo_namespace}/{props.builds.repo_name} + {jobSuffix} + + ); + } }; export { TriggerLink }; diff --git a/frontend/src/app/Usage/Usage.tsx b/frontend/src/app/Usage/Usage.tsx index 1659721b..297abf8d 100644 --- a/frontend/src/app/Usage/Usage.tsx +++ b/frontend/src/app/Usage/Usage.tsx @@ -1,13 +1,13 @@ import React from "react"; import { - PageSection, - PageSectionVariants, - TextContent, - Text, - Tabs, - Tab, - Card, - CardBody, + PageSection, + PageSectionVariants, + TextContent, + Text, + Tabs, + Tab, + Card, + CardBody, } from "@patternfly/react-core"; import { UsageList } from "./UsageList"; @@ -15,58 +15,58 @@ import { UsageInterval } from "./UsageInterval"; import { useTitle } from "../utils/useTitle"; const Usage = () => { - useTitle("Usage"); - const [activeTabKey, setActiveTabKey] = React.useState(0); - const handleTabClick = ( - _: React.MouseEvent, - tabIndex: number | string, - ) => { - setActiveTabKey(tabIndex); - }; + useTitle("Usage"); + const [activeTabKey, setActiveTabKey] = React.useState(0); + const handleTabClick = ( + _: React.MouseEvent, + tabIndex: number | string, + ) => { + setActiveTabKey(tabIndex); + }; - return ( - <> - - - Usage - Usage of Packit Service. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); + return ( + <> + + + Usage + Usage of Packit Service. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); }; export { Usage }; diff --git a/frontend/src/app/Usage/UsageInterval.tsx b/frontend/src/app/Usage/UsageInterval.tsx index 720b3792..0f7dba3b 100644 --- a/frontend/src/app/Usage/UsageInterval.tsx +++ b/frontend/src/app/Usage/UsageInterval.tsx @@ -1,22 +1,22 @@ import { - PageSection, - Card, - CardBody, - CardTitle, - Title, - Flex, - FlexItem, - LabelGroup, - Label, + PageSection, + Card, + CardBody, + CardTitle, + Title, + Flex, + FlexItem, + LabelGroup, + Label, } from "@patternfly/react-core"; import { - Chart, - ChartAxis, - ChartGroup, - ChartLine, - createContainer, - ChartLegendTooltip, - ChartThemeColor, + Chart, + ChartAxis, + ChartGroup, + ChartLine, + createContainer, + ChartLegendTooltip, + ChartThemeColor, } from "@patternfly/react-charts"; import { ErrorConnection } from "../Errors/ErrorConnection"; @@ -26,251 +26,244 @@ import { UsageListData } from "./UsageListData"; import { ForgeIcon } from "../Forge/ForgeIcon"; const fetchDataByGranularity = (granularity: UsageIntervalProps) => - fetch( - `${import.meta.env.VITE_API_URL}/usage/intervals?days=${ - granularity.days - }&hours=${granularity.hours}&count=${granularity.count}`, - ).then((response) => response.json()); + fetch( + `${import.meta.env.VITE_API_URL}/usage/intervals?days=${ + granularity.days + }&hours=${granularity.hours}&count=${granularity.count}`, + ).then((response) => response.json()); interface UsageIntervalProps { - days: number; - hours: number; - count: number; + days: number; + hours: number; + count: number; } const UsageInterval: React.FC = (props) => { - const { data, isInitialLoading, isError } = useQuery( - [`usage-intervals-${props.days}-${props.hours}&-${props.count}`], - () => fetchDataByGranularity(props), - { - keepPreviousData: true, - }, - ); - - // If backend API is down - if (isError) { - return ; - } + const { data, isInitialLoading, isError } = useQuery( + [`usage-intervals-${props.days}-${props.hours}&-${props.count}`], + () => fetchDataByGranularity(props), + { + keepPreviousData: true, + }, + ); - // Show preloader if waiting for API data - if (isInitialLoading) { - return ; - } + // If backend API is down + if (isError) { + return ; + } - if (!data || "error" in data) { - return ( - - - - - Not Found. - - - - - ); - } + // Show preloader if waiting for API data + if (isInitialLoading) { + return ; + } - function getReadableName(job_name: string) { - return job_name - .replaceAll("_", " ") - .toLowerCase() - .split(" ") - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join(" ") - .replace(" Groups", "s") - .replace(" Targets", "s") - .replace("Vm", "VM") - .replace("Tft", "TFT") - .replace("Srpm", "SRPM"); - } + if (!data || "error" in data) { + return ( + + + + + Not Found. + + + + + ); + } - function getChartLine( - line_data: [{ name: string; x: string; y: number }], - name: string, - ) { - return ; - } + function getReadableName(job_name: string) { + return job_name + .replaceAll("_", " ") + .toLowerCase() + .split(" ") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" ") + .replace(" Groups", "s") + .replace(" Targets", "s") + .replace("Vm", "VM") + .replace("Tft", "TFT") + .replace("Srpm", "SRPM"); + } - function getLineChart(keysToShow, dataMapping, description) { - var CursorVoronoiContainer = createContainer("voronoi", "cursor"); - var jobChartLineLegendData = keysToShow.map((key) => ({ - childName: getReadableName(key), - name: getReadableName(key), - })); - var jobChartLines = keysToShow.map((key, i) => - getChartLine(dataMapping[key], key), - ); + function getChartLine( + line_data: [{ name: string; x: string; y: number }], + name: string, + ) { + return ; + } - return ( - - - -
- `${datum.y}`} - constrainToVisibleArea - labelComponent={ - datum.x} - /> - } - mouseFollowTooltips - voronoiDimension="x" - /> - } - legendData={jobChartLineLegendData} - legendOrientation="vertical" - legendPosition="right" - minDomain={{ y: 0 }} - themeColor={ChartThemeColor.multiUnordered} - padding={{ - bottom: 50, - left: 0, // Adjusted to accommodate axis label - right: 100, // Adjusted to accommodate legend - top: 20, - }} - > - - - {jobChartLines} - -
-
-
-
- ); - } + function getLineChart(keysToShow, dataMapping, description) { + var CursorVoronoiContainer = createContainer("voronoi", "cursor"); + var jobChartLineLegendData = keysToShow.map((key) => ({ + childName: getReadableName(key), + name: getReadableName(key), + })); + var jobChartLines = keysToShow.map((key, i) => + getChartLine(dataMapping[key], key), + ); - function getListOfNewProjects(projects, categoryName, color) { - return ( - - {projects.map((project) => ( - - ))} - - ); - } + return ( + + + +
+ `${datum.y}`} + constrainToVisibleArea + labelComponent={ + datum.x} + /> + } + mouseFollowTooltips + voronoiDimension="x" + /> + } + legendData={jobChartLineLegendData} + legendOrientation="vertical" + legendPosition="right" + minDomain={{ y: 0 }} + themeColor={ChartThemeColor.multiUnordered} + padding={{ + bottom: 50, + left: 0, // Adjusted to accommodate axis label + right: 100, // Adjusted to accommodate legend + top: 20, + }} + > + + + {jobChartLines} + +
+
+
+
+ ); + } - function getListOfNewProjectsForJobs(projectsForJobs) { - return ( - <> - {Object.keys(projectsForJobs).map((job) => ( - <>{getListOfNewProjects(projectsForJobs[job], job)} - ))} - - ); - } + function getListOfNewProjects(projects, categoryName, color) { + return ( + + {projects.map((project) => ( + + ))} + + ); + } + function getListOfNewProjectsForJobs(projectsForJobs) { return ( - <> - - Project activity - - - {getLineChart( - ["active_projects"], - data, - "Number of active projects", - )} - {getLineChart( - Object.keys(data.events), - data.events, - "Number of processed events", - )} - - - - - Processed jobs - - - {getLineChart( - Object.keys(data.jobs).filter( - (obj) => obj !== "sync_release_runs", - ), - data.jobs, - "Number of processed jobs", - )} - {getLineChart( - ["sync_release_runs"], - data.jobs, - "Number of synced releases", - )} - - - - - Active projects - - - {getLineChart( - Object.keys(data.jobs_project_count).filter( - (obj) => obj !== "sync_release_runs", - ), - data.jobs_project_count, - "Number of projects with processed jobs of this type", - )} - {getLineChart( - ["sync_release_runs"], - data.jobs_project_count, - "Number of projects with synced releases", - )} - - - - - Onboarded projects + <> + {Object.keys(projectsForJobs).map((job) => ( + <>{getListOfNewProjects(projectsForJobs[job], job)} + ))} + + ); + } + + return ( + <> + + Project activity + + + {getLineChart( + ["active_projects"], + data, + "Number of active projects", + )} + {getLineChart( + Object.keys(data.events), + data.events, + "Number of processed events", + )} + + + + + Processed jobs + + + {getLineChart( + Object.keys(data.jobs).filter( + (obj) => obj !== "sync_release_runs", + ), + data.jobs, + "Number of processed jobs", + )} + {getLineChart( + ["sync_release_runs"], + data.jobs, + "Number of synced releases", + )} + + + + + Active projects + + + {getLineChart( + Object.keys(data.jobs_project_count).filter( + (obj) => obj !== "sync_release_runs", + ), + data.jobs_project_count, + "Number of projects with processed jobs of this type", + )} + {getLineChart( + ["sync_release_runs"], + data.jobs_project_count, + "Number of projects with synced releases", + )} + + + + + Onboarded projects + + + {getLineChart( + Object.keys(data.jobs_project_cumulative_count), + data.jobs_project_cumulative_count, + "Cumulative number of projects with at least a single job run", + )} + {getLineChart( + ["active_projects_cumulative"], + data, + "Active projects", + )} + + + New projects: - - {getLineChart( - Object.keys(data.jobs_project_cumulative_count), - data.jobs_project_cumulative_count, - "Cumulative number of projects with at least a single job run", - )} - {getLineChart( - ["active_projects_cumulative"], - data, - "Active projects", - )} - - - New projects: - - {getListOfNewProjects( - data.onboarded_projects, - "New setup", - )} - {getListOfNewProjectsForJobs( - data.onboarded_projects_per_job, - )} - - - - + {getListOfNewProjects(data.onboarded_projects, "New setup")} + {getListOfNewProjectsForJobs(data.onboarded_projects_per_job)} - - - ); + + + + + + + ); }; export { UsageInterval }; diff --git a/frontend/src/app/Usage/UsageList.tsx b/frontend/src/app/Usage/UsageList.tsx index d90b984c..1b2e1403 100644 --- a/frontend/src/app/Usage/UsageList.tsx +++ b/frontend/src/app/Usage/UsageList.tsx @@ -1,11 +1,11 @@ import { - PageSection, - Card, - CardBody, - CardTitle, - Title, - Flex, - FlexItem, + PageSection, + Card, + CardBody, + CardTitle, + Title, + Flex, + FlexItem, } from "@patternfly/react-core"; import { ChartDonut, ChartBullet } from "@patternfly/react-charts"; @@ -15,313 +15,290 @@ import { useQuery } from "@tanstack/react-query"; import { UsageListData } from "./UsageListData"; const fetchDataByGranularity = (granularity: UsageListProps["what"]) => - fetch(`${import.meta.env.VITE_API_URL}/usage/${granularity}`).then( - (response) => response.json(), - ); + fetch(`${import.meta.env.VITE_API_URL}/usage/${granularity}`).then( + (response) => response.json(), + ); interface UsageListProps { - what: "past-day" | "past-week" | "past-month" | "past-year" | "total"; + what: "past-day" | "past-week" | "past-month" | "past-year" | "total"; } const UsageList: React.FC = (props) => { - const { data, isInitialLoading, isError } = useQuery( - ["usage" + props.what], - () => fetchDataByGranularity(props.what), - { - keepPreviousData: true, - }, - ); + const { data, isInitialLoading, isError } = useQuery( + ["usage" + props.what], + () => fetchDataByGranularity(props.what), + { + keepPreviousData: true, + }, + ); - // If backend API is down - if (isError) { - return ; - } + // If backend API is down + if (isError) { + return ; + } - // Show preloader if waiting for API data - if (isInitialLoading) { - return ; - } + // Show preloader if waiting for API data + if (isInitialLoading) { + return ; + } - if (!data || "error" in data) { - return ( - - - - - Not Found. - - - - - ); - } + if (!data || "error" in data) { + return ( + + + + + Not Found. + + + + + ); + } - type ChartData = [ - top_projects_data: { x: string; y: number }[], - top_projects_legend: { name: string }[], + type ChartData = [ + top_projects_data: { x: string; y: number }[], + top_projects_legend: { name: string }[], + ]; + function getChartData( + top_projects: { [key: string]: number }, + sum_of_all: number, + ): ChartData { + const top_projects_rest = + sum_of_all - + Object.keys(top_projects).reduce((a, v) => (a = a + top_projects[v]), 0); + const top_projects_data = [ + ...Object.keys(top_projects).map((key, i) => ({ + x: `${key.replace("https://", "")}`, + y: top_projects[key], + })), + { x: "other projects", y: top_projects_rest }, ]; - function getChartData( - top_projects: { [key: string]: number }, - sum_of_all: number, - ): ChartData { - const top_projects_rest = - sum_of_all - - Object.keys(top_projects).reduce( - (a, v) => (a = a + top_projects[v]), - 0, - ); - const top_projects_data = [ - ...Object.keys(top_projects).map((key, i) => ({ - x: `${key.replace("https://", "")}`, - y: top_projects[key], - })), - { x: "other projects", y: top_projects_rest }, - ]; - const top_projects_legend = [ - ...Object.keys(top_projects).map((key, i) => ({ - name: `${key.replace("https://", "")}: ${ - top_projects[key] - } (${Math.floor((100 * top_projects[key]) / sum_of_all)}%)`, - })), - { name: `other projects: ${top_projects_rest}` }, - ]; - return [top_projects_data, top_projects_legend]; - } - - type InstanceChartData = [ - instances_to_chart_data: { x: string; y: number }[], - instances_to_chart_legend: { name: string }[], - instances_to_chart_count: number, + const top_projects_legend = [ + ...Object.keys(top_projects).map((key, i) => ({ + name: `${key.replace("https://", "")}: ${ + top_projects[key] + } (${Math.floor((100 * top_projects[key]) / sum_of_all)}%)`, + })), + { name: `other projects: ${top_projects_rest}` }, ]; - function getInstanceChartData(instances: { - [key: string]: number; - }): InstanceChartData { - const instances_to_chart_data = Object.keys(instances).map( - (key, i) => ({ - x: `${key}`, - y: instances[key], - }), - ); - const instances_to_chart_legend = Object.keys(instances).map( - (key, i) => ({ - name: `${key}: ${instances[key]}`, - }), - ); - const instances_to_chart_count = Object.keys(instances).reduce( - (a, v) => (a = a + instances[v]), - 0, - ); - return [ - instances_to_chart_data, - instances_to_chart_legend, - instances_to_chart_count, - ]; - } - - function getProjectChart( - data_and_labels: ChartData, - job_name: string, - total_number: number, - active_project_number: number, - ) { - return ( - - - {job_name} - - - - - `${datum.x}: ${datum.y}` - } - subTitle={job_name} - title={total_number.toString()} - width={200} - padding={{ - bottom: 20, - left: 20, - right: 20, - top: 20, - }} - /> - - - 0 - ? 100 - : 0, - }, - ]} - labels={({ datum }) => `${datum.x}`} - subTitle="Active projects" - title={active_project_number.toString()} - width={200} - padding={{ - bottom: 20, - left: 20, - right: 20, - top: 20, - }} - /> - - - - - - ); - } - function getInstanceChart( - data_and_labels_total_number: InstanceChartData, - job_name: string, - ) { - return ( - - - - `${datum.x}: ${datum.y}`} - legendData={data_and_labels_total_number[1]} - legendOrientation="vertical" - legendPosition="right" - padding={{ - bottom: 20, - left: 20, - right: 250, // Adjusted to accommodate legend - top: 20, - }} - subTitle={job_name} - title={data_and_labels_total_number[2].toString()} - width={424} - /> - - - - ); - } - - function getGoalProgress() { - if (props.what != "total") { - return <>; - } - const goalDescription = - data.jobs.sync_release_runs.active_projects.toString() + - " projects (" + - ( - (data.jobs.sync_release_runs.active_projects / 200) * - 100 - ).toString() + - "%)"; - return ( - - Packit goal for Q4/2023 - - - - - Sync release used by 200 projects by the end of - 2023. - - - - `${datum.name}: ${datum.y}` - } - maxDomain={{ y: 200 }} - name="Chart describing onboarding for release syncing" - primarySegmentedMeasureData={[ - { - name: "Onboarded projects", - y: data.jobs.sync_release_runs - .active_projects, - }, - ]} - qualitativeRangeData={[ - { name: "Q3 state", y: 124 }, - { name: "Q4 goal", y: 200 }, - ]} - height={150} - width={424} - /> - - - - - - ); - } - - function getReadableJobName(job_name: string) { - return job_name - .toLowerCase() - .split(" ") - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join(" "); - } + return [top_projects_data, top_projects_legend]; + } - const active_projects_instance_chart = getInstanceChart( - getInstanceChartData(data.active_projects.instances), - "Active projects", + type InstanceChartData = [ + instances_to_chart_data: { x: string; y: number }[], + instances_to_chart_legend: { name: string }[], + instances_to_chart_count: number, + ]; + function getInstanceChartData(instances: { + [key: string]: number; + }): InstanceChartData { + const instances_to_chart_data = Object.keys(instances).map((key, i) => ({ + x: `${key}`, + y: instances[key], + })); + const instances_to_chart_legend = Object.keys(instances).map((key, i) => ({ + name: `${key}: ${instances[key]}`, + })); + const instances_to_chart_count = Object.keys(instances).reduce( + (a, v) => (a = a + instances[v]), + 0, ); + return [ + instances_to_chart_data, + instances_to_chart_legend, + instances_to_chart_count, + ]; + } - const job_charts = Object.keys(data.jobs).map((key, i) => - getProjectChart( - getChartData( - data.jobs[key].top_projects_by_job_runs, - data.jobs[key].job_runs, - ), - getReadableJobName(key.replaceAll("_", " ")) - .replace(" Groups", "s") - .replace(" Targets", "s") - .replace("Vm", "VM") - .replace("Tft", "TFT") - .replace("Srpm", "SRPM"), - data.jobs[key].job_runs, - data.jobs[key].active_projects, - ), + function getProjectChart( + data_and_labels: ChartData, + job_name: string, + total_number: number, + active_project_number: number, + ) { + return ( + + + {job_name} + + + + `${datum.x}: ${datum.y}`} + subTitle={job_name} + title={total_number.toString()} + width={200} + padding={{ + bottom: 20, + left: 20, + right: 20, + top: 20, + }} + /> + + + 0 ? 100 : 0, + }, + ]} + labels={({ datum }) => `${datum.x}`} + subTitle="Active projects" + title={active_project_number.toString()} + width={200} + padding={{ + bottom: 20, + left: 20, + right: 20, + top: 20, + }} + /> + + + + + + ); + } + function getInstanceChart( + data_and_labels_total_number: InstanceChartData, + job_name: string, + ) { + return ( + + + + `${datum.x}: ${datum.y}`} + legendData={data_and_labels_total_number[1]} + legendOrientation="vertical" + legendPosition="right" + padding={{ + bottom: 20, + left: 20, + right: 250, // Adjusted to accommodate legend + top: 20, + }} + subTitle={job_name} + title={data_and_labels_total_number[2].toString()} + width={424} + /> + + + ); + } + function getGoalProgress() { + if (props.what != "total") { + return <>; + } + const goalDescription = + data.jobs.sync_release_runs.active_projects.toString() + + " projects (" + + ((data.jobs.sync_release_runs.active_projects / 200) * 100).toString() + + "%)"; return ( - <> - - - {active_projects_instance_chart} - - + + Packit goal for Q4/2023 + + - - {job_charts} - + + Sync release used by 200 projects by the end of 2023. + + + `${datum.name}: ${datum.y}`} + maxDomain={{ y: 200 }} + name="Chart describing onboarding for release syncing" + primarySegmentedMeasureData={[ + { + name: "Onboarded projects", + y: data.jobs.sync_release_runs.active_projects, + }, + ]} + qualitativeRangeData={[ + { name: "Q3 state", y: 124 }, + { name: "Q4 goal", y: 200 }, + ]} + height={150} + width={424} + /> + - {getGoalProgress()} - + + + ); + } + + function getReadableJobName(job_name: string) { + return job_name + .toLowerCase() + .split(" ") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); + } + + const active_projects_instance_chart = getInstanceChart( + getInstanceChartData(data.active_projects.instances), + "Active projects", + ); + + const job_charts = Object.keys(data.jobs).map((key, i) => + getProjectChart( + getChartData( + data.jobs[key].top_projects_by_job_runs, + data.jobs[key].job_runs, + ), + getReadableJobName(key.replaceAll("_", " ")) + .replace(" Groups", "s") + .replace(" Targets", "s") + .replace("Vm", "VM") + .replace("Tft", "TFT") + .replace("Srpm", "SRPM"), + data.jobs[key].job_runs, + data.jobs[key].active_projects, + ), + ); + + return ( + <> + + + {active_projects_instance_chart} + + + + + {job_charts} + + + {getGoalProgress()} + + ); }; export { UsageList }; diff --git a/frontend/src/app/Usage/UsageListData.ts b/frontend/src/app/Usage/UsageListData.ts index f8008552..f567e4ad 100644 --- a/frontend/src/app/Usage/UsageListData.ts +++ b/frontend/src/app/Usage/UsageListData.ts @@ -1,67 +1,67 @@ type TopProjectJobRuns = { - job_runs: number; - top_projects_by_job_runs: { - [key: string]: number; - }; + job_runs: number; + top_projects_by_job_runs: { + [key: string]: number; + }; }; type JobBuildGroup = TopProjectJobRuns & { - per_event: { - branch_push: TopProjectJobRuns; - issue: TopProjectJobRuns; - pull_request: TopProjectJobRuns; - release: TopProjectJobRuns; - }; + per_event: { + branch_push: TopProjectJobRuns; + issue: TopProjectJobRuns; + pull_request: TopProjectJobRuns; + release: TopProjectJobRuns; + }; }; export interface UsageListData { - all_projects: { - project_count: number; - instances: { - [key: string]: number; - }; + all_projects: { + project_count: number; + instances: { + [key: string]: number; + }; + }; + active_projects: { + project_count: number; + instances: { + [key: string]: number; + }; + top_projects_by_events_handled: { + [key: string]: number; }; - active_projects: { - project_count: number; - instances: { - [key: string]: number; - }; - top_projects_by_events_handled: { - [key: string]: number; - }; + }; + events: { + branch_push: { + events_handled: number; + top_projects: { + [key: string]: number; + }; }; - events: { - branch_push: { - events_handled: number; - top_projects: { - [key: string]: number; - }; - }; - issue: { - events_handled: number; - top_projects: { - [key: string]: number; - }; - }; - pull_request: { - events_handled: number; - top_projects: { - [key: string]: number; - }; - }; - release: { - events_handled: number; - top_projects: { - [key: string]: number; - }; - }; + issue: { + events_handled: number; + top_projects: { + [key: string]: number; + }; }; - jobs: { - copr_build_groups: JobBuildGroup; - koji_build_groups: JobBuildGroup; - srpm_builds: JobBuildGroup; - sync_release_runs: JobBuildGroup; - tft_test_run_groups: JobBuildGroup; - vm_image_build_targets: JobBuildGroup; - [key: string]: JobBuildGroup; + pull_request: { + events_handled: number; + top_projects: { + [key: string]: number; + }; + }; + release: { + events_handled: number; + top_projects: { + [key: string]: number; + }; }; + }; + jobs: { + copr_build_groups: JobBuildGroup; + koji_build_groups: JobBuildGroup; + srpm_builds: JobBuildGroup; + sync_release_runs: JobBuildGroup; + tft_test_run_groups: JobBuildGroup; + vm_image_build_targets: JobBuildGroup; + [key: string]: JobBuildGroup; + }; } diff --git a/frontend/src/app/routes.tsx b/frontend/src/app/routes.tsx index 388c69c3..3ffc2fa0 100644 --- a/frontend/src/app/routes.tsx +++ b/frontend/src/app/routes.tsx @@ -25,157 +25,153 @@ import { ErrorApp } from "./Errors/ErrorApp"; // App routes // Those with labels indicate they should be in sidebar const routes: RouteObject[] = [ - { - Component: AppLayout, + { + Component: AppLayout, + path: "/", + errorElement: , + children: [ + { + Component: Dashboard, + index: true, path: "/", - errorElement: , + handle: { + label: "Home", + }, + }, + { + element: , + path: "/projects", + handle: { + divider: true, + label: "Projects", + category: "Dashboards", + }, + }, + { + Component: Jobs, + id: "jobs", + path: "/jobs", + handle: { + category: "Dashboards", + label: "Jobs", + }, children: [ - { - Component: Dashboard, - index: true, - path: "/", - handle: { - label: "Home", - }, + { + element: , + index: true, + id: "copr-builds", + path: "copr-builds", + handle: { + label: "Copr Builds", }, - { - element: , - path: "/projects", - handle: { - divider: true, - label: "Projects", - category: "Dashboards", - }, + }, + { + element: , + id: "koji-builds", + path: "koji-builds", + handle: { + label: "Upstream Koji Builds", }, - { - Component: Jobs, - id: "jobs", - path: "/jobs", - handle: { - category: "Dashboards", - label: "Jobs", - }, - children: [ - { - element: , - index: true, - id: "copr-builds", - path: "copr-builds", - handle: { - label: "Copr Builds", - }, - }, - { - element: , - id: "koji-builds", - path: "koji-builds", - handle: { - label: "Upstream Koji Builds", - }, - }, - { - element: , - id: "srpm-builds", - path: "srpm-builds", - handle: { - label: "SRPM Builds", - }, - }, - { - element: , - id: "testing-farm-runs", - path: "testing-farm-runs", - handle: { - label: "Testing Farm Runs", - }, - }, - { - element: , - id: "propose-downstreams", - path: "propose-downstreams", - handle: { - label: "Propose Downstreams", - }, - }, - { - element: , - id: "pull-from-upstreams", - path: "pull-from-upstreams", - handle: { - label: "Pull From Upstreams", - }, - }, - { - element: , - id: "downstream-koji-builds", - path: "downstream-koji-builds", - handle: { - label: "Downstream (production) Koji builds", - }, - }, - ], + }, + { + element: , + id: "srpm-builds", + path: "srpm-builds", + handle: { + label: "SRPM Builds", }, - { - element: , - path: "/pipelines", - handle: { - category: "Dashboards", - label: "Pipelines", - }, + }, + { + element: , + id: "testing-farm-runs", + path: "testing-farm-runs", + handle: { + label: "Testing Farm Runs", }, - { - path: "/projects/:forge", - element: , + }, + { + element: , + id: "propose-downstreams", + path: "propose-downstreams", + handle: { + label: "Propose Downstreams", }, - { - path: "/projects/:forge/:namespace", - element: , + }, + { + element: , + id: "pull-from-upstreams", + path: "pull-from-upstreams", + handle: { + label: "Pull From Upstreams", }, - { - path: "/projects/:forge/:namespace/:repoName", - element: , - }, - { - path: "/results/srpm-builds/:id", - element: , - }, - { - path: "/results/copr-builds/:id", - element: , - }, - { - path: "/results/koji-builds/:id", - element: , - }, - { - path: "/results/testing-farm/:id", - element: , - }, - { - path: "/results/propose-downstream/:id", - element: ( - - ), - }, - { - path: "/results/pull-from-upstream/:id", - element: ( - - ), - }, - { - element: , - path: "/usage", - handle: { - label: "Usage", - }, - }, - { - element: , - path: "*", + }, + { + element: , + id: "downstream-koji-builds", + path: "downstream-koji-builds", + handle: { + label: "Downstream (production) Koji builds", }, + }, ], - }, + }, + { + element: , + path: "/pipelines", + handle: { + category: "Dashboards", + label: "Pipelines", + }, + }, + { + path: "/projects/:forge", + element: , + }, + { + path: "/projects/:forge/:namespace", + element: , + }, + { + path: "/projects/:forge/:namespace/:repoName", + element: , + }, + { + path: "/results/srpm-builds/:id", + element: , + }, + { + path: "/results/copr-builds/:id", + element: , + }, + { + path: "/results/koji-builds/:id", + element: , + }, + { + path: "/results/testing-farm/:id", + element: , + }, + { + path: "/results/propose-downstream/:id", + element: , + }, + { + path: "/results/pull-from-upstream/:id", + element: , + }, + { + element: , + path: "/usage", + handle: { + label: "Usage", + }, + }, + { + element: , + path: "*", + }, + ], + }, ]; export { routes }; diff --git a/frontend/src/app/utils/LabelLink.tsx b/frontend/src/app/utils/LabelLink.tsx index 7e3ccce8..f4a02433 100644 --- a/frontend/src/app/utils/LabelLink.tsx +++ b/frontend/src/app/utils/LabelLink.tsx @@ -3,24 +3,24 @@ import React from "react"; import { Link } from "react-router-dom"; export interface LabelLinkProps { - to: string; - children?: React.ReactNode; - className?: string; + to: string; + children?: React.ReactNode; + className?: string; } export const LabelLink: React.FC = ({ - to, - className = "", - children = null, + to, + className = "", + children = null, }) => ( - + ); diff --git a/frontend/src/app/utils/SHACopy.tsx b/frontend/src/app/utils/SHACopy.tsx index edc1e21a..9126e9cb 100644 --- a/frontend/src/app/utils/SHACopy.tsx +++ b/frontend/src/app/utils/SHACopy.tsx @@ -3,37 +3,37 @@ import React from "react"; import { getCommitLink } from "./forgeUrls"; export interface SHACopyInterface { - git_repo: string; - commit_sha: string; + git_repo: string; + commit_sha: string; } export const SHACopy: React.FC = ({ - git_repo, - commit_sha, + git_repo, + commit_sha, }) => { - if (!git_repo || !commit_sha) { - return <>; - } - const onCopyHash = () => { - navigator.clipboard.writeText(commit_sha); - }; + if (!git_repo || !commit_sha) { + return <>; + } + const onCopyHash = () => { + navigator.clipboard.writeText(commit_sha); + }; - return ( - - - {commit_sha.substring(0, 7)} - - - ); + return ( + + + {commit_sha.substring(0, 7)} + + + ); }; diff --git a/frontend/src/app/utils/Timestamp.tsx b/frontend/src/app/utils/Timestamp.tsx index 9074e1c7..c2b1f554 100644 --- a/frontend/src/app/utils/Timestamp.tsx +++ b/frontend/src/app/utils/Timestamp.tsx @@ -7,79 +7,79 @@ import React from "react"; * https://stackoverflow.com/questions/1551382/user-friendly-time-format-in-python */ const prettyTimeDifference = (difference: number) => { - let secondDiff = difference; - let dayDiff = Math.floor(difference / (60 * 60 * 24)); + let secondDiff = difference; + let dayDiff = Math.floor(difference / (60 * 60 * 24)); - // we set this to null further down the code to indicate estimates like "just now" and "a minute ago" - let numericPart: number | null = dayDiff / 365; - let units = " years ago"; + // we set this to null further down the code to indicate estimates like "just now" and "a minute ago" + let numericPart: number | null = dayDiff / 365; + let units = " years ago"; - if (dayDiff === 0) { - [numericPart, units] = [secondDiff / 3600, " hours ago"]; + if (dayDiff === 0) { + [numericPart, units] = [secondDiff / 3600, " hours ago"]; - if (secondDiff < 10) { - [numericPart, units] = [null, "just now"]; - } else if (secondDiff < 60) { - [numericPart, units] = [secondDiff, " seconds ago"]; - } else if (secondDiff < 120) { - [numericPart, units] = [null, "a minute ago"]; - } else if (secondDiff < 3600) { - [numericPart, units] = [secondDiff / 60, " minutes ago"]; - } else if (secondDiff < 7200) { - [numericPart, units] = [null, "an hour ago"]; - } - } else if (dayDiff === 1) { - [numericPart, units] = [null, "yesterday"]; - } else if (dayDiff < 7) { - [numericPart, units] = [dayDiff, " days ago"]; - } else if (dayDiff < 31) { - [numericPart, units] = [dayDiff / 7, " weeks ago"]; - } else if (dayDiff < 365) { - [numericPart, units] = [dayDiff / 30, " months ago"]; + if (secondDiff < 10) { + [numericPart, units] = [null, "just now"]; + } else if (secondDiff < 60) { + [numericPart, units] = [secondDiff, " seconds ago"]; + } else if (secondDiff < 120) { + [numericPart, units] = [null, "a minute ago"]; + } else if (secondDiff < 3600) { + [numericPart, units] = [secondDiff / 60, " minutes ago"]; + } else if (secondDiff < 7200) { + [numericPart, units] = [null, "an hour ago"]; } + } else if (dayDiff === 1) { + [numericPart, units] = [null, "yesterday"]; + } else if (dayDiff < 7) { + [numericPart, units] = [dayDiff, " days ago"]; + } else if (dayDiff < 31) { + [numericPart, units] = [dayDiff / 7, " weeks ago"]; + } else if (dayDiff < 365) { + [numericPart, units] = [dayDiff / 30, " months ago"]; + } - return `${numericPart ? Math.floor(numericPart) : ""}${units}`; + return `${numericPart ? Math.floor(numericPart) : ""}${units}`; }; const getPrettyTime = (date: number | Date | null) => { - if (date === null) { - return null; - } + if (date === null) { + return null; + } - let now = Date.now(); - let diff = Math.floor( - (now - (date instanceof Date ? date.getTime() : date)) / 1000, - ); + let now = Date.now(); + let diff = Math.floor( + (now - (date instanceof Date ? date.getTime() : date)) / 1000, + ); - return prettyTimeDifference(diff); + return prettyTimeDifference(diff); }; interface TimestampProps { - stamp: number | null; - verbose?: boolean; + stamp: number | null; + verbose?: boolean; } const Timestamp: React.FC = (props) => { - if (props.stamp === null) { - return not provided; - } + if (props.stamp === null) { + return not provided; + } - // Convert UNIX timestamp into date object, Date gets passed **milliseconds** - // since Epoch. - const time = new Date(1000 * props.stamp); + // Convert UNIX timestamp into date object, Date gets passed **milliseconds** + // since Epoch. + const time = new Date(1000 * props.stamp); - let prettyTime = getPrettyTime(time); - let verboseTime = time.toLocaleString(); + let prettyTime = getPrettyTime(time); + let verboseTime = time.toLocaleString(); - if (props.verbose) { - [prettyTime, verboseTime] = [verboseTime, prettyTime ?? ""]; - } + if (props.verbose) { + [prettyTime, verboseTime] = [verboseTime, prettyTime ?? ""]; + } - return ( - - {prettyTime} - - ); + return ( + + {prettyTime} + + ); }; export { Timestamp }; diff --git a/frontend/src/app/utils/forgeUrls.tsx b/frontend/src/app/utils/forgeUrls.tsx index 53386aca..49698dcd 100644 --- a/frontend/src/app/utils/forgeUrls.tsx +++ b/frontend/src/app/utils/forgeUrls.tsx @@ -1,89 +1,89 @@ // getHostName - returns the hostname if possible, otherwise an empty string function getHostName(url: string | URL) { - let hostname = ""; - try { - hostname = new URL(url).hostname; - } catch (error) { - console.log("Invalid URL"); - console.error(error); - } - return hostname; + let hostname = ""; + try { + hostname = new URL(url).hostname; + } catch (error) { + console.log("Invalid URL"); + console.error(error); + } + return hostname; } // getPRLink - returns the PR link if possible otherwise an empty string function getPRLink(gitRepo: string, prID: number) { - const forge = getHostName(gitRepo); - switch (forge) { - case "github.com": - return `${gitRepo}/pull/${prID}`; - case "src.fedoraproject.org": - case "pagure.io": - return `${gitRepo}/pull-request/${prID}`; - default: // various Gitlab instances - return `${gitRepo}/-/merge_requests/${prID}`; - } + const forge = getHostName(gitRepo); + switch (forge) { + case "github.com": + return `${gitRepo}/pull/${prID}`; + case "src.fedoraproject.org": + case "pagure.io": + return `${gitRepo}/pull-request/${prID}`; + default: // various Gitlab instances + return `${gitRepo}/-/merge_requests/${prID}`; + } } // getBranchLink - returns the branch link if possible otherwise an empty string function getBranchLink(gitRepo: string, branchName: string) { - const forge = getHostName(gitRepo); - switch (forge) { - case "github.com": - return `${gitRepo}/tree/${branchName}`; - case "src.fedoraproject.org": - case "pagure.io": - return `${gitRepo}/tree/${branchName}`; - default: // various Gitlab instances - return `${gitRepo}/-/tree/${branchName}`; - } + const forge = getHostName(gitRepo); + switch (forge) { + case "github.com": + return `${gitRepo}/tree/${branchName}`; + case "src.fedoraproject.org": + case "pagure.io": + return `${gitRepo}/tree/${branchName}`; + default: // various Gitlab instances + return `${gitRepo}/-/tree/${branchName}`; + } } // getIssueLink - returns the issue link if possible otherwise an empty string function getIssueLink(gitRepo: string, issueID: number) { - const forge = getHostName(gitRepo); - switch (forge) { - case "github.com": - return `${gitRepo}/issues/${issueID}`; - case "src.fedoraproject.org": - case "pagure.io": - return `${gitRepo}/issue/${issueID}`; - default: // various Gitlab instances - return `${gitRepo}/issues/-/${issueID}`; - } + const forge = getHostName(gitRepo); + switch (forge) { + case "github.com": + return `${gitRepo}/issues/${issueID}`; + case "src.fedoraproject.org": + case "pagure.io": + return `${gitRepo}/issue/${issueID}`; + default: // various Gitlab instances + return `${gitRepo}/issues/-/${issueID}`; + } } // getReleaseLink - returns the link to release if possible otherwise an empty string function getReleaseLink(gitRepo: string, release: string) { - const forge = getHostName(gitRepo); - switch (forge) { - case "github.com": - return `${gitRepo}/releases/tag/${release}`; - case "gitlab.com": - return `${gitRepo}/-/tags/${release}`; - default: // various Gitlab instances - return `${gitRepo}/-/tags/${release}`; - } + const forge = getHostName(gitRepo); + switch (forge) { + case "github.com": + return `${gitRepo}/releases/tag/${release}`; + case "gitlab.com": + return `${gitRepo}/-/tags/${release}`; + default: // various Gitlab instances + return `${gitRepo}/-/tags/${release}`; + } } // getCommitLink - returns a link to the commit function getCommitLink(gitRepo: string, commit_hash: string) { - const forge = getHostName(gitRepo); - switch (forge) { - case "github.com": - return `${gitRepo}/commit/${commit_hash}`; - case "src.fedoraproject.org": - case "pagure.io": - return `${gitRepo}/c/${commit_hash}`; - default: // various Gitlab instances - return `${gitRepo}/-/commit/${commit_hash}`; - } + const forge = getHostName(gitRepo); + switch (forge) { + case "github.com": + return `${gitRepo}/commit/${commit_hash}`; + case "src.fedoraproject.org": + case "pagure.io": + return `${gitRepo}/c/${commit_hash}`; + default: // various Gitlab instances + return `${gitRepo}/-/commit/${commit_hash}`; + } } export { - getHostName, - getPRLink, - getBranchLink, - getIssueLink, - getReleaseLink, - getCommitLink, + getHostName, + getPRLink, + getBranchLink, + getIssueLink, + getReleaseLink, + getCommitLink, }; diff --git a/frontend/src/app/utils/storybook/withQueryClient.jsx b/frontend/src/app/utils/storybook/withQueryClient.jsx index ef4f8255..3af67f98 100644 --- a/frontend/src/app/utils/storybook/withQueryClient.jsx +++ b/frontend/src/app/utils/storybook/withQueryClient.jsx @@ -2,30 +2,30 @@ import React from "react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; const TestQueryClient = new QueryClient({ - logger: { - log: console.log, - warn: console.warn, - error: () => {}, - }, - defaultOptions: { - queries: { - retry: false, - refetchOnWindowFocus: false, - queryFn: async ({ queryKey: [url] }) => { - if (typeof url !== "string") { - throw new Error("Invalid QueryKey"); - } - return fetch(url).then((response) => response.text()); - }, - cacheTime: 0, - }, + logger: { + log: console.log, + warn: console.warn, + error: () => {}, + }, + defaultOptions: { + queries: { + retry: false, + refetchOnWindowFocus: false, + queryFn: async ({ queryKey: [url] }) => { + if (typeof url !== "string") { + throw new Error("Invalid QueryKey"); + } + return fetch(url).then((response) => response.text()); + }, + cacheTime: 0, }, + }, }); export const withQueryClient = (Story) => { - return ( - - - - ); + return ( + + + + ); }; diff --git a/frontend/src/app/utils/useTitle.tsx b/frontend/src/app/utils/useTitle.tsx index 20c448d8..1ccb0ffc 100644 --- a/frontend/src/app/utils/useTitle.tsx +++ b/frontend/src/app/utils/useTitle.tsx @@ -1,3 +1,3 @@ export function useTitle(title: string) { - document.title = "Packit Service" + (title ? ` - ${title}` : ""); + document.title = "Packit Service" + (title ? ` - ${title}` : ""); } diff --git a/frontend/src/global.d.ts b/frontend/src/global.d.ts index 2c6dfe15..6a97bec0 100644 --- a/frontend/src/global.d.ts +++ b/frontend/src/global.d.ts @@ -1,10 +1,10 @@ /// interface ImportMetaEnv { - readonly VITE_API_URL: string?; - readonly VITE_GIT_SHA: string?; - // more env variables... + readonly VITE_API_URL: string?; + readonly VITE_GIT_SHA: string?; + // more env variables... } interface ImportMeta { - readonly env: ImportMetaEnv; + readonly env: ImportMetaEnv; } diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index ec2e1f03..1f953fa4 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -4,7 +4,7 @@ import { App } from "./App"; const root = createRoot(document.getElementById("root") as HTMLElement); root.render( - - - , + + + , ); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 462051a3..44ba5f40 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -3,13 +3,13 @@ import react from "@vitejs/plugin-react"; // https://vitejs.dev/config/ export default defineConfig(() => ({ - plugins: [react()], - esbuild: { - loader: "tsx", - include: /src\/.*\.[tj]sx?$/, - exclude: [], - }, - server: { - open: true, - }, + plugins: [react()], + esbuild: { + loader: "tsx", + include: /src\/.*\.[tj]sx?$/, + exclude: [], + }, + server: { + open: true, + }, }));