diff --git a/frontend/src/apiDefinitions.ts b/frontend/src/apiDefinitions.ts index 62a9956b..e1a3e759 100644 --- a/frontend/src/apiDefinitions.ts +++ b/frontend/src/apiDefinitions.ts @@ -180,6 +180,51 @@ export interface KojiBuild { web_url: string; } +// /api/koji-tag-requests +export interface KojiTagRequestGroup { + branch_name: string | null; + tag_request_submitted_time: number; + chroot: string; + sidetag: string; + nvr: string; + packit_id: number; + // TODO: @Venefilyn - 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; + project_url: string; + release: string | null; + repo_name: string; + repo_namespace: string; + task_id: string | null; + web_url: string; +} + +// /api/koji-tag-requests/$id +export interface KojiTagRequest { + anitya_package: string | null; + anitya_project_id: number | null; + anitya_project_name: string | null; + anitya_version: string | null; + branch_name: string | null; + tag_request_submitted_time: number; + chroot: string; + sidetag: string; + nvr: string; + commit_sha: string; + issue_id: number | null; + non_git_upstream: boolean; + // TODO: @Venefilyn - 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; + project_url: string; + release: string | null; + repo_name: string; + repo_namespace: string; + run_ids: number[]; + task_id: string; + web_url: string; +} + // /api/srpm-builds export interface SRPMBuildGroup { branch_name: string | null; diff --git a/frontend/src/components/jobs/Jobs.tsx b/frontend/src/components/jobs/Jobs.tsx index 1404317e..46525a8a 100644 --- a/frontend/src/components/jobs/Jobs.tsx +++ b/frontend/src/components/jobs/Jobs.tsx @@ -64,6 +64,11 @@ const Jobs = () => { OpenScanHub + + + Koji Tagging Requests + + diff --git a/frontend/src/components/koji/KojiTagRequest.tsx b/frontend/src/components/koji/KojiTagRequest.tsx new file mode 100644 index 00000000..5b8ad29d --- /dev/null +++ b/frontend/src/components/koji/KojiTagRequest.tsx @@ -0,0 +1,133 @@ +// Copyright Contributors to the Packit project. +// SPDX-License-Identifier: MIT + +import { + Card, + CardBody, + Content, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + Label, + PageSection, + Title, +} from "@patternfly/react-core"; +import { Table, Tbody, Td, Tr } from "@patternfly/react-table"; +import { useQuery } from "@tanstack/react-query"; +import { Link } from "@tanstack/react-router"; +import { kojiTagRequestQueryOptions } from "../../queries/koji/kojiTagRequestQuery"; +import { Route as KojiRoute } from "../../routes/jobs_/koji-tag-request.$id"; +import { ErrorConnection } from "../errors/ErrorConnection"; +import { LabelLink } from "../shared/LabelLink"; +import { Preloader } from "../shared/Preloader"; +import { SHACopy } from "../shared/SHACopy"; +import { Timestamp } from "../shared/Timestamp"; +import { StatusLabel } from "../statusLabels/StatusLabel"; +import { TriggerLink, TriggerSuffix } from "../trigger/TriggerLink"; + +export const KojiTagRequest = () => { + const { id } = KojiRoute.useParams(); + const { data, isLoading, isError } = useQuery( + kojiTagRequestQueryOptions({ id }), + ); + + // If backend API is down + if (isError) { + return ; + } + + // Show preloader if waiting for API data + if (isLoading || data === undefined) { + return ; + } + + if ("error" in data) { + return ( + + + + + Not Found. + + + + + ); + } + return ( + <> + + + Koji Tagging Request Results + + + + + + +
+
+
+ + + + + + + Koji Tagging Request + + + + Sidetag + + + + {data.sidetag} + + + + NVR + + + + {data.nvr} + + + + + + + Koji tagging request submitted + + + {} + + + + + + + + ); +}; diff --git a/frontend/src/components/koji/KojiTagRequestsTable.tsx b/frontend/src/components/koji/KojiTagRequestsTable.tsx new file mode 100644 index 00000000..8110c86a --- /dev/null +++ b/frontend/src/components/koji/KojiTagRequestsTable.tsx @@ -0,0 +1,155 @@ +// Copyright Contributors to the Packit project. +// SPDX-License-Identifier: MIT + +import React, { useMemo } from "react"; + +import { + Table, + TableVariant, + Tbody, + Td, + Th, + Thead, + Tr, +} from "@patternfly/react-table"; + +import { SkeletonTable } from "@patternfly/react-component-groups"; +import { useInfiniteQuery } from "@tanstack/react-query"; +import { Timestamp } from "../../components/shared/Timestamp"; +import { + TriggerLink, + TriggerSuffix, +} from "../../components/trigger/TriggerLink"; +import { kojiTagRequestsQueryOptions } from "../../queries/koji/kojiTagRequestsQuery"; +import { ForgeIcon } from "../icons/ForgeIcon"; +import { LoadMore } from "../shared/LoadMore"; +import { StatusLabel } from "../statusLabels/StatusLabel"; + +export const KojiTagRequestsTable = () => { + // Headings + const columnNames = { + forge: "Forge", + trigger: "Trigger", + target: "Target", + sidetag: "Sidetag", + nvr: "NVR", + timeSubmitted: "Time Submitted", + kojiTagRequestTask: "Koji Tagging Request Task", + }; + + const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } = + useInfiniteQuery(kojiTagRequestsQueryOptions()); + + // 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]); + + const TableHeads = [ + + {columnNames.forge} + , + + {columnNames.trigger} + , + + {columnNames.target} + , + + {columnNames.sidetag} + , + + {columnNames.nvr} + , + + {columnNames.timeSubmitted} + , + + {columnNames.kojiTagRequestTask} + , + ]; + + if (isLoading) { + return ( + + ); + } + + return ( + <> + + + {TableHeads} + + + {rows.map((koji_tag_request) => ( + + + + + + + + + + ))} + +
+ + + + + + + + + + + + + {koji_tag_request.sidetag} + + + + + + {koji_tag_request.nvr} + + + + + + + + {koji_tag_request.task_id} + + +
+ void fetchNextPage()} + /> + + ); +}; diff --git a/frontend/src/queries/koji/kojiTagRequest.ts b/frontend/src/queries/koji/kojiTagRequest.ts new file mode 100644 index 00000000..71039a90 --- /dev/null +++ b/frontend/src/queries/koji/kojiTagRequest.ts @@ -0,0 +1,28 @@ +// Copyright Contributors to the Packit project. +// SPDX-License-Identifier: MIT + +import { KojiTagRequest } from "../../apiDefinitions"; + +export interface fetchKojiTagRequestProps { + id: string; + signal?: AbortSignal; +} + +// Fetch data from dashboard backend (or if we want, directly from the API) +export const fetchKojiTagRequest = async ({ + id, + signal, +}: fetchKojiTagRequestProps): Promise => { + const data = await fetch( + `${import.meta.env.VITE_API_URL}/koji-tag-requests/${id}`, + { signal }, + ) + .then((response) => response.json()) + .catch((err) => { + if (err.status === 404) { + throw new Error(`Koji tagging request ${id} not found!`); + } + throw err; + }); + return data; +}; diff --git a/frontend/src/queries/koji/kojiTagRequestQuery.ts b/frontend/src/queries/koji/kojiTagRequestQuery.ts new file mode 100644 index 00000000..f9ce655b --- /dev/null +++ b/frontend/src/queries/koji/kojiTagRequestQuery.ts @@ -0,0 +1,17 @@ +// Copyright Contributors to the Packit project. +// SPDX-License-Identifier: MIT + +import { queryOptions } from "@tanstack/react-query"; +import { + fetchKojiTagRequest, + fetchKojiTagRequestProps, +} from "./kojiTagRequest"; + +type QueryParameters = Omit; + +export const kojiTagRequestQueryOptions = (params: QueryParameters) => + queryOptions({ + queryKey: ["koji", params], + queryFn: async ({ signal }) => + await fetchKojiTagRequest({ signal, ...params }), + }); diff --git a/frontend/src/queries/koji/kojiTagRequests.ts b/frontend/src/queries/koji/kojiTagRequests.ts new file mode 100644 index 00000000..4c3c7d10 --- /dev/null +++ b/frontend/src/queries/koji/kojiTagRequests.ts @@ -0,0 +1,28 @@ +// Copyright Contributors to the Packit project. +// SPDX-License-Identifier: MIT + +import { KojiTagRequestGroup } from "../../apiDefinitions"; + +export interface fetchKojiTagRequestsProps { + pageParam: number; + signal?: AbortSignal; +} + +// Fetch data from dashboard backend (or if we want, directly from the API) +export const fetchKojiTagRequests = async ({ + pageParam = 1, + signal, +}: fetchKojiTagRequestsProps): Promise => { + const data = await fetch( + `${import.meta.env.VITE_API_URL}/koji-tag-requests?page=${pageParam}`, + { signal }, + ) + .then((response) => response.json()) + .catch((err) => { + if (err.status === 404) { + throw new Error(`Koji tag requests not found!`); + } + throw err; + }); + return data; +}; diff --git a/frontend/src/queries/koji/kojiTagRequestsQuery.ts b/frontend/src/queries/koji/kojiTagRequestsQuery.ts new file mode 100644 index 00000000..863c296e --- /dev/null +++ b/frontend/src/queries/koji/kojiTagRequestsQuery.ts @@ -0,0 +1,25 @@ +// Copyright Contributors to the Packit project. +// SPDX-License-Identifier: MIT + +import { infiniteQueryOptions } from "@tanstack/react-query"; +import { fetchKojiTagRequests } from "./kojiTagRequests"; + +export const kojiTagRequestsQueryOptions = () => + infiniteQueryOptions({ + queryKey: ["koji_tag"], + queryFn: async ({ pageParam, signal }) => + await fetchKojiTagRequests({ pageParam, signal }), + initialPageParam: 1, + getNextPageParam: (lastPage, _allPages, lastPageParam) => { + if (lastPage.length === 0) { + return undefined; + } + return lastPageParam + 1; + }, + getPreviousPageParam: (_firstPage, _allPages, firstPageParam) => { + if (firstPageParam <= 1) { + return undefined; + } + return firstPageParam - 1; + }, + }); diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index db79b41d..4bc66c47 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -31,6 +31,7 @@ import { Route as JobsPullFromUpstreamImport } from './routes/jobs/pull-from-ups import { Route as JobsProposeDownstreamsImport } from './routes/jobs/propose-downstreams' import { Route as JobsProposeDownstreamImport } from './routes/jobs/propose-downstream' import { Route as JobsOpenscanhubImport } from './routes/jobs/openscanhub' +import { Route as JobsKojiTagRequestsImport } from './routes/jobs/koji-tag-requests' import { Route as JobsKojiDownstreamImport } from './routes/jobs/koji-downstream' import { Route as JobsKojiBuildsImport } from './routes/jobs/koji-builds' import { Route as JobsKojiImport } from './routes/jobs/koji' @@ -45,6 +46,7 @@ import { Route as JobsPullFromUpstreamIdImport } from './routes/jobs_/pull-from- import { Route as JobsProposeDownstreamIdImport } from './routes/jobs_/propose-downstream.$id' import { Route as JobsOpenscanhubIdImport } from './routes/jobs_/openscanhub.$id' import { Route as JobsKojiIdImport } from './routes/jobs_/koji.$id' +import { Route as JobsKojiTagRequestIdImport } from './routes/jobs_/koji-tag-request.$id' import { Route as JobsKojiDownstreamIdImport } from './routes/jobs_/koji-downstream.$id' import { Route as JobsCoprIdImport } from './routes/jobs_/copr.$id' import { Route as JobsBodhiIdImport } from './routes/jobs_/bodhi.$id' @@ -158,6 +160,11 @@ const JobsOpenscanhubRoute = JobsOpenscanhubImport.update({ getParentRoute: () => JobsRoute, } as any) +const JobsKojiTagRequestsRoute = JobsKojiTagRequestsImport.update({ + path: '/koji-tag-requests', + getParentRoute: () => JobsRoute, +} as any) + const JobsKojiDownstreamRoute = JobsKojiDownstreamImport.update({ path: '/koji-downstream', getParentRoute: () => JobsRoute, @@ -237,6 +244,11 @@ const JobsKojiIdRoute = JobsKojiIdImport.update({ getParentRoute: () => rootRoute, } as any) +const JobsKojiTagRequestIdRoute = JobsKojiTagRequestIdImport.update({ + path: '/jobs/koji-tag-request/$id', + getParentRoute: () => rootRoute, +} as any) + const JobsKojiDownstreamIdRoute = JobsKojiDownstreamIdImport.update({ path: '/jobs/koji-downstream/$id', getParentRoute: () => rootRoute, @@ -363,6 +375,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof JobsKojiDownstreamImport parentRoute: typeof JobsImport } + '/jobs/koji-tag-requests': { + id: '/jobs/koji-tag-requests' + path: '/koji-tag-requests' + fullPath: '/jobs/koji-tag-requests' + preLoaderRoute: typeof JobsKojiTagRequestsImport + parentRoute: typeof JobsImport + } '/jobs/openscanhub': { id: '/jobs/openscanhub' path: '/openscanhub' @@ -475,6 +494,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof JobsKojiDownstreamIdImport parentRoute: typeof rootRoute } + '/jobs/koji-tag-request/$id': { + id: '/jobs/koji-tag-request/$id' + path: '/jobs/koji-tag-request/$id' + fullPath: '/jobs/koji-tag-request/$id' + preLoaderRoute: typeof JobsKojiTagRequestIdImport + parentRoute: typeof rootRoute + } '/jobs/koji/$id': { id: '/jobs/koji/$id' path: '/jobs/koji/$id' @@ -545,6 +571,7 @@ interface JobsRouteChildren { JobsKojiRoute: typeof JobsKojiRoute JobsKojiBuildsRoute: typeof JobsKojiBuildsRoute JobsKojiDownstreamRoute: typeof JobsKojiDownstreamRoute + JobsKojiTagRequestsRoute: typeof JobsKojiTagRequestsRoute JobsOpenscanhubRoute: typeof JobsOpenscanhubRoute JobsProposeDownstreamRoute: typeof JobsProposeDownstreamRoute JobsProposeDownstreamsRoute: typeof JobsProposeDownstreamsRoute @@ -566,6 +593,7 @@ const JobsRouteChildren: JobsRouteChildren = { JobsKojiRoute: JobsKojiRoute, JobsKojiBuildsRoute: JobsKojiBuildsRoute, JobsKojiDownstreamRoute: JobsKojiDownstreamRoute, + JobsKojiTagRequestsRoute: JobsKojiTagRequestsRoute, JobsOpenscanhubRoute: JobsOpenscanhubRoute, JobsProposeDownstreamRoute: JobsProposeDownstreamRoute, JobsProposeDownstreamsRoute: JobsProposeDownstreamsRoute, @@ -595,6 +623,7 @@ export interface FileRoutesByFullPath { '/jobs/koji': typeof JobsKojiRoute '/jobs/koji-builds': typeof JobsKojiBuildsRoute '/jobs/koji-downstream': typeof JobsKojiDownstreamRoute + '/jobs/koji-tag-requests': typeof JobsKojiTagRequestsRoute '/jobs/openscanhub': typeof JobsOpenscanhubRoute '/jobs/propose-downstream': typeof JobsProposeDownstreamRoute '/jobs/propose-downstreams': typeof JobsProposeDownstreamsRoute @@ -611,6 +640,7 @@ export interface FileRoutesByFullPath { '/jobs/bodhi/$id': typeof JobsBodhiIdRoute '/jobs/copr/$id': typeof JobsCoprIdRoute '/jobs/koji-downstream/$id': typeof JobsKojiDownstreamIdRoute + '/jobs/koji-tag-request/$id': typeof JobsKojiTagRequestIdRoute '/jobs/koji/$id': typeof JobsKojiIdRoute '/jobs/openscanhub/$id': typeof JobsOpenscanhubIdRoute '/jobs/propose-downstream/$id': typeof JobsProposeDownstreamIdRoute @@ -635,6 +665,7 @@ export interface FileRoutesByTo { '/jobs/koji': typeof JobsKojiRoute '/jobs/koji-builds': typeof JobsKojiBuildsRoute '/jobs/koji-downstream': typeof JobsKojiDownstreamRoute + '/jobs/koji-tag-requests': typeof JobsKojiTagRequestsRoute '/jobs/openscanhub': typeof JobsOpenscanhubRoute '/jobs/propose-downstream': typeof JobsProposeDownstreamRoute '/jobs/propose-downstreams': typeof JobsProposeDownstreamsRoute @@ -651,6 +682,7 @@ export interface FileRoutesByTo { '/jobs/bodhi/$id': typeof JobsBodhiIdRoute '/jobs/copr/$id': typeof JobsCoprIdRoute '/jobs/koji-downstream/$id': typeof JobsKojiDownstreamIdRoute + '/jobs/koji-tag-request/$id': typeof JobsKojiTagRequestIdRoute '/jobs/koji/$id': typeof JobsKojiIdRoute '/jobs/openscanhub/$id': typeof JobsOpenscanhubIdRoute '/jobs/propose-downstream/$id': typeof JobsProposeDownstreamIdRoute @@ -677,6 +709,7 @@ export interface FileRoutesById { '/jobs/koji': typeof JobsKojiRoute '/jobs/koji-builds': typeof JobsKojiBuildsRoute '/jobs/koji-downstream': typeof JobsKojiDownstreamRoute + '/jobs/koji-tag-requests': typeof JobsKojiTagRequestsRoute '/jobs/openscanhub': typeof JobsOpenscanhubRoute '/jobs/propose-downstream': typeof JobsProposeDownstreamRoute '/jobs/propose-downstreams': typeof JobsProposeDownstreamsRoute @@ -693,6 +726,7 @@ export interface FileRoutesById { '/jobs/bodhi/$id': typeof JobsBodhiIdRoute '/jobs/copr/$id': typeof JobsCoprIdRoute '/jobs/koji-downstream/$id': typeof JobsKojiDownstreamIdRoute + '/jobs/koji-tag-request/$id': typeof JobsKojiTagRequestIdRoute '/jobs/koji/$id': typeof JobsKojiIdRoute '/jobs/openscanhub/$id': typeof JobsOpenscanhubIdRoute '/jobs/propose-downstream/$id': typeof JobsProposeDownstreamIdRoute @@ -720,6 +754,7 @@ export interface FileRouteTypes { | '/jobs/koji' | '/jobs/koji-builds' | '/jobs/koji-downstream' + | '/jobs/koji-tag-requests' | '/jobs/openscanhub' | '/jobs/propose-downstream' | '/jobs/propose-downstreams' @@ -736,6 +771,7 @@ export interface FileRouteTypes { | '/jobs/bodhi/$id' | '/jobs/copr/$id' | '/jobs/koji-downstream/$id' + | '/jobs/koji-tag-request/$id' | '/jobs/koji/$id' | '/jobs/openscanhub/$id' | '/jobs/propose-downstream/$id' @@ -759,6 +795,7 @@ export interface FileRouteTypes { | '/jobs/koji' | '/jobs/koji-builds' | '/jobs/koji-downstream' + | '/jobs/koji-tag-requests' | '/jobs/openscanhub' | '/jobs/propose-downstream' | '/jobs/propose-downstreams' @@ -775,6 +812,7 @@ export interface FileRouteTypes { | '/jobs/bodhi/$id' | '/jobs/copr/$id' | '/jobs/koji-downstream/$id' + | '/jobs/koji-tag-request/$id' | '/jobs/koji/$id' | '/jobs/openscanhub/$id' | '/jobs/propose-downstream/$id' @@ -799,6 +837,7 @@ export interface FileRouteTypes { | '/jobs/koji' | '/jobs/koji-builds' | '/jobs/koji-downstream' + | '/jobs/koji-tag-requests' | '/jobs/openscanhub' | '/jobs/propose-downstream' | '/jobs/propose-downstreams' @@ -815,6 +854,7 @@ export interface FileRouteTypes { | '/jobs/bodhi/$id' | '/jobs/copr/$id' | '/jobs/koji-downstream/$id' + | '/jobs/koji-tag-request/$id' | '/jobs/koji/$id' | '/jobs/openscanhub/$id' | '/jobs/propose-downstream/$id' @@ -839,6 +879,7 @@ export interface RootRouteChildren { JobsBodhiIdRoute: typeof JobsBodhiIdRoute JobsCoprIdRoute: typeof JobsCoprIdRoute JobsKojiDownstreamIdRoute: typeof JobsKojiDownstreamIdRoute + JobsKojiTagRequestIdRoute: typeof JobsKojiTagRequestIdRoute JobsKojiIdRoute: typeof JobsKojiIdRoute JobsOpenscanhubIdRoute: typeof JobsOpenscanhubIdRoute JobsProposeDownstreamIdRoute: typeof JobsProposeDownstreamIdRoute @@ -862,6 +903,7 @@ const rootRouteChildren: RootRouteChildren = { JobsBodhiIdRoute: JobsBodhiIdRoute, JobsCoprIdRoute: JobsCoprIdRoute, JobsKojiDownstreamIdRoute: JobsKojiDownstreamIdRoute, + JobsKojiTagRequestIdRoute: JobsKojiTagRequestIdRoute, JobsKojiIdRoute: JobsKojiIdRoute, JobsOpenscanhubIdRoute: JobsOpenscanhubIdRoute, JobsProposeDownstreamIdRoute: JobsProposeDownstreamIdRoute, @@ -896,6 +938,7 @@ export const routeTree = rootRoute "/jobs/bodhi/$id", "/jobs/copr/$id", "/jobs/koji-downstream/$id", + "/jobs/koji-tag-request/$id", "/jobs/koji/$id", "/jobs/openscanhub/$id", "/jobs/propose-downstream/$id", @@ -920,6 +963,7 @@ export const routeTree = rootRoute "/jobs/koji", "/jobs/koji-builds", "/jobs/koji-downstream", + "/jobs/koji-tag-requests", "/jobs/openscanhub", "/jobs/propose-downstream", "/jobs/propose-downstreams", @@ -976,6 +1020,10 @@ export const routeTree = rootRoute "filePath": "jobs/koji-downstream.tsx", "parent": "/jobs" }, + "/jobs/koji-tag-requests": { + "filePath": "jobs/koji-tag-requests.tsx", + "parent": "/jobs" + }, "/jobs/openscanhub": { "filePath": "jobs/openscanhub.tsx", "parent": "/jobs" @@ -1034,6 +1082,9 @@ export const routeTree = rootRoute "/jobs/koji-downstream/$id": { "filePath": "jobs_/koji-downstream.$id.tsx" }, + "/jobs/koji-tag-request/$id": { + "filePath": "jobs_/koji-tag-request.$id.tsx" + }, "/jobs/koji/$id": { "filePath": "jobs_/koji.$id.tsx" }, diff --git a/frontend/src/routes/jobs/koji-tag-requests.tsx b/frontend/src/routes/jobs/koji-tag-requests.tsx new file mode 100644 index 00000000..f2a170d8 --- /dev/null +++ b/frontend/src/routes/jobs/koji-tag-requests.tsx @@ -0,0 +1,9 @@ +// Copyright Contributors to the Packit project. +// SPDX-License-Identifier: MIT + +import { createFileRoute } from "@tanstack/react-router"; +import { KojiTagRequestsTable } from "../../components/koji/KojiTagRequestsTable"; + +export const Route = createFileRoute("/jobs/koji-tag-requests")({ + component: () => KojiTagRequestsTable(), +}); diff --git a/frontend/src/routes/jobs_/koji-tag-request.$id.tsx b/frontend/src/routes/jobs_/koji-tag-request.$id.tsx new file mode 100644 index 00000000..8c7f7d51 --- /dev/null +++ b/frontend/src/routes/jobs_/koji-tag-request.$id.tsx @@ -0,0 +1,15 @@ +// Copyright Contributors to the Packit project. +// SPDX-License-Identifier: MIT + +import { createFileRoute } from "@tanstack/react-router"; +import { KojiTagRequest } from "../../components/koji/KojiTagRequest"; +import { kojiTagRequestQueryOptions } from "../../queries/koji/kojiTagRequestQuery"; + +export const Route = createFileRoute("/jobs/koji-tag-request/$id")({ + staticData: { + title: "Koji tag request detail", + }, + loader: ({ context: { queryClient }, params: { id } }) => + queryClient.ensureQueryData(kojiTagRequestQueryOptions({ id })), + component: KojiTagRequest, +});