tyoshikawa1106のブログ

- Force.com Developer Blog -

SFDC:MavensMateサポート終了のお知らせと今後の開発ツールについて

Apex開発でいつもお世話になっているMavensMateのサポートが終了するとのことです。
f:id:tyoshikawa1106:20170808233553p:plain

MavensMate | Open Source Force.com IDEs


まだPreview版ですがVisual Studio Codeをつかった開発が公式でリリースされたのでそちらを使って下さいとありました。
f:id:tyoshikawa1106:20170808234024p:plain


こちらがVisual Studio Codeの拡張アプリサイトです。
f:id:tyoshikawa1106:20170808234158p:plain

Visual Studio Code Extension Pack for Salesforce DX - Visual Studio Marketplace


VSCode自体はどこからインストールできるのかと確認したところ、画面右上のリンクからダウンロードサイトに移動できました。


ダウンロードしたZipを開くとappファイルが入っています。
f:id:tyoshikawa1106:20170808234621p:plain


それをアプリケーションフォルダに移動すれば他のアプリと同じように利用できるようになります。
f:id:tyoshikawa1106:20170808234750p:plain


こんな感じです。
f:id:tyoshikawa1106:20170808234821p:plain


VSCodeをダウンロードしたら先程のサイトでINSTALLボタンを押します。
f:id:tyoshikawa1106:20170808235319p:plain


最初のポップアップはVSCodeの準備ができてなかったらここでダウンロードしてくださいといったメッセージです。このポップアップをクローズするとappファイルを開くかのメッセージが表示されます。
f:id:tyoshikawa1106:20170808235503p:plain


ここでappファイルを開くを選択すると拡張アプリのインストール画面が表示されます。
f:id:tyoshikawa1106:20170808235557p:plain

f:id:tyoshikawa1106:20170808235626p:plain


Google日本語訳化ですがおそらくこのインストール作業で開発に必要な複数のパッケージがインストールされているんだと思います。
f:id:tyoshikawa1106:20170808235957p:plain


開発にはSalesforce DXが前提になります。
f:id:tyoshikawa1106:20170809000136p:plain


詳細はGitHubのページにもまとめられています。


今回確認できたのはここまでになります。MavensMateもサポートが終了したのでできるだけはやく次の開発環境を準備する必要がありますが、GitHubからインストーラをダウンロードできますので明日から開発できなくなるというわけではないみたいです。


とはいえできるだけ早く次の対応を検討する必要はあると思います。

SFDC:Lightning Design SystemとreRender

Lightning Design SystemをつかうとLEXのスタイルを適用したVisualforce開発がやりやすくなりますが、apexタグを中心に開発を行うときは注意が必要な部分があります。その1つがreRenderをつかった画面更新処理です。


次のように正しく表示されている画面でも・・・

f:id:tyoshikawa1106:20170805205846p:plain

f:id:tyoshikawa1106:20170805205912p:plain


下記のようにactionからApex処理を呼び出しreRenderで画面更新を行うと・・

<apex:commandButton styleClass="slds-button slds-button--neutral" value="Set" 
    action="{!setValue}" reRender="form" />


一部スタイルが除外されてしまいました。
f:id:tyoshikawa1106:20170805210136p:plain

f:id:tyoshikawa1106:20170805210125p:plain


この問題はreRenderの更新対象を絞り込むことで回避できます。

<apex:commandButton styleClass="slds-button slds-button--neutral" value="Set" 
    action="{!setValue}" reRender="form-content,resultTable" />

f:id:tyoshikawa1106:20170805210301p:plain

f:id:tyoshikawa1106:20170805211014p:plain


こういう感じでapexタグとreRenderをつかった開発でもLightning Design Systemを利用することができるはずです。ただ、可能であればRemoteActionをつかってJavaScriptをメインに開発するときに利用した方が開発がしやすくなると思います。


SFDC:Salesforce DXの環境構築を試してみました - Part1

Salesforce DXの環境構築を試してみました。TrailheadのApp Development with Salesforce DXで学ぶことができます。


30日間利用できるトライアル環境が必要になります。
f:id:tyoshikawa1106:20170723194401p:plain

dx-signup | Salesforce Developers

ログイン後の画面です。
f:id:tyoshikawa1106:20170723200129p:plain


CLIは下記URLからインストールできます。
f:id:tyoshikawa1106:20170723194511p:plain


Mac用

https://sfdc.co/sfdx_cli_osx

f:id:tyoshikawa1106:20170723194654p:plain:w300


インストールできているかは下記コマンドで確認できます。

$ sfdx

f:id:tyoshikawa1106:20170723195218p:plain

便利なコマンド

利用可能なすべてのトピック

$ sfdx force --help

f:id:tyoshikawa1106:20170723195327p:plain


使用可能なすべてのコマンド

$ sfdx force:doc:commands:display

f:id:tyoshikawa1106:20170723195539p:plain

Dev Hubにログインする

$ sfdx force:auth:web:login -d -a DevHub

f:id:tyoshikawa1106:20170723200258p:plain


実行するとログインページに移動します。DevHubのトライアル環境にログインします。
f:id:tyoshikawa1106:20170723200419p:plain


ログインすると認証ページに移動します。
f:id:tyoshikawa1106:20170723200432p:plain


これでDev Hub環境にログインできました。
f:id:tyoshikawa1106:20170723200536p:plain

f:id:tyoshikawa1106:20170723200632p:plain


一度ログインすると次のコマンドでDevHub組織にアクセスできます。

sfdx force:org:open -u DevHub

f:id:tyoshikawa1106:20170723200936p:plain


サンドボックスにログインする場合は次のとおりです。

$ sfdx force:auth:web:login -a FullSandbox
$ sfdx force:auth:web:login -a DevSandbox


The Power of Aliasing - 別名の扱い

$ sfdx force:org:open -u FullSandbox
$ sfdx force:org:open -u MyScratchOrg
$ sfdx force:limits:api:display -u DevSandbox


すべての組織を表示 (画面キャプチャは組織なしの場合)

$ sfdx force:org:list

f:id:tyoshikawa1106:20170723201359p:plain

『--verbose』オプションでより詳細を確認できるそうです。

プロジェクトの作成

下記のコマンドでプロジェクトを作成できます。

$ sfdx force:project:create -n geolocation

f:id:tyoshikawa1106:20170723202923p:plain

f:id:tyoshikawa1106:20170723203003p:plain


ファイルのイメージ (Google翻訳版)
f:id:tyoshikawa1106:20170723203037p:plain

スクラッチ組織を作成する

$ sfdx force:org:create -s -f config/project-scratch-def.json -a GeoAppScratch

f:id:tyoshikawa1106:20170723203201p:plain

上のエラーがでたときはディレクトリが正しい確認します。
f:id:tyoshikawa1106:20170723203340p:plain


これで正常に実行できました。
f:id:tyoshikawa1106:20170723203435p:plain


コマンドのオプションの意味はこちら
f:id:tyoshikawa1106:20170723203524p:plain


作成できたかはDevHub組織で確認できます。
f:id:tyoshikawa1106:20170723203637p:plain

f:id:tyoshikawa1106:20170723204146p:plain

カスタムオブジェクトを作成

下記コマンドで作成したスクラッチ組織にアクセスできます。

$ sfdx force:org:open

f:id:tyoshikawa1106:20170723203959p:plain

f:id:tyoshikawa1106:20170723204200p:plain


スクラッチ組織にアクセスしたら取引先にカスタム項目を作成します。
f:id:tyoshikawa1106:20170723204617p:plain


権限セットを作成します。
f:id:tyoshikawa1106:20170723205050p:plain


次のコマンドで権限セットをユーザに割り当てることができます。

$ sfdx force:user:permset:assign -n Geolocation

f:id:tyoshikawa1106:20170723205154p:plain

f:id:tyoshikawa1106:20170723205233p:plain


変更をローカルプロジェクトに取込

Salesforce環境を反映するスクラッチ組織を作成し、そのスクラッチ組織内で直接いくつかの変更を加えました。今、魔法が起こります。1つのコマンドで、アプリケーションをビルドしたときに作成されたすべてのメタデータをプロジェクトに取り込むことができます。

$ sfdx force:source:pull

f:id:tyoshikawa1106:20170723205654p:plain


ベストプラクティスとして、プロジェクトに持ってきたソースをすぐにVCSにコミットします。スクラッチオルガンは一時的で一時的なものなので、ローカルに保存した作業のバックアップが常に必要です。

Salesforce DXはバージョンコントロールに依存しないため、好きなシステムを使用できます。GitHubで使用できるGitコマンドの例を次に示します。レポを初期化してGitHubに接続するには、次の1回限りのコマンドを使用します。

$ git init
$ git remote add origin [github ssh url]

これらのコマンドは、ファイルをリポジトリmasterにコミットします。

$ git add -A
$ git commit -m “Added custom object and permset”
$ git push origin master

どのVCSを使用する場合でも、.sfdxフォルダをリポジトリに追加しないように設定することをお勧めし ます。このフォルダには、スクラッチオーガニックの一時的な情報が格納されているため、VCSに後世向けに保存する必要はありません。gitでは、.gitignoreファイルに追加します。

VCSでは、更新されたオブジェクト定義が安全で健全です。しかし、関連するデータがなければそれほど興味深いものではありません。スクラッチオルガンには、選択したエディションに基づいた標準データが付属しています。ただし、作成するアプリや成果物に関連するサンプルデータを追加することは重要です。この例では、新しいカスタムLocationフィールドでサンプルデータを使用できます。Salesforce CLIを使用して、新しい複合フィールドにいくつか追加しましょう。

サンプルデータの作成

テストデータを作成します。
f:id:tyoshikawa1106:20170723210656p:plain


次のコマンドでクエリを実行して対象データをエクスポートできます。

$ sfdx force:data:tree:export -q "SELECT Name, Location__Latitude__s, Location__Longitude__s FROM Account WHERE Location__Latitude__s != NULL AND Location__Longitude__s != NULL" -d ./data

f:id:tyoshikawa1106:20170723210813p:plain


エクスポートしたファイルはJSON形式でdataフォルダ内に保存されます。
f:id:tyoshikawa1106:20170723210854p:plain


エクスポートした情報は次のコマンドでインポートできます。

$ force:data:tree:import --sobjecttreefiles data/Account.json


これでCLIのインストール、DevHub環境への接続、スクラッチ組織の作成、権限セットの割り当て、サンプルデータのエクスポートとインポートの方法まで確認できました。

補足

sfdx-project.jsonファイルにはどのような情報が含まれていますか?

ソースとメタデータをスクラッチ・オーガナと同期するために必要な情報

MyNewAppというプロジェクトワークスペースを作成するためのCLIコマンドは何ですか?

sfdx force:project:create -n MyNewApp

既存のアカウントをインポートするためのCLIコマンドの例は、異なるプロジェクトワークスペースのデータのサンプルですか?

sfdx force:data:tree:import --sobjecttreefiles data/Account.json

SFDC:Apexで『Content-Type: multipart/form-data』のAPIを実行

ApexではHttpRequestをつかって外部APIを実行することができます。『Content-Type: multipart/form-data』の実行がうまくいかずに困っていたのですが、実行方法を教えてもらったのでメモ。


下記のcurlコマンドで実行できる処理があります。

$ curl -X POST -H "Cache-Control: no-cache" -H "Content-Type: multipart/form-data;" -F "grant_type=password" -F “client_id=[sample]" -F "client_secret=[sample]" -F "username=[sample]" -F "password=[sample]" "http://[sample]”


これをApexから実行するとこうなります。

// Body
String boundary = '------------' + String.valueOf(DateTime.now().getTime());
String body  = '';
body += this.create_multi_param(boundary, 'grant_type', 'password');
body += this.create_multi_param(boundary, 'name', '[sample]');
body += this.create_multi_param(boundary, 'client_id', '[sample]');
body += this.create_multi_param(boundary, 'client_secret', '[sample]');
body += this.create_multi_param(boundary, 'username', '[sample]');
body += this.create_multi_param(boundary, 'password', '[sample]');
body += '--' + boundary + '--\r\n';

Http http = new Http();
HttpRequest req = new HttpRequest();
req.setHeader('Cache-Control', 'no-cache');
req.setHeader('Content-Length', String.valueOf(body.length()));
req.setHeader('Content-Type', 'multipart/form-data; boundary='+ boundary);
req.setEndpoint('http://[sample]');
req.setBody(body);
req.setMethod('POST');
HttpResponse res = http.send(req);
System.debug(res.getBody());

「create_multi_param」メソッドの処理はこんな感じ。

private String create_multi_param(String boundary, String name, String val){
    String param = '--' + boundary+'\r\n'
    + 'Content-Disposition: form-data; name="' + name + '"'
    + '\r\n\r\n' + val + '\r\n';
    return param;
}


これで『Content-Type: multipart/form-data』のAPIを実行できました。boundary変数に「----」やシステム日時をセットしていますがこれはユニークな文字列を生成するためのものです。これを区切り文字として使用すればいいとのことです。

その他のContent-TypeのAPI

GET処理の場合

Http http = new Http();
HttpRequest req = new HttpRequest();
req.setHeader('Cache-Control', 'no-cache');
req.setHeader('Authorization', 'OAuth ' + <token>);
req.setHeader('Content-Type', 'application/json; charset=utf-8');
req.setEndpoint('http://<sample>');
req.setMethod('GET');
HttpResponse res = http.send(req);


POST処理の場合

// API実行
Http http = new Http();
HttpRequest req = new HttpRequest();
req.setHeader('Cache-Control', 'no-cache');
req.setHeader('Authorization', 'OAuth ' + <token>);
req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
req.setEndpoint('http://<sample>');
req.setMethod('POST');
req.setBody(body);
HttpResponse res = http.send(req);


こんな感じです。

Apexコールアウトのサンプル

参考

こちらのリンクも教えてもらったのでメモ

SFDC:Platform Eventsを試してみました

Trailheadを見ながらPlatform Eventsの機能を試してみました。

Platform Eventsの作成

設定で「Platform Events」と検索。New Platform Eventボタンをクリックします。
f:id:tyoshikawa1106:20170717171844p:plain


ラベルや説明項目を入力します。
f:id:tyoshikawa1106:20170717172939p:plain


拡張子は__eとなりました。
f:id:tyoshikawa1106:20170717173013p:plain


検証用に下記項目を作成します。(詳細はTrailheadを確認のこと)
f:id:tyoshikawa1106:20170717173624p:plain

ReplayIdシステムのフィールドとイベントの保持

Salesforceはプラットフォームイベントを24時間保存します。Apexではなく、APIクライアントで保存されたイベントを取得できます。各イベントレコードには、ReplayIDというフィールドが含まれています。このフィールドは、イベントが発行された後にシステムに取り込まれます。各リプレイIDは、前のイベントのIDよりも高いことが保証されていますが、連続するイベントでは必ずしも連続している必要はありません。格納されているすべてのイベントを取得することも、取得したイベントのベースラインとしてイベントのリプレイIDを指定することもできます。

Salesforceはイベントレコードを一時的に保持していますが、SOQLまたはSOSLを使用してイベントレコードを照会することはできません。同様に、レポート、リストビュー、および検索のユーザーインターフェイスでイベントレコードを使用することはできません。CometDを購読し、ReplayIdオプションを使用している場合にのみ、過去のイベントを取得できます。次のユニットのイベントを購読する方法を示します。

イベントを公開する

アプリケーションがSalesforceプラットフォームにある場合は、Apexメソッドを使用するか、Process BuilderやCloud Flow Designerなどの宣言ツールを使用してイベントをパブリッシュできます。アプリが外部アプリの場合、Salesforce APIを使用してイベントを公開できます。

Apexを使用してイベントメッセージを発行する

設定画面でPlatform Eventsを作成後はApexで処理を行えばいいみたいです。

// Create an instance of the event and store it in the newsEvent variable
Cloud_News__e newsEvent = new Cloud_News__e(
           Location__c='Mountain City', 
           Urgent__c=true, 
           News_Content__c='Lake Road is closed due to mudslides.');

// Call method to publish events
Database.SaveResult sr = EventBus.publish(newsEvent);

// Inspect publishing result 
if (sr.isSuccess()) {
    System.debug('Successfully published event.');
} else {
    for(Database.Error err : sr.getErrors()) {
        System.debug('Error returned: ' +
                     err.getStatusCode() +
                     ' - ' +
                     err.getMessage());
    }
}
複数のイベントを公開する場合はこちら
// List to hold event objects to be published.
List<Cloud_News__e> newsEventList = new List<Cloud_News__e>();
// Create event objects.
Cloud_News__e newsEvent1 = new Cloud_News__e(
           Location__c='Mountain City', 
           Urgent__c=true, 
           News_Content__c='Lake Road is closed due to mudslides.');
Cloud_News__e newsEvent2 = new Cloud_News__e(
           Location__c='Mountain City', 
           Urgent__c=false, 
           News_Content__c='Small incident on Goat Lane causing traffic.');
// Add event objects to the list.
newsEventList.add(newsEvent1);
newsEventList.add(newsEvent2);

// Call method to publish events.
List<Database.SaveResult> results = EventBus.publish(newsEventList);

// Inspect publishing result for each event
for (Database.SaveResult sr : results) {
    if (sr.isSuccess()) {
        System.debug('Successfully published event.');
    } else {
        for(Database.Error err : sr.getErrors()) {
            System.debug('Error returned: ' +
                        err.getStatusCode() +
                        ' - ' +
                        err.getMessage());
        }
    }       
}
プロセスビルダーをつかった公開

f:id:tyoshikawa1106:20170717204911p:plain

Salesforce APIを使用してイベントメッセージを公開

sObject RESTエンドポイント:

/services/data/v40.0/sobjects/Cloud_News__e/

POSTリクエストの本文をリクエストする:

{
   "Location__c" : "Mountain City",
   "Urgent__c" : true,
   "News_Content__c" : "Lake Road is closed due to mudslides."
}

プラットフォーム・イベント・レコードが作成されると、REST応答はこの出力のようになります。ヘッダーは簡潔にするために削除されます。

HTTP/1.1 201 Created 

{   
   "id" : "e00xx000000000B",
   "success" : true,
   "errors" : [ ],
   "warnings" : [ ] 
}

Apexから実行

試しにApexから実行してみました。
f:id:tyoshikawa1106:20170717205910p:plain


結果はこちら
f:id:tyoshikawa1106:20170717205952p:plain


ひとまず正常にPlatform Eventsを公開できました。

Platform Eventsの購読

Apexトリガをつかって通知を確認するそうです。

// Trigger for listening to Cloud_News events.
trigger CloudNewsTrigger on Cloud_News__e (after insert) {    
    System.debug('Apex Trigger Go!');
    // List to hold all cases to be created.
    List<Case> cases = new List<Case>();
    
    // Get queue Id for case owner
    //Group queue = [SELECT Id FROM Group WHERE Name='Regional Dispatch' LIMIT 1];
       
    // Iterate through each notification.
    for (Cloud_News__e event : Trigger.New) {
        if (event.Urgent__c == true) {
            // Create Case to dispatch new team.
            Case cs = new Case();
            cs.Priority = 'High';
            cs.Subject = 'News team dispatch to ' + 
                event.Location__c;
            //cs.OwnerId = queue.Id;
            cs.OwnerId = UserInfo.getUserId();
            cases.add(cs);
        }
   }
    
    // Insert all cases corresponding to events received.
    insert cases;
}

f:id:tyoshikawa1106:20170717210423p:plain


ケースを作成するトリガを用意した後に、先程のApexをつかったPlatform EventsのINSERT処理を実行します。するとApexトリガが実行されて無事にケースが登録されました。
f:id:tyoshikawa1106:20170717211201p:plain


使い方はこんな感じで良いみたいです。

Apexテスト

専用のテストの書き方が用意されています。
f:id:tyoshikawa1106:20170717211327p:plain

f:id:tyoshikawa1106:20170717211348p:plain

@isTest
public class PlatformEventTest {
    @isTest static void test1() {
        // Create test event instance
        Cloud_News__e newsEvent = new Cloud_News__e(
            Location__c='Mountain City', 
            Urgent__c=true, 
            News_Content__c='Test message.');
        
        Test.startTest();

        // Call method to publish events
        Database.SaveResult sr = EventBus.publish(newsEvent);
        
        Test.stopTest();
        
        // Perform validation here
        // Check that the case that the trigger created is present.
        List<Case> cases = [SELECT Id FROM Case];
        // Validate that this case was found.
        // There is only one test case in test context.
        System.assertEquals(1, cases.size());
    }
}

CometDでプラットフォームイベント通知を購読する

f:id:tyoshikawa1106:20170717211439p:plain

f:id:tyoshikawa1106:20170717211457p:plain

f:id:tyoshikawa1106:20170717211521p:plain


この辺は試していないのでTrailhead要確認という感じです。


Platform Eventsの使い方はこんな感じでした。

SFDC:StandardControllerのaddFieldsとApexテスト

standardControllerのaddFieldsを利用すればクエリを投げずに追加で項目を取得することが可能です。

private void SampleController(ApexPages.StandardController stdController) {
    stdController.addFields(new List<String> {'Sample__c', 'Sample__r.Name'});
}


便利な処理ですが、テストクラスで下記エラーが発生することがあります。

System.SObjectException: You cannot call addFields when the data is being passed into the controller by the caller.


エラーの発生はテストクラス内で次のように対象オブジェクトをnewするような処理を書いたときに発生します。

SampleController cls = new SampleController(new ApexPages.StandardController(new Demo__c()));


回避方法を検索したところ、Test.isRunnningTestを利用する方法になるみたいです。

if (!Test.isRunningTest()) { 
    stdController.addFields(new List<String> {'Sample__c', 'Sample__r.Name'});
}      

参考