From cd8e176ff5104e739c52966f66854fc3c33d9ff2 Mon Sep 17 00:00:00 2001 From: Dazhan Date: Thu, 18 Jun 2026 15:00:18 +0800 Subject: [PATCH 1/4] feat(ui-dashboard): expand ChartConfigLike contract for chart options (3b) Foundation for migrating the chart render/option components. Expands the minimal ChartConfigLike (was just yAxis) into a full structural mirror of the proto ChartConfig tree, plus the chart-config enums: - enums: Calculation / Direction / MarkerType / SortBy / ValueFormatter / ValueStyle / PieType / LineStyle - chart.ts: xAxis, lineConfig, valueConfig (+ mappingRules/colorTheme), pieConfig, barGauge, queryValueConfig, tableConfig (+ columnSort), labelConfig, scatterConfig, seriesConfig, dataConfig, timeRangeOverride (timeRange payload opaque), markers All fields optional so a consumer's generated ChartConfig assigns structurally. No proto/private imports. build + tsc clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/ui-dashboard/src/types/chart.ts | 189 ++++++++++++++++++++++- packages/ui-dashboard/src/types/enums.ts | 26 ++++ 2 files changed, 209 insertions(+), 6 deletions(-) diff --git a/packages/ui-dashboard/src/types/chart.ts b/packages/ui-dashboard/src/types/chart.ts index a6ea17c..f60bb9c 100644 --- a/packages/ui-dashboard/src/types/chart.ts +++ b/packages/ui-dashboard/src/types/chart.ts @@ -1,9 +1,18 @@ +import type { DurationLike } from '@sentio/ui-core' import type { ChartTypeLike, DataSourceTypeLike, NoteFontSizeLike, NoteAlignmentLike, - NoteVerticalAlignmentLike + NoteVerticalAlignmentLike, + CalculationLike, + DirectionLike, + MarkerTypeLike, + SortByLike, + ValueFormatterLike, + ValueStyleLike, + PieTypeLike, + LineStyleLike } from './enums' // Type-only import; the chart <-> dashboard type cycle is erased at compile time. import type { GroupLike } from './dashboard' @@ -11,12 +20,18 @@ import type { GroupLike } from './dashboard' /* * Chart-level shapes (chart / note / chart config / overlay graph). * - * NOTE: ChartConfigLike is intentionally a MINIMAL subset for now. The full set - * of fields (xAxis, tableConfig, pieConfig, valueConfig, mapping rules, color - * themes, etc.) is fleshed out alongside the chart-rendering components, adding - * only the fields those components actually read. + * ChartConfigLike and its sub-interfaces are structural mirrors of the proto + * `ChartConfig` message tree (all fields optional) — they back the chart render + * components and the per-chart option panels. Deep payloads that the option + * panels don't edit (e.g. an overridden TimeRange) are kept opaque (`unknown`). */ +/** Sort key + direction (x-axis, bar-gauge). */ +export interface SortLike { + sortBy?: SortByLike + orderDesc?: boolean +} + /** Y-axis configuration. */ export interface YAxisConfigLike { min?: string @@ -27,9 +42,171 @@ export interface YAxisConfigLike { name?: string } -/** Minimal subset of a chart's config. Extended alongside chart components. */ +/** X-axis configuration. */ +export interface XAxisConfigLike { + type?: string + min?: string + max?: string + scale?: boolean + name?: string + column?: string + sort?: SortLike + format?: string +} + +/** Text/background color overrides for value + mapping rules. */ +export interface ColorThemeLike { + textColor?: string + backgroundColor?: string + themeType?: string +} + +/** A value→text/color mapping rule. */ +export interface MappingRuleLike { + comparison?: string + value?: number | 'NaN' | 'Infinity' | '-Infinity' + text?: string + colorTheme?: ColorThemeLike +} + +/** Number/string/date value formatting. */ +export interface ValueConfigLike { + valueFormatter?: ValueFormatterLike + showValueLabel?: boolean + maxSignificantDigits?: number + dateFormat?: string + mappingRules?: MappingRuleLike[] + style?: ValueStyleLike + maxFractionDigits?: number + precision?: number + currencySymbol?: string + tooltipTotal?: boolean + prefix?: string + suffix?: string +} + +/** Line chart style. */ +export interface LineConfigLike { + style?: LineStyleLike + smooth?: boolean +} + +/** Pie/donut config. */ +export interface PieConfigLike { + pieType?: PieTypeLike + showPercent?: boolean + showValue?: boolean + calculation?: CalculationLike + absValue?: boolean +} + +/** Bar-gauge config. */ +export interface BarGaugeConfigLike { + direction?: DirectionLike + calculation?: CalculationLike + sort?: SortLike +} + +/** Query-value (single big number) config. */ +export interface QueryValueConfigLike { + colorTheme?: ColorThemeLike + showBackgroundChart?: boolean + calculation?: CalculationLike + seriesCalculation?: CalculationLike +} + +/** Per-column label config (event/log tables). */ +export interface LabelConfigColumnLike { + name?: string + showLabel?: boolean + showValue?: boolean +} + +/** Series label config. */ +export interface LabelConfigLike { + columns?: LabelConfigColumnLike[] + alias?: string +} + +/** Scatter chart config. */ +export interface ScatterConfigLike { + symbolSize?: string + color?: string + minSize?: number + maxSize?: number +} + +/** A mark line/area on a chart. */ +export interface MarkerLike { + type?: MarkerTypeLike + value?: number | 'NaN' | 'Infinity' | '-Infinity' + color?: string + label?: string + valueX?: string +} + +/** Series count limit. */ +export interface DataConfigLike { + seriesLimit?: number +} + +/** Per-series override (currently just chart type). */ +export interface SeriesConfigSeriesLike { + type?: ChartTypeLike +} + +/** Per-series config keyed by series id. */ +export interface SeriesConfigLike { + series?: { [key: string]: SeriesConfigSeriesLike } +} + +/** Table column sort entry. */ +export interface ColumnSortLike { + column?: string + orderDesc?: boolean +} + +/** Table chart config. */ +export interface TableConfigLike { + calculation?: CalculationLike + showColumns?: { [key: string]: boolean } + sortColumns?: ColumnSortLike[] + columnOrders?: string[] + columnWidths?: { [key: string]: number } + showPlainData?: boolean + calculations?: { [key: string]: CalculationLike } + valueConfigs?: { [key: string]: ValueConfigLike } + rowLimit?: number +} + +/** Compare-to-previous-period offset. */ +export interface CompareTimeLike { + ago?: DurationLike +} + +/** Per-chart time-range override. `timeRange` payload kept opaque. */ +export interface TimeRangeOverrideLike { + enabled?: boolean + timeRange?: unknown + compareTime?: CompareTimeLike +} + +/** A chart's full config — structural mirror of proto `ChartConfig`. */ export interface ChartConfigLike { yAxis?: YAxisConfigLike + xAxis?: XAxisConfigLike + lineConfig?: LineConfigLike + valueConfig?: ValueConfigLike + pieConfig?: PieConfigLike + barGauge?: BarGaugeConfigLike + queryValueConfig?: QueryValueConfigLike + tableConfig?: TableConfigLike + labelConfig?: LabelConfigLike + scatterConfig?: ScatterConfigLike + seriesConfig?: SeriesConfigLike + dataConfig?: DataConfigLike + timeRangeOverride?: TimeRangeOverrideLike + markers?: MarkerLike[] } /** A note (text) panel's content and styling. */ diff --git a/packages/ui-dashboard/src/types/enums.ts b/packages/ui-dashboard/src/types/enums.ts index e438574..d0e6abd 100644 --- a/packages/ui-dashboard/src/types/enums.ts +++ b/packages/ui-dashboard/src/types/enums.ts @@ -47,3 +47,29 @@ export type NoteAlignmentLike = 'LEFT' | 'CENTER' | 'RIGHT' /** Note panel vertical alignment. */ export type NoteVerticalAlignmentLike = 'TOP' | 'MIDDLE' | 'BOTTOM' + +// ── chart-config enums (mirror proto ChartConfig.* enums) ────────────────── + +/** Series/value aggregation calculation. */ +export type CalculationLike = 'LAST' | 'FIRST' | 'MEAN' | 'TOTAL' | 'ALL' | 'MIN' | 'MAX' + +/** Bar-gauge layout direction. */ +export type DirectionLike = 'HORIZONTAL' | 'VERTICAL' + +/** Mark type for chart markers. */ +export type MarkerTypeLike = 'LINE' | 'AREA' | 'LINEX' + +/** Sort key for axis/bar ordering. */ +export type SortByLike = 'ByName' | 'ByValue' + +/** Value formatter family. */ +export type ValueFormatterLike = 'NumberFormatter' | 'DateFormatter' | 'StringFormatter' + +/** Number value display style. */ +export type ValueStyleLike = 'Standard' | 'Compact' | 'Scientific' | 'Percent' | 'Currency' | 'None' + +/** Pie vs donut. */ +export type PieTypeLike = 'Pie' | 'Donut' + +/** Line style. */ +export type LineStyleLike = 'Solid' | 'Dotted' From e05d64e518b6e3234f0a4b662a3003a7337cd5a8 Mon Sep 17 00:00:00 2001 From: Dazhan Date: Thu, 18 Jun 2026 15:11:56 +0800 Subject: [PATCH 2/4] feat(ui-dashboard): migrate pure chart option panels (0.2.1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First batch of chart option panels into ui-dashboard (phase 3b) — the ones that only need ui-core atoms + the ChartConfigLike contract, no data/sql coupling: - LineControls, LabelControls, PieChartControls, BarGaugeControls proto ChartConfig_* types -> *Like contracts; DisclosurePanel/Checkbox/Button/ NewButtonGroup/HelpIcon from @sentio/ui-core; strict-mode param types. Ladle stories added. build + tsc clean. Deferred to later 3b slices: the value-formatting trio (needs query-value-theme relocation), QueryValueControls (ColorSelect), and the data/sql-coupled panels (Table/Scatter/Yaxis/Xaxis/Data/Series/TimeRangeOverride). Bumps 0.2.0 -> 0.2.1. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/ui-dashboard/package.json | 2 +- packages/ui-dashboard/src/charts/index.ts | 3 + .../src/charts/options/BarGaugeControls.tsx | 163 ++++++++++++++++++ .../src/charts/options/LabelControls.tsx | 108 ++++++++++++ .../src/charts/options/LineControls.tsx | 55 ++++++ .../charts/options/OptionPanels.stories.tsx | 82 +++++++++ .../src/charts/options/PieChartControls.tsx | 116 +++++++++++++ .../ui-dashboard/src/charts/options/index.ts | 12 ++ 8 files changed, 540 insertions(+), 1 deletion(-) create mode 100644 packages/ui-dashboard/src/charts/options/BarGaugeControls.tsx create mode 100644 packages/ui-dashboard/src/charts/options/LabelControls.tsx create mode 100644 packages/ui-dashboard/src/charts/options/LineControls.tsx create mode 100644 packages/ui-dashboard/src/charts/options/OptionPanels.stories.tsx create mode 100644 packages/ui-dashboard/src/charts/options/PieChartControls.tsx create mode 100644 packages/ui-dashboard/src/charts/options/index.ts diff --git a/packages/ui-dashboard/package.json b/packages/ui-dashboard/package.json index 18a24d5..a89905c 100644 --- a/packages/ui-dashboard/package.json +++ b/packages/ui-dashboard/package.json @@ -1,7 +1,7 @@ { "private": false, "name": "@sentio/ui-dashboard", - "version": "0.2.0", + "version": "0.2.1", "description": "Dashboard UI components for the Sentio platform, decoupled from backend services.", "exports": { ".": { diff --git a/packages/ui-dashboard/src/charts/index.ts b/packages/ui-dashboard/src/charts/index.ts index 8e52abd..57dd2d0 100644 --- a/packages/ui-dashboard/src/charts/index.ts +++ b/packages/ui-dashboard/src/charts/index.ts @@ -14,6 +14,9 @@ export { ChartLegend } from './ChartLegend' // Refresh affordance export { RefreshContext, RefreshButton } from './RefreshContext' +// Option panels +export * from './options' + // Theme export { sentioColors } from './theme/sentio-colors' export { sentioTheme, sentioThemeDark } from './theme/sentio-theme' diff --git a/packages/ui-dashboard/src/charts/options/BarGaugeControls.tsx b/packages/ui-dashboard/src/charts/options/BarGaugeControls.tsx new file mode 100644 index 0000000..27d4763 --- /dev/null +++ b/packages/ui-dashboard/src/charts/options/BarGaugeControls.tsx @@ -0,0 +1,163 @@ +import { produce } from 'immer' +import { defaults } from 'lodash' +import { DisclosurePanel } from '@sentio/ui-core' +import type { + BarGaugeConfigLike, + CalculationLike, + DirectionLike, + SortByLike +} from '../../types' + +interface Props { + config?: BarGaugeConfigLike + defaultOpen?: boolean + onChange: (config: BarGaugeConfigLike) => void +} + +export const defaultConfig: BarGaugeConfigLike = { + direction: 'HORIZONTAL', + calculation: 'LAST', + sort: { + sortBy: 'ByName', + orderDesc: true + } +} + +const directionItems = [ + { label: 'Horizontal', value: 'HORIZONTAL' }, + { label: 'Vertical', value: 'VERTICAL' } +] + +const CalculationItems = [ + { label: 'Last', value: 'LAST' }, + { label: 'First', value: 'FIRST' }, + { label: 'Total', value: 'TOTAL' }, + { label: 'Mean', value: 'MEAN' }, + { label: 'Max', value: 'MAX' }, + { label: 'Min', value: 'MIN' } +] + +const sortByItems = [ + { label: 'Name', value: 'ByName' }, + { label: 'Value', value: 'ByValue' } +] + +const orderItems = [ + { label: 'Ascendant', value: 'false' }, + { label: 'Descendant', value: 'true' } +] + +export function BarGaugeControls({ config, defaultOpen, onChange }: Props) { + config = defaults(config, defaultConfig) + + function onCalculationChange(cal: CalculationLike) { + config && + onChange(produce(config, (draft) => void (draft.calculation = cal))) + } + + function onDirectionChange(dir: DirectionLike) { + config && onChange(produce(config, (draft) => void (draft.direction = dir))) + } + + function onOrderChange(orderDesc: boolean) { + config && + onChange( + produce(config, (draft) => { + draft.sort = draft.sort || {} + draft.sort.orderDesc = orderDesc + }) + ) + } + + function onSortByChange(sortBy: SortByLike) { + config && + onChange( + produce(config, (draft) => { + draft.sort = draft.sort || {} + draft.sort.sortBy = sortBy + }) + ) + } + + return ( + +
+
+ + Direction + + +
+ +
+ + Calculation + + +
+ +
+ + Sort by + + + +
+
+
+ ) +} diff --git a/packages/ui-dashboard/src/charts/options/LabelControls.tsx b/packages/ui-dashboard/src/charts/options/LabelControls.tsx new file mode 100644 index 0000000..b194e86 --- /dev/null +++ b/packages/ui-dashboard/src/charts/options/LabelControls.tsx @@ -0,0 +1,108 @@ +import { useEffect, useMemo } from 'react' +import { produce } from 'immer' +import { Button as NewButton, DisclosurePanel, HelpIcon } from '@sentio/ui-core' +import type { LabelConfigLike } from '../../types' + +interface Props { + config?: LabelConfigLike + setConfig: (value: LabelConfigLike) => void + defaultOpen?: boolean +} + +const initialConfig: LabelConfigLike = { + columns: [], + alias: '' +} + +export const LabelControls = ({ config, setConfig, defaultOpen }: Props) => { + // Migrate existing columns config to alias on component mount + useEffect(() => { + if (config?.columns && config.columns.length > 0 && !config.alias) { + const aliasParts: string[] = [] + config.columns.forEach((colConfig) => { + if (!colConfig.name) return // Skip if name is undefined + + if (colConfig.showLabel === false && colConfig.showValue === false) { + // ignore + } else if (colConfig.showValue === false) { + aliasParts.push(colConfig.name) + } else { + aliasParts.push(`{{${colConfig.name}}}`) + } + }) + + if (aliasParts.length > 0) { + const migratedAlias = aliasParts.join(', ') + setConfig( + produce(config, (draft) => { + draft.alias = migratedAlias + draft.columns = [] // Clear the old columns config + }) + ) + } + } + }, [config, setConfig]) + + const onAliasChanged = (alias: string) => { + setConfig( + produce(config ?? initialConfig, (draft) => { + draft.alias = alias + }) + ) + } + + const _defaultOpen = useMemo(() => { + if (defaultOpen) { + return true + } + return ( + config?.alias !== '' || (config?.columns && config.columns.length > 0) + ) + }, [config, defaultOpen]) + + return ( + +
+
+ + Label Alias + +
Series name override or template.
+
+ Ex.{' '} + + {'{{contract}}'} + {' '} + will be replaced with the value of the contract label. +
+
+ } + /> + + onAliasChanged(e.target.value)} + placeholder="Enter alias..." + className="focus:border-primary-500 sm:text-icontent border-main inline-flex w-64 items-center rounded-r-md border px-2" + /> +
+ { + setConfig(initialConfig) + }} + > + Reset + + +
+ ) +} diff --git a/packages/ui-dashboard/src/charts/options/LineControls.tsx b/packages/ui-dashboard/src/charts/options/LineControls.tsx new file mode 100644 index 0000000..ca67fdc --- /dev/null +++ b/packages/ui-dashboard/src/charts/options/LineControls.tsx @@ -0,0 +1,55 @@ +import { produce } from 'immer' +import { + DisclosurePanel, + NewButtonGroup as ButtonGroup, + Checkbox +} from '@sentio/ui-core' +import type { LineConfigLike, LineStyleLike } from '../../types' + +const lineStyles: { label: string; value: LineStyleLike }[] = [ + { label: 'Solid', value: 'Solid' }, + { label: 'Dotted', value: 'Dotted' } +] + +interface Props { + config?: LineConfigLike + defaultOpen?: boolean + onChange: (config: LineConfigLike) => void +} + +export const LineControls = ({ config, defaultOpen, onChange }: Props) => { + const setStyle = (style: LineStyleLike) => { + onChange( + produce(config || {}, (draft) => { + draft.style = style + }) + ) + } + const setSmooth = (smooth: boolean) => { + onChange( + produce(config || {}, (draft) => { + draft.smooth = smooth + }) + ) + } + return ( + +
+ + +
+
+ ) +} diff --git a/packages/ui-dashboard/src/charts/options/OptionPanels.stories.tsx b/packages/ui-dashboard/src/charts/options/OptionPanels.stories.tsx new file mode 100644 index 0000000..253d7f5 --- /dev/null +++ b/packages/ui-dashboard/src/charts/options/OptionPanels.stories.tsx @@ -0,0 +1,82 @@ +import type { Story } from '@ladle/react' +import { useState } from 'react' +import { LineControls } from './LineControls' +import { LabelControls } from './LabelControls' +import { PieChartControls } from './PieChartControls' +import { BarGaugeControls } from './BarGaugeControls' +import type { + LineConfigLike, + LabelConfigLike, + PieConfigLike, + BarGaugeConfigLike +} from '../../types' + +function Frame({ + children, + value +}: { + children: React.ReactNode + value: unknown +}) { + return ( +
+ {children} +
+        {JSON.stringify(value, null, 2)}
+      
+
+ ) +} + +export const Line: Story = () => { + const [config, setConfig] = useState({ + style: 'Solid', + smooth: false + }) + return ( + + + + ) +} +Line.meta = { description: 'Line style options' } + +export const Label: Story = () => { + const [config, setConfig] = useState({ + alias: '{{contract}}', + columns: [] + }) + return ( + + + + ) +} +Label.meta = { description: 'Series label alias/template' } + +export const Pie: Story = () => { + const [config, setConfig] = useState({ + pieType: 'Pie', + calculation: 'LAST', + showValue: true + }) + return ( + + + + ) +} +Pie.meta = { description: 'Pie/donut options' } + +export const BarGauge: Story = () => { + const [config, setConfig] = useState({ + direction: 'HORIZONTAL', + calculation: 'LAST' + }) + return ( + + + + ) +} +BarGauge.meta = { description: 'Bar-gauge direction/calculation/sort options' } diff --git a/packages/ui-dashboard/src/charts/options/PieChartControls.tsx b/packages/ui-dashboard/src/charts/options/PieChartControls.tsx new file mode 100644 index 0000000..0d3aa44 --- /dev/null +++ b/packages/ui-dashboard/src/charts/options/PieChartControls.tsx @@ -0,0 +1,116 @@ +import { produce } from 'immer' +import { defaults } from 'lodash' +import { + Checkbox, + DisclosurePanel, + NewButtonGroup as ButtonGroup +} from '@sentio/ui-core' +import type { CalculationLike, PieConfigLike, PieTypeLike } from '../../types' + +interface Props { + config?: PieConfigLike + defaultOpen?: boolean + onChange: (config: PieConfigLike) => void +} + +export const defaultConfig: PieConfigLike = { + pieType: 'Pie', + calculation: 'LAST', + showPercent: true, + showValue: true, + absValue: false +} + +const CalculationItems = [ + { label: 'Last', value: 'LAST' }, + { label: 'First', value: 'FIRST' }, + { label: 'Total', value: 'TOTAL' }, + { label: 'Mean', value: 'MEAN' }, + { label: 'Max', value: 'MAX' }, + { label: 'Min', value: 'MIN' } +] + +const PieTypeItems: { label: string; value: PieTypeLike }[] = [ + { label: 'Pie', value: 'Pie' }, + { label: 'Donut', value: 'Donut' } +] + +export function PieChartControls({ config, defaultOpen, onChange }: Props) { + config = defaults(config, defaultConfig) + + function onCalculationChange(cal: CalculationLike) { + config && + onChange(produce(config, (draft) => void (draft.calculation = cal))) + } + + function onPieTypeChange(pieType: PieTypeLike) { + config && + onChange(produce(config, (draft) => void (draft.pieType = pieType))) + } + + function toggle( + field: 'showValue' | 'showPercent' | 'absValue', + value: boolean + ) { + config && + onChange( + produce(config, (draft) => { + draft[field] = value + }) + ) + } + + return ( + +
+
+ +
+
+ + Calculation + + +
+ toggle('showValue', v)} + label="Show value" + /> + toggle('showPercent', v)} + label="Show percent" + /> + toggle('absValue', v)} + label="Use absolute values" + /> +
+
+ ) +} diff --git a/packages/ui-dashboard/src/charts/options/index.ts b/packages/ui-dashboard/src/charts/options/index.ts new file mode 100644 index 0000000..5c2bb8d --- /dev/null +++ b/packages/ui-dashboard/src/charts/options/index.ts @@ -0,0 +1,12 @@ +// Chart option panels (phase 3b). Data-coupled panels (Data/Series/TimeRange/ +// Table/Scatter/Yaxis/Xaxis, value-formatting trio) follow in later slices. +export { LineControls } from './LineControls' +export { LabelControls } from './LabelControls' +export { + PieChartControls, + defaultConfig as defaultPieConfig +} from './PieChartControls' +export { + BarGaugeControls, + defaultConfig as defaultBarGaugeConfig +} from './BarGaugeControls' From 79ff435b91f2b60d141d0a2289f9162543c7f90e Mon Sep 17 00:00:00 2001 From: Dazhan Date: Thu, 18 Jun 2026 15:54:37 +0800 Subject: [PATCH 3/4] style(ui-dashboard): add whitespace-nowrap class to label elements in chart controls --- packages/ui-dashboard/src/charts/options/BarGaugeControls.tsx | 2 +- packages/ui-dashboard/src/charts/options/PieChartControls.tsx | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/ui-dashboard/src/charts/options/BarGaugeControls.tsx b/packages/ui-dashboard/src/charts/options/BarGaugeControls.tsx index 27d4763..ed9d0d9 100644 --- a/packages/ui-dashboard/src/charts/options/BarGaugeControls.tsx +++ b/packages/ui-dashboard/src/charts/options/BarGaugeControls.tsx @@ -127,7 +127,7 @@ export function BarGaugeControls({ config, defaultOpen, onChange }: Props) {
- + Sort by