Skip to content

Commit 63971fc

Browse files
authored
GLSP-211: Revise TypeHints and server side feedback for creation acti… (#60)
* GLSP-211: Revise TypeHints and server side feedback for creation actions (#210) - Add action handler for `RequestEdgeCheckAction` and `EdgeCreationChecker` API - Add a optional `EdgeCreationchecker` component that can be implemented by adopters to provide dynamic typehints - Adapt workflow example to use dynamic edge hints for weighted edges Also: Update eslint config to show warnings if we import from internal index instead of the relative path to the file Fixes eclipse-glsp/glsp/issues/211
1 parent 342c713 commit 63971fc

File tree

11 files changed

+342
-185
lines changed

11 files changed

+342
-185
lines changed

.eslintrc.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ module.exports = {
1616
name: 'sprotty-protocol',
1717
message:
1818
"The sprotty-protocol default exports are customized and reexported by GLSP. Please import from '@eclipse-glsp/protocol' instead"
19-
}
19+
},
20+
'.',
21+
'..',
22+
'../..'
2023
]
2124
}
2225
};

examples/workflow-server/src/common/workflow-diagram-configuration.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ import {
1818
DiagramConfiguration,
1919
GCompartment,
2020
GEdge,
21-
getDefaultMapping,
2221
GLabel,
2322
GModelElementConstructor,
24-
ServerLayoutKind
23+
ServerLayoutKind,
24+
getDefaultMapping
2525
} from '@eclipse-glsp/server';
2626
import { injectable } from 'inversify';
2727
import { ActivityNode, Category, TaskNode } from './graph-extension';
@@ -58,15 +58,7 @@ export class WorkflowDiagramConfiguration implements DiagramConfiguration {
5858
deletable: true,
5959
resizable: true,
6060
reparentable: true,
61-
containableElementTypeIds: [
62-
types.DECISION_NODE,
63-
types.MERGE_NODE,
64-
types.FORK_NODE,
65-
types.JOIN_NODE,
66-
types.AUTOMATED_TASK,
67-
types.MANUAL_TASK,
68-
types.CATEGORY
69-
]
61+
containableElementTypeIds: [types.TASK, types.ACTIVITY_NODE, types.CATEGORY]
7062
}
7163
];
7264
}
@@ -79,8 +71,9 @@ export class WorkflowDiagramConfiguration implements DiagramConfiguration {
7971
repositionable: true,
8072
deletable: true,
8173
routable: true,
82-
sourceElementTypeIds: [types.DECISION_NODE],
83-
targetElementTypeIds: [types.MANUAL_TASK, types.AUTOMATED_TASK, types.FORK_NODE, types.JOIN_NODE]
74+
dynamic: true,
75+
sourceElementTypeIds: [types.ACTIVITY_NODE],
76+
targetElementTypeIds: [types.TASK, types.ACTIVITY_NODE]
8477
}
8578
];
8679
}

examples/workflow-server/src/common/workflow-diagram-module.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
ContextEditValidator,
2121
ContextMenuItemProvider,
2222
DiagramConfiguration,
23+
EdgeCreationChecker,
2324
GLSPServer,
2425
GModelDiagramModule,
2526
InstanceMultiBinding,
@@ -55,6 +56,7 @@ import { EditTaskOperationHandler } from './taskedit/edit-task-operation-handler
5556
import { TaskEditContextActionProvider } from './taskedit/task-edit-context-provider';
5657
import { TaskEditValidator } from './taskedit/task-edit-validator';
5758
import { WorkflowDiagramConfiguration } from './workflow-diagram-configuration';
59+
import { WorkflowEdgeCreationChecker } from './workflow-edge-creation-checker';
5860
import { WorkflowGLSPServer } from './workflow-glsp-server';
5961
import { WorkflowPopupFactory } from './workflow-popup-factory';
6062

@@ -133,4 +135,8 @@ export class WorkflowDiagramModule extends GModelDiagramModule {
133135
super.configureContextEditValidators(binding);
134136
binding.add(TaskEditValidator);
135137
}
138+
139+
protected override bindEdgeCreationChecker(): BindingTarget<EdgeCreationChecker> | undefined {
140+
return WorkflowEdgeCreationChecker;
141+
}
136142
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/********************************************************************************
2+
* Copyright (c) 2023 EclipseSource and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the Eclipse
10+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
* with the GNU Classpath Exception which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
********************************************************************************/
16+
import { EdgeCreationChecker, GModelElement } from '@eclipse-glsp/server/';
17+
import { injectable } from 'inversify';
18+
import { TaskNode } from './graph-extension';
19+
import { ModelTypes } from './util/model-types';
20+
21+
@injectable()
22+
export class WorkflowEdgeCreationChecker implements EdgeCreationChecker {
23+
isValidSource(edgeType: string, sourceElement: GModelElement): boolean {
24+
return edgeType !== ModelTypes.WEIGHTED_EDGE || sourceElement.type === ModelTypes.DECISION_NODE;
25+
}
26+
isValidTarget(edgeType: string, sourceElement: GModelElement, targetElement: GModelElement): boolean {
27+
return (
28+
targetElement instanceof TaskNode || targetElement.type === ModelTypes.FORK_NODE || targetElement.type === ModelTypes.JOIN_NODE
29+
);
30+
}
31+
}

packages/server/src/common/di/diagram-module.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import { ClientActionForwarder } from '../actions/client-action-handler';
2121
import { CommandStack, DefaultCommandStack } from '../command/command-stack';
2222
import { UndoRedoActionHandler } from '../command/undo-redo-action-handler';
2323
import { DiagramConfiguration } from '../diagram/diagram-configuration';
24-
import { RequestTypeHintsActionHandler } from '../diagram/request-type-hints-action-handler';
2524
import { CommandPaletteActionProvider } from '../features/contextactions/command-palette-action-provider';
2625
import { ContextActionsProvider } from '../features/contextactions/context-actions-provider';
2726
import { ContextActionsProviderRegistry } from '../features/contextactions/context-actions-provider-registry';
@@ -57,6 +56,9 @@ import { ResolveNavigationTargetsActionHandler } from '../features/navigation/re
5756
import { PopupModelFactory } from '../features/popup/popup-model-factory';
5857
import { RequestPopupModelActionHandler } from '../features/popup/request-popup-model-action-handler';
5958
import { DefaultProgressService, ProgressService } from '../features/progress/progress-service';
59+
import { EdgeCreationChecker } from '../features/type-hints/edge-creation-checker';
60+
import { RequestCheckEdgeActionHandler } from '../features/type-hints/request-check-edge-action-handler';
61+
import { RequestTypeHintsActionHandler } from '../features/type-hints/request-type-hints-action-handler';
6062
import { ModelValidator } from '../features/validation/model-validator';
6163
import { RequestMarkersHandler } from '../features/validation/request-markers-handler';
6264
import { CompoundOperationHandler } from '../operations/compound-operation-handler';
@@ -121,6 +123,7 @@ import {
121123
* - {@link ClientSessionInitializer} as {@link ClassMultiBinding<ClientSessionInitializer>}
122124
* - {@link PopupModelFactory} as optional binding
123125
* - {@link LayoutEngine} as optional binding
126+
* - {@link EdgeCreationChecker} as optional binding
124127
*/
125128

126129
export abstract class DiagramModule extends GLSPModule {
@@ -172,7 +175,7 @@ export abstract class DiagramModule extends GLSPModule {
172175
applyBindingTarget(context, CommandStack, this.bindCommandStack()).inSingletonScope();
173176

174177
// Navigation
175-
applyOptionalBindingTarget(context, NavigationTargetResolver, this.bindNavigationTargetResolver());
178+
applyOptionalBindingTarget(context, NavigationTargetResolver, this.bindNavigationTargetResolver())?.inSingletonScope();
176179
this.configureMultiBinding(new MultiBinding<NavigationTargetProvider>(NavigationTargetProviders), binding =>
177180
this.configureNavigationTargetProviders(binding)
178181
);
@@ -189,8 +192,9 @@ export abstract class DiagramModule extends GLSPModule {
189192
this.configureClientSessionInitializers(binding)
190193
);
191194
applyBindingTarget(context, ProgressService, this.bindProgressService()).inSingletonScope();
192-
applyOptionalBindingTarget(context, PopupModelFactory, this.bindPopupModelFactory());
193-
applyOptionalBindingTarget(context, LayoutEngine, this.bindLayoutEngine?.());
195+
applyOptionalBindingTarget(context, PopupModelFactory, this.bindPopupModelFactory())?.inSingletonScope();
196+
applyOptionalBindingTarget(context, LayoutEngine, this.bindLayoutEngine())?.inSingletonScope();
197+
applyOptionalBindingTarget(context, EdgeCreationChecker, this.bindEdgeCreationChecker())?.inSingletonScope();
194198
}
195199

196200
configureClientSessionInitializers(binding: MultiBinding<ClientSessionInitializer>): void {
@@ -203,6 +207,7 @@ export abstract class DiagramModule extends GLSPModule {
203207
binding.add(RequestContextActionsHandler);
204208
binding.add(RequestTypeHintsActionHandler);
205209
binding.add(OperationActionHandler);
210+
binding.add(RequestCheckEdgeActionHandler);
206211
binding.add(RequestMarkersHandler);
207212
binding.add(RequestPopupModelActionHandler);
208213
binding.add(RequestEditValidationHandler);
@@ -331,16 +336,19 @@ export abstract class DiagramModule extends GLSPModule {
331336
protected bindModelValidator(): BindingTarget<ModelValidator> | undefined {
332337
return undefined;
333338
}
339+
334340
protected bindLabelEditValidator(): BindingTarget<LabelEditValidator> | undefined {
335341
return undefined;
336342
}
337343

338344
protected bindToolPaletteItemProvider(): BindingTarget<ToolPaletteItemProvider> | undefined {
339345
return DefaultToolPaletteItemProvider;
340346
}
347+
341348
protected bindCommandPaletteActionProvider(): BindingTarget<CommandPaletteActionProvider> | undefined {
342349
return undefined;
343350
}
351+
344352
protected bindContextMenuItemProvider(): BindingTarget<ContextMenuItemProvider> | undefined {
345353
return undefined;
346354
}
@@ -352,7 +360,12 @@ export abstract class DiagramModule extends GLSPModule {
352360
protected bindPopupModelFactory(): BindingTarget<PopupModelFactory> | undefined {
353361
return undefined;
354362
}
363+
355364
protected bindLayoutEngine(): BindingTarget<LayoutEngine> | undefined {
356365
return undefined;
357366
}
367+
368+
protected bindEdgeCreationChecker(): BindingTarget<EdgeCreationChecker> | undefined {
369+
return undefined;
370+
}
358371
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/********************************************************************************
2+
* Copyright (c) 2023 EclipseSource and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the Eclipse
10+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
* with the GNU Classpath Exception which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
********************************************************************************/
16+
17+
import { GModelElement } from '@eclipse-glsp/graph';
18+
19+
/**
20+
* Optional service used to check the validity of an edge being created. Used in combination with `dynamic`
21+
* `EdgeTypeHints`.
22+
* A dynamic edge type hint is used for cases where a plain list of allowed source and target element ids is not enough
23+
* to determine wether an edge being created is valid. In this cases the client will query the server to determine wether the edge
24+
* is valid. The `EdgeCreationChecker` then checks the given edge information and returns wether the edge being created is
25+
* valid.
26+
*/
27+
export interface EdgeCreationChecker {
28+
/**
29+
* Checks wether the given source element for an edge being created is valid i.e. if the
30+
* given source is and allowed source element for the given edge type.
31+
*
32+
* @returns `true` if the edge source is valid, `false` otherwise
33+
*/
34+
isValidSource(edgeType: string, sourceElement: GModelElement): boolean;
35+
36+
/**
37+
* Checks wether the given information for an edge being created is valid i.e. if the
38+
* given target is an allowed target for the given source and edge type.
39+
*
40+
* @return `true` if the edge target is valid, `false` otherwise
41+
*/
42+
isValidTarget(edgeType: string, sourceElement: GModelElement, targetElement: GModelElement): boolean;
43+
}
44+
export const EdgeCreationChecker = Symbol('EdgeCreationChecker');
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/********************************************************************************
2+
* Copyright (c) 2023 EclipseSource and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the Eclipse
10+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
* with the GNU Classpath Exception which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
********************************************************************************/
16+
import { Action, CheckEdgeResultAction, MaybePromise, RequestCheckEdgeAction } from '@eclipse-glsp/protocol';
17+
import { inject, injectable, optional } from 'inversify';
18+
import { ActionHandler } from '../../actions/action-handler';
19+
import { DiagramConfiguration } from '../../diagram/diagram-configuration';
20+
import { GLSPServerError, getOrThrow } from '../../utils/glsp-server-error';
21+
import { ModelState } from '../model/model-state';
22+
import { EdgeCreationChecker } from './edge-creation-checker';
23+
24+
@injectable()
25+
export class RequestCheckEdgeActionHandler implements ActionHandler {
26+
@inject(ModelState)
27+
protected modelState: ModelState;
28+
29+
@inject(DiagramConfiguration)
30+
protected diagramConfiguration: DiagramConfiguration;
31+
32+
@inject(EdgeCreationChecker)
33+
@optional()
34+
protected edgeCreationChecker?: EdgeCreationChecker;
35+
36+
readonly actionKinds: string[] = [RequestCheckEdgeAction.KIND];
37+
38+
execute(action: RequestCheckEdgeAction): MaybePromise<Action[]> {
39+
const hasDynamicHint = this.diagramConfiguration.edgeTypeHints.some(hint => hint.elementTypeId === action.edgeType && hint.dynamic);
40+
const { edgeType, sourceElementId, targetElementId } = action;
41+
const isValid = this.edgeCreationChecker && hasDynamicHint ? this.validate(action) : true;
42+
43+
return [CheckEdgeResultAction.create({ edgeType, isValid, sourceElementId, targetElementId })];
44+
}
45+
46+
protected validate(action: RequestCheckEdgeAction): boolean {
47+
const sourceElement = getOrThrow(
48+
this.modelState.index.get(action.sourceElementId),
49+
'Invalid `RequestCheckEdgeTargetAction`!. Could not find a source element with id: ' + action.sourceElementId
50+
);
51+
const targetElement = action.targetElementId ? this.modelState.index.get(action.targetElementId) : undefined;
52+
53+
if (action.targetElementId && !targetElement) {
54+
throw new GLSPServerError(
55+
'Invalid `RequestCheckEdgeTargetAction`! Could not find a target element with id: ' + action.targetElementId
56+
);
57+
}
58+
return targetElement
59+
? this.edgeCreationChecker!.isValidTarget(action.edgeType, sourceElement, targetElement)
60+
: this.edgeCreationChecker!.isValidSource(action.edgeType, sourceElement);
61+
}
62+
}

packages/server/src/common/diagram/request-type-hints-action-handler.spec.ts renamed to packages/server/src/common/features/type-hints/request-type-hints-action-handler.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
import { EdgeTypeHint, RequestTypeHintsAction, SetTypeHintsAction, ShapeTypeHint } from '@eclipse-glsp/protocol';
1717
import { expect } from 'chai';
1818
import { Container, ContainerModule } from 'inversify';
19-
import * as mock from '../test/mock-util';
20-
import { DiagramConfiguration } from './diagram-configuration';
19+
import { DiagramConfiguration } from '../../diagram/diagram-configuration';
20+
import * as mock from '../../test/mock-util';
2121
import { RequestTypeHintsActionHandler } from './request-type-hints-action-handler';
2222

2323
describe('test RequestTypeHintsActionHandler', () => {

packages/server/src/common/diagram/request-type-hints-action-handler.ts renamed to packages/server/src/common/features/type-hints/request-type-hints-action-handler.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,12 @@
1515
********************************************************************************/
1616
import { Action, MaybePromise, RequestTypeHintsAction, SetTypeHintsAction } from '@eclipse-glsp/protocol';
1717
import { inject, injectable } from 'inversify';
18-
import { ActionHandler } from '../actions/action-handler';
19-
import { DiagramConfiguration } from './diagram-configuration';
18+
import { ActionHandler } from '../../actions/action-handler';
19+
import { DiagramConfiguration } from '../../diagram/diagram-configuration';
2020

2121
@injectable()
2222
export class RequestTypeHintsActionHandler implements ActionHandler {
2323
@inject(DiagramConfiguration) protected diagramConfiguration: DiagramConfiguration;
24-
static KINDS = [RequestTypeHintsAction.KIND];
2524

2625
execute(action: RequestTypeHintsAction): MaybePromise<Action[]> {
2726
return [

packages/server/src/common/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ export * from './di/multi-binding';
3333
export * from './di/server-module';
3434
export * from './di/service-identifiers';
3535
export * from './diagram/diagram-configuration';
36-
export * from './diagram/request-type-hints-action-handler';
3736
export * from './features/clipboard/request-clipboard-data-action-handler';
3837
export * from './features/contextactions/command-palette-action-provider';
3938
export * from './features/contextactions/context-actions-provider';
@@ -65,6 +64,8 @@ export * from './features/navigation/request-navigation-targets-action-handler';
6564
export * from './features/navigation/resolve-navigation-targets-action-handler';
6665
export * from './features/popup/popup-model-factory';
6766
export * from './features/popup/request-popup-model-action-handler';
67+
export * from './features/type-hints/edge-creation-checker';
68+
export * from './features/type-hints/request-type-hints-action-handler';
6869
export * from './features/validation/model-validator';
6970
export * from './features/validation/request-markers-handler';
7071
export * from './gmodel/index';

0 commit comments

Comments
 (0)