Skip to content

Commit

Permalink
Show warning banner on user namespace FailedScheduling event
Browse files Browse the repository at this point in the history
  • Loading branch information
vinokurig committed Oct 4, 2024
1 parent 3350f84 commit ff5a1da
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright (c) 2018-2024 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';

export default class BannerAlertNoNodeAvailable extends React.PureComponent {
public render(): React.ReactElement {
return <div>Mock BannerAlertNoNodeAvailable component</div>;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright (c) 2018-2024 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 { api } from '@eclipse-che/common';
import { render, RenderResult, screen, waitFor } from '@testing-library/react';
import React from 'react';
import { Provider } from 'react-redux';

import BannerAlertNoNodeAvailable from '@/components/BannerAlert/NoNodeAvailable';
import { container } from '@/inversify.config';
import { WebsocketClient } from '@/services/backend-client/websocketClient';
import { FakeStoreBuilder } from '@/store/__mocks__/storeBuilder';

const websocketClient = container.get(WebsocketClient);
const text =
'Cluster autoscaler is provisioning a new node at the moment. Please be patient, workspace startup will be taking longer than usual.';

describe('BannerAlertNoNodeAvailable component', () => {
it('should show alert when failedScheduling event is received and hide alert when workspace has started', async () => {
renderComponent();

// Dispatch FailedScheduling event to add workspace to the state.
(websocketClient as any).messageHandler.listeners.get(api.webSocket.Channel.EVENT)![0]({
event: {
reason: 'FailedScheduling',
message: 'No preemption victims found for incoming pod',
metadata: { uid: 'uid' },
},
} as any);
await waitFor(() => expect(screen.queryAllByText(text).length).toEqual(1));

// Dispatch workspace started event to clear the state.
(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));

await waitFor(() => expect(screen.queryAllByText(text).length).toEqual(0));
});
});

function renderComponent(): RenderResult {
const store = new FakeStoreBuilder().build();
const component = (
<Provider store={store}>
<BannerAlertNoNodeAvailable />
</Provider>
);
return render(<Provider store={store}>{component}</Provider>);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright (c) 2018-2024 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 { 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 { AppState } from '@/store';
import { selectBranding } from '@/store/Branding/selectors';
type Props = MappedProps;

type State = {
startingWorkspaces: string[];
};

class BannerAlertNoNodeAvailable extends React.PureComponent<Props, State> {
private readonly websocketClient: WebsocketClient;

constructor(props: Props) {
super(props);
this.websocketClient = container.get(WebsocketClient);
this.state = {
startingWorkspaces: [],
};
}

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: [] });
}
};
const eventListener: ChannelListener = message => {
const event = (message as api.webSocket.EventMessage).event;
if (event.reason === undefined || event.message === undefined) {
return;
} else if (
event.reason === 'FailedScheduling' &&
event.message.indexOf('No preemption victims found for incoming pod') > -1
) {
this.setState({ startingWorkspaces: [event.metadata!.uid!] });
}
};
this.websocketClient.addChannelMessageListener(api.webSocket.Channel.EVENT, eventListener);
this.websocketClient.addChannelMessageListener(
api.webSocket.Channel.DEV_WORKSPACE,
devWorkspaceListener,
);
}

render() {
if (this.state.startingWorkspaces.length === 0) {
return null;
}

return (
<Banner className="pf-u-text-align-center" variant="warning">
Cluster autoscaler is provisioning a new node at the moment. Please be patient, workspace
startup will be taking longer than usual.
</Banner>
);
}
}

const mapStateToProps = (state: AppState) => ({
branding: selectBranding(state),
});

const connector = connect(mapStateToProps);

type MappedProps = ConnectedProps<typeof connector>;
export default connector(BannerAlertNoNodeAvailable);
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,10 @@ exports[`BannerAlert snapshot 1`] = `
Mock BannerAlertCustomWarning component
</div>
</div>
<div>
<div>
Mock BannerAlertNoNodeAvailable component
</div>
</div>
</div>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ jest.mock('@/components/BannerAlert/Branding');
jest.mock('@/components/BannerAlert/Custom');
jest.mock('@/components/BannerAlert/NotSupportedBrowser');
jest.mock('@/components/BannerAlert/WebSocket');
jest.mock('@/components/BannerAlert/NoNodeAvailable');

const { createSnapshot } = getComponentRenderer(getComponent);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import React from 'react';

import BannerAlertBranding from '@/components/BannerAlert/Branding';
import BannerAlertCustomWarning from '@/components/BannerAlert/Custom';
import BannerAlertNoNodeAvailable from '@/components/BannerAlert/NoNodeAvailable';
import BannerAlertWebSocket from '@/components/BannerAlert/WebSocket';

type Props = unknown;
Expand All @@ -30,6 +31,7 @@ export class BannerAlert extends React.PureComponent<Props, State> {
<BannerAlertWebSocket key="BannerAlertWebSocket"></BannerAlertWebSocket>,
<BannerAlertBranding key="BannerAlertBranding"></BannerAlertBranding>,
<BannerAlertCustomWarning key="BannerAlertCustomWarning"></BannerAlertCustomWarning>,
<BannerAlertNoNodeAvailable key="BannerAlertNoNodeAvailable"></BannerAlertNoNodeAvailable>,
],
};
}
Expand Down

0 comments on commit ff5a1da

Please sign in to comment.