diff --git a/packages/dashboard-backend/src/devworkspaceClient/services/personalAccessTokenApi/__tests__/helpers.spec.ts b/packages/dashboard-backend/src/devworkspaceClient/services/personalAccessTokenApi/__tests__/helpers.spec.ts
index 20cae7ab5..a9fae005c 100644
--- a/packages/dashboard-backend/src/devworkspaceClient/services/personalAccessTokenApi/__tests__/helpers.spec.ts
+++ b/packages/dashboard-backend/src/devworkspaceClient/services/personalAccessTokenApi/__tests__/helpers.spec.ts
@@ -10,18 +10,17 @@
* Red Hat, Inc. - initial API and implementation
*/
+import { api } from '@eclipse-che/common';
+import k8s from '@kubernetes/client-node';
import {
buildLabelSelector,
+ DUMMY_TOKEN_DATA,
+ isPatSecret,
+ PersonalAccessTokenSecret,
toSecret,
toSecretName,
toToken,
- isPatSecret,
- PersonalAccessTokenSecret,
- TokenName,
- DUMMY_TOKEN_DATA,
} from '../helpers';
-import k8s from '@kubernetes/client-node';
-import { api } from '@eclipse-che/common';
describe('Helpers for Personal Access Token API', () => {
test('buildLabelSelector', () => {
diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/StepTitle/index.module.css b/packages/dashboard-frontend/src/components/WorkspaceProgress/StepTitle/index.module.css
index 456b41c1b..c75794f46 100644
--- a/packages/dashboard-frontend/src/components/WorkspaceProgress/StepTitle/index.module.css
+++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/StepTitle/index.module.css
@@ -10,6 +10,10 @@
* Red Hat, Inc. - initial API and implementation
*/
+.pf-c-wizard__nav {
+ color: var(--pf-global--palette--black-1000);
+}
+
.pf-c-wizard__nav .error {
color: var(--pf-global--palette--black-1000);
font-weight: bold;
diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/Wizard/__tests__/__snapshots__/index.spec.tsx.snap b/packages/dashboard-frontend/src/components/WorkspaceProgress/Wizard/__tests__/__snapshots__/index.spec.tsx.snap
new file mode 100644
index 000000000..fe4e43208
--- /dev/null
+++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/Wizard/__tests__/__snapshots__/index.spec.tsx.snap
@@ -0,0 +1,157 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`WorkspaceProgressWizard component snapshot 1`] = `
+
+
+
+
+
+
+
+
+
+`;
diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/Wizard/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/Wizard/__tests__/index.spec.tsx
new file mode 100644
index 000000000..1fdcf3969
--- /dev/null
+++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/Wizard/__tests__/index.spec.tsx
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2018-2023 Red Hat, Inc.
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ */
+
+import React from 'react';
+import WorkspaceProgressWizard, { WorkspaceProgressWizardStep } from '..';
+import { Step, StepId } from '../..';
+import getComponentRenderer, { screen } from '../../../../services/__mocks__/getComponentRenderer';
+
+const mockGoToNext = jest.fn();
+
+const { createSnapshot, renderComponent } = getComponentRenderer(getComponent);
+
+describe('WorkspaceProgressWizard', () => {
+ let steps: WorkspaceProgressWizardStep[];
+ let ref: React.RefObject;
+
+ beforeEach(() => {
+ steps = [
+ {
+ id: Step.INITIALIZE,
+ name: Step.INITIALIZE,
+ component: <>>,
+ },
+ {
+ id: Step.LIMIT_CHECK,
+ name: Step.LIMIT_CHECK,
+ component: <>>,
+ },
+ {
+ id: Step.CREATE,
+ name: Step.CREATE,
+ component: <>>,
+ steps: [
+ {
+ id: Step.FETCH,
+ name: Step.FETCH,
+ component: <>>,
+ },
+ {
+ id: Step.CONFLICT_CHECK,
+ name: Step.CONFLICT_CHECK,
+ component: <>>,
+ },
+ {
+ id: Step.APPLY,
+ name: Step.APPLY,
+ component: <>>,
+ },
+ ],
+ },
+ ];
+ ref = React.createRef();
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ test('component snapshot', () => {
+ const activeStepId: StepId = Step.LIMIT_CHECK;
+
+ const snapshot = createSnapshot(activeStepId, steps, ref);
+ expect(snapshot).toMatchSnapshot();
+ });
+
+ test('switching active step', () => {
+ const activeStepId: StepId = Step.INITIALIZE;
+
+ const { reRenderComponent } = renderComponent(activeStepId, steps, ref);
+
+ const buttonInitialize = screen.getByRole('button', { name: Step.INITIALIZE });
+ const buttonLimitCheck = screen.getByRole('button', { name: Step.LIMIT_CHECK });
+
+ expect(buttonInitialize.className.split(' ')).toEqual(expect.arrayContaining(['pf-m-current']));
+ expect(buttonLimitCheck.className.split(' ')).not.toEqual(
+ expect.arrayContaining(['pf-m-current']),
+ );
+
+ const nextActiveStepId = Step.LIMIT_CHECK;
+ reRenderComponent(nextActiveStepId, steps, ref);
+
+ const nextButtonInitialize = screen.getByRole('button', { name: Step.INITIALIZE });
+ const nextButtonLimitCheck = screen.getByRole('button', { name: Step.LIMIT_CHECK });
+
+ expect(nextButtonInitialize.className.split(' ')).not.toEqual(
+ expect.arrayContaining(['pf-m-current']),
+ );
+ expect(nextButtonLimitCheck.className.split(' ')).toEqual(
+ expect.arrayContaining(['pf-m-current']),
+ );
+ });
+
+ describe('trigger goToNext using reference', () => {
+ test('on the very first step', () => {
+ const activeStepId: StepId = Step.INITIALIZE;
+
+ renderComponent(activeStepId, steps, ref);
+
+ expect(mockGoToNext).not.toHaveBeenCalled();
+
+ ref.current?.goToNext();
+
+ expect(mockGoToNext).toHaveBeenCalledWith(Step.LIMIT_CHECK, Step.INITIALIZE);
+ });
+
+ test('on the very last step', () => {
+ const activeStepId: StepId = Step.APPLY;
+
+ renderComponent(activeStepId, steps, ref);
+
+ expect(mockGoToNext).not.toHaveBeenCalled();
+
+ ref.current?.goToNext();
+
+ expect(mockGoToNext).toHaveBeenCalledWith(undefined, Step.APPLY);
+ });
+ });
+});
+
+function getComponent(
+ activeStepId: StepId,
+ steps: WorkspaceProgressWizardStep[],
+ ref: React.RefObject,
+): React.ReactElement {
+ return (
+
+ );
+}
diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/Wizard/index.module.css b/packages/dashboard-frontend/src/components/WorkspaceProgress/Wizard/index.module.css
new file mode 100644
index 000000000..7aca84ceb
--- /dev/null
+++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/Wizard/index.module.css
@@ -0,0 +1,40 @@
+/*
+* Copyright (c) 2018-2023 Red Hat, Inc.
+* This program and the accompanying materials are made
+* available under the terms of the Eclipse Public License 2.0
+* which is available at https://www.eclipse.org/legal/epl-2.0/
+*
+* SPDX-License-Identifier: EPL-2.0
+*
+* Contributors:
+* Red Hat, Inc. - initial API and implementation
+*/
+
+.progress {
+ height: 700px;
+}
+
+.progress .pf-c-wizard__nav {
+ width: 100%;
+ border: none;
+ box-shadow: none;
+}
+
+.progress .pf-c-wizard__nav-link {
+ margin-left: 8px;
+ pointer-events: none;
+}
+
+.progress .pf-c-wizard__nav-link:hover,
+.progress .pf-c-wizard__nav-link:focus {
+ --pf-c-wizard__nav-link--Color: var(--pf-c-wizard__nav-link--Color);
+ --pf-c-wizard__nav-link-toggle--Color: var(--pf-c-wizard__nav-link--Color);
+}
+
+.progress .pf-c-wizard__nav-link > svg {
+ margin-right: 5px;
+}
+
+.progress .pf-c-wizard__toggle {
+ display: none;
+}
diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/Wizard/index.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/Wizard/index.tsx
new file mode 100644
index 000000000..feee8d193
--- /dev/null
+++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/Wizard/index.tsx
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2018-2023 Red Hat, Inc.
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ */
+
+import * as PF from '@patternfly/react-core';
+import wizardStyles from '@patternfly/react-styles/css/components/Wizard/wizard';
+import React from 'react';
+import { StepId } from '..';
+import styles from './index.module.css';
+
+export interface WorkspaceProgressWizardStep extends PF.WizardStep {
+ id: StepId;
+ steps?: WorkspaceProgressWizardStep[];
+}
+
+export type Props = {
+ activeStepId: StepId;
+ steps: WorkspaceProgressWizardStep[];
+ onNext: (nextStepId: StepId | undefined, prevStepId: StepId) => void;
+};
+
+class WorkspaceProgressWizard extends React.Component {
+ private handleGoToStepById() {
+ console.warn('Not implemented: handleGoToStepById');
+ return false;
+ }
+
+ private handleGoToStepByName() {
+ console.warn('Not implemented: handleGoToStepByName');
+ return false;
+ }
+
+ private handleBack() {
+ console.warn('Not implemented: handleBack');
+ return false;
+ }
+
+ private handleClose() {
+ console.warn('Not implemented: handleClose');
+ return false;
+ }
+
+ public goToNext(): void {
+ const flattenedSteps = this.flattenSteps(this.props.steps);
+ // find the index of the next after the current active step
+ const activeStepNumber = this.getFlattenedStepsNumber(flattenedSteps, this.props.activeStepId);
+
+ if (activeStepNumber === 0) {
+ // current step is not found by its id, do nothing
+ return;
+ }
+
+ if (activeStepNumber === flattenedSteps.length) {
+ // last step
+ this.props.onNext(undefined, this.props.activeStepId);
+ return;
+ }
+
+ const nextStep = flattenedSteps[activeStepNumber];
+ this.props.onNext(nextStep.id, this.props.activeStepId);
+ }
+
+ private handleNavToggle() {
+ console.warn('Not implemented: handleNavToggle');
+ return false;
+ }
+
+ private setDefaultValues(steps: WorkspaceProgressWizardStep[]): WorkspaceProgressWizardStep[] {
+ const withDefaults = (step: WorkspaceProgressWizardStep) =>
+ Object.assign({ canJumpTo: true }, step);
+
+ return steps.map(step => {
+ const subSteps = step.steps;
+ if (subSteps !== undefined) {
+ subSteps.map(subStep => withDefaults(subStep));
+ }
+ return withDefaults(step);
+ });
+ }
+
+ private flattenSteps(steps: WorkspaceProgressWizardStep[]): WorkspaceProgressWizardStep[] {
+ return steps.reduce((acc, step) => {
+ if (step.steps) {
+ return acc.concat(step.steps);
+ }
+ return acc.concat(step);
+ }, [] as WorkspaceProgressWizardStep[]);
+ }
+
+ /**
+ * Returns the number (index + 1) of the step in the flattened steps array
+ */
+ private getFlattenedStepsNumber(
+ flattenedSteps: WorkspaceProgressWizardStep[],
+ stepId: StepId,
+ ): number {
+ return flattenedSteps.findIndex(step => step.id === stepId) + 1;
+ }
+
+ private buildWizardNav(
+ activeStepId: StepId,
+ steps: WorkspaceProgressWizardStep[],
+ ): React.ReactElement {
+ const stepsWithDefaults = this.setDefaultValues(steps);
+ const flattenedSteps = this.flattenSteps(stepsWithDefaults);
+
+ return (
+
+ {stepsWithDefaults.map(step => {
+ const { canJumpTo, name, steps = [], id } = step;
+ const flattenedStepNumber = this.getFlattenedStepsNumber(flattenedSteps, id);
+
+ const hasActiveChild = steps.some(subStep => subStep.id === activeStepId);
+
+ return (
+ this.handleGoToStepById()}
+ >
+ {steps.length !== 0 && (
+
+ {steps.map(subStep => {
+ if (subStep.isFinishedStep) {
+ return;
+ }
+
+ const { canJumpTo, name, id } = subStep;
+ const flattenedStepNumber = this.getFlattenedStepsNumber(flattenedSteps, id);
+ return (
+ this.handleGoToStepById()}
+ />
+ );
+ })}
+
+ )}
+
+ );
+ })}
+
+ );
+ }
+
+ render() {
+ const { activeStepId, steps } = this.props;
+
+ const hasNoBodyPadding = false;
+
+ const activeStep = steps[0];
+
+ return (
+ this.handleGoToStepById(),
+ goToStepByName: () => this.handleGoToStepByName(),
+ onBack: () => this.handleBack(),
+ onClose: () => this.handleClose(),
+ onNext: () => this.goToNext(),
+ }}
+ >
+
+
this.buildWizardNav(activeStepId, steps)}
+ onNavToggle={() => this.handleNavToggle()}
+ steps={[]}
+ >
+
+
+
+
+ );
+ }
+}
+
+export default React.forwardRef((props, ref) => (
+
+));
diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/__tests__/__snapshots__/index.spec.tsx.snap b/packages/dashboard-frontend/src/components/WorkspaceProgress/__tests__/__snapshots__/index.spec.tsx.snap
index 759c18320..889f6167e 100644
--- a/packages/dashboard-frontend/src/components/WorkspaceProgress/__tests__/__snapshots__/index.spec.tsx.snap
+++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/__tests__/__snapshots__/index.spec.tsx.snap
@@ -2,12 +2,12 @@
exports[`LoaderProgress workspace creation flow snapshot 1`] = `