Skip to content

Commit 0909c1c

Browse files
authored
fix: show the 'Downloading IDE binaries...' step only when needed (#1405)
* fix: show the 'Downloading IDE binaries...' step only when needed Signed-off-by: Oleksii Orel <[email protected]>
1 parent a2044d8 commit 0909c1c

File tree

4 files changed

+240
-23
lines changed

4 files changed

+240
-23
lines changed
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/*
2+
* Copyright (c) 2018-2024 Red Hat, Inc.
3+
* This program and the accompanying materials are made
4+
* available under the terms of the Eclipse Public License 2.0
5+
* which is available at https://www.eclipse.org/legal/epl-2.0/
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Red Hat, Inc. - initial API and implementation
11+
*/
12+
13+
import {
14+
EDITORS_WITHOUT_BINARIES,
15+
hasDownloadBinaries,
16+
} from '@/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/helpers';
17+
import { DEVWORKSPACE_CHE_EDITOR } from '@/services/devfileApi/devWorkspace/metadata';
18+
import { constructWorkspace } from '@/services/workspace-adapter';
19+
import { DevWorkspaceBuilder } from '@/store/__mocks__/devWorkspaceBuilder';
20+
21+
describe('hasDownloadBinaries', () => {
22+
let devWorkspaceBuilder: DevWorkspaceBuilder;
23+
24+
beforeAll(() => {
25+
devWorkspaceBuilder = new DevWorkspaceBuilder();
26+
});
27+
28+
it('should return false', () => {
29+
const name = 'test';
30+
const namespace = 'che-user';
31+
const editorId = 'che-incubator/che-code/latest';
32+
const workspaceWithEditorId = constructWorkspace(
33+
devWorkspaceBuilder
34+
.withMetadata({
35+
name,
36+
namespace,
37+
annotations: {
38+
[DEVWORKSPACE_CHE_EDITOR]: editorId,
39+
},
40+
})
41+
.build(),
42+
);
43+
44+
// if workspaces list is empty
45+
expect(hasDownloadBinaries([], { namespace, workspaceName: name })).toEqual(false);
46+
// if namespace is undefined
47+
expect(
48+
hasDownloadBinaries([workspaceWithEditorId], { namespace: undefined, workspaceName: name }),
49+
).toEqual(false);
50+
// if workspaceName is undefined
51+
expect(
52+
hasDownloadBinaries([workspaceWithEditorId], { namespace, workspaceName: undefined }),
53+
).toEqual(false);
54+
// skip if no target workspace found (wrong namespace)
55+
expect(
56+
hasDownloadBinaries([workspaceWithEditorId], {
57+
namespace: 'wrong-namespace',
58+
workspaceName: name,
59+
}),
60+
).toEqual(false);
61+
// skip if no target workspace found (wrong name)
62+
expect(
63+
hasDownloadBinaries([workspaceWithEditorId], { namespace, workspaceName: 'wrong-name' }),
64+
).toEqual(false);
65+
66+
// skip if editor name is in the list of editors without binaries
67+
expect(
68+
hasDownloadBinaries([workspaceWithEditorId], { namespace, workspaceName: name }),
69+
).toEqual(false);
70+
71+
const workspaceWithEditorDevfileEmptyContent = constructWorkspace(
72+
devWorkspaceBuilder
73+
.withMetadata({
74+
name,
75+
namespace,
76+
annotations: {
77+
[DEVWORKSPACE_CHE_EDITOR]: '',
78+
},
79+
})
80+
.build(),
81+
);
82+
// skip if editor annotation is empty
83+
expect(
84+
hasDownloadBinaries([workspaceWithEditorDevfileEmptyContent], {
85+
namespace,
86+
workspaceName: name,
87+
}),
88+
).toEqual(false);
89+
90+
const editorDevfileV2Content = 'schemaVersion: 2.2.0';
91+
const workspaceWithEditorDevfileV2Content = constructWorkspace(
92+
devWorkspaceBuilder
93+
.withMetadata({
94+
name,
95+
namespace,
96+
annotations: {
97+
[DEVWORKSPACE_CHE_EDITOR]: editorDevfileV2Content,
98+
},
99+
})
100+
.build(),
101+
);
102+
// skip if editor annotation contains the V2 devfile content
103+
expect(
104+
hasDownloadBinaries([workspaceWithEditorDevfileV2Content], {
105+
namespace,
106+
workspaceName: name,
107+
}),
108+
).toEqual(false);
109+
110+
const editorDevfileV1Content = 'apiVersion: 1.0.0';
111+
const workspaceWithEditorDevfileV1Content = constructWorkspace(
112+
devWorkspaceBuilder
113+
.withMetadata({
114+
name,
115+
namespace,
116+
annotations: {
117+
[DEVWORKSPACE_CHE_EDITOR]: editorDevfileV1Content,
118+
},
119+
})
120+
.build(),
121+
);
122+
// skip if editor annotation contains the V1 devfile content
123+
expect(
124+
hasDownloadBinaries([workspaceWithEditorDevfileV1Content], {
125+
namespace,
126+
workspaceName: name,
127+
}),
128+
).toEqual(false);
129+
});
130+
131+
it('should return true', () => {
132+
const name = 'test';
133+
const namespace = 'che-user';
134+
const editorId = 'che-incubator/che-idea-server/latest';
135+
const workspace = constructWorkspace(
136+
devWorkspaceBuilder
137+
.withMetadata({
138+
name,
139+
namespace,
140+
annotations: {
141+
[DEVWORKSPACE_CHE_EDITOR]: editorId,
142+
},
143+
})
144+
.build(),
145+
);
146+
// verify the list of editors without binaries
147+
expect(EDITORS_WITHOUT_BINARIES).toEqual(['che-code']);
148+
// if editor name is not in the list of editors without binaries
149+
expect(hasDownloadBinaries([workspace], { namespace, workspaceName: name })).toEqual(true);
150+
});
151+
});

packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/__tests__/index.spec.tsx

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414

1515
import { screen, waitFor } from '@testing-library/react';
1616
import React from 'react';
17+
import { Provider } from 'react-redux';
1718
import { Location } from 'react-router-dom';
19+
import { Store } from 'redux';
1820

1921
import {
2022
conditionChangedTo,
@@ -25,6 +27,7 @@ import {
2527
import { ConditionType } from '@/components/WorkspaceProgress/utils';
2628
import { WorkspaceRouteParams } from '@/Routes';
2729
import getComponentRenderer from '@/services/__mocks__/getComponentRenderer';
30+
import { MockStoreBuilder } from '@/store/__mocks__/mockStore';
2831

2932
import StartingStepWorkspaceConditions from '..';
3033

@@ -45,8 +48,11 @@ const matchParams: WorkspaceRouteParams = {
4548
workspaceName,
4649
};
4750

51+
let store: Store;
52+
4853
describe('Starting steps, checking workspace conditions', () => {
4954
beforeEach(() => {
55+
store = new MockStoreBuilder().build();
5056
jest.useFakeTimers();
5157
});
5258

@@ -168,19 +174,21 @@ describe('Starting steps, checking workspace conditions', () => {
168174

169175
function getComponent(condition: ConditionType, _matchParams = matchParams): React.ReactElement {
170176
return (
171-
<React.Fragment>
172-
<StartingStepWorkspaceConditions
173-
distance={0}
174-
hasChildren={false}
175-
condition={condition}
176-
location={{} as Location}
177-
navigate={jest.fn()}
178-
matchParams={_matchParams}
179-
onNextStep={mockOnNextStep}
180-
onRestart={mockOnRestart}
181-
onError={mockOnError}
182-
onHideError={mockOnHideError}
183-
/>
184-
</React.Fragment>
177+
<Provider store={store}>
178+
<React.Fragment>
179+
<StartingStepWorkspaceConditions
180+
distance={0}
181+
hasChildren={false}
182+
condition={condition}
183+
location={{} as Location}
184+
navigate={jest.fn()}
185+
matchParams={_matchParams}
186+
onNextStep={mockOnNextStep}
187+
onRestart={mockOnRestart}
188+
onError={mockOnError}
189+
onHideError={mockOnHideError}
190+
/>
191+
</React.Fragment>
192+
</Provider>
185193
);
186194
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (c) 2018-2024 Red Hat, Inc.
3+
* This program and the accompanying materials are made
4+
* available under the terms of the Eclipse Public License 2.0
5+
* which is available at https://www.eclipse.org/legal/epl-2.0/
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Red Hat, Inc. - initial API and implementation
11+
*/
12+
13+
import { WorkspaceRouteParams } from '@/Routes';
14+
import { DEVWORKSPACE_CHE_EDITOR } from '@/services/devfileApi/devWorkspace/metadata';
15+
import { Workspace } from '@/services/workspace-adapter';
16+
17+
export const EDITORS_WITHOUT_BINARIES = ['che-code'];
18+
19+
export function hasDownloadBinaries(
20+
allWorkspaces: Workspace[],
21+
matchParams: WorkspaceRouteParams,
22+
): boolean {
23+
const { namespace: targetNamespace, workspaceName: targetWorkspaceName } = matchParams;
24+
// skip if target namespace or workspace name is empty or workspaces list is empty
25+
if (!targetNamespace || !targetWorkspaceName || allWorkspaces.length === 0) {
26+
return false;
27+
}
28+
// find target workspace
29+
const targetWorkspace = allWorkspaces.find(
30+
w => w.name === targetWorkspaceName && w.namespace === targetNamespace,
31+
);
32+
// skip if no target workspace found
33+
if (!targetWorkspace) {
34+
return false;
35+
}
36+
const cheEditor = targetWorkspace.ref.metadata.annotations?.[DEVWORKSPACE_CHE_EDITOR];
37+
// skip if editor annotation empty or contains the devfile content
38+
if (!cheEditor || cheEditor.startsWith('apiVersion') || cheEditor.startsWith('schemaVersion')) {
39+
return false;
40+
}
41+
// extract editor name from annotation
42+
const name = cheEditor.split('/')[1];
43+
// check if editor is in the list of editors without binaries
44+
return !EDITORS_WITHOUT_BINARIES.includes(name);
45+
}

packages/dashboard-frontend/src/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/index.tsx

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ import common from '@eclipse-che/common';
1414
import { AlertVariant } from '@patternfly/react-core';
1515
import isEqual from 'lodash/isEqual';
1616
import React from 'react';
17+
import { connect, ConnectedProps } from 'react-redux';
1718

1819
import {
1920
ProgressStep,
2021
ProgressStepProps,
2122
ProgressStepState,
2223
} from '@/components/WorkspaceProgress/ProgressStep';
24+
import { hasDownloadBinaries } from '@/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/helpers';
2325
import styles from '@/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/index.module.css';
2426
import { PureSubCondition } from '@/components/WorkspaceProgress/StartingSteps/WorkspaceConditions/PureSubCondition';
2527
import { ProgressStepTitle } from '@/components/WorkspaceProgress/StepTitle';
@@ -30,19 +32,22 @@ import {
3032
} from '@/components/WorkspaceProgress/utils';
3133
import { WorkspaceRouteParams } from '@/Routes';
3234
import { AlertItem, LoaderTab } from '@/services/helpers/types';
33-
34-
export type Props = ProgressStepProps & {
35-
condition: ConditionType;
36-
matchParams: WorkspaceRouteParams;
37-
};
35+
import { RootState } from '@/store';
36+
import { selectAllWorkspaces } from '@/store/Workspaces';
37+
38+
export type Props = ProgressStepProps &
39+
MappedProps & {
40+
condition: ConditionType;
41+
matchParams: WorkspaceRouteParams;
42+
};
3843
export type State = ProgressStepState & {
3944
isWarning: boolean;
4045
isReady: boolean;
4146
condition: ConditionType;
4247
subConditionTitle: string;
4348
};
4449

45-
export default class StartingStepWorkspaceConditions extends ProgressStep<Props, State> {
50+
class StartingStepWorkspaceConditions extends ProgressStep<Props, State> {
4651
private timerId: number | undefined;
4752

4853
constructor(props: Props) {
@@ -85,12 +90,12 @@ export default class StartingStepWorkspaceConditions extends ProgressStep<Props,
8590

8691
// Show sub-condition only for DeploymentReady
8792
if (condition.type === 'DeploymentReady') {
88-
// Show sub-condition after 20 seconds
89-
this.timerId = window.setTimeout(() => {
93+
const { allWorkspaces, matchParams } = this.props;
94+
if (hasDownloadBinaries(allWorkspaces, matchParams)) {
9095
this.setState({
9196
subConditionTitle: 'Downloading IDE binaries... (it can take a few minutes)',
9297
});
93-
}, 20000);
98+
}
9499
}
95100
}
96101

@@ -179,3 +184,11 @@ export default class StartingStepWorkspaceConditions extends ProgressStep<Props,
179184
);
180185
}
181186
}
187+
188+
const connector = connect((state: RootState) => ({
189+
allWorkspaces: selectAllWorkspaces(state),
190+
}));
191+
192+
type MappedProps = ConnectedProps<typeof connector>;
193+
194+
export default connector(StartingStepWorkspaceConditions);

0 commit comments

Comments
 (0)