tyoshikawa1106のブログ

- Force.com Developer Blog -

SFDC:LEXのメッセージ機能が利用できない問題で対応したこと

Salesforce ClassicからLightning Experienceに移行する際にひとつ問題がありました。Chatterのメッセージ機能が利用できない問題です。
f:id:tyoshikawa1106:20180414175245p:plain


「メッセージ機能ですか?Salesforceではサポートを終了しました。そういうものなんです。」で押し通そうと思っていたのですが、GitHubに公開されている+Messageを使わせてもらうことで解決しました。



Lightning Experienceにはユーティリティバーというどのページからもアクセスできる機能が利用できます。これをつかってメッセージ機能にアクセスできるようにしました。

f:id:tyoshikawa1106:20180414175548p:plain

f:id:tyoshikawa1106:20180414175601p:plain


もともとはLightning Expcerienceが公開されるよりも前、Salesforce1モバイルアプリでの利用を想定されているのでLEXでの利用は想定されていません。ただ、非公開パッケージも公開されているので開発環境を用意することは簡単にできる状態でした。


SalesforceはLightning Design SystemというCSSフレームワークを公開してくれています。これを利用すればLEX的な見た目に調整することができそうでした。

Lightning Design System


実際にやってみたのがこちら。
f:id:tyoshikawa1106:20180414180144p:plain

f:id:tyoshikawa1106:20180414180201p:plain


少し強引にやってごまかしたところがありますが (Clickリンクのところなど) ひとまずうまくいきました。LEXでのメッセージ機能はこれで運用してみようと思います。

変更した箇所

変更したのはHTML部分とJSの一部処理だけです。

PlusMessageView.page
<apex:page docType="html-5.0" applyHtmlTag="false" showHeader="false" sidebar="false" standardStylesheets="false" controller="PlusMessageCtrl">
<html lang="ja" data-framework="angularjs" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0"></meta>
    <title>Chatter Message</title>
    <apex:stylesheet value="{!URLFOR($Resource.PlusMessageResource,'css/bootstrap.min.css')}" />
    <apex:slds />
  </head>
  <body ng-app="msgapp" ng-init="userId='{!$User.Id}'; languageLocaleKey='{!languageLocaleKey}'" class="slds-scope">
    <ng-view />
    <!-- conversations.html -->
    <script type="text/ng-template" id="conversations.html">
      <section id="msgapp">
        <div class="slds-text-align--right">
          <button onclick="location.href='#/send/'" class="slds-button slds-button_icon slds-button_icon-border-filled" aria-pressed="false">
            <svg class="slds-button__icon" aria-hidden="true">
              <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="{!URLFOR($Asset.SLDS, '/assets/icons/utility-sprite/svg/symbols.svg#new_direct_message')}" />
            </svg>
            <span class="slds-assistive-text">New</span>
          </button>
        </div>
        <div>
          <div ng-show="err!=null" class="slds-m-vertical_small slds-notify slds-notify_alert slds-theme_alert-texture slds-theme_error" role="alert">
            <h2>{{err.message}}</h2>
          </div>
          <div ng-show="loading">
            <div style="height: 6rem;">
              <div role="status" class="slds-spinner slds-spinner_medium">
                <span class="slds-assistive-text">Loading</span>
                <div class="slds-spinner__dot-a"></div>
                <div class="slds-spinner__dot-b"></div>
              </div>
            </div>
          </div>
          <div class="slds-feed">
            <ul class="slds-feed__list">
              <li class="slds-feed__item" ng-repeat="conv in convs.conversations">
                <article class="slds-post">
                  <header class="slds-post__header slds-media">
                    <div class="slds-media__figure">
                      <a href="#/{{conv.id}}" class="slds-avatar slds-avatar_circle slds-avatar_medium">
                        <img src="{{conv.latestMessage.sender.photo.smallPhotoUrl}}" />
                      </a>
                    </div>
                    <div class="slds-media__body">
                      <div class="slds-grid slds-grid_align-spread slds-has-flexi-truncate">
                        <p><a href="#/{{conv.id}}">{{conv.latestMessage.sender.name}}</a></p>
                      </div>
                      <p class="slds-text-body_small"><a href="#/{{conv.id}}" class="slds-text-link_reset">{{conv.latestMessage.sentDate}}</a></p>
                    </div>
                  </header>
                  <div class="slds-post__content slds-text-longform">
                    <p><span ng-bind="conv.latestMessage.body.text" style="white-space: pre-wrap;"/></p>
                  </div>
                  <footer class="slds-post__footer">
                    <div class="slds-text-align--right"><a href="#/{{conv.id}}">Click!</a></div>
                  </footer>
                </article>
              </li>
            </ul>
          </div>
        </div>
      </section>
    </script><!-- conversations.html -->

    <!-- send-message.html -->
    <script type="text/ng-template" id="send-message.html">
      <section id="msgapp">
        <div class="slds-clearfix">
          <div class="slds-clearfix">
            <div class="slds-float_left">
              <button onclick="location.href='#/'" class="slds-button slds-button_icon slds-button_icon-border-filled" aria-pressed="false" title="Like">
                <svg class="slds-button__icon" aria-hidden="true">
                  <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="{!URLFOR($Asset.SLDS, '/assets/icons/utility-sprite/svg/symbols.svg#back')}" />
                </svg>
                <span class="slds-assistive-text">Back</span>
              </button>
            </div>
          </div>
        </div>
        <div >
          <div ng-show="err!=null" class="slds-m-vertical_small slds-notify slds-notify_alert slds-theme_alert-texture slds-theme_error" role="alert">
            <h2>{{err.message}}</h2>
          </div>
          <div class="slds-m-top--small">
            <textarea name="message" ng-model="message" class="slds-textarea" placeholder="{{ 'MESSAGE' | translate }}"></textarea>
            <div class="input-group-btn"><button type="button" class="slds-button slds-button_brand" ng-click="sendMessage()" translate="SEND">Send</button></div>
          </div>
          <div ng-show="loading">
            <div style="height: 6rem;">
              <div role="status" class="slds-spinner slds-spinner_medium">
                <span class="slds-assistive-text">Loading</span>
                <div class="slds-spinner__dot-a"></div>
                <div class="slds-spinner__dot-b"></div>
              </div>
            </div>
          </div>
          <div style="padding-top: 14px">
            <ul class="list-group">
              <li class="list-group-item list-group-item-info">
                <span translate="RECIPIENTS">Recipients</span>
                <span style="padding-left: 14px;">
                  <button ng-click="openSearchUsers()" ng-show="members.length<9" class="slds-button slds-button_icon slds-button_icon-border-filled" aria-pressed="false">
                    <svg class="slds-button__icon" aria-hidden="true">
                      <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="{!URLFOR($Asset.SLDS, '/assets/icons/utility-sprite/svg/symbols.svg#adduser')}" />
                    </svg>
                    <span class="slds-assistive-text">Add</span>
                  </button>
                </span>
            </li>
              <li class="list-group-item" ng-show="members.length==0" translate="NO_RECIPIENTS_MESSAGE">Add Recipients</li>
              <li class="list-group-item" ng-repeat="member in members">
                <div class="slds-size_3-of-4">
                  <div class="slds-media">
                    <div class="slds-media__figure">
                      <span class="slds-avatar slds-avatar_large">
                        <img src="{{member.photo.smallPhotoUrl}}" />
                      </span>
                    </div>
                    <div class="slds-media__body">
                      <div class="name">{{member.name}}</div>
                      <div class="title">{{member.title}}</div>
                    </div>
                  </div>
                </div>
              </li>
            </ul>
          </div>
        </div>
      </section>
    </script><!-- send-message.html -->

    <!-- search-users-dialog.html -->
    <script type="text/ng-template" id="search-user-dialog.html">
      <div class="modal-header">
        <div class="input-group">
          <span class="input-group-addon">@</span>
          <input type="text" name="query" ng-model="searchUsers.query" class="form-control" placeholder="{{ 'RECIPIENTS' | translate }}" x-webkit-speech lang="ja"/>
        </div>
      </div>
      <div class="modal-body">
        <div class="alert alert-danger" ng-show="errDialog!=null">{{errDialog.message}}</div>
        <div ng-show="loadingDialog">
          <div style="height: 6rem;">
            <div role="status" class="slds-spinner slds-spinner_medium">
              <span class="slds-assistive-text">Loading</span>
              <div class="slds-spinner__dot-a"></div>
              <div class="slds-spinner__dot-b"></div>
            </div>
          </div>
        </div>
        <form role="form">
          <div class="list-group" ng-hide="loadingDialog">
            <div class="list-group-item" ng-show="users.length==0" translate="NO_MATCH_USER_MESSAGE">No match user</div>
            <a class="list-group-item" ng-repeat="user in users" ng-click="addUser(user)">
              <div class="slds-size_3-of-4">
                <div class="slds-media">
                  <div class="slds-media__figure">
                    <span class="slds-avatar slds-avatar_large">
                      <img src="{{user.photo.smallPhotoUrl}}" />
                    </span>
                  </div>
                  <div class="slds-media__body">
                    <div class="name">{{user.name}}</div>
                    <div class="title">{{user.title}}</div>
                  </div>
                </div>
              </div>
            </a>
          </div>
        </form>
      </div>
      <div class="modal-footer">
        <button type="button" class="slds-button slds-button_neutral" ng-click="$close()" translate="CLOSE">Close</button>
      </div>
    </script><!-- search-users-dialog.html -->

    <!-- messages.html -->
    <script type="text/ng-template" id="messages.html">
      <section id="msgapp">
        <div class="slds-clearfix">
          <div class="slds-clearfix">
            <div class="slds-float_left">
              <button onclick="location.href='#/'" class="slds-button slds-button_icon slds-button_icon-border-filled" aria-pressed="false" title="Like">
                <svg class="slds-button__icon" aria-hidden="true">
                  <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="{!URLFOR($Asset.SLDS, '/assets/icons/utility-sprite/svg/symbols.svg#back')}" />
                </svg>
                <span class="slds-assistive-text">Back</span>
              </button>
            </div>
            <div class="slds-float_right">
              <button ng-click="openUsersDialog()" class="slds-button slds-button_icon slds-button_icon-border-filled" aria-pressed="false" title="Like">
                <svg class="slds-button__icon" aria-hidden="true">
                  <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="{!URLFOR($Asset.SLDS, '/assets/icons/utility-sprite/svg/symbols.svg#user')}" />
                </svg>
                <span class="slds-assistive-text">Chat Member</span>
              </button>
            </div>
          </div>
        </div>
        <div>
          <div ng-show="err!=null" class="slds-m-vertical_small slds-notify slds-notify_alert slds-theme_alert-texture slds-theme_error" role="alert">
            <h2>{{err.message}}</h2>
          </div>
          <div class="slds-m-top--small">
            <textarea name="message" ng-model="message" class="slds-textarea" placeholder="{{ 'MESSAGE' | translate }}" rows="3"></textarea>
            <div class="input-group-btn"><button type="button" class="slds-button slds-button_brand" ng-click="replyToMessage()" translate="SEND">Send</button></div>
          </div>
          <div ng-show="loading">
            <div style="height: 6rem;">
              <div role="status" class="slds-spinner slds-spinner_medium">
                <span class="slds-assistive-text">Loading</span>
                <div class="slds-spinner__dot-a"></div>
                <div class="slds-spinner__dot-b"></div>
              </div>
            </div>
          </div>
          <div class="slds-feed">
            <ul class="slds-feed__list" ng-hide="loading">
              <li class="slds-feed__item" ng-repeat="msg in msgs.messages.messages">
                <article class="slds-post">
                  <header class="slds-post__header slds-media">
                    <div class="slds-media__figure">
                      <a class="slds-avatar slds-avatar_circle slds-avatar_medium">
                        <img src="{{msg.sender.photo.smallPhotoUrl}}" />
                      </a>
                    </div>
                    <div class="slds-media__body">
                      <div class="slds-grid slds-grid_align-spread slds-has-flexi-truncate">
                        <p><a>{{msg.sender.name}}</a></p>
                      </div>
                      <p class="slds-text-body_small"><a class="slds-text-link_reset">{{msg.sentDate}}</a></p>
                    </div>
                  </header>
                  <div class="slds-post__content slds-text-longform">
                    <p><span ng-bind="msg.body.text" style="white-space: pre-wrap;"/></p>
                  </div>
                </article>
              </li>
            </ul>
          </div>
        </div>
      </section>
    </script><!-- messages.html -->

    <!-- users-dialog.html -->
    <script type="text/ng-template" id="users-dialog.html">
      <div class="modal-header" translate="MEMBER">
        Member
      </div>
      <div class="modal-body">
        <ul class="list-group">
          <li class="list-group-item" ng-repeat="member in msgs.members">
            <div class="slds-size_3-of-4">
              <div class="slds-media">
                <div class="slds-media__figure">
                  <span class="slds-avatar slds-avatar_large">
                    <img src="{{member.photo.smallPhotoUrl}}" />
                  </span>
                </div>
                <div class="slds-media__body">
                  <div class="name">{{member.name}}</div>
                  <div class="title">{{member.title}}</div>
                </div>
              </div>
            </div>
          </li>
        </ul>
      </div>
      <div class="modal-footer">
        <button type="button" class="slds-button slds-button_neutral" ng-click="$close()" translate="CLOSE">Close</button>
      </div>
    </script><!-- susers-dialog.html -->


    <!-- waiting-dialog.html -->
    <script type="text/ng-template" id="waiting-dialog.html">
      <div class="modal-header" translate="SENDING_MESSAGE">
        Sending...
      </div>
      <div class="modal-body">
        <div style="height: 6rem;">
          <div role="status" class="slds-spinner slds-spinner_medium">
            <span class="slds-assistive-text">Loading</span>
            <div class="slds-spinner__dot-a"></div>
            <div class="slds-spinner__dot-b"></div>
          </div>
        </div>
      </div>
    </script><!-- waiting-dialog.html -->

    <apex:includeScript value="{!URLFOR($Resource.PlusMessageResource, 'js/angular.min.js')}" />
    <apex:includeScript value="{!URLFOR($Resource.PlusMessageResource, 'js/angular-route.min.js')}" />
    <apex:includeScript value="{!URLFOR($Resource.PlusMessageResource, 'js/angular-translate.min.js')}" />
    <apex:includeScript value="{!URLFOR($Resource.PlusMessageResource, 'js/ui-bootstrap-tpls-0.10.0.min.js')}" />
    <apex:includeScript value="{!URLFOR($Resource.PlusMessageResource, 'js/jquery-2.1.0.min.js')}" />
    <apex:includeScript value="{!URLFOR($Resource.PlusMessageResource, 'js/bootstrap.min.js')}" />
    <apex:includeScript value="{!URLFOR($Resource.PlusMessageJS, 'app.js')}" />
    <apex:includeScript value="{!URLFOR($Resource.PlusMessageJS, 'controllers/messageCtrl.js')}" />
    <apex:includeScript value="{!URLFOR($Resource.PlusMessageJS, 'services/messageService.js')}" />
  </body>
</html>
</apex:page>


JSの方は日本時間に調整したいところがあったので他で実装されていた処理をコピペする形でちょっと手を入れました。

messageService.js

f:id:tyoshikawa1106:20180414180627p:plain


ということでGitHubに公開されている+MessageのおかげでLightning Experienceにメッセージ機能を表示することができました。メッセージ通知の機能とかの要望がくるかもしれませんが、おそらく「メールで気づいてください」で押し通せると思います。いつか標準でサポートされればいいなと思います。(Skype for Salesforceが用意されていましたが試してみたところメッセージ機能とはすこし用途がことなりました。)

管理パッケージ対応

上記で用意したカスタマイズバージョンの+メッセージですが、組織にインストールするときは管理パッケージとしてインストールします。未管理パッケージでも同じ用にインストール可能ですが、管理パッケージにすることで開発時に組織コードに混ざって表示されないようになります。組織に合わせてバンバンカスタマイズしてく場合は未管理パッケージで気軽に開発できるようにした方がいいと思うのですが、通常さわらないのであれば管理パッケージの方が良いと思います。 ※書いた後にしったのですが、管理パッケージはパートナー組織じゃないとバージョンアップできない落とし穴がありました。

管理パッケージ化するときの注意

管理パッケージにすると組織に名前空間プレフィックスが追加されます。JSからApexクラスにアクセスする処理は下記の規則で修正が必要になります。

<名前空間プレフィックス>.SampleController.geSampleMethod()


先頭に名前空間プレフィックスをつけるだけなので規則がわかればそれほど大変ではないと思います。

追記

Salesforceモバイルアプリでも利用可能ですが、タブの作成が必要になります。またモバイルアプリで表示したときに気づいたのですが、paddingを入れとけばよかったです。

SFDC:すべてのコミュニティ設定のワークスペースリンクにアクセスできなくなったときの対処方法

コミュニティのメンバー追加などの各種設定は「すべてのコミュニティ設定」のワークスペース(管理)リンクで設定ページに移動して行います。
f:id:tyoshikawa1106:20180413152906p:plain


このシステム管理者なら当然アクセスできるよねというリンクは、コミュニティのメンバーでないとアクセスできないルールがあります。
f:id:tyoshikawa1106:20180413152957p:plain


上記ルール自体把握できていませんでしたが、うっかり外すなんて状況普通ないよね...と思っているとこんなケースで発生してしまいました。
1. コミュニティ作成
2. メンバー追加で対象のコミュニティユーザプロファイルを追加
3. 本来なら管理者プロファイルも追加する必要があるのに忘れて保存
4. そのままログアウト
5. 次回ログインして設定の続きをやろうとすると・・・リンクが無い。


はじめ管理者権限があればなんとでもなるのだと思い、権限セットでコミュニティ管理の権限を付与したりしたのですが、アクセスできませんでした。ヘルプサイトを確認するとこの状況になってしまうとAPIでメンバーを登録する必要がある状況になってしまっているとのことです。

API を使用してコミュニティのメンバーシップを更新するには?

Help | Training | Salesforce


ヘルプに手順が記載されていますが、問題解決のためにはコミュニティのNetworkIdを取得する必要あります。コミュニティURLを右クリックして検証すると確認できます。
f:id:tyoshikawa1106:20180413153549p:plain


f:id:tyoshikawa1106:20180413153810p:plain:w300


NetworkIDを取得したらこんな感じでCSVを作成します。
f:id:tyoshikawa1106:20180413154315p:plain


※profileIDは追加したい管理者プロファイルのIDをセットします。詳細ページのURLから取得できます。


CSVの準備ができたらデータローダでINSERTします。対象オブジェクトは[ネットワークメンバーグループ]です。
f:id:tyoshikawa1106:20180413154511p:plain

INSERTの前に

このデータ更新処理を行うには下記の権限が必要です。標準システム管理者プロファイルは値の変更ができないため権限セットで対応します。

コミュニティ管理にアクセスするためには、メンバーに「コミュニティの作成および設定」または「コミュニティの管理」権限も必要です。

正しくCSVを用意できていれば問題なくINSERTが実行されると思います。これでうっかり除外した管理者プロファイルをコミュニティのメンバーに追加できます。ワークスペースリンクが復活しているはずです。

SFDC:パートナーコミュニティユーザのアクセス権限と所有者の関係

パートナーコミュニティユーザに取引先と取引先責任者の作成権限を付与した場合、別会社の取引先と取引先責任者を作成することができます。その場合は作成者と所有者はパートナーコミュニティユーザとなります。


所有者を社内ユーザに切り替えた場合パートナーコミュニティユーザは自分が作成した取引先のデータでも参照不可となります。(共有設定等で非公開設定している前提です。)


わかってはいたけど、確認したついでにメモ。

SFDC:SOQLクエリでLightning Experiecneの利用状況を確認

Lightning Expcerience導入後、引き続きClassicを利用しているユーザがどの程度いるかは開発者コンソールからSOQLクエリを実行することで確認できます。


UserPreferencesLightningExperiencePreferredがTrueのものが有効にして使っているものです。

基本のクエリ
SELECT Id,Name, UserPreferencesLightningExperiencePreferred FROM User
プロファイル名判定
SELECT Id,Name, UserPreferencesLightningExperiencePreferred FROM User 
WHERE Profile.Name = 'サンプル' OR Profile.Name = 'システム管理者'
Lightning Expcerience利用していないユーザの判定
SELECT Id,Name, UserPreferencesLightningExperiencePreferred FROM User 
WHERE UserPreferencesLightningExperiencePreferred = false


単純にクエリを実行するとコミュニティユーザや無効なユーザも集計されるのでプロファイル名などで絞り込む必要があります。

関連記事

SFDC:Salesforce AuthenticatorとApple Watch連携で2要素認証作業の効率化

Salesforce Authenticatorアプリを利用して2要素認証を有効化することでセキュリティをより強化にすることができます。

Salesforce Security


有効化の方法はこちら。


有効化して数日間試してみたのですが、やはりログイン時にスマートフォンのロックを解除して承認を行うのはなかなかに面倒でした。調べてみたところこの問題の解決方法が見つかりました。


Salesforce Authenticatorアプリですが、Apple Watchアプリもサポートしています。このApple Watchアプリの場合は手につけてロックを解除している状態の場合はすでに安全な状態が確保されています。そのためだと思うのですが、2要素認証の自動承認の機能が利用可能となっていました。


そのためSalesforceにログイン→認証ページが表示→Apple Watchのアプリが自動承認という流れでスムーズにログインすることができます。スマートフォンのロック解除の手間もなくなり非常に便利です。


この運用を行う場合はApple Watchのパスワード設定無しでの運用は禁止にする必要がありそうです。またおそらくスマートフォンが近くにない場合は自動承認が利用できない状態になってくれると思います。(検証はしてないですが)


Apple Watchによる自動承認機能ですが、Lightning Login機能を有効にしている場合は利用できませんでした。Lightning Loginはパスワードを入力不要になるためApple Watchだけでログイン可能になる問題を防ぐためかもしれません。ちなみに下記のページで有効化の要望が上がっていました。

参考

SFDC:Einsteinハンズオンに参加してサンプルアプリを作ってみました

Einsteinハンズオンに参加してサンプルアプリを作ってみました。
f:id:tyoshikawa1106:20180409170016p:plain

環境の準備

TrailheadのChallengeのところからPlayground環境を作成します。作成したらユーザの言語を英語に変更。
f:id:tyoshikawa1106:20180409153302p:plain


手順に従いオブジェクトを作成

  • Cat オブジェクトを作成する
  • Interested Person オブジェクトを作成する

上記オブジェクトのタブを作成する。
上記オブジェクトタブを含むアプリケーションを作成する。

f:id:tyoshikawa1106:20180409154056p:plain


テストデータを作成する。
f:id:tyoshikawa1106:20180409155114p:plain

f:id:tyoshikawa1106:20180409154321p:plain


次のようなエラーがでたら設定ミス。タブの名前変更が必要。
f:id:tyoshikawa1106:20180409154832p:plain


この設定ページから。ユーザの地域を英語にしておけばオブジェクト作成時に登録できたかも。
f:id:tyoshikawa1106:20180409154920p:plain


グローバル選択リストを作成する。
f:id:tyoshikawa1106:20180409155307p:plain


カスタムオブジェクトに選択リストを割り当てる
f:id:tyoshikawa1106:20180409155438p:plain

f:id:tyoshikawa1106:20180409155732p:plain


こんな感じで使えるようになれば準備OK。
f:id:tyoshikawa1106:20180409155522p:plain

f:id:tyoshikawa1106:20180409155747p:plain


2つのオブジェクトに追加した項目に値をセット。
f:id:tyoshikawa1106:20180409155946p:plain

f:id:tyoshikawa1106:20180409155921p:plain


2つのオブジェクトは参照関係などの紐付けがありませんが、Einsteinでマッピングできるというのが今回の目標みたいです。
→ Interested Person オブジェクト特に使わなかった。(処理の裏側で使われていたのかも)

追記

Interested Personの方は複数選択リスト型でした。
f:id:tyoshikawa1106:20180409162018p:plain

Einsteinの準備

Einstein プラットフォームサービスアカウントにサインアップする
https://api.einstein.ai/signup


einstein_platform.pemファイルをダウンロードできます。(同じメールアドレスで2つ目は作成できない。過去に作成済みの場合はそちらを利用。)
f:id:tyoshikawa1106:20180409153204p:plain

Salesforce に認証証明書を保存する

静的リソースではなくSalesforce Filesへのアップロードを行う。
f:id:tyoshikawa1106:20180409160437p:plain

f:id:tyoshikawa1106:20180409160510p:plain

未管理パッケージをダウンロード

ダウンロードURLはTrailheadに記載されています。
f:id:tyoshikawa1106:20180409160953p:plain

f:id:tyoshikawa1106:20180409161017p:plain

※事前にユーザのパスワードをリセットして自動生成ではないパスワードを指定すること。

f:id:tyoshikawa1106:20180409161111p:plain


未管理パッケージをインストールするとカスタム設定が追加されています。Einsteinにサインアップしたメールアドレスを登録します。
f:id:tyoshikawa1106:20180409161250p:plain

ApexクラスとLightning コンポーネントとLightningアプリケーションを作成

下記のApexクラスを作成します。(※コードはTrailheadを参照)

  • EinsteinVision_Admin.cls

f:id:tyoshikawa1106:20180409161546p:plain


次にLightning コンポーネントを作成します。

  • EinsteinVision_Admin_UI.cmp

f:id:tyoshikawa1106:20180409161744p:plain


最後にLightningアプリケーションを作成します。これは開発者コンソールではなく設定ページから。
f:id:tyoshikawa1106:20180409162330p:plain

f:id:tyoshikawa1106:20180409162540p:plain

f:id:tyoshikawa1106:20180409162608p:plain

f:id:tyoshikawa1106:20180409162624p:plain

f:id:tyoshikawa1106:20180409162637p:plain

f:id:tyoshikawa1106:20180409162653p:plain

f:id:tyoshikawa1106:20180409162706p:plain


アプリケーションへの追加はLightning Experieneタブを選択してそこで設定。
f:id:tyoshikawa1106:20180409162834p:plain


こんな感じで表示されればOK。
f:id:tyoshikawa1106:20180409162917p:plain

データセットの作成

Einsteinで分析するためのデータ・セットを作成します。

[https://developer.salesforce.com/files/Cats.zip]


URLを貼ってCreateボタンをクリック。こんな感じ。
f:id:tyoshikawa1106:20180409163103p:plain


Refreshするとこうなる。
f:id:tyoshikawa1106:20180409163138p:plain


最後にTrainボタンをクリックすると何かの処理が実行される。
f:id:tyoshikawa1106:20180409163341p:plain


Success100%と表示されたらOK。処理には少し時間がかかります。うっかり二回処理をうごかしたら動いてしまいました。
f:id:tyoshikawa1106:20180409163717p:plain

Cat (猫) オブジェクトに画像認識機能を追加

Lightningコンポーネントを作成
f:id:tyoshikawa1106:20180409163919p:plain


Cat レイアウトに Lightning コンポーネントを追加
f:id:tyoshikawa1106:20180409164031p:plain

猫の画像を分類する

これでアプリの準備が整いました。実際にデータを読み込ませて分類を試します。使うのはこの画像。
f:id:tyoshikawa1106:20180409164155p:plain


ファイルはTrailheadからダウンロード。
f:id:tyoshikawa1106:20180409164228p:plain



ファイルをアップするとデータが更新されることを確認できます。
f:id:tyoshikawa1106:20180409164441p:plain

アプリの利用例:Chatterと連携

グループを3つ作ります。
f:id:tyoshikawa1106:20180409164817p:plain


プロセスビルダーを作成します。
f:id:tyoshikawa1106:20180409165002p:plain


ざっくりこんな感じ。
f:id:tyoshikawa1106:20180409165313p:plain


猫画像をアップロードすると、「Bengal」と判別されて・・・
f:id:tyoshikawa1106:20180409165512p:plain


先ほど作成したプロセスどおりBengalのグループにメッセージが投稿されます。
f:id:tyoshikawa1106:20180409165551p:plain


里親になりたい人はこのChatterグループでメッセージを通知を受けることができるという流れになります。Chatter投稿の部分はおまけ的な感じで画像をアップすると猫の分類が自動で行われ値が更新されたことを確認するというのが今回のハンズオンの目的でした。

f:id:tyoshikawa1106:20180409165758p:plain


認証キーとなるeinstein_platform.pemファイルをSalesforceにアップしてEinstein APIを実行できたのだと思います。今回サンプルコードの中身は確認してみませんがこれをベースにAPIガイドを見ながらいろいろ試せるんだと思います。

SFDC:Lightning Loginと Two Factor Authencicationを試してみました

Lightning LoginとTwoFactorAuthencication有効化を試してみました。以前にも試したことのある機能なのですが、久しぶりに設定した際により便利にアップデートされていたので改めてやってみました。


これらの機能を有効化する場合はプロファイルではなく権限セットで個別に設定します。


権限セットのシステムのセクションで下記を有効化します。

  • Lightning Login ユーザ
  • ユーザインターフェースログインの 2 要素認証


用意した権限セットは対象のユーザに割り当てます。


この機能を利用するには専用のアプリが必要です。業務で利用する携帯電話にインストールしておきます。

‎「Salesforce Authenticator」をApp Storeで

https://play.google.com/store/apps/details?id=com.salesforce.authenticator&hl=ja


権限を追加すると次回ログイン時に次の画面が表示されます。


先ほどインストールした専用アプリを起動して画面下側にある新規アカウントをタップします。すると2語の語句が表示されます。


これを認証画面に入力して次に進めます。


携帯電話のアプリで承認依頼が届くので承認します。


承認するとログインが実施されて次回ログインからTwoFactorAuthencication (2要素認証)が有効化されます。実際に試してみるとわかりますが、ログインするとアプリの通知が届き、アクセスすると承認ページが表示されています。承認するだけでログイン完了するのでランダムなキーワードを入力する必要はありません。


さらにLightning Login機能を有効化していると次ページが表示されます。


画面に従って有効化と承認を行います。


なお、Lightning Logiinの機能を利用するにはログイン時にユーザ情報を保存しておく必要があります。

※保存しても設定が反映されないことがあるのでLightning Loginが利用できるようになるまで少し時間がかかるのかも知れません。


Lightning Loginなどの無効化はユーザの詳細ページから行うことができます。


また、2要素認証の機能には「ワンタイムパスワードジェネレータ」と「Salesforce Authenticator」の二種類がありますが、基本的にはタップだけで認証できる「Salesforce Authenticator」を利用すればいいと思います。


Lightning Loginは「Salesforce Authenticator」を有効化した後により作業を効率化出来る機能となっています。

Lightning Loginを有効化した後

ログインページにユーザ名が表示されます。


クリックするとアプリに通知が届くので承認します。それでログイン完了です。


TwoFactorAuthencicationのメインであるスマートフォンの承認があればセキュリティが保証されているという考え方だと思います。認証デバイスは複数登録できないと思われます。必ずユーザの手元にある状態であることが重要です。携帯電話を使い回す場合は利用できません。また共用PCへのユーザ登録も避けて下さい。うっかり登録してしまった場合はユーザ詳細ページで無効にします。

関連記事