おめでとうございます、かしゆかの誕生日です、こんにちは。
社内でも見られてしまうものとしては、冒頭の文章は失敗でしかない気もしますが、気にしないで Amazon echo で遊んでみた記録を残しておきます。
前提条件・必要なもの
- Salesforce アカウント (Developer Editionで良いでしょう。本番で試すのはDEのあとでどうぞ)
- Amazon Echo (実機で試す場合。なくても、テストだけはできます)
- Amazon Developer のアカウント (Alexa で登録したメアドと同じアカウント)
- AWSアカウント (lambda を利用する場合)
今回のシーケンス
アーキテクチャ図を書いたらシンプルすぎてかわいそうになったので、動的シーケンスっぽい図にしてみました。
大したことはやっていなくて、Alexa Skill Kit でインテントを取得して、lambda上の functionをコールさせています。
lambdaからは、Salesforce へ RESTコールして Apexを呼び出します。Apex はレポートAPIを利用して、商談の売上データをもとに集計した、表形式の金額のサマリー情報を取得し、lambda 側へ返却します。
lambdaはEchoで読み上げるための文章を作成して、Alexa Skill Kit へ戻すという流れです。
lambda部分は、Herokuのアプリを実行させることもできますが、なんとなくlambdaをちゃんと使ってみたかったのと、一般的っぽいのでそちらを採用しました。今度はHerokuでも試してみます。
開発のおおまかな手順
では、実際に Alexa Skill Kit や lambdaでの実装手順はどうなるかというと、次の2つで済むケースが多いでしょう。今回は、レポートの集計データを利用したかったので、Apexの開発も行いましたが、Salesforce側での開発が必要なく、RESTで取得できるものであれば、Salesforce側は特にいじる必要はないです。Chatter へ投稿させる程度なら、Chatter APIコールすればいいですしね。
- Developer Console で Alexa Skill Kit を完全に準備すること
- lambda か外部の Web上で稼働するアプリを開発し、Alexa Skill からそのアプリへ連携させること
では、具体的な開発の流れは次の手順は、というと次のとおりになります。今回はApex開発がありましたので、2. にはその手順が含まれています。
- Developer Console で Alexa Skill Kitの基本情報を準備する
- Salesforce 上で、レポートの集計データを取得する Apex class を開発し、REST で公開する
- lambda または Web上で稼働する Alexa用アプリを開発する
- Developer Console 上の Alexa Skill Kitからアプリを連携
- Developer Console 上で Alexa Skill Kitのアプリをテスト
- Developer Console で、βテストに必要な次の項目をすべて埋める
- 公開情報
- プライバシーとコンプライアンス
- Skill Beta Testing で、Amazon Echo とひも付けられたメールアドレスを、テスターとして追加し、「アクティブ」化させる
細かい手順については、たくさんの公開された情報があるので、そちらを参照していただければと思いますが、とにかく、Amazon Echo の実機でテストを行うには、Alexa Skill Kit のすべての項目をうめて、申請直前までできあがっている必要があります。
各手順での注意・留意事項
Developer Console で Alexa Skill を作成する場合の注意事項
先にも述べたとおり、実機でテストを行うためには、すべての情報を準備する必要があります。次の各項目すべての設定を完了させると、初めて、Echo 実機でテストをする準備ができあがります。地味に厄介なのは、アイコンがないといけない点です。108x108 と 512x512 のアイコン画像が必要なので、がんばって準備しましょう。
- Skill Information (スキル情報)
- Interaction Model (対話モデル)
- Configuration (設定)
- Test (テスト)
- Publishing Information (108x108 および 512x512 のアイコン・ロゴが必須です) (公開情報)
- Privacy & Compliance (プライバシーとコンプライアンス)
lambda または Web上で稼働するアプリを開発する際の注意事項
- Node.js で作る場合には非同期に注意
- Account Linking で Salesforce との OAuth をさせるのは、残念ながら、現時点では難しいようです。そのため、JWTフローで開発することをおすすめします。なお、リンク先の Expire の指定方法は、今では仕様が変わってしまったようで動きません(stomita さんにはお伝え済み)。下のサンプルコードを参照ください。秒指定になって、整数型になりました。
- Alexa へのレスポンス返却には、this.emit などを利用するが、「this」が示すとおり、Node.js でコードブロックの入れ子の深さが変わる場合には、”const self = this” などで、退避しておいて、”self.emit(“:tell”, “メッセージ”);” などで返却する必要がある。
- 自由文章を入力して、それを引数とすることは現時点では、ほぼ無理そう。そのため、メッセージをもらって、その内容を chatter へ投稿するのは Alexaではできなさそう。Googleアシスタントなら行けそうなので、今度はそちらにチャレンジ。
'use strict'
const Alexa = require('alexa-sdk');
const fs = require('fs');
const jwt = require('jsonwebtoken');
const request = require('request');
const jsforce = require('jsforce');
const APP_ID = 'amzn1.ask.skill.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'; // undefined でも良さげ
const TOKEN_ENDPOINT_URL = 'https://login.salesforce.com/services/oauth2/token'; // mydomain を使っているなら、そっちにしましょう
const ISSUER = '3MVG9ZL0pp(中略).ZHga(中略)nhqW1'; // 接続アプリのコンシューマ鍵(client_id)
const AUDIENCE = 'https://login.salesforce.com'; // 固定
const REPORT_ID = '00O0I00000A8XxXXxXXX'; // 取得するレポートのID
const cert = fs.readFileSync('./ssl/myapp.pem'); // 秘密鍵の読み込み
// JWTに記載されるメッセージの内容
const claim = {
iss: ISSUER,
aud: AUDIENCE,
sub: 'xxxxxx@example.com', // 接続するSalesforceのユーザアカウント名(メアド)
exp: Math.floor(Date.now()/1000) + 3 * 60 //現在時刻から3分間のみ有効 (以前からの仕様変更部分)
};
// JWTの生成と署名
const token = jwt.sign(claim, cert, { algorithm: 'RS256'});
function handleGetReportSummary(intent, response, callback) {
// JWT Bearer Token フローによるアクセストークンのリクエスト
request({
method: 'POST',
url: TOKEN_ENDPOINT_URL,
form: {
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: token
}
}, function(err, response, body) {
if (err) {
return console.error("request エラー" + err);
}
const ret = JSON.parse(body);
const conn = new jsforce.Connection({
accessToken: ret.access_token,
instanceUrl: ret.instance_url
});
const id = { reportId: REPORT_ID };
// apex で作成した REST をコール
conn.apex.post("/summary/", id, function(err, result) {
if (err) { return console.error(err); }
callback(err, result);
});
});
};
const handlers = {
'LaunchRequest': function () {
this.emit(':ask', 'chatter へ投稿するメッセージをお伝え下さい');
},
'SubmitChatter': function (intent, session, response) {
const self = this;
handleGetReportSummary(intent, response, function(err, result) {
console.log("result : " + result);
self.emit(':tell', '今期の売上は' + result + '円です');
});
},
'AMAZON.HelpIntent': function () {
this.emit(':ask', '現在の売上を報告します');
},
'AMAZON.CancelIntent': function () {
this.emit(':tell', 'またご利用ください');
},
'AMAZON.StopIntent': function () {
this.emit(':tell', 'またご利用ください');
},
'Unhandled': function () {
this.emit(':tell', '未対応です');
},
};
exports.handler = function (event, context) {
const alexa = Alexa.handler(event, context);
alexa.APP_ID = APP_ID;
// To enable string internationalization (i18n) features, set a resources object.
alexa.registerHandlers(handlers);
alexa.execute();
};
レポートの結果を取得するにはApexを叩くしかない
Salesforceでは、レポートのサマリなど情報を取得するには、Apex の Classでアクセスするしか方法がなさそうです。したがって、外部システムからレポートの結果を取得するためには、Apex で Classを開発し、@RestResource()
と @HttpPost
アノテーションを使って、外部からREST apiを叩けるように Classを公開する必要があります。今回、表形式で、売上の集計値(合計)をサマリーとしてもつレポートから、集計値を取り出すサンプルを作りましたので、参考になさってください。
レポート大変すぎる。
:summary.apxc
@RestResource(urlMapping='/summary/*')
global with sharing class summary {
// 現時点では POST のみを受け入れ
// 引数は JSON形式で、"reportId" に、レポートのIDを指定する必要があります
@HttpPost
global static Integer doPost(String reportId) {
Reports.ReportResults results = Reports.ReportManager.runReport(reportId, true);
MAP<String,Reports.ReportFact> summary = results.getFactMap();
LIST<Reports.SummaryValue> aggr = summary.get('T!T').getAggregates();
return Integer.valueOf(aggr[0].getValue());
}
}
何やってるか、ものすごい難しいです。検証の結果、こうなりました orz
参考情報