diff --git a/assets/src/components/cd/cluster/Cluster.tsx b/assets/src/components/cd/cluster/Cluster.tsx index 4ddc37a6ea..147c9ff079 100644 --- a/assets/src/components/cd/cluster/Cluster.tsx +++ b/assets/src/components/cd/cluster/Cluster.tsx @@ -25,6 +25,7 @@ import { CLUSTER_NODES_PATH, CLUSTER_PARAM_ID, CLUSTER_PODS_PATH, + CLUSTER_PRS_REL_PATH, CLUSTER_SERVICES_PATH, } from 'routes/cdRoutesConsts' import { useTheme } from 'styled-components' @@ -46,6 +47,7 @@ const directory = [ { path: CLUSTER_METADATA_PATH, label: 'Metadata' }, { path: CLUSTER_LOGS_PATH, label: 'Logs', logs: true }, { path: CLUSTER_ADDONS_REL_PATH, label: 'Add-ons' }, + { path: CLUSTER_PRS_REL_PATH, label: 'PRs' }, ] as const const POLL_INTERVAL = 10 * 1000 @@ -130,6 +132,7 @@ export default function Cluster() { scrollable={ tab !== 'services' && tab !== 'pods' && + tab !== CLUSTER_PRS_REL_PATH && tab !== 'addons' && tab !== 'logs' } diff --git a/assets/src/components/cd/cluster/ClusterPRs.tsx b/assets/src/components/cd/cluster/ClusterPRs.tsx new file mode 100644 index 0000000000..b52fca8d07 --- /dev/null +++ b/assets/src/components/cd/cluster/ClusterPRs.tsx @@ -0,0 +1,108 @@ +import { useTheme } from 'styled-components' +import { Input, LoopingLogo, SearchIcon, Table } from '@pluralsh/design-system' +import { ComponentProps, useState } from 'react' + +import { FullHeightTableWrap } from '../../utils/layout/FullHeightTableWrap' +import { + ColActions, + ColCreator, + ColInsertedAt, + ColLabels, + ColService, + ColStatus, + ColTitle, +} from '../../pr/queue/PrQueueColumns' +import { + PRS_REACT_VIRTUAL_OPTIONS, + PR_QUERY_PAGE_SIZE, +} from '../../pr/queue/PrQueue' +import { GqlError } from '../../utils/Alert' +import { useFetchPaginatedData } from '../utils/useFetchPaginatedData' +import { usePullRequestsQuery } from '../../../generated/graphql' +import { useThrottle } from '../../hooks/useThrottle' + +import { useClusterContext } from './Cluster' + +export const columns = [ + ColTitle, + ColStatus, + ColService, + ColCreator, + ColLabels, + ColInsertedAt, + ColActions, +] + +export default function ClusterPRs() { + const { cluster } = useClusterContext() + const theme = useTheme() + const [searchString, setSearchString] = useState('') + const debouncedSearchString = useThrottle(searchString, 200) + + const { + data, + loading, + error, + refetch, + pageInfo, + fetchNextPage, + setVirtualSlice, + } = useFetchPaginatedData( + { + queryHook: usePullRequestsQuery, + pageSize: PR_QUERY_PAGE_SIZE, + keyPath: ['pullRequests'], + }, + { + q: debouncedSearchString, + clusterId: cluster.id, + } + ) + + const reactTableOptions: ComponentProps['reactTableOptions'] = { + meta: { refetch }, + } + + if (error) return + + if (!data) return + + return ( +
+
+ } + showClearButton + value={searchString} + onChange={(e) => setSearchString(e.currentTarget.value)} + css={{ flexGrow: 1 }} + /> +
+ + + + + ) +} diff --git a/assets/src/components/cd/services/service/ServiceDetails.tsx b/assets/src/components/cd/services/service/ServiceDetails.tsx index 78ab2744d2..a7ed7512ad 100644 --- a/assets/src/components/cd/services/service/ServiceDetails.tsx +++ b/assets/src/components/cd/services/service/ServiceDetails.tsx @@ -30,8 +30,10 @@ import { } from 'components/contexts/DocPageContext' import { getDocsData } from 'components/apps/app/App' import { + SERVICE_COMPONENTS_PATH, SERVICE_PARAM_CLUSTER_ID, SERVICE_PARAM_ID, + SERVICE_PRS_PATH, getClusterDetailsPath, getServiceDetailsPath, } from 'routes/cdRoutesConsts' @@ -123,7 +125,7 @@ export const getDirectory = ({ return [ { - path: 'components', + path: SERVICE_COMPONENTS_PATH, label: ( () + const { service } = useServiceContext() + + useSetBreadcrumbs( + useMemo( + () => [ + ...getServiceDetailsBreadcrumbs({ + cluster: service?.cluster || { id: clusterId || '' }, + service: service || { id: serviceId || '' }, + }), + { + label: 'pull requests', + url: `${CD_REL_PATH}/services/${serviceId}/${SERVICE_PRS_PATH}`, + }, + ], + [clusterId, service, serviceId] + ) + ) + + const [searchString, setSearchString] = useState('') + const debouncedSearchString = useThrottle(searchString, 200) + + const { + data, + loading, + error, + refetch, + pageInfo, + fetchNextPage, + setVirtualSlice, + } = useFetchPaginatedData( + { + queryHook: usePullRequestsQuery, + pageSize: PR_QUERY_PAGE_SIZE, + keyPath: ['pullRequests'], + }, + { + q: debouncedSearchString, + serviceId, + } + ) + + const reactTableOptions: ComponentProps['reactTableOptions'] = { + meta: { refetch }, + } + + if (error) return + + if (!data) return + + return ( + +
+
+ } + showClearButton + value={searchString} + onChange={(e) => setSearchString(e.currentTarget.value)} + css={{ flexGrow: 1 }} + /> +
+ {isEmpty(data?.pullRequests?.edges) ? ( + + ) : ( + +
+ + )} + + + ) +} diff --git a/assets/src/components/pr/queue/PrQueueColumns.tsx b/assets/src/components/pr/queue/PrQueueColumns.tsx index 881cbaa9ae..2ef92a936f 100644 --- a/assets/src/components/pr/queue/PrQueueColumns.tsx +++ b/assets/src/components/pr/queue/PrQueueColumns.tsx @@ -78,7 +78,7 @@ function ColServiceContent({ export const columnHelper = createColumnHelper>() -const ColTitle = columnHelper.accessor(({ node }) => node?.title, { +export const ColTitle = columnHelper.accessor(({ node }) => node?.title, { id: 'title', header: 'PR Title', meta: { truncate: true }, @@ -107,7 +107,7 @@ export function PrStatusChip({ status }: { status?: PrStatus | null }) { return {capitalize(status)} } -const ColStatus = columnHelper.accessor(({ node }) => node?.status, { +export const ColStatus = columnHelper.accessor(({ node }) => node?.status, { id: 'status', header: 'Status', cell: function Cell({ getValue }) { @@ -115,7 +115,7 @@ const ColStatus = columnHelper.accessor(({ node }) => node?.status, { }, }) -const ColCreator = columnHelper.accessor(({ node }) => node?.creator, { +export const ColCreator = columnHelper.accessor(({ node }) => node?.creator, { id: 'creator', header: 'Creator', cell: function Cell({ getValue }) { @@ -123,12 +123,12 @@ const ColCreator = columnHelper.accessor(({ node }) => node?.creator, { }, }) -const ColLabelsSC = styled.div(({ theme }) => ({ +export const ColLabelsSC = styled.div(({ theme }) => ({ display: 'flex', gap: theme.spacing.xsmall, flexWrap: 'wrap', })) -const ColLabels = columnHelper.accessor( +export const ColLabels = columnHelper.accessor( ({ node }) => node?.labels?.join(', ') || '', { id: 'labels', @@ -157,15 +157,18 @@ const ColLabels = columnHelper.accessor( } ) -const ColCluster = columnHelper.accessor(({ node }) => node?.cluster?.name, { - id: 'cluster', - header: 'Cluster', - cell: function Cell({ row }) { - return - }, -}) +export const ColCluster = columnHelper.accessor( + ({ node }) => node?.cluster?.name, + { + id: 'cluster', + header: 'Cluster', + cell: function Cell({ row }) { + return + }, + } +) -const ColService = columnHelper.accessor(({ node }) => node, { +export const ColService = columnHelper.accessor(({ node }) => node, { id: 'service', header: 'Service', cell: function Cell({ row }) { @@ -178,13 +181,16 @@ const ColService = columnHelper.accessor(({ node }) => node, { }, }) -const ColInsertedAt = columnHelper.accessor(({ node }) => node?.insertedAt, { - id: 'insertedAt', - header: 'Created', - cell: function Cell({ getValue }) { - return - }, -}) +export const ColInsertedAt = columnHelper.accessor( + ({ node }) => node?.insertedAt, + { + id: 'insertedAt', + header: 'Created', + cell: function Cell({ getValue }) { + return + }, + } +) export const ColActions = columnHelper.accessor(({ node }) => node, { id: 'actions', diff --git a/assets/src/routes/cdRoutes.tsx b/assets/src/routes/cdRoutes.tsx index d23e1407dc..3c5c9ac096 100644 --- a/assets/src/routes/cdRoutes.tsx +++ b/assets/src/routes/cdRoutes.tsx @@ -82,6 +82,10 @@ import ServicesTree from '../components/cd/services/ServicesTree' import ServicesTable from '../components/cd/services/ServicesTable' +import ClusterPRs from '../components/cd/cluster/ClusterPRs' + +import ServicePRs from '../components/cd/services/service/ServicePRs' + import { CD_REL_PATH, CLUSTERS_REL_PATH, @@ -90,6 +94,7 @@ import { CLUSTER_METADATA_PATH, CLUSTER_NODES_PATH, CLUSTER_PODS_PATH, + CLUSTER_PRS_REL_PATH, CLUSTER_REL_PATH, CLUSTER_SERVICES_PATH, GLOBAL_SERVICES_REL_PATH, @@ -110,6 +115,7 @@ import { SERVICE_COMPONENT_PATH_MATCHER_REL, SERVICE_PARAM_CLUSTER_ID, SERVICE_POD_REL_PATH, + SERVICE_PRS_PATH, SERVICE_REL_PATH, } from './cdRoutesConsts' import { pipelineRoutes } from './pipelineRoutes' @@ -313,6 +319,10 @@ const clusterDetailsRoutes = [ path={CLUSTER_METADATA_PATH} element={} /> + } + /> } @@ -448,6 +458,10 @@ const serviceDetailsRoutes = ( element={} path="errors" /> + } + path={SERVICE_PRS_PATH} + /> } path="secrets" diff --git a/assets/src/routes/cdRoutesConsts.tsx b/assets/src/routes/cdRoutesConsts.tsx index fc4ac902dc..2d354eb2ce 100644 --- a/assets/src/routes/cdRoutesConsts.tsx +++ b/assets/src/routes/cdRoutesConsts.tsx @@ -26,6 +26,7 @@ export const CLUSTER_PODS_PATH = 'pods' as const export const CLUSTER_METADATA_PATH = 'metadata' as const export const CLUSTER_ADDONS_REL_PATH = 'addons' as const export const CLUSTER_ADDONS_PARAM_ID = 'addOnId' as const +export const CLUSTER_PRS_REL_PATH = 'prs' as const export const CLUSTER_LOGS_PATH = 'logs' as const export const NODE_PARAM_NAME = 'name' as const @@ -87,6 +88,7 @@ export const SERVICE_ABS_PATH = getServiceDetailsPath({ serviceId: `:${SERVICE_PARAM_ID}`, }) export const SERVICE_COMPONENTS_PATH = 'components' +export const SERVICE_PRS_PATH = 'prs' export const COMPONENT_PARAM_ID = `componentId` as const export const SERVICE_COMPONENT_PATH_MATCHER_REL = getServiceComponentPath({