Skip to content

Commit 2fd75ad

Browse files
[Orchestrator] UI improvements: workflows, runs, details, and Admin nav placement (#3446)
* Improve Orchestrator UI and move nav item under Administration. Updates workflows and runs tables, empty states, success ratio, run logs, entity/run-by filters, and dynamic plugin config for the Admin sidebar placement. Co-authored-by: Cursor <cursoragent@cursor.com> * Update orchestrator API reports for new translation keys. Reflect UI-related translation additions in report.api.md and report-alpha.api.md. Co-authored-by: Cursor <cursoragent@cursor.com> * Extract shared Orchestrator UI to reduce code duplication. Move repeated table filters, info card layout, and permission batch logic into reusable modules to address Sonar duplication findings. Co-authored-by: Cursor <cursoragent@cursor.com> * Remove average duration from workflow details until API support exists. The backend does not yet expose avgDurationMs, so drop the field and related translation keys from the entity workflow details card. Co-authored-by: Cursor <cursoragent@cursor.com> * Show average duration on entity workflow details. Display workflowRunStats.averageTimeToComplete on the entity layout workflow details card now that the overview API exposes run stats. Co-authored-by: Cursor <cursoragent@cursor.com> * Update orchestrator API reports for average duration translation key. Reorder workflow.fields.averageDuration in report output to match api-reports ordering. Co-authored-by: Cursor <cursoragent@cursor.com> * Address Orchestrator UI PR feedback for runs and run details. Disable the run variables table action with a tooltip when no variables exist, align Results card content, add Run by to the run details card, and export VariablesDialog props for typing. Co-authored-by: Cursor <cursoragent@cursor.com> * Improve Orchestrator workflows tab UX and fix legacy app startup. Keep the workflows count visible across tabs without reload flicker, link runs-last-month counts to workflow runs pages, and register toastApi in the legacy dev app. Co-authored-by: Cursor <cursoragent@cursor.com> * Improve Orchestrator workflows list and runs filter UX. Populate Run by options from actual workflow runners, add backend pagination with global search and synced counts on the workflows tab, and keep tab and table totals aligned when filtering. Co-authored-by: Cursor <cursoragent@cursor.com> * Disable Version column sorting on All runs table. The data index ProcessInstanceOrderBy enum does not support version, so clicking the column header triggered a GraphQL error. Co-authored-by: Cursor <cursoragent@cursor.com> * Address Orchestrator UI PR review feedback. Keep the workflows table visible when search has no matches, trim Run by effect dependencies, fix legacy toast success severity, use translation interpolation helpers, localize success ratio and table filter strings. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent e01563c commit 2fd75ad

62 files changed

Lines changed: 3816 additions & 819 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-orchestrator': minor
3+
---
4+
5+
Improve Orchestrator UI for workflows, runs, and workflow details, and place the sidebar item under Administration.

workspaces/orchestrator/docs/dynamic-plugin-installation.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ plugins:
2424
menuItem:
2525
icon: orchestratorIcon
2626
text: Orchestrator
27+
textKey: menuItem.orchestrator
2728
path: /orchestrator
29+
menuItems:
30+
orchestrator:
31+
parent: default.admin
32+
icon: orchestratorIcon
2833
entityTabs:
2934
- path: /workflows
3035
title: Workflows

workspaces/orchestrator/entities/users.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
---
22
apiVersion: backstage.io/v1alpha1
33
kind: User
4+
metadata:
5+
name: guest
6+
namespace: development
7+
spec:
8+
profile:
9+
displayName: Guest User
10+
memberOf: []
11+
---
12+
apiVersion: backstage.io/v1alpha1
13+
kind: User
414
metadata:
515
name: mareklibra
616
spec:

workspaces/orchestrator/packages/app-legacy/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"@backstage/core-app-api": "^1.20.1",
2727
"@backstage/core-components": "^0.18.10",
2828
"@backstage/core-plugin-api": "^1.12.6",
29+
"@backstage/frontend-plugin-api": "^0.17.1",
2930
"@backstage/integration-react": "^1.2.18",
3031
"@backstage/plugin-api-docs": "^0.14.1",
3132
"@backstage/plugin-catalog": "^2.0.5",

workspaces/orchestrator/packages/app-legacy/src/apis.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,73 @@
1414
* limitations under the License.
1515
*/
1616

17+
import { isValidElement, ReactNode } from 'react';
18+
1719
import {
20+
alertApiRef,
1821
AnyApiFactory,
1922
configApiRef,
2023
createApiFactory,
2124
} from '@backstage/core-plugin-api';
25+
import { ToastApiMessage, toastApiRef } from '@backstage/frontend-plugin-api';
2226
import {
2327
ScmAuth,
2428
ScmIntegrationsApi,
2529
scmIntegrationsApiRef,
2630
} from '@backstage/integration-react';
2731

32+
const reactNodeToString = (node: ReactNode | null | undefined): string => {
33+
if (node === null || node === undefined) {
34+
return '';
35+
}
36+
if (typeof node === 'string') {
37+
return node;
38+
}
39+
if (Array.isArray(node)) {
40+
return node.map(reactNodeToString).join(' ');
41+
}
42+
if (isValidElement(node)) {
43+
return reactNodeToString(node.props.children);
44+
}
45+
return '';
46+
};
47+
48+
const toastStatusToSeverity = (message: ToastApiMessage) => {
49+
const toastStatus = message.status ?? 'success';
50+
switch (toastStatus) {
51+
case 'warning':
52+
return 'warning';
53+
case 'danger':
54+
return 'error';
55+
case 'success':
56+
return 'success';
57+
default:
58+
return 'info';
59+
}
60+
};
61+
2862
export const apis: AnyApiFactory[] = [
2963
createApiFactory({
3064
api: scmIntegrationsApiRef,
3165
deps: { configApi: configApiRef },
3266
factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi),
3367
}),
3468
ScmAuth.createDefaultApiFactory(),
69+
createApiFactory({
70+
api: toastApiRef,
71+
deps: { alertApi: alertApiRef },
72+
factory: ({ alertApi }) => ({
73+
post(toast: ToastApiMessage) {
74+
alertApi.post({
75+
message: reactNodeToString(toast?.title),
76+
severity: toastStatusToSeverity(toast),
77+
display: 'transient',
78+
});
79+
80+
return {
81+
close: () => {},
82+
};
83+
},
84+
}),
85+
}),
3586
];

workspaces/orchestrator/plugins/orchestrator/app-config.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ dynamicPlugins:
1414
menuItem:
1515
icon: orchestratorIcon
1616
text: Orchestrator
17+
textKey: menuItem.orchestrator
18+
menuItems:
19+
orchestrator:
20+
parent: default.admin
21+
icon: orchestratorIcon
1722
entityTabs:
1823
- path: /workflows
1924
title: Workflows

workspaces/orchestrator/plugins/orchestrator/report-alpha.api.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,18 +229,27 @@ export const orchestratorTranslationRef: TranslationRef<
229229
readonly 'table.headers.version': string;
230230
readonly 'table.headers.duration': string;
231231
readonly 'table.headers.status': string;
232+
readonly 'table.headers.entity': string;
232233
readonly 'table.headers.runStatus': string;
233234
readonly 'table.headers.started': string;
234235
readonly 'table.headers.workflowStatus': string;
235236
readonly 'table.headers.lastRun': string;
236237
readonly 'table.headers.lastRunStatus': string;
238+
readonly 'table.headers.runsLastMonth': string;
239+
readonly 'table.headers.successRatio': string;
237240
readonly 'table.headers.workflowName': string;
241+
readonly 'table.headers.runBy': string;
238242
readonly 'table.actions.run': string;
239243
readonly 'table.actions.runAsEvent': string;
240244
readonly 'table.actions.viewRuns': string;
241245
readonly 'table.actions.viewInputSchema': string;
246+
readonly 'table.actions.viewRunVariables': string;
247+
readonly 'table.filters.placeholder': string;
242248
readonly 'table.filters.status': string;
249+
readonly 'table.filters.entity': string;
243250
readonly 'table.filters.started': string;
251+
readonly 'table.filters.runBy': string;
252+
readonly 'table.filters.clearAll': string;
244253
readonly 'table.filters.startedOptions.today': string;
245254
readonly 'table.filters.startedOptions.yesterday': string;
246255
readonly 'table.filters.startedOptions.last7days': string;
@@ -287,6 +296,7 @@ export const orchestratorTranslationRef: TranslationRef<
287296
readonly 'run.status.failed': string;
288297
readonly 'run.status.completed': string;
289298
readonly 'run.status.aborted': string;
299+
readonly 'run.status.abortedWithoutTime': string;
290300
readonly 'run.status.completedWithMessage': string;
291301
readonly 'run.status.failedAt': string;
292302
readonly 'run.status.completedAt': string;
@@ -319,14 +329,25 @@ export const orchestratorTranslationRef: TranslationRef<
319329
readonly 'workflow.definition': string;
320330
readonly 'workflow.status.available': string;
321331
readonly 'workflow.status.unavailable': string;
332+
readonly 'workflow.successRatio': string;
333+
readonly 'workflow.inputSchema': string;
334+
readonly 'workflow.inputSchemaDescription': string;
335+
readonly 'workflow.successRatioDescription': string;
336+
readonly 'workflow.runSuccess': string;
337+
readonly 'workflow.ofTotal': string;
338+
readonly 'workflow.statsSuccess': string;
339+
readonly 'workflow.statsFailed': string;
322340
readonly 'workflow.fields.description': string;
323341
readonly 'workflow.fields.version': string;
324342
readonly 'workflow.fields.workflowId': string;
325343
readonly 'workflow.fields.duration': string;
344+
readonly 'workflow.fields.entity': string;
326345
readonly 'workflow.fields.runStatus': string;
327346
readonly 'workflow.fields.started': string;
328347
readonly 'workflow.fields.workflowStatus': string;
348+
readonly 'workflow.fields.runBy': string;
329349
readonly 'workflow.fields.workflow': string;
350+
readonly 'workflow.fields.averageDuration': string;
330351
readonly 'workflow.fields.workflowIdCopied': string;
331352
readonly 'workflow.messages.areYouSureYouWantToRunThisWorkflow': string;
332353
readonly 'workflow.messages.userNotAuthorizedExecute': string;
@@ -336,7 +357,9 @@ export const orchestratorTranslationRef: TranslationRef<
336357
readonly 'workflow.buttons.running': string;
337358
readonly 'workflow.buttons.runWorkflow': string;
338359
readonly 'workflow.buttons.runAgain': string;
360+
readonly 'workflow.buttons.entireWorkflow': string;
339361
readonly 'workflow.buttons.fromFailurePoint': string;
362+
readonly 'workflow.buttons.fromAbortedPoint': string;
340363
readonly 'workflow.buttons.runFailedAgain': string;
341364
readonly 'messages.noDataAvailable': string;
342365
readonly 'messages.noVariablesFound': string;
@@ -353,6 +376,7 @@ export const orchestratorTranslationRef: TranslationRef<
353376
readonly 'tooltips.workflowDown': string;
354377
readonly 'tooltips.suspended': string;
355378
readonly 'tooltips.userNotAuthorizedAbort': string;
379+
readonly 'tooltips.retriggerNotSupportedForAborted': string;
356380
readonly 'reviewStep.hiddenFieldsNote': string;
357381
readonly 'reviewStep.showHiddenParameters': string;
358382
readonly 'permissions.accessDenied': string;
@@ -363,6 +387,13 @@ export const orchestratorTranslationRef: TranslationRef<
363387
readonly 'permissions.notYourRun': string;
364388
readonly 'alerts.duplicateWorkflowIds.message': string;
365389
readonly 'alerts.duplicateWorkflowIds.learnMore': string;
390+
readonly 'emptyState.workflows.title': string;
391+
readonly 'emptyState.workflows.description': string;
392+
readonly 'emptyState.workflows.viewDocumentation': string;
393+
readonly 'emptyState.runs.title': string;
394+
readonly 'emptyState.runs.description': string;
395+
readonly 'emptyState.runs.runWorkflow': string;
396+
readonly 'emptyState.illustrationAlt': string;
366397
readonly 'stepperObjectField.error': string;
367398
readonly 'formDecorator.error': string;
368399
readonly 'samlSso.body': string;

workspaces/orchestrator/plugins/orchestrator/report.api.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,27 @@ export const orchestratorTranslationRef: TranslationRef<
4545
readonly 'table.headers.version': string;
4646
readonly 'table.headers.duration': string;
4747
readonly 'table.headers.status': string;
48+
readonly 'table.headers.entity': string;
4849
readonly 'table.headers.runStatus': string;
4950
readonly 'table.headers.started': string;
5051
readonly 'table.headers.workflowStatus': string;
5152
readonly 'table.headers.lastRun': string;
5253
readonly 'table.headers.lastRunStatus': string;
54+
readonly 'table.headers.runsLastMonth': string;
55+
readonly 'table.headers.successRatio': string;
5356
readonly 'table.headers.workflowName': string;
57+
readonly 'table.headers.runBy': string;
5458
readonly 'table.actions.run': string;
5559
readonly 'table.actions.runAsEvent': string;
5660
readonly 'table.actions.viewRuns': string;
5761
readonly 'table.actions.viewInputSchema': string;
62+
readonly 'table.actions.viewRunVariables': string;
63+
readonly 'table.filters.placeholder': string;
5864
readonly 'table.filters.status': string;
65+
readonly 'table.filters.entity': string;
5966
readonly 'table.filters.started': string;
67+
readonly 'table.filters.runBy': string;
68+
readonly 'table.filters.clearAll': string;
6069
readonly 'table.filters.startedOptions.today': string;
6170
readonly 'table.filters.startedOptions.yesterday': string;
6271
readonly 'table.filters.startedOptions.last7days': string;
@@ -103,6 +112,7 @@ export const orchestratorTranslationRef: TranslationRef<
103112
readonly 'run.status.failed': string;
104113
readonly 'run.status.completed': string;
105114
readonly 'run.status.aborted': string;
115+
readonly 'run.status.abortedWithoutTime': string;
106116
readonly 'run.status.completedWithMessage': string;
107117
readonly 'run.status.failedAt': string;
108118
readonly 'run.status.completedAt': string;
@@ -135,14 +145,25 @@ export const orchestratorTranslationRef: TranslationRef<
135145
readonly 'workflow.definition': string;
136146
readonly 'workflow.status.available': string;
137147
readonly 'workflow.status.unavailable': string;
148+
readonly 'workflow.successRatio': string;
149+
readonly 'workflow.inputSchema': string;
150+
readonly 'workflow.inputSchemaDescription': string;
151+
readonly 'workflow.successRatioDescription': string;
152+
readonly 'workflow.runSuccess': string;
153+
readonly 'workflow.ofTotal': string;
154+
readonly 'workflow.statsSuccess': string;
155+
readonly 'workflow.statsFailed': string;
138156
readonly 'workflow.fields.description': string;
139157
readonly 'workflow.fields.version': string;
140158
readonly 'workflow.fields.workflowId': string;
141159
readonly 'workflow.fields.duration': string;
160+
readonly 'workflow.fields.entity': string;
142161
readonly 'workflow.fields.runStatus': string;
143162
readonly 'workflow.fields.started': string;
144163
readonly 'workflow.fields.workflowStatus': string;
164+
readonly 'workflow.fields.runBy': string;
145165
readonly 'workflow.fields.workflow': string;
166+
readonly 'workflow.fields.averageDuration': string;
146167
readonly 'workflow.fields.workflowIdCopied': string;
147168
readonly 'workflow.messages.areYouSureYouWantToRunThisWorkflow': string;
148169
readonly 'workflow.messages.userNotAuthorizedExecute': string;
@@ -152,7 +173,9 @@ export const orchestratorTranslationRef: TranslationRef<
152173
readonly 'workflow.buttons.running': string;
153174
readonly 'workflow.buttons.runWorkflow': string;
154175
readonly 'workflow.buttons.runAgain': string;
176+
readonly 'workflow.buttons.entireWorkflow': string;
155177
readonly 'workflow.buttons.fromFailurePoint': string;
178+
readonly 'workflow.buttons.fromAbortedPoint': string;
156179
readonly 'workflow.buttons.runFailedAgain': string;
157180
readonly 'messages.noDataAvailable': string;
158181
readonly 'messages.noVariablesFound': string;
@@ -169,6 +192,7 @@ export const orchestratorTranslationRef: TranslationRef<
169192
readonly 'tooltips.workflowDown': string;
170193
readonly 'tooltips.suspended': string;
171194
readonly 'tooltips.userNotAuthorizedAbort': string;
195+
readonly 'tooltips.retriggerNotSupportedForAborted': string;
172196
readonly 'reviewStep.hiddenFieldsNote': string;
173197
readonly 'reviewStep.showHiddenParameters': string;
174198
readonly 'permissions.accessDenied': string;
@@ -179,6 +203,13 @@ export const orchestratorTranslationRef: TranslationRef<
179203
readonly 'permissions.notYourRun': string;
180204
readonly 'alerts.duplicateWorkflowIds.message': string;
181205
readonly 'alerts.duplicateWorkflowIds.learnMore': string;
206+
readonly 'emptyState.workflows.title': string;
207+
readonly 'emptyState.workflows.description': string;
208+
readonly 'emptyState.workflows.viewDocumentation': string;
209+
readonly 'emptyState.runs.title': string;
210+
readonly 'emptyState.runs.description': string;
211+
readonly 'emptyState.runs.runWorkflow': string;
212+
readonly 'emptyState.illustrationAlt': string;
182213
readonly 'stepperObjectField.error': string;
183214
readonly 'formDecorator.error': string;
184215
readonly 'samlSso.body': string;

0 commit comments

Comments
 (0)