From 11dc90faa23d2abe7921a9e70b8d7289e5b614bf Mon Sep 17 00:00:00 2001 From: John Redwood Date: Thu, 26 Jun 2025 17:44:41 +1000 Subject: [PATCH 001/180] fix: full contrib for report and length of audit Signed-off-by: John Redwood --- .changeset/three-mammals-move.md | 5 +++ app-config.yaml | 2 ++ plugins/scaffolder-backend/report.api.md | 3 ++ .../src/scaffolder/tasks/TaskWorker.ts | 32 ++++++++++++++++++- .../scaffolder-backend/src/service/router.ts | 1 + 5 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 .changeset/three-mammals-move.md diff --git a/.changeset/three-mammals-move.md b/.changeset/three-mammals-move.md new file mode 100644 index 0000000000..646e60f84c --- /dev/null +++ b/.changeset/three-mammals-move.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +Implement max length for scaffolder auditor audit logging with default of 256 diff --git a/app-config.yaml b/app-config.yaml index 910a3e4f99..d0189595e4 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -200,6 +200,8 @@ catalog: - allow: [Template] scaffolder: + auditor: + maxLength: 256 # Use to customize default commit author info used when new components are created defaultAuthor: name: Scaffolder diff --git a/plugins/scaffolder-backend/report.api.md b/plugins/scaffolder-backend/report.api.md index 7ece0f84ea..debc9ac60a 100644 --- a/plugins/scaffolder-backend/report.api.md +++ b/plugins/scaffolder-backend/report.api.md @@ -273,6 +273,7 @@ export type CreateWorkerOptions = { workingDirectory: string; logger: LoggerService; auditor?: AuditorService; + config?: Config; additionalTemplateFilters?: Record; concurrentTasksLimit?: number; additionalTemplateGlobals?: Record; @@ -601,6 +602,8 @@ export class TaskWorker { start(): void; // (undocumented) stop(): Promise; + // (undocumented) + protected truncateParameters(parameters: JsonObject): JsonObject; } // @public @deprecated diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index b0c3166ff6..221121b176 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -29,6 +29,8 @@ import { TemplateActionRegistry } from '../actions'; import { NunjucksWorkflowRunner } from './NunjucksWorkflowRunner'; import { WorkflowRunner } from './types'; import { setTimeout } from 'timers/promises'; +import { JsonObject } from '@backstage/types'; +import { Config } from '@backstage/config'; /** * TaskWorkerOptions @@ -44,6 +46,7 @@ export type TaskWorkerOptions = { permissions?: PermissionEvaluator; logger?: LoggerService; auditor?: AuditorService; + config?: Config; gracefulShutdown?: boolean; }; @@ -59,6 +62,7 @@ export type CreateWorkerOptions = { workingDirectory: string; logger: LoggerService; auditor?: AuditorService; + config?: Config; additionalTemplateFilters?: Record; /** * The number of tasks that can be executed at the same time by the worker @@ -87,12 +91,14 @@ export class TaskWorker { private taskQueue: PQueue; private logger: LoggerService | undefined; private auditor: AuditorService | undefined; + private config: Config | undefined; private stopWorkers: boolean; private constructor(private readonly options: TaskWorkerOptions) { this.stopWorkers = false; this.logger = options.logger; this.auditor = options.auditor; + this.config = options.config; this.taskQueue = new PQueue({ concurrency: options.concurrentTasksLimit, }); @@ -103,6 +109,7 @@ export class TaskWorker { taskBroker, logger, auditor, + config, actionRegistry, integrations, workingDirectory, @@ -130,6 +137,7 @@ export class TaskWorker { concurrentTasksLimit, permissions, auditor, + config, gracefulShutdown, }); } @@ -182,6 +190,28 @@ export class TaskWorker { }); } + protected truncateParameters(parameters: JsonObject) { + const auditMaxLength = + this.config?.getOptionalNumber('scaffolder.auditor.maxLength') ?? 256; + const truncatedParameters: JsonObject = {}; + + for (const key in parameters) { + if (Object.prototype.hasOwnProperty.call(parameters, key)) { + const rawValue = parameters[key]; + const value = rawValue?.toString(); + if (value && value.length > auditMaxLength) { + truncatedParameters[key] = value + .slice(0, auditMaxLength) + .concat('...'); + } else { + truncatedParameters[key] = rawValue; + } + } + } + + return truncatedParameters; + } + async runOneTask(task: TaskContext) { const auditorEvent = await this.auditor?.createEvent({ eventId: 'task', @@ -189,7 +219,7 @@ export class TaskWorker { meta: { actionType: 'execution', taskId: task.taskId, - taskParameters: task.spec.parameters, + taskParameters: this.truncateParameters(task.spec.parameters), templateRef: task.spec.templateInfo?.entityRef, }, }); diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 0ea6293f36..64e7dcb4b4 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -271,6 +271,7 @@ export async function createRouter( integrations, logger, auditor, + config, workingDirectory, concurrentTasksLimit, permissions, From b29c4a8a0744ef2b9c5de5130601410ee73662a5 Mon Sep 17 00:00:00 2001 From: John Redwood Date: Wed, 9 Jul 2025 18:19:24 +1000 Subject: [PATCH 002/180] fix: added ability to disable the audit logger with value set to -1 Signed-off-by: John Redwood --- .../scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index 221121b176..40a56e4368 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -193,6 +193,14 @@ export class TaskWorker { protected truncateParameters(parameters: JsonObject) { const auditMaxLength = this.config?.getOptionalNumber('scaffolder.auditor.maxLength') ?? 256; + + if (auditMaxLength === -1) { + this.logger?.debug( + `scaffolder.auditor.maxLength manually disabled via configuration, no task parameter length limit set.`, + ); + return parameters; + } + const truncatedParameters: JsonObject = {}; for (const key in parameters) { From 3025cf52807114852a77f16b852ca2e82757b8be Mon Sep 17 00:00:00 2001 From: Juan Pablo Garcia Ripa Date: Fri, 16 May 2025 11:13:42 +0200 Subject: [PATCH 003/180] remove padding from k8s content Signed-off-by: Juan Pablo Garcia Ripa --- .changeset/slick-cameras-bet.md | 5 +++++ plugins/kubernetes/src/KubernetesContent.tsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/slick-cameras-bet.md diff --git a/.changeset/slick-cameras-bet.md b/.changeset/slick-cameras-bet.md new file mode 100644 index 0000000000..4bee2cc007 --- /dev/null +++ b/.changeset/slick-cameras-bet.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes': patch +--- + +Removed the kubernetes content padding to avoid double padding on k8s entity page diff --git a/plugins/kubernetes/src/KubernetesContent.tsx b/plugins/kubernetes/src/KubernetesContent.tsx index 5a9df819c8..71f58fa3a3 100644 --- a/plugins/kubernetes/src/KubernetesContent.tsx +++ b/plugins/kubernetes/src/KubernetesContent.tsx @@ -64,7 +64,7 @@ export const KubernetesContent = ({ return ( - + Date: Wed, 9 Jul 2025 23:30:18 +0200 Subject: [PATCH 004/180] remove page and content as its already part of the entity page layout Signed-off-by: Juan Pablo Garcia Ripa --- plugins/kubernetes/src/KubernetesContent.tsx | 167 +++++++++---------- 1 file changed, 78 insertions(+), 89 deletions(-) diff --git a/plugins/kubernetes/src/KubernetesContent.tsx b/plugins/kubernetes/src/KubernetesContent.tsx index 71f58fa3a3..a8d62a5a3d 100644 --- a/plugins/kubernetes/src/KubernetesContent.tsx +++ b/plugins/kubernetes/src/KubernetesContent.tsx @@ -29,12 +29,7 @@ import { DetectedError, detectErrors, } from '@backstage/plugin-kubernetes-common'; -import { - Content, - EmptyState, - Page, - Progress, -} from '@backstage/core-components'; +import { EmptyState, Progress } from '@backstage/core-components'; import { RequireKubernetesPermissions } from './RequireKubernetesPermissions'; type KubernetesContentProps = { @@ -63,93 +58,87 @@ export const KubernetesContent = ({ : new Map(); return ( - - - - - {kubernetesObjects === undefined && error === undefined && ( - - )} + + + {kubernetesObjects === undefined && error === undefined && } - {/* errors retrieved from the kubernetes clusters */} - {clustersWithErrors.length > 0 && ( - - - - - - )} + {/* errors retrieved from the kubernetes clusters */} + {clustersWithErrors.length > 0 && ( + + + + + + )} - {/* other errors */} - {error !== undefined && ( - - - - - - )} + {/* other errors */} + {error !== undefined && ( + + + + + + )} - {kubernetesObjects && ( - - - + {kubernetesObjects && ( + + + + + + Your Clusters + + + {kubernetesObjects?.items.length <= 0 && ( + + + + - - Your Clusters - - - {kubernetesObjects?.items.length <= 0 && ( - - - - + )} + {kubernetesObjects?.items.length > 0 && + kubernetesObjects?.items.map((item, i) => { + const podsWithErrors = new Set( + detectedErrors + .get(item.cluster.name) + ?.filter(de => de.sourceRef.kind === 'Pod') + .map(de => de.sourceRef.name), + ); + + return ( + + - )} - {kubernetesObjects?.items.length > 0 && - kubernetesObjects?.items.map((item, i) => { - const podsWithErrors = new Set( - detectedErrors - .get(item.cluster.name) - ?.filter(de => de.sourceRef.kind === 'Pod') - .map(de => de.sourceRef.name), - ); - - return ( - - - - ); - })} - - - )} - - - - + ); + })} + + + )} + + ); }; From fa6fa60445d681e3cc8a62f63a0bc1190398267e Mon Sep 17 00:00:00 2001 From: Jamie Tang Date: Wed, 9 Jul 2025 14:18:05 -0700 Subject: [PATCH 005/180] fix getLocationByEntity to use original_value Signed-off-by: Jamie Tang --- .changeset/shaggy-parrots-deny.md | 5 +++++ .../catalog-backend/src/providers/DefaultLocationStore.ts | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 .changeset/shaggy-parrots-deny.md diff --git a/.changeset/shaggy-parrots-deny.md b/.changeset/shaggy-parrots-deny.md new file mode 100644 index 0000000000..1c8d8bad7e --- /dev/null +++ b/.changeset/shaggy-parrots-deny.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Fixed getLocationByEntity to use `original_value` instead of `value` when querying search table diff --git a/plugins/catalog-backend/src/providers/DefaultLocationStore.ts b/plugins/catalog-backend/src/providers/DefaultLocationStore.ts index 2b3459ecb1..8420cbb8c4 100644 --- a/plugins/catalog-backend/src/providers/DefaultLocationStore.ts +++ b/plugins/catalog-backend/src/providers/DefaultLocationStore.ts @@ -137,15 +137,15 @@ export class DefaultLocationStore implements LocationStore, EntityProvider { entity_id: entityRow.entity_id, key: `metadata.annotations.${ANNOTATION_ORIGIN_LOCATION}`, }) - .select('value') + .select('original_value') .limit(1); - if (!searchRow?.value) { + if (!searchRow?.original_value) { throw new NotFoundError( `found no origin annotation for ref ${entityRefString}`, ); } - const { type, target } = parseLocationRef(searchRow.value); + const { type, target } = parseLocationRef(searchRow.original_value); const [locationRow] = await this.db('locations') .where({ type, target }) .select() From 4d63ed24235b8a4ac4126d38e185465ba2af4943 Mon Sep 17 00:00:00 2001 From: Jamie Tang Date: Wed, 9 Jul 2025 15:11:57 -0700 Subject: [PATCH 006/180] update DefaultLocationStore.test.ts Signed-off-by: Jamie Tang --- .../catalog-backend/src/providers/DefaultLocationStore.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/catalog-backend/src/providers/DefaultLocationStore.test.ts b/plugins/catalog-backend/src/providers/DefaultLocationStore.test.ts index 5e3f3e0b17..05e078723d 100644 --- a/plugins/catalog-backend/src/providers/DefaultLocationStore.test.ts +++ b/plugins/catalog-backend/src/providers/DefaultLocationStore.test.ts @@ -211,6 +211,7 @@ describe('DefaultLocationStore', () => { entity_id: entityId, key: `metadata.annotations.${ANNOTATION_ORIGIN_LOCATION}`, value: `url:https://example.com`, + original_value: `url:https://example.com`, }); await knex('locations').insert({ From 752d983346a8f82a8120d6a38b724eabea888b1b Mon Sep 17 00:00:00 2001 From: Brie Hodson Date: Fri, 11 Jul 2025 09:33:29 -0600 Subject: [PATCH 007/180] Link Google Analytics plugin to G4 version The plugin docs were linking to the original Google Analytics plugin that only supports Universal Analytics, which is now deprecated by Google. The G4 version is the compatible version with Google Analytics now and should be the default link. Signed-off-by: Brie Hodson --- microsite/data/plugins/analytics-module-ga.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/microsite/data/plugins/analytics-module-ga.yaml b/microsite/data/plugins/analytics-module-ga.yaml index 968b1831ab..e6a60c02d5 100644 --- a/microsite/data/plugins/analytics-module-ga.yaml +++ b/microsite/data/plugins/analytics-module-ga.yaml @@ -4,7 +4,7 @@ author: Spotify authorUrl: https://github.com/spotify category: Monitoring description: Track usage of your Backstage instance using Google Analytics. -documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-ga/README.md +documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/analytics/plugins/analytics-module-ga4#readme iconUrl: /img/ga-icon.png -npmPackageName: '@backstage/plugin-analytics-module-ga' -addedDate: '2021-10-07' +npmPackageName: '@backstage/plugin-analytics-module-ga4' +addedDate: '2024-04-19' From 95d14a95a64f0fc1975afbf208a0aee3e53f9bf4 Mon Sep 17 00:00:00 2001 From: Brie Hodson Date: Fri, 11 Jul 2025 10:20:52 -0600 Subject: [PATCH 008/180] Use original added date and add GA version 4 to description Signed-off-by: Brie Hodson --- microsite/data/plugins/analytics-module-ga.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/microsite/data/plugins/analytics-module-ga.yaml b/microsite/data/plugins/analytics-module-ga.yaml index e6a60c02d5..3dcb27bcd5 100644 --- a/microsite/data/plugins/analytics-module-ga.yaml +++ b/microsite/data/plugins/analytics-module-ga.yaml @@ -3,8 +3,8 @@ title: 'Analytics Module: Google Analytics' author: Spotify authorUrl: https://github.com/spotify category: Monitoring -description: Track usage of your Backstage instance using Google Analytics. +description: Track usage of your Backstage instance using Google Analytics 4. documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/analytics/plugins/analytics-module-ga4#readme iconUrl: /img/ga-icon.png npmPackageName: '@backstage/plugin-analytics-module-ga4' -addedDate: '2024-04-19' +addedDate: '2021-10-07' From b90047cfec1a7b205a361862cfe44077ac4982c0 Mon Sep 17 00:00:00 2001 From: John Redwood Date: Wed, 16 Jul 2025 21:05:03 +1000 Subject: [PATCH 009/180] chore: resolve raised changes Signed-off-by: John Redwood --- app-config.yaml | 2 +- plugins/scaffolder-backend/config.d.ts | 15 +++++ .../src/scaffolder/tasks/TaskWorker.test.ts | 57 ++++++++++++++++++- .../src/scaffolder/tasks/TaskWorker.ts | 44 ++++++++------ 4 files changed, 99 insertions(+), 19 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index d0189595e4..59a790d406 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -201,7 +201,7 @@ catalog: scaffolder: auditor: - maxLength: 256 + taskParameterMaxLength: 256 # Use to customize default commit author info used when new components are created defaultAuthor: name: Scaffolder diff --git a/plugins/scaffolder-backend/config.d.ts b/plugins/scaffolder-backend/config.d.ts index ae91c905b3..b4fb532901 100644 --- a/plugins/scaffolder-backend/config.d.ts +++ b/plugins/scaffolder-backend/config.d.ts @@ -93,5 +93,20 @@ export interface Config { * Default value is 24 hours. */ taskTimeout?: HumanDuration | string; + + /** + * Sets the maximum length for task parameters recorded by the auditor. + * + * If set to -1, the limit is disabled and parameters are not truncated. + * Defaults to 256 character length. + * + * @example + * scaffolder: + * auditor: + * taskParameterMaxLength: 512 + */ + auditor?: { + taskParameterMaxLength?: number; + }; }; } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts index 6fedc4df40..c66b5dbae5 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts @@ -16,7 +16,7 @@ import os from 'os'; import { DatabaseManager } from '@backstage/backend-defaults/database'; -import { ConfigReader } from '@backstage/config'; +import { Config, ConfigReader } from '@backstage/config'; import { DatabaseTaskStore } from './DatabaseTaskStore'; import { StorageTaskBroker } from './StorageTaskBroker'; import { TaskWorker, TaskWorkerOptions } from './TaskWorker'; @@ -343,3 +343,58 @@ describe('TaskWorker internals', () => { expect(inflightTasks.length).toBe(2); }); }); + +describe('TaskWorker.truncateParameters', () => { + let worker: TaskWorker; + + beforeEach(async () => { + jest.resetAllMocks(); + + const logger = { debug: jest.fn() } as any; + + const config = { + getOptionalNumber: jest.fn().mockReturnValue(5), + } as unknown as Config; + + worker = await TaskWorker.create({ + logger, + workingDirectory: '/tmp', + integrations: {} as ScmIntegrations, + taskBroker: {} as TaskBroker, + actionRegistry: {} as TemplateActionRegistry, + config, + }); + }); + + it('successfully does nothing', async () => { + const testParams = {}; + + // @ts-expect-error (truncateParameters is private, but for test we can access) + const result = worker.truncateParameters(testParams); + + expect(result).toEqual({}); + }); + + it('truncates long strings in nested objects and arrays', async () => { + const params = { + test: 'short', + test2: 'thisisaverylongstring', + nested: { + test3: 'anotherlongstringhere', + test4: ['ok', 'toolongstring'], + }, + }; + + // @ts-expect-error (truncateParameters is private, but for test we can access) + const result = worker.truncateParameters(params); + + expect(result).toEqual({ + test: 'short', + test2: 'thisi...', + nested: { + test3: 'anoth...', + test4: ['ok', 'toolo...'], + }, + }); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index 40a56e4368..119cfaf180 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -190,34 +190,44 @@ export class TaskWorker { }); } - protected truncateParameters(parameters: JsonObject) { - const auditMaxLength = - this.config?.getOptionalNumber('scaffolder.auditor.maxLength') ?? 256; + private truncateParameters(parameters: JsonObject) { + const taskParameterMaxLength = + this.config?.getOptionalNumber( + 'scaffolder.auditor.taskParameterMaxLength', + ) ?? 256; - if (auditMaxLength === -1) { + if (taskParameterMaxLength === -1) { this.logger?.debug( - `scaffolder.auditor.maxLength manually disabled via configuration, no task parameter length limit set.`, + `scaffolder.auditor.taskParameterMaxLength manually disabled via configuration, no task parameter length limit set.`, ); return parameters; } - const truncatedParameters: JsonObject = {}; - - for (const key in parameters) { - if (Object.prototype.hasOwnProperty.call(parameters, key)) { - const rawValue = parameters[key]; - const value = rawValue?.toString(); - if (value && value.length > auditMaxLength) { - truncatedParameters[key] = value - .slice(0, auditMaxLength) + function truncate(value: unknown): unknown { + if (typeof value === 'string') { + if (value.length > taskParameterMaxLength) { + return value + .slice(0, taskParameterMaxLength) .concat('...'); - } else { - truncatedParameters[key] = rawValue; } + return value; } + if (Array.isArray(value)) { + return value.map(truncate); + } + if (value && typeof value === 'object') { + const result: Record = {}; + for (const k in value as object) { + if (Object.hasOwn(value, k)) { + result[k] = truncate((value as any)[k]); + } + } + return result; + } + return value; } - return truncatedParameters; + return truncate(parameters) as JsonObject; } async runOneTask(task: TaskContext) { From c91f267b88b06189c65ed801e195dfd27aa3eeb5 Mon Sep 17 00:00:00 2001 From: John Redwood Date: Thu, 17 Jul 2025 09:12:04 +1000 Subject: [PATCH 010/180] chore: run api-reports Signed-off-by: John Redwood --- plugins/scaffolder-backend/report.api.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/scaffolder-backend/report.api.md b/plugins/scaffolder-backend/report.api.md index debc9ac60a..d833fd1cd9 100644 --- a/plugins/scaffolder-backend/report.api.md +++ b/plugins/scaffolder-backend/report.api.md @@ -602,8 +602,6 @@ export class TaskWorker { start(): void; // (undocumented) stop(): Promise; - // (undocumented) - protected truncateParameters(parameters: JsonObject): JsonObject; } // @public @deprecated From 3af9eacf0b40069c4563926cb7bfa3e32112f8ed Mon Sep 17 00:00:00 2001 From: John Redwood Date: Thu, 17 Jul 2025 09:49:22 +1000 Subject: [PATCH 011/180] fix: merge issues Signed-off-by: John Redwood --- plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index 6035a54fdf..9cdab0d58a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -236,10 +236,9 @@ export class TaskWorker { severityLevel: 'medium', meta: { actionType: 'execution', + createdBy: task.createdBy, taskId: task.taskId, taskParameters: this.truncateParameters(task.spec.parameters), - createdBy: task.createdBy, - taskParameters: task.spec.parameters, templateRef: task.spec.templateInfo?.entityRef, }, }); From 67d3698100b1eb0c18680aa0aae4a57096a0ed18 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Fri, 25 Jul 2025 07:36:59 +0100 Subject: [PATCH 012/180] Improve Table cells Signed-off-by: Charles de Dreuille --- .../components/DataTable/mocked-columns.tsx | 59 +++++++++- .../components/DataTable/mocked-components.ts | 107 ++++++++++++++++++ packages/ui/src/components/Table/Table.tsx | 2 - .../TableCellLink/TableCellLink.stories.tsx | 48 -------- .../TableCellLink/TableCellLink.styles.css | 21 ---- .../Table/TableCellLink/TableCellLink.tsx | 47 -------- .../components/Table/TableCellLink/types.ts | 26 ----- .../TableCellProfile.stories.tsx | 2 +- .../TableCellProfile/TableCellProfile.tsx | 15 ++- .../Table/TableCellProfile/types.ts | 5 +- .../TableCellText/TableCellText.stories.tsx | 50 +++++++- .../TableCellText/TableCellText.styles.css | 14 +++ .../Table/TableCellText/TableCellText.tsx | 37 +++++- .../components/Table/TableCellText/types.ts | 3 + packages/ui/src/components/Table/index.ts | 1 - packages/ui/src/css/components.css | 1 - packages/ui/src/utils/componentDefinitions.ts | 3 +- 17 files changed, 278 insertions(+), 163 deletions(-) delete mode 100644 packages/ui/src/components/Table/TableCellLink/TableCellLink.stories.tsx delete mode 100644 packages/ui/src/components/Table/TableCellLink/TableCellLink.styles.css delete mode 100644 packages/ui/src/components/Table/TableCellLink/TableCellLink.tsx delete mode 100644 packages/ui/src/components/Table/TableCellLink/types.ts diff --git a/packages/ui/src/components/DataTable/mocked-columns.tsx b/packages/ui/src/components/DataTable/mocked-columns.tsx index c44a434d6a..4b06bbfaa1 100644 --- a/packages/ui/src/components/DataTable/mocked-columns.tsx +++ b/packages/ui/src/components/DataTable/mocked-columns.tsx @@ -66,7 +66,7 @@ export const columns: ColumnDef[] = [ ); }, @@ -84,3 +84,60 @@ export const columns: ColumnDef[] = [ size: 150, }, ]; + +export const columns2: ColumnDef[] = [ + { + accessorKey: 'type', + header: 'Type', + cell: ({ row }) => ( + + ), + size: 100, + }, + { + accessorKey: 'name', + header: 'Name', + cell: ({ row }) => ( + + ), + size: 450, + }, + { + accessorKey: 'owner', + header: 'Owner', + cell: ({ row }) => { + const owner = row.getValue('owner') as DataProps['owner']; + + return ( + + ); + }, + }, + { + accessorKey: 'lifecycle', + header: 'Lifecycle', + cell: ({ row }) => ( + + ), + size: 100, + }, + { + accessorKey: 'system', + header: 'System', + cell: ({ row }) => ( + + ), + size: 100, + }, +]; diff --git a/packages/ui/src/components/DataTable/mocked-components.ts b/packages/ui/src/components/DataTable/mocked-components.ts index 40d790de78..2c7b1f9064 100644 --- a/packages/ui/src/components/DataTable/mocked-components.ts +++ b/packages/ui/src/components/DataTable/mocked-components.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export interface DataProps { name: string; owner: { @@ -23,6 +24,7 @@ export interface DataProps { type: 'documentation' | 'library' | 'service' | 'website' | 'other'; description?: string; tags?: string[]; + lifecycle: 'experimental' | 'production'; } export const data: DataProps[] = [ @@ -37,6 +39,7 @@ export const data: DataProps[] = [ description: 'A comprehensive service handling user authentication and role-based access control across all applications.', tags: ['security', 'authentication', 'authorization'], + lifecycle: 'production', }, { name: 'user-interface-dashboard-and-analytics-platform', @@ -49,6 +52,7 @@ export const data: DataProps[] = [ description: 'Interactive dashboard providing real-time analytics and data visualization for business metrics.', tags: ['analytics', 'visualization', 'dashboard'], + lifecycle: 'production', }, { name: 'payment-gateway', @@ -61,6 +65,7 @@ export const data: DataProps[] = [ description: 'Secure payment processing system supporting multiple payment methods and currencies.', tags: ['payments', 'security', 'finance'], + lifecycle: 'production', }, { name: 'real-time-analytics-processing-and-visualization-engine', @@ -73,6 +78,7 @@ export const data: DataProps[] = [ description: 'High-performance engine for processing and visualizing streaming data analytics.', tags: ['analytics', 'real-time', 'data-processing'], + lifecycle: 'experimental', }, { name: 'notification-center', @@ -85,6 +91,7 @@ export const data: DataProps[] = [ description: 'Centralized system for managing and delivering notifications across multiple channels.', tags: ['notifications', 'messaging'], + lifecycle: 'production', }, { name: 'administrative-control-panel-and-user-management-interface', @@ -97,6 +104,7 @@ export const data: DataProps[] = [ description: 'Admin interface for managing users, permissions, and system configurations.', tags: ['admin', 'user-management', 'configuration'], + lifecycle: 'production', }, { name: 'search-indexer', @@ -109,6 +117,7 @@ export const data: DataProps[] = [ description: 'Service responsible for indexing and updating searchable content across the platform.', tags: ['search', 'indexing'], + lifecycle: 'production', }, { name: 'cross-platform-mobile-application-framework', @@ -121,6 +130,7 @@ export const data: DataProps[] = [ description: 'Framework enabling development of cross-platform mobile applications with shared codebase.', tags: ['mobile', 'framework', 'cross-platform'], + lifecycle: 'experimental', }, { name: 'database-migration', @@ -133,6 +143,7 @@ export const data: DataProps[] = [ description: 'Tools and scripts for managing database schema migrations and data transformations.', tags: ['database', 'migration', 'devops'], + lifecycle: 'production', }, { name: 'api-gateway', @@ -145,6 +156,7 @@ export const data: DataProps[] = [ description: 'Central entry point for all API requests, handling routing, authentication, and rate limiting.', tags: ['api', 'gateway', 'security', 'routing'], + lifecycle: 'production', }, { name: 'content-management', @@ -157,6 +169,7 @@ export const data: DataProps[] = [ description: 'System for managing and delivering digital content across multiple channels.', tags: ['content', 'management', 'delivery'], + lifecycle: 'production', }, { name: 'enterprise-reporting-and-analytics-dashboard', @@ -169,6 +182,7 @@ export const data: DataProps[] = [ description: 'Comprehensive business intelligence platform for enterprise-wide reporting and analytics.', tags: ['analytics', 'reporting', 'business-intelligence'], + lifecycle: 'production', }, { name: 'image-processing-and-optimization-service', @@ -181,6 +195,7 @@ export const data: DataProps[] = [ description: 'Service for processing, optimizing, and delivering images across different devices and networks.', tags: ['media', 'optimization', 'processing'], + lifecycle: 'production', }, { name: 'customer-portal', @@ -193,6 +208,7 @@ export const data: DataProps[] = [ description: 'Self-service portal for customers to manage their accounts and access services.', tags: ['customer', 'self-service'], + lifecycle: 'production', }, { name: 'log-aggregator', @@ -205,6 +221,7 @@ export const data: DataProps[] = [ description: 'Centralized logging system for collecting, processing, and analyzing application logs.', tags: ['logging', 'monitoring', 'devops'], + lifecycle: 'production', }, { name: 'identity-provider', @@ -217,6 +234,7 @@ export const data: DataProps[] = [ description: 'Service managing user identities and authentication across the organization.', tags: ['identity', 'security', 'authentication'], + lifecycle: 'production', }, { name: 'document-storage', @@ -229,6 +247,7 @@ export const data: DataProps[] = [ description: 'Secure and scalable document storage system with version control and access management.', tags: ['storage', 'documents', 'version-control'], + lifecycle: 'production', }, { name: 'workflow-engine', @@ -241,6 +260,7 @@ export const data: DataProps[] = [ description: 'Engine for defining and executing business processes and workflows.', tags: ['workflow', 'automation'], + lifecycle: 'experimental', }, { name: 'mobile-backend', @@ -253,6 +273,7 @@ export const data: DataProps[] = [ description: 'Backend services supporting mobile applications with optimized APIs and data synchronization.', tags: ['mobile', 'backend', 'api'], + lifecycle: 'production', }, { name: 'system-monitoring-and-alerting-dashboard', @@ -265,6 +286,7 @@ export const data: DataProps[] = [ description: 'Real-time monitoring and alerting system for infrastructure and application health.', tags: ['monitoring', 'alerting', 'devops', 'infrastructure'], + lifecycle: 'production', }, { name: 'email-service', @@ -277,6 +299,7 @@ export const data: DataProps[] = [ description: 'Reliable email delivery service with templates and tracking capabilities.', tags: ['email', 'communication'], + lifecycle: 'production', }, { name: 'data-pipeline', @@ -289,6 +312,7 @@ export const data: DataProps[] = [ description: 'ETL pipeline for processing and transforming large volumes of data.', tags: ['data', 'etl', 'pipeline'], + lifecycle: 'production', }, { name: 'configuration-manager', @@ -301,6 +325,7 @@ export const data: DataProps[] = [ description: 'Centralized system for managing application configurations across environments.', tags: ['configuration', 'management'], + lifecycle: 'production', }, { name: 'testing-framework', @@ -313,6 +338,7 @@ export const data: DataProps[] = [ description: 'Comprehensive testing framework supporting various types of automated tests.', tags: ['testing', 'automation', 'qa'], + lifecycle: 'production', }, { name: 'cache-service', @@ -325,6 +351,7 @@ export const data: DataProps[] = [ description: 'Distributed caching service for improving application performance.', tags: ['caching', 'performance'], + lifecycle: 'production', }, { name: 'billing-system', @@ -337,6 +364,7 @@ export const data: DataProps[] = [ description: 'System for managing customer billing, invoicing, and payment processing.', tags: ['billing', 'finance', 'payments'], + lifecycle: 'production', }, { name: 'comprehensive-product-documentation-and-api-reference', @@ -349,6 +377,7 @@ export const data: DataProps[] = [ description: 'Complete documentation covering product features, APIs, and integration guides.', tags: ['documentation', 'api', 'reference'], + lifecycle: 'production', }, { name: 'queue-manager', @@ -361,6 +390,7 @@ export const data: DataProps[] = [ description: 'Message queue system for asynchronous processing and event handling.', tags: ['queue', 'messaging', 'async'], + lifecycle: 'production', }, { name: 'security-scanner', @@ -373,6 +403,7 @@ export const data: DataProps[] = [ description: 'Automated security scanning tool for identifying vulnerabilities in code and infrastructure.', tags: ['security', 'scanning', 'vulnerability'], + lifecycle: 'experimental', }, { name: 'user-profile', @@ -385,6 +416,7 @@ export const data: DataProps[] = [ description: 'User profile management interface with personalization features.', tags: ['user', 'profile', 'personalization'], + lifecycle: 'production', }, { name: 'data-warehouse', @@ -397,6 +429,7 @@ export const data: DataProps[] = [ description: 'Centralized data repository for business intelligence and analytics.', tags: ['data', 'warehouse', 'analytics'], + lifecycle: 'production', }, { name: 'deployment-automation', @@ -409,6 +442,7 @@ export const data: DataProps[] = [ description: 'Automated deployment pipeline for continuous integration and delivery.', tags: ['deployment', 'automation', 'ci-cd', 'devops'], + lifecycle: 'production', }, { name: 'chat-service', @@ -421,6 +455,7 @@ export const data: DataProps[] = [ description: 'Real-time chat service supporting text, file sharing, and group conversations.', tags: ['chat', 'communication', 'real-time'], + lifecycle: 'experimental', }, { name: 'analytics-dashboard', @@ -433,6 +468,7 @@ export const data: DataProps[] = [ description: 'Interactive dashboard for visualizing and analyzing business metrics.', tags: ['analytics', 'dashboard', 'visualization'], + lifecycle: 'production', }, { name: 'file-uploader', @@ -445,6 +481,7 @@ export const data: DataProps[] = [ description: 'Service for handling secure file uploads with progress tracking and validation.', tags: ['storage', 'upload', 'files'], + lifecycle: 'production', }, { name: 'search-service', @@ -457,6 +494,7 @@ export const data: DataProps[] = [ description: 'Full-text search service with advanced filtering and ranking capabilities.', tags: ['search', 'full-text'], + lifecycle: 'production', }, { name: 'mobile-sdk', @@ -469,6 +507,7 @@ export const data: DataProps[] = [ description: 'Software development kit for building mobile applications with native features.', tags: ['mobile', 'sdk', 'development'], + lifecycle: 'production', }, { name: 'performance-monitor', @@ -481,6 +520,7 @@ export const data: DataProps[] = [ description: 'System for monitoring and analyzing application performance metrics.', tags: ['performance', 'monitoring', 'metrics'], + lifecycle: 'production', }, { name: 'content-delivery', @@ -493,6 +533,7 @@ export const data: DataProps[] = [ description: 'CDN service for optimized content delivery across global networks.', tags: ['cdn', 'content', 'delivery'], + lifecycle: 'production', }, { name: 'user-authentication', @@ -505,6 +546,7 @@ export const data: DataProps[] = [ description: 'Service handling user login, session management, and authentication flows.', tags: ['authentication', 'security', 'user'], + lifecycle: 'production', }, { name: 'data-export', @@ -517,6 +559,7 @@ export const data: DataProps[] = [ description: 'Service for exporting data in various formats with scheduling capabilities.', tags: ['data', 'export', 'scheduling'], + lifecycle: 'production', }, { name: 'admin-api', @@ -529,6 +572,7 @@ export const data: DataProps[] = [ description: 'API endpoints for administrative functions and system management.', tags: ['api', 'admin', 'management'], + lifecycle: 'production', }, { name: 'testing-dashboard', @@ -540,6 +584,7 @@ export const data: DataProps[] = [ type: 'website', description: 'Dashboard for monitoring test results and quality metrics.', tags: ['testing', 'dashboard', 'qa'], + lifecycle: 'production', }, { name: 'message-broker', @@ -552,6 +597,7 @@ export const data: DataProps[] = [ description: 'Message broker service for reliable event-driven communication between services.', tags: ['messaging', 'broker', 'event-driven'], + lifecycle: 'production', }, { name: 'payment-processor', @@ -564,6 +610,7 @@ export const data: DataProps[] = [ description: 'Service for processing financial transactions and payment methods.', tags: ['payments', 'finance', 'processing'], + lifecycle: 'production', }, { name: 'document-viewer', @@ -575,6 +622,7 @@ export const data: DataProps[] = [ type: 'website', description: 'Web-based document viewer supporting multiple file formats.', tags: ['documents', 'viewer'], + lifecycle: 'production', }, { name: 'load-balancer', @@ -587,6 +635,7 @@ export const data: DataProps[] = [ description: 'Service for distributing network traffic across multiple servers.', tags: ['load-balancing', 'networking', 'infrastructure'], + lifecycle: 'production', }, { name: 'security-audit', @@ -599,6 +648,7 @@ export const data: DataProps[] = [ description: 'Tools and processes for conducting security audits and compliance checks.', tags: ['security', 'audit', 'compliance'], + lifecycle: 'production', }, { name: 'user-settings', @@ -611,6 +661,7 @@ export const data: DataProps[] = [ description: 'Interface for users to manage their preferences and account settings.', tags: ['user', 'settings', 'preferences'], + lifecycle: 'production', }, { name: 'data-import', @@ -623,6 +674,7 @@ export const data: DataProps[] = [ description: 'Service for importing and validating data from external sources.', tags: ['data', 'import', 'validation'], + lifecycle: 'production', }, { name: 'infrastructure-monitor', @@ -635,6 +687,7 @@ export const data: DataProps[] = [ description: 'Monitoring system for infrastructure components and resources.', tags: ['monitoring', 'infrastructure', 'devops'], + lifecycle: 'production', }, { name: 'notification-manager', @@ -647,6 +700,7 @@ export const data: DataProps[] = [ description: 'Service for managing and delivering notifications across multiple channels.', tags: ['notifications', 'management'], + lifecycle: 'production', }, { name: 'analytics-processor', @@ -659,6 +713,7 @@ export const data: DataProps[] = [ description: 'Service for processing and analyzing business data and metrics.', tags: ['analytics', 'processing', 'metrics'], + lifecycle: 'production', }, { name: 'file-manager', @@ -670,6 +725,7 @@ export const data: DataProps[] = [ type: 'website', description: 'Web interface for managing files and storage resources.', tags: ['files', 'storage', 'management'], + lifecycle: 'production', }, { name: 'search-index', @@ -681,6 +737,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for maintaining and updating search indices.', tags: ['search', 'indexing'], + lifecycle: 'production', }, { name: 'mobile-authentication', @@ -693,6 +750,7 @@ export const data: DataProps[] = [ description: 'Authentication service specifically designed for mobile applications.', tags: ['mobile', 'authentication', 'security'], + lifecycle: 'experimental', }, { name: 'system-monitor', @@ -705,6 +763,7 @@ export const data: DataProps[] = [ description: 'Monitoring service for system health and performance metrics.', tags: ['monitoring', 'system', 'metrics'], + lifecycle: 'production', }, { name: 'media-processor', @@ -716,6 +775,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for processing and optimizing media files.', tags: ['media', 'processing', 'optimization'], + lifecycle: 'production', }, { name: 'user-management', @@ -727,6 +787,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for managing user accounts and permissions.', tags: ['user', 'management', 'security'], + lifecycle: 'production', }, { name: 'data-transformer', @@ -739,6 +800,7 @@ export const data: DataProps[] = [ description: 'Service for transforming data between different formats and structures.', tags: ['data', 'transformation'], + lifecycle: 'production', }, { name: 'admin-dashboard', @@ -751,6 +813,7 @@ export const data: DataProps[] = [ description: 'Administrative interface for system management and monitoring.', tags: ['admin', 'dashboard', 'management'], + lifecycle: 'production', }, { name: 'test-automation', @@ -762,6 +825,7 @@ export const data: DataProps[] = [ type: 'other', description: 'Tools and frameworks for automating testing processes.', tags: ['testing', 'automation', 'qa'], + lifecycle: 'production', }, { name: 'event-bus', @@ -773,6 +837,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Event-driven communication system between services.', tags: ['events', 'messaging', 'communication'], + lifecycle: 'production', }, { name: 'invoice-generator', @@ -784,6 +849,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for generating and managing invoices.', tags: ['invoices', 'finance'], + lifecycle: 'production', }, { name: 'document-editor', @@ -795,6 +861,7 @@ export const data: DataProps[] = [ type: 'website', description: 'Web-based document editing interface.', tags: ['documents', 'editor'], + lifecycle: 'experimental', }, { name: 'service-discovery', @@ -806,6 +873,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for discovering and registering available services.', tags: ['discovery', 'services', 'devops'], + lifecycle: 'production', }, { name: 'security-monitor', @@ -817,6 +885,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for monitoring security events and threats.', tags: ['security', 'monitoring', 'threats'], + lifecycle: 'production', }, { name: 'user-preferences', @@ -828,6 +897,7 @@ export const data: DataProps[] = [ type: 'website', description: 'Interface for managing user preferences and settings.', tags: ['user', 'preferences'], + lifecycle: 'production', }, { name: 'data-validator', @@ -839,6 +909,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for validating data integrity and format.', tags: ['data', 'validation'], + lifecycle: 'production', }, { name: 'infrastructure-automation', @@ -851,6 +922,7 @@ export const data: DataProps[] = [ description: 'Tools for automating infrastructure provisioning and management.', tags: ['infrastructure', 'automation', 'devops'], + lifecycle: 'production', }, { name: 'notification-dispatcher', @@ -863,6 +935,7 @@ export const data: DataProps[] = [ description: 'Service for dispatching notifications to appropriate channels.', tags: ['notifications', 'dispatch'], + lifecycle: 'production', }, { name: 'analytics-collector', @@ -874,6 +947,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for collecting and aggregating analytics data.', tags: ['analytics', 'collection', 'aggregation'], + lifecycle: 'production', }, { name: 'file-processor', @@ -885,6 +959,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for processing and managing files.', tags: ['files', 'processing'], + lifecycle: 'production', }, { name: 'search-analyzer', @@ -896,6 +971,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for analyzing search queries and results.', tags: ['search', 'analysis'], + lifecycle: 'experimental', }, { name: 'mobile-notifications', @@ -907,6 +983,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for sending notifications to mobile devices.', tags: ['mobile', 'notifications'], + lifecycle: 'experimental', }, { name: 'system-alerts', @@ -918,6 +995,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for managing and dispatching system alerts.', tags: ['alerts', 'system', 'monitoring'], + lifecycle: 'production', }, { name: 'media-encoder', @@ -929,6 +1007,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for encoding and processing media files.', tags: ['media', 'encoding'], + lifecycle: 'production', }, { name: 'user-authorization', @@ -940,6 +1019,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for managing user permissions and access control.', tags: ['authorization', 'security', 'user'], + lifecycle: 'production', }, { name: 'data-aggregator', @@ -951,6 +1031,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for aggregating data from multiple sources.', tags: ['data', 'aggregation'], + lifecycle: 'production', }, { name: 'admin-authentication', @@ -962,6 +1043,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Authentication service for administrative access.', tags: ['admin', 'authentication', 'security'], + lifecycle: 'production', }, { name: 'test-coverage', @@ -973,6 +1055,7 @@ export const data: DataProps[] = [ type: 'other', description: 'Tools for measuring and reporting test coverage.', tags: ['testing', 'coverage', 'qa'], + lifecycle: 'production', }, { name: 'event-processor', @@ -984,6 +1067,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for processing and handling events.', tags: ['events', 'processing'], + lifecycle: 'production', }, { name: 'payment-validator', @@ -995,6 +1079,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for validating payment transactions.', tags: ['payments', 'validation', 'finance'], + lifecycle: 'production', }, { name: 'document-converter', @@ -1006,6 +1091,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for converting documents between different formats.', tags: ['documents', 'conversion'], + lifecycle: 'experimental', }, { name: 'service-health', @@ -1017,6 +1103,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for monitoring and reporting service health status.', tags: ['health', 'monitoring', 'services'], + lifecycle: 'production', }, { name: 'security-logger', @@ -1028,6 +1115,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for logging security-related events and activities.', tags: ['security', 'logging'], + lifecycle: 'production', }, { name: 'user-analytics', @@ -1040,6 +1128,7 @@ export const data: DataProps[] = [ description: 'Analytics dashboard for user behavior and engagement metrics.', tags: ['analytics', 'user', 'metrics'], + lifecycle: 'experimental', }, { name: 'data-cleaner', @@ -1051,6 +1140,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for cleaning and standardizing data.', tags: ['data', 'cleaning'], + lifecycle: 'production', }, { name: 'infrastructure-deployer', @@ -1062,6 +1152,7 @@ export const data: DataProps[] = [ type: 'other', description: 'Tools for deploying and managing infrastructure resources.', tags: ['infrastructure', 'deployment', 'devops'], + lifecycle: 'production', }, { name: 'notification-queue', @@ -1073,6 +1164,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Queue system for managing notification delivery.', tags: ['notifications', 'queue'], + lifecycle: 'production', }, { name: 'analytics-exporter', @@ -1084,6 +1176,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for exporting analytics data in various formats.', tags: ['analytics', 'export'], + lifecycle: 'production', }, { name: 'file-validator', @@ -1095,6 +1188,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for validating file integrity and format.', tags: ['files', 'validation'], + lifecycle: 'production', }, { name: 'search-optimizer', @@ -1106,6 +1200,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for optimizing search performance and relevance.', tags: ['search', 'optimization'], + lifecycle: 'experimental', }, { name: 'mobile-analytics', @@ -1117,6 +1212,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Analytics service specifically for mobile applications.', tags: ['mobile', 'analytics'], + lifecycle: 'experimental', }, { name: 'system-logger', @@ -1128,6 +1224,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for logging system events and activities.', tags: ['logging', 'system'], + lifecycle: 'production', }, { name: 'media-validator', @@ -1139,6 +1236,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for validating media files and formats.', tags: ['media', 'validation'], + lifecycle: 'production', }, { name: 'user-audit', @@ -1150,6 +1248,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for auditing user activities and access.', tags: ['audit', 'user', 'security'], + lifecycle: 'production', }, { name: 'data-normalizer', @@ -1161,6 +1260,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for normalizing data formats and structures.', tags: ['data', 'normalization'], + lifecycle: 'production', }, { name: 'admin-authorization', @@ -1172,6 +1272,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Authorization service for administrative functions.', tags: ['admin', 'authorization', 'security'], + lifecycle: 'production', }, { name: 'test-reporting', @@ -1183,6 +1284,7 @@ export const data: DataProps[] = [ type: 'other', description: 'Tools for generating and managing test reports.', tags: ['testing', 'reporting', 'qa'], + lifecycle: 'production', }, { name: 'event-aggregator', @@ -1194,6 +1296,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for aggregating and processing events.', tags: ['events', 'aggregation'], + lifecycle: 'production', }, { name: 'payment-reconciler', @@ -1205,6 +1308,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for reconciling payment transactions.', tags: ['payments', 'reconciliation', 'finance'], + lifecycle: 'production', }, { name: 'document-validator', @@ -1216,6 +1320,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for validating document formats and content.', tags: ['documents', 'validation'], + lifecycle: 'production', }, { name: 'service-monitor', @@ -1227,6 +1332,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for monitoring service health and performance.', tags: ['monitoring', 'services', 'health'], + lifecycle: 'production', }, { name: 'security-validator', @@ -1238,5 +1344,6 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for validating security configurations and policies.', tags: ['security', 'validation'], + lifecycle: 'production', }, ]; diff --git a/packages/ui/src/components/Table/Table.tsx b/packages/ui/src/components/Table/Table.tsx index ca89cae0c0..4c2597ba79 100644 --- a/packages/ui/src/components/Table/Table.tsx +++ b/packages/ui/src/components/Table/Table.tsx @@ -18,7 +18,6 @@ import { forwardRef } from 'react'; import clsx from 'clsx'; import { TableCell } from './TableCell/TableCell'; import { TableCellText } from './TableCellText/TableCellText'; -import { TableCellLink } from './TableCellLink/TableCellLink'; import { TableCellProfile } from './TableCellProfile/TableCellProfile'; import { useStyles } from '../../hooks/useStyles'; @@ -116,7 +115,6 @@ export const Table = { Row: TableRow, Cell: TableCell, CellText: TableCellText, - CellLink: TableCellLink, CellProfile: TableCellProfile, Caption: TableCaption, }; diff --git a/packages/ui/src/components/Table/TableCellLink/TableCellLink.stories.tsx b/packages/ui/src/components/Table/TableCellLink/TableCellLink.stories.tsx deleted file mode 100644 index bc9b66f18a..0000000000 --- a/packages/ui/src/components/Table/TableCellLink/TableCellLink.stories.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { Meta, StoryFn, StoryObj } from '@storybook/react'; -import { TableCellLink } from './TableCellLink'; -import { MemoryRouter } from 'react-router-dom'; - -const meta = { - title: 'Components/Table/TableCellLink', - component: TableCellLink, - decorators: [ - (Story: StoryFn) => ( - - - - ), - ], -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - args: { - title: 'I am a link', - href: 'https://ui.backstage.io', - }, -}; - -export const WithDescription: Story = { - args: { - ...Default.args, - description: 'This is a description', - }, -}; diff --git a/packages/ui/src/components/Table/TableCellLink/TableCellLink.styles.css b/packages/ui/src/components/Table/TableCellLink/TableCellLink.styles.css deleted file mode 100644 index d4e10631fe..0000000000 --- a/packages/ui/src/components/Table/TableCellLink/TableCellLink.styles.css +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -.bui-TableCellLink { - display: flex; - flex-direction: column; - gap: var(--bui-space-0_5); -} diff --git a/packages/ui/src/components/Table/TableCellLink/TableCellLink.tsx b/packages/ui/src/components/Table/TableCellLink/TableCellLink.tsx deleted file mode 100644 index e27a37977f..0000000000 --- a/packages/ui/src/components/Table/TableCellLink/TableCellLink.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { forwardRef } from 'react'; -import clsx from 'clsx'; -import { TableCellLinkProps } from './types'; -import { Text } from '../../Text/Text'; -import { Link } from '../../Link/Link'; -import { useStyles } from '../../../hooks/useStyles'; - -/** @public */ -const TableCellLink = forwardRef( - ({ className, title, description, href, render, ...props }, ref) => { - const { classNames } = useStyles('Table'); - - return ( -
- {title && {title}} - {description && ( - - {description} - - )} -
- ); - }, -); -TableCellLink.displayName = 'TableCellLink'; - -export { TableCellLink }; diff --git a/packages/ui/src/components/Table/TableCellLink/types.ts b/packages/ui/src/components/Table/TableCellLink/types.ts deleted file mode 100644 index 51df3724cf..0000000000 --- a/packages/ui/src/components/Table/TableCellLink/types.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { useRender } from '@base-ui-components/react/use-render'; - -/** @public */ -export interface TableCellLinkProps - extends React.HTMLAttributes { - title: string; - description?: string; - href: string; - render?: useRender.ComponentProps<'a'>['render']; -} diff --git a/packages/ui/src/components/Table/TableCellProfile/TableCellProfile.stories.tsx b/packages/ui/src/components/Table/TableCellProfile/TableCellProfile.stories.tsx index 230fc0e3d1..9543d751ac 100644 --- a/packages/ui/src/components/Table/TableCellProfile/TableCellProfile.stories.tsx +++ b/packages/ui/src/components/Table/TableCellProfile/TableCellProfile.stories.tsx @@ -50,6 +50,6 @@ export const Fallback: Story = { export const WithLink: Story = { args: { ...Default.args, - to: 'https://www.google.com', + href: 'https://www.google.com', }, }; diff --git a/packages/ui/src/components/Table/TableCellProfile/TableCellProfile.tsx b/packages/ui/src/components/Table/TableCellProfile/TableCellProfile.tsx index bb2371bd6d..25e5337e0d 100644 --- a/packages/ui/src/components/Table/TableCellProfile/TableCellProfile.tsx +++ b/packages/ui/src/components/Table/TableCellProfile/TableCellProfile.tsx @@ -24,7 +24,10 @@ import { useStyles } from '../../../hooks/useStyles'; /** @public */ const TableCellProfile = forwardRef( - ({ className, src, name, to, withImage = true, ...rest }, ref) => { + ( + { className, src, name, href, description, color = 'primary', ...rest }, + ref, + ) => { const { classNames } = useStyles('Table'); return ( @@ -33,7 +36,7 @@ const TableCellProfile = forwardRef( className={clsx(classNames.cellProfile, className)} {...rest} > - {withImage && ( + {src && ( ( )} - {name && to ? ( - {name} + {name && href ? ( + {name} ) : ( - {name} + + {name} + )} ); diff --git a/packages/ui/src/components/Table/TableCellProfile/types.ts b/packages/ui/src/components/Table/TableCellProfile/types.ts index d5a7b18cc2..9c66ca2d3a 100644 --- a/packages/ui/src/components/Table/TableCellProfile/types.ts +++ b/packages/ui/src/components/Table/TableCellProfile/types.ts @@ -19,6 +19,7 @@ export interface TableCellProfileProps extends React.HTMLAttributes { src?: string; name?: string; - to?: string; - withImage?: boolean; + href?: string; + description?: string; + color?: 'primary' | 'secondary'; } diff --git a/packages/ui/src/components/Table/TableCellText/TableCellText.stories.tsx b/packages/ui/src/components/Table/TableCellText/TableCellText.stories.tsx index 3756dd2544..108ad5341d 100644 --- a/packages/ui/src/components/Table/TableCellText/TableCellText.stories.tsx +++ b/packages/ui/src/components/Table/TableCellText/TableCellText.stories.tsx @@ -14,8 +14,10 @@ * limitations under the License. */ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryFn, StoryObj } from '@storybook/react'; import { TableCellText } from './TableCellText'; +import { Icon } from '../../Icon/Icon'; +import { MemoryRouter } from 'react-router-dom'; const meta = { title: 'Components/Table/TableCellText', @@ -37,3 +39,49 @@ export const WithDescription: Story = { description: 'This is a description', }, }; + +export const WithIcon: Story = { + args: { + ...Default.args, + }, + render: args => ( + } /> + ), +}; + +export const WithIconAndDescription: Story = { + args: { + ...WithDescription.args, + }, + render: args => ( + } /> + ), +}; + +export const WithLink: Story = { + args: { + ...WithDescription.args, + href: '/home', + }, + decorators: [ + (Story: StoryFn) => ( + + + + ), + ], +}; + +export const WithExternalLink: Story = { + args: { + ...WithDescription.args, + href: 'https://www.google.com', + }, + decorators: [ + (Story: StoryFn) => ( + + + + ), + ], +}; diff --git a/packages/ui/src/components/Table/TableCellText/TableCellText.styles.css b/packages/ui/src/components/Table/TableCellText/TableCellText.styles.css index f38f8b3689..1dbc168b47 100644 --- a/packages/ui/src/components/Table/TableCellText/TableCellText.styles.css +++ b/packages/ui/src/components/Table/TableCellText/TableCellText.styles.css @@ -15,6 +15,20 @@ */ .bui-TableCellText { + display: inline-flex; + flex-direction: row; + align-items: center; + gap: var(--bui-space-2); +} + +.bui-TableCellTextIcon, +.bui-TableCellTextIcon svg { + display: inline-flex; + align-items: center; + color: var(--bui-fg-primary); +} + +.bui-TableCellTextContent { display: flex; flex-direction: column; gap: var(--bui-space-0_5); diff --git a/packages/ui/src/components/Table/TableCellText/TableCellText.tsx b/packages/ui/src/components/Table/TableCellText/TableCellText.tsx index ee8db20ada..ddf4258c56 100644 --- a/packages/ui/src/components/Table/TableCellText/TableCellText.tsx +++ b/packages/ui/src/components/Table/TableCellText/TableCellText.tsx @@ -18,11 +18,23 @@ import { forwardRef } from 'react'; import clsx from 'clsx'; import { TableCellTextProps } from './types'; import { Text } from '../../Text/Text'; +import { Link } from '../../Link/Link'; import { useStyles } from '../../../hooks/useStyles'; /** @public */ const TableCellText = forwardRef( - ({ className, title, description, ...props }, ref) => { + ( + { + className, + title, + description, + color = 'primary', + leadingIcon, + href, + ...props + }, + ref, + ) => { const { classNames } = useStyles('Table'); return ( @@ -31,12 +43,25 @@ const TableCellText = forwardRef( className={clsx(classNames.cellText, className)} {...props} > - {title && {title}} - {description && ( - - {description} - + {leadingIcon && ( +
{leadingIcon}
)} +
+ {href ? ( + + {title} + + ) : ( + + {title} + + )} + {description && ( + + {description} + + )} +
); }, diff --git a/packages/ui/src/components/Table/TableCellText/types.ts b/packages/ui/src/components/Table/TableCellText/types.ts index 99a27ad248..f497f2ef0a 100644 --- a/packages/ui/src/components/Table/TableCellText/types.ts +++ b/packages/ui/src/components/Table/TableCellText/types.ts @@ -19,4 +19,7 @@ export interface TableCellTextProps extends React.HTMLAttributes { title: string; description?: string; + color?: 'primary' | 'secondary'; + leadingIcon?: React.ReactNode; + href?: string; } diff --git a/packages/ui/src/components/Table/index.ts b/packages/ui/src/components/Table/index.ts index 5c7f3da195..ecf1cd7b18 100644 --- a/packages/ui/src/components/Table/index.ts +++ b/packages/ui/src/components/Table/index.ts @@ -16,5 +16,4 @@ export * from './Table'; export * from './TableCellText/types'; -export * from './TableCellLink/types'; export * from './TableCellProfile/types'; diff --git a/packages/ui/src/css/components.css b/packages/ui/src/css/components.css index 268bbe40dd..68f834b36a 100644 --- a/packages/ui/src/css/components.css +++ b/packages/ui/src/css/components.css @@ -38,7 +38,6 @@ @import '../components/Table/styles.css'; @import '../components/Table/TableCell/TableCell.styles.css'; @import '../components/Table/TableCellText/TableCellText.styles.css'; -@import '../components/Table/TableCellLink/TableCellLink.styles.css'; @import '../components/Table/TableCellProfile/TableCellProfile.styles.css'; @import '../components/Tabs/Tabs.styles.css'; @import '../components/Text/styles.css'; diff --git a/packages/ui/src/utils/componentDefinitions.ts b/packages/ui/src/utils/componentDefinitions.ts index 30d1c93d23..5f48c8524e 100644 --- a/packages/ui/src/utils/componentDefinitions.ts +++ b/packages/ui/src/utils/componentDefinitions.ts @@ -239,7 +239,8 @@ export const componentDefinitions = { caption: 'bui-TableCaption', cell: 'bui-TableCell', cellText: 'bui-TableCellText', - cellLink: 'bui-TableCellLink', + cellTextContent: 'bui-TableCellTextContent', + cellTextIcon: 'bui-TableCellTextIcon', cellProfile: 'bui-TableCellProfile', cellProfileAvatar: 'bui-TableCellProfileAvatar', cellProfileAvatarImage: 'bui-TableCellProfileAvatarImage', From 660a378678c5a8f99f1f013da4a3da60ca751a3b Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Fri, 25 Jul 2025 07:52:31 +0100 Subject: [PATCH 013/180] Improve Table components Signed-off-by: Charles de Dreuille --- .../ui/src/components/DataTable/DataTable.tsx | 36 ++++++----- .../DataTable/Table/DataTableTable.tsx | 45 ++++++++------ .../components/DataTable/mocked-columns.tsx | 4 +- .../ui/src/components/Table/Table.stories.tsx | 59 ++++++++++--------- packages/ui/src/components/Table/Table.tsx | 41 +++++-------- packages/ui/src/components/Table/index.ts | 11 ++-- 6 files changed, 103 insertions(+), 93 deletions(-) diff --git a/packages/ui/src/components/DataTable/DataTable.tsx b/packages/ui/src/components/DataTable/DataTable.tsx index 5a587df56f..1f2270bd28 100644 --- a/packages/ui/src/components/DataTable/DataTable.tsx +++ b/packages/ui/src/components/DataTable/DataTable.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,17 +15,26 @@ */ import { forwardRef } from 'react'; -import { Table } from '../Table'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableCellProfile, + TableCellText, + TableHeader, + TableRow, +} from '../Table'; import { DataTableRoot } from './Root/DataTableRoot'; import { DataTablePagination } from './Pagination/DataTablePagination'; import { Table as TanstackTable } from '@tanstack/react-table'; import { DataTableTable } from './Table/DataTableTable'; const TableRoot = forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ); -TableRoot.displayName = Table.Root.displayName; + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ); +TableRoot.displayName = Table.displayName; /** * DataTable component for displaying tabular data with pagination @@ -40,12 +49,11 @@ export const DataTable = { Pagination: DataTablePagination, Table: DataTableTable, TableRoot: TableRoot, - TableHeader: Table.Header, - TableBody: Table.Body, - TableRow: Table.Row, - TableCell: Table.Cell, - TableCellText: Table.CellText, - TableCellLink: Table.CellLink, - TableCellProfile: Table.CellProfile, - TableHead: Table.Head, + TableHeader: TableHeader, + TableBody: TableBody, + TableRow: TableRow, + TableCell: TableCell, + TableCellText: TableCellText, + TableCellProfile: TableCellProfile, + TableHead: TableHead, }; diff --git a/packages/ui/src/components/DataTable/Table/DataTableTable.tsx b/packages/ui/src/components/DataTable/Table/DataTableTable.tsx index 954cf9f703..368d31e78b 100644 --- a/packages/ui/src/components/DataTable/Table/DataTableTable.tsx +++ b/packages/ui/src/components/DataTable/Table/DataTableTable.tsx @@ -17,7 +17,14 @@ import { forwardRef } from 'react'; import clsx from 'clsx'; import { DataTableTableProps } from './types'; -import { Table } from '../../Table'; +import { + Table, + TableRow, + TableHeader, + TableHead, + TableBody, + TableCell, +} from '../../Table'; import { useDataTable } from '../Root/DataTableRoot'; import { flexRender } from '@tanstack/react-table'; @@ -28,18 +35,18 @@ const DataTableTable = forwardRef( const { table } = useDataTable(); return ( - - + {table.getHeaderGroups().map(headerGroup => ( - + {headerGroup.headers.map(header => { return ( - @@ -49,42 +56,42 @@ const DataTableTable = forwardRef( header.column.columnDef.header, header.getContext(), )} - + ); })} - + ))} - - + + {table.getRowModel().rows?.length ? ( table.getRowModel().rows.map(row => ( - {row.getVisibleCells().map(cell => ( - {flexRender(cell.column.columnDef.cell, cell.getContext())} - + ))} - + )) ) : ( - - + No results. - - + + )} - - + +
); }, ); diff --git a/packages/ui/src/components/DataTable/mocked-columns.tsx b/packages/ui/src/components/DataTable/mocked-columns.tsx index 4b06bbfaa1..600065bdf5 100644 --- a/packages/ui/src/components/DataTable/mocked-columns.tsx +++ b/packages/ui/src/components/DataTable/mocked-columns.tsx @@ -48,7 +48,7 @@ export const columns: ColumnDef[] = [ accessorKey: 'name', header: 'Name', cell: ({ row }) => ( - [] = [ accessorKey: 'name', header: 'Name', cell: ({ row }) => ( - + ), size: 450, }, diff --git a/packages/ui/src/components/Table/Table.stories.tsx b/packages/ui/src/components/Table/Table.stories.tsx index f008e73bae..c8279e5a83 100644 --- a/packages/ui/src/components/Table/Table.stories.tsx +++ b/packages/ui/src/components/Table/Table.stories.tsx @@ -16,7 +16,14 @@ import { ComponentType } from 'react'; import type { Meta, StoryObj } from '@storybook/react'; -import { Table } from '.'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '.'; const invoices = [ { @@ -65,13 +72,13 @@ const invoices = [ const meta = { title: 'Components/Table', - component: Table.Root, + component: Table, subcomponents: { - Body: Table.Body as ComponentType, - Cell: Table.Cell as ComponentType, - Head: Table.Head as ComponentType, - Header: Table.Header as ComponentType, - Row: Table.Row as ComponentType, + Body: TableBody as ComponentType, + Cell: TableCell as ComponentType, + Head: TableHead as ComponentType, + Header: TableHeader as ComponentType, + Row: TableRow as ComponentType, }, } satisfies Meta; @@ -80,27 +87,25 @@ type Story = StoryObj; export const Default: Story = { render: () => ( - - - - Invoice - Status - Method - Amount - - - + + + + Invoice + Status + Method + Amount + + + {invoices.map(invoice => ( - - {invoice.invoice} - {invoice.paymentStatus} - {invoice.paymentMethod} - - {invoice.totalAmount} - - + + {invoice.invoice} + {invoice.paymentStatus} + {invoice.paymentMethod} + {invoice.totalAmount} + ))} - - + +
), }; diff --git a/packages/ui/src/components/Table/Table.tsx b/packages/ui/src/components/Table/Table.tsx index 4c2597ba79..e82885d318 100644 --- a/packages/ui/src/components/Table/Table.tsx +++ b/packages/ui/src/components/Table/Table.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,10 @@ import { forwardRef } from 'react'; import clsx from 'clsx'; -import { TableCell } from './TableCell/TableCell'; -import { TableCellText } from './TableCellText/TableCellText'; -import { TableCellProfile } from './TableCellProfile/TableCellProfile'; import { useStyles } from '../../hooks/useStyles'; -const TableRoot = forwardRef< +/** @public */ +export const Table = forwardRef< HTMLTableElement, React.HTMLAttributes >(({ className, ...props }, ref) => { @@ -31,9 +29,10 @@ const TableRoot = forwardRef< ); }); -TableRoot.displayName = 'TableRoot'; +Table.displayName = 'Table'; -const TableHeader = forwardRef< +/** @public */ +export const TableHeader = forwardRef< HTMLTableSectionElement, React.HTMLAttributes >(({ className, ...props }, ref) => { @@ -49,7 +48,8 @@ const TableHeader = forwardRef< }); TableHeader.displayName = 'TableHeader'; -const TableBody = forwardRef< +/** @public */ +export const TableBody = forwardRef< HTMLTableSectionElement, React.HTMLAttributes >(({ className, ...props }, ref) => { @@ -61,7 +61,8 @@ const TableBody = forwardRef< }); TableBody.displayName = 'TableBody'; -const TableRow = forwardRef< +/** @public */ +export const TableRow = forwardRef< HTMLTableRowElement, React.HTMLAttributes >(({ className, ...props }, ref) => { @@ -75,7 +76,8 @@ const TableRow = forwardRef< }); TableRow.displayName = 'TableRow'; -const TableHead = forwardRef< +/** @public */ +export const TableHead = forwardRef< HTMLTableCellElement, React.ThHTMLAttributes >(({ className, ...props }, ref) => { @@ -87,7 +89,8 @@ const TableHead = forwardRef< }); TableHead.displayName = 'TableHead'; -const TableCaption = forwardRef< +/** @public */ +export const TableCaption = forwardRef< HTMLTableCaptionElement, React.HTMLAttributes >(({ className, ...props }, ref) => { @@ -102,19 +105,3 @@ const TableCaption = forwardRef< ); }); TableCaption.displayName = 'TableCaption'; - -/** - * Table component for displaying tabular data - * @public - */ -export const Table = { - Root: TableRoot, - Header: TableHeader, - Body: TableBody, - Head: TableHead, - Row: TableRow, - Cell: TableCell, - CellText: TableCellText, - CellProfile: TableCellProfile, - Caption: TableCaption, -}; diff --git a/packages/ui/src/components/Table/index.ts b/packages/ui/src/components/Table/index.ts index ecf1cd7b18..aa9bd105cd 100644 --- a/packages/ui/src/components/Table/index.ts +++ b/packages/ui/src/components/Table/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,9 @@ * limitations under the License. */ -export * from './Table'; -export * from './TableCellText/types'; -export * from './TableCellProfile/types'; +export { Table, TableBody, TableHead, TableHeader, TableRow } from './Table'; +export { TableCell } from './TableCell/TableCell'; +export { TableCellText } from './TableCellText/TableCellText'; +export { TableCellProfile } from './TableCellProfile/TableCellProfile'; +export type { TableCellTextProps } from './TableCellText/types'; +export type { TableCellProfileProps } from './TableCellProfile/types'; From afaede9418f8934531d3131b58f0be3f6496df97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20K=C3=B6gel?= Date: Fri, 25 Jul 2025 13:28:38 +0200 Subject: [PATCH 014/180] scaffolder-backend-module-gitlab: Show cause for GitbeakerRequestError MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Peter Kögel --- plugins/scaffolder-backend-module-gitlab/package.json | 1 + .../src/actions/helpers.ts | 7 +++++++ yarn.lock | 3 ++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index 9d0f037aa2..c195b387b8 100644 --- a/plugins/scaffolder-backend-module-gitlab/package.json +++ b/plugins/scaffolder-backend-module-gitlab/package.json @@ -50,6 +50,7 @@ "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", + "@gitbeaker/requester-utils": "^41.2.0", "@gitbeaker/rest": "^41.2.0", "luxon": "^3.0.0", "winston": "^3.2.1", diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/helpers.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/helpers.ts index 7f6dead3ca..a180c35524 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/helpers.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/helpers.ts @@ -17,6 +17,7 @@ import { parseRepoUrl } from '@backstage/plugin-scaffolder-node'; import { ErrorLike, InputError, isError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { Gitlab } from '@gitbeaker/rest'; +import { GitbeakerRequestError } from '@gitbeaker/requester-utils'; export function createGitlabApi(options: { integrations: ScmIntegrationRegistry; @@ -57,7 +58,13 @@ function isGitlabError(e: unknown): e is GitlabError { return isError(e) && 'description' in e && typeof e.description === 'string'; } +function isGitbeakerRequestError(e: unknown): e is GitbeakerRequestError { + return isError(e) && (e as any).name === 'GitbeakerRequestError'; +} + export function getErrorMessage(e: unknown): string { + if (isGitbeakerRequestError(e) && e.cause) + return `${e} - ${e.cause.description}`; if (isGitlabError(e)) return `${e} - ${e.description}`; return String(e); } diff --git a/yarn.lock b/yarn.lock index 72dceeed70..8b0633a1cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7240,6 +7240,7 @@ __metadata: "@backstage/integration": "workspace:^" "@backstage/plugin-scaffolder-node": "workspace:^" "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" + "@gitbeaker/requester-utils": "npm:^41.2.0" "@gitbeaker/rest": "npm:^41.2.0" luxon: "npm:^3.0.0" winston: "npm:^3.2.1" @@ -9788,7 +9789,7 @@ __metadata: languageName: node linkType: hard -"@gitbeaker/requester-utils@npm:^41.3.0": +"@gitbeaker/requester-utils@npm:^41.2.0, @gitbeaker/requester-utils@npm:^41.3.0": version: 41.3.0 resolution: "@gitbeaker/requester-utils@npm:41.3.0" dependencies: From 917d004aa6f1c0cc931fc65f669bd5ff65ea40b8 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Fri, 25 Jul 2025 19:20:35 +0100 Subject: [PATCH 015/180] First pass at bringing new DataTable to BUI Signed-off-by: Charles de Dreuille --- .../DataTable/DataTable.stories.tsx | 69 +++++---- .../components/DataTable/DataTable.styles.css | 37 +++++ .../ui/src/components/DataTable/DataTable.tsx | 133 +++++++++++++----- .../DataTable/DataTableHeadContent.tsx | 85 +++++++++++ .../Pagination/DataTablePagination.tsx | 96 ------------- .../DataTable/Root/DataTableRoot.styles.css | 5 - .../DataTable/Root/DataTableRoot.tsx | 58 -------- .../DataTable/Table/DataTableTable.tsx | 101 ------------- packages/ui/src/components/DataTable/index.ts | 8 +- .../components/DataTable/mocked-columns.tsx | 29 ++-- .../components/DataTable/{Root => }/types.ts | 7 +- .../DataTablePagination.stories.tsx | 13 +- .../DataTablePagination.styles.css | 0 .../DataTablePagination.tsx | 108 ++++++++++++++ .../types.ts => DataTablePagination/index.ts} | 5 +- .../components/DataTablePagination/types.ts | 34 +++++ .../src/components/Hidden/Hidden.stories.tsx | 32 +++++ .../src/components/Hidden/Hidden.styles.css | 28 ++++ packages/ui/src/components/Hidden/Hidden.tsx | 22 +++ .../Table/types.ts => Hidden/index.ts} | 4 +- .../components/Table/TableCell/TableCell.tsx | 2 +- packages/ui/src/css/components.css | 7 +- packages/ui/src/index.ts | 2 + 23 files changed, 512 insertions(+), 373 deletions(-) create mode 100644 packages/ui/src/components/DataTable/DataTable.styles.css create mode 100644 packages/ui/src/components/DataTable/DataTableHeadContent.tsx delete mode 100644 packages/ui/src/components/DataTable/Pagination/DataTablePagination.tsx delete mode 100644 packages/ui/src/components/DataTable/Root/DataTableRoot.styles.css delete mode 100644 packages/ui/src/components/DataTable/Root/DataTableRoot.tsx delete mode 100644 packages/ui/src/components/DataTable/Table/DataTableTable.tsx rename packages/ui/src/components/DataTable/{Root => }/types.ts (84%) rename packages/ui/src/components/{DataTable/Pagination => DataTablePagination}/DataTablePagination.stories.tsx (81%) rename packages/ui/src/components/{DataTable/Pagination => DataTablePagination}/DataTablePagination.styles.css (100%) create mode 100644 packages/ui/src/components/DataTablePagination/DataTablePagination.tsx rename packages/ui/src/components/{DataTable/Pagination/types.ts => DataTablePagination/index.ts} (83%) create mode 100644 packages/ui/src/components/DataTablePagination/types.ts create mode 100644 packages/ui/src/components/Hidden/Hidden.stories.tsx create mode 100644 packages/ui/src/components/Hidden/Hidden.styles.css create mode 100644 packages/ui/src/components/Hidden/Hidden.tsx rename packages/ui/src/components/{DataTable/Table/types.ts => Hidden/index.ts} (85%) diff --git a/packages/ui/src/components/DataTable/DataTable.stories.tsx b/packages/ui/src/components/DataTable/DataTable.stories.tsx index 29f38e7bc2..cca1fa1f35 100644 --- a/packages/ui/src/components/DataTable/DataTable.stories.tsx +++ b/packages/ui/src/components/DataTable/DataTable.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,15 @@ import type { Meta, StoryFn, StoryObj } from '@storybook/react'; import { DataTable } from '.'; +import { DataTablePagination } from '../DataTablePagination'; +import { + TableHeader, + TableBody, + TableRow, + TableCell, + TableHead, + Table, +} from '../Table'; import { data, DataProps } from './mocked-components'; import { columns } from './mocked-columns'; import { @@ -52,10 +61,10 @@ export const Uncontrolled: Story = { }); return ( - - - - + <> + + + ); }, }; @@ -79,10 +88,10 @@ export const Controlled: Story = { }); return ( - - - - + <> + + + ); }, }; @@ -97,57 +106,57 @@ export const WithCustomTable: Story = { }); return ( - - - + <> +
+ {table.getHeaderGroups().map(headerGroup => ( - + {headerGroup.headers.map(header => { return ( - + {header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext(), )} - + ); })} - + ))} - - + + {table.getRowModel().rows?.length ? ( table.getRowModel().rows.map(row => ( - {row.getVisibleCells().map(cell => ( - + {flexRender( cell.column.columnDef.cell, cell.getContext(), )} - + ))} - + )) ) : ( - - + No results. - - + + )} - - - - + +
+ + ); }, }; diff --git a/packages/ui/src/components/DataTable/DataTable.styles.css b/packages/ui/src/components/DataTable/DataTable.styles.css new file mode 100644 index 0000000000..78c463a883 --- /dev/null +++ b/packages/ui/src/components/DataTable/DataTable.styles.css @@ -0,0 +1,37 @@ +.bui-DataTableRoot { + display: flex; + flex-direction: column; + gap: var(--bui-space-3); +} + +.bui-DataTableRoot--sort { + cursor: pointer; + user-select: none; + display: inline-flex; + align-items: center; + gap: var(--bui-space-1); +} + +.bui-DataTableRoot--sort .bui-DataTableRoot--unsorted { + opacity: 0; + transition: opacity 0.1s ease-in-out, transform 0.1s ease-in-out; +} + +.bui-DataTableRoot--sort:hover .bui-DataTableRoot--unsorted, +.bui-DataTableRoot--sort:focus .bui-DataTableRoot--unsorted { + opacity: 0.5; +} + +.bui-DataTableRoot--sort .bui-DataTableRoot--sorted-asc, +.bui-DataTableRoot--sort:hover .bui-DataTableRoot--sorted-asc, +.bui-DataTableRoot--sort:focus .bui-DataTableRoot--sorted-asc { + opacity: 1; + transform: rotate(0); +} + +.bui-DataTableRoot--sort .bui-DataTableRoot--sorted-desc, +.bui-DataTableRoot--sort:hover .bui-DataTableRoot--sorted-desc, +.bui-DataTableRoot--sort:focus .bui-DataTableRoot--sorted-desc { + opacity: 1; + transform: rotate(180deg); +} diff --git a/packages/ui/src/components/DataTable/DataTable.tsx b/packages/ui/src/components/DataTable/DataTable.tsx index 1f2270bd28..b092b9b94d 100644 --- a/packages/ui/src/components/DataTable/DataTable.tsx +++ b/packages/ui/src/components/DataTable/DataTable.tsx @@ -14,46 +14,107 @@ * limitations under the License. */ -import { forwardRef } from 'react'; +import clsx from 'clsx'; +import { DataTableProps } from './types'; import { Table, + TableRow, + TableHeader, + TableHead, TableBody, TableCell, - TableHead, - TableCellProfile, - TableCellText, - TableHeader, - TableRow, } from '../Table'; -import { DataTableRoot } from './Root/DataTableRoot'; -import { DataTablePagination } from './Pagination/DataTablePagination'; -import { Table as TanstackTable } from '@tanstack/react-table'; -import { DataTableTable } from './Table/DataTableTable'; +import { flexRender } from '@tanstack/react-table'; +import { DataTableHeadContent } from './DataTableHeadContent'; -const TableRoot = forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ); -TableRoot.displayName = Table.displayName; +function getAriaSort(sortDirection: string | false) { + if (sortDirection === 'asc') { + return 'ascending'; + } + if (sortDirection === 'desc') { + return 'descending'; + } + return 'none'; +} -/** - * DataTable component for displaying tabular data with pagination - * @public - */ -export const DataTable = { - Root: DataTableRoot as ( - props: { - table: TanstackTable; - } & React.HTMLAttributes, - ) => JSX.Element, - Pagination: DataTablePagination, - Table: DataTableTable, - TableRoot: TableRoot, - TableHeader: TableHeader, - TableBody: TableBody, - TableRow: TableRow, - TableCell: TableCell, - TableCellText: TableCellText, - TableCellProfile: TableCellProfile, - TableHead: TableHead, -}; +/** @public */ +function DataTable( + props: DataTableProps & { ref?: React.ForwardedRef }, +) { + const { className, table, ref, ...rest } = props; + + return ( +
+ + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(header => { + return ( + + {header.isPlaceholder ? null : ( + + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map(row => { + const rowData = row.original as TData & { onClick?: () => void }; + const handleRowClick = rowData.onClick + ? (e: React.MouseEvent) => { + if (!e.isPropagationStopped()) { + rowData.onClick!(); + } + } + : undefined; + + return ( + + {row.getVisibleCells().map(cell => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ); + }) + ) : ( + + + No results. + + + )} + +
+ ); +} + +DataTable.displayName = 'DataTable'; + +export { DataTable }; diff --git a/packages/ui/src/components/DataTable/DataTableHeadContent.tsx b/packages/ui/src/components/DataTable/DataTableHeadContent.tsx new file mode 100644 index 0000000000..8a8453715a --- /dev/null +++ b/packages/ui/src/components/DataTable/DataTableHeadContent.tsx @@ -0,0 +1,85 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { useCallback, useMemo } from 'react'; +import clsx from 'clsx'; +import { flexRender } from '@tanstack/react-table'; +import { Icon } from '../Icon'; +import { Header } from '@tanstack/react-table'; +import { Hidden } from '../Hidden'; + +function getSortTitle(nextOrder: string | false) { + if (nextOrder === 'asc') { + return 'Sort ascending'; + } + if (nextOrder === 'desc') { + return 'Sort descending'; + } + return 'Clear sort'; +} + +interface DataTableHeadContentProps { + header: Header; +} + +export function DataTableHeadContent({ + header, +}: DataTableHeadContentProps) { + const headerContent = useMemo( + () => flexRender(header.column.columnDef.header, header.getContext()), + [header], + ); + + const handleSort = useCallback( + (e: React.MouseEvent | React.KeyboardEvent) => { + if ('key' in e && e.key !== 'Enter' && e.key !== ' ') { + return; + } + e.preventDefault(); + header.column.getToggleSortingHandler()?.(e); + }, + [header], + ); + + if (!header.column.getCanSort()) { + return headerContent; + } + + return ( + + {headerContent} + , {getSortTitle(header.column.getNextSortingOrder())} + + + ); +} diff --git a/packages/ui/src/components/DataTable/Pagination/DataTablePagination.tsx b/packages/ui/src/components/DataTable/Pagination/DataTablePagination.tsx deleted file mode 100644 index eb319d948e..0000000000 --- a/packages/ui/src/components/DataTable/Pagination/DataTablePagination.tsx +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { forwardRef } from 'react'; -import { Text } from '../../Text'; -import { DataTablePaginationProps } from './types'; -import { ButtonIcon } from '../../ButtonIcon'; -import clsx from 'clsx'; -import { Select } from '../../Select'; -import { useDataTable } from '../Root/DataTableRoot'; -import { Icon } from '../../Icon'; - -/** @public */ -const DataTablePagination = forwardRef( - ( - props: DataTablePaginationProps, - ref: React.ForwardedRef, - ) => { - const { className, ...rest } = props; - const { table } = useDataTable(); - const pageIndex = table?.getState().pagination.pageIndex; - const pageSize = table?.getState().pagination.pageSize; - const rowCount = table?.getRowCount(); - const fromCount = (pageIndex ?? 0) * (pageSize ?? 10) + 1; - const toCount = Math.min( - ((pageIndex ?? 0) + 1) * (pageSize ?? 10), - rowCount, - ); - - return ( -
-
- {!table.options.manualPagination && ( - { + const newPageSize = Number(value); + table?.setPageSize(newPageSize); + onPageSizeChange?.(newPageSize); + }} + className="bui-DataTablePagination--select" + /> + )} +
+
+ {`${fromCount} - ${toCount} of ${rowCount}`} + { + table?.previousPage(); + onPreviousPage?.(); + }} + isDisabled={!table?.getCanPreviousPage()} + icon={} + aria-label="Previous" + /> + { + table?.nextPage(); + onNextPage?.(); + }} + isDisabled={!table?.getCanNextPage()} + icon={} + aria-label="Next" + /> +
+
+ ); +} + +DataTablePagination.displayName = 'DataTablePagination'; + +export { DataTablePagination }; diff --git a/packages/ui/src/components/DataTable/Pagination/types.ts b/packages/ui/src/components/DataTablePagination/index.ts similarity index 83% rename from packages/ui/src/components/DataTable/Pagination/types.ts rename to packages/ui/src/components/DataTablePagination/index.ts index b2a895c465..0ddc63a304 100644 --- a/packages/ui/src/components/DataTable/Pagination/types.ts +++ b/packages/ui/src/components/DataTablePagination/index.ts @@ -14,6 +14,5 @@ * limitations under the License. */ -/** @public */ -export interface DataTablePaginationProps - extends React.HTMLAttributes {} +export { DataTablePagination } from './DataTablePagination'; +export type { DataTablePaginationProps } from './types'; diff --git a/packages/ui/src/components/DataTablePagination/types.ts b/packages/ui/src/components/DataTablePagination/types.ts new file mode 100644 index 0000000000..ca73af1ec7 --- /dev/null +++ b/packages/ui/src/components/DataTablePagination/types.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Table } from '@tanstack/react-table'; + +/** @public */ +export interface DataTablePaginationProps + extends React.HTMLAttributes { + table?: Table; + onNextPage?: () => void; + onPreviousPage?: () => void; + onPageSizeChange?: (pageSize: number) => void; + showPageSizeOptions?: boolean; +} + +/** @public */ +export type DataTablePaginationComponent = ( + props: DataTablePaginationProps & { + ref?: React.ForwardedRef; + }, +) => React.ReactElement; diff --git a/packages/ui/src/components/Hidden/Hidden.stories.tsx b/packages/ui/src/components/Hidden/Hidden.stories.tsx new file mode 100644 index 0000000000..0957bf2e18 --- /dev/null +++ b/packages/ui/src/components/Hidden/Hidden.stories.tsx @@ -0,0 +1,32 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta, StoryObj } from '@storybook/react'; +import { Hidden } from './Hidden'; + +const meta = { + title: 'Components/Hidden', + component: Hidden, +} satisfies Meta