diff --git a/.changeset/25501-approval-timeline-step-label.md b/.changeset/25501-approval-timeline-step-label.md new file mode 100644 index 00000000000..ce1842a320b --- /dev/null +++ b/.changeset/25501-approval-timeline-step-label.md @@ -0,0 +1,10 @@ +--- +'@objectstack/plugin-approvals': patch +--- + +fix: the approval request page's Timeline shows each action's step by its label, not the node's machine id + +Clause-②: no +Metadata-protocol: none + +The Timeline tab of the `sys_approval_request` record page lists the request's `sys_approval_action` rows. Its step column asked for `step_name`, which holds the Approval node's id (for example `dual_signoff`), so every row showed that id. The column is now `step_label`, the step's label that the service writes on every row and the boot backfill fills on older rows. The strip's step chip (`current_step_label`) and the object's own lists already show this label. diff --git a/packages/plugins/plugin-approvals/src/sys-approval-request-page.test.ts b/packages/plugins/plugin-approvals/src/sys-approval-request-page.test.ts index 73eaba5902d..d8d1d74f1e7 100644 --- a/packages/plugins/plugin-approvals/src/sys-approval-request-page.test.ts +++ b/packages/plugins/plugin-approvals/src/sys-approval-request-page.test.ts @@ -217,6 +217,23 @@ describe('sys_approval_request detail page — the slot map', () => { expect((SysApprovalAction.fields as AnyRec).request_id).toMatchObject({ type: 'lookup', reference: 'sys_approval_request' }); }); + it('names each Timeline step by its label, step_label, never by the node\'s machine id, step_name', () => { + // Both are stored on every `sys_approval_action` row: `step_name` is the + // Approval node's id (`dual_signoff`), `step_label` the step's label the + // service copies from the request (`actionRow`). The existence check below + // cannot tell them apart, since both are declared fields, so the choice is + // pinned here, the way the strip's `current_step_label` is. + const [timeline] = TAB_ITEMS[1].children as AnyRec[]; + const columns = timeline.properties.columns as string[]; + const fields = SysApprovalAction.fields as AnyRec; + expect({ label: columns.includes('step_label'), machine: columns.includes('step_name') }) + .toEqual({ label: true, machine: false }); + expect(fields.step_label).toMatchObject({ type: 'text', label: 'Approval Step' }); + // Control: the machine id stays a declared field. It is dropped from the + // Timeline, not from the object. + expect(fields.step_name).toMatchObject({ type: 'text' }); + }); + it('every field the page names exists on its object', () => { const requestFields = Object.keys(SysApprovalRequest.fields); const actionFields = Object.keys(SysApprovalAction.fields); diff --git a/packages/plugins/plugin-approvals/src/sys-approval-request.page.ts b/packages/plugins/plugin-approvals/src/sys-approval-request.page.ts index a7d3ac80c4d..1b31bf215cb 100644 --- a/packages/plugins/plugin-approvals/src/sys-approval-request.page.ts +++ b/packages/plugins/plugin-approvals/src/sys-approval-request.page.ts @@ -61,7 +61,10 @@ import type { Page } from '@objectstack/spec/ui'; * above, so the Details tab does not draw it as a row. * - `tabs` is authored: Details (`record:details`, a bare node: see the * strip above) and Timeline — `sys_approval_action` as a - * related list keyed on `request_id`, newest first. The `tabs` slot is + * related list keyed on `request_id`, newest first. Its step column is + * `step_label` (the step's label, stored on every row), not `step_name` + * (the node's machine id, such as `dual_signoff`), as the strip's step + * chip is `current_step_label`. The `tabs` slot is * authored rather than `details` because the console builds the tab strip * from `tabs` when both are present; the details body therefore lives in * the first tab here, and the page authors no `details` slot. @@ -120,7 +123,7 @@ export const SysApprovalRequestDetailPage: Page = { properties: { objectName: 'sys_approval_action', relationshipField: 'request_id', - columns: ['created_at', 'action', 'actor_id', 'step_name', 'comment', 'attachments'], + columns: ['created_at', 'action', 'actor_id', 'step_label', 'comment', 'attachments'], sort: [{ field: 'created_at', order: 'desc' }], limit: 50, showViewAll: true,