From 881d586722627e49b5903d98a0580893714d4d25 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 28 Mar 2026 21:20:06 +0100 Subject: [PATCH] scaffolder: use high-level Table component with column configs Replace manual composition of TableRoot/TableHeader/TableBody/Row/Column primitives with the top-level Table component and ColumnConfig-based column definitions. Signed-off-by: Patrik Oldsberg Made-with: Cursor --- .../RenderSchema/RenderSchema.test.tsx | 76 ++++++-- .../components/RenderSchema/RenderSchema.tsx | 180 +++++++----------- 2 files changed, 130 insertions(+), 126 deletions(-) diff --git a/plugins/scaffolder/src/components/RenderSchema/RenderSchema.test.tsx b/plugins/scaffolder/src/components/RenderSchema/RenderSchema.test.tsx index 68615426ea..4c91befb55 100644 --- a/plugins/scaffolder/src/components/RenderSchema/RenderSchema.test.tsx +++ b/plugins/scaffolder/src/components/RenderSchema/RenderSchema.test.tsx @@ -128,16 +128,39 @@ describe('JSON schema UI rendering', () => { }, }; type qt = typeof queries; + + const getRowById = (container: HTMLElement, rowId: string): HTMLElement => { + const el = container.querySelector(`[data-key="${rowId}"]`); + if (!el) { + throw new Error(`Could not find row with data-key="${rowId}"`); + } + return el as HTMLElement; + }; + + const queryRowById = ( + container: HTMLElement, + rowId: string, + ): HTMLElement | null => { + return container.querySelector(`[data-key="${rowId}"]`); + }; + + const findRowById = ( + container: HTMLElement, + rowId: string, + ): Promise => { + return waitFor(() => getRowById(container, rowId)); + }; + const assertBasicSchemaProperties = ( q: { [P in keyof qt]: BoundFunction; - }, + } & { container: HTMLElement }, id: string, ) => { - const { getByTestId, queryByTestId } = q; + const { container, queryByTestId } = q; for (const p of Object.keys(basic.properties!)) { - const tr = getByTestId(`properties-row_${id}.${p}`); + const tr = getRowById(container, `properties-row_${id}.${p}`); expect(tr).toBeInTheDocument(); const pt = (basic.properties![p] as JSONSchema7).type; @@ -197,17 +220,17 @@ describe('JSON schema UI rendering', () => { const assertDeepSchemaProperties = async ( q: { [P in keyof qt]: BoundFunction; - }, + } & { container: HTMLElement }, id: string, ) => { - const { findByTestId, getByTestId, queryByTestId } = q; + const { container, findByTestId, getByTestId, queryByTestId } = q; const xp: (k: string) => Promise<{ expander: HTMLElement; expansionId: string; expansion: HTMLElement; }> = async (k: string) => { - const r = getByTestId(`properties-row_${id}.${k}`); + const r = getRowById(container, `properties-row_${id}.${k}`); expect(r).toBeInTheDocument(); const expansionId = `expansion_${id}.${k}`; @@ -226,7 +249,10 @@ describe('JSON schema UI rendering', () => { const b = await xp('basic'); const m = await xp('msvs'); - assertBasicSchemaProperties(within(b.expansion), `${id}.basic`); + assertBasicSchemaProperties( + { ...within(b.expansion), container: b.expansion }, + `${id}.basic`, + ); assertMsv(within(m.expansion), `${id}.msvs`); await userEvent.click(b.expander); @@ -276,7 +302,10 @@ describe('JSON schema UI rendering', () => { const expanded = await findByTestId('expansion_test'); expect(expanded).toBeInTheDocument(); - assertBasicSchemaProperties(within(expanded), 'test'); + assertBasicSchemaProperties( + { ...within(expanded), container: expanded }, + 'test', + ); await userEvent.click(expand); @@ -325,7 +354,10 @@ describe('JSON schema UI rendering', () => { const expansion = await findByTestId('expansion_test'); expect(expansion).toBeInTheDocument(); - await assertDeepSchemaProperties(within(expansion), 'test'); + await assertDeepSchemaProperties( + { ...within(expansion), container: expansion }, + 'test', + ); await userEvent.click(expand); @@ -387,22 +419,25 @@ describe('JSON schema UI rendering', () => { const rendered = await renderInTestApp( , ); - const { findByTestId, getByTestId, queryByTestId } = rendered; + const { container, getByTestId } = rendered; for (const k of Object.keys(schema.properties!)) { - const tr = getByTestId(`properties-row_test.${k}`); + const tr = getRowById(container, `properties-row_test.${k}`); expect(tr).toBeInTheDocument(); } expect( - queryByTestId('properties-row_test_oneOf0.foo'), + queryRowById(container, 'properties-row_test_oneOf0.foo'), ).not.toBeInTheDocument(); const expandOneOf = getByTestId('expand_test_oneOf'); await userEvent.click(expandOneOf); for (const [i, k] of Object.keys(schema.properties!).entries()) { - const sub = await findByTestId(`properties-row_test_oneOf${i}.${k}`); + const sub = await findRowById( + container, + `properties-row_test_oneOf${i}.${k}`, + ); expect(sub).toBeInTheDocument(); const nameCell = @@ -411,7 +446,7 @@ describe('JSON schema UI rendering', () => { expect(nameCell.textContent).toContain('*'); expect( - getByTestId(`properties-row_test_oneOf${i}.${k}Flag`), + getRowById(container, `properties-row_test_oneOf${i}.${k}Flag`), ).toBeInTheDocument(); } @@ -419,7 +454,7 @@ describe('JSON schema UI rendering', () => { await waitFor(() => { expect( - queryByTestId('properties-row_test_oneOf0.foo'), + queryRowById(container, 'properties-row_test_oneOf0.foo'), ).not.toBeInTheDocument(); }); }); @@ -466,7 +501,10 @@ describe('JSON schema UI rendering', () => { for (const i of subs.keys()) { for (const k of Object.keys(subs[i].properties!)) { expect( - await rendered.findByTestId(`properties-row_test_oneOf${i}.${k}`), + await findRowById( + rendered.container, + `properties-row_test_oneOf${i}.${k}`, + ), ).toBeInTheDocument(); } } @@ -491,17 +529,17 @@ describe('JSON schema UI rendering', () => { ); expect( - rendered.queryByTestId('root-row_test.bs_anyOf0'), + queryRowById(rendered.container, 'root-row_test.bs_anyOf0'), ).not.toBeInTheDocument(); const expandAnyOf = rendered.getByTestId('expand_test.bs_anyOf'); await userEvent.click(expandAnyOf); expect( - await rendered.findByTestId('root-row_test.bs_anyOf0'), + await findRowById(rendered.container, 'root-row_test.bs_anyOf0'), ).toBeInTheDocument(); expect( - rendered.getByTestId('root-row_test.bs_anyOf1'), + getRowById(rendered.container, 'root-row_test.bs_anyOf1'), ).toBeInTheDocument(); }); }); diff --git a/plugins/scaffolder/src/components/RenderSchema/RenderSchema.tsx b/plugins/scaffolder/src/components/RenderSchema/RenderSchema.tsx index 6387739ebd..45a195e0d3 100644 --- a/plugins/scaffolder/src/components/RenderSchema/RenderSchema.tsx +++ b/plugins/scaffolder/src/components/RenderSchema/RenderSchema.tsx @@ -19,12 +19,9 @@ import { Button, Cell, CellText, - Column, + ColumnConfig, Flex, - Row, - TableBody, - TableHeader, - TableRoot, + Table, Text, Tooltip, TooltipTrigger, @@ -34,7 +31,7 @@ import { JSONSchema7Definition, JSONSchema7Type, } from 'json-schema'; -import { FC, JSX } from 'react'; +import { FC } from 'react'; import { scaffolderTranslationRef } from '../../translation'; import { SchemaRenderContext, SchemaRenderStrategy } from './types'; @@ -123,29 +120,20 @@ const getSubschemas = (schema: JSONSchema7Definition): subSchemasType => { ); }; -type SchemaRenderElement = { +interface SchemaTableItem { + id: string; schema: JSONSchema7Definition; - key?: string; + propKey?: string; required?: boolean; -}; - -type RenderColumn = ( - element: SchemaRenderElement, - context: SchemaRenderContext, -) => JSX.Element; - -type ColumnDef = { - key: string; - title: string; - render: RenderColumn; - width?: number | `${number}fr`; -}; +} const generateId = ( - element: SchemaRenderElement, + item: { propKey?: string }, context: SchemaRenderContext, ) => { - return element.key ? `${context.parentId}.${element.key}` : context.parentId; + return item.propKey + ? `${context.parentId}.${item.propKey}` + : context.parentId; }; const enumFrom = (schema: JSONSchema7) => { @@ -247,41 +235,48 @@ export const RenderSchema = ({ }) => { const { t } = useTranslationRef(scaffolderTranslationRef); + const columnConfig: ColumnConfig[] = + strategy === 'root' + ? [ + { + id: 'value', + label: t('renderSchema.tableCell.value'), + defaultWidth: '3fr' as any, + cell: item => , + }, + ] + : [ + { + id: 'name', + label: t('renderSchema.tableCell.name'), + isRowHeader: true, + defaultWidth: 200, + cell: item => { + const name = item.propKey ?? ''; + return ; + }, + }, + { + id: 'value', + label: t('renderSchema.tableCell.value'), + defaultWidth: '1fr' as any, + cell: item => , + }, + ]; + const result = (() => { if (typeof schema === 'object') { const subschemas = getSubschemas(schema); - let columns: ColumnDef[] | undefined; - let elements: SchemaRenderElement[] | undefined; + let data: SchemaTableItem[] | undefined; if (strategy === 'root') { if ('type' in schema || !Object.keys(subschemas).length) { - elements = [{ schema }]; - columns = [ - { - key: 'value', - title: t('renderSchema.tableCell.value'), - render: renderValueCell, - width: '3fr', - }, - ]; + data = [{ id: `root-row_${context.parentId}`, schema }]; } } else if (schema.properties) { - columns = [ - { - key: 'name', - title: t('renderSchema.tableCell.name'), - render: renderNameCell, - width: 200, - }, - { - key: 'value', - title: t('renderSchema.tableCell.value'), - render: renderValueCell, - width: '1fr', - }, - ]; - elements = Object.entries(schema.properties!).map(([key, v]) => ({ + data = Object.entries(schema.properties).map(([key, v]) => ({ + id: `${strategy}-row_${context.parentId}.${key}`, schema: v, - key, + propKey: key, required: schema.required?.includes(key), })); } else if (!Object.keys(subschemas).length) { @@ -291,36 +286,14 @@ export const RenderSchema = ({ return ( - {columns && elements && ( - - - {columns.map((col, index) => ( - - {col.title} - - ))} - - - {elements.map(el => ( - - {col => col.render(el, context)} - - ))} - - + {data && ( +
+ + )} {(Object.keys(subschemas) as Array).map(sk => { const subId = `${context.parentId}_${sk}`; @@ -369,20 +342,18 @@ export const RenderSchema = ({ }; function RenderExpansion({ - element, + item, context, }: { - element: SchemaRenderElement; + item: SchemaTableItem; context: SchemaRenderContext; }) { - const id = generateId(element, context); - const info = inspectSchema(element.schema); + const id = generateId(item, context); + const info = inspectSchema(item.schema); const hasDetails = - typeof element.schema !== 'boolean' && (info.canSubschema || info.hasEnum); + typeof item.schema !== 'boolean' && (info.canSubschema || info.hasEnum); const s = - typeof element.schema !== 'boolean' - ? (element.schema as JSONSchema7) - : undefined; + typeof item.schema !== 'boolean' ? (item.schema as JSONSchema7) : undefined; const [isExpanded] = context.expanded; const isOpen = hasDetails && s && (!getTypes(s) || isExpanded[id]); @@ -417,26 +388,21 @@ function RenderExpansion({ ); } -function renderNameCell( - element: SchemaRenderElement, - _context: SchemaRenderContext, -) { - const name = element.key ?? ''; - return ; -} - -function renderValueCell( - element: SchemaRenderElement, - context: SchemaRenderContext, -) { - if (typeof element.schema === 'boolean') { - return ; +function ValueCell({ + item, + context, +}: { + item: SchemaTableItem; + context: SchemaRenderContext; +}) { + if (typeof item.schema === 'boolean') { + return ; } - const types = getTypes(element.schema); + const types = getTypes(item.schema); const [isExpanded, setIsExpanded] = context.expanded; - const id = generateId(element, context); - const info = inspectSchema(element.schema); - const description = element.schema.description; + const id = generateId(item, context); + const info = inspectSchema(item.schema); + const description = item.schema.description; return ( @@ -470,7 +436,7 @@ function renderValueCell( )} {description && } - + );