Skip to content

Commit

Permalink
run actions in options. closes #165
Browse files Browse the repository at this point in the history
  • Loading branch information
andersonba committed Nov 29, 2018
1 parent 2f22ed7 commit 4cf304b
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 20 deletions.
18 changes: 18 additions & 0 deletions docs/custom-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,21 @@ bot.actions.define('welcomeEmail', async (actionOptions, bot) => {
- title: Welcome to our site!
templateName: first-step-done.html
```
## Define actions in options
You can run `postActions` after user chooses any option in types `SingleChoice` and `MultipleChoice`.

```yaml
- message: How do you want receive news?
type: MultipleChoice
options:
- label: E-mail
postActions:
- setNewsletterMethod: email
- label: SMS
postActions:
- setNewsletterMethod: sms
```

_If the user select all options, it will perform all actions._
48 changes: 48 additions & 0 deletions src/core/__tests__/bot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,54 @@ test('running actions', async () => {
expect(postStringAct).toBeCalledWith(true, rules[0], bot);
});

test('running actions in option', async () => {
const act = jest.fn();
const stringAct = jest.fn();
const preAct = jest.fn();
const preStringAct = jest.fn();
const postAct = jest.fn();
const postStringAct = jest.fn();
const rules = loadYaml(`
- message: Hello
type: SingleChoice
options:
- label: Option
value: okay
actions:
- testAction: false
- unknown: 10
- testStringWay
preActions:
- testPreAction: true
- testPreStringWay
postActions:
- testPostAction: true
- testPostStringWay
`);
const bot = new YveBot(rules, OPTS);
bot.actions.define('testAction', act);
bot.actions.define('testStringWay', stringAct);
bot.actions.define('testPreAction', preAct);
bot.actions.define('testPreStringWay', preStringAct);
bot.actions.define('testPostAction', postAct);
bot.actions.define('testPostStringWay', postStringAct);
bot.start();

await sleep();
expect(act).not.toHaveBeenCalled();
expect(preAct).not.toHaveBeenCalled();
expect(stringAct).not.toHaveBeenCalled();
expect(preAct).not.toHaveBeenCalled();
expect(preStringAct).not.toHaveBeenCalled();
expect(postAct).not.toBeCalled();
expect(postStringAct).not.toBeCalled();

bot.hear('okay');
await sleep();
expect(postAct).toBeCalledWith(true, rules[0], bot);
expect(postStringAct).toBeCalledWith(true, rules[0], bot);
});

test('end bot when last message has skip', async () => {
const rules = loadYaml(`
- message: Hello
Expand Down
2 changes: 1 addition & 1 deletion src/core/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export class Controller {
}

// run post-actions
await utils.runActions(bot, rule, 'postActions');
await utils.runActions(bot, rule, 'postActions', answer);

if (rule.type === 'PassiveLoop') {
return this;
Expand Down
16 changes: 12 additions & 4 deletions src/core/sanitizers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,18 @@ export function sanitizeRule(input: IRule): IRule {

// string way
['actions', 'preActions', 'postActions', 'validators'].forEach(key => {
if (rule[key] && rule[key].length) {
rule[key] = rule[key].map(
k => (typeof k === 'string' ? { [k]: true } : k)
);
function sanitizeInObject(obj) {
if (obj[key] && obj[key].length) {
obj[key] = obj[key].map(
k => (typeof k === 'string' ? { [k]: true } : k)
);
}
}

sanitizeInObject(rule);

if (rule.options && rule.options.length) {
rule.options.forEach(opt => sanitizeInObject(opt));
}
});

Expand Down
44 changes: 29 additions & 15 deletions src/core/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,23 +109,26 @@ export function getReplyMessage(
rule: IRule,
answers: Answer | Answer[]
): string | null {
const { replyMessage } = rule;
if (!rule.options.length) {
return replyMessage;
return rule.replyMessage;
}
let opt;
// multiple
if (answers instanceof Array) {
[opt = null] = answers
.map(a => findOptionByAnswer(rule.options, a))
.filter(o => o.replyMessage);
const [option = null] = getOptionsFromAnswer(rule, answers).filter(
opt => opt && opt.replyMessage
);
if (option && option.replyMessage) {
return option.replyMessage;
}
// single
opt = findOptionByAnswer(rule.options, answers);
if (opt && opt.replyMessage) {
return opt.replyMessage;
return rule.replyMessage;
}

export function getOptionsFromAnswer(
rule: IRule,
answer: Answer | Answer[]
): IRuleOption[] {
if (answer instanceof Array) {
return answer.map(ans => findOptionByAnswer(rule.options, ans));
}
return replyMessage;
return [findOptionByAnswer(rule.options, answer)];
}

export function compileMessage(bot: YveBot, message: string): string {
Expand Down Expand Up @@ -161,10 +164,21 @@ export function compileMessage(bot: YveBot, message: string): string {
export function runActions(
bot: YveBot,
rule: IRule,
prop: RuleActionProp
prop: RuleActionProp,
answer?: Answer | Answer[]
): Promise<any> {
let actions = rule[prop] || [];

// find actions inside of option
const options = (answer !== undefined
? getOptionsFromAnswer(rule, answer)
: []
).filter(opt => opt && opt[prop] && opt[prop].length > 0);
if (options.length) {
actions = [].concat.apply([], options.map(opt => opt[prop]));
}

const output = bot.store.output();
const actions = rule[prop] || [];
return Promise.all(
actions.map(async action => {
return Promise.all(
Expand Down

0 comments on commit 4cf304b

Please sign in to comment.