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

Improve multi-executors RuleTypes flow #26

Merged
merged 8 commits into from
Oct 25, 2017
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion lib/yve-bot.core.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/yve-bot.ui.js

Large diffs are not rendered by default.

40 changes: 37 additions & 3 deletions src/core/__tests__/bot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { sleep, loadYaml } from '@test/utils';
import { YveBot } from '../bot';
import { Store } from '../store';
import { Controller } from '../controller';
import { WaitForUserInput } from '../types';

const OPTS = {
enableWaitForSleep: false,
Expand Down Expand Up @@ -372,13 +373,46 @@ test('ruleTypes with multi executors', async () => {
bot.on('end', onEnd).start();
await sleep();
expect(bot.store.get('executors.testStep.currentIdx')).toEqual(undefined);
bot.hear('answer');
await sleep();
expect(bot.store.get('executors.testStep.currentIdx')).toEqual(undefined);
expect(bot.store.get('output.testStep')).toEqual('answer transformed transformed2');
expect(onEnd).toHaveBeenCalledTimes(1);
});

test('ruleTypes with multi executors and waitForUserInput', async () => {
const rules = loadYaml(`
- message: Hello
name: testStep2
type: MultiStep2
`);
const bot = new YveBot(rules, OPTS);
const onEnd = jest.fn();
bot.types.define('MultiStep2', {
executors: [
{
transform: async (answer) => `${answer} transformed`,
},
{
transform: async (answer) => `${answer} transformed2`,
},
WaitForUserInput,
{
transform: async (answer) => `${answer} transformed3`,
}
]
});

bot.on('end', onEnd).start();
await sleep();
expect(bot.store.get('executors.testStep2.currentIdx')).toEqual(undefined);
bot.hear('first answer');
await sleep();
expect(bot.store.get('executors.testStep.currentIdx')).toEqual(1);
expect(bot.store.get('executors.testStep2.currentIdx')).toEqual(3);
bot.hear('second answer');
await sleep();
expect(bot.store.get('executors.testStep.currentIdx')).toEqual(undefined);
expect(bot.store.get('output.testStep')).toEqual('second answer transformed2');
expect(bot.store.get('executors.testStep2.currentIdx')).toEqual(undefined);
expect(bot.store.get('output.testStep2')).toEqual('second answer transformed3');
expect(onEnd).toHaveBeenCalledTimes(1);
});

Expand Down
11 changes: 10 additions & 1 deletion src/core/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { YveBotOptions, Rule, Answer } from '../types';
import { Store, StoreData } from './store';
import { Controller } from './controller';
import { Actions } from './actions';
import { Types } from './types';
import { Types, WaitForUserInput } from './types';
import { Validators } from './validators';

import * as Exceptions from './exceptions';

function sanitizeRule(rule: Rule): Rule {
if (typeof rule === 'string') {
return { message: rule };
Expand All @@ -23,6 +25,9 @@ function sanitizeRule(rule: Rule): Rule {
}

export class YveBot {
static Exceptions: any;
Copy link
Owner

@andersonba andersonba Oct 25, 2017

Choose a reason for hiding this comment

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

Exceptions instead of any

static WaitForUserInputExecutor: typeof WaitForUserInput;
Copy link
Owner

Choose a reason for hiding this comment

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

try without typeof


private handlers: { [handler: string]: Array<() => any> };
private _rules?: Rule[];

Expand Down Expand Up @@ -121,3 +126,7 @@ export class YveBot {
YveBot.prototype.types = new Types;
YveBot.prototype.actions = new Actions;
YveBot.prototype.validators = new Validators;


YveBot.Exceptions = Exceptions;
Copy link
Owner

Choose a reason for hiding this comment

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

use exceptions to keep the pattern
YveBot.exceptions.RuleNotFound

YveBot.WaitForUserInputExecutor = WaitForUserInput;
Copy link
Owner

Choose a reason for hiding this comment

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

YveBot.executors = new Executors
to be accessed by YveBot.executors.WaitForUserInput

65 changes: 45 additions & 20 deletions src/core/controller.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { Rule, RuleNext, Answer } from '../types';
import { YveBot } from './bot';
import { ValidatorError, InvalidAttributeError, RuleNotFound } from './exceptions';
import {
InvalidAttributeError,
PauseRuleTypeExecutors,
RuleNotFound,
ValidatorError,
} from './exceptions';
import * as utils from './utils';


async function validateAnswer(
answers: Answer | Answer[],
rule: Rule,
Expand Down Expand Up @@ -190,12 +196,36 @@ export class Controller {
return this.bot.store.get(`executors.${rule.name}.currentIdx`) || 0;
}

setRuleExecutorIndex(rule: Rule, value?: any): void {
if (value !== undefined) {
this.bot.store.set(`executors.${rule.name}.currentIdx`, value);
} else {
this.bot.store.unset(`executors.${rule.name}.currentIdx`);
incRuleExecutorIndex(rule: Rule): void {
this.bot.store.set(
`executors.${rule.name}.currentIdx`, this.getRuleExecutorIndex(rule) + 1
);
}

resetRuleExecutorIndex(rule: Rule): void {
this.bot.store.unset(`executors.${rule.name}.currentIdx`);
}

async executeRuleTypeExecutors(rule: Rule, lastAnswer: Answer | Answer[]): Promise<any> {
Copy link
Owner

Choose a reason for hiding this comment

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

Promise<Answer | Answer[]> breaks?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is no guarantee that return will be answer... the return will be whatever transform + validators returns

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

const { bot } = this;
const executorIdx = this.getRuleExecutorIndex(rule);
const executors = bot.types[rule.type].executors;

const executor = executors.slice(executorIdx)[0] || {};
const { transform = (...args) => Promise.resolve(args[0]) } = executor;
const answer = await (
transform(lastAnswer, rule, bot)
.then(answer => validateAnswer(answer, rule, bot, this.getRuleExecutorIndex(rule)))
);

const completed = (this.getRuleExecutorIndex(rule) === executors.length-1);
if (!completed) {
this.incRuleExecutorIndex(rule);
return await this.executeRuleTypeExecutors(rule, answer);
}

this.resetRuleExecutorIndex(rule);
return answer;
}

async receiveMessage(message: Answer | Answer[]): Promise<this> {
Expand All @@ -208,24 +238,19 @@ export class Controller {
}

let answer;
const { executors } = bot.types[rule.type];
const executor = executors[this.getRuleExecutorIndex(rule)] || {};
const { transform = (...args) => Promise.resolve(args[0]) } = executor;
try {
answer = await transform(message, rule, bot).then(answer => validateAnswer(
answer, rule, bot, this.getRuleExecutorIndex(rule)
));

if ( this.getRuleExecutorIndex(rule) < executors.length-1 ) {
this.setRuleExecutorIndex(rule, this.getRuleExecutorIndex(rule)+1);
bot.dispatch('hear');
return this;
}

this.setRuleExecutorIndex(rule);
answer = await this.executeRuleTypeExecutors(rule, message);
} catch (e) {
let expectedError = false;
Copy link
Owner

Choose a reason for hiding this comment

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

[minor] renames to expectedReply

if (e instanceof ValidatorError) {
expectedError = true;
await this.sendMessage(e.message, rule);
} if (e instanceof PauseRuleTypeExecutors) {
expectedError = true;
this.incRuleExecutorIndex(rule);
}

if (expectedError) {
bot.dispatch('hear');
return this;
}
Expand Down
5 changes: 5 additions & 0 deletions src/core/exceptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,8 @@ export function RuleNotFound(name: string, indexes: { [k: string]: number }) {
this.message = `Rule "${name}" not found in indexes`;
this.indexes = indexes;
}

export function PauseRuleTypeExecutors(name: string) {
this.key = 'pauseRuleTypeExecutors';
this.message = `Rule "${name}" should pause exection`;
Copy link
Owner

Choose a reason for hiding this comment

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

typo

}
14 changes: 12 additions & 2 deletions src/core/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import { uniq } from 'lodash';
import { DefineModule } from './module';
import { Rule, Answer } from '../types';
import { PauseRuleTypeExecutors } from './exceptions';
import { Rule, Answer, RuleType, RuleTypeExecutor } from '../types';
import * as utils from './utils';

const types = {

export const WaitForUserInput: RuleTypeExecutor = {
Copy link
Owner

Choose a reason for hiding this comment

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

move it to ./executors

validators: [{
function: (_: Answer, rule: Rule): void => {
throw new PauseRuleTypeExecutors(rule.name);
}
}]
};

const types: { [name: string]: RuleType } = {
Any: { executors: [{}] },

String: {
Expand Down
11 changes: 11 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import { YveBot } from './core/bot';

export interface YveBotOptions {
enableWaitForSleep?: boolean;
rule?: Rule;
}

export interface RuleType {
executors: RuleTypeExecutor[];
}

export interface RuleTypeExecutor {
validators?: RuleValidator[];
transform?: (value: any, rule?: Rule, bot?: YveBot) => Promise<any>;
Copy link
Owner

@andersonba andersonba Oct 25, 2017

Choose a reason for hiding this comment

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

create a type RuleTypeTransform

}

export interface Rule {
name?: string;
type?: string;
Expand Down