Skip to content

Commit

Permalink
#507 Implement dispatchOnNextUpdate for ActionDispatcher (#1)
Browse files Browse the repository at this point in the history
- Implement `dispatchOnNextUpdate` for ActionDispatcher 
- Use  the new API in the `CreateOperationHandler` to automatically select newly created nodes.

Also:
- Consume new @eclipse-glsp/mocha-config package and remove redundant devdependencies
- Update debug config for workflow example
- Add missing CHANGELOG.md file

Contributed on behalf of STMicroelectronics
Closes eclipse-glsp/glsp/issues/507
  • Loading branch information
tortmayr authored Jan 18, 2022
1 parent 5ce3b9a commit d6c44bc
Show file tree
Hide file tree
Showing 11 changed files with 92 additions and 39 deletions.
9 changes: 1 addition & 8 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
/** @type {import('eslint').Linter.Config} */
const year = new Date().getFullYear();
module.exports = {
extends: '@eclipse-glsp',
parserOptions: {
tsconfigRootDir: __dirname,
project: 'tsconfig.json'
},
plugins: ['chai-friendly'],
rules: {
'no-shadow': 'off',
'brace-style': 'off',
'@typescript-eslint/no-this-alias': 'off',
// chai friendly
'no-unused-expressions': 0,
'chai-friendly/no-unused-expressions': 2
'no-shadow': 'off'
}
};
6 changes: 1 addition & 5 deletions .mocharc.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
{
"$schema": "https://json.schemastore.org/mocharc",
"require": ["ts-node/register", "reflect-metadata/Reflect"],
"reporter": "spec",
"color": true,
"watch-files": ["*.ts", "*.tsx"],
"timeout": 2000
"extends": "@eclipse-glsp/mocha-config"
}
20 changes: 15 additions & 5 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,22 @@
"port": 9229
},
{
"name": "Debug workflow example GLSP Server",
"program": "${workspaceFolder}/examples/workflow-server/lib/index.js",
"type": "node",
"request": "launch",
"skipFiles": ["<node_internals>/**"],
"console": "integratedTerminal",
"type": "pwa-node"
"name": "Debug Workflow Example Server",
"program": "${workspaceFolder}/examples/workflow-server/lib/index.js",
"env": {
"NODE_ENV": "development"
},
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/node_modules/@eclipse-glsp/*/lib/**/*.js",
"${workspaceRoot}/node_modules/@eclipse-glsp-examples/*/lib/**/*.js",
"${workspaceRoot}/packages/*/lib/**/*.js"
],
"smartStep": true,
"internalConsoleOptions": "openOnSessionStart",
"outputCapture": "std"
}
]
}
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Eclipse GLSP Server Node Changelog

## v0.10.0- Upcoming

Inception of the Node GLSP Server.
This project provides the Node based server component for the Eclipse Graphical Language Platform (GLSP).
The implementation of this server is aligned with the default Java based [GLSP Server](https://github.com/eclipse-glsp/glsp-server).
The initial initial implementation was contributed on behalf of STMicroelectronics.
3 changes: 0 additions & 3 deletions examples/workflow-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,6 @@
"@eclipse-glsp/server-node": "0.9.0"
},
"devDependencies": {
"@types/node": "12.x",
"reflect-metadata": "^0.1.13",
"ts-node": "^10.4.0",
"typescript": "^3.9.2"
},
"scripts": {
Expand Down
15 changes: 2 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,11 @@
"upgrade:next": "yarn upgrade -p \"@eclipse-glsp.*\" --next ",
"start": "yarn --cwd examples/workflow-server start:server"
},
"resolutions": {
"**/@eclipse-glsp/protocol": "0.9.0-next.e0116720"
},
"devDependencies": {
"@eclipse-glsp/config": "next",
"eslint-plugin-chai-friendly": "^0.7.2",
"@types/node": "12.x",
"lerna": "^4.0.0",
"typescript": "^3.9.2",
"mocha-jenkins-reporter": "^0.4.7",
"mocha": "^9.1.3",
"chai": "^4.3.4",
"sinon": "^12.0.1",
"ts-node": "^10.4.0",
"@types/chai": "^4.2.22",
"@types/mocha": "^9.0.0",
"@types/sinon": "^10.0.6"
"typescript": "^3.9.2"
},
"workspaces": [
"packages/*",
Expand Down
1 change: 0 additions & 1 deletion packages/server-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
},
"devDependencies": {
"@types/fs-extra": "^9.0.13",
"@types/node": "12.x",
"reflect-metadata": "^0.1.13",
"typescript": "^3.9.2"
},
Expand Down
36 changes: 35 additions & 1 deletion packages/server-node/src/actions/action-dispatcher.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { ActionHandler } from './action-handler';
import { ActionHandlerRegistry } from './action-handler-registry';
import * as sinon from 'sinon';
import { expect } from 'chai';
import { Action } from '@eclipse-glsp/protocol';
import { Action, UpdateModelAction } from '@eclipse-glsp/protocol';

function waitSync(timeInMillis: number): void {
const start = Date.now();
Expand Down Expand Up @@ -252,4 +252,38 @@ describe('test DefaultActionDispatcher', () => {
sinon.assert.callOrder(spy_responseHandler1_execute, spy_responseHandler2_execute);
});
});

describe('test dispatch after next update', () => {
it('dispatchAfterNextUpdate', async () => {
// Mock setup
const updateModelAction = new UpdateModelAction({ id: 'newRoot', type: 'myType' });
const intermediateAction = 'intermediate';
const postUpdateAction = 'postUpdate';
const handler = new mock.StubActionHandler([updateModelAction.kind, intermediateAction]);
const postUpdateHandler = new mock.StubActionHandler([postUpdateAction]);

const getHandler = (kind: string): ActionHandler[] => {
if (kind === updateModelAction.kind || kind === intermediateAction) {
return [handler];
} else if (kind === postUpdateAction) {
return [postUpdateHandler];
}

return [];
};
registry_get_stub.callsFake(getHandler);
const spy_postUpdateHandler_execute = sinon.spy(postUpdateHandler, 'execute');

// Test execution
actionDispatcher.dispatchAfterNextUpdate({ kind: postUpdateAction });
expect(spy_postUpdateHandler_execute.called).to.be.false;
await actionDispatcher.dispatch({ kind: intermediateAction });
expect(spy_postUpdateHandler_execute.called).to.be.false;
await actionDispatcher.dispatch(updateModelAction);
expect(spy_postUpdateHandler_execute.calledOnce);
// Check that action does not get dispatched again
await actionDispatcher.dispatch(updateModelAction);
expect(spy_postUpdateHandler_execute.calledOnce);
});
});
});
26 changes: 24 additions & 2 deletions packages/server-node/src/actions/action-dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { Action, flatPush, isAction, isRequestAction, MaybeArray, ResponseAction } from '@eclipse-glsp/protocol';
import { Action, flatPush, isAction, isRequestAction, isUpdateModelAction, MaybeArray, ResponseAction } from '@eclipse-glsp/protocol';
import { inject, injectable } from 'inversify';
import { ActionHandler } from '../actions/action-handler';
import { ClientActionKinds, ClientId } from '../di/service-identifiers';
Expand Down Expand Up @@ -46,6 +46,16 @@ export interface ActionDispatcher {
*/
dispatchAll(actions: Action[]): Promise<void>;
dispatchAll(...actions: Action[]): Promise<void>;

/**
* Processes all given actions, by dispatching them to the corresponding handlers, after the next model update.
* The given actions are queued until the next model update cycle has been completed i.e. an
* `UpdateModelAction` has been dispatched and processed by this action dispatcher.
*
* @param actions The actions that should be dispatched after the next model update
*/
dispatchAfterNextUpdate(actions: Action[]): void;
dispatchAfterNextUpdate(...actions: Action[]): void;
}

@injectable()
Expand All @@ -63,6 +73,7 @@ export class DefaultActionDispatcher extends Disposable implements ActionDispatc
protected clientId: string;

protected actionQueue = new PromiseQueue();
protected postUpdateQueue: Action[] = [];

dispatch(action: Action): Promise<void> {
// Dont queue actions that are just delegated to the client
Expand All @@ -85,7 +96,12 @@ export class DefaultActionDispatcher extends Disposable implements ActionDispatc
responses.push(...response);
}

return this.dispatchResponses(responses);
if (isUpdateModelAction(action) && this.postUpdateQueue.length > 0) {
responses.push(...this.postUpdateQueue);
this.postUpdateQueue = [];
}

await this.dispatchResponses(responses);
}

protected async executeHandler(handler: ActionHandler, request: Action): Promise<Action[]> {
Expand All @@ -111,6 +127,12 @@ export class DefaultActionDispatcher extends Disposable implements ActionDispatc
return Promise.all(flat.map(action => this.dispatch(action))).then(() => Promise.resolve());
}

dispatchAfterNextUpdate(...actions: MaybeArray<Action>[]): void {
if (actions.length !== 0) {
flatPush(this.postUpdateQueue, actions);
}
}

doDispose(): void {
this.actionQueue.clear();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
CreateOperation,
Operation,
Point,
SelectAction,
TriggerEdgeCreationAction,
TriggerElementCreationAction,
TriggerNodeCreationAction
Expand Down Expand Up @@ -84,8 +85,8 @@ export abstract class CreateNodeOperationHandler extends CreateOperationHandler
if (element) {
container.children.push(element);
element.parent = container;
this.actionDispatcher.dispatchAfterNextUpdate(new SelectAction(), new SelectAction([element.id]));
}
// TODO: Dispatch Select Action after next update
}

getContainer(operation: CreateNodeOperation): GModelElement | undefined {
Expand Down
4 changes: 4 additions & 0 deletions packages/server-node/src/test/mock-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ export class StubCreateEdgeOperationHandler extends CreateEdgeOperationHandler {
}

export class StubActionDispatcher implements ActionDispatcher {
dispatchAfterNextUpdate(...actions: MaybeArray<Action[]>): void {
// NO-OP
}

dispatch(action: Action): Promise<void> {
return Promise.resolve();
}
Expand Down

0 comments on commit d6c44bc

Please sign in to comment.