From 9bf095a967bb3cc4fee27e8c17564b6723c97745 Mon Sep 17 00:00:00 2001 From: Oleksii Kurinnyi Date: Mon, 31 Jul 2023 16:24:41 +0300 Subject: [PATCH] Fix duplicating requests (#878) --- .../__tests__/helpers.spec.ts | 11 +- .../StepTitle/index.module.css | 4 + .../__snapshots__/index.spec.tsx.snap | 157 ++++++++++++++ .../Wizard/__tests__/index.spec.tsx | 142 +++++++++++++ .../WorkspaceProgress/Wizard/index.module.css | 40 ++++ .../WorkspaceProgress/Wizard/index.tsx | 198 ++++++++++++++++++ .../__snapshots__/index.spec.tsx.snap | 118 ++--------- .../__tests__/index.spec.tsx | 7 +- .../components/WorkspaceProgress/index.tsx | 183 +++++++++------- 9 files changed, 672 insertions(+), 188 deletions(-) create mode 100644 packages/dashboard-frontend/src/components/WorkspaceProgress/Wizard/__tests__/__snapshots__/index.spec.tsx.snap create mode 100644 packages/dashboard-frontend/src/components/WorkspaceProgress/Wizard/__tests__/index.spec.tsx create mode 100644 packages/dashboard-frontend/src/components/WorkspaceProgress/Wizard/index.module.css create mode 100644 packages/dashboard-frontend/src/components/WorkspaceProgress/Wizard/index.tsx 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`] = `