From 753e0dd3b599b14cd60417fa0a4f53e9735de348 Mon Sep 17 00:00:00 2001 From: Alex Fedotyev <61838744+alex-fedotyev@users.noreply.github.com> Date: Tue, 21 Jul 2026 15:53:08 +0000 Subject: [PATCH 1/2] feat(api): expose table tile alternateRowBackground in the external Dashboards API Follow-up to #2519, which added the Alternate Row Background display setting to table tiles in the app and internal schema. That field lives on the shared chart-settings schema, so it applies to both builder and raw SQL table tiles. This exposes it in the external REST Dashboards API on both the builder and raw SQL table chart configs, alongside the OpenAPI docs, so API consumers can set striping wherever the UI allows it. Round-trip integration coverage is extended for POST and PUT on both config kinds. Co-Authored-By: Claude Opus 4.8 --- .../table-tile-alternate-row-external-api.md | 7 +++++++ packages/api/openapi.json | 12 ++++++++++++ .../external-api/__tests__/dashboards.int.test.ts | 4 ++++ .../api/src/routers/external-api/v2/dashboards.ts | 14 ++++++++++++++ .../routers/external-api/v2/utils/dashboards.ts | 10 ++++++++++ packages/api/src/utils/zod.ts | 2 ++ 6 files changed, 49 insertions(+) create mode 100644 .changeset/table-tile-alternate-row-external-api.md diff --git a/.changeset/table-tile-alternate-row-external-api.md b/.changeset/table-tile-alternate-row-external-api.md new file mode 100644 index 0000000000..a50e32c332 --- /dev/null +++ b/.changeset/table-tile-alternate-row-external-api.md @@ -0,0 +1,7 @@ +--- +"@hyperdx/api": patch +--- + +feat(api): expose table tile alternate row background in the external Dashboards API + +Add the optional `alternateRowBackground` boolean to the external REST Dashboards API for both builder and raw SQL table charts, matching the display setting available in the app. When true, the table tile renders alternating row background colors (zebra striping) for easier scanning on wide tables; it defaults to false. diff --git a/packages/api/openapi.json b/packages/api/openapi.json index 4c076f92fa..8a4343133b 100644 --- a/packages/api/openapi.json +++ b/packages/api/openapi.json @@ -1767,6 +1767,12 @@ "default": false, "example": false }, + "alternateRowBackground": { + "type": "boolean", + "description": "When true, apply alternating row background colors (zebra striping) to improve readability of wide tables. Defaults to false.\n", + "default": false, + "example": false + }, "onClick": { "$ref": "#/components/schemas/OnClick", "description": "Optional link-out configuration applied when a user clicks a row." @@ -2259,6 +2265,12 @@ "description": "Display as a table chart.", "example": "table" }, + "alternateRowBackground": { + "type": "boolean", + "description": "When true, apply alternating row background colors (zebra striping) to improve readability of wide tables. Defaults to false.\n", + "default": false, + "example": false + }, "onClick": { "$ref": "#/components/schemas/OnClick", "description": "Optional link-out configuration applied when a user clicks a row." diff --git a/packages/api/src/routers/external-api/__tests__/dashboards.int.test.ts b/packages/api/src/routers/external-api/__tests__/dashboards.int.test.ts index d1b7323eb4..5c7296f9cd 100644 --- a/packages/api/src/routers/external-api/__tests__/dashboards.int.test.ts +++ b/packages/api/src/routers/external-api/__tests__/dashboards.int.test.ts @@ -2355,6 +2355,7 @@ describe('External API v2 Dashboards - new format', () => { average: true, }, groupByColumnsOnLeft: true, + alternateRowBackground: true, onClick: { type: 'search', target: { @@ -2945,6 +2946,7 @@ describe('External API v2 Dashboards - new format', () => { sqlTemplate, sourceId, numberFormat: { output: 'percent', mantissa: 1 }, + alternateRowBackground: true, onClick: { type: 'search', target: { @@ -4080,6 +4082,7 @@ describe('External API v2 Dashboards - new format', () => { average: true, }, groupByColumnsOnLeft: true, + alternateRowBackground: true, onClick: { type: 'search', target: { @@ -4308,6 +4311,7 @@ describe('External API v2 Dashboards - new format', () => { sqlTemplate, sourceId, numberFormat: { output: 'percent', mantissa: 1 }, + alternateRowBackground: true, onClick: { type: 'dashboard', target: { diff --git a/packages/api/src/routers/external-api/v2/dashboards.ts b/packages/api/src/routers/external-api/v2/dashboards.ts index 3bb6749daf..aad46efb38 100644 --- a/packages/api/src/routers/external-api/v2/dashboards.ts +++ b/packages/api/src/routers/external-api/v2/dashboards.ts @@ -732,6 +732,13 @@ const EXTERNAL_DASHBOARD_PROJECTION = { * in the table. Defaults to false (Group By columns on the right). * default: false * example: false + * alternateRowBackground: + * type: boolean + * description: > + * When true, apply alternating row background colors (zebra striping) + * to improve readability of wide tables. Defaults to false. + * default: false + * example: false * onClick: * $ref: '#/components/schemas/OnClick' * description: Optional link-out configuration applied when a user clicks a row. @@ -1139,6 +1146,13 @@ const EXTERNAL_DASHBOARD_PROJECTION = { * enum: [table] * description: Display as a table chart. * example: "table" + * alternateRowBackground: + * type: boolean + * description: > + * When true, apply alternating row background colors (zebra + * striping) to improve readability of wide tables. Defaults to false. + * default: false + * example: false * onClick: * $ref: '#/components/schemas/OnClick' * description: Optional link-out configuration applied when a user clicks a row. diff --git a/packages/api/src/routers/external-api/v2/utils/dashboards.ts b/packages/api/src/routers/external-api/v2/utils/dashboards.ts index 638efbcb1b..5185a8362d 100644 --- a/packages/api/src/routers/external-api/v2/utils/dashboards.ts +++ b/packages/api/src/routers/external-api/v2/utils/dashboards.ts @@ -220,6 +220,7 @@ const convertToExternalTileChartConfig = ( sqlTemplate: config.sqlTemplate, sourceId: config.source, numberFormat: config.numberFormat, + alternateRowBackground: config.alternateRowBackground, onClick: config.onClick, }; case DisplayType.Number: @@ -372,6 +373,7 @@ const convertToExternalTileChartConfig = ( 'having', 'numberFormat', 'groupByColumnsOnLeft', + 'alternateRowBackground', 'onClick', ]), displayType: config.displayType, @@ -666,6 +668,13 @@ export function convertToInternalTileConfig( externalConfig.displayType === 'table' ? externalConfig.onClick : undefined, + // Zebra striping is a table-only presentational flag that lives on + // the shared raw SQL config; only table tiles honor it, mirroring + // onClick above. `_.omitBy(_.isNil)` below drops it for other types. + alternateRowBackground: + externalConfig.displayType === 'table' + ? externalConfig.alternateRowBackground + : undefined, // Only the raw SQL number variant carries `color`; table and pie // do not expose it. `_.omitBy(_.isNil)` below drops it when absent. color: @@ -714,6 +723,7 @@ export function convertToInternalTileConfig( 'having', 'orderBy', 'groupByColumnsOnLeft', + 'alternateRowBackground', 'onClick', ]), displayType: DisplayType.Table, diff --git a/packages/api/src/utils/zod.ts b/packages/api/src/utils/zod.ts index 42f11afeab..865577b48d 100644 --- a/packages/api/src/utils/zod.ts +++ b/packages/api/src/utils/zod.ts @@ -301,12 +301,14 @@ const externalDashboardTableChartConfigSchema = z.object({ asRatio: z.boolean().optional(), numberFormat: NumberFormatSchema.optional(), groupByColumnsOnLeft: z.boolean().optional(), + alternateRowBackground: z.boolean().optional(), onClick: externalOnClickSchema.optional(), }); const externalDashboardTableRawSqlChartConfigSchema = externalDashboardRawSqlChartConfigBaseSchema.extend({ displayType: z.literal('table'), + alternateRowBackground: z.boolean().optional(), onClick: externalOnClickSchema.optional(), }); From 11e1ef69609be724d2d364aebc2e4adf89417e1a Mon Sep 17 00:00:00 2001 From: Alex Fedotyev <61838744+alex-fedotyev@users.noreply.github.com> Date: Thu, 23 Jul 2026 15:47:12 +0000 Subject: [PATCH 2/2] test(api): pin alternateRowBackground omit and explicit-false round-trip Adds a table-tile case asserting that omitting alternateRowBackground leaves it absent on read-back, and that an explicit false persists, mirroring the existing omits-orderBy coverage. Addresses a review nit on the field's test coverage. Co-Authored-By: Claude Opus 4.8 --- .../__tests__/dashboards.int.test.ts | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/packages/api/src/routers/external-api/__tests__/dashboards.int.test.ts b/packages/api/src/routers/external-api/__tests__/dashboards.int.test.ts index 5c7296f9cd..9cf3178aa8 100644 --- a/packages/api/src/routers/external-api/__tests__/dashboards.int.test.ts +++ b/packages/api/src/routers/external-api/__tests__/dashboards.int.test.ts @@ -2626,6 +2626,65 @@ describe('External API v2 Dashboards - new format', () => { expect(response.body.data.tiles[0].config).not.toHaveProperty('orderBy'); }); + it('omits alternateRowBackground on a table tile when not provided, and persists explicit false', async () => { + const tableNoStripe: ExternalDashboardTile = { + name: 'Table without stripe setting', + x: 0, + y: 0, + w: 6, + h: 3, + config: { + displayType: 'table', + sourceId: traceSource._id.toString(), + select: [ + { + aggFn: 'count', + alias: 'Count', + where: '', + whereLanguage: 'sql', + }, + ], + groupBy: 'ServiceName', + }, + }; + + const tableStripeOff: ExternalDashboardTile = { + name: 'Table with stripe explicitly off', + x: 6, + y: 0, + w: 6, + h: 3, + config: { + displayType: 'table', + sourceId: traceSource._id.toString(), + select: [ + { + aggFn: 'count', + alias: 'Count', + where: '', + whereLanguage: 'sql', + }, + ], + groupBy: 'ServiceName', + alternateRowBackground: false, + }, + }; + + const response = await authRequest('post', BASE_URL) + .send({ + name: 'Dashboard table stripe defaults', + tiles: [tableNoStripe, tableStripeOff], + }) + .expect(200); + + expect(response.body.data.tiles[0].config).not.toHaveProperty( + 'alternateRowBackground', + ); + expect(response.body.data.tiles[1].config.alternateRowBackground).toBe( + false, + ); + }); + // Schema-level rejections that exercise pure Zod constraints // (discriminated-union absence, `min(1)` on valueExpression, and // `length(1)` on the select array). The non-Trace-source case