From e3fad741eae8026373c69c0bf999864a2818bf00 Mon Sep 17 00:00:00 2001 From: Oleksii Kurinnyi Date: Fri, 11 Aug 2023 10:56:13 +0300 Subject: [PATCH] Revert "Workspaces Startup Page Improvements" --- packages/dashboard-frontend/jest.config.js | 3 - .../__tests__/index.spec.tsx | 1 - .../CheckRunningWorkspacesLimit/index.tsx | 11 +- .../Apply/Devfile/__tests__/index.spec.tsx | 1 - .../CreatingSteps/Apply/Devfile/index.tsx | 11 +- .../Apply/Resources/__tests__/index.spec.tsx | 1 - .../CreatingSteps/Apply/Resources/index.tsx | 9 +- .../__tests__/index.spec.tsx | 1 - .../CheckExistingWorkspaces/index.tsx | 11 +- .../CreateWorkspace/__tests__/index.spec.tsx | 1 - .../CreatingSteps/CreateWorkspace/index.tsx | 9 +- .../Devfile/__tests__/buildStepName.spec.tsx | 31 +- .../Fetch/Devfile/__tests__/index.spec.tsx | 22 +- .../Fetch/Devfile/buildStepName.ts | 11 +- .../CreatingSteps/Fetch/Devfile/index.tsx | 18 +- .../Fetch/Resources/__tests__/index.spec.tsx | 1 - .../CreatingSteps/Fetch/Resources/index.tsx | 9 +- .../Initialize/__tests__/index.spec.tsx | 1 - .../CreatingSteps/Initialize/index.tsx | 9 +- .../WorkspaceProgress/ProgressStep.tsx | 1 - .../Initialize/__tests__/index.spec.tsx | 1 - .../StartingSteps/Initialize/index.tsx | 9 +- .../OpenWorkspace/__tests__/index.spec.tsx | 1 - .../StartingSteps/OpenWorkspace/index.tsx | 9 +- .../StartWorkspace/__tests__/index.spec.tsx | 1 - .../StartingSteps/StartWorkspace/index.tsx | 14 +- .../__snapshots__/index.spec.tsx.snap | 52 --- .../WorkspaceConditions/__tests__/fixtures.ts | 107 ------ .../__tests__/index.spec.tsx | 328 +++++++++++++----- .../__tests__/utils.spec.ts | 56 --- .../WorkspaceConditions/index.tsx | 135 +++++-- .../StepTitle/Icon/index.module.css | 8 +- .../__snapshots__/index.spec.tsx.snap | 42 +-- .../StepTitle/__tests__/index.spec.tsx | 32 +- .../WorkspaceProgress/StepTitle/index.tsx | 16 +- .../WorkspaceProgress/Wizard/index.module.css | 4 + .../WorkspaceProgress/Wizard/index.tsx | 10 +- .../__tests__/index.spec.tsx | 49 +-- .../WorkspaceProgress/index.module.css | 37 ++ .../components/WorkspaceProgress/index.tsx | 201 ++++------- .../WorkspaceProgress/scoreConditions.ts | 32 ++ .../src/components/WorkspaceProgress/utils.ts | 70 ---- 42 files changed, 548 insertions(+), 828 deletions(-) delete mode 100644 packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/__tests__/__snapshots__/index.spec.tsx.snap delete mode 100644 packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/__tests__/fixtures.ts delete mode 100644 packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/__tests__/utils.spec.ts create mode 100644 packages/dashboard-frontend/src/components/WorkspaceProgress/index.module.css create mode 100644 packages/dashboard-frontend/src/components/WorkspaceProgress/scoreConditions.ts delete mode 100644 packages/dashboard-frontend/src/components/WorkspaceProgress/utils.ts diff --git a/packages/dashboard-frontend/jest.config.js b/packages/dashboard-frontend/jest.config.js index 9d8a4b428..bf9170ef0 100644 --- a/packages/dashboard-frontend/jest.config.js +++ b/packages/dashboard-frontend/jest.config.js @@ -25,9 +25,6 @@ module.exports = { 'vscode-languageserver-protocol/lib/common/utils/is', 'vscode-languageserver-protocol/lib/main': 'vscode-languageserver-protocol/lib/node/main', }, - modulePathIgnorePatterns: [ - '__mocks__/index.tsx', - ], globals: { 'ts-jest': { tsconfig: 'tsconfig.test.json', diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/CommonSteps/CheckRunningWorkspacesLimit/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/CommonSteps/CheckRunningWorkspacesLimit/__tests__/index.spec.tsx index b453763fe..d0c8f6804 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/CommonSteps/CheckRunningWorkspacesLimit/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/CommonSteps/CheckRunningWorkspacesLimit/__tests__/index.spec.tsx @@ -446,7 +446,6 @@ function getComponent(store: Store, localState?: Partial): React.ReactEle const component = ( { } protected handleTimeout(redundantWorkspace: Workspace | undefined): void { + // eslint-disable-next-line no-debugger + debugger; const message = redundantWorkspace ? `The workspace status remains "${redundantWorkspace.status}" in the last ${TIMEOUT_TO_STOP_SEC} seconds.` : `Could not check running workspaces limit in the last ${TIMEOUT_TO_STOP_SEC} seconds.`; @@ -308,7 +310,7 @@ class CommonStepCheckRunningWorkspacesLimit extends ProgressStep { } render(): React.ReactNode { - const { distance, hasChildren } = this.props; + const { distance } = this.props; const { name, lastError } = this.state; const redundantWorkspace = this.findRedundantWorkspace(this.props, this.state); @@ -325,12 +327,7 @@ class CommonStepCheckRunningWorkspacesLimit extends ProgressStep { onTimeout={() => this.handleTimeout(redundantWorkspace)} /> )} - + {name} diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/__tests__/index.spec.tsx index 0352fba21..8cd17bdea 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/__tests__/index.spec.tsx @@ -676,7 +676,6 @@ function getComponent( const component = ( { - protected readonly name = 'Generating a DevWorkspace from the Devfile'; + protected readonly name = 'Applying devfile'; constructor(props: Props) { super(props); @@ -391,7 +391,7 @@ class CreatingStepApplyDevfile extends ProgressStep { } render(): React.ReactElement { - const { distance, hasChildren } = this.props; + const { distance } = this.props; const { name, lastError, warning } = this.state; const isActive = distance === 0; @@ -403,12 +403,7 @@ class CreatingStepApplyDevfile extends ProgressStep { {isActive && ( this.handleTimeout()} /> )} - + {name} diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Resources/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Resources/__tests__/index.spec.tsx index 1d03dd042..4c60584fc 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Resources/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Resources/__tests__/index.spec.tsx @@ -377,7 +377,6 @@ function getComponent(store: Store, searchParams: URLSearchParams): React.ReactE const component = ( { } render(): React.ReactElement { - const { distance, hasChildren } = this.props; + const { distance } = this.props; const { name, lastError, warning } = this.state; const isActive = distance === 0; @@ -266,12 +266,7 @@ class CreatingStepApplyResources extends ProgressStep { {isActive && ( this.handleTimeout()} /> )} - + {name} diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/CheckExistingWorkspaces/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/CheckExistingWorkspaces/__tests__/index.spec.tsx index 3bc7e86b9..e5ef03105 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/CheckExistingWorkspaces/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/CheckExistingWorkspaces/__tests__/index.spec.tsx @@ -321,7 +321,6 @@ function getComponent(store: Store, searchParams: URLSearchParams): React.ReactE const component = ( { - protected readonly name = 'Checking if a workspace with the same name exists'; + protected readonly name = 'Checking existing workspaces'; constructor(props: Props) { super(props); @@ -204,7 +204,7 @@ class CreatingStepCheckExistingWorkspaces extends ProgressStep { } render(): React.ReactElement { - const { distance, hasChildren } = this.props; + const { distance } = this.props; const { name, lastError } = this.state; const isError = lastError !== undefined; @@ -212,12 +212,7 @@ class CreatingStepCheckExistingWorkspaces extends ProgressStep { return ( - + {name} diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/CreateWorkspace/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/CreateWorkspace/__tests__/index.spec.tsx index bbb7dda28..b4c55afca 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/CreateWorkspace/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/CreateWorkspace/__tests__/index.spec.tsx @@ -60,7 +60,6 @@ function getComponent(store: Store, searchParams: URLSearchParams): React.ReactE - + {name} diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Fetch/Devfile/__tests__/buildStepName.spec.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Fetch/Devfile/__tests__/buildStepName.spec.tsx index f714e8e58..560f26047 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Fetch/Devfile/__tests__/buildStepName.spec.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Fetch/Devfile/__tests__/buildStepName.spec.tsx @@ -42,12 +42,7 @@ describe('Factory flow: step Fetch Devfile', () => { }, converted: { isConverted: false, - devfileV2: { - metadata: { - name: 'my-project', - generateName: 'my-project-', - }, - } as devfileApi.Devfile, + devfileV2: {} as devfileApi.Devfile, }, }) .build(); @@ -59,7 +54,7 @@ describe('Factory flow: step Fetch Devfile', () => { store.getState().factoryResolver.converted!, ); - expect(newTitle).toEqual('Devfile found with name "my-project".'); + expect(newTitle).toEqual(`Devfile loaded from ${factoryUrl}.`); }); test('devfile not found', async () => { @@ -72,12 +67,7 @@ describe('Factory flow: step Fetch Devfile', () => { }, converted: { isConverted: false, - devfileV2: { - metadata: { - name: 'my-project', - generateName: 'my-project-', - }, - } as devfileApi.Devfile, + devfileV2: {} as devfileApi.Devfile, }, }) .build(); @@ -104,12 +94,7 @@ describe('Factory flow: step Fetch Devfile', () => { }, converted: { isConverted: false, - devfileV2: { - metadata: { - name: 'my-project', - generateName: 'my-project-', - }, - } as devfileApi.Devfile, + devfileV2: {} as devfileApi.Devfile, }, }) .build(); @@ -121,7 +106,7 @@ describe('Factory flow: step Fetch Devfile', () => { store.getState().factoryResolver.converted!, ); - expect(newTitle).toEqual('Devfile found with name "my-project".'); + expect(newTitle).toEqual(`Devfile found in repo ${factoryUrl} as 'devfile.yaml'.`); }); test('devfile converted', async () => { @@ -136,10 +121,6 @@ describe('Factory flow: step Fetch Devfile', () => { isConverted: true, // <- devfileV2: { schemaVersion: '2.1.0', - metadata: { - name: 'my-project', - generateName: 'my-project-', - }, } as devfileApi.Devfile, }, }) @@ -153,7 +134,7 @@ describe('Factory flow: step Fetch Devfile', () => { ); expect(newTitle).toEqual( - 'Devfile found with name "my-project". Devfile version 1 found, converting it to devfile version 2.', + `Devfile found in repo ${factoryUrl} as 'devfile.yaml'. Devfile version 1 found, converting it to devfile version 2.`, ); }); }); diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Fetch/Devfile/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Fetch/Devfile/__tests__/index.spec.tsx index 00308a881..4e69d3265 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Fetch/Devfile/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Fetch/Devfile/__tests__/index.spec.tsx @@ -73,12 +73,7 @@ describe('Creating steps, fetching a devfile', () => { location: factoryUrl, }, converted: { - devfileV2: { - metadata: { - name: 'my-project', - generateName: 'my-project-', - }, - } as devfileApi.Devfile, + devfileV2: {} as devfileApi.Devfile, }, }) .build(); @@ -492,12 +487,7 @@ describe('Creating steps, fetching a devfile', () => { location: factoryUrl, }, converted: { - devfileV2: { - metadata: { - name: 'my-project', - generateName: 'my-project-', - }, - } as devfileApi.Devfile, + devfileV2: {} as devfileApi.Devfile, }, }) .build(); @@ -630,12 +620,7 @@ describe('Creating steps, fetching a devfile', () => { location: factoryUrl, }, converted: { - devfileV2: { - metadata: { - name: 'my-project', - generateName: 'my-project-', - }, - } as devfileApi.Devfile, + devfileV2: {} as devfileApi.Devfile, }, }) .build(); @@ -670,7 +655,6 @@ function getComponent(store: Store, searchParams: URLSearchParams): React.ReactE { - protected readonly name = 'Inspecting repo'; + protected readonly name = 'Looking for devfile'; constructor(props: Props) { super(props); - const factoryParams = buildFactoryParams(props.searchParams); - const name = `Inspecting repo ${factoryParams.sourceUrl} for a devfile`; - this.state = { - factoryParams, + factoryParams: buildFactoryParams(props.searchParams), shouldResolve: true, useDefaultDevfile: false, - name, + name: this.name, }; } @@ -377,7 +374,7 @@ class CreatingStepFetchDevfile extends ProgressStep { } render(): React.ReactElement { - const { distance, hasChildren } = this.props; + const { distance } = this.props; const { name, lastError } = this.state; const isActive = distance === 0; @@ -389,12 +386,7 @@ class CreatingStepFetchDevfile extends ProgressStep { {isActive && ( this.handleTimeout()} /> )} - + {name} diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Fetch/Resources/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Fetch/Resources/__tests__/index.spec.tsx index f22f1df2a..b74ba5f3d 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Fetch/Resources/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Fetch/Resources/__tests__/index.spec.tsx @@ -252,7 +252,6 @@ function getComponent(store: Store, searchParams: URLSearchParams): React.ReactE { } render(): React.ReactElement { - const { distance, hasChildren } = this.props; + const { distance } = this.props; const { name, lastError } = this.state; const isActive = distance === 0; @@ -179,12 +179,7 @@ class CreatingStepFetchResources extends ProgressStep { {isActive && ( this.handleTimeout()} /> )} - + {name} diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Initialize/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Initialize/__tests__/index.spec.tsx index c00518ba8..e96cd8018 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Initialize/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Initialize/__tests__/index.spec.tsx @@ -244,7 +244,6 @@ function getComponent(store: Store, searchParams: URLSearchParams): React.ReactE { } render(): React.ReactElement { - const { distance, hasChildren } = this.props; + const { distance } = this.props; const { name, lastError } = this.state; const isError = lastError !== undefined; @@ -171,12 +171,7 @@ class CreatingStepInitialize extends ProgressStep { return ( - + {name} diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/ProgressStep.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/ProgressStep.tsx index 6eb6feb13..7079d2ffb 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/ProgressStep.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/ProgressStep.tsx @@ -20,7 +20,6 @@ import { MIN_STEP_DURATION_MS } from './const'; export type ProgressStepProps = { distance: -1 | 0 | 1 | undefined; - hasChildren: boolean; history: History; onError: (alertItem: AlertItem) => void; onHideError: (key: string) => void; diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/Initialize/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/Initialize/__tests__/index.spec.tsx index 155af390d..01a54cb6e 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/Initialize/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/Initialize/__tests__/index.spec.tsx @@ -466,7 +466,6 @@ function getComponent( { } render(): React.ReactNode { - const { distance, hasChildren } = this.props; + const { distance } = this.props; const { name, lastError } = this.state; const workspace = this.findTargetWorkspace(this.props); @@ -196,12 +196,7 @@ class StartingStepInitialize extends ProgressStep { onTimeout={() => this.handleTimeout(workspace)} /> )} - + {name} diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/OpenWorkspace/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/OpenWorkspace/__tests__/index.spec.tsx index 7b1e4b50d..ff9427f45 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/OpenWorkspace/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/OpenWorkspace/__tests__/index.spec.tsx @@ -420,7 +420,6 @@ function getComponent( { } render(): React.ReactNode { - const { distance, hasChildren } = this.props; + const { distance } = this.props; const { name, lastError } = this.state; const isActive = distance === 0; @@ -181,12 +181,7 @@ class StartingStepOpenWorkspace extends ProgressStep { {isActive && ( this.handleTimeout()} /> )} - + {name} diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/StartWorkspace/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/StartWorkspace/__tests__/index.spec.tsx index c4f07e3d9..cef3ac865 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/StartWorkspace/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/StartWorkspace/__tests__/index.spec.tsx @@ -808,7 +808,6 @@ function getComponent( { return true; } - // show/hide spinner near the step title - if (this.props.hasChildren !== nextProps.hasChildren) { - return true; - } - const workspace = this.findTargetWorkspace(this.props); const nextWorkspace = this.findTargetWorkspace(nextProps); @@ -200,7 +195,7 @@ class StartingStepStartWorkspace extends ProgressStep { } render(): React.ReactNode { - const { distance, hasChildren, startTimeout } = this.props; + const { distance, startTimeout } = this.props; const { name, lastError } = this.state; const isActive = distance === 0; @@ -214,12 +209,7 @@ class StartingStepStartWorkspace extends ProgressStep { {isActive && ( this.handleTimeout(workspace)} /> )} - + {name} diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/__tests__/__snapshots__/index.spec.tsx.snap b/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/__tests__/__snapshots__/index.spec.tsx.snap deleted file mode 100644 index 4ccd6f5d2..000000000 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/__tests__/__snapshots__/index.spec.tsx.snap +++ /dev/null @@ -1,52 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Starting steps, checking workspace conditions snapshot - condition failed 1`] = ` -
-
-
- 1 -
- - Something happened - -
-`; - -exports[`Starting steps, checking workspace conditions snapshot - condition in-progress 1`] = ` -
-
- 0 -
- - Preparing networking - -
-`; - -exports[`Starting steps, checking workspace conditions snapshot - condition ready 1`] = ` -
-
- 1 -
- - Networking ready - -
-`; diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/__tests__/fixtures.ts b/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/__tests__/fixtures.ts deleted file mode 100644 index 68cfccaaf..000000000 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/__tests__/fixtures.ts +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 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 { ConditionType } from '../../../utils'; - -export const conditionChangedTo: { - [key: string]: [condition: ConditionType, prevCondition: ConditionType | undefined]; -} = { - inProgress1: [ - { - status: 'False', - type: 'Started', - }, - undefined, - ], - inProgress2: [ - { - status: 'False', - type: 'Started', - }, - { - status: 'False', - type: 'Started', - }, - ], - - done1: [ - { - status: 'True', - type: 'Started', - }, - { - status: 'False', - type: 'Started', - }, - ], - done2: [ - { - status: 'Unknown', - type: 'Started', - }, - { - status: 'True', - type: 'Started', - }, - ], - - fail1: [ - { - status: 'Unknown', - type: 'Started', - }, - { - status: 'False', - type: 'Started', - }, - ], - fail2: [ - { - status: 'False', - type: 'Started', - message: 'Workspace stopped due to error', - }, - { - status: 'Unknown', - type: 'Started', - }, - ], - fail3: [ - { - status: 'True', - type: 'Started', - reason: 'Failed', - }, - undefined, - ], -}; - -export const conditionStatusFalse: ConditionType = { - message: 'Preparing networking', - status: 'False', - type: 'RoutingReady', -}; -export const conditionStatusTrue: ConditionType = { - message: 'Networking ready', - status: 'True', - type: 'RoutingReady', -}; -export const conditionError: ConditionType = { - status: 'True', - type: 'FailedStart', - reason: 'Failure', - message: 'Something happened', -}; -export const conditionStatusUnknown: ConditionType = { - status: 'Unknown', - type: 'RoutingReady', -}; diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/__tests__/index.spec.tsx index 0a7b9c990..2c6aa0d80 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/__tests__/index.spec.tsx @@ -15,16 +15,14 @@ import { screen, waitFor } from '@testing-library/react'; import { createMemoryHistory } from 'history'; import React from 'react'; -import StartingStepWorkspaceConditions from '..'; +import { Provider } from 'react-redux'; +import { Store } from 'redux'; +import StartingStepWorkspaceConditions, { ConditionType } from '..'; import { WorkspaceParams } from '../../../../../Routes/routes'; import getComponentRenderer from '../../../../../services/__mocks__/getComponentRenderer'; -import { ConditionType } from '../../../utils'; -import { - conditionChangedTo, - conditionError, - conditionStatusFalse, - conditionStatusTrue, -} from './fixtures'; +import { DevWorkspaceBuilder } from '../../../../../store/__mocks__/devWorkspaceBuilder'; +import { FakeStoreBuilder } from '../../../../../store/__mocks__/storeBuilder'; +import { MIN_STEP_DURATION_MS } from '../../../const'; jest.mock('../../../TimeLimit'); jest.mock('../../../StepTitle'); @@ -34,7 +32,7 @@ const mockOnRestart = jest.fn(); const mockOnError = jest.fn(); const mockOnHideError = jest.fn(); -const { renderComponent, createSnapshot } = getComponentRenderer(getComponent); +const { renderComponent } = getComponentRenderer(getComponent); const namespace = 'che-user'; const workspaceName = 'test-workspace'; @@ -42,9 +40,26 @@ const matchParams: WorkspaceParams = { namespace, workspaceName, }; +const startTimeout = 500; describe('Starting steps, checking workspace conditions', () => { + const conditionInProgress: ConditionType = { + message: 'Preparing networking', + status: 'False', + type: 'RoutingReady', + }; + const conditionReady: ConditionType = { + message: 'Networking ready', + status: 'True', + type: 'RoutingReady', + }; + const conditionFailed: ConditionType = { + status: 'Unknown', + type: 'RoutingReady', + }; + beforeEach(() => { + getStoreBuilder(); jest.useFakeTimers(); }); @@ -54,111 +69,241 @@ describe('Starting steps, checking workspace conditions', () => { jest.useRealTimers(); }); - test('snapshot - condition in-progress', () => { - const snapshot = createSnapshot(conditionStatusFalse); - expect(snapshot.toJSON()).toMatchSnapshot(); - }); - - test('snapshot - condition ready', () => { - const snapshot = createSnapshot(conditionStatusTrue); - expect(snapshot.toJSON()).toMatchSnapshot(); - }); - - test('snapshot - condition failed', () => { - const snapshot = createSnapshot(conditionError); - expect(snapshot.toJSON()).toMatchSnapshot(); - }); - - test('in progress 1', async () => { - const [condition] = conditionChangedTo.inProgress1; - renderComponent(condition); - - expect(screen.queryByTestId('isError')).toBeFalsy(); - expect(screen.queryByTestId('distance')?.textContent).toEqual('0'); + test('condition not found', async () => { + const store = getStoreBuilder() + .withDevWorkspaces({ + workspaces: [ + new DevWorkspaceBuilder() + .withName(workspaceName) + .withNamespace(namespace) + .withStatus({ phase: 'STARTING' }) + .build(), + ], + }) + .build(); + + renderComponent(store, conditionInProgress); + + jest.runAllTimers(); + + // nothing should happen + expect(mockOnError).not.toHaveBeenCalled(); + expect(mockOnNextStep).not.toHaveBeenCalled(); + expect(mockOnRestart).not.toHaveBeenCalled(); }); - test('in progress 2', async () => { - const [condition, prevCondition] = conditionChangedTo.inProgress2; - const { reRenderComponent } = renderComponent(prevCondition!); + test('condition is ready initially', async () => { + const devworkspace = new DevWorkspaceBuilder() + .withName(workspaceName) + .withNamespace(namespace) + .withStatus({ phase: 'STARTING' }) + .build(); + devworkspace.status!.conditions = [conditionReady]; + const store = getStoreBuilder() + .withDevWorkspaces({ + workspaces: [devworkspace], + }) + .build(); - expect(screen.queryByTestId('isError')).toBeFalsy(); - expect(screen.queryByTestId('distance')?.textContent).toEqual('0'); + renderComponent(store, conditionReady); - reRenderComponent(condition); + jest.advanceTimersByTime(MIN_STEP_DURATION_MS); - expect(screen.queryByTestId('isError')).toBeFalsy(); - expect(screen.queryByTestId('distance')?.textContent).toEqual('0'); - }); + await waitFor(() => expect(mockOnNextStep).toHaveBeenCalled()); - test('done 1', async () => { - const [condition, prevCondition] = conditionChangedTo.done1; - const { reRenderComponent } = renderComponent(prevCondition!); - - expect(screen.queryByTestId('isError')).toBeFalsy(); - expect(screen.queryByTestId('distance')?.textContent).toEqual('0'); - - reRenderComponent(condition); - - expect(screen.queryByTestId('isError')).toBeFalsy(); - expect(screen.queryByTestId('distance')?.textContent).toEqual('1'); + expect(mockOnError).not.toHaveBeenCalled(); + expect(mockOnRestart).not.toHaveBeenCalled(); }); - test('done 2', async () => { - const [condition, prevCondition] = conditionChangedTo.done2; - const { reRenderComponent } = renderComponent(prevCondition!); - - expect(screen.queryByTestId('isError')).toBeFalsy(); - // expect(screen.queryByTestId('distance')?.textContent).toEqual('0'); - - reRenderComponent(condition); - - expect(screen.queryByTestId('isError')).toBeFalsy(); - expect(screen.queryByTestId('distance')?.textContent).toEqual('1'); + test('condition is ready later', async () => { + const devworkspace = new DevWorkspaceBuilder() + .withName(workspaceName) + .withNamespace(namespace) + .withStatus({ phase: 'STARTING' }) + .build(); + devworkspace.status!.conditions = [conditionInProgress]; + const store = getStoreBuilder() + .withDevWorkspaces({ + workspaces: [devworkspace], + }) + .build(); + + const { reRenderComponent } = renderComponent(store, conditionInProgress); + + jest.advanceTimersByTime(MIN_STEP_DURATION_MS); + // need to flush promises + await Promise.resolve(); + + // nothing should happen + expect(mockOnError).not.toHaveBeenCalled(); + expect(mockOnNextStep).not.toHaveBeenCalled(); + expect(mockOnRestart).not.toHaveBeenCalled(); + + const nextDevworkspace = new DevWorkspaceBuilder() + .withName(workspaceName) + .withNamespace(namespace) + .withStatus({ phase: 'STARTING' }) + .build(); + nextDevworkspace.status!.conditions = [conditionReady]; + const nextStore = getStoreBuilder() + .withDwServerConfig({ + timeouts: { + inactivityTimeout: -1, + runTimeout: -1, + startTimeout, + }, + }) + .withDevWorkspaces({ + workspaces: [nextDevworkspace], + }) + .build(); + reRenderComponent(nextStore, conditionInProgress); + + // jest.advanceTimersByTime(MIN_STEP_DURATION_MS); + jest.runAllTimers(); + + await waitFor(() => expect(mockOnNextStep).toHaveBeenCalled()); + expect(mockOnError).not.toHaveBeenCalled(); + expect(mockOnRestart).not.toHaveBeenCalled(); }); - test('fail 1', async () => { - const [condition, prevCondition] = conditionChangedTo.fail1; - const { reRenderComponent } = renderComponent(prevCondition!); - - expect(screen.queryByTestId('isError')).toBeFalsy(); - expect(screen.queryByTestId('distance')?.textContent).toEqual('0'); - - reRenderComponent(condition); - + test('condition is failed later', async () => { + const devworkspace = new DevWorkspaceBuilder() + .withUID('wksp-123') + .withName(workspaceName) + .withNamespace(namespace) + .withStatus({ phase: 'STARTING' }) + .build(); + devworkspace.status!.conditions = [conditionInProgress]; + const store = getStoreBuilder() + .withDevWorkspaces({ + workspaces: [devworkspace], + }) + .build(); + + const { reRenderComponent } = renderComponent(store, conditionInProgress); + + jest.advanceTimersByTime(MIN_STEP_DURATION_MS); + // need to flush promises + await Promise.resolve(); + + // nothing should happen + expect(mockOnError).not.toHaveBeenCalled(); + expect(mockOnNextStep).not.toHaveBeenCalled(); + expect(mockOnRestart).not.toHaveBeenCalled(); + + const nextDevworkspace = new DevWorkspaceBuilder() + .withUID('wksp-123') + .withName(workspaceName) + .withNamespace(namespace) + .withStatus({ phase: 'STARTING' }) + .build(); + nextDevworkspace.status!.conditions = [conditionFailed]; + const nextStore = getStoreBuilder() + .withDwServerConfig({ + timeouts: { + inactivityTimeout: -1, + runTimeout: -1, + startTimeout, + }, + }) + .withDevWorkspaces({ + workspaces: [nextDevworkspace], + }) + .build(); + reRenderComponent(nextStore, conditionInProgress); + + jest.runAllTimers(); + + /* step marked as failed */ await waitFor(() => expect(screen.queryByTestId('isError')).toBeTruthy()); - expect(screen.queryByTestId('distance')?.textContent).toEqual('1'); - }); - test('fail 2', async () => { - const [condition, prevCondition] = conditionChangedTo.fail2; - const { reRenderComponent } = renderComponent(prevCondition!); + /* no alerts should fire */ + expect(mockOnError).not.toHaveBeenCalled(); + expect(mockOnNextStep).not.toHaveBeenCalled(); + expect(mockOnRestart).not.toHaveBeenCalled(); + }); + test('workspace fails after condition is ready', async () => { + const devworkspace = new DevWorkspaceBuilder() + .withUID('wksp-123') + .withName(workspaceName) + .withNamespace(namespace) + .withStatus({ phase: 'STARTING' }) + .build(); + devworkspace.status!.conditions = [conditionReady]; + const store = getStoreBuilder() + .withDevWorkspaces({ + workspaces: [devworkspace], + }) + .build(); + + const { reRenderComponent } = renderComponent(store, conditionInProgress); + + jest.advanceTimersByTime(MIN_STEP_DURATION_MS); + // need to flush promises + await Promise.resolve(); + + // nothing should happen + expect(mockOnError).not.toHaveBeenCalled(); + expect(mockOnNextStep).not.toHaveBeenCalled(); + expect(mockOnRestart).not.toHaveBeenCalled(); + + const nextDevworkspace = new DevWorkspaceBuilder() + .withUID('wksp-123') + .withName(workspaceName) + .withNamespace(namespace) + .withStatus({ phase: 'STARTING' }) + .build(); + nextDevworkspace.status!.conditions = [conditionFailed]; + const nextStore = getStoreBuilder() + .withDwServerConfig({ + timeouts: { + inactivityTimeout: -1, + runTimeout: -1, + startTimeout, + }, + }) + .withDevWorkspaces({ + workspaces: [nextDevworkspace], + }) + .build(); + reRenderComponent(nextStore, conditionInProgress); + + jest.runAllTimers(); + // try to flush promises + await Promise.resolve(); + + /* step marked as failed */ expect(screen.queryByTestId('isError')).toBeFalsy(); - expect(screen.queryByTestId('distance')?.textContent).toEqual('0'); - - reRenderComponent(condition); - await waitFor(() => expect(screen.queryByTestId('isError')).toBeTruthy()); - expect(screen.queryByTestId('distance')?.textContent).toEqual('1'); + /* no alerts should fire */ + expect(mockOnError).not.toHaveBeenCalled(); + expect(mockOnNextStep).not.toHaveBeenCalled(); + expect(mockOnRestart).not.toHaveBeenCalled(); }); +}); - test('fail 3', async () => { - const [condition] = conditionChangedTo.fail3; - - renderComponent(condition); - - await waitFor(() => expect(screen.queryByTestId('isError')).toBeTruthy()); - expect(screen.queryByTestId('distance')?.textContent).toEqual('1'); +function getStoreBuilder() { + return new FakeStoreBuilder().withDwServerConfig({ + timeouts: { + inactivityTimeout: -1, + runTimeout: -1, + startTimeout, + }, }); -}); +} -function getComponent(condition: ConditionType, _matchParams = matchParams): React.ReactElement { +function getComponent( + store: Store, + condition: ConditionType, + _matchParams = matchParams, +): React.ReactElement { const history = createMemoryHistory(); - return ( + const component = ( ); + return {component}; } diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/__tests__/utils.spec.ts b/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/__tests__/utils.spec.ts deleted file mode 100644 index 9e1ff65c8..000000000 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/__tests__/utils.spec.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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 { isConditionReady, isConditionError } from '../../../utils'; -import { - conditionChangedTo, - conditionError, - conditionStatusFalse, - conditionStatusTrue, - conditionStatusUnknown, -} from './fixtures'; - -describe('utils', () => { - test('isReady', () => { - expect(isConditionReady(...conditionChangedTo.inProgress1)).toEqual(false); - expect(isConditionReady(...conditionChangedTo.inProgress2)).toEqual(false); - - expect(isConditionReady(...conditionChangedTo.done1)).toEqual(true); - expect(isConditionReady(...conditionChangedTo.done2)).toEqual(true); - - expect(isConditionReady(...conditionChangedTo.fail1)).toEqual(true); - expect(isConditionReady(...conditionChangedTo.fail2)).toEqual(true); - expect(isConditionReady(...conditionChangedTo.fail3)).toEqual(true); - - expect(isConditionReady(conditionStatusFalse, undefined)).toEqual(false); - expect(isConditionReady(conditionStatusTrue, undefined)).toEqual(true); - expect(isConditionReady(conditionStatusUnknown, undefined)).toEqual(false); - expect(isConditionReady(conditionError, undefined)).toEqual(true); - }); - - test('isError', () => { - expect(isConditionError(...conditionChangedTo.inProgress1)).toEqual(false); - expect(isConditionError(...conditionChangedTo.inProgress2)).toEqual(false); - - expect(isConditionError(...conditionChangedTo.done1)).toEqual(false); - expect(isConditionError(...conditionChangedTo.done2)).toEqual(false); - - expect(isConditionError(...conditionChangedTo.fail1)).toEqual(true); - expect(isConditionError(...conditionChangedTo.fail2)).toEqual(true); - expect(isConditionError(...conditionChangedTo.fail3)).toEqual(true); - - expect(isConditionError(conditionStatusFalse, undefined)).toEqual(false); - expect(isConditionError(conditionStatusTrue, undefined)).toEqual(false); - expect(isConditionError(conditionStatusUnknown, undefined)).toEqual(false); - expect(isConditionError(conditionError, undefined)).toEqual(true); - }); -}); diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/index.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/index.tsx index f84816c07..e2b6cce48 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/index.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/index.tsx @@ -10,74 +10,106 @@ * Red Hat, Inc. - initial API and implementation */ +import { V1alpha2DevWorkspaceStatusConditions } from '@devfile/api'; import common from '@eclipse-che/common'; import { AlertVariant } from '@patternfly/react-core'; -import isEqual from 'lodash/isEqual'; import React from 'react'; +import { connect, ConnectedProps } from 'react-redux'; import { WorkspaceParams } from '../../../../Routes/routes'; +import { delay } from '../../../../services/helpers/delay'; +import { findTargetWorkspace } from '../../../../services/helpers/factoryFlow/findTargetWorkspace'; import { AlertItem, LoaderTab } from '../../../../services/helpers/types'; +import { Workspace } from '../../../../services/workspace-adapter'; +import { AppState } from '../../../../store'; +import { selectStartTimeout } from '../../../../store/ServerConfig/selectors'; +import * as WorkspaceStore from '../../../../store/Workspaces'; +import { selectAllWorkspaces } from '../../../../store/Workspaces/selectors'; +import { MIN_STEP_DURATION_MS } from '../../const'; import { ProgressStep, ProgressStepProps, ProgressStepState } from '../../ProgressStep'; import { ProgressStepTitle } from '../../StepTitle'; -import { ConditionType, isConditionError, isConditionReady } from '../../utils'; - import styles from './index.module.css'; -export type Props = ProgressStepProps & { - condition: ConditionType; - matchParams: WorkspaceParams; -}; +export type ConditionType = V1alpha2DevWorkspaceStatusConditions & + ( + | { + message: string; + status: 'True' | 'False'; + } + | { + status: 'Unknown'; + } + ); + +export type Props = MappedProps & + ProgressStepProps & { + matchParams: WorkspaceParams; + condition: ConditionType; + }; export type State = ProgressStepState & { - isError: boolean; + isFailed: boolean; isReady: boolean; - condition: ConditionType; }; -export default class StartingStepWorkspaceConditions extends ProgressStep { +export class StartingStepWorkspaceConditions extends ProgressStep { constructor(props: Props) { super(props); - this.state = this.buildState(props, undefined, undefined); + const { condition } = this.props; + + this.state = { + isReady: false, + isFailed: false, + name: condition.message || condition.type, + }; } protected get name(): string { return this.state.name; } - private buildState(props: Props, prevProps: Props | undefined, state: State | undefined): State { - const condition = props.condition; - const prevCondition = prevProps?.condition; + private init() { + const workspace = this.findTargetWorkspace(this.props); + const condition = this.findTargetCondition(workspace); - let name: string; - if (state === undefined) { - name = condition.message || condition.type; - } else { - name = condition.message || state.name; + const isReady = this.state.isReady === true || condition?.status === 'True'; + const isFailed = + isReady === false && (condition === undefined || condition.status === 'Unknown'); + + if (isReady !== this.state.isReady || isFailed !== this.state.isFailed) { + this.setState({ + isReady, + isFailed, + }); } - return { - isReady: isConditionReady(condition, prevCondition), - isError: isConditionError(condition, prevCondition), - name, - condition, - }; + this.prepareAndRun(); } public componentDidMount() { - const state = this.buildState(this.props, undefined, this.state); - this.setState(state); + this.init(); } - public async componentDidUpdate(prevProps: Props) { - const state = this.buildState(this.props, prevProps, this.state); - this.setState(state); + public async componentDidUpdate() { + this.init(); } public shouldComponentUpdate(nextProps: Props, nextState: State): boolean { - if (isEqual(this.props.condition, nextProps.condition) === false) { + const workspace = this.findTargetWorkspace(this.props); + const nextWorkspace = this.findTargetWorkspace(nextProps); + + // change workspace status, etc. + if (workspace?.uid !== nextWorkspace?.uid || workspace?.status !== nextWorkspace?.status) { + return true; + } + + const condition = this.findTargetCondition(workspace); + const nextCondition = this.findTargetCondition(nextWorkspace); + + if (nextCondition !== undefined && nextCondition.status !== condition?.status) { return true; } - if (isEqual(this.state.condition, nextState.condition) === false) { + if (this.state.isReady !== nextState.isReady || this.state.isFailed !== nextState.isFailed) { return true; } @@ -87,7 +119,29 @@ export default class StartingStepWorkspaceConditions extends ProgressStep condition.type === this.props.condition.type, + ); + return condition ? (condition as ConditionType) : undefined; + } + protected async runStep(): Promise { + await delay(MIN_STEP_DURATION_MS); + + if (this.state.isReady) { + return true; + } + return false; } @@ -119,10 +173,10 @@ export default class StartingStepWorkspaceConditions extends ProgressStep @@ -140,3 +193,15 @@ export default class StartingStepWorkspaceConditions extends ProgressStep ({ + allWorkspaces: selectAllWorkspaces(state), + startTimeout: selectStartTimeout(state), +}); + +const connector = connect(mapStateToProps, WorkspaceStore.actionCreators, null, { + // forwardRef is mandatory for using `@react-mock/state` in unit tests + forwardRef: true, +}); +type MappedProps = ConnectedProps; +export default connector(StartingStepWorkspaceConditions); diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/StepTitle/Icon/index.module.css b/packages/dashboard-frontend/src/components/WorkspaceProgress/StepTitle/Icon/index.module.css index af1597565..861768a4c 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/StepTitle/Icon/index.module.css +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/StepTitle/Icon/index.module.css @@ -10,14 +10,16 @@ * Red Hat, Inc. - initial API and implementation */ -.pf-c-wizard__nav-item .stepIcon { +.pf-c-wizard__nav-link .stepIcon { position: absolute; top: 3px; left: -56px; } -.pf-c-wizard__nav-item .pf-c-wizard__nav-item .stepIcon { - left: -23px; +.pf-c-wizard__toggle .pf-c-wizard__toggle-list-item .stepIcon { + position: absolute; + top: 3px; + left: -56px; } .errorIcon { diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/StepTitle/__tests__/__snapshots__/index.spec.tsx.snap b/packages/dashboard-frontend/src/components/WorkspaceProgress/StepTitle/__tests__/__snapshots__/index.spec.tsx.snap index 066e912c3..7fb191a55 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/StepTitle/__tests__/__snapshots__/index.spec.tsx.snap +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/StepTitle/__tests__/__snapshots__/index.spec.tsx.snap @@ -54,47 +54,7 @@ Array [ /> , - Step 1 - , -] -`; - -exports[`ProgressStepTitle snapshot - active step has children 1`] = ` - - Step 1 - -`; - -exports[`ProgressStepTitle snapshot - active step warning 1`] = ` -Array [ - - - , - Step 1 diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/StepTitle/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/StepTitle/__tests__/index.spec.tsx index d85b6ea4a..44777d784 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/StepTitle/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/StepTitle/__tests__/index.spec.tsx @@ -18,46 +18,24 @@ const { createSnapshot } = getComponentRenderer(getComponent); describe('ProgressStepTitle', () => { test('snapshot - non-active step', () => { - const snapshot = createSnapshot(-1, {}); + const snapshot = createSnapshot(-1, false, false); expect(snapshot).toMatchSnapshot(); }); test('snapshot - active step', () => { - const snapshot = createSnapshot(0, {}); + const snapshot = createSnapshot(0, false, false); expect(snapshot).toMatchSnapshot(); }); test('snapshot - active step failed', () => { - const snapshot = createSnapshot(0, { isError: true }); - expect(snapshot).toMatchSnapshot(); - }); - - test('snapshot - active step warning', () => { - const snapshot = createSnapshot(0, { isWarning: true }); - expect(snapshot).toMatchSnapshot(); - }); - - test('snapshot - active step has children', () => { - const snapshot = createSnapshot(0, { hasChildren: true }); + const snapshot = createSnapshot(0, true, false); expect(snapshot).toMatchSnapshot(); }); }); -function getComponent( - distance: -1 | 0 | 1 | undefined, - { - hasChildren = false, - isError = false, - isWarning = false, - }: { isError?: boolean; isWarning?: boolean; hasChildren?: boolean }, -) { +function getComponent(distance: -1 | 0 | 1 | undefined, isError: boolean, isWarning: boolean) { return ( - + Step 1 ); diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/StepTitle/index.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/StepTitle/index.tsx index 3510f997c..23c4035e3 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/StepTitle/index.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/StepTitle/index.tsx @@ -13,35 +13,29 @@ import React, { PropsWithChildren } from 'react'; import { ProgressStepTitleIcon } from './Icon'; -import styles from './index.module.css'; - export type Props = PropsWithChildren<{ className?: string; distance: -1 | 0 | 1 | undefined; - hasChildren: boolean; isError: boolean; isWarning: boolean; }>; +import styles from './index.module.css'; + export class ProgressStepTitle extends React.Component { render(): React.ReactElement { - const { children, className, hasChildren, distance, isError, isWarning } = this.props; + const { children, className, distance, isError, isWarning } = this.props; let readiness = styles.ready; if (distance === 0) { - readiness = styles.progress; - } else if (isError) { - readiness = styles.error; + readiness = isError ? styles.error : styles.progress; } const fullClassName = [readiness, className].filter(c => c).join(' '); - // for step with children do not show in-progress spinner - const dist = hasChildren && distance === 0 ? -1 : distance; - return ( <> - + {children} diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/Wizard/index.module.css b/packages/dashboard-frontend/src/components/WorkspaceProgress/Wizard/index.module.css index 7adcf0731..7aca84ceb 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/Wizard/index.module.css +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/Wizard/index.module.css @@ -31,6 +31,10 @@ --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 index 474dec14b..feee8d193 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/Wizard/index.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/Wizard/index.tsx @@ -118,10 +118,6 @@ class WorkspaceProgressWizard extends React.Component { const { canJumpTo, name, steps = [], id } = step; const flattenedStepNumber = this.getFlattenedStepsNumber(flattenedSteps, id); - const hasChildren = steps.length !== 0; - const allChildrenFinished = steps.every(subStep => subStep.isFinishedStep); - const showChildren = hasChildren && allChildrenFinished === false; - const hasActiveChild = steps.some(subStep => subStep.id === activeStepId); return ( @@ -133,9 +129,13 @@ class WorkspaceProgressWizard extends React.Component { isDisabled={!canJumpTo} onNavItemClick={() => this.handleGoToStepById()} > - {showChildren && ( + {steps.length !== 0 && ( {steps.map(subStep => { + if (subStep.isFinishedStep) { + return; + } + const { canJumpTo, name, id } = subStep; const flattenedStepNumber = this.getFlattenedStepsNumber(flattenedSteps, id); return ( diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/__tests__/index.spec.tsx index 9396e3060..6ec2684fb 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/__tests__/index.spec.tsx @@ -285,16 +285,16 @@ describe('LoaderProgress', () => { const factoryParams = buildFactoryParams(searchParams); const localState: Partial = { alertItems: [], - // active step is "open IDE" - activeStepId: Step.OPEN, // <-- not 8th but 5th step, active + // active step is "check running workspaces limit" + activeStepId: Step.OPEN, // <-- 8th step, active doneSteps: [ Step.INITIALIZE, Step.LIMIT_CHECK, Step.CREATE, - Step.FETCH, // <-- hidden - Step.CONFLICT_CHECK, // <-- hidden - Step.APPLY, // <-- hidden - Step.START, // <-- not 7th but 4th step, non-active + Step.FETCH, + Step.CONFLICT_CHECK, + Step.APPLY, + Step.START, // <-- 7th step, non-active ], factoryParams, initialLoaderMode: { @@ -306,23 +306,23 @@ describe('LoaderProgress', () => { const steps = getSteps(); - // 4th step distance (non-active) - expect(within(steps[3]).getByTestId('step-distance').textContent).toEqual('1'); + // 7th step distance (non-active) + expect(within(steps[6]).getByTestId('step-distance').textContent).toEqual('1'); - // 5th step distance (active) - expect(within(steps[4]).getByTestId('step-distance').textContent).toEqual('0'); + // 8th step distance (active) + expect(within(steps[7]).getByTestId('step-distance').textContent).toEqual('0'); // trigger onRestart on the active step - const onRestart = within(steps[4]).getByRole('button', { name: 'onRestart' }); + const onRestart = within(steps[7]).getByRole('button', { name: 'onRestart' }); userEvent.click(onRestart); - // 4th step distance becomes 0 (active) + // 7th step distance becomes 0 (active) await waitFor(() => - expect(within(steps[3]).getByTestId('step-distance').textContent).toEqual('0'), + expect(within(steps[6]).getByTestId('step-distance').textContent).toEqual('0'), ); - // 5th step distance becomes -1 (non-active) - expect(within(steps[4]).getByTestId('step-distance').textContent).toEqual('-1'); + // 8th step distance becomes -1 (non-active) + expect(within(steps[7]).getByTestId('step-distance').textContent).toEqual('-1'); }); test('on non-active step', async () => { @@ -423,29 +423,16 @@ describe('LoaderProgress', () => { expect(within(steps[3]).getByTestId('step-name')).toHaveTextContent('Open workspace'); }); - test('with condition steps', async () => { + test('with condition steps', () => { const store = new FakeStoreBuilder() .withDevWorkspaces({ workspaces: [devWorkspace] }) .build(); - renderComponent(history, store, searchParams, false, { - activeStepId: Step.START, - alertItems: [], - conditions: [], - doneSteps: [Step.INITIALIZE, Step.LIMIT_CHECK], - hasBeenStarted: true, - initialLoaderMode: { - mode: 'workspace', - workspaceParams: { - namespace, - workspaceName, - }, - }, - }); + renderComponent(history, store, searchParams, false); const steps = getSteps(); - await waitFor(() => expect(getSteps().length).toEqual(6)); + expect(steps.length).toEqual(6); expect(within(steps[0]).getByTestId('step-name')).toHaveTextContent('Initialize'); expect(within(steps[1]).getByTestId('step-name')).toHaveTextContent( diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/index.module.css b/packages/dashboard-frontend/src/components/WorkspaceProgress/index.module.css new file mode 100644 index 000000000..c7f2587df --- /dev/null +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/index.module.css @@ -0,0 +1,37 @@ +/* +* 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; + pointer-events: none; +} + +.progress .pf-c-wizard__nav-link { + margin-left: 8px; +} + +.progress .pf-c-wizard__nav-link > svg { + margin-right: 5px; +} + +.progress .pf-c-wizard__toggle { + padding-left: 56px; +} + +.progress .pf-c-wizard__toggle .pf-c-wizard__toggle-list-item > svg { + margin-right: 5px; +} diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/index.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/index.tsx index 828e5ff21..40f9c6439 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/index.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/index.tsx @@ -10,7 +10,6 @@ * Red Hat, Inc. - initial API and implementation */ -import { V1alpha2DevWorkspaceStatusConditions } from '@devfile/api'; import { History } from 'history'; import isEqual from 'lodash/isEqual'; import React from 'react'; @@ -38,8 +37,9 @@ import CreatingStepInitialize from './CreatingSteps/Initialize'; import StartingStepInitialize from './StartingSteps/Initialize'; import StartingStepOpenWorkspace from './StartingSteps/OpenWorkspace'; import StartingStepStartWorkspace from './StartingSteps/StartWorkspace'; -import StartingStepWorkspaceConditions from './StartingSteps/WorkspaceConditions'; -import { ConditionType, isWorkspaceStatusCondition } from './utils'; +import StartingStepWorkspaceConditions, { + ConditionType, +} from './StartingSteps/WorkspaceConditions'; import WorkspaceProgressWizard, { WorkspaceProgressWizardStep } from './Wizard'; export type Props = MappedProps & { @@ -51,8 +51,7 @@ export type Props = MappedProps & { export type State = { activeStepId: StepId; alertItems: AlertItem[]; - conditions: V1alpha2DevWorkspaceStatusConditions[]; - hasBeenStarted: boolean; + conditions: ConditionType[]; doneSteps: StepId[]; factoryParams: FactoryParams; initialLoaderMode: LoaderMode; @@ -86,7 +85,6 @@ class Progress extends React.Component { activeStepId: Step.INITIALIZE, alertItems: [], conditions: [], - hasBeenStarted: false, doneSteps: [], factoryParams, initialLoaderMode, @@ -122,42 +120,23 @@ class Progress extends React.Component { } public componentDidMount(): void { - this.init(this.props, this.state, undefined); + this.init(); } - public componentDidUpdate(prevProps: Props): void { - this.init(this.props, this.state, prevProps); + public componentDidUpdate(): void { + this.init(); } - private init(props: Props, state: State, prevProps: Props | undefined): void { - const workspace = this.findTargetWorkspace(props); - const prevWorkspace = this.findTargetWorkspace(prevProps); - - if ( - (prevWorkspace === undefined || prevWorkspace.status !== DevWorkspaceStatus.STARTING) && - workspace?.status === DevWorkspaceStatus.STARTING && - state.activeStepId === Step.START - ) { - this.setState({ - hasBeenStarted: true, - }); - } - - if ( - workspace && - (workspace.status === DevWorkspaceStatus.STARTING || - workspace.status === DevWorkspaceStatus.RUNNING || - workspace.status === DevWorkspaceStatus.FAILING || - workspace.status === DevWorkspaceStatus.FAILED) - ) { - const conditions = workspace.ref.status?.conditions || []; + private init(): void { + const workspace = this.findTargetWorkspace(this.props); + if (workspace && workspace.status === DevWorkspaceStatus.STARTING) { + const conditions = (workspace.ref.status?.conditions || []).filter( + condition => condition.message, + ) as ConditionType[]; const lastScore = this.scoreConditions(this.state.conditions); const score = this.scoreConditions(conditions); - if ( - score > lastScore || - (score !== 0 && score === lastScore && isEqual(this.state.conditions, conditions) === false) - ) { + if (score > lastScore) { this.setState({ conditions, }); @@ -165,11 +144,7 @@ class Progress extends React.Component { } } - private findTargetWorkspace(props?: Props): Workspace | undefined { - if (props === undefined) { - return; - } - + private findTargetWorkspace(props: Props): Workspace | undefined { const { allWorkspaces, history } = props; const loaderMode = getLoaderMode(history.location); @@ -180,7 +155,7 @@ class Progress extends React.Component { return findTargetWorkspace(allWorkspaces, loaderMode.workspaceParams); } - private scoreConditions(conditions: V1alpha2DevWorkspaceStatusConditions[]): number { + private scoreConditions(conditions: ConditionType[]): number { const typeScore = { Started: 1, DevWorkspaceResolved: 1, @@ -266,7 +241,6 @@ class Progress extends React.Component { activeStepId: newActiveStep, doneSteps: newDoneSteps, conditions: [], - hasBeenStarted: false, }); if (tab) { @@ -302,7 +276,6 @@ class Progress extends React.Component { name: ( this.handleStepsShowAlert(Step.INITIALIZE, alertItem)} @@ -326,7 +299,6 @@ class Progress extends React.Component { name: ( this.handleStepsShowAlert(Step.INITIALIZE, alertItem)} @@ -351,7 +323,6 @@ class Progress extends React.Component { name: ( this.handleStepsShowAlert(Step.LIMIT_CHECK, alertItem)} @@ -370,22 +341,13 @@ class Progress extends React.Component { const { factoryParams } = this.state; const usePrebuiltResources = factoryParams.useDevworkspaceResources; - const steps = [ - usePrebuiltResources ? this.getFactoryFetchResources() : this.getFactoryFetchDevfile(), - this.getCheckExistingWorkspacesStep(), - usePrebuiltResources ? this.getFactoryApplyResources() : this.getFactoryApplyDevfile(), - ]; - - const areFinishedChildren = steps.every(step => step.isFinishedStep); - const distance = areFinishedChildren ? 1 : -1; return [ { id: Step.CREATE, name: ( this.handleStepsShowAlert(Step.CREATE, alertItem)} @@ -395,45 +357,37 @@ class Progress extends React.Component { /> ), component: <>, - steps, + steps: [ + usePrebuiltResources ? this.getFactoryFetchResources() : this.getFactoryFetchDevfile(), + { + id: Step.CONFLICT_CHECK, + name: ( + this.handleStepsShowAlert(Step.CONFLICT_CHECK, alertItem)} + onHideError={alertId => this.handleCloseStepAlert(alertId)} + onNextStep={() => this.handleStepsGoToNext(Step.CONFLICT_CHECK)} + onRestart={tab => this.handleStepsRestart(Step.CONFLICT_CHECK, tab)} + /> + ), + component: <>, + }, + usePrebuiltResources ? this.getFactoryApplyResources() : this.getFactoryApplyDevfile(), + ], }, ]; } - private getCheckExistingWorkspacesStep(): WorkspaceProgressWizardStep { - const { history, searchParams } = this.props; - const distance = this.getDistance(Step.CONFLICT_CHECK); - - return { - id: Step.CONFLICT_CHECK, - isFinishedStep: distance === 1, - name: ( - this.handleStepsShowAlert(Step.CONFLICT_CHECK, alertItem)} - onHideError={alertId => this.handleCloseStepAlert(alertId)} - onNextStep={() => this.handleStepsGoToNext(Step.CONFLICT_CHECK)} - onRestart={tab => this.handleStepsRestart(Step.CONFLICT_CHECK, tab)} - /> - ), - component: <>, - }; - } - private getFactoryFetchResources(): WorkspaceProgressWizardStep { const { history, searchParams } = this.props; - const distance = this.getDistance(Step.FETCH); return { id: Step.FETCH, - isFinishedStep: distance === 1, name: ( this.handleStepsShowAlert(Step.FETCH, alertItem)} @@ -448,15 +402,12 @@ class Progress extends React.Component { private getFactoryApplyResources(): WorkspaceProgressWizardStep { const { history, searchParams } = this.props; - const distance = this.getDistance(Step.APPLY); return { id: Step.APPLY, - isFinishedStep: distance === 1, name: ( this.handleStepsShowAlert(Step.APPLY, alertItem)} @@ -471,15 +422,12 @@ class Progress extends React.Component { private getFactoryFetchDevfile(): WorkspaceProgressWizardStep { const { history, searchParams } = this.props; - const distance = this.getDistance(Step.FETCH); return { id: Step.FETCH, - isFinishedStep: distance === 1, name: ( this.handleStepsShowAlert(Step.FETCH, alertItem)} @@ -494,15 +442,12 @@ class Progress extends React.Component { private getFactoryApplyDevfile(): WorkspaceProgressWizardStep { const { history, searchParams } = this.props; - const distance = this.getDistance(Step.APPLY); return { id: Step.APPLY, - isFinishedStep: distance === 1, name: ( this.handleStepsShowAlert(Step.APPLY, alertItem)} @@ -522,12 +467,7 @@ class Progress extends React.Component { const matchParams = loaderMode.mode === 'workspace' ? loaderMode.workspaceParams : undefined; - // hide spinner near this (parent) step - const showChildren = this.state.hasBeenStarted; - - // this (parent) step cannot be active if it contains child steps - // we need this step to be activated and start workspace and only then allow to appear condition steps - const conditionSteps = showChildren ? this.buildConditionSteps() : []; + const conditionSteps = this.buildConditionSteps(); const steps = conditionSteps.length > 0 ? { steps: conditionSteps } : {}; return [ @@ -536,7 +476,6 @@ class Progress extends React.Component { name: ( this.handleStepsShowAlert(Step.START, alertItem)} onHideError={key => this.handleCloseStepAlert(key)} onNextStep={() => this.handleStepsGoToNext(Step.START)} @@ -552,7 +491,6 @@ class Progress extends React.Component { name: ( this.handleStepsShowAlert(Step.OPEN, alertItem)} onHideError={key => this.handleCloseStepAlert(key)} onNextStep={() => this.handleStepsGoToNext(Step.OPEN)} @@ -574,45 +512,24 @@ class Progress extends React.Component { return []; } - // Children steps get hidden when all of them are finished. This usually - // happens in the middle of a devWorkspace starting flow and makes - // the condition sub-steps to flicker. The fix is to wait until - // the condition of type 'Ready' is done and only then set all - // condition steps as finished. - const areFinishedSteps = conditions.some( - condition => condition.type === 'Ready' && condition.status === 'True', - ); - - const workspaceStartFailed = conditions.some(condition => condition.reason !== undefined); - - return conditions - .filter((condition): condition is ConditionType => isWorkspaceStatusCondition(condition)) - .filter(condition => { - // show only condition with the failure description - return workspaceStartFailed ? condition.status === 'True' : true; - }) - .map(condition => { - const stepId: ConditionStepId = `condition-${condition.type}`; - const distance = condition.status === 'True' ? 1 : 0; - - return { - id: stepId, - isFinishedStep: areFinishedSteps, - name: ( - this.handleStepsShowAlert(stepId, alertItem)} - onHideError={key => this.handleCloseStepAlert(key)} - onNextStep={() => this.handleStepsGoToNext(stepId)} - onRestart={tab => this.handleStepsRestart(stepId, tab)} - /> - ), - }; - }); + return conditions.map(condition => { + const stepId: ConditionStepId = `condition-${condition.type}`; + return { + id: stepId, + name: ( + this.handleStepsShowAlert(stepId, alertItem)} + onHideError={key => this.handleCloseStepAlert(key)} + onNextStep={() => this.handleStepsGoToNext(stepId)} + onRestart={tab => this.handleStepsRestart(stepId, tab)} + /> + ), + }; + }); } private handleSwitchToNextStep(nextStepId: StepId | undefined, prevStepId: StepId): void { diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/scoreConditions.ts b/packages/dashboard-frontend/src/components/WorkspaceProgress/scoreConditions.ts new file mode 100644 index 000000000..6d3f1c649 --- /dev/null +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/scoreConditions.ts @@ -0,0 +1,32 @@ +/* + * 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 { ConditionType } from './StartingSteps/WorkspaceConditions'; + +export function scoreConditions(conditions: ConditionType[]): number { + const typeScore = { + Started: 1, + DevWorkspaceResolved: 1, + StorageReady: 1, + RoutingReady: 1, + ServiceAccountReady: 1, + PullSecretsReady: 1, + DeploymentReady: 1, + }; + + return conditions.reduce((acc, condition) => { + if (typeScore[condition.type] !== undefined) { + return acc + typeScore[condition.type]; + } + return acc; + }, 0); +} diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/utils.ts b/packages/dashboard-frontend/src/components/WorkspaceProgress/utils.ts deleted file mode 100644 index d5796172b..000000000 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/utils.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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 { V1alpha2DevWorkspaceStatusConditions } from '@devfile/api'; - -export type ConditionType = V1alpha2DevWorkspaceStatusConditions & { - status: 'True' | 'False' | 'Unknown'; -}; - -export function isWorkspaceStatusCondition( - condition: V1alpha2DevWorkspaceStatusConditions, -): condition is ConditionType { - return ( - (condition as ConditionType).status === 'False' || - (condition as ConditionType).status === 'True' || - (condition as ConditionType).status === 'Unknown' - ); -} - -export function isConditionReady( - condition: ConditionType, - prevCondition: ConditionType | undefined, -): boolean { - return ( - isConditionError(condition, prevCondition) === true || - condition.status === 'True' || - (condition.status === 'Unknown' && prevCondition?.status === 'True') - ); -} - -export function isConditionError( - condition: ConditionType, - prevCondition: ConditionType | undefined, -): boolean { - return ( - (prevCondition?.status === 'False' && condition.status === 'Unknown') || - (prevCondition?.status === 'Unknown' && - condition.status === 'False' && - condition.message === 'Workspace stopped due to error') || - condition.reason !== undefined - ); -} - -export function scoreConditions(conditions: ConditionType[]): number { - const typeScore = { - Started: 1, - DevWorkspaceResolved: 1, - StorageReady: 1, - RoutingReady: 1, - ServiceAccountReady: 1, - PullSecretsReady: 1, - DeploymentReady: 1, - }; - - return conditions.reduce((acc, condition) => { - if (typeScore[condition.type] !== undefined) { - return acc + typeScore[condition.type]; - } - return acc; - }, 0); -}