Skip to content

Commit

Permalink
Merge pull request #398 from duyet/chore/update-update
Browse files Browse the repository at this point in the history
feat: introduce a new 'CardMultiMetrics' component
  • Loading branch information
duyet authored Nov 4, 2024
2 parents e4d4e46 + 2d938c8 commit 4ac530c
Show file tree
Hide file tree
Showing 13 changed files with 206 additions and 77 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
[![Build and Test](https://github.com/duyet/clickhouse-monitoring/actions/workflows/ci.yml/badge.svg)](https://github.com/duyet/clickhouse-monitoring/actions/workflows/ci.yml)
[![All-time uptime](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fduyet%2Fuptime%2FHEAD%2Fapi%2Fclickhouse-monitoring-vercel-app%2Fuptime.json)](https://duyet.github.io/uptime/history/clickhouse-monitoring-vercel-app)


The simple Next.js dashboard that relies on `system.*` tables to help monitor and provide an overview of your ClickHouse cluster.

Features:
Expand Down
2 changes: 1 addition & 1 deletion app/[host]/[query]/queries/running-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const runningQueriesConfig: QueryConfig = {
name: 'running-queries',
sql: `
SELECT *,
multiIf (elapsed < 30, 'a few seconds',
multiIf (elapsed < 30, format('{} seconds', round(elapsed, 1)),
elapsed < 90, 'a minute',
formatReadableTimeDelta(elapsed, 'days', 'minutes')) as readable_elapsed,
round(100 * elapsed / max(elapsed) OVER ()) AS pct_elapsed,
Expand Down
1 change: 1 addition & 0 deletions components/charts/merge-count.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export async function ChartMergeCount({
<ChartCard
title={title}
className={cn('justify-between', className)}
contentClassName="flex flex-col justify-between"
sql={query}
data={data}
>
Expand Down
2 changes: 1 addition & 1 deletion components/charts/replication-queue-count.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type ChartProps } from '@/components/charts/chart-props'
import { CardMultiMetrics } from '@/components/generic-charts/card-multi-metrics'
import { ChartCard } from '@/components/generic-charts/chart-card'
import { CardMultiMetrics } from '@/components/tremor/card-multi-metrics'
import { fetchData } from '@/lib/clickhouse'
import { cn } from '@/lib/utils'

Expand Down
20 changes: 11 additions & 9 deletions components/charts/summary-used-by-merges.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { ArrowRightIcon } from '@radix-ui/react-icons'
import Link from 'next/link'

import { type ChartProps } from '@/components/charts/chart-props'
import { ChartCard } from '@/components/generic-charts/chart-card'
import {
CardMultiMetrics,
type CardMultiMetricsProps,
} from '@/components/tremor/card-multi-metrics'
} from '@/components/generic-charts/card-multi-metrics'
import { ChartCard } from '@/components/generic-charts/chart-card'
import { fetchData } from '@/lib/clickhouse'
import { getScopedLink } from '@/lib/scoped-link'

Expand Down Expand Up @@ -181,13 +181,15 @@ export async function ChartSummaryUsedByMerges({
<div className="flex flex-col justify-between p-0">
<CardMultiMetrics
primary={
<span className="flex flex-row items-center gap-2">
{rowsReadWritten.readable_rows_read} rows read,{' '}
{used.readable_memory_usage} memory used for merges
<Link href={await getScopedLink('/merges')} className="inline">
<ArrowRightIcon className="size-5" />
</Link>
</span>
<div className="flex flex-col">
<div>{rowsReadWritten.readable_rows_read} rows read</div>
<div className="flex flex-row items-center gap-2">
{used.readable_memory_usage} memory used for merges
<Link href={await getScopedLink('/merges')} className="inline">
<ArrowRightIcon className="size-5" />
</Link>
</div>
</div>
}
items={items}
className="p-2"
Expand Down
8 changes: 1 addition & 7 deletions components/charts/summary-used-by-mutations.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { type ChartProps } from '@/components/charts/chart-props'
import { CardMultiMetrics } from '@/components/generic-charts/card-multi-metrics'
import { ChartCard } from '@/components/generic-charts/chart-card'
import {
CardMultiMetrics,
type CardMultiMetricsProps,
} from '@/components/tremor/card-multi-metrics'
import { fetchData } from '@/lib/clickhouse'

export async function ChartSummaryUsedByMutations({
Expand All @@ -22,8 +19,6 @@ export async function ChartSummaryUsedByMutations({
>({ query })
const count = data?.[0] || { running_count: 0 }

const items: CardMultiMetricsProps['items'] = []

return (
<ChartCard title={title} className={className} sql={query} data={data}>
<div className="flex flex-col content-stretch items-center p-0">
Expand All @@ -33,7 +28,6 @@ export async function ChartSummaryUsedByMutations({
{count.running_count} running mutations
</span>
}
items={items}
className="p-2"
/>
</div>
Expand Down
26 changes: 14 additions & 12 deletions components/charts/summary-used-by-running-queries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { ArrowRightIcon } from '@radix-ui/react-icons'
import Link from 'next/link'

import { type ChartProps } from '@/components/charts/chart-props'
import { ChartCard } from '@/components/generic-charts/chart-card'
import {
CardMultiMetrics,
type CardMultiMetricsProps,
} from '@/components/tremor/card-multi-metrics'
} from '@/components/generic-charts/card-multi-metrics'
import { ChartCard } from '@/components/generic-charts/chart-card'
import { fetchData } from '@/lib/clickhouse'
import { formatReadableQuantity } from '@/lib/format-readable'
import { getScopedLink } from '@/lib/scoped-link'
Expand Down Expand Up @@ -158,16 +158,18 @@ export async function ChartSummaryUsedByRunningQueries({
<div className="flex flex-col justify-between p-0">
<CardMultiMetrics
primary={
<span className="flex flex-row items-center gap-2">
{first.query_count} queries, {first.readable_memory_usage} memory
used for running queries
<Link
href={await getScopedLink('/running-queries')}
className="inline"
>
<ArrowRightIcon className="size-5" />
</Link>
</span>
<div className="flex flex-col">
<div>{first.query_count} queries</div>
<div className="flex flex-row items-center gap-2">
{first.readable_memory_usage} memory used for running queries
<Link
href={await getScopedLink('/running-queries')}
className="inline"
>
<ArrowRightIcon className="size-5" />
</Link>
</div>
</div>
}
items={items}
className="p-2"
Expand Down
2 changes: 1 addition & 1 deletion components/charts/zookeeper-uptime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { fetchData } from '@/lib/clickhouse'
import { cn } from '@/lib/utils'

import { ArrowUpIcon } from '@radix-ui/react-icons'
import { CardMultiMetrics } from '../tremor/card-multi-metrics'
import { CardMultiMetrics } from '../generic-charts/card-multi-metrics'

export async function ChartZookeeperUptime({
title = 'Zookeeper Uptime',
Expand Down
14 changes: 11 additions & 3 deletions components/data-table/column-defs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,24 @@ import type { ColumnDef, Row, RowData, Table } from '@tanstack/react-table'
import { formatCell } from '@/components/data-table/format-cell'
import { Button } from '@/components/ui/button'
import { ColumnFormat, ColumnFormatOptions } from '@/types/column-format'
import { type Icon } from '@/types/icon'
import { type QueryConfig } from '@/types/query-config'

export type ColumnType = { [key: string]: string }

const formatHeader = (name: string, format: ColumnFormat) => {
const formatHeader = (name: string, format: ColumnFormat, icon?: Icon) => {
const CustomIcon = icon

switch (format) {
case ColumnFormat.Action:
return <div className="text-muted-foreground">action</div>
default:
return <div className="text-muted-foreground">{name}</div>
return (
<div className="text-muted-foreground">
{CustomIcon ? <CustomIcon /> : null}
{name}
</div>
)
}
}

Expand Down Expand Up @@ -65,7 +73,7 @@ export const getColumnDefs = <
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
{formatHeader(name, columnFormat)}
{formatHeader(name, columnFormat, config.columnIcons?.[name])}

{column.getIsSorted() === false ? (
<CaretSortIcon className="ml-2 size-4" />
Expand Down
93 changes: 93 additions & 0 deletions components/generic-charts/card-multi-metrics.cy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { CardMultiMetrics } from './card-multi-metrics'

describe('<CardMultiMetrics />', () => {
const mockItems = [
{
current: 100,
target: 200,
currentReadable: '100 users',
targetReadable: '200 users',
},
{
current: 50,
target: 150,
currentReadable: '50 sessions',
targetReadable: '150 sessions',
},
]

it('renders with default props', () => {
cy.mount(<CardMultiMetrics />)

// Should have aria-description
cy.get('[aria-description="card-metrics"]').should('exist')

// Should not render labels when no items
cy.contains('Current').should('not.exist')
cy.contains('Total').should('not.exist')
})

it('renders with primary content', () => {
cy.mount(<CardMultiMetrics primary="Dashboard Metrics" />)

cy.get('div.text-xl').contains('Dashboard Metrics').should('be.visible')
})

it('renders with custom labels and items', () => {
cy.mount(
<CardMultiMetrics
currentLabel="Active"
targetLabel="Maximum"
items={mockItems}
/>
)

// Labels should be visible when items exist
cy.contains('Active').should('be.visible')
cy.contains('Maximum').should('be.visible')
})

it('renders with items data', () => {
cy.mount(<CardMultiMetrics items={mockItems} />)

// Check if readable values are displayed
cy.contains('100 users').should('be.visible')
cy.contains('200 users').should('be.visible')
cy.contains('50 sessions').should('be.visible')
cy.contains('150 sessions').should('be.visible')

// Check for dotted separators
cy.get('hr.border-dotted').should('have.length', mockItems.length)
})

it('renders with custom className', () => {
const customClass = 'custom-test-class'
cy.mount(<CardMultiMetrics className={customClass} />)

cy.get('[aria-description="card-metrics"]').should(
'have.class',
customClass
)
})

it('renders with ReactNode as primary content', () => {
cy.mount(
<CardMultiMetrics
primary={<div className="test-primary">Custom Primary</div>}
/>
)

cy.get('.test-primary').contains('Custom Primary').should('be.visible')
})

it('handles empty items array', () => {
cy.mount(<CardMultiMetrics items={[]} />)

// Should not render labels when items array is empty
cy.contains('Current').should('not.exist')
cy.contains('Total').should('not.exist')

// Should not render any separators
cy.get('hr.border-dotted').should('not.exist')
})
})
56 changes: 56 additions & 0 deletions components/generic-charts/card-multi-metrics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { cn } from '@/lib/utils'

export interface CardMultiMetricsProps {
primary?: string | number | React.ReactNode
items?: {
current: number
target: number
currentReadable?: string
targetReadable?: string
}[]
currentLabel?: string
targetLabel?: string
className?: string
}

export function CardMultiMetrics({
primary,
items = [],
currentLabel = 'Current',
targetLabel = 'Total',
className,
}: CardMultiMetricsProps) {
return (
<div
className={cn('flex flex-col gap-4', className)}
aria-description="card-metrics"
>
<div className="text-xl md:text-3xl">{primary}</div>

<div className="flex flex-col justify-between text-sm">
{items.length ? (
<div className="mt-2 flex flex-row justify-between font-bold">
<span className="truncate">{currentLabel}</span>
<span className="truncate">{targetLabel}</span>
</div>
) : null}

{items.map((item, i) => {
const _percent = (item.current / item.target) * 100

return (
<div key={i}>
<div className="mt-2 flex flex-row items-center justify-between gap-2">
<span className="truncate text-muted-foreground">
{item.currentReadable}
</span>
<hr className="shrink grow border-dotted" />
<span className="text-nowrap">{item.targetReadable}</span>
</div>
</div>
)
})}
</div>
</div>
)
}
42 changes: 0 additions & 42 deletions components/tremor/card-multi-metrics.tsx

This file was deleted.

Loading

1 comment on commit 4ac530c

@vercel
Copy link

@vercel vercel bot commented on 4ac530c Nov 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.