Skip to content

Commit

Permalink
feat: error page for idled workspace
Browse files Browse the repository at this point in the history
Signed-off-by: David Kwon <dakwon@redhat.com>
  • Loading branch information
dkwon17 authored and ibuziuk committed Jun 22, 2022
1 parent 3f21a14 commit f6562c6
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,60 @@ exports[`Issue component should render the certification error 1`] = `
</p>
</div>
`;

exports[`Issue component should render the workspaceInactive error 1`] = `
<div
className="pf-c-content"
>
<h1
className=""
data-pf-content={true}
>
<svg
aria-hidden={true}
aria-labelledby={null}
fill="currentColor"
height="1em"
role="img"
style={
Object {
"verticalAlign": "-0.125em",
}
}
viewBox="0 0 1088 1024"
width="1em"
>
<path
d="M1057.10141,663.5 L845.101405,215.4 C787.101405,71.8 665.401405,0 542.901405,0 C420.201405,0 296.701405,71.9 235.001405,215.6 L31.7014051,648.5 C10.4014051,700 -0.0985948775,752.3 0.000697596367,800.8 C0.301405123,924.8 70.2014051,1024 209.101405,1024 L868.401405,1024 C1005.80141,1024 1087.70141,918.6 1088.00215,795.5 C1088.10141,752.4 1078.20141,707.2 1057.10141,663.5 Z M959.401405,800.3 C958.701405,822.9 952.901405,843.5 942.601405,859.7 C926.801405,884.6 902.601405,896.7 868.301405,896.7 L209.101405,896.7 C191.201405,896.7 176.601405,893.8 165.401405,888.2 C157.301405,884 150.801405,878.4 145.401405,870.3 C135.101405,855 129.101405,832 128.401405,805.6 C127.601405,772.8 134.901405,736.5 149.401405,700.5 L353.001405,266.7 C363.201405,242.9 376.101405,221.5 391.101405,203.2 C404.801405,186.6 420.301405,172.4 437.401405,161.1 C469.201405,139.9 505.701405,128.8 542.901405,128.8 C579.701405,128.8 615.401405,139.8 646.001405,160.5 C662.401405,171.6 677.101405,185.4 690.101405,201.6 C704.501405,219.6 716.401405,240.6 725.901405,264 L940.901405,718.9 L941.101405,719.3 L941.301405,719.7 C953.901405,746 960.201405,773.9 959.401405,800.3 Z M586.601405,832 L501.301405,832 C489.501405,831.8 480.201405,821.5 480.001405,808.7 L480.001405,727.3 C480.201405,714.5 489.601405,704.3 501.301405,704 L586.601405,704 C598.401405,704.2 607.701405,714.5 607.901405,727.3 L607.901405,808.7 L608.001405,808.7 C607.701405,821.5 598.301405,831.8 586.601405,832 M639.901405,290.7 L613.201405,610.4 C611.801405,626.9 598.001405,640 581.301405,640 L506.601405,640 C490.001405,640 476.101405,627.2 474.701405,610.7 L448.101405,291 C446.501405,272.3 461.301405,256.3 480.001405,256.3 L608.001405,256 C626.701405,256 641.401405,272 639.901405,290.7"
/>
</svg>
Warning
</h1>
<pre
className=""
data-pf-content={true}
>
The workspace is inactive.
</pre>
<p
className=""
data-pf-content={true}
>
<a
onClick={[Function]}
>
Restart your workspace
</a>
</p>
<p
className=""
data-pf-content={true}
>
<a
onClick={[Function]}
>
Return to the dashboard
</a>
</p>
</div>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ describe('Issue component', () => {
expect(renderer.create(component).toJSON()).toMatchSnapshot();
});

it('should render the workspaceInactive error', () => {
const issue = {
type: 'workspaceInactive',
error: new Error('The workspace is inactive.'),
data: { ideLoader: '', workspaceDetails: '' },
} as Issue;
const component = <IssueComponent branding={brandingData} issue={issue} />;

expect(renderer.create(component).toJSON()).toMatchSnapshot();
});

it('should render an unknown error', () => {
const issue = {
type: 'unknown',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { TextContent, Text, TextVariants } from '@patternfly/react-core';
import { WarningTriangleIcon } from '@patternfly/react-icons';
import React from 'react';
import { BrandingData } from '../../../services/bootstrap/branding.constant';
import { Issue } from '../../../services/bootstrap/issuesReporter';
import { Issue, WorkspaceRoutes } from '../../../services/bootstrap/issuesReporter';

import * as styles from './index.module.css';

Expand All @@ -32,6 +32,8 @@ export class IssueComponent extends React.PureComponent<Props> {
return this.renderCertError();
case 'sso':
return this.renderSsoError(issue.error);
case 'workspaceInactive':
return this.renderWorkspaceInactiveError(issue.error, issue.data);
default:
return this.renderUnknownError(issue.error);
}
Expand Down Expand Up @@ -92,6 +94,53 @@ export class IssueComponent extends React.PureComponent<Props> {
);
}

private renderWorkspaceInactiveError(
error: Error,
workspaceRoutes: WorkspaceRoutes | undefined,
): React.ReactNode {
const linkOnClick = (hash: string) => {
return () => {
window.location.hash = hash;
window.location.reload();
};
};

let ideLoader: React.ReactNode;
let workspaceDetails: React.ReactNode;

if (workspaceRoutes) {
ideLoader = (
<Text component={TextVariants.p}>
<a onClick={linkOnClick(workspaceRoutes.ideLoader)}>Restart your workspace</a>
</Text>
);

workspaceDetails = (
<Text component={TextVariants.p}>
<a onClick={linkOnClick(workspaceRoutes.workspaceDetails)}>Return to the dashboard</a>
</Text>
);
}

const warningTextbox = !error ? undefined : (
<Text component={TextVariants.pre} className={styles.errorMessage}>
{error.message}
</Text>
);

return (
<TextContent>
<Text component={TextVariants.h1}>
<WarningTriangleIcon className={styles.warningIcon} />
Warning
</Text>
{warningTextbox}
{ideLoader}
{workspaceDetails}
</TextContent>
);
}

private renderUnknownError(error: Error): React.ReactNode {
const errorTextbox = !error ? undefined : (
<Text component={TextVariants.pre} className={styles.errorMessage}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
* Red Hat, Inc. - initial API and implementation
*/

import { buildFactoryLoaderPath } from '../';
import SessionStorageService, { SessionStorageKey } from '../../services/session-storage';
import { buildFactoryLoaderPath, storePathIfNeeded } from '../';

describe('Location test', () => {
test('new policy', () => {
Expand Down Expand Up @@ -49,3 +50,22 @@ describe('Location test', () => {
);
});
});

describe('storePathnameIfNeeded test', () => {
let mockUpdate: jest.Mock;

beforeAll(() => {
mockUpdate = jest.fn();
SessionStorageService.update = mockUpdate;
});

test('empty path', () => {
storePathIfNeeded('/');
expect(mockUpdate).toBeCalledTimes(0);
});

test('regular path', () => {
storePathIfNeeded('/test');
expect(mockUpdate).toBeCalledWith(SessionStorageKey.ORIGINAL_LOCATION_PATH, '/test');
});
});
11 changes: 11 additions & 0 deletions packages/dashboard-frontend/src/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@
* Red Hat, Inc. - initial API and implementation
*/

import SessionStorageService, { SessionStorageKey } from '../services/session-storage';

(function acceptNewFactoryLink(): void {
if (window.location.pathname.startsWith('/dashboard/')) {
return;
}

storePathIfNeeded(window.location.pathname);

const hash = window.location.hash.replace(/(\/?)#(\/?)/, '#');
if (hash.startsWith('#http')) {
let factoryUrl = hash.substring(1);
Expand All @@ -27,6 +32,12 @@
}
})();

export function storePathIfNeeded(path: string) {
if (path !== '/') {
SessionStorageService.update(SessionStorageKey.ORIGINAL_LOCATION_PATH, path);
}
}

export function buildFactoryLoaderPath(url: string): string {
const fullUrl = new window.URL(url);
const editor = extractUrlParam(fullUrl, 'che-editor');
Expand Down
36 changes: 35 additions & 1 deletion packages/dashboard-frontend/src/services/bootstrap/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ import { selectDefaultNamespace } from '../../store/InfrastructureNamespaces/sel
import { selectDevWorkspacesResourceVersion } from '../../store/Workspaces/devWorkspaces/selectors';
import { AppAlerts } from '../alerts/appAlerts';
import { AlertVariant } from '@patternfly/react-core';
import SessionStorageService, { SessionStorageKey } from '../session-storage';
import { buildDetailsLocation, buildIdeLoaderLocation } from '../helpers/location';
import { selectAllWorkspaces } from '../../store/Workspaces/selectors';

/**
* This class executes a few initial instructions
Expand Down Expand Up @@ -80,10 +83,11 @@ export default class Bootstrap {
this.fetchRegistriesMetadata(settings),
this.watchNamespaces(),
this.updateDevWorkspaceTemplates(settings),
this.fetchWorkspaces(),
this.fetchWorkspaces().then(() => this.checkInactivityShutdown()),
this.fetchClusterInfo(),
this.fetchClusterConfig(),
]);

const errors = results
.filter(result => result.status === 'rejected')
.map(result => (result as PromiseRejectedResult).reason.toString());
Expand Down Expand Up @@ -267,4 +271,34 @@ export default class Bootstrap {
const { requestUserProfile } = UserProfileStore.actionCreators;
return requestUserProfile()(this.store.dispatch, this.store.getState, undefined);
}

private checkInactivityShutdown() {
const path = SessionStorageService.remove(SessionStorageKey.ORIGINAL_LOCATION_PATH);
if (!path) {
return;
}

const state = this.store.getState();

const workspace = selectAllWorkspaces(state).find(w => w.ideUrl?.includes(path));
if (!workspace) {
return;
}

if (workspace.isRunning && workspace.ideUrl) {
window.location.href = workspace.ideUrl;
return;
}

const ideLoader = buildIdeLoaderLocation(workspace).pathname;
const workspaceDetails = buildDetailsLocation(workspace).pathname;

this.issuesReporterService.registerIssue(
'workspaceInactive',
new Error(
'Your workspace has stopped. This could happen if your workspace shuts down due to inactivity.',
),
{ ideLoader, workspaceDetails },
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@

import { injectable } from 'inversify';

export type IssueType = 'cert' | 'sso' | 'unknown';
export type IssueType = 'cert' | 'sso' | 'workspaceInactive' | 'unknown';
export type Issue = {
type: IssueType;
error: Error;
data?: WorkspaceRoutes;
};

export type WorkspaceRoutes = { ideLoader: string; workspaceDetails: string };

@injectable()
export class IssuesReporterService {
private issues: Issue[] = [];
Expand All @@ -26,8 +29,8 @@ export class IssuesReporterService {
return this.issues.length !== 0;
}

public registerIssue(type: IssueType, error: Error): void {
this.issues.push({ type, error });
public registerIssue(type: IssueType, error: Error, data?: WorkspaceRoutes): void {
this.issues.push({ type, error, data });
}

public reportIssue(): Issue | undefined {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

export enum SessionStorageKey {
PRIVATE_FACTORY_RELOADS = 'private-factory-reloads-number',
ORIGINAL_LOCATION_PATH = 'original-location-path',
}

export default class SessionStorageService {
Expand All @@ -22,4 +23,10 @@ export default class SessionStorageService {
static get(key: SessionStorageKey): string | undefined {
return window.sessionStorage.getItem(key) || undefined;
}

static remove(key: SessionStorageKey): string | undefined {
const value = this.get(key);
window.sessionStorage.removeItem(key);
return value;
}
}

0 comments on commit f6562c6

Please sign in to comment.