2014年8月20日 (水)

意外と知らないSalesforce Tips (6~7)

またもやこのシリーズです。
今回は6つめと7つめをお送りします。

6. VisualforceをRead-Onlyモードで実行する

Apexの実行には実はRead-Onlyモードというものがあります。
これ、何か良いことがあるのかというとガバナ制限が緩和されるというメリットがあります。

どうしても大量のデータを扱いたいといったケースなどには、特別な細工をせずに扱えるデータ件数が増えるので利用価値がありそうです。

Read-Onlyモードの設定の仕方には2通りの方法があります。

(1) Visualforceページ全体をRead-Onlyモードにする
(2) コントローラークラスのメソッドをRead-Onlyモードにする

どちらの方法でモード設定するかによって緩和される制限に差があります。

具体的な緩和されるガバナ制限の内容は下記になります。

・1トランザクションで取得可能なレコード数上限が緩和される

50,000件 → 1,000,000件

・繰り返しコンポーネントでの使用可能コレクションサイズの上限が緩和される

1,000件 → 10,000件
対象の繰り返しコンポーネントは<apex:repeat><apex:dataTable><apex:dataList>の3つ
※Visualforceページ全体をRead-Onlyモードにした時のみ適用されます。

ここからはRead-Onlyモードの設定の仕方について書いていきます。

まずは、Visualforceページ全体をRead-Onlyモードにする方法から。
これはすごく簡単で、<apex:page>タグにパラメータ「readOnly="true"」を追加するだけです。

使用例: Visualforce側

<apex:page readOnly="true" controller="ReadOnlyController" action="{!init}" >
    <apex:repeat value="{!accountList}" var="ac">
        <apex:outputField value="{!ac.Id}"/>
        <apex:outputField value="{!ac.Name}"/>
        <hr />
    </apex:repeat>
    <apex:form>
        <apex:commandButton action="{!save}" value="Save"/>
    </apex:form>
</apex:page>

使用例: コントローラー側

global class ReadOnlyController {

    public List<Account> accountList {get; set;}
    
    public PageReference init(){
        accountList = [SELECT Id, Name FROM Account];
        return null;
    }
    
    // Read-Onlyモードだとこの処理は失敗する
    public PageReference save(){
        update [SELECT Id, Name FROM Account LIMIT 1];
        return null;
    }
}

コントローラー側は特に何もする必要はありません。
ちなみに、Read-Onlyモードの時にコントローラー側でデータの更新をしようとすると「Too many DML statements」の例外が発生します。

次はコントローラーのメソッド単位でRead-Onlyモードを設定する方法です。
コントローラーのメソッドにRead-Onlyモードを設定するには「@ReadOnly」あのテーションを付与します。
ただ、どんなメソッドにもアノテーションを付けられる訳ではなく、次の条件を満たしている必要があります。

・ global もしくは public である
・ static である
・ @RemoteAction アノテーションが付いている

ちなみにVisualforceということを抜きにすれば「@ReadOnly」自体はwebserviceなどでも使用できます。
詳細はDeveloper's Guideを見てください。

Force.com Apex Code Developer's Guide - ReadOnly Annotation
https://www.salesforce.com/us/developer/docs/apexcode/Content/apex_classes_annotation_ReadOnly.htm

Visualforce Developer's Guide - Working with Large Sets of Data
https://www.salesforce.com/us/developer/docs/pages/Content/pages_controller_readonly_context.htm

では、実際の使用例です。

使用例: Visualforce側

<apex:page controller="ReadOnlyController2">
    <apex:includeScript value="//code.jquery.com/jquery-1.11.0.min.js" />
    <apex:includeScript value="//code.jquery.com/jquery-migrate-1.2.1.min.js" />
    <apex:includeScript value="//cdnjs.cloudflare.com/ajax/libs/knockout/2.2.1/knockout-min.js" />

    <div data-bind="foreach: accountList">
        <span data-bind="text: Id" /> / <span data-bind="text: Name" />
        <hr/>
    </div>
    
    <button data-bind="click:save1">Save1</button><br/>
    <button data-bind="click:save2">Save2</button><br/>
    
    <apex:form>
        <apex:commandButton action="{!save3}" value="Save3"/>
    </apex:form>

    <script>
        jQuery.noConflict();    
        jQuery(document).ready(function($){
            var viewModel = new (function(){
                var self = this;
                self.accountList = ko.observableArray();
                
                self.find = function(){
                    Visualforce.remoting.Manager.invokeAction(
                        '{!$RemoteAction.ReadOnlyController2.find}',
                        function(result, e){
                            if(!e.status){
                                alert('error:' + ko.toJSON(e));
                                return;
                            }
                            self.accountList(ko.utils.arrayMap(result, function(o){
                                return {
                                    Id: ko.observable(o.Id),
                                    Name : ko.observable(o.Name)
                                };
                            }));
                        },
                        {escape :false}
                    );
                };
                
                self.save1 = function(){
                    Visualforce.remoting.Manager.invokeAction(
                        '{!$RemoteAction.ReadOnlyController2.save1}',
                        function(result, e){
                            alert((e.status?"success":("failed"+ko.toJSON(e))));
                        },
                        {escape :false}
                    );
                };
                
                self.save2 = function(){
                    Visualforce.remoting.Manager.invokeAction(
                        '{!$RemoteAction.ReadOnlyController2.save2}',
                        function(result, e){
                            alert((e.status?"success":("failed"+ko.toJSON(e))));
                        },
                        {escape :false}
                    );
                };
                
                //initialize
                self.find();
            })();
            ko.applyBindings(viewModel);
        });
    </script>
</apex:page>

使用例: コントローラー側

global class ReadOnlyController2 {
   
    @ReadOnly @RemoteAction global static List<Account> find() {
        return [SELECT Id, Name FROM Account];
    }
    
    // Read-Onlyモードなのでこの処理は失敗する
    @ReadOnly @RemoteAction global static void save1() {
        update [SELECT Id, Name FROM Account LIMIT 1];
    }
    
    @RemoteAction global static void save2() {
        update [SELECT Id, Name FROM Account LIMIT 1];
    }
    
    public PageReference save3(){
        update [SELECT Id, Name FROM Account LIMIT 1];
        return null;
    }
}

ページ全体がRead-Onlyモードになるわけではないので通常のメソッドを呼び出してデータを更新することもできます。

いかがだったでしょうか。
機会があったら活用してみてください。

7. 変更セットへ楽に項目を追加する

変更セット使ってますか。
変更セットは便利といえば便利なのですが、そのUIはお世辞にも使い易いとは言えません。
特にカスタム項目数が数千件ある時に、そのなかから必要なものを選択する場合は一苦労です。

こんなときに次の手順を踏むと少しだけ項目を選ぶのが楽になります。

(1) 一覧で「▼増やす」のリンクをクリック

(2) URLに追加されるパラメータ「rowsperpage」を1000に書き換えたURLに移動

これで一気に一覧の表示件数を上限の1000件に変更することができます。
あとはソートするなり、ページ内検索するなりすればほんの少し楽に項目追加ができるようになります。

さらに手動でURLを書き換えるのすら面倒だという方のために一覧表示を1000件にするブックマークレットを用意してみました。
(やっていることは表示しているURLにパラメータ「rowperpage」があれば値を1000に変更し、なければ値を1000で追加して遷移させているだけです。)

javascript:(function(){var l=location;var h=l.href;if(!(/entityType=/i).test(h)){h+=('&entityType='+document.getElementById('entityType').value);} l.href=((/rowsperpage=[0-9]+/i).test(h))?h.replace(/rowsperpage=[0-9]+/ig,'rowsperpage=1000'):(h+'&rowsperpage=1000');})();

一応、Chromeで動作確認してあります。
もし良かったら使ってやってください。

それでは。

2014年8月18日 (月)

意外と知らないSalesforce Tips (4~5)

前回からの続きです。
今回は4つめと5つめをお送りします。

4. オブジェクトでトピックを利用する

Spring'14からChatterのトピック機能がオブジェクトでも利用できる様になりました。
トピックはTwitterで言うところのハッシュタグみたいなもので、レコードの整理に使うことができます。

・使うための準備
オブジェクトに対してトピック機能を有効化する必要があります。
Spring'14以降に作成した組織の場合はトピックを使用可能なすべての標準オブジェクトは、デフォルトで機能が有効になっている様です。

トピックの有効化は「カスタマイズ>トピック>オブジェクトのトピック」から行います。

Mame20140818_01

オブジェクトのトピックが有効になると、そのオブジェクト種別のレコードで公開タグが無効になるので、公開タグを使っている場合は注意が必要です。

・使ってみる
トピックをレコードに追加するには詳細画面を開いて、タイトルの下にある「クリックしてトピックを追加」リンクをクリックします。
(すでにトピックが追加済みの場合はリンク名が「トピック」になります)

Mame20140818_02

トピック名を入力して「完了」をクリックするとトピックが追加されます。

Mame20140818_03

追加されたトピック名をクリックすると、対象のトピックが付与されているレコードが一覧で表示されます。

Mame20140818_04

Mame20140818_05

また、トピックはビューの絞り込み条件に指定することもできます。
トピックを絞り込み条件に指定する場合は、絞り込みの項目で「トピック」を選びます。

Mame20140818_06

次はApexからトピックを操作してみます。
ConnectApiネームスペースのTopicsクラスがこの辺りの処理の為のメソッドを持っています。

トピックを追加するレコードのIDとトピック名を指定してトピックを追加します。
(第1引数はコミュニティのIDでコミュニティに対してトピックを追加する時でなければ、nullを指定します。)

String communityId = null;
String recordId = 'a04U000000Jj29h';
String topicName = 'トピックテスト';
ConnectApi.Topic objTopic = ConnectApi.Topics.assignTopicByName(communityId, recordId, topicName);

トピックのIDを指定して削除します。
(第1引数のコミュニティIDの扱いは追加の時と同じです。)

String communityId = null;
String topicId = '0TOU00000008ePB';
ConnectApi.Topics.deleteTopic(communityId, topicId);

トピックが追加されているレコードの一覧はTopicAsssignmentオブジェクトから取得できます。

Select Id, EntityId, TopicId FROM TopicAssignment WHERE TopicId = '0TOU00000008eP6OAI'

EntityIdにレコードのID、TopicIdにトピックのIDが入っています。
(組織でコミュニティが有効化されている場合はオブジェクトにNetworkIdカラムが追加され、ここにコミュニティのIDが入ります。)

・権限
トピック機能を扱うにはトピックの権限とレコードに対する権限が必要です。

一般ユーザ権限説明
トピックを割り当てる レコードへのトピックの追加・削除に必要です。
トピックを作成 トピックそのものを作成するのに必要です。
トピックを削除 トピックそのものを削除するのに必要です。
トピックを編集 トピックそのものを編集するのに必要です。

トピックを割り当てるには対象のレコードを更新できる必要があります。
参照権限のみの場合は、参照はできますが、割り当てはできません。

トピック機能は種類の異なるオブジェクトを関連づける事もできるので、うまく使って効果的にレコードを整理してみてください。

5. レコードの閲覧日時と参照日時を参照する

最近使ったXXX的な一覧を作ってみたくなったことはありませんか。
まさにそのためだけに作られたような項目が Summer'13 から各オブジェクトに追加されています。

「LastReferencedDate」と「LastViewedDate」がその項目です。

この2つの項目はどちらも読み取り専用の項目で、違いは項目の値が更新されるタイミングです。
詳細は次の表にまとめましたのでご覧ください。

 LastReferencedDateLastViewedDate
レコードの詳細画面を見たとき 更新される 更新される
レコードを画面からルックアップして参照項目に設定したとき 更新される 更新されない

このように画面で操作したときは上のタイミングで項目が更新されるのですが、Apexからの操作の場合は何もしなければこの2項目は更新されません。
Apexから更新したい場合は次のオプションを付けたSELECT文を発行することでこれらの項目を更新することができます。

詳細画面を見たときと同じ更新を行う
  ・・・ 「FOR VIEW」オプションを付与します。

SELECT Id,Name,AccountId,LastViewedDate,LastReferencedDate FROM Contact
WHERE ID='003U00000024dJgIAI' FOR VIEW

レコードを画面からルックアップして参照項目に設定したときと同じ更新を行う
  ・・・ 「FOR REFERENCE」オプションを付与します。

SELECT Id,Name,AccountId,LastViewedDate,LastReferencedDate FROM Contact WHERE
ID='003U00000024dJgIAI' FOR REFERENCE

機会があったら使ってみてください。

それでは。

2014年8月 8日 (金)

意外と知らないSalesforce Tips

こんにちは。エンジニアの若葉です。

先日、社内で「意外と知らないSalesforce Tipes 10撰」という題目で
ネタを持ち寄ったので、とりあえず3つ程ご紹介しますね。

1. 標準ボタンによる画面リロード

まず、標準のリストビューにビューの選択状態を保ったままリロードする方法をご紹介します。

例えば、申請→処理中→完了のような状態が遷移するオブジェクトがある場合、

①各ステータスに応じたリストビューを設定してステータスごとにデータを管理する

②一括で状態を更新するApexを書いて、リストビューにカスタムボタンを配置

③カスタムボタンを実行したら、更新ボタンでリフレッシュして、現在のビューから当該のデータを追い出す

なんてケースが良くあるかと思います。


ただ、実行と更新が2段階に分かれていているので、処理件数が多い場合には、
更新ボタンを押し忘れて、処理済みのデータを対象に再度更新処理を実行してしまう恐れが残ります。
そこで、カスタムボタンに記述するJavascriptに以下のコードを追加しておくと、

var views = ListViewport.instances, i;
for (i in views) {
    if (views.hasOwnProperty(i)) {
// 現在のリストビューを保持したままリロード views[i].refreshList(); return; } }

現在のビューを保ったままリロードができます。
事故の防止、操作の簡易化が図れてとても便利ですね。

2. Sitesで強制SSL

次ですが、Sitesで公開WEBフォームを作る際、住所情報などを登録するので
SSLアクセスが必須となるケースが良くあります。

設定によってSSLアクセスを強制できる助かるのですが、残念ながらその設定は無いようです。
そこで、

①フォームを表示するリクエストをチェックして、

②httpだったらhttpsにしてリダイレクトする

という感じの機能を実装するのですが、URL関連のAPIがわかりにくく、
毎回リファレンスを見直しているので、この際整理してみました。

使用するAPI

プロトコルの確認 URL.getProtcol()
現在のセキュアなドメインの取得 Site.getBaseSecureUrl()
現在のPathの取得 URL.getPath()

Sitesのドメインはsand/prod間でドメインが異なるので、環境差分を吸収するのに一工夫必要です。このAPIを知るまでは、この環境違いへ対応するのにカスタム表示ラベルに切り出すなど、悲しい対応をしていました。。

また、Pathの取得はちょっと注意が必要で、取得できるPathは実際のURL中に現れない「/apex」が含まれてしまうので、リダイレクトさせるには、これを除外する必要あります。

使用例

// 現在のURL情報
public class CurrentUrl {
    public Boolean isSecure() {
        return (URL.getCurrentRequestUrl().getProtocol() == 'https');
    }
    public Boolean isInSecure() {
        return !isSecure();
    }
    
    public String getSecureSiteUrl() {
        String baseUrl = Site.getBaseSecureUrl();
        String path = URL.getCurrentRequestUrl().getPath();
        Integer pos = path.lastIndexof('/');
            return baseUrl + path.substring(pos, path.length());
        }
    }
    

こんな感じで、現在のURLを取り扱うクラスを定義しておくと、
コントローラからシンプルに呼び出せて、使いまわしが簡単になります。

    // 呼び出し側
    CurrentUrl cu = new CurrentUrl();
    if (cu.isInSecure()) {
        // httpsでリダイレクト
        PageReference pr = new PageReference(cu.getSecureSiteUrl());
        pr.setRedirect(true);
        return pr;
    }
    return null;
}

3. MIXED_DML_EXCEPTIONの避け方

最後です。テストコードが失敗する場合に、

MIXED_DML_OPERATION, 非設定オブジェクトを更新した後の設定オブジェクト上の DML操作 (またはその逆) は、許可されていません

この例外が原因となっているケースがたまにあります。


サンプル

@isTest
public static void test() {
    User u = getUser();
    insert u;//・・・(1) 設定オブジェクト

    MixedDmlSample md = new MixedDmlSample();
    Id accountId = md.createAccount(u.Id);
    System.assertEquals('flect', [SELECT Name FROM Account WHERE Id=:accountId][0].Name);
}

// プロダクトコード
public class MixedDmlSample {
    public Id createAccount(String userId) {
        Account a = new Account(Name='flect');
        insert a;//・・・(2) 非設定オブジェクト
        User u = [SELECT LastName FROM User WHERE Id=:userId];
        Contact c = new Contact(LastName=u.LastName, Account=a);
        insert c;//・・・(3) 非設定オブジェクト
        return a.Id; 
    }
}

これは、例えば上記のサンプルでいうと、User(1)とAccount(2)は、同一トランザクションでInsert/Updateできない、というような制限です。

詳細は以下のリンクから確認できますが、制限の理由は、UserもAccountも、アクセスする側とされる側、としてレコードの共有情報を更新する必要があり、それは各々のDMLを直列に確定させないと正しく処理できない可能性があるためのようです。

http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_dml_non_mix_sobjects.htm

上記のサンプルのように、プロダクトコードのみでは該当しないけど、テストデータとしてUserを事前にInsertすることは多いので、この制限はテストコードでよく遭遇します。

制限だから仕方いないかと思いきや、そこはちゃんとテストコード用に回避策が用意されています。

https://www.salesforce.com/us/developer/docs/apexcode/Content/apex_dml_non_mix_sobjects_test_methods.htm

回避例

@isTest
public static void test() {
    User thisUser = [SELECT Id FROM User WHERE Id = :UserInfo.getUserId()];
    System.runAs (thisUser) {//・・★ これで囲うだけ
    User u = getUser();
    insert u;

    MixedDmlSample md = new MixedDmlSample();
    Id accountId = md.createAccount(u.Id);
    System.assertEquals('flect', [SELECT Name FROM Account WHERE Id=:accountId][0].Name);
    }
}


System.runAs ブロックで囲ってあげれば、それだけでOKです。
アクセス制御に関するテストはどうなるんだろ?とか追加の調査の余地はありますが、とりあえずは回避策があるので良しとしておきます。

それでは。

2014年7月 8日 (火)

Apex一括メール送信の制限

お久しぶりです、こころです。

Apexからの一括メール送信には

”1 組織あたり 1 日に合計 1,000 個の外部メールアドレスまで” 

つまり

”アドレス重複を含んで1日1000通まで”

というガバナ制限があります。

1日で閾値ギリギリまでメール配信されそうな要件があり、閾値を超えるとどうなるのか試してみました。

例えば1回に75通Apexからメールを送るとして、
13回送信すると975通になります。
14回目にどうなるのでしょうか?

①エラーになってメールは1通も配信されない
②25通まで配信され1000に達した後は配信されない
③なんと1000通超えて配信完了する

①であって欲しいと思いながらメールの一括送信用メソッドMassEmailMessageのレファレンスを確認すると、

sendEmail メソッドで送信される一括メールメッセージは、送信する組織の 1 日の一括メール制限にカウントされます。この制限値に達すると、MassEmailMessage を使用する sendEmail メソッドへのコールは拒否され、ユーザは MASS_MAIL_LIMIT_EXCEEDED エラーコードを受信します。

とあり、MASS_MAIL_LIMIT_EXCEEDEDの解釈が問題となりそうです。

DeveloperEdition(配信制限数は10通/日)でテストしてみました。

Apexクラス

public class sendMassEmail {
    public List contactIds = new List();

    public static void sendMass() {
    List conObj = [SELECT id, name FROM Contact LIMIT 3];//配信対象件数
    List contactIds = new List();
    for(Integer i=0; i < conObj.size(); i++){
        contactIds.add(conObj[i].Id);
    }
    Messaging.MassEmailMessage mail = new Messaging.MassEmailMessage();
    mail.setTargetObjectIds(contactIds);
    mail.setTemplateID('00X10000000NzIC'); //送信メールのテンプレートのID
    Messaging.sendEmail(new Messaging.MassEmailMessage[] {mail}); 
    }
}

ページ

<apex:page controller="sendMassEmail">
    <apex:form >
        <apex:commandButton action="{!sendMass}" value="一括送信"/>
    </apex:form>
</apex:page>

ボタンを1回押すと3通の一括送信メールが送られます。DeveloperEdition組織の一括メール送信制限は10通です。3回ボタン押下で9通配信されますが、4回目で何が起こるでしょう。

※一括メール送信はログインユーザで実行されます。

20140708_14439_2

3回クリックして4回目は、、

20140707_234827_2

キタ!! System.EmailException: SendEmail failed. First exception on row 0; first error: MASS_MAIL_LIMIT_EXCEEDED, Failed to send email: []

のエラーになりました。

デバッグログを確認すると

00:47:08.033 (33768787)|EXCEPTION_THROWN|[42]|System.EmailException: SendEmail failed. First exception on row 0; first error: MASS_MAIL_LIMIT_EXCEEDED, Failed to send email: []
00:47:08.034 (34189036)|SYSTEM_METHOD_EXIT|[42]|Messaging.sendEmail(LIST<Messaging.Email>)
00:47:08.034 (34251510)|FATAL_ERROR|System.EmailException: SendEmail failed. First exception on row 0; first error: MASS_MAIL_LIMIT_EXCEEDED, Failed to send email: []


4回目の一括配信メールは失敗して送信されませんでした。
(もちろん最初の9通は届きましたが、それ以降は1通も届いていません。)

設定>ログ>メールログファイル よりメールログをリクエストして確認すると、やはり9通です。

1

というわけで予想通り①でした。

実は検証を仕込んでいる間に、

SendEmailResult オブジェクトで返されるすべてのエラーは、メールが送信されなかったことを表します。

の文字をApexレファレンス中(P294)に見つけてしまって、検証するまでもなく①な気が限りなくしていたのですが、念のため確認してみました。

Sandboxで1000通の制限に対してのテストをしてみましたが、同様に一括メール件数が閾値を超えるリクエストの場合は配信はされません^^

Apexからの一括メール配信を設計する上で参考にしてみてください。

2014年6月 9日 (月)

OpenID Connectを使用したシングルサインオン

エンジニアの木下です。

今回はWinter'14でサポートされたOpenID Connectを使用したSSOを試してみましたので設定方法をまとめてみました。

1. [Google]OAuthクライアントの登録

まずはOAuthクライアントIDを作成します。

Google Developers Console から"APIs & Auth" > "Credentials Oath"と進み、"Create new Client ID"をクリックします。

101createoauthclient

ApplicationTypeは"Web application"を選択します。
AUTHORIZED JAVASCRIPT ORIGINS、AUTHORIZED REDIRECT URIは後ほど設定します。

02settingapplicationtype

Client IDを作成後、作成されたClient IDとClient Secretなどが表示されます。
これをSalesforceへ設定します。

103createdclientid

"APIs & Auth" > "Consent screen"にある"PRODUCT NAME"で最初のログイン時に表示される名前を設定します。

104consentscreensetting

2. [Salesforce]認証プロバイダの設定

Salesforceにログイン後、"設定" > "セキュリティのコントロール" > "認証プロバイダ"と進み、新規ボタンをクリックし以下を設定します。
プロバイダタイプ:Open ID Connect
コンシューマ鍵:1で作成したClient ID
コンシューマの秘密:1で作成したClient Secret
承認エンドポイント URL:https://accounts.google.com/o/oauth2/auth
トークンエンドポイント URL:https://accounts.google.com/o/oauth2/token
ユーザ情報エンドポイント URL:https://www.googleapis.com/oauth2/v3/userinfo

保存するとクライアント設定が表示されるので1で作成したOAuthクライアントを編集します。AUTHORIZED REDIRECT URIにコールバック URLを設定します。AUTHORIZED JAVASCRIPT ORIGINSは空で良いようです。

106createdauthprovider

3. [Salesforce]登録ハンドラの実装

登録ハンドラはAuth.RegistrationHandlerインターフェースを実装する必要があります。実装するメソッドはcreateUserとupdateUserの二つです。

createUserは初回ログイン時に、updateUserは二回目以降のログイン時に呼ばれるメソッドです。Auth.UserDataにはidentifier(GoogleのID)やemail(メールアドレス)などのプロパティがあるのでcreateUserではそれらの情報をもとにユーザを特定します。

updateUserではここでは特になにもしていませんが、Auth.UserData dataからユーザの情報を更新などできそうです。

public class GoogleHandler implements Auth.RegistrationHandler{
    public User createUser(Id portalId, Auth.UserData data){
        User u = [SELECT Id FROM User WHERE Username = :data.email];
        return u;
    }

    public void updateUser(Id userId, Id portalId, Auth.UserData data){
    }
}

4. [Salesforce]ログイン

最後にログインページの設定です。シングルサインオン初期化 URLからでもSSOできますが、今回はドメインを作成し、ログインページのSSOのボタンからログインしてみます。

"ドメイン" > "私のドメイン"からドメインを作成し、ログインページのブランド設定で認証サービスに2で設定した認証プロバイダ名「Google Login」を追加します。

20140609_63331

通常のログインボタンの下に「Google Login」が表示されました。このボタンからSSOでログインができます。

設定も簡単でログインの手間も省けて嬉しい限りです。

2014年6月 1日 (日)

Visualforce Remote Objectsを使ったデータ更新

エンジニアの谷隈です。

Spring'14からパイロットリリースされている新機能「Visualforce Remote Objects」でのデータ取得について前回検証しました。
今回は更新処理について検証します。

ちなみに「Visualforce Remote Objects」というのはVisualforce上からJavaScript経由でセールスフォース上のオブジェクトデータを操作(取得・作成・更新・削除)する仕組みです。

○使い方

「Visualforce Remote Objects」を使用する際の大まかな手順は次の2ステップになります。
データ更新をする場合でも大まかな手順はデータ取得の場合と同じです。

Step

まずはイメージを掴むための、簡単なサンプルから。

コード例:Visualforce

<apex:remoteObjects >
  <apex:remoteObjectModel name="Test__c" fields="Id, Name, DateField__c, DateTimeField__c, CreatedDate"/>
</apex:remoteObjects>

「Visualforce Remote Objects」でオブジェクトのデータを操作する場合には、どのオブジェクトのどのフィールドを使用するかどうかをapexタグで宣言する必要があります。
上のコードはVisualforceページにTest__cオブジェクトを使用するためのタグを記載したコード例です。
Test__cオブジェクトのId, Name, DateField__c, DateTimeField__c, CreatedDateフィールドへの使用を宣言しています。
apexのタグの使い方はデータ取得の場合と同じです。

apexタグのドキュメントは下記です。
apex:remoteObjects
http://www.salesforce.com/us/developer/docs/pages/Content/pages_compref_remoteObjects.htm
apex:remoteObjectModel
http://www.salesforce.com/us/developer/docs/pages/Content/pages_compref_remoteObjectModel.htm
apex:remoteObjectField
http://www.salesforce.com/us/developer/docs/pages/Content/pages_compref_remoteObjectField.htm

コード例 JavaScript

var obj = new SObjectModel.Test__c();
obj.set('Id', 'a00U0000006eU8fIAE');
obj.set('DateField__c', new Date());

obj.update(function(error, result, event){
    // 更新完了後の処理
});

JavaScript側のコードは、apexタグにより生成されたモデルを利用してデータの操作を行います。
データの更新には「update」functionを使用します。
上の例は最も使うであろう呼び出し方で書いていますが、「update」function にはいくつか呼び出し方にバリエーションがあります。

○JavaScriptコードについての詳細

「Visualforce Remote Objects」利用時のJavaScriptコードの基本的な流れは次の2ステップです。

1. apexタグにより生成されたモデルのオブジェクトを生成
2. 生成したオブジェクトのメソッドを呼び出す。

データの更新にはapexタグで生成されたモデルの「update」functionを使います。

Updating Records with Remote Objects
https://www.salesforce.com/us/developer/docs/pages/Content/pages_remote_objects_using_update.htm

パターン① シンプルなupdate

「update」functionの一番シンプルな呼び出し方は、引数無しでfunctionを呼び出すだけの形になります。

var obj = new SObjectModel.Test__c();
obj.set('Id', 'a00U0000006eU8fIAE');
obj.set('DateField__c', new Date());

obj.update();

ドキュメントには上の形の変形パターンとして引数でセットする値をオブジェクトで渡す形の説明がありましたが、実際に試してみたところこちらは正しく動作しませんでした。
何か試し方が悪かったのかもしれません。

var obj = new SObjectModel.Test__c();
obj.update({
	Id: 'a00U0000006eU8fIAE',
	DateField__c: new Date()
});

いずれにしろ、シンプルな呼び出しのパターンではupdate処理が成功したかどうかが確認できないので使うケースはあまり無さそうです。

パターン② updateの結果をコールバックfunctionで処理する

「update」functionは引数に実行結果を処理するコールバックfunctionを指定して呼び出すこともできます。

var obj = new SObjectModel.Test__c();
obj.set('Id', 'a00U0000006eU8fIAE');
obj.set('DateField__c', new Date());

obj.update(function(error, result, event){
    // 更新完了後の処理
});

使うとしたらこれが一番活躍するパターンだと思われます。
引数にコールバックfunctionを指定します。

更新処理完了後、ここで指定したfunctionが呼び出されるので、処理結果に応じた後処理を行うことができます。

function callback(error, results, event) { // ... }

基本、retrieveの時と同じですが一部格納されるデータに違いがあります。

コールバックfunctionの引数

引数説明
error エラーメッセージが格納されます。正常終了時はnullです。
results 更新処理で影響を受けたオブジェクトのIDが配列で返されます。
event DML操作の詳細情報が格納されます。

Remote Objects Callback Functions
http://www.salesforce.com/us/developer/docs/pages/Content/pages_remote_objects_callback_functions.htm

パターン③ 複数レコードを一括で更新する

最後は複数レコードを一括で更新する呼び出し方です。
第1引数に更新するオブジェクトのIDの配列、第2引数に更新する値を指定したオブジェクト、第3引数にコールバックfunctionを指定します。

var obj = new SObjectModel.Test__c();
obj.update(
    ['a00U0000006eU8fIAE','a00U0000006eU8eIAE']
    ,{DateField__c: new Date(2020,8,1)}
    ,function(error, result, event){
        // 更新完了後の処理
    }
);

この一括更新は指定した全オブジェクトを同じ値に更新することしかできないので、使いどころは限られてくると思います。

○その他気になるポイント

・バリデーションエラー

バリデーションエラー発生時の動きは必須項目設定、入力規則、その他の場合、すべて同じです。
コールバックfunctionのerrorオブジェクトにエラーメッセージが設定されて帰ってきます。

function callback(error, results, event) { // ... }

簡易にチェックするならerrorがnullでないかで確認が可能です。

また、同様な情報はeventオブジェクトにも含まれています。

・トランザクション

トランザクションはDML操作のfunction呼び出し単位になります。
トランザクションを考慮した処理を実装したい場合は別の方式を考える必要がありそうです。

Best Practices for Using Remote Objects
https://www.salesforce.com/us/developer/docs/pages/Content/pages_remote_objects_considerations.htm

・オブジェクト権限と項目レベルセキュリティ

オブジェクト権限に読み取りしかない場合、updateの呼び出し結果はエラーになります。
項目レベルセキュリティについては読み取りのみの項目の場合も更新できました。
これは裏側の実装がapexだからということなのかもしれません。

Visualforce Remote Objects
http://www.salesforce.com/us/developer/docs/pages/Content/pages_remote_objects.htm

・日付/日時型の扱い

日付/日時型の値は取得時にはローカルタイムゾーンでDate型に変換されて表示されます。
ここまでは問題ありません。
ところが保存時はJavaScript上のDateの時間がGMT+0000扱いされている様で、SF上で9時間加算されます。
結果、日本環境だと取得してそのまま保存するだけで9時間加算されてしまいます。
今がパイロットだからということであれば良いのですが、修正されない様であれば注意が必要です。

○おまけ

Summer'14のリリースでVisualforce Remote Objectsに機能追加がされる様です。
主な追加機能。
・検索処理時に指定できる条件の追加(lte, gte, ne, order by)
・upsert()操作の追加
・デフォルトの処理のオーバーライドが可能になる

Visualforce Remote Objects Enhancements
https://developer.salesforce.com/releases/release/Summer14/Visualforce+Remote+Objects+Improvements

2014年5月28日 (水)

Visualforce Remote Objectsを使ったデータ取得

エンジニアの谷隈です。

Spring'14からパイロットリリースされている新機能「Visualforce Remote Objects」を試してみました。
「Visualforce Remote Objects」というのはVisualforce上からJavaScript経由でセールスフォース上のオブジェクトデータを操作(取得・作成・更新・削除)する仕組みです。
APIコール数を消費しない、Apexコードの実装が不要といった特長があります。

今回はこの機能のうちデータ取得についての検証を行いました。

○使い方

「Visualforce Remote Objects」を使用する際の大まかな手順は次の2ステップになります。

Step

まずはイメージを掴むための、簡単なサンプルから。

コード例:Visualforce

<apex:remoteObjects >
  <apex:remoteObjectModel name="Test__c" fields="Id, Name, DateField__c, DateTimeField__c, CreatedDate"/>
</apex:remoteObjects>

「Visualforce Remote Objects」でオブジェクトのデータを操作する場合には、どのオブジェクトのどのフィールドを使用するかどうかをapexタグで宣言する必要があります。
上のコードはVisualforceページにTest__cオブジェクトを取得するためのタグを記載したコード例です。
Test__cオブジェクトのId, Name, DateField__c, DateTimeField__c, CreatedDateフィールドへの使用を宣言しています。

コード例 JavaScript

var som = new SObjectModel.Test__c();
som.retrieve({limit:22}, function(err, records){
	// データ取得後の処理
});

JavaScript側のコードは、apexタグにより生成されたモデルを利用してデータの操作を行います。
データの取得には「retrieve」functionを使用します。
retriveには第1引数に取得条件、第2引数に取得したデータを処理するコールバックfunctionを渡します。

実際にはこの後、画面に表示するための処理が必要になりますがSF上からデータを取得するという部分はこれだけです。

○apexタグについての詳細

サンプルはミニマムな例でしたが、「Visualforce Remote Objects」のapexタグにはいくつかオプションがあります。
ここでは指定できるオプションについて見ていきたいと思います。

・apex:remoteObjectsの属性

属性説明
jsNamespace タグにより生成されるJavaScriptの名前空間を指定できます。省略時はSObjectModelになります。
id 他のapexタグと同じです。
rendered 他のapexタグと同じです。

・apex:remoteObjectModelの属性

属性説明
name 利用するオブジェクトのAPI参照名を指定します。(必須)
jsShorthand 利用するオブジェクトのJavaScriptコード上での名前を指定します。省略時はnameと同じになります。
fields 利用するフィールドのAPI参照名をカンマ区切りで指定します。ここで指定しない場合はapex:remoteObjectFieldタグで利用するフィールドを指定します。
id 他のapexタグと同じです。
rendered 他のapexタグと同じです。

・apex:remoteObjectFieldの属性

属性説明
name 利用するフィールドのAPI参照名を指定します。(必須)
jsShorthand 利用するフィールドのJavaScriptコード上での名前を指定します。省略時はnameと同じになります。
id 他のapexタグと同じです。
rendered 他のapexタグと同じです。

各タグのドキュメントは下記です。
apex:remoteObjects
http://www.salesforce.com/us/developer/docs/pages/Content/pages_compref_remoteObjects.htm
apex:remoteObjectModel
http://www.salesforce.com/us/developer/docs/pages/Content/pages_compref_remoteObjectModel.htm
apex:remoteObjectField
http://www.salesforce.com/us/developer/docs/pages/Content/pages_compref_remoteObjectField.htm

○JavaScriptコードについての詳細

「Visualforce Remote Objects」利用時のJavaScriptコードの基本的な流れは次の2ステップです。

1. apexタグにより生成されたモデルのオブジェクトを生成
2. 生成したオブジェクトのメソッドを呼び出す。

データの取得にはapexタグで生成されたモデルのretrieve functionを使います。

Retrieving Records with Remote Objects
https://www.salesforce.com/us/developer/docs/pages/Content/pages_remote_objects_using_retrieve.htm

retieve function

retrieve functionは検索条件と取得結果を処理するコールバックfunctionの2つの引数を受け取ります。

RemoteObjectModel.retrieve({criteria}, callback_function)

・retieve functionの第1引数

第1引数にはデータを取得する条件をオブジェクトに格納して指定します。

プロパティ説明
limit データ取得件数を指定します。省略時は20で最大が100です。
offset データを取得する際に何件目から取得するかを指定します。省略時は1です。
Where データの取得条件を指定します。現在はeq,lt,gt,like,and,orが指定できます。

※limitに100以上の値を指定した場合の動きについて
エラーにはなりませんが100件までしかデータを取得できませんでした。

※Where条件の指定イメージ

{
  limit: 100,
  where: {
    or: {
      DateField__c: {gt: new Date(2014,05,01)},
      Name : {like: '田中%'}
    }
  }
}

Where条件の指定は上のような形式で記述します。
ちなみにドキュメントで利用可能な比較条件にnotが無かったのが気になったので、ダメ元で試してみましたがやはりnot条件は利用できない様です。

Format and Options for Remote Objects Query Criteria
http://www.salesforce.com/us/developer/docs/pages/Content/pages_remote_objects_using_retrieve_query_object.htm

・retieve functionの第2引数

第2引数には取得した結果を処理するコールバックfunctionを指定します。

function callback(error, results, event) { // ... }

コールバックfunctionの引数

引数説明
error エラーメッセージが格納されます。正常終了時はnullです。
result 取得結果レコードがモデルオブジェクトの配列で返されます。
event DML操作の詳細情報が格納されます。

※コールバックfunctionの第3引数について
コールバックfunctionの第3引数には処理結果がJavaScriptのオブジェクトとして格納されます。
retrieve()の結果を確認したところ次のものが格納されていました。
(statusCode, type, tid, ref, action, method, result, status)
ドキュメントには通常は第1,第2引数でエラーチェックを行い、第3引数はデバッグもしくは細かいエラー管理用として使用するとの記載があるのみで詳細には触れられていなかったので通常利用は想定されていないのかもしれません。

Remote Objects Callback Functions
http://www.salesforce.com/us/developer/docs/pages/Content/pages_remote_objects_callback_functions.htm

・モデルオブジェクトのフィールドへのアクセス

取得した結果レコードのオブジェクトのフィールド値へのアクセスはget() / set() functionを利用します。

var name = obj.get('Name');
obj.set('Name', name);

また、日付型、日付/日時型については共にJavaScriptのDateに変換された形で取得することができます。(タイムゾーンはローカルです。)

・オブジェクト権限と項目レベルセキュリティ

オブジェクト、項目レベルどちらの場合も権限がなければ呼び出し結果はエラーになります。
項目レベルセキュリティについてはその項目だけ取得できないという動きではないので注意が必要です。

Visualforce Remote Objects
http://www.salesforce.com/us/developer/docs/pages/Content/pages_remote_objects.htm

・その他制限事項

基本的にVisualforceの制限事項はすべて適用されます。
また、地図情報は取得できません。取得しようとしても何事もなかったのかのように無視されます。

Limitations of Remote Objects
http://www.salesforce.com/us/developer/docs/pages/Content/pages_remote_objects_limitations.htm

2014年5月27日 (火)

コードカバー率が正常に表示されない件について

エンジニアの川名です。

現在、私が運用を担当している組織にて、
Apexコードカバー率が正常に表示されない現象が発生しているため
これについてまとめたいと思います。


Salesforceではテストコードのカバー率が75%以上ないと本番組織にリリースする事ができません。
そのため組織全体のコードカバー率のチェックは本番リリースの際には欠かせない作業となっています。

ある日の本番リリース後、念のため本番組織のコードカバー率も確認しておこうと
[設定]→[開発]→[Apexクラス]と進んだApex一覧の画面の
「組織のコードカバー率を見積る」を押下したところ、なんと…!

Codecoverage_2

58%という目を疑うような数字が表示されました。

実は本番リリースに失敗しているのではないかとちょっと慌てて
リリース対象物を確認しましたが正常にデプロイされている様子。

これはおかしい。ということでサポートに問い合わせて現象について確認しました。
回答は以下。

・Winter'14以降で発生している不具合。
・テストクラスを持たないApexクラスの場合、コメントアウトや System.debug() などのコードもカバー率の母数に含まれてしまい、
 本来のカバー率よりも低い数字が表示されてしまっている。
・デプロイ時のコードカバレッジの算出はこの影響を受けていないため正常なカバレッジでデプロイが行われる。

とのことでした。


回避策として、すべてのApexクラスに対してテストクラスを作成することで正常なカバー率が算出されるようです。

以下でもケースとして上がっていました。
Summer'14でバグフィクス予定のようです。

Drop in Overal Code Coverage value due to incorrect count of class lines with 0% code coverage

https://success.salesforce.com/issues_view?id=a1p30000000T1m1


有効な行数がそもそも少ないクラスや大部分がコメントアウトされたクラスなどは
テストクラスの作成が後回しになってしまいがちですが、

・不要なクラスは本番組織に残しておかない。
・有効なクラスに対してはテストクラスを必ず作成する。

など、日頃から気をつけることが必要であると今回の件で感じました。

○おまけ

リリース関連の小ネタです。

本番リリースする際、リリース状況を確認することが出来ましたが
画面の更新を行いながら進捗をチェックする必要がありました。

Spring'14より、リリース状況を表すプログレスグラフが自動更新されるようになりました。
[設定]→[リリース]→[リリース状況]から確認することが出来ます。

Releasestatus

細かいですがリリース担当者としては地味に嬉しい機能ですね。

2014年5月22日 (木)

Salesforce1 Webinar セキュリティ&プッシュ通知編

こころです。

5/14にSalesforce1 Platformモバイル開発TipsWebinar 〜セキュリティ及びプッシュ通知編 〜 Webinarがありました。資料録画が公開されていますので詳しくはそちらをご覧ください。気づいた点をまとめてみました。


YouTube: Salesforce1 Platformモバイル開発Tips Webinar 〜セキュリティ及びプッシュ通知 編 〜

1.Salesforce1のpush通知とセキュリティ(標準機能設定)
2.モバイルSDKのプッシュ通知とセキュリティ(開発者向け)
という構成です。

1.Salesforce1のpush通知とセキュリティ
iPhoneアプリのバージョンは5.2 以降なので、試す場合はiPhoneの方はSalesforce1アプリを最新にアップデートしてください。

<PUSH>
TodoがPush通知できる(アプリからユーザの好きなタイミングで設定)
※ユーザーがが自分自身宛に作成したTodoはPushされないので注意
Apexで作成したTaskについてもPush通知できる。
Task pushtask = new Task(
                               OwnerId = <通知相手のUserId>,
                               WhatId   = <関連レコードのID>,
                               Subject   = <タイトル>, 
                               .....
                         );
insert pushtask;

<セキュリティ>

IP制限の緩和・・・リフレッシュトークンの有効期限の設定
トークンの強制失効を設定できる
高保証セッション=二要素認証
PIN保護(4桁以上)
ブランディング可能になったロゴ
など



2.モバイルSDKのプッシュ通知とセキュリティ
Pilot版で、Android向けとiOS向けが異なります。iOS向けは要iOSデベロッパー登録(有料)です
。(私はiPhoneユーザーなのでiPhoneで試してみました。Androidは割愛)

<Push通知について参考資料>

Salesforce Mobile Push Notifications
Implementation Guide

About Push Notifications

MobileSDKを使わずにSalesforceにアクセスするiPhoneアプリを作る【プッシュ通知編】

pom0325さんの記事で全く同じ事が詳しく紹介されています。



<PUSH>

Pushの仕組みはApple/Googleそれぞれ
Device Token(Androidの場合は Registration ID)を取得し、APN(Apple Push Notofication Service)とモバイルアプリ間で
トークンを受け渡してPush通知しています。

Appleの場合

1.証明書の作成(Wildcard AppIDは選択できない)

2.取得した証明書を、「 接続アプリケーション」>「モバイルアプリケーション設定」で登録

3.Push通知の実装

Messaging.PushNotification msg = new Messaging.PushNotification();
Map<String, Object> payload =
Messaging.PushNotificationPayload.apple( '<PUSHする内容>', '', null, null);

msg.setPayload(payload);
String userId1 = '<PUSHするユーザID>';
String userId2 = '';
.....

Set users = new Set();
users.add(userId1);
users.add(userId2);
.....

msg.send('<登録したアプリ名>', users);

※Badgeに表示される数値はPushする際に設定します。

<セキュリティ>

接続アプリケーションでできるセキュリティは設定はSalesforce1のセキュリティと同じ起動時にPIN保護の入力が求められるアプリが出来る(Mobile SDKのみ)
※設定後アプリケーションからログアウトし、再起動すると有効になる。

Androidの方はAndroid版で試してみてください。

Webinarまとめは以上です。

モバイルアプリ独特の機能であるPush通知とセキュリティについてフォーカスした内容でした。

セキュリティ権限設定が細かくできるのはSalesforce1アプリを個人の端末に入れて使用する場合などに色々なニーズに応えられるのではと思います。

Mobile SDKのPush通知についてはPilot版なので正式リリースされるか分かりませんが、設定手順が簡単になると嬉しいです。



2014年4月23日 (水)

Spring'14 共有セット

エンジニアのこころです。

Spring'14の新機能の中で、カスタマーポータル、ポータルコミュニティユーザのレコードアクセス制御の新機能「共有セット」をコミュニティで試してみました。

これまでは取引先に属するコミュニティユーザに関連レコードを共有したい場合、共有したいレコードの所有者をコミュニティユーザに変更したりと一手間ありましたが、「共有セット」を使用して、取引先、取引先責任者、ケース、サービス契約、ユーザ、カスタムオブジェクトへのアクセス権(参照のみ|参照・更新)を付与できます。

さっそく取引先と参照関係にある「対応履歴」というカスタムオブジェクトを共有セットを使って共有してみます。※対応履歴の共有設定は予め「非公開」となっています。

○コミュニティの設定

1.[設定]→[コミュニティ]→[設定]より、共有セットの新規ボタンで新規共有セットを作成します。

Photo

2.選択可能なプロファイルは、コミュニティユーザの所属するプロファイル(ここではCommunity)を選択します。

Photo_2

3.アクセスマッピング でユーザ「Account」ターゲット対応履歴「Account__c」※Account__cは対応履歴から取引先への参照関係 を選択します。

Photo_3

マッピングの起点として選択できるのは、取引先、親取引先、取引先責任者、取引先責任者の取引先、上司、コミュニティユーザのマネージャの取引先、コミュニティユーザのマネージャの取引責任者です。

○コミュニティ側の確認

以上の設定をコミュニティ側から確認します。

コミュニティユーザが関連する取引先の対応履歴のみが表示され、共有セットが効いているのがわかります。

Photo_4

このように、特定の取引先や取引先責任者を起点としたレコードをカスタマーポータル、ポータルコミュニティユーザと共有するのが共有セットによって非常に簡単になりました。

是非使ってみてください。

採用情報

株式会社フレクトでは、事業拡大のため、
・Salesforce/Force.comのアプリケーション開発
・HerokuやAWSなどのクラウドプラットフォーム上
でのWebアプリケーション開発
エンジニア、マネージャーを募集中です。

未経験でも、これからクラウドをやってみたい方、
是非ご応募下さい。

フレクト採用ページへ

会社紹介

  • 株式会社フレクトは、
      ・認定コンサルタント
      ・認定上級デベロッパー
      ・認定デベロッパー
    が在籍している、セールスフォースパートナーです。
    また、heroku partnersにも登録されています。
    herokuパートナー
    株式会社フレクト
    Salesforce/Force.com
    導入支援サービス
    弊社の認定プロフェッショナルが支援致します。
    ・Visualforce/Apexによるアプリ開発
    ・Salesforceと連携するWebアプリ開発
    も承っております。
    セールスフォースご検討の際は、
    お気軽にお問合せください。

Twitter

リファレンス

■Developer's Guide(リファレンス)
・Apex  HTML | PDF | 日本語PDF | ガバナ制限
・Visualforce  HTML | PDF
・Web Services API  HTML | PDF | 日本語PDF
・Bulk API  HTML | PDF
・REST API  HTML | PDF | 日本語PDF
・Metadata API  HTML | PDF
・Migration Tool  HTML | PDF
・AJAX Toolkit  HTML | PDF
・Data Loader PDF | 日本語PDF

■早見表 (日本語)
数式
Apex
Visualforce
Web Services API
Chatter