Skip to content

Commit cd2f0aa

Browse files
committed
Show workflow state transitions in the History list (#30)
Workflow rows now read like Volto: 'Publish (Private → Published)', using the backend-translated state titles. Unlike Volto, no literal 'undefined' is shown when no previous state is known (e.g. creation); those entries read 'Create (Private)'. This is an interim display until the final action-label wording from the design is decided.
1 parent b01174c commit cd2f0aa

2 files changed

Lines changed: 57 additions & 1 deletion

File tree

packages/cmsui/components/History/HistoryView.test.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,28 @@ describe('HistoryView', () => {
240240
expect(screen.getAllByRole('row')).toHaveLength(1);
241241
});
242242

243+
it('shows the workflow state transition like Volto', () => {
244+
const entries = [
245+
versionedEntry({ version: 2 }),
246+
workflowEntry(),
247+
workflowEntry({
248+
action: null,
249+
transition_title: 'Create',
250+
review_state: 'private',
251+
state_title: 'Private',
252+
time: '2026-06-10T10:00:00+00:00',
253+
}),
254+
] as GetHistoryResponse;
255+
render(<HistoryView content={content} history={entries} />);
256+
257+
// transition with a known previous state
258+
expect(
259+
screen.getByText('Publish (Private → Published)'),
260+
).toBeInTheDocument();
261+
// creation: no previous state, no Volto-style literal "undefined"
262+
expect(screen.getByText('Create (Private)')).toBeInTheDocument();
263+
});
264+
243265
it('survives entries with an unparsable time', () => {
244266
render(
245267
<HistoryView

packages/cmsui/components/History/HistoryView.tsx

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,37 @@ export function formatRelativeTime(iso: string, locale: string): string {
8383
return iso;
8484
}
8585

86+
// Interim Volto-parity label for workflow rows: "Publish (Private → Published)",
87+
// or just "(Private)" when no previous state is known (e.g. creation) — unlike
88+
// Volto, which renders a literal "undefined" in that case. Uses the
89+
// backend-translated state titles, so it stays neutral on the final wording
90+
// ("Published from Private", Figma) that is still an open question in #30.
91+
export function workflowStateSuffix(
92+
entry: HistoryEntry,
93+
prevStateTitle: string | undefined,
94+
): string {
95+
if (!('state_title' in entry) || !entry.state_title) return '';
96+
const from = entry.action && prevStateTitle ? `${prevStateTitle} → ` : '';
97+
return ` (${from}${entry.state_title})`;
98+
}
99+
100+
// Walks the (newest-first) entries and returns, per index, the workflow state
101+
// title that was active BEFORE that entry (Volto's prev_state_title).
102+
export function deriveWorkflowPrevStates(
103+
history: GetHistoryResponse,
104+
): (string | undefined)[] {
105+
const prev: (string | undefined)[] = new Array(history.length);
106+
let title: string | undefined;
107+
for (let i = history.length - 1; i >= 0; i -= 1) {
108+
const entry = history[i];
109+
if ('state_title' in entry && entry.state_title) {
110+
prev[i] = title;
111+
title = entry.state_title;
112+
}
113+
}
114+
return prev;
115+
}
116+
86117
interface HistoryViewProps {
87118
content: Content;
88119
history: GetHistoryResponse;
@@ -141,6 +172,8 @@ export default function HistoryView({ content, history }: HistoryViewProps) {
141172
// revision. The current revision cannot be reverted to itself.
142173
const currentVersion = history.find((entry) => 'version' in entry)?.version;
143174

175+
const workflowPrevStates = deriveWorkflowPrevStates(history);
176+
144177
// Breadcrumbs from the content's @components.breadcrumbs, mirroring the
145178
// inline Quanta breadcrumb of @plone/contents (ContentsTable) for a
146179
// consistent look. The root item carries the HomeIcon.
@@ -206,7 +239,7 @@ export default function HistoryView({ content, history }: HistoryViewProps) {
206239
</Column>
207240
</TableHeader>
208241
<TableBody>
209-
{history.map((entry) => {
242+
{history.map((entry, index) => {
210243
const versioned = 'version' in entry;
211244
const isCurrent = versioned && entry.version === currentVersion;
212245
// Stable row identity: after a revert the loader prepends a new
@@ -228,6 +261,7 @@ export default function HistoryView({ content, history }: HistoryViewProps) {
228261
`}
229262
/>
230263
{entry.transition_title}
264+
{workflowStateSuffix(entry, workflowPrevStates[index])}
231265
</span>
232266
</Cell>
233267
<Cell>{entry.actor?.fullname}</Cell>

0 commit comments

Comments
 (0)