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 support for server progress reporting #52

Merged
merged 2 commits into from
Jul 13, 2023
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- [launch] Add a launcher component for starting WebSocket based GLSP servers [#41](https://github.com/eclipse-glsp/glsp-server-node/pull/41)
- [validation] Add explicit support and API for live and batch validation [#43](https://github.com/eclipse-glsp/glsp-server-node/pull/43)
- [launch] Launcher components now auto allocate a free port if the port argument is 0 [#42](https://github.com/eclipse-glsp/glsp-server-node/pull/42)
- [server] Add support for server progress reporting [#52](https://github.com/eclipse-glsp/glsp-server-node/pull/52)

### Breaking Changes

Expand Down
27 changes: 20 additions & 7 deletions packages/server/src/common/di/diagram-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import {
CenterAction,
DeleteMarkersAction,
EndProgressAction,
ExportSvgAction,
FitToScreenAction,
NavigateToExternalTargetAction,
Expand All @@ -39,9 +40,11 @@ import {
SetTypeHintsAction,
SetViewportAction,
SourceModelChangedAction,
StartProgressAction,
TriggerEdgeCreationAction,
TriggerNodeCreationAction,
UpdateModelAction
UpdateModelAction,
UpdateProgressAction
} from '@eclipse-glsp/protocol';
import { interfaces } from 'inversify';
import { ActionDispatcher, DefaultActionDispatcher } from '../actions/action-dispatcher';
Expand Down Expand Up @@ -86,14 +89,15 @@ import { RequestNavigationTargetsActionHandler } from '../features/navigation/re
import { ResolveNavigationTargetsActionHandler } from '../features/navigation/resolve-navigation-targets-action-handler';
import { PopupModelFactory } from '../features/popup/popup-model-factory';
import { RequestPopupModelActionHandler } from '../features/popup/request-popup-model-action-handler';
import { DefaultProgressService, ProgressService } from '../features/progress/progress-service';
import { ModelValidator } from '../features/validation/model-validator';
import { RequestMarkersHandler } from '../features/validation/request-markers-handler';
import { CompoundOperationHandler } from '../operations/compound-operation-handler';
import { OperationActionHandler } from '../operations/operation-action-handler';
import { OperationHandlerConstructor, OperationHandlerFactory } from '../operations/operation-handler';
import { OperationHandlerRegistry, OperationHandlerRegistryInitializer } from '../operations/operation-handler-registry';
import { ClientSessionInitializer } from '../session/client-session-initializer';
import { applyBindingTarget, applyOptionalBindingTarget, BindingTarget } from './binding-target';
import { BindingTarget, applyBindingTarget, applyOptionalBindingTarget } from './binding-target';
import { GLSPModule } from './glsp-module';
import { InstanceMultiBinding, MultiBinding } from './multi-binding';
import {
Expand Down Expand Up @@ -133,6 +137,7 @@ import {
* - {@link ContextActionsProviders} as {@link ClassMultiBinding<ContextActionsProvider>} (empty)
* - {@link ContextActionsProviderRegistry}
* - {@link ActionDispatcher}
* - {@link ProgressService}
* - {@link ClientActionKinds} as {@link InstanceMultiBinding<string>}
* - {@link ActionHandler} as {@link InstanceMultiBinding<ActionHandlerConstructor>}
* - {@link ActionHandlerFactory}
Expand Down Expand Up @@ -217,6 +222,7 @@ export abstract class DiagramModule extends GLSPModule {
this.configureMultiBinding(new MultiBinding<ClientSessionInitializer>(ClientSessionInitializer), binding =>
this.configureClientSessionInitializers(binding)
);
applyBindingTarget(context, ProgressService, this.bindProgressService()).inSingletonScope();
applyOptionalBindingTarget(context, PopupModelFactory, this.bindPopupModelFactory());
applyOptionalBindingTarget(context, LayoutEngine, this.bindLayoutEngine?.());
}
Expand Down Expand Up @@ -308,6 +314,10 @@ export abstract class DiagramModule extends GLSPModule {
binding.add(LayoutOperationHandler);
}

protected bindProgressService(): BindingTarget<ProgressService> {
return DefaultProgressService;
}

protected configureContextActionProviders(binding: MultiBinding<ContextActionsProvider>): void {
// empty as default
}
Expand All @@ -322,16 +332,17 @@ export abstract class DiagramModule extends GLSPModule {

protected configureClientActions(binding: InstanceMultiBinding<string>): void {
binding.add(CenterAction.KIND);
binding.add(ExportSvgAction.KIND);
binding.add(DeleteMarkersAction.KIND);
binding.add(EndProgressAction.KIND);
binding.add(ExportSvgAction.KIND);
binding.add(FitToScreenAction.KIND);
binding.add(SourceModelChangedAction.KIND);
binding.add(NavigateToTargetAction.KIND);
binding.add(NavigateToExternalTargetAction.KIND);
binding.add(NavigateToTargetAction.KIND);
binding.add(RequestBoundsAction.KIND);
binding.add(SelectAction.KIND);
binding.add(SelectAllAction.KIND);
binding.add(ServerMessageAction.KIND);
binding.add(ServerStatusAction.KIND);
binding.add(SetBoundsAction.KIND);
binding.add(SetClipboardDataAction.KIND);
binding.add(SetContextActions.KIND);
Expand All @@ -345,10 +356,12 @@ export abstract class DiagramModule extends GLSPModule {
binding.add(SetResolvedNavigationTargetAction.KIND);
binding.add(SetTypeHintsAction.KIND);
binding.add(SetViewportAction.KIND);
binding.add(ServerStatusAction.KIND);
binding.add(TriggerNodeCreationAction.KIND);
binding.add(SourceModelChangedAction.KIND);
binding.add(StartProgressAction.KIND);
binding.add(TriggerEdgeCreationAction.KIND);
binding.add(TriggerNodeCreationAction.KIND);
binding.add(UpdateModelAction.KIND);
binding.add(UpdateProgressAction.KIND);
}

protected bindContextActionsProviderRegistry(): BindingTarget<ContextActionsProviderRegistry> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { Action, RequestModelAction, ServerMessageAction, ServerStatusAction } from '@eclipse-glsp/protocol';
import { Action, RequestModelAction, ServerStatusAction } from '@eclipse-glsp/protocol';
import { inject, injectable } from 'inversify';
import { ActionDispatcher } from '../../actions/action-dispatcher';
import { ActionHandler } from '../../actions/action-handler';
import { Logger } from '../../utils/logger';
import { ProgressMonitor, ProgressService } from '../progress/progress-service';
import { ModelState } from './model-state';
import { ModelSubmissionHandler } from './model-submission-handler';
import { SourceModelStorage } from './source-model-storage';
Expand All @@ -41,27 +42,27 @@ export class RequestModelActionHandler implements ActionHandler {
@inject(ModelSubmissionHandler)
protected submissionHandler: ModelSubmissionHandler;

@inject(ProgressService)
protected progressService: ProgressService;

async execute(action: RequestModelAction): Promise<Action[]> {
this.logger.debug('Execute RequestModelAction:', action);
this.modelState.setAll(action.options ?? {});

this.notifyClient('Model loading in progress');
const progress = this.reportModelLoading('Model loading in progress');
await this.sourceModelStorage.loadSourceModel(action);
// Clear the previous notification.
this.notifyClient();
this.reportModelLoadingFinished(progress);

return this.submissionHandler.submitModel();
}

/**
* Send a message and status notification with the given message to the client.
* An empty message is an indication for the client to clear previously received notifications.
* @param message The message that should be sent to the client
*/
protected notifyClient(message = ''): void {
const severity = message.length > 0 ? 'INFO' : 'NONE';
this.actionDispatcher.dispatchAll(
ServerMessageAction.create(message, { severity }),
ServerStatusAction.create(message, { severity })
);
protected reportModelLoading(message: string): ProgressMonitor {
this.actionDispatcher.dispatch(ServerStatusAction.create(message, { severity: 'INFO' }));
return this.progressService.start(message);
}

protected reportModelLoadingFinished(monitor: ProgressMonitor): void {
this.actionDispatcher.dispatch(ServerStatusAction.create('', { severity: 'NONE' }));
monitor.end();
}
}
74 changes: 74 additions & 0 deletions packages/server/src/common/features/progress/progress-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/********************************************************************************
* Copyright (c) 2023 EclipseSource and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { EndProgressAction, StartProgressAction, UpdateProgressAction } from '@eclipse-glsp/protocol';
import { inject, injectable } from 'inversify';
import * as uuid from 'uuid';
import { ActionDispatcher } from '../../actions/action-dispatcher';

export const ProgressService = Symbol('ProgressService');

/**
* Service for starting and monitoring progress reporting to the client.
*/
export interface ProgressService {
/**
* Start a progress reporting.
* @param title The title shown in the UI for the progress reporting.
* @param options Additional optional options for the progress reporting.
* @returns a monitor to update and end the progress reporting later.
*/
start(title: string, options?: ProgressOptions): ProgressMonitor;
}

/**
* Optional progress reporting options.
*/
export interface ProgressOptions {
/** A message shown in the UI. */
message?: string;
/* The percentage (value range: 0 to 100) to show in the progress reporting. */
percentage?: number;
}

/**
* The monitor of a progress reporting, which can be used to update and end the reporting.
*/
export interface ProgressMonitor {
/**
* Updates an ongoing progress reporting.
* @param options Updated message and/or percentage (value range: 0 to 100).
*/
update(options: { message?: string; percentage?: number }): void;
/**
* Ends an ongoing progress reporting.
*/
end(): void;
}

@injectable()
export class DefaultProgressService implements ProgressService {
@inject(ActionDispatcher)
protected actionDispatcher: ActionDispatcher;

start(title: string, options?: ProgressOptions): ProgressMonitor {
const progressId = uuid.v4();
this.actionDispatcher.dispatch(StartProgressAction.create({ progressId, title, ...options }));
return {
update: updateOptions => this.actionDispatcher.dispatch(UpdateProgressAction.create(progressId, updateOptions)),
end: () => this.actionDispatcher.dispatch(EndProgressAction.create(progressId))
};
}
}
Loading