Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Server Offline Landing to Placeholder Widget #1043

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 reconnect 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,28 +4,38 @@ 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' : 'Resume Trace Extension';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be Reconnect instead of Resume Trace Extension?


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>
Expand Down
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,19 +2,24 @@ 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,
tracesOpened: false
};

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

@postConstruct()
protected init(): void {
Expand All @@ -23,10 +28,17 @@ export class TraceExplorerPlaceholderWidget extends ReactWidget {
this.update();
}

dispose(): void {
super.dispose();
}

render(): React.ReactNode {
const { loading } = this.state;
const { loading, serverStatus, tracesOpened } = this.state;
return (
<ReactExplorerPlaceholderWidget
tracesOpen={tracesOpened}
serverOn={serverStatus}
handleStartServer={this.handleStartServer}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, when porting this change to the vscode-trace-extension, the start server command is only available if vscode-trace-server is installed, which is not always the case. So, starting the trace should be handled there using the start/stop vscode API that is provided by the vscode-trace-extension and which extensions can contribute the start/stop functionality. The vscode-trace-server is contributing to that API.

loading={loading}
handleOpenTrace={this.handleOpenTrace}
></ReactExplorerPlaceholderWidget>
Expand All @@ -42,4 +54,20 @@ 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;
this.update();
}

public setStateAndShow(newState: { serverStatus: boolean; tracesOpened: boolean }): void {
this.state = { ...this.state, ...newState };
this.show();
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,16 +18,24 @@ 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' }}
/>
<i id="server-status-id" className={className} title={title} style={{ color, marginLeft: '5px' }} />
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,16 @@ export class TraceExplorerWidget extends BaseWidget {
layout.addWidget(this.traceViewsContainer);
this.node.tabIndex = 0;
signalManager().on(Signals.OPENED_TRACES_UPDATED, this.onUpdateSignal);
// TODO - Should we be using the backend, since we're on the backend?
this.connectionStatusClient.addServerStatusChangeListener(this.onServerStatusChange);
this.update();
}

dispose(): void {
super.dispose();
signalManager().off(Signals.OPENED_TRACES_UPDATED, this.onUpdateSignal);
// TODO - Should we be using the backend, since we're on the backend?
this.connectionStatusClient.removeServerStatusChangeListener(this.onServerStatusChange);
}

protected onUpdateSignal = (payload: OpenedTracesUpdatedSignalPayload): void =>
Expand All @@ -100,12 +104,17 @@ export class TraceExplorerWidget extends BaseWidget {

protected onUpdateRequest(msg: Message): void {
super.onUpdateRequest(msg);
if (this._numberOfOpenedTraces > 0) {
this.traceViewsContainer.show();
this.placeholderWidget.hide();
} else {

const serverStatus = this.connectionStatusClient.status;
const tracesOpened = this._numberOfOpenedTraces > 0;
const shouldShowPlaceholder = serverStatus === false || tracesOpened === false;

if (shouldShowPlaceholder) {
this.placeholderWidget.setStateAndShow({ serverStatus, tracesOpened });
this.traceViewsContainer.hide();
this.placeholderWidget.show();
} else {
this.placeholderWidget.hide();
this.traceViewsContainer.show();
}
}

Expand All @@ -115,12 +124,19 @@ export class TraceExplorerWidget extends BaseWidget {
}

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

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

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

protected doHandleOnServerStatusChange(status: boolean): void {
this.serverStatusWidget.updateStatus(status);
this.update();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,40 @@
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 {
public updateStatus(status: boolean): void {
this._status = status;
if (this.active) {
TraceServerConnectionStatusClientImpl.renderStatus(status);
this.listeners.forEach(fn => fn(status));
}
}

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

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

static renderStatus(status: boolean): void {
if (document.getElementById('server-status-id')) {
document.getElementById('server-status-id')!.className = status
? 'fa fa-check-circle-o fa-lg'
: 'fa fa-times-circle-o fa-lg';
document.getElementById('server-status-id')!.title = status
? 'Server health and latency are good. No known issues'
: 'Trace Viewer Critical Error: Trace Server Offline';
document.getElementById('server-status-id')!.style.color = status ? 'green' : 'red';
public addServerStatusChangeListener(fn: Listener): void {
this.listeners.push(fn);
}

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

get status(): boolean {
return this._status;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ import { TracePreferences, TRACE_PATH, TRACE_ARGS } from '../trace-server-prefer
import { TspClientProvider } from '../tsp-client-provider-impl';
import { ChartShortcutsDialog } from '../trace-explorer/trace-explorer-sub-widgets/charts-cheatsheet-component';
import { signalManager } from 'traceviewer-base/lib/signals/signal-manager';
import { TraceServerConnectionStatusClientImpl } from '../trace-server-connection-status-client-impl';
import { FileStat } from '@theia/filesystem/lib/common/files';
import { ITspClient } from 'tsp-typescript-client';
import { TraceServerConnectionStatusClient } from '../../common/trace-server-connection-status';

interface TraceViewerWidgetOpenerOptions extends WidgetOpenerOptions {
traceUUID: string;
Expand All @@ -50,6 +50,8 @@ export class TraceViewerContribution
@inject(TracePreferences) protected tracePreferences: TracePreferences;
@inject(TraceServerConfigService) protected readonly traceServerConfigService: TraceServerConfigService;
@inject(MessageService) protected readonly messageService: MessageService;
@inject(TraceServerConnectionStatusClient)
protected readonly serverStatusService: TraceServerConnectionStatusClient;

readonly id = TraceViewerWidget.ID;
readonly label = 'Trace Viewer';
Expand Down Expand Up @@ -94,7 +96,7 @@ export class TraceViewerContribution
progress.report({ message: 'Trace server started.', work: { done: 100, total: 100 } });
}
progress.cancel();
TraceServerConnectionStatusClientImpl.renderStatus(true);
this.serverStatusService.updateStatus(true);
signalManager().fireTraceServerStartedSignal();
this.openDialog(rootPath);
}
Expand Down Expand Up @@ -163,7 +165,7 @@ export class TraceViewerContribution
} else {
progress.report({ message: 'Trace server started.', work: { done: 100, total: 100 } });
}
TraceServerConnectionStatusClientImpl.renderStatus(true);
this.serverStatusService.updateStatus(true);
signalManager().fireTraceServerStartedSignal();
return super.open(traceURI, options);
}
Expand Down Expand Up @@ -230,7 +232,7 @@ export class TraceViewerContribution
} else {
progress.report({ message: 'Trace server started.', work: { done: 100, total: 100 } });
}
TraceServerConnectionStatusClientImpl.renderStatus(true);
this.serverStatusService.updateStatus(true);
signalManager().fireTraceServerStartedSignal();
return;
}
Expand Down Expand Up @@ -261,7 +263,7 @@ export class TraceViewerContribution
try {
await this.traceServerConfigService.stopTraceServer();
this.messageService.info('Trace server terminated successfully.');
TraceServerConnectionStatusClientImpl.renderStatus(false);
this.serverStatusService.updateStatus(false);
} catch (err) {
this.messageService.error('Failed to stop the trace server.');
}
Expand Down
Loading
Loading