Skip to content

Commit

Permalink
add cluster upgradeability filter
Browse files Browse the repository at this point in the history
  • Loading branch information
jsladerman committed Feb 28, 2025
1 parent 742dbd9 commit 4b43f67
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 12 deletions.
16 changes: 14 additions & 2 deletions assets/src/components/cd/clusters/Clusters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { useCDEnabled } from '../utils/useCDEnabled'
import {
ClusterStatusTabKey,
ClustersFilters,
UpgradeableFilterKey,
} from '../services/ClustersFilters'

import { TagsFilter } from '../services/ClusterTagsFilter'
Expand Down Expand Up @@ -109,6 +110,8 @@ export default function Clusters() {
const tabStateRef = useRef<any>(null)
const [statusFilter, setStatusFilter] = useState<ClusterStatusTabKey>('ALL')
const [selectedTagKeys, setSelectedTagKeys] = useState(new Set<Key>())
const [upgradeableFilter, setUpgradeableFilter] =
useState<UpgradeableFilterKey>('ALL')

const [searchString, setSearchString] = useState<string>()
const debouncedSearchString = useDebounce(searchString, 100)
Expand Down Expand Up @@ -145,6 +148,10 @@ export default function Clusters() {
...(!isEmpty(searchTags)
? { tagQuery: { op: tagOp, tags: searchTags } }
: {}),
upgradeable:
upgradeableFilter === 'ALL'
? undefined
: upgradeableFilter === 'UPGRADEABLE',
}
)

Expand Down Expand Up @@ -194,9 +201,11 @@ export default function Clusters() {
useSetBreadcrumbs(CD_CLUSTERS_BASE_CRUMBS)

const clusterEdges = data?.clusters?.edges
const isDemo = statusCounts.ALL === 0 || !cdIsEnabled
const hasStatFilters = !!debouncedSearchString || !!projectId
const isDemo = (statusCounts.ALL === 0 && !hasStatFilters) || !cdIsEnabled
const tableData = isDemo ? DEMO_CLUSTERS : clusterEdges
const showGettingStarted = isDemo || (statusCounts.ALL ?? 0) < 2
const showGettingStarted =
isDemo || ((statusCounts.ALL ?? 0) < 2 && !hasStatFilters)

useSetPageScrollable(showGettingStarted || isDemo)

Expand Down Expand Up @@ -229,6 +238,9 @@ export default function Clusters() {
setTagOp={
setTagOp as ComponentProps<typeof TagsFilter>['setSearchOp']
}
upgradeableFilter={upgradeableFilter}
setUpgradeableFilter={setUpgradeableFilter}
upgradeStats={data.upgradeStatistics}
/>
<TabPanel
stateRef={tabStateRef}
Expand Down
61 changes: 55 additions & 6 deletions assets/src/components/cd/services/ClustersFilters.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
import { type ComponentProps, useDeferredValue, useState } from 'react'
import {
Chip,
Input,
ListBoxItem,
SearchIcon,
Select,
SubTab,
TabList,
} from '@pluralsh/design-system'
import styled from 'styled-components'
import { Dispatch, MutableRefObject, SetStateAction, useEffect } from 'react'
import isNil from 'lodash/isNil'
import {
type ComponentProps,
Dispatch,
RefObject,
SetStateAction,
useDeferredValue,
useEffect,
useState,
} from 'react'
import styled from 'styled-components'

import { useDebounce } from '@react-hooks-library/core'

import { serviceStatusToSeverity } from './ServiceStatusChip'
import { UpgradeStatistics } from 'generated/graphql'
import { isNumber } from 'lodash'
import { TagsFilter } from './ClusterTagsFilter'
import { serviceStatusToSeverity } from './ServiceStatusChip'

export type ClusterStatusTabKey = 'HEALTHY' | 'UNHEALTHY' | 'ALL'
export type UpgradeableFilterKey = 'ALL' | 'UPGRADEABLE' | 'NON-UPGRADEABLE'
export const statusTabs = Object.entries({
ALL: { label: 'All' },
HEALTHY: {
Expand Down Expand Up @@ -46,20 +58,28 @@ export function ClustersFilters({
setSelectedTagKeys,
tagOp,
setTagOp,
upgradeableFilter,
setUpgradeableFilter,
upgradeStats,
}: {
setQueryStatusFilter: Dispatch<SetStateAction<ClusterStatusTabKey>>
setQueryString: (string) => void
tabStateRef: MutableRefObject<any>
tabStateRef: RefObject<any>
statusCounts: Record<ClusterStatusTabKey, number | undefined>
selectedTagKeys: ComponentProps<typeof TagsFilter>['selectedTagKeys']
setSelectedTagKeys: ComponentProps<typeof TagsFilter>['setSelectedTagKeys']
tagOp: ComponentProps<typeof TagsFilter>['searchOp']
setTagOp: ComponentProps<typeof TagsFilter>['setSearchOp']
upgradeableFilter: UpgradeableFilterKey
setUpgradeableFilter: (val: UpgradeableFilterKey) => void
upgradeStats: Nullable<Pick<UpgradeStatistics, 'upgradeable' | 'count'>>
}) {
const [searchString, setSearchString] = useState('')
const debouncedSearchString = useDebounce(searchString, 400)
const [statusFilter, setStatusFilter] = useState<ClusterStatusTabKey>('ALL')
const deferredStatusFilter = useDeferredValue(statusFilter)
const { count, upgradeable } = upgradeStats ?? {}
const hasUpgradeStats = isNumber(count) && isNumber(upgradeable)

useEffect(() => {
setQueryString(debouncedSearchString)
Expand All @@ -79,7 +99,7 @@ export function ClustersFilters({
setSearchOp={setTagOp}
/>
</div>
<div css={{ flex: '1 1 50%' }}>
<div css={{ flex: '1 1 50%', minWidth: 120 }}>
<Input
placeholder="Search"
startIcon={<SearchIcon />}
Expand Down Expand Up @@ -118,6 +138,35 @@ export function ClustersFilters({
</SubTab>
))}
</TabList>
<div css={{ minWidth: 240 }}>
<Select
label={upgradeableFilter}
selectedKey={upgradeableFilter}
onSelectionChange={(key) =>
setUpgradeableFilter(key as UpgradeableFilterKey)
}
>
<ListBoxItem
key="ALL"
label="All upgrade statuses"
rightContent={hasUpgradeStats && <Chip size="small">{count}</Chip>}
/>
<ListBoxItem
key="UPGRADEABLE"
label="Upgradeable only"
rightContent={
hasUpgradeStats && <Chip size="small">{upgradeable}</Chip>
}
/>
<ListBoxItem
key="NON-UPGRADEABLE"
label="Non-upgradeable only"
rightContent={
hasUpgradeStats && <Chip size="small">{count - upgradeable}</Chip>
}
/>
</Select>
</div>
</ClustersFiltersSC>
)
}
13 changes: 10 additions & 3 deletions assets/src/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10189,10 +10189,11 @@ export type ClustersQueryVariables = Exact<{
healthy?: InputMaybe<Scalars['Boolean']['input']>;
tagQuery?: InputMaybe<TagQuery>;
projectId?: InputMaybe<Scalars['ID']['input']>;
upgradeable?: InputMaybe<Scalars['Boolean']['input']>;
}>;


export type ClustersQuery = { __typename?: 'RootQueryType', tags?: Array<string | null> | null, clusters?: { __typename?: 'ClusterConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null, hasPreviousPage: boolean, startCursor?: string | null }, edges?: Array<{ __typename?: 'ClusterEdge', node?: { __typename?: 'Cluster', currentVersion?: string | null, id: string, self?: boolean | null, healthy?: boolean | null, protect?: boolean | null, name: string, handle?: string | null, distro?: ClusterDistro | null, installed?: boolean | null, pingedAt?: string | null, deletedAt?: string | null, version?: string | null, kubeletVersion?: string | null, virtual?: boolean | null, metricsSummary?: { __typename?: 'ClusterMetricsSummary', cpuUsed?: number | null, cpuAvailable?: number | null, cpuTotal?: number | null, memoryUsed?: number | null, memoryAvailable?: number | null, memoryTotal?: number | null } | null, provider?: { __typename?: 'ClusterProvider', id: string, cloud: string, name: string, namespace: string, supportedVersions?: Array<string | null> | null } | null, prAutomations?: Array<{ __typename?: 'PrAutomation', id: string, name: string, icon?: string | null, darkIcon?: string | null, documentation?: string | null, addon?: string | null, identifier?: string | null, role?: PrRole | null, cluster?: { __typename?: 'Cluster', protect?: boolean | null, deletedAt?: string | null, version?: string | null, currentVersion?: string | null, self?: boolean | null, virtual?: boolean | null, id: string, name: string, handle?: string | null, distro?: ClusterDistro | null, upgradePlan?: { __typename?: 'ClusterUpgradePlan', compatibilities?: boolean | null, deprecations?: boolean | null, incompatibilities?: boolean | null } | null, provider?: { __typename?: 'ClusterProvider', name: string, cloud: string } | null } | null, service?: { __typename?: 'ServiceDeployment', id: string, name: string } | null, repository?: { __typename?: 'GitRepository', url: string, refs?: Array<string> | null } | null, connection?: { __typename?: 'ScmConnection', id: string, name: string, insertedAt?: string | null, updatedAt?: string | null, type: ScmType, username?: string | null, baseUrl?: string | null, apiUrl?: string | null } | null, createBindings?: Array<{ __typename?: 'PolicyBinding', id?: string | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null, group?: { __typename?: 'Group', id: string, name: string } | null } | null> | null, writeBindings?: Array<{ __typename?: 'PolicyBinding', id?: string | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null, group?: { __typename?: 'Group', id: string, name: string } | null } | null> | null, configuration?: Array<{ __typename?: 'PrConfiguration', values?: Array<string | null> | null, default?: string | null, documentation?: string | null, longform?: string | null, name: string, optional?: boolean | null, placeholder?: string | null, type: ConfigurationType, condition?: { __typename?: 'PrConfigurationCondition', field: string, operation: Operation, value?: string | null } | null } | null> | null, confirmation?: { __typename?: 'PrConfirmation', text?: string | null, checklist?: Array<{ __typename?: 'PrChecklist', label: string } | null> | null } | null } | null> | null, service?: { __typename?: 'ServiceDeployment', id: string, repository?: { __typename?: 'GitRepository', url: string } | null } | null, tags?: Array<{ __typename?: 'Tag', name: string, value: string } | null> | null, upgradePlan?: { __typename?: 'ClusterUpgradePlan', compatibilities?: boolean | null, deprecations?: boolean | null, incompatibilities?: boolean | null } | null, insight?: { __typename?: 'AiInsight', id: string, summary?: string | null, freshness?: InsightFreshness | null, insertedAt?: string | null, updatedAt?: string | null, cluster?: { __typename?: 'Cluster', id: string, name: string, distro?: ClusterDistro | null, provider?: { __typename?: 'ClusterProvider', cloud: string } | null } | null, clusterInsightComponent?: { __typename?: 'ClusterInsightComponent', id: string, name: string } | null, service?: { __typename?: 'ServiceDeployment', id: string, name: string, cluster?: { __typename?: 'Cluster', id: string, name: string, handle?: string | null, distro?: ClusterDistro | null, provider?: { __typename?: 'ClusterProvider', name: string, cloud: string } | null } | null } | null, serviceComponent?: { __typename?: 'ServiceComponent', id: string, name: string, service?: { __typename?: 'ServiceDeployment', id: string, name: string, cluster?: { __typename?: 'Cluster', id: string, name: string, handle?: string | null, distro?: ClusterDistro | null, provider?: { __typename?: 'ClusterProvider', name: string, cloud: string } | null } | null } | null } | null, stack?: { __typename?: 'InfrastructureStack', id?: string | null, name: string, type: StackType } | null, stackRun?: { __typename?: 'StackRun', id: string, message?: string | null, type: StackType, stack?: { __typename?: 'InfrastructureStack', id?: string | null, name: string } | null } | null, alert?: { __typename?: 'Alert', id: string, title?: string | null, message?: string | null } | null } | null } | null } | null> | null } | null, clusterStatuses?: Array<{ __typename?: 'ClusterStatusInfo', count?: number | null, healthy?: boolean | null } | null> | null };
export type ClustersQuery = { __typename?: 'RootQueryType', tags?: Array<string | null> | null, clusters?: { __typename?: 'ClusterConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null, hasPreviousPage: boolean, startCursor?: string | null }, edges?: Array<{ __typename?: 'ClusterEdge', node?: { __typename?: 'Cluster', currentVersion?: string | null, id: string, self?: boolean | null, healthy?: boolean | null, protect?: boolean | null, name: string, handle?: string | null, distro?: ClusterDistro | null, installed?: boolean | null, pingedAt?: string | null, deletedAt?: string | null, version?: string | null, kubeletVersion?: string | null, virtual?: boolean | null, metricsSummary?: { __typename?: 'ClusterMetricsSummary', cpuUsed?: number | null, cpuAvailable?: number | null, cpuTotal?: number | null, memoryUsed?: number | null, memoryAvailable?: number | null, memoryTotal?: number | null } | null, provider?: { __typename?: 'ClusterProvider', id: string, cloud: string, name: string, namespace: string, supportedVersions?: Array<string | null> | null } | null, prAutomations?: Array<{ __typename?: 'PrAutomation', id: string, name: string, icon?: string | null, darkIcon?: string | null, documentation?: string | null, addon?: string | null, identifier?: string | null, role?: PrRole | null, cluster?: { __typename?: 'Cluster', protect?: boolean | null, deletedAt?: string | null, version?: string | null, currentVersion?: string | null, self?: boolean | null, virtual?: boolean | null, id: string, name: string, handle?: string | null, distro?: ClusterDistro | null, upgradePlan?: { __typename?: 'ClusterUpgradePlan', compatibilities?: boolean | null, deprecations?: boolean | null, incompatibilities?: boolean | null } | null, provider?: { __typename?: 'ClusterProvider', name: string, cloud: string } | null } | null, service?: { __typename?: 'ServiceDeployment', id: string, name: string } | null, repository?: { __typename?: 'GitRepository', url: string, refs?: Array<string> | null } | null, connection?: { __typename?: 'ScmConnection', id: string, name: string, insertedAt?: string | null, updatedAt?: string | null, type: ScmType, username?: string | null, baseUrl?: string | null, apiUrl?: string | null } | null, createBindings?: Array<{ __typename?: 'PolicyBinding', id?: string | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null, group?: { __typename?: 'Group', id: string, name: string } | null } | null> | null, writeBindings?: Array<{ __typename?: 'PolicyBinding', id?: string | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null, group?: { __typename?: 'Group', id: string, name: string } | null } | null> | null, configuration?: Array<{ __typename?: 'PrConfiguration', values?: Array<string | null> | null, default?: string | null, documentation?: string | null, longform?: string | null, name: string, optional?: boolean | null, placeholder?: string | null, type: ConfigurationType, condition?: { __typename?: 'PrConfigurationCondition', field: string, operation: Operation, value?: string | null } | null } | null> | null, confirmation?: { __typename?: 'PrConfirmation', text?: string | null, checklist?: Array<{ __typename?: 'PrChecklist', label: string } | null> | null } | null } | null> | null, service?: { __typename?: 'ServiceDeployment', id: string, repository?: { __typename?: 'GitRepository', url: string } | null } | null, tags?: Array<{ __typename?: 'Tag', name: string, value: string } | null> | null, upgradePlan?: { __typename?: 'ClusterUpgradePlan', compatibilities?: boolean | null, deprecations?: boolean | null, incompatibilities?: boolean | null } | null, insight?: { __typename?: 'AiInsight', id: string, summary?: string | null, freshness?: InsightFreshness | null, insertedAt?: string | null, updatedAt?: string | null, cluster?: { __typename?: 'Cluster', id: string, name: string, distro?: ClusterDistro | null, provider?: { __typename?: 'ClusterProvider', cloud: string } | null } | null, clusterInsightComponent?: { __typename?: 'ClusterInsightComponent', id: string, name: string } | null, service?: { __typename?: 'ServiceDeployment', id: string, name: string, cluster?: { __typename?: 'Cluster', id: string, name: string, handle?: string | null, distro?: ClusterDistro | null, provider?: { __typename?: 'ClusterProvider', name: string, cloud: string } | null } | null } | null, serviceComponent?: { __typename?: 'ServiceComponent', id: string, name: string, service?: { __typename?: 'ServiceDeployment', id: string, name: string, cluster?: { __typename?: 'Cluster', id: string, name: string, handle?: string | null, distro?: ClusterDistro | null, provider?: { __typename?: 'ClusterProvider', name: string, cloud: string } | null } | null } | null } | null, stack?: { __typename?: 'InfrastructureStack', id?: string | null, name: string, type: StackType } | null, stackRun?: { __typename?: 'StackRun', id: string, message?: string | null, type: StackType, stack?: { __typename?: 'InfrastructureStack', id?: string | null, name: string } | null } | null, alert?: { __typename?: 'Alert', id: string, title?: string | null, message?: string | null } | null } | null } | null } | null> | null } | null, clusterStatuses?: Array<{ __typename?: 'ClusterStatusInfo', count?: number | null, healthy?: boolean | null } | null> | null, upgradeStatistics?: { __typename?: 'UpgradeStatistics', upgradeable?: number | null, count?: number | null } | null };

export type ClusterMinimalFragment = { __typename?: 'Cluster', id: string, name: string, handle?: string | null, distro?: ClusterDistro | null, provider?: { __typename?: 'ClusterProvider', name: string, cloud: string } | null };

Expand Down Expand Up @@ -17687,14 +17688,15 @@ export type DeleteCatalogMutationHookResult = ReturnType<typeof useDeleteCatalog
export type DeleteCatalogMutationResult = Apollo.MutationResult<DeleteCatalogMutation>;
export type DeleteCatalogMutationOptions = Apollo.BaseMutationOptions<DeleteCatalogMutation, DeleteCatalogMutationVariables>;
export const ClustersDocument = gql`
query Clusters($first: Int, $after: String, $q: String, $healthy: Boolean, $tagQuery: TagQuery, $projectId: ID) {
query Clusters($first: Int, $after: String, $q: String, $healthy: Boolean, $tagQuery: TagQuery, $projectId: ID, $upgradeable: Boolean) {
clusters(
first: $first
after: $after
q: $q
healthy: $healthy
tagQuery: $tagQuery
projectId: $projectId
upgradeable: $upgradeable
) {
pageInfo {
...PageInfo
Expand All @@ -17705,9 +17707,13 @@ export const ClustersDocument = gql`
}
}
}
clusterStatuses {
clusterStatuses(q: $q, projectId: $projectId) {
...ClusterStatusInfo
}
upgradeStatistics(q: $q, projectId: $projectId) {
upgradeable
count
}
tags
}
${PageInfoFragmentDoc}
Expand All @@ -17732,6 +17738,7 @@ ${ClusterStatusInfoFragmentDoc}`;
* healthy: // value for 'healthy'
* tagQuery: // value for 'tagQuery'
* projectId: // value for 'projectId'
* upgradeable: // value for 'upgradeable'
* },
* });
*/
Expand Down
8 changes: 7 additions & 1 deletion assets/src/graph/cdClusters.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ query Clusters(
$healthy: Boolean
$tagQuery: TagQuery
$projectId: ID
$upgradeable: Boolean
) {
clusters(
first: $first
Expand All @@ -369,6 +370,7 @@ query Clusters(
healthy: $healthy
tagQuery: $tagQuery
projectId: $projectId
upgradeable: $upgradeable
) {
pageInfo {
...PageInfo
Expand All @@ -379,9 +381,13 @@ query Clusters(
}
}
}
clusterStatuses {
clusterStatuses(q: $q, projectId: $projectId) {
...ClusterStatusInfo
}
upgradeStatistics(q: $q, projectId: $projectId) {
upgradeable
count
}
tags
}

Expand Down

0 comments on commit 4b43f67

Please sign in to comment.