Skip to content

Commit

Permalink
Merge pull request #126 from JPinkney/override-params2
Browse files Browse the repository at this point in the history
Add banner that triggers when websockets arent available
  • Loading branch information
JPinkney authored Jan 22, 2021
2 parents 6fc0b28 + bbff089 commit a7c5645
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 3 deletions.
2 changes: 2 additions & 0 deletions src/Layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { KeycloakAuthService } from '../services/keycloak/auth';
import { IssuesReporterService } from '../services/bootstrap/issuesReporter';
import { ErrorReporter } from './ErrorReporter';
import { IssueComponent } from './ErrorReporter/Issue';
import WebSocketBannerAlert from '../components/WebSocketBannerAlert';

const THEME_KEY = 'theme';
const IS_MANAGED_SIDEBAR = false;
Expand Down Expand Up @@ -152,6 +153,7 @@ export class Layout extends React.PureComponent<Props, State> {
}
isManagedSidebar={IS_MANAGED_SIDEBAR}
>
<WebSocketBannerAlert />
{this.props.children}
</Page>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright (c) 2018-2020 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/

import React from 'react';
import { container } from '../../../inversify.config';
import WebSocketBannerAlert from '../';
import { CheWorkspaceClient } from '../../../services/cheWorkspaceClient';
import { Provider } from 'react-redux';
import { FakeStoreBuilder } from '../../../store/__mocks__/storeBuilder';
import { BrandingData } from '../../../services/bootstrap/branding.constant';
import { render, RenderResult } from '@testing-library/react';

const failingWebSocketName = 'Failing websocket';
const failingMessage = 'WebSocket connections are failing';

class mockCheWorkspaceClient extends CheWorkspaceClient {
get failingWebSockets() { return [failingWebSocketName]; }
}

const store = new FakeStoreBuilder().withBranding({
docs: {
webSocketTroubleshooting: 'http://sample_documentation'
}
} as BrandingData).build();

describe('WebSocketBannerAlert component', () => {
it('should show error message when error found before mounting', () => {
container.rebind(CheWorkspaceClient).to(mockCheWorkspaceClient).inSingletonScope();
const component = renderComponent(<WebSocketBannerAlert />);
container.rebind(CheWorkspaceClient).to(CheWorkspaceClient).inSingletonScope();
expect(component.getAllByText(failingMessage, {
exact: false
}).length).toEqual(1);
});

it('should show error message when error found after mounting', () => {
const comp = (
<Provider store={store}>
<WebSocketBannerAlert />
</Provider>
);
const component = renderComponent(comp);
expect(component.queryAllByText(failingMessage, {
exact: false
})).toEqual([]);
container.rebind(CheWorkspaceClient).to(mockCheWorkspaceClient).inSingletonScope();
component.rerender(comp);
container.rebind(CheWorkspaceClient).to(CheWorkspaceClient).inSingletonScope();
expect(component.getAllByText(failingMessage, {
exact: false
}).length).toEqual(1);
});

it('should not show error message if none is present', () => {
const component = renderComponent(<WebSocketBannerAlert />);
expect(component.queryAllByText(failingMessage, {
exact: false
})).toEqual([]);
});

});

function renderComponent(
component: React.ReactElement
): RenderResult {
return render(
<Provider store={store}>
{component}
</Provider>
);
}
75 changes: 75 additions & 0 deletions src/components/WebSocketBannerAlert/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright (c) 2018-2020 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 { Banner } from '@patternfly/react-core';
import React from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { container } from '../../inversify.config';
import { CheWorkspaceClient } from '../../services/cheWorkspaceClient';
import { AppState } from '../../store';

type Props = MappedProps & {};

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

class WebSocketBannerAlert extends React.PureComponent<Props, State> {
private readonly cheWorkspaceClient: CheWorkspaceClient;

constructor(props: Props) {
super(props);
this.cheWorkspaceClient = container.get(CheWorkspaceClient);
this.state = {
erroringWebSockets: this.cheWorkspaceClient.failingWebSockets,
};
}

public componentWillUnmount() {
this.cheWorkspaceClient.removeWebSocketFailedListener();
}

public componentDidMount() {
this.cheWorkspaceClient.onWebSocketFailed(() => {
this.setState({
erroringWebSockets: this.cheWorkspaceClient.failingWebSockets,
});
});
}

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

const webSocketTroubleshootingDocs = this.props.brandingStore.data.docs
.webSocketTroubleshooting;
return (
<Banner className="pf-u-text-align-center" variant="warning">
WebSocket connections are failing. Refer to &quot;
<a href={webSocketTroubleshootingDocs} rel="noreferrer" target="_blank">
Network Troubleshooting
</a>&quot;
in the user guide.
</Banner>
);
}
}

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

const connector = connect(mapStateToProps);

type MappedProps = ConnectedProps<typeof connector>;
export default connector(WebSocketBannerAlert);
28 changes: 28 additions & 0 deletions src/services/cheWorkspaceClient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import { AxiosInstance } from 'axios';
import { injectable } from 'inversify';
import WorkspaceClient, { IWorkspaceMasterApi, IRemoteAPI } from '@eclipse-che/workspace-client';
import { KeycloakAuthService } from '../keycloak/auth';
import { EventEmitter } from 'events';

export type WebSocketsFailedCallback = () => void;

/**
* This class manages the api connection.
Expand All @@ -26,13 +29,18 @@ export class CheWorkspaceClient {
private websocketContext: string;
private _restApiClient: IRemoteAPI;
private _jsonRpcMasterApi: IWorkspaceMasterApi;
private _failingWebSockets: string[];
private webSocketEventEmitter: EventEmitter;
private webSocketEventName = 'websocketChanged';

/**
* Default constructor that is using resource.
*/
constructor() {
this.baseUrl = '/api';
this.websocketContext = '/api/websocket';
this._failingWebSockets = [];
this.webSocketEventEmitter = new EventEmitter();

this.originLocation = new URL(window.location.href).origin;

Expand Down Expand Up @@ -132,8 +140,28 @@ export class CheWorkspaceClient {
jsonRpcApiLocation += `?token=${this.token}`;
}
this._jsonRpcMasterApi = WorkspaceClient.getJsonRpcApi(jsonRpcApiLocation);
this._jsonRpcMasterApi.onDidWebSocketStatusChange((websockets: string[]) => {
this._failingWebSockets = [];
for (const websocket of websockets) {
const trimmedWebSocketId = websocket.substring(0, websocket.indexOf('?'));
this._failingWebSockets.push(trimmedWebSocketId);
}
this.webSocketEventEmitter.emit(this.webSocketEventName);
});
await this._jsonRpcMasterApi.connect(jsonRpcApiLocation);
const clientId = this._jsonRpcMasterApi.getClientId();
console.log('WebSocket connection clientId', clientId);
}

onWebSocketFailed(callback: WebSocketsFailedCallback) {
this.webSocketEventEmitter.on(this.webSocketEventName, callback);
}

removeWebSocketFailedListener() {
this.webSocketEventEmitter.removeAllListeners(this.webSocketEventName);
}

get failingWebSockets(): string[] {
return Array.from(this._failingWebSockets);
}
}
6 changes: 3 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -467,9 +467,9 @@
integrity sha512-KnnnDpnxxK0TBgR0Ux3oOYCvmCv6d4cRA+1j9wiLkaJighCqgCUaxnHWXEfolYbRq1+o/sfsxN+BxRDuF+H8wQ==

"@eclipse-che/workspace-client@^0.0.1-1608729566":
version "0.0.1-1608729566"
resolved "https://registry.yarnpkg.com/@eclipse-che/workspace-client/-/workspace-client-0.0.1-1608729566.tgz#833fd4ce3b32672a9329973ad10070c4bb78a8ab"
integrity sha512-yCWGK+iv3Zs8bLc+3oNho6NEiZ6deCs7MS1/oIGV/Z+edhcmzXWuEWE7BBBlV1kjsx63bnPuSXz4R/JLmD5v3Q==
version "0.0.1-1610629049"
resolved "https://registry.yarnpkg.com/@eclipse-che/workspace-client/-/workspace-client-0.0.1-1610629049.tgz#fd34b7b3f22bc969570c391cd4683c05782c2c9f"
integrity sha512-GF/1b3F/BsrqeF+BZ1QXZrEpQFLoiztD6QTQw0rcGBIr04K6IL+QpI+i3z6MbaI59yWvxEkhA/VL2oFTprEyyA==
dependencies:
"@eclipse-che/api" "^7.0.0-beta-4.0"
axios "0.20.0"
Expand Down

0 comments on commit a7c5645

Please sign in to comment.