diff --git a/packages/dashboard-frontend/src/components/BannerAlert/NoNodeAvailable/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/BannerAlert/NoNodeAvailable/__tests__/index.spec.tsx index 52d889894..99e3cb30c 100644 --- a/packages/dashboard-frontend/src/components/BannerAlert/NoNodeAvailable/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/components/BannerAlert/NoNodeAvailable/__tests__/index.spec.tsx @@ -10,23 +10,24 @@ * Red Hat, Inc. - initial API and implementation */ -import { api } from '@eclipse-che/common'; -import { EventPhase } from '@eclipse-che/common/lib/dto/api/webSocket'; -import { render, RenderResult, screen, waitFor } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import React from 'react'; import { Provider } from 'react-redux'; import { Store } from 'redux'; import BannerAlertNoNodeAvailable from '@/components/BannerAlert/NoNodeAvailable'; -import { container } from '@/inversify.config'; -import { WebsocketClient } from '@/services/backend-client/websocketClient'; +import getComponentRenderer from '@/services/__mocks__/getComponentRenderer'; +import { DevWorkspaceBuilder } from '@/store/__mocks__/devWorkspaceBuilder'; import { FakeStoreBuilder } from '@/store/__mocks__/storeBuilder'; +const { renderComponent } = getComponentRenderer(getComponent); const text = '"FailedScheduling" event occurred. If cluster autoscaler is enabled it might be provisioning a new node now and workspace startup will take longer than usual.'; -const websocketClient = container.get(WebsocketClient); + describe('BannerAlertNoNodeAvailable component', () => { it('should show alert when failedScheduling event is received and hide alert when workspace has started', async () => { + const { reRenderComponent } = renderComponent(new FakeStoreBuilder().build()); + const events = [ { reason: 'FailedScheduling', @@ -34,38 +35,50 @@ describe('BannerAlertNoNodeAvailable component', () => { metadata: { uid: 'uid' }, } as any, ]; - - renderComponent(new FakeStoreBuilder().withEvents({ events }).build()); + const store = new FakeStoreBuilder().withEvents({ events }).build(); + reRenderComponent(store); await waitFor(() => expect(screen.queryAllByText(text).length).toEqual(1)); + }); - // Dispatch workspace started event to clear the state. - await (websocketClient as any).messageHandler.listeners.get( - api.webSocket.Channel.DEV_WORKSPACE, - )![0]({ - devWorkspace: { status: { phase: 'Running' } }, - } as any); - - // wait banner to hide - await new Promise(resolve => setTimeout(resolve, 1000)); + it('should hide alert when workspace has started', async () => { + const { reRenderComponent } = renderComponent(new FakeStoreBuilder().build()); - await waitFor(() => expect(screen.queryAllByText(text).length).toEqual(0)); - }); + const events = [ + { + reason: 'FailedScheduling', + message: 'No preemption victims found for incoming pod', + metadata: { uid: 'uid' }, + } as any, + ]; + const workspaces = [ + new DevWorkspaceBuilder().withStatus({ phase: 'STARTING', devworkspaceId: 'id' }).build(), + ]; + const store = new FakeStoreBuilder() + .withEvents({ events }) + .withDevWorkspaces({ workspaces }) + .build(); + reRenderComponent(store); - it('should not show alert if user namespace event is undefined', async () => { - const events = [{} as any]; + await waitFor(() => expect(screen.queryAllByText(text).length).toEqual(1)); - renderComponent(new FakeStoreBuilder().withEvents({ events }).build()); + const nextWorkspaces = [ + new DevWorkspaceBuilder().withStatus({ phase: 'RUNNING', devworkspaceId: 'id' }).build(), + ]; + const nextStore = new FakeStoreBuilder() + .withEvents({ events }) + .withDevWorkspaces({ workspaces: nextWorkspaces }) + .build(); + reRenderComponent(nextStore); await waitFor(() => expect(screen.queryAllByText(text).length).toEqual(0)); }); }); -function renderComponent(store: Store): RenderResult { - const component = ( +function getComponent(store: Store) { + return ( ); - return render({component}); } diff --git a/packages/dashboard-frontend/src/components/BannerAlert/NoNodeAvailable/index.tsx b/packages/dashboard-frontend/src/components/BannerAlert/NoNodeAvailable/index.tsx index eb37f9bdc..a3f9a528d 100644 --- a/packages/dashboard-frontend/src/components/BannerAlert/NoNodeAvailable/index.tsx +++ b/packages/dashboard-frontend/src/components/BannerAlert/NoNodeAvailable/index.tsx @@ -10,14 +10,13 @@ * Red Hat, Inc. - initial API and implementation */ -import { api } from '@eclipse-che/common'; import { Banner } from '@patternfly/react-core'; import React from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { container } from '@/inversify.config'; import { WebsocketClient } from '@/services/backend-client/websocketClient'; -import { ChannelListener } from '@/services/backend-client/websocketClient/messageHandler'; +import { DevWorkspaceStatus } from '@/services/helpers/types'; import { AppState } from '@/store'; import { selectAllEvents } from '@/store/Events/selectors'; import { selectAllWorkspaces } from '@/store/Workspaces/selectors'; @@ -26,7 +25,6 @@ type Props = MappedProps; type State = { startingWorkspaces: string[]; - eventsLength: number; }; class BannerAlertNoNodeAvailable extends React.PureComponent { @@ -37,49 +35,57 @@ class BannerAlertNoNodeAvailable extends React.PureComponent { this.websocketClient = container.get(WebsocketClient); this.state = { startingWorkspaces: [], - eventsLength: 0, }; } - public async componentDidMount() { - const devWorkspaceListener: ChannelListener = message => { - const devWorkspace = (message as api.webSocket.DevWorkspaceMessage).devWorkspace; - if (devWorkspace.status === undefined) { - return; - } else if ( - devWorkspace.status.phase === 'Running' && - this.state.startingWorkspaces.length > 0 - ) { - this.setState({ startingWorkspaces: [] }); - } - }; - this.websocketClient.addChannelMessageListener( - api.webSocket.Channel.DEV_WORKSPACE, - devWorkspaceListener, - ); + componentDidUpdate(prevProps: Readonly) { + this.handleAllEventsChange(prevProps); + this.handleAllWorkspacesChange(prevProps); } - private handleAllEventsChange() { + private handleAllEventsChange(prevProps: Readonly) { const allEvents = this.props.allEvents; - if (allEvents.length === this.state.eventsLength) { + const prevAllEvents = prevProps.allEvents; + + if (JSON.stringify(allEvents) === JSON.stringify(prevAllEvents)) { return; } + const event = allEvents[allEvents.length - 1]; - if (event.message === undefined) { - return; - } else if ( + if ( + event.message !== undefined && event.reason === 'FailedScheduling' && event.message.indexOf('No preemption victims found for incoming pod') > -1 && this.state.startingWorkspaces.length === 0 ) { this.setState({ startingWorkspaces: [event.metadata!.uid!] }); - this.setState({ eventsLength: allEvents.length }); } } - render() { - this.handleAllEventsChange(); + private handleAllWorkspacesChange(prevProps: Readonly) { + const prevAllWorkspaces = prevProps.allWorkspaces; + const allWorkspaces = this.props.allWorkspaces; + + if (JSON.stringify(allWorkspaces) === JSON.stringify(prevAllWorkspaces)) { + return; + } + if ( + allWorkspaces.some( + workspace => + workspace.status === DevWorkspaceStatus.RUNNING && + prevAllWorkspaces.find( + prevWorkspace => + prevWorkspace.id === workspace.id && + prevWorkspace.status === DevWorkspaceStatus.STARTING, + ), + ) + ) { + this.setState({ startingWorkspaces: [] }); + } + } + + render() { if (this.state.startingWorkspaces.length === 0) { return null; }