コンポーネントが無効な場合、cmp.get()で判定できるとのことです。
SFDC:Lightningコンポーネントの開発でコンポーネントの有効性チェックする方法
SFDC:Winter18のコラボレーション売上予測機能強化
Winter'18でLightning Experienceの売上予測機能でできることが増えています。
下記のような特徴の機能が追加されています。
- アクセス権があるユーザの売上予測にジャンプして表示する
- 目標達成率情報を表示する
- Lightning セールスコンソールでコラボレーション売上予測にアクセスする。
- 自分の商品ファミリの売上予測を調整する。これは、Lightning Experience に固有の機能です。
SFDC:Winter18のQuipドキュメントとの連携強化について
Winter'18からQuip ドキュメントとの連携がより便利になったそうです。複雑な認証設定をしなくても簡単にQuipとSalesforce間でドキュメントのやりとりが可能になっています。
SFDC:Winter18のLightning Experienceダッシュボード埋め込み機能について
Winter18からLightning Experienceのホームタブとアプリケーションページにダッシュボード埋め込みができるようになったとのことです。
SFDC:Lightning Experienceで使えるChatterの絵文字機能について
Winter'18からLightning ExperienceでもChatterの絵文字機能が利用できるようになったみたいです。Classicと同じで設定で有効化する必要はあると思います。
SFDC:Lightning Experienceで使えるテリトリー機能について
Winter'18からLightning Experienceでテリトリー機能が使えるようになったみたいです。名称はエンタープライズテリトリー管理です。
SFDC:Apexテストクラス開発で使える並列テストの実行オプション
SFDC:Winter18のSalesforceDXに関する情報
もうSpring'18のリリースノートが公開されているので情報が古いかもしれませんがWinter18のSalesforceDXに関する情報がリリースノートにまとめられています。
役立つリンクが紹介されているかもしれません。
SFDC:APIバージョン41.0以降のLightningコンポーネント開発について
APIバージョン41.0以降のLightningコンポーネント開発ではコンポーネントコードの保存時の自動検証が実行されるようになっているそうです。APIバージョンを最新にして開発していくようにした方がよさそうです。
SFDC:活動のアーカイブと活動関連リストについて
Salesforceには1年間経過した活動はアーカイブされる仕組みがあります。これはデータが削除されるわけではなくレポート、リストビュー、検索でヒットしなくなるという仕様になっています。
なので一番重要な活動関連リストにはアーカイブ後も引き続き表示されるようになっています。(すべて表示ボタンで表示される)
SFDC:自動採番項目の表示形式変更を試してみました
Salesforceの自動採番項目の表示形式変更を試してみました。
Salesforceの自動採番項目を導入するときにうっかり「A-0001」と指定してしまうことがあると思います。基本的に任意の値に切り替えることはできませんが特定の手順で変更することが可能です。
※変更の際は必ずデータ入力が行われていない時間帯に作業します。
作業の流れ
まずはデータローダで既存データをエクスポートします。
続いてCSVデータを加工します。この作業はGoogleドライブにCSVファイルをアップしてGoogleスプレッドシートで行うとスムーズです。
別の列で新しい連番を用意したあとに既存の列の情報を更新します。
加工完了したらCSVファイル形式でダウンロードします。これで更新用データの準備ができました。
続いてName項目のデータ型をテキスト型に変更します。
テキスト型に変更するとName項目の値を更新することができるようになります。先程用意したCSVファイルとデータローダをつかってUPDATE作業を行います。(マッピングでName項目が表示されていない場合は、データローダを一度ログアウトして再ログインすると表示されます。)
UPDATE完了後にデータが正しく変更されているかを確認します。
更新に問題がないことを確認したらデータ型を自動採番型に変更します。そのとき開始番号には既存データの次の番号をセットします。
(選択リストで自動採番を選び保存を押すと各項目が表示されます)
データ型を自動採番に切り替えた後試しに一件データを作成してみます。
データが作成できることを確認できたら作業完了です。
この作業は他にデータ登録処理が実行されない時間帯に行う必要があります。また自動採番の値をつかって他システムと連携していないかの確認も必要です。作業後はApexクラスのテストがエラーになったりしていないかも確認しておくと無難かもしれません。基本的には変更しないというのが一番ですが他のデータと区別をつけやすくしたいときはこの手順で変更可能のはずです。
SFDC:Magic Mover for Notes And Attachments to Lightning Experience を試してみました
Magic Mover for Notes And Attachments to Lightning Experience を試してみました。メモ&添付ファイルをLightning Experienceがサポートするメモオブジェクトとファイルオブジェクトの形式に変換するためのアプリケーションです。
Magic Mover for Notes And Attachments to Lightning Experience - Salesforce Labs - AppExchange
このアプリケーションはSummer'17でファイルの変換方法として紹介されています。Salesforce社のSalesforce LabsチームがAppExchangeで公開していますが、Sales CloudやService Cloudのようにサポートされている製品ではないと思います)
メモ&添付ファイルの場合、LightningExperineceで参照すると自動でダウンロードされてしまいますが、ファイル形式の場合はプレビューモードが起動されます。そのため、Lightning Experienceの有効化には避けては通れない作業となっています。
アプリの利用ガイドも用意されています。作業の前には確認しておくと良さそうです。Summer'17のリリースノート公開されたときからアプリ名は変更されていたみたいです。
https://appexchange.salesforce.com/servlet/servlet.FileDownload?file=00P3A00000cWkhLUAS
AppExchangeなのでインストールは簡単に実施できます。
ただし、そのままインストールするとエラーになると思います。
このエラーはインストール作業前に必要な設定を行っていないからでした。ユーザインターフェースの設定でチェックを付ける必要があります。(詳細はガイドの方に記載されていました。)
メモ機能の有効化もやっておいたほうが良さそうです。
これで問題なくインストールできると思います。
インストールすると権限セットが追加されます。これを管理者ユーザに割り当てます。
また下記の権限を追加してください。
下記のVisualforceページタブが追加されます。
- Attachments
- Attachments to Files
- Configuration
- Last Documents
- Latest Files & Notes
- Notes Conversion
- Update Page Layouts
メモ&添付ファイルを移行するのはAttachments to Filesタブのページです。確認時に複数あることに気づかずメモ変換用のNotes Conversionページを起動したのですが、最初にリモートサイト設定の追加が必要になります。おそらく画面にメッセージが表示されるのでボタンをクリックするだけで追加完了です。権限セットが割り当てられていないとここで警告も表示されました。
ひとまずこれで事前準備が整いました。作業前にConfigurationタブにアクセスしてください。ここでバッチサイズを切り替えることが可能みたいです。
ファイル変換処理ですが、何も考えずに実行していくと『Apex CPU time limit exceeded』エラーが発生します。これは1日の使用量をオーバーしたことで発生するのですが、これが発生すると24時間一部機能が利用できなくなります。エラー通知のメールに「サポートに問い合わせて下さい。」と記載ありました。念のために問い合わせてみましたがさすがに緩和とかは難しいみたいでした。(※ファイルの変換作業は一度に処理する件数を減らすことで進めることができました。)
こうしたエラーを回避するためにもバッチサイズを50〜80ぐらいに減らしておいた方が無難かと思います。変換作業を実施したときにはこのページの存在に気づかずに上記CPUタイムの上限エラーを起こしてしました。その後70件から80件だと動くことに気付き毎回手動実行する方法で対応したのですが、本来はこのページでバッチサイズを変更すれば簡単に対応できると思います。
バッチサイズの設定ができたら、Attachments to Filesタブにアクセスします。ここでファイル変換作業を実施できます。
まずは対象ファイルを日付とオブジェクトで絞り込みます。今回変更対象のファイルは長年使われている組織と比べて多くなかったため、全期間と全オブジェクトで取得しても問題ありませんでしたが、必要に応じて絞り込むと良さそうです。条件を指定したらFilterボタンで検索できます。
検索した移行対象は画面左下に表示されます。移行対象にチェックをつけて少し上にあるConvertボタンをクリックします。
最初は検証用に数件試すのが良いと思います。実行前のメモ&添付ファイルはこのようになっています。
Converボタンをクリックするとポップアップで確認メッセージが表示されるので問題なければ実行します。
処理はApexバッチで行われます。完了まで少し時間が掛かります。
[:300]
処理が完了するとメールで通知が届きます。ガバナ制限が発生しそうな場合などの通知メールも届くようになっています。
処理完了後はこのようになります。
ファイル関連リストとメモ&添付ファイルに同じものが表示されているのは、Salesforceの仕様なので問題ありません。(ファイル関連リストのデータはメモ&添付ファイルからも参照可能となっています。)
タイトル部分にIDが割り当てられたファイル、これが移行元となったファイルです。
移行元のファイルはAttachments to Filesの機能で削除することが可能です。
ゴミ箱をクリックすると削除バッチが実行されます。
これで不要になったファイルを削除できました。ファイルはゴミ箱にはいかず完全削除されるので注意してください。
この作業が完了した後すぐに対応しなくてはならないことがあります。ページレイアウトの設定です。メモ&添付ファイル関連リストが表示されたままだとまた新たなファイルがアップロードされてしまいます。それを防ぐためにメモ&添付関連リストを除外してファイル関連リストに差し替える必要があります。
こうした作業はUpdate Page Layoutタブの機能を利用すると簡単です。
画面左側のチェックボックスがメモ&添付ファイルが表示されているページレイアウトを表します。画面右側のチェックがファイル関連リスト・メモ関連リストを表示していることを表しています。関連リストの追加と除外は右上のボタンから実行できました。
これでMagic Mover for Notes And Attachments to Lightning Experienceをつかってファイル関連リストへの変換が完了しました。メモの方は試していませんが同じように実行できると思います。この機能は正式な製品ではないと思いますので基本的には自己責任での利用が求められると思います。利用前には事前の検証を必ず行うようにした方が良さそうです。
注意点として、このパッケージのApexクラスがテストクラスでエラーが発生しました。インストール中はリリース作業ができなくなる可能性があります。(パッケージではなく組織側の問題かもしれません。)
検証時にはその当たりも含めて確認しておくのが良さそうです。
正式な製品ではありませんが、Salesforceのチームが公開しているためかTrailblazerCommunityにグループが用意されています。そこで最新情報を取得したり質問したりができそうです。
https://success.salesforce.com/_ui/core/chatter/groups/GroupProfilePage?g=0F93A000000LgpSSAS
補足
メモ&添付ファイル関連リストはAttachmentオブジェクトでしたが、ファイル関連リストのデータはContentVersion/ContentDocumentで管理されます。Apex側での改修が必要になったり、Force.comサイトのゲストユーザでアクセスできなかったりしますので、開発が入っている組織の場合は移行して問題が内科のチェックを行う必要があります。
SFDC:価格表と商品のメンテナンスを試してみました
Sales Cloudの商談機能を使えば商品の金額を管理できます。商品の管理には価格表が必要になるのですが、値を更新したときの扱いなどについて確認してみました。
商談商品の利用開始にはまず価格表を用意します。用意ができたら商談の詳細ページの商品関連リストから商品を追加ができるようになります。
初回の商品追加は価格表の選択が求められます。
続いて対象の商品を選択します。
数量と金額を入力します。残りの項目は必須ではないので状況に応じて利用します。
これで商品の追加が完了しました。
商品関連リストのすべてリンクをクリックすると選択した商品が一覧表示されます。
登録した商品はもちろん編集可能です。
価格表の更新の影響
ここからが今回の調査対象です。まず選択した価格表の金額が変更された場合、商談商品に影響があるか確認しました。価格表の金額はリスト価格で管理されています。
標準価格表チェックがONになっていると変更できないのでOFFにしてから値を更新します。
これで価格表の金額を更新しました。
その状態で商談商品の金額が変更されているか確認したところ、値は変更されずに残っていました。
価格表の金額を更新しても商談に登録した金額(販売価格)が勝手に変更されることはないようです。ただし、リスト価格の値が異なる値に更新されていました。基本的には問題ありませんが定価と比較してどの程度値下げもしくは値上げされているかが確認できなくなってしまいます。
価格表の金額更新は可能ですが、基本的には行わない方が良さそうです。
新しい価格の追加
上記の通り、価格表の金額を変える時は既存の価格表を更新するのではなく新規価格表を作成する方が良さそうです。その場合は既存価格表をコピーする形で登録できます。
・・・が、コピーした場合は商品データはコピーされないみたいです。。
これに関してはClassicから作成すると商品を含めてコピーできる機能があります。
今後のバージョンアップで対応可能になると思いますがそれまではClassicでの対応が良さそうです。
上記手順で新しい価格表を作成できました。
これで新しい商談を登録する際に金額が変更された価格表を選択できるようになります。
価格表の無効化
新しい価格設定が行われた後、旧価格が登録されてしまうのはあまり良くありません。価格表には無効化の機能もあるので試してみました。
無効になった価格表は次のように選択できなくなります。
ここで心配なのが既に進行済の案件です。これらの商談の価格表が切り替わってしまうと正しい集計ができません。確認したところそれらの価格表は問題なく利用できました。
ただし、価格表を変更してしまうと無効になった価格表は選択できなくなります。(価格表の変更を行うと商品情報もリセットされるので気軽には実施できません。)
商品の無効化
価格表の無効化の挙動は確認できました。最後に商品の無効化について確認します。
新しい商品の提供により古い商品のサポートが終了することがあると思います。そうした商品が商談登録時に選択肢としてでてしまうのは少し不便です。そのため無効化することで整理することができます。商品を無効化すると商談商品の選択肢からは表示されなくなります。
既に登録済の商談商品はそのまま利用できるようです。
価格表にもそのまま登録されていますが、無効なデータとして登録されています。
新規価格表作成時にも選択できなくなります。
商品を無効にすると全ての価格表に影響がでます。ですのでもしも過去の価格表では引き続き利用したい場合は商品を無効にするのではなく価格表から除外するだけの方が良いかもしれません。
価格表と商品のメンテナンスはこんな感じでした。
SFDC:Lightning Loginと Two Factor Authencicationを試してみました
Lightning LoginとTwoFactorAuthencication有効化を試してみました。以前にも試したことのある機能なのですが、久しぶりに設定した際により便利にアップデートされていたので改めてやってみました。
これらの機能を有効化する場合はプロファイルではなく権限セットで個別に設定します。
権限セットのシステムのセクションで下記を有効化します。
- Lightning Login ユーザ
- ユーザインターフェースログインの 2 要素認証
用意した権限セットは対象のユーザに割り当てます。
この機能を利用するには専用のアプリが必要です。業務で利用する携帯電話にインストールしておきます。
権限を追加すると次回ログイン時に次の画面が表示されます。
先ほどインストールした専用アプリを起動して画面下側にある新規アカウントをタップします。すると2語の語句が表示されます。
これを認証画面に入力して次に進めます。
携帯電話のアプリで承認依頼が届くので承認します。
承認するとログインが実施されて次回ログインからTwoFactorAuthencication (2要素認証)が有効化されます。実際に試してみるとわかりますが、ログインするとアプリの通知が届き、アクセスすると承認ページが表示されています。承認するだけでログイン完了するのでランダムなキーワードを入力する必要はありません。
さらにLightning Login機能を有効化していると次ページが表示されます。
画面に従って有効化と承認を行います。
なお、Lightning Logiinの機能を利用するにはログイン時にユーザ情報を保存しておく必要があります。
※保存しても設定が反映されないことがあるのでLightning Loginが利用できるようになるまで少し時間がかかるのかも知れません。
Lightning Loginなどの無効化はユーザの詳細ページから行うことができます。
また、2要素認証の機能には「ワンタイムパスワードジェネレータ」と「Salesforce Authenticator」の二種類がありますが、基本的にはタップだけで認証できる「Salesforce Authenticator」を利用すればいいと思います。
Lightning Loginは「Salesforce Authenticator」を有効化した後により作業を効率化出来る機能となっています。
Lightning Loginを有効化した後
ログインページにユーザ名が表示されます。
クリックするとアプリに通知が届くので承認します。それでログイン完了です。
TwoFactorAuthencicationのメインであるスマートフォンの承認があればセキュリティが保証されているという考え方だと思います。認証デバイスは複数登録できないと思われます。必ずユーザの手元にある状態であることが重要です。携帯電話を使い回す場合は利用できません。また共用PCへのユーザ登録も避けて下さい。うっかり登録してしまった場合はユーザ詳細ページで無効にします。
関連記事
SFDC:Einsteinハンズオンに参加してサンプルアプリを作ってみました
Einsteinハンズオンに参加してサンプルアプリを作ってみました。
環境の準備
TrailheadのChallengeのところからPlayground環境を作成します。作成したらユーザの言語を英語に変更。
手順に従いオブジェクトを作成
- Cat オブジェクトを作成する
- Interested Person オブジェクトを作成する
上記オブジェクトのタブを作成する。
上記オブジェクトタブを含むアプリケーションを作成する。
テストデータを作成する。
次のようなエラーがでたら設定ミス。タブの名前変更が必要。
この設定ページから。ユーザの地域を英語にしておけばオブジェクト作成時に登録できたかも。
グローバル選択リストを作成する。
カスタムオブジェクトに選択リストを割り当てる
こんな感じで使えるようになれば準備OK。
2つのオブジェクトに追加した項目に値をセット。
2つのオブジェクトは参照関係などの紐付けがありませんが、Einsteinでマッピングできるというのが今回の目標みたいです。
追記
Interested Personの方は複数選択リスト型でした。
Einsteinの準備
Einstein プラットフォームサービスアカウントにサインアップする
https://api.einstein.ai/signup
einstein_platform.pemファイルをダウンロードできます。(同じメールアドレスで2つ目は作成できない。過去に作成済みの場合はそちらを利用。)
Salesforce に認証証明書を保存する
静的リソースではなくSalesforce Filesへのアップロードを行う。
未管理パッケージをダウンロード
ダウンロードURLはTrailheadに記載されています。
※事前にユーザのパスワードをリセットして自動生成ではないパスワードを指定すること。
未管理パッケージをインストールするとカスタム設定が追加されています。Einsteinにサインアップしたメールアドレスを登録します。
ApexクラスとLightning コンポーネントとLightningアプリケーションを作成
下記のApexクラスを作成します。(※コードはTrailheadを参照)
- EinsteinVision_Admin.cls
次にLightning コンポーネントを作成します。
- EinsteinVision_Admin_UI.cmp
最後にLightningアプリケーションを作成します。これは開発者コンソールではなく設定ページから。
アプリケーションへの追加はLightning Experieneタブを選択してそこで設定。
こんな感じで表示されればOK。
データセットの作成
Einsteinで分析するためのデータ・セットを作成します。
[https://developer.salesforce.com/files/Cats.zip]
URLを貼ってCreateボタンをクリック。こんな感じ。
Refreshするとこうなる。
最後にTrainボタンをクリックすると何かの処理が実行される。
Success100%と表示されたらOK。処理には少し時間がかかります。うっかり二回処理をうごかしたら動いてしまいました。
Cat (猫) オブジェクトに画像認識機能を追加
Lightningコンポーネントを作成
Cat レイアウトに Lightning コンポーネントを追加
猫の画像を分類する
これでアプリの準備が整いました。実際にデータを読み込ませて分類を試します。使うのはこの画像。
ファイルはTrailheadからダウンロード。
ファイルをアップするとデータが更新されることを確認できます。
アプリの利用例:Chatterと連携
グループを3つ作ります。
プロセスビルダーを作成します。
ざっくりこんな感じ。
猫画像をアップロードすると、「Bengal」と判別されて・・・
先ほど作成したプロセスどおりBengalのグループにメッセージが投稿されます。
里親になりたい人はこのChatterグループでメッセージを通知を受けることができるという流れになります。Chatter投稿の部分はおまけ的な感じで画像をアップすると猫の分類が自動で行われ値が更新されたことを確認するというのが今回のハンズオンの目的でした。
認証キーとなるeinstein_platform.pemファイルをSalesforceにアップしてEinstein APIを実行できたのだと思います。今回サンプルコードの中身は確認してみませんがこれをベースにAPIガイドを見ながらいろいろ試せるんだと思います。
SFDC:Salesforce AuthenticatorとApple Watch連携で2要素認証作業の効率化
Salesforce Authenticatorアプリを利用して2要素認証を有効化することでセキュリティをより強化にすることができます。
有効化の方法はこちら。
有効化して数日間試してみたのですが、やはりログイン時にスマートフォンのロックを解除して承認を行うのはなかなかに面倒でした。調べてみたところこの問題の解決方法が見つかりました。
Salesforce Authenticatorアプリですが、Apple Watchアプリもサポートしています。このApple Watchアプリの場合は手につけてロックを解除している状態の場合はすでに安全な状態が確保されています。そのためだと思うのですが、2要素認証の自動承認の機能が利用可能となっていました。
そのためSalesforceにログイン→認証ページが表示→Apple Watchのアプリが自動承認という流れでスムーズにログインすることができます。スマートフォンのロック解除の手間もなくなり非常に便利です。
この運用を行う場合はApple Watchのパスワード設定無しでの運用は禁止にする必要がありそうです。またおそらくスマートフォンが近くにない場合は自動承認が利用できない状態になってくれると思います。(検証はしてないですが)
Apple Watchによる自動承認機能ですが、Lightning Login機能を有効にしている場合は利用できませんでした。Lightning Loginはパスワードを入力不要になるためApple Watchだけでログイン可能になる問題を防ぐためかもしれません。ちなみに下記のページで有効化の要望が上がっていました。
参考
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:パートナーコミュニティユーザのアクセス権限と所有者の関係
パートナーコミュニティユーザに取引先と取引先責任者の作成権限を付与した場合、別会社の取引先と取引先責任者を作成することができます。その場合は作成者と所有者はパートナーコミュニティユーザとなります。
所有者を社内ユーザに切り替えた場合パートナーコミュニティユーザは自分が作成した取引先のデータでも参照不可となります。(共有設定等で非公開設定している前提です。)
わかってはいたけど、確認したついでにメモ。
SFDC:すべてのコミュニティ設定のワークスペースリンクにアクセスできなくなったときの対処方法
コミュニティのメンバー追加などの各種設定は「すべてのコミュニティ設定」のワークスペース(管理)リンクで設定ページに移動して行います。
このシステム管理者なら当然アクセスできるよねというリンクは、コミュニティのメンバーでないとアクセスできないルールがあります。
上記ルール自体把握できていませんでしたが、うっかり外すなんて状況普通ないよね...と思っているとこんなケースで発生してしまいました。
1. コミュニティ作成
2. メンバー追加で対象のコミュニティユーザプロファイルを追加
3. 本来なら管理者プロファイルも追加する必要があるのに忘れて保存
4. そのままログアウト
5. 次回ログインして設定の続きをやろうとすると・・・リンクが無い。
はじめ管理者権限があればなんとでもなるのだと思い、権限セットでコミュニティ管理の権限を付与したりしたのですが、アクセスできませんでした。ヘルプサイトを確認するとこの状況になってしまうとAPIでメンバーを登録する必要がある状況になってしまっているとのことです。
API を使用してコミュニティのメンバーシップを更新するには?
ヘルプに手順が記載されていますが、問題解決のためにはコミュニティのNetworkIdを取得する必要あります。コミュニティURLを右クリックして検証すると確認できます。
NetworkIDを取得したらこんな感じでCSVを作成します。
※profileIDは追加したい管理者プロファイルのIDをセットします。詳細ページのURLから取得できます。
CSVの準備ができたらデータローダでINSERTします。対象オブジェクトは[ネットワークメンバーグループ]です。
INSERTの前に
このデータ更新処理を行うには下記の権限が必要です。標準システム管理者プロファイルは値の変更ができないため権限セットで対応します。
コミュニティ管理にアクセスするためには、メンバーに「コミュニティの作成および設定」または「コミュニティの管理」権限も必要です。
正しくCSVを用意できていれば問題なくINSERTが実行されると思います。これでうっかり除外した管理者プロファイルをコミュニティのメンバーに追加できます。ワークスペースリンクが復活しているはずです。
SFDC:LEXのメッセージ機能が利用できない問題で対応したこと
Salesforce ClassicからLightning Experienceに移行する際にひとつ問題がありました。Chatterのメッセージ機能が利用できない問題です。
「メッセージ機能ですか?Salesforceではサポートを終了しました。そういうものなんです。」で押し通そうと思っていたのですが、GitHubに公開されている+Messageを使わせてもらうことで解決しました。
Lightning Experienceにはユーティリティバーというどのページからもアクセスできる機能が利用できます。これをつかってメッセージ機能にアクセスできるようにしました。
もともとはLightning Expcerienceが公開されるよりも前、Salesforce1モバイルアプリでの利用を想定されているのでLEXでの利用は想定されていません。ただ、非公開パッケージも公開されているので開発環境を用意することは簡単にできる状態でした。
SalesforceはLightning Design SystemというCSSフレームワークを公開してくれています。これを利用すればLEX的な見た目に調整することができそうでした。
実際にやってみたのがこちら。
少し強引にやってごまかしたところがありますが (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
ということでGitHubに公開されている+MessageのおかげでLightning Experienceにメッセージ機能を表示することができました。メッセージ通知の機能とかの要望がくるかもしれませんが、おそらく「メールで気づいてください」で押し通せると思います。いつか標準でサポートされればいいなと思います。(Skype for Salesforceが用意されていましたが試してみたところメッセージ機能とはすこし用途がことなりました。)
管理パッケージ対応
上記で用意したカスタマイズバージョンの+メッセージですが、組織にインストールするときは管理パッケージとしてインストールします。未管理パッケージでも同じ用にインストール可能ですが、管理パッケージにすることで開発時に組織コードに混ざって表示されないようになります。組織に合わせてバンバンカスタマイズしてく場合は未管理パッケージで気軽に開発できるようにした方がいいと思うのですが、通常さわらないのであれば管理パッケージの方が良いと思います。※書いた後にしったのですが、管理パッケージはパートナー組織じゃないとバージョンアップできない落とし穴がありました。
管理パッケージ化するときの注意
管理パッケージにすると組織に名前空間プレフィックスが追加されます。JSからApexクラスにアクセスする処理は下記の規則で修正が必要になります。
<名前空間プレフィックス>.SampleController.geSampleMethod()
先頭に名前空間プレフィックスをつけるだけなので規則がわかればそれほど大変ではないと思います。
追記
Salesforceモバイルアプリでも利用可能ですが、タブの作成が必要になります。またモバイルアプリで表示したときに気づいたのですが、paddingを入れとけばよかったです。