Skip to content

Commit

Permalink
feat: Add dedicated views for daemonsets and canaries (#567)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaeljguarino authored Dec 25, 2023
1 parent 135cfc5 commit 6af4a71
Show file tree
Hide file tree
Showing 32 changed files with 918 additions and 103 deletions.
2 changes: 1 addition & 1 deletion assets/src/components/cd/pipelines/Pipelines.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo } from 'react'
import { useCallback, useMemo } from 'react'
import {
AppIcon,
Card,
Expand Down
4 changes: 4 additions & 0 deletions assets/src/components/component/ComponentDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ import LoadingIndicator from 'components/utils/LoadingIndicator'
import { ViewLogsButton } from 'components/component/ViewLogsButton'
import { directory } from 'components/component/directory'
import {
CanaryDocument,
CertificateDocument,
CronJobDocument,
DaemonSetDocument,
DeploymentDocument,
IngressDocument,
JobDocument,
Expand All @@ -36,6 +38,8 @@ export const kindToQuery = {
job: JobDocument,
service: ServiceDocument,
statefulset: StatefulSetDocument,
daemonset: DaemonSetDocument,
canary: CanaryDocument,
} as const

type DetailsComponent = {
Expand Down
7 changes: 7 additions & 0 deletions assets/src/components/component/ComponentInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ import Deployment from './info/Deployment'
import StatefulSet from './info/StatefulSet'
import Metadata from './info/Metadata'
import { ComponentDetailsContext } from './ComponentDetails'
import DaemonSet from './info/Daemonset'
import CanaryInfo from './info/Canary'

const componentsWithPods: string[] = [
'deployment',
'job',
'service',
'statefulset',
'daemonset',
]

export const componentsWithLogs: string[] = ['deployment', 'statefulset']
Expand All @@ -43,6 +46,10 @@ function getInfo(kind: string): JSX.Element | undefined {
return <Service />
case 'statefulset':
return <StatefulSet />
case 'daemonset':
return <DaemonSet />
case 'canary':
return <CanaryInfo />
default:
return undefined
}
Expand Down
201 changes: 201 additions & 0 deletions assets/src/components/component/info/Canary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import { useOutletContext } from 'react-router-dom'
import { useTheme } from 'styled-components'
import { createColumnHelper } from '@tanstack/react-table'

import { Canary, Deployment, Ingress } from 'generated/graphql'

import { Chip, Table } from '@pluralsh/design-system'

import {
InfoSectionH2,
InfoSectionH3,
PaddedCard,
PropWideBold,
} from './common'
import { ConditionsTable } from './Conditions'

const deploymentHelper = createColumnHelper<Deployment>()
const ingressHelper = createColumnHelper<Ingress>()

function StatusChip({ healthy }) {
return (
<Chip severity={healthy ? 'success' : 'warning'}>
{healthy ? 'Healthy' : 'Pending'}
</Chip>
)
}

const ColDepName = deploymentHelper.accessor((row) => row.metadata?.name, {
id: 'name',
header: 'Name',
cell: ({ getValue }) => getValue(),
})

const ColDepStatus = deploymentHelper.accessor((row) => row.status, {
id: 'status',
header: 'Status',
cell: ({ getValue }) => {
const status = getValue()

if (!status) return null
const healthy = status.replicas === status.availableReplicas

return <StatusChip healthy={healthy} />
},
})

const ColDepAvailable = deploymentHelper.accessor(
(row) => row.status?.availableReplicas,
{
id: 'available',
header: 'Available',
cell: ({ getValue }) => getValue(),
}
)

const ColDepReplicas = deploymentHelper.accessor(
(row) => row.status?.replicas,
{
id: 'replicas',
header: 'Replicas',
cell: ({ getValue }) => getValue(),
}
)

const depColumns = [ColDepName, ColDepStatus, ColDepReplicas, ColDepAvailable]

const ColIngName = ingressHelper.accessor((row) => row.metadata?.name, {
id: 'name',
header: 'Name',
cell: ({ getValue }) => getValue(),
})

const ColIngStatus = ingressHelper.accessor((row) => row.status, {
id: 'status',
header: 'Status',
cell: ({ getValue }) => {
const status = getValue()

if (!status) return null
const healthy = (status?.loadBalancer?.ingress?.length || 0) > 0

return <StatusChip healthy={healthy} />
},
})

const ColIngClass = ingressHelper.accessor(
(row) => row.spec?.ingressClassName,
{
id: 'class',
header: 'Class',
cell: ({ getValue }) => getValue(),
}
)

const ingressColumns = [ColIngName, ColIngStatus, ColIngClass]

function CanaryDeployments({ canary }: { canary: Canary }) {
const theme = useTheme()

return (
<>
<InfoSectionH3
css={{
marginBottom: theme.spacing.medium,
marginTop: theme.spacing.large,
}}
>
Deployments
</InfoSectionH3>
<Table
data={[canary.primaryDeployment, canary.canaryDeployment].filter(
(v) => !!v
)}
columns={depColumns}
/>
</>
)
}

function CanaryIngresses({ canary }: { canary: Canary }) {
const theme = useTheme()

return (
<>
<InfoSectionH3
css={{
marginBottom: theme.spacing.medium,
marginTop: theme.spacing.large,
}}
>
Ingresses
</InfoSectionH3>
<Table
data={[canary.ingress, canary.ingressCanary].filter((v) => !!v)}
columns={ingressColumns}
/>
</>
)
}

export default function CanaryInfo() {
const theme = useTheme()
const { data } = useOutletContext<any>()
const canary = data?.canary as Nullable<Canary>

if (!canary) return null

const { status } = canary

return (
<div
css={{
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
}}
>
<InfoSectionH2>References</InfoSectionH2>
<CanaryDeployments canary={canary} />
<CanaryIngresses canary={canary} />
<InfoSectionH2
css={{
marginBottom: theme.spacing.medium,
marginTop: theme.spacing.medium,
}}
>
Status
</InfoSectionH2>
<PaddedCard>
<div
css={{
display: 'flex',
gap: theme.spacing.xlarge,
}}
>
<div
css={{
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
justifyContent: 'center',
}}
>
<PropWideBold title="Failed Checks">
{status?.failedChecks || 0}
</PropWideBold>
<PropWideBold title="Canary Weight">
{status?.canaryWeight || 0}
</PropWideBold>
<PropWideBold title="Iterations">
{status?.iterations || 0}
</PropWideBold>
</div>
</div>
</PaddedCard>
<div css={{ marginTop: theme.spacing.medium }}>
<ConditionsTable conditions={status?.conditions} />
</div>
</div>
)
}
49 changes: 49 additions & 0 deletions assets/src/components/component/info/Conditions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Table } from '@pluralsh/design-system'
import { createColumnHelper } from '@tanstack/react-table'
import { StatusConditionFragment } from 'generated/graphql'

const columnHelper = createColumnHelper<StatusConditionFragment>()

const ColType = columnHelper.accessor((row) => row.type, {
id: 'type',
header: 'Type',
cell: function Cell({ getValue }) {
return getValue()
},
})

const ColMessage = columnHelper.accessor((row) => row.message, {
id: 'message',
header: 'Message',
cell: function Cell({ getValue }) {
return getValue()
},
})

const ColReason = columnHelper.accessor((row) => row.reason, {
id: 'reason',
header: 'Reason',
cell: function Cell({ getValue }) {
return getValue()
},
})

const ColStatus = columnHelper.accessor((row) => row.reason, {
id: 'status',
header: 'Status',
cell: function Cell({ getValue }) {
return getValue()
},
})

const columns = [ColType, ColStatus, ColReason, ColMessage]

export function ConditionsTable({ conditions, ...props }) {
return (
<Table
columns={columns}
data={conditions}
{...props}
/>
)
}
84 changes: 84 additions & 0 deletions assets/src/components/component/info/Daemonset.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { useOutletContext } from 'react-router-dom'
import { useTheme } from 'styled-components'

import { StatusChart } from './Deployment'
import { InfoSectionH2, PaddedCard, PropWideBold } from './common'

export default function DaemonSet() {
const theme = useTheme()
const { data } = useOutletContext<any>()

if (!data?.daemonSet) return null

const {
daemonSet: {
spec,
status: { desiredNumberScheduled, numberReady, currentNumberScheduled },
},
} = data

return (
<div
css={{
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
}}
>
<InfoSectionH2 css={{ marginBottom: theme.spacing.medium }}>
Status
</InfoSectionH2>
<PaddedCard>
<div
css={{
display: 'flex',
gap: theme.spacing.xlarge,
}}
>
<div
css={{
display: 'flex',
width: 180,
height: 180,
}}
>
<StatusChart
available={numberReady}
unavailable={currentNumberScheduled - numberReady}
pending={desiredNumberScheduled - currentNumberScheduled}
/>
</div>
<div
css={{
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
justifyContent: 'center',
}}
>
<PropWideBold title="Desired">
{desiredNumberScheduled || 0}
</PropWideBold>
<PropWideBold title="Current Scheduled">
{currentNumberScheduled || 0}
</PropWideBold>
<PropWideBold title="Ready">{numberReady || 0}</PropWideBold>
</div>
</div>
</PaddedCard>
<InfoSectionH2
css={{
marginBottom: theme.spacing.medium,
marginTop: theme.spacing.large,
}}
>
Spec
</InfoSectionH2>
<PaddedCard>
<PropWideBold title="Strategy">
{spec?.strategy?.type || '-'}
</PropWideBold>
</PaddedCard>
</div>
)
}
2 changes: 1 addition & 1 deletion assets/src/components/component/info/Deployment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useTheme } from 'styled-components'

import { InfoSectionH2, PaddedCard, PropWideBold } from './common'

function StatusChart({
export function StatusChart({
available,
unavailable,
pending,
Expand Down
Loading

0 comments on commit 6af4a71

Please sign in to comment.