Skip to content

Commit

Permalink
Merge pull request #36 from Accenture/actions-definition
Browse files Browse the repository at this point in the history
Actions definition
  • Loading branch information
misyak authored Nov 25, 2016
2 parents 4a81b14 + 2799cc9 commit 0e6faa6
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 10 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,8 @@ Feature of Alexia that helps you to control flow of the intents. To understand i

By defining the action you enable transition from one intent to another. When no actions are specified, every intent transition is allowed.

Action properties `from` and `to` can be defined as `string` (one intent), `array` (multiple intents) or `'*'` (all intents).

Each action could have condition to check whether the transition should be handled or the fail method should be invoked. If no fail method is defined `app.defaultActionFail()` is invoked when condition of handling is not met or the action (transition) is not defined.

```javascript
Expand All @@ -335,7 +337,7 @@ app.action({
to: 'intent1'
});

// Allow transition from start intent to `intent2`.
// Allow transition from `@start` intent to `intent2`.
app.action({
from: '@start',
to: 'intent2'
Expand All @@ -349,6 +351,12 @@ app.action({
fail: (slots, attrs) => 'Sorry, your pin is invalid'
});

// Allow transition from `intent2` to `intent3` and also `intent4`.
app.action({
from: 'intent2',
to: ['intent3', 'intent4']
});

// Set default fail handler
app.defaultActionFail(() => 'Sorry, your request is invalid');
```
Expand Down
44 changes: 37 additions & 7 deletions src/create-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,17 +129,30 @@ module.exports = (name, options) => {
* Creates action
* @param {string} action - Action object
* @param {string} action.from - Name of the intent to allow transition from
* @param {string} action.to - Name of th eintent to allow transition to
* @param {string} action.to - Name of the intent to allow transition to
* @param {function} action.if - Function returning boolean whether this transition should be handled.
* @param {function} action.fail - Handler to be called if `action.if` returned `false`
*/
app.action = (action) => {
app.actions.push({
from: typeof (action.from) === 'string' ? action.from : action.from.name,
to: typeof (action.to) === 'string' ? action.to : action.to.name,
if: action.if,
fail: action.fail
});
if (Array.isArray(action.from)) {
action.from.forEach(fromItem => {
if (Array.isArray(action.to)) {
action.to.forEach(toItem => {
addAction(app.actions, fromItem, toItem, action.if, action.fail);
});
} else {
addAction(app.actions, fromItem, action.to, action.if, action.fail);
}
});
} else {
if (Array.isArray(action.to)) {
action.to.forEach(toItem => {
addAction(app.actions, action.from, toItem, action.if, action.fail);
});
} else {
addAction(app.actions, action.from, action.to, action.if, action.fail);
}
}
};

/**
Expand Down Expand Up @@ -191,3 +204,20 @@ module.exports = (name, options) => {

return app;
};

/**
* Adds action to app's actions array
* @param {object} actions - Actions object of alexia app
* @param {string} from - Name of the intent to allow transition from
* @param {string} to - Name of the intent to allow transition to
* @param {function} condition - Function returning boolean whether this transition should be handled
* @param {function} fail - Handler to be called if `condition` returned `false`
*/
const addAction = (actions, from, to, condition, fail) => {
actions.push({
from: typeof (from) === 'string' ? from : from.name,
to: typeof (to) === 'string' ? to : to.name,
if: condition,
fail: fail
});
};
4 changes: 2 additions & 2 deletions src/handle-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const callHandler = (handler, slots, attrs, app, data, done) => {
};

/**
* Checks for `actions` presence to help us with Alexa coznversation workflow configuration
* Checks for `actions` presence to help us with Alexa conversation workflow configuration
*
* 1) no actions: just call the intent.handler method without any checks
* 2) with actions: check if action for current intent transition is found
Expand Down Expand Up @@ -165,7 +165,7 @@ const createCardObject = (card) => {
*/
const getShouldEndSession = (intentOptions, appOptions) => {
if (!intentOptions || intentOptions.end === undefined) {
if(!appOptions || appOptions.shouldEndSessionByDefault === undefined){
if (!appOptions || appOptions.shouldEndSessionByDefault === undefined) {
return true;
} else {
return appOptions.shouldEndSessionByDefault;
Expand Down
36 changes: 36 additions & 0 deletions test/contacts.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use strict';
const expect = require('chai').expect;
const app = require('./test-apps/contacts-app');
const alexia = require('..');

describe('action app handler', () => {

it('should handle OpenContactList -> NewContact and return correct outputSpeech', () => {
const request = alexia.createIntentRequest('NewContact', null, {previousIntent: 'OpenContactList'});
app.handle(request, (response) => {
expect(response.response.outputSpeech.text).to.equal('Please insert value for name or phone number of contact.');
});
});

it('should handle NewContact -> SetName and return correct outputSpeech', () => {
const request = alexia.createIntentRequest('SetName', null, {previousIntent: 'NewContact'});
app.handle(request, (response) => {
expect(response.response.outputSpeech.text).to.equal('Name was saved.');
});
});

it('should handle SetName -> CloseContactList and return correct outputSpeech', () => {
const request = alexia.createIntentRequest('CloseContactList', null, {previousIntent: 'SetName'});
app.handle(request, (response) => {
expect(response.response.outputSpeech.text).to.equal('See you next time.');
});
});

it('should not handle OpenContactList -> SetName and return correct outputSpeech', () => {
const request = alexia.createIntentRequest('SetName', null, {previousIntent: 'OpenContactList'});
app.handle(request, (response) => {
expect(response.response.outputSpeech.text).to.equal('Sorry, your command is invalid');
});
});

});
45 changes: 45 additions & 0 deletions test/test-apps/contacts-app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// short app for testing action's transitions saved in arrays
'use strict';
const alexia = require('../..');
const app = alexia.createApp('ContactsApp');

app.intent('OpenContactList', 'Open contact list', () => {
return 'You can list your contacts, add new one or change already saved ones.';
});

app.intent('NewContact', 'Create new contact', () => {
return 'Please insert value for name or phone number of contact.';
});

app.intent('ChangeContact', 'Change name for Glogo contact', () => {
return 'Please insert new value for name or phone number.';
});

app.intent('SetName', 'Set name to Misyak', () => {
return 'Name was saved.';
});

app.intent('SetNumber', 'Set number to {number:NUMBER}', () => {
return 'Number was saved.';
});

app.intent('CloseContactList', 'Close contact list', () => {
return 'See you next time.';
});

app.action({
from: 'OpenContactList',
to: ['NewContact', 'ChangeContact']
});

app.action({
from: ['NewContact', 'ChangeContact'],
to: ['SetName', 'SetNumber']
});

app.action({
from: ['NewContact', 'ChangeContact', 'SetName', 'SetNumber'],
to: 'CloseContactList'
});

module.exports = app;

0 comments on commit 0e6faa6

Please sign in to comment.