Skip to content

Commit

Permalink
Add Server Offline Landing to Placeholder Widget
Browse files Browse the repository at this point in the history
Adds a prompt to start the server when the front-end detects that there is no server
currently running.  This provides a clearer application flow for the user.

Signed-off-by: Will Yang <[email protected]>
  • Loading branch information
williamsyang-work committed Feb 15, 2024
1 parent ddb5ca7 commit 07f3f22
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface TraceContextProps {
removeResizeHandler: (handler: () => void) => void;
backgroundTheme: string;
persistedState?: PersistedState;
serverStatus?: boolean;
}

export interface TraceContextState {
Expand Down Expand Up @@ -491,20 +492,29 @@ export class TraceContextComponent extends React.Component<TraceContextProps, Tr
}

render(): JSX.Element {
const { serverStatus } = this.props;
const shouldRenderOutputs =
this.state.style.width > 0 && (this.props.outputs.length || this.props.overviewDescriptor);
return (
<div
className="trace-context-container"
onContextMenu={event => this.onContextMenu(event)}
onKeyDown={event => this.onKeyDown(event)}
onKeyUp={event => this.onKeyUp(event)}
ref={this.traceContextContainer}
>
<TooltipComponent ref={this.tooltipComponent} />
<TooltipXYComponent ref={this.tooltipXYComponent} />
{shouldRenderOutputs ? this.renderOutputs() : this.renderPlaceHolder()}
</div>
<>
{/* Render the grey-out overlay if the server is down */}
{serverStatus === false && (
<div className="overlay">
<div className="warning-text">Please start the server to resume using the application.</div>
</div>
)}
<div
className="trace-context-container"
onContextMenu={event => this.onContextMenu(event)}
onKeyDown={event => this.onKeyDown(event)}
onKeyUp={event => this.onKeyUp(event)}
ref={this.traceContextContainer}
>
<TooltipComponent ref={this.tooltipComponent} />
<TooltipXYComponent ref={this.tooltipXYComponent} />
{shouldRenderOutputs ? this.renderOutputs() : this.renderPlaceHolder()}
</div>
</>
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,43 @@ import * as React from 'react';

export interface ReactPlaceholderWidgetProps {
loading: boolean;
serverOn: boolean;
tracesOpen: boolean;
handleOpenTrace: () => void;
handleStartServer: () => void;
}

export class ReactExplorerPlaceholderWidget extends React.Component<ReactPlaceholderWidgetProps, unknown> {
export class ReactExplorerPlaceholderWidget extends React.Component<ReactPlaceholderWidgetProps, {}> {
constructor(props: ReactPlaceholderWidgetProps) {
super(props);
}

render(): React.ReactNode {
const { loading, serverOn, handleOpenTrace, handleStartServer } = this.props;
const onClick = serverOn ? handleOpenTrace : handleStartServer;
const infoText = serverOn
? 'You have not yet opened a trace.'
: 'No trace server instance is currently running.';
const buttonText = serverOn ? 'Open Trace' : 'Start Trace Server';

return (
<div className="placeholder-container" tabIndex={0}>
<div className="center">{'You have not yet opened a trace.'}</div>
<div className="center">{infoText}</div>
<div className="placeholder-open-workspace-button-container">
<button
className="plcaeholder-open-workspace-button"
title="Select a trace to open"
onClick={this.props.handleOpenTrace}
disabled={this.props.loading}
title={buttonText}
onClick={onClick}
disabled={loading}
>
{this.props.loading && <FontAwesomeIcon icon={faSpinner} spin style={{ marginRight: '5px' }} />}
{this.props.loading && <span>Connecting to trace server</span>}
{!this.props.loading && <span>Open Trace</span>}
{loading && <FontAwesomeIcon icon={faSpinner} spin style={{ marginRight: '5px' }} />}
{loading && <span>Connecting to trace server</span>}
{!loading && <span>{buttonText}</span>}
</button>
</div>
</div>
);
}
}

export default ReactExplorerPlaceholderWidget;
20 changes: 20 additions & 0 deletions packages/react-components/style/trace-viewer.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,24 @@ div {
.trace-viewer-container {
margin: 0px 5px 0px 5px;
height: 100%;
}

/* Grey out container */
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent black */
display: flex;
justify-content: center;
align-items: center;
z-index: 999; /* Ensure the overlay appears on top */
}

.warning-text {
color: white; /* Color of the warning text */
text-align: center;
font-size: 24px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,45 @@ import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'
import { ReactWidget } from '@theia/core/lib/browser';
import * as React from 'react';
import { CommandService } from '@theia/core';
import { OpenTraceCommand } from '../../trace-viewer/trace-viewer-commands';
import { OpenTraceCommand, StartServerCommand } from '../../trace-viewer/trace-viewer-commands';
import { ReactExplorerPlaceholderWidget } from 'traceviewer-react-components/lib/trace-explorer/trace-explorer-placeholder-widget';
import { TraceServerConnectionStatusClient } from '../../../common/trace-server-connection-status';

@injectable()
export class TraceExplorerPlaceholderWidget extends ReactWidget {
static ID = 'trace-explorer-placeholder-widget';
static LABEL = 'Trace Explorer Placeholder Widget';

state = {
loading: false
loading: false,
serverStatus: false
};

@inject(CommandService) protected readonly commandService!: CommandService;
@inject(TraceServerConnectionStatusClient)
protected traceServerConnectionStatusProxy: TraceServerConnectionStatusClient;

@postConstruct()
protected init(): void {
this.id = TraceExplorerPlaceholderWidget.ID;
this.title.label = TraceExplorerPlaceholderWidget.LABEL;
this.traceServerConnectionStatusProxy.addServerStatusChangeListener(this.handleOnServerStatusChange);
this.update();
}

dispose(): void {
super.dispose();
this.traceServerConnectionStatusProxy.removeServerStatusChangeListener(this.handleOnServerStatusChange);

}

render(): React.ReactNode {
const { loading } = this.state;
const { loading, serverStatus } = this.state;
return (
<ReactExplorerPlaceholderWidget
tracesOpen={false} // If we can see this component, there are no opened traces.
serverOn={serverStatus}
handleStartServer={this.handleStartServer}
loading={loading}
handleOpenTrace={this.handleOpenTrace}
></ReactExplorerPlaceholderWidget>
Expand All @@ -42,4 +56,22 @@ export class TraceExplorerPlaceholderWidget extends ReactWidget {
this.state.loading = false;
this.update();
}

protected handleStartServer = async (): Promise<void> => this.doHandleStartServer();

private async doHandleStartServer() {
this.state.loading = true;
this.update();
await this.commandService.executeCommand(StartServerCommand.id);
this.state.loading = false;
// const status = await this.traceServerConnectionStatusProxy.getStatus();
// this.handleOnServerStatusChange(status);
}

protected handleOnServerStatusChange = (status: boolean): void => this.doHandleOnServerStatusChange(status);

private doHandleOnServerStatusChange = (status: boolean): void => {
this.state.serverStatus = status;
this.update();
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { CommandService } from '@theia/core';
export class TraceExplorerServerStatusWidget extends ReactWidget {
static ID = 'trace-explorer-server-status-widget';
static LABEL = 'Trace Explorer Server Status Widget';
private serverOn = false;

@inject(CommandService) protected readonly commandService!: CommandService;

Expand All @@ -17,17 +18,34 @@ export class TraceExplorerServerStatusWidget extends ReactWidget {
this.update();
}

public updateStatus = (status: boolean): void => {
this.serverOn = status;
this.update();
};

render(): React.ReactNode {

const className = this.serverOn ?
'fa fa-check-circle-o fa-lg' :
'fa fa-times-circle-o fa-lg';

const title = this.serverOn ?
'Server health and latency are good. No known issues' :
'Trace Viewer Critical Error: Trace Server Offline';

const color = this.serverOn ? 'green' : 'red';

return (
<div className="server-status-header">
<span className="theia-header">Server Status </span>
<i
id="server-status-id"
className="fa fa-times-circle-o fa-lg"
title="Trace Viewer Critical Error: Trace Server Offline"
style={{ color: 'red', marginLeft: '5px' }}
/>
className={className}
title={title}
style={{ color, marginLeft: '5px' }}
/>
</div>
);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,14 @@ export class TraceExplorerWidget extends BaseWidget {
layout.addWidget(this.traceViewsContainer);
this.node.tabIndex = 0;
signalManager().on(Signals.OPENED_TRACES_UPDATED, this.onUpdateSignal);
this.connectionStatusClient.addServerStatusChangeListener(this.onServerStatusChange);
this.update();
}

dispose(): void {
super.dispose();
signalManager().off(Signals.OPENED_TRACES_UPDATED, this.onUpdateSignal);
this.connectionStatusClient.removeServerStatusChangeListener(this.onServerStatusChange);
}

protected onUpdateSignal = (payload: OpenedTracesUpdatedSignalPayload): void =>
Expand All @@ -100,6 +102,13 @@ export class TraceExplorerWidget extends BaseWidget {

protected onUpdateRequest(msg: Message): void {
super.onUpdateRequest(msg);

if (this.connectionStatusClient.status === false) {
this.traceViewsContainer.hide();
this.placeholderWidget.show();
return;
}

if (this._numberOfOpenedTraces > 0) {
this.traceViewsContainer.show();
this.placeholderWidget.hide();
Expand All @@ -115,12 +124,25 @@ export class TraceExplorerWidget extends BaseWidget {
}

protected async onAfterShow(): Promise<void> {
this.connectionStatusClient.addConnectionStatusListener();
this.connectionStatusClient.subscribe();
const status = await this.traceServerConnectionStatusProxy.getStatus();
this.connectionStatusClient.updateStatus(status);
}

protected onAfterHide(): void {
this.connectionStatusClient.removeConnectionStatusListener();
this.connectionStatusClient.unsubscribe();
}

protected onServerStatusChange = (status: boolean): void => this.doHandleOnServerStatusChange(status);
protected doHandleOnServerStatusChange(status: boolean): void {
alert(`server status is: ${status}`);
this.serverStatusWidget.updateStatus(status);
if (status === true) {
this.placeholderWidget.hide();
} else {
this.placeholderWidget.show();
}
this.update();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,47 @@
import { injectable } from 'inversify';
import { TraceServerConnectionStatusClient } from '../common/trace-server-connection-status';

type Listener = (serverStatus: boolean) => void;
@injectable()
export class TraceServerConnectionStatusClientImpl implements TraceServerConnectionStatusClient {
protected active = false;
protected _status = false;
protected listeners: Listener[] = [];

updateStatus(status: boolean): void {
this._status = status;
if (this.active) {
TraceServerConnectionStatusClientImpl.renderStatus(status);
// TraceServerConnectionStatusClientImpl.renderStatus(status);
this.listeners.forEach(fn => {
fn(status);
console.dir(fn);
});
}
}

addConnectionStatusListener(): void {
subscribe(): void {
this.active = true;
}

removeConnectionStatusListener(): void {
unsubscribe(): void {
this.active = false;
}

addServerStatusChangeListener(fn: Listener): void {
this.listeners.push(fn);
}

removeServerStatusChangeListener(fn: Listener): void {
const index = this.listeners.indexOf(fn);
if (index) {
this.listeners.splice(index, 1);
}
}

get status(): boolean {
return this._status;
}

static renderStatus(status: boolean): void {
if (document.getElementById('server-status-id')) {
document.getElementById('server-status-id')!.className = status
Expand Down
Loading

0 comments on commit 07f3f22

Please sign in to comment.