diff --git a/workspaces/orchestrator/.changeset/yellow-poets-fold.md b/workspaces/orchestrator/.changeset/yellow-poets-fold.md
new file mode 100644
index 000000000..2f0381ab7
--- /dev/null
+++ b/workspaces/orchestrator/.changeset/yellow-poets-fold.md
@@ -0,0 +1,5 @@
+---
+'@red-hat-developer-hub/backstage-plugin-orchestrator': patch
+---
+
+add workflow tabs - details and runs
diff --git a/workspaces/orchestrator/plugins/orchestrator/src/components/Router.tsx b/workspaces/orchestrator/plugins/orchestrator/src/components/Router.tsx
index db1b8908f..0f2c0b4da 100644
--- a/workspaces/orchestrator/plugins/orchestrator/src/components/Router.tsx
+++ b/workspaces/orchestrator/plugins/orchestrator/src/components/Router.tsx
@@ -18,30 +18,28 @@ import { Route, Routes } from 'react-router-dom';
import {
executeWorkflowRouteRef,
- workflowDefinitionsRouteRef,
workflowInstanceRouteRef,
+ workflowRouteRef,
} from '../routes';
import { ExecuteWorkflowPage } from './ExecuteWorkflowPage/ExecuteWorkflowPage';
import { OrchestratorPage } from './OrchestratorPage';
-import { WorkflowDefinitionViewerPage } from './WorkflowDefinitionViewerPage';
import { WorkflowInstancePage } from './WorkflowInstancePage';
+import { WorkflowPage } from './WorkflowPage';
export const Router = () => {
return (
+ // relative to orchestrator/
} />
- }
- />
- }
- />
+ } />
}
/>
+ }
+ />
);
};
diff --git a/workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowDefinitionViewerPage/WorkflowDefinitionViewerPage.tsx b/workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowDefinitionViewerPage/WorkflowDefinitionViewerPage.tsx
index 30768389b..70e70e624 100644
--- a/workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowDefinitionViewerPage/WorkflowDefinitionViewerPage.tsx
+++ b/workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowDefinitionViewerPage/WorkflowDefinitionViewerPage.tsx
@@ -34,16 +34,13 @@ import {
import { orchestratorApiRef } from '../../api';
import { usePermissionArrayDecision } from '../../hooks/usePermissionArray';
-import {
- executeWorkflowRouteRef,
- workflowDefinitionsRouteRef,
-} from '../../routes';
+import { executeWorkflowRouteRef, workflowRouteRef } from '../../routes';
import { BaseOrchestratorPage } from '../BaseOrchestratorPage';
import { EditorViewKind, WorkflowEditor } from '../WorkflowEditor';
import WorkflowDefinitionDetailsCard from './WorkflowDefinitionDetailsCard';
export const WorkflowDefinitionViewerPage = () => {
- const { workflowId, format } = useRouteRefParams(workflowDefinitionsRouteRef);
+ const { workflowId, format } = useRouteRefParams(workflowRouteRef);
const orchestratorApi = useApi(orchestratorApiRef);
const { loading: loadingPermission, allowed: canRun } =
diff --git a/workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowEditor/WorkflowEditor.tsx b/workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowEditor/WorkflowEditor.tsx
index 9fd6c3eb3..8b71157eb 100644
--- a/workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowEditor/WorkflowEditor.tsx
+++ b/workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowEditor/WorkflowEditor.tsx
@@ -64,7 +64,7 @@ import {
} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common';
import { orchestratorApiRef } from '../../api';
-import { workflowDefinitionsRouteRef } from '../../routes';
+import { workflowRouteRef } from '../../routes';
import { WorkflowEditorLanguageService } from './channel/WorkflowEditorLanguageService';
import { WorkflowEditorLanguageServiceChannelApiImpl } from './channel/WorkflowEditorLanguageServiceChannelApiImpl';
@@ -111,7 +111,7 @@ const RefForwardingWorkflowEditor: ForwardRefRenderFunction<
const [canRender, setCanRender] = useState(false);
const [ready, setReady] = useState(false);
const navigate = useNavigate();
- const viewWorkflowLink = useRouteRef(workflowDefinitionsRouteRef);
+ const viewWorkflowLink = useRouteRef(workflowRouteRef);
const currentProcessInstance = useMemo(() => {
if (kind !== EditorViewKind.RUNTIME) {
diff --git a/workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowPage.tsx b/workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowPage.tsx
new file mode 100644
index 000000000..642bdc41c
--- /dev/null
+++ b/workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowPage.tsx
@@ -0,0 +1,43 @@
+/*
+ * 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 React from 'react';
+
+import { TabbedLayout } from '@backstage/core-components';
+import { useRouteRefParams } from '@backstage/core-plugin-api';
+
+import { workflowRouteRef, workflowRunsRouteRef } from '../routes';
+import { BaseOrchestratorPage } from './BaseOrchestratorPage';
+import { WorkflowDefinitionViewerPage } from './WorkflowDefinitionViewerPage';
+import { WorkflowRunsTabContentFiltered } from './WorkflowRunsTabContentFiltered';
+
+export const WorkflowPage = () => {
+ const { workflowId } = useRouteRefParams(workflowRouteRef);
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowRunsTabContent.tsx b/workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowRunsTabContent.tsx
index c634d919a..d9b2760a9 100644
--- a/workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowRunsTabContent.tsx
+++ b/workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowRunsTabContent.tsx
@@ -29,6 +29,7 @@ import { Grid } from '@material-ui/core';
import {
capitalize,
ellipsis,
+ ProcessInstanceState,
ProcessInstanceStatusDTO,
} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common';
@@ -44,11 +45,11 @@ import { WorkflowRunDetail } from './WorkflowRunDetail';
const makeSelectItemsFromProcessInstanceValues = () =>
[
- ProcessInstanceStatusDTO.Active,
- ProcessInstanceStatusDTO.Error,
- ProcessInstanceStatusDTO.Completed,
- ProcessInstanceStatusDTO.Aborted,
- ProcessInstanceStatusDTO.Suspended,
+ ProcessInstanceState.Active,
+ ProcessInstanceState.Error,
+ ProcessInstanceState.Completed,
+ ProcessInstanceState.Aborted,
+ ProcessInstanceState.Suspended,
].map(
(status): SelectItem => ({
label: capitalize(status),
@@ -116,7 +117,7 @@ export const WorkflowRunsTabContent = () => {
(value ?? []).filter(
(row: WorkflowRunDetail) =>
statusSelectorValue === Selector.AllItems ||
- row.status === statusSelectorValue,
+ row.status?.toLocaleLowerCase('en-US') === statusSelectorValue,
),
[statusSelectorValue, value],
);
diff --git a/workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowRunsTabContentFiltered.tsx b/workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowRunsTabContentFiltered.tsx
new file mode 100644
index 000000000..06b3b4bfd
--- /dev/null
+++ b/workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowRunsTabContentFiltered.tsx
@@ -0,0 +1,165 @@
+/*
+ * 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 React, { useState } from 'react';
+
+import {
+ ErrorPanel,
+ InfoCard,
+ Link,
+ SelectItem,
+ TableColumn,
+} from '@backstage/core-components';
+import {
+ useApi,
+ useRouteRef,
+ useRouteRefParams,
+} from '@backstage/core-plugin-api';
+
+import { Grid } from '@material-ui/core';
+
+import {
+ capitalize,
+ ellipsis,
+ ProcessInstanceState,
+ ProcessInstanceStatusDTO,
+} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common';
+
+import { orchestratorApiRef } from '../api';
+import { DEFAULT_TABLE_PAGE_SIZE, VALUE_UNAVAILABLE } from '../constants';
+import usePolling from '../hooks/usePolling';
+import { workflowInstanceRouteRef, workflowRouteRef } from '../routes';
+import { Selector } from './Selector';
+import OverrideBackstageTable from './ui/OverrideBackstageTable';
+import { mapProcessInstanceToDetails } from './WorkflowInstancePageContent';
+import { WorkflowInstanceStatusIndicator } from './WorkflowInstanceStatusIndicator';
+import { WorkflowRunDetail } from './WorkflowRunDetail';
+
+const makeSelectItemsFromProcessInstanceValues = () =>
+ [
+ ProcessInstanceState.Active,
+ ProcessInstanceState.Error,
+ ProcessInstanceState.Completed,
+ ProcessInstanceState.Aborted,
+ ProcessInstanceState.Suspended,
+ ].map(
+ (status): SelectItem => ({
+ label: capitalize(status),
+ value: status,
+ }),
+ );
+
+export const WorkflowRunsTabContentFiltered = () => {
+ const { workflowId } = useRouteRefParams(workflowRouteRef);
+
+ const orchestratorApi = useApi(orchestratorApiRef);
+ const workflowInstanceLink = useRouteRef(workflowInstanceRouteRef);
+ const [statusSelectorValue, setStatusSelectorValue] = useState(
+ Selector.AllItems,
+ );
+
+ const fetchInstances = React.useCallback(async () => {
+ // TODO: use pagination with generic permission, skip (or use FE-only) for specific permissions
+ const instances = await orchestratorApi.listInstances({});
+ const clonedData: WorkflowRunDetail[] =
+ instances.data.items?.map(mapProcessInstanceToDetails) || [];
+ return clonedData.filter(item => item.workflowId === workflowId);
+ }, [orchestratorApi, workflowId]);
+
+ const { loading, error, value } = usePolling(fetchInstances);
+
+ const columns = React.useMemo(
+ (): TableColumn[] => [
+ {
+ title: 'ID',
+ field: 'id',
+ render: data => (
+
+ {ellipsis(data.id)}
+
+ ),
+ sorting: false,
+ },
+ {
+ title: 'Workflow name',
+ field: 'name',
+ },
+ {
+ title: 'Status',
+ field: 'status',
+ render: data => (
+
+ ),
+ },
+ {
+ title: 'Category',
+ field: 'category',
+ render: data => capitalize(data.category ?? VALUE_UNAVAILABLE),
+ },
+ { title: 'Started', field: 'started', defaultSort: 'desc' },
+ { title: 'Duration', field: 'duration' },
+ ],
+ [workflowInstanceLink],
+ );
+
+ const statuses = React.useMemo(makeSelectItemsFromProcessInstanceValues, []);
+
+ const filteredData = React.useMemo(
+ () =>
+ (value ?? []).filter(
+ (row: WorkflowRunDetail) =>
+ statusSelectorValue === Selector.AllItems ||
+ row.status?.toLocaleLowerCase('en-US') === statusSelectorValue,
+ ),
+ [statusSelectorValue, value],
+ );
+
+ const selectors = React.useMemo(
+ () => (
+
+
+
+
+
+ ),
+ [statusSelectorValue, statuses],
+ );
+ const paging = (value?.length || 0) > DEFAULT_TABLE_PAGE_SIZE; // this behavior fits the backstage catalog table behavior https://github.com/backstage/backstage/blob/v1.14.0/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx#L228
+
+ return error ? (
+
+ ) : (
+
+
+
+ );
+};
diff --git a/workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowsTable.tsx b/workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowsTable.tsx
index 52c8c7e44..c7b4e457f 100644
--- a/workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowsTable.tsx
+++ b/workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowsTable.tsx
@@ -38,10 +38,7 @@ import WorkflowOverviewFormatter, {
FormattedWorkflowOverview,
} from '../dataFormatters/WorkflowOverviewFormatter';
import { usePermissionArray } from '../hooks/usePermissionArray';
-import {
- executeWorkflowRouteRef,
- workflowDefinitionsRouteRef,
-} from '../routes';
+import { executeWorkflowRouteRef, workflowRouteRef } from '../routes';
import OverrideBackstageTable from './ui/OverrideBackstageTable';
import { WorkflowInstanceStatusIndicator } from './WorkflowInstanceStatusIndicator';
@@ -98,7 +95,7 @@ const usePermittedToViewBatch = (
export const WorkflowsTable = ({ items }: WorkflowsTableProps) => {
const navigate = useNavigate();
- const definitionLink = useRouteRef(workflowDefinitionsRouteRef);
+ const definitionLink = useRouteRef(workflowRouteRef);
const executeWorkflowLink = useRouteRef(executeWorkflowRouteRef);
const [data, setData] = useState([]);
diff --git a/workspaces/orchestrator/plugins/orchestrator/src/routes.ts b/workspaces/orchestrator/plugins/orchestrator/src/routes.ts
index 2a10d7764..ab4af60e8 100644
--- a/workspaces/orchestrator/plugins/orchestrator/src/routes.ts
+++ b/workspaces/orchestrator/plugins/orchestrator/src/routes.ts
@@ -15,30 +15,40 @@
*/
import { createRouteRef, createSubRouteRef } from '@backstage/core-plugin-api';
+// orchestrator page
export const orchestratorRootRouteRef = createRouteRef({
- id: 'orchestrator',
-});
-
-export const workflowDefinitionsRouteRef = createSubRouteRef({
- id: 'orchestrator/workflows',
- parent: orchestratorRootRouteRef,
- path: '/workflows/:format/:workflowId',
+ id: 'orchestrator', // display WorkflowsTabContent
});
export const workflowInstancesRouteRef = createSubRouteRef({
id: 'orchestrator/instances',
parent: orchestratorRootRouteRef,
- path: '/instances',
+ path: '/instances', // display WorkflowRunsTabContent
});
+// single instance page
export const workflowInstanceRouteRef = createSubRouteRef({
id: 'orchestrator/instances',
parent: orchestratorRootRouteRef,
- path: '/instances/:instanceId',
+ path: '/instances/:instanceId', // display WorkflowInstancePage
+});
+
+// workflow page
+export const workflowRouteRef = createSubRouteRef({
+ id: 'orchestrator/workflows',
+ parent: orchestratorRootRouteRef,
+ path: '/workflows/:format/:workflowId', // display WorkflowDefinitionViewerPage
+});
+
+export const workflowRunsRouteRef = createSubRouteRef({
+ id: 'orchestrator/workflows',
+ parent: orchestratorRootRouteRef,
+ path: '/runs', // display WorkflowRunsTabContentFiltered
});
+// execute workflow page
export const executeWorkflowRouteRef = createSubRouteRef({
id: 'orchestrator/workflows/execute',
parent: orchestratorRootRouteRef,
- path: '/workflows/:workflowId/execute',
+ path: '/workflows/:workflowId/execute', // diaplsy ExecuteWorkflowPage
});