tyoshikawa1106のブログ

- Force.com Developer Blog -

SFDC:Salesforce CLIのエラーメモ

根本的に使い方を間違えているかもしれませんが、下記のコマンドを実行した時にエラーになりました。

$ sfdx force:source:pull


エラーメッセージはこちら。

MemberName, IsNameObsolete FROM SourceMember WHERE RevisionNum >
^
ERROR at Row:1:Column:52
sObject type 'SourceMember' is not supported. If you are attempting to use a custom object, be sure to append the '__c' after the entity name. Please reference your WSDL or the describe call for the appropriate names.

f:id:tyoshikawa1106:20180103231850p:plain

検索したらこちらがヒット。


あとはこちら。


スクラッチ組織でしか実行できないのか、設定ファイルが必要なのか、それ以外なのかわかっていませんがいつか使うかもしれないのでメモ。

追記

やっぱりforce:source:pullはスクラッチ組織用のコマンドっぽい。

SFDC:Salesforce CLIのヘルプコマンド

トピック一覧

ヘルプコマンド

$ sfdx force --help
 force:alias        manage username aliases
 force:apex         work with Apex code
 force:auth         authorize an org for use with the Salesforce CLI
 force:config       configure the Salesforce CLI
 force:data         manipulate records in your org
 force:doc          display help for force commands
 force:lightning    create and test Lightning component bundles
 force:limits       view your org’s limits
 force:mdapi        retrieve and deploy metadata using Metadata API
 force:org          manage your Salesforce DX orgs
 force:package      install and uninstall first- and second-generation packages
 force:package1     develop first-generation managed and unmanaged packages
 force:package2     develop second-generation packages
 force:project      set up a Salesforce DX project
 force:schema       view standard and custom objects
 force:source       sync your project with your orgs
 force:user         perform user-related admin tasks
 force:visualforce  create and edit Visualforce files


利用できるコマンド一覧

$ sfdx force:doc:commands:list
  force:alias:list                   # list username aliases for the Salesforce CLI
  force:alias:set                    # set username aliases for the Salesforce CLI
  force:apex:class:create            # create an Apex class
  force:apex:execute                 # execute anonymous Apex code
  force:apex:log:get                 # fetch a debug log
  force:apex:log:list                # list debug logs
  force:apex:test:report             # display test results
  force:apex:test:run                # invoke Apex tests
  force:apex:trigger:create          # create an Apex trigger
  force:auth:jwt:grant               # authorize an org using the JWT flow
  force:auth:sfdxurl:store           # authorize an org using an SFDX auth URL
  force:auth:web:login               # authorize an org using the web login flow
  force:config:get                   # get config var values for given names
  force:config:list                  # list config vars for the Salesforce CLI
  force:config:set                   # set config vars for the Salesforce CLI
  force:data:bulk:delete             # bulk delete records from a csv file
  force:data:bulk:status             # view the status of a bulk data load job or batch
  force:data:bulk:upsert             # bulk upsert records from a CSV file
  force:data:record:create           # create a record
  force:data:record:delete           # delete a record
  force:data:record:get              # view a record
  force:data:record:update           # update a record
  force:data:soql:query              # execute a SOQL query
  force:data:tree:export             # export data from an org into sObject tree format for force:data:tree:import consumption
  force:data:tree:import             # import data into an org using SObject Tree Save API
  force:doc:commands:display         # display help for force commands
  force:doc:commands:list            # list the force commands
  force:lightning:app:create         # create a Lightning app
  force:lightning:component:create   # create a Lightning component
  force:lightning:event:create       # create a Lightning event
  force:lightning:interface:create   # create a Lightning interface
  force:lightning:lint               # analyse (lint) Lightning component code
  force:lightning:test:create        # create a Lightning test
  force:lightning:test:install       # install Lightning Testing Service unmanaged package in your org
  force:lightning:test:run           # invoke Lightning component tests
  force:limits:api:display           # display current org’s limits
  force:mdapi:convert                # convert Metadata API source into the Salesforce DX source format
  force:mdapi:deploy                 # deploy metadata to an org using Metadata API
  force:mdapi:deploy:report          # check the status of a metadata deployment
  force:mdapi:retrieve               # retrieve metadata from an org using Metadata API
  force:mdapi:retrieve:report        # check the status of a metadata retrieval
  force:org:create                   # create a scratch org
  force:org:delete                   # mark a scratch org for deletion
  force:org:display                  # get org description
  force:org:list                     # list all orgs you’ve created or authenticated to
  force:org:open                     # open an org in your browser
  force:org:shape:create             # create a snapshot of org edition, features, and licenses
  force:org:shape:delete             # delete all org shapes for a target org
  force:org:shape:list               # list all org shapes you’ve created
  force:package1:version:create      # create a first-generation package version in the release org
  force:package1:version:create:get  # retrieve the status of a package version creation request
  force:package1:version:display     # display details about a first-generation package version
  force:package1:version:list        # list package versions for the specified first-generation package or for the org
  force:package2:create              # create a second-generation package
  force:package2:list                # list all second-generation packages in the Dev Hub org
  force:package2:update              # update a second-generation package
  force:package2:version:create      # create a second-generation package version
  force:package2:version:create:get  # retrieve a package version creation request
  force:package2:version:create:list # list package version creation requests
  force:package2:version:get         # retrieve a package version in the Dev Hub org
  force:package2:version:list        # list all package versions in the Dev Hub org
  force:package2:version:update      # update a second-generation package version
  force:package:install              # install a package in the target org
  force:package:install:get          # retrieve the status of a package installation request
  force:package:installed:list       # list the org’s installed packages
  force:package:uninstall            # uninstall a second-generation package from the target org
  force:package:uninstall:get        # retrieve status of package uninstall request
  force:project:create               # create a new SFDX project
  force:project:upgrade              # update project config files to the latest format
  force:schema:sobject:describe      # describe an object
  force:schema:sobject:list          # list all objects of a specified category
  force:source:convert               # convert Salesforce DX source into the Metadata API source format
  force:source:open                  # edit a Lightning Page with Lightning App Builder
  force:source:pull                  # pull source from the scratch org to the project
  force:source:push                  # push source to an org from the project
  force:source:status                # list local changes and/or changes in a scratch org
  force:user:create                  # create a user for a scratch org
  force:user:display                 # displays information about a user of a scratch org
  force:user:list                    # lists all users of a scratch org
  force:user:password:generate       # generate a password for scratch org users
  force:user:permset:assign          # assign a permission set to one or more users of an org
  force:visualforce:component:create # create a Visualforce component
  force:visualforce:page:create      # create a Visualforce page

SFDC:Salesforce DXの接続組織の参照と削除

Salesforce DXのCLIで接続組織の一覧表示

参照はこちら。

$ sfdx force:org:list

f:id:tyoshikawa1106:20180103182910p:plain

Salesforce DXのCLIでSalesforceへログイン

接続していると下記コマンドでログインできます。

$ sfdx force:org:open -u <登録したい組織の別名>
例)
$ sfdx force:org:open -u MyDev

別名を付けて接続するには下記コマンドを実行します。

$ sfdx force:auth:web:login -r https://login.salesforce.com -a <登録したい組織の別名>
例)
$ sfdx force:auth:web:login -r https://login.salesforce.com -a MyDev

実行するとブラウザでログインページが表示されます。ログイン後に認証確認画面が表示されるので承認すれば完了です。

Salesforce DXのCLIで接続組織の削除

不要になった場合は下記のコマンドで組織を削除できます。※スクラッチ組織削除用?

$ sfdx force:org:delete  --targetusername

例)

$ sfdx force:org:delete  --sample@example.com


sfdx force:org:listのリストから除外するコマンドだと思ったのですが、過去のWebセミナーで組織を削除できる話を聞いた気がするのでスクラッチ組織削除のためのコマンドだと思います。

force:org:listから除外する方法

「force:org:list remove」で検索したら下記ページが見つかりました。


下記コマンドを実行します。

$ cd ~/.sfdx
$ rm <username>.json


これでsfdx force:org:listのリストから除外できました。
f:id:tyoshikawa1106:20180103213650p:plain

SFDC:API ExplorerでSalesforce APIの動作確認

まだPreview版ですが、Salesforce APIの動作確認ができるAPI Explorerサイトが用意されているみたいです。

f:id:tyoshikawa1106:20171230071644p:plain

https://developer.salesforce.com/docs/api-explorer


REST APIページはこんな感じ。
f:id:tyoshikawa1106:20171230071733p:plain


Try it nowボタンをクリックするとSalesforceへの接続ボタンが表示されます。
f:id:tyoshikawa1106:20171230071816p:plain


こんな感じで実行できました。
f:id:tyoshikawa1106:20171230072013p:plain

SFDC:Salesforce DX Quick Startを試してみました

Salesforce DX Quick Startを試してみました。トライアル環境は下記リンク先から取得できます。ログイン後マイドメイン有効化をしておきました。

f:id:tyoshikawa1106:20171228174316p:plain

dx-signup | Salesforce Developers


設定のDevHub画面はこんな感じでした。
f:id:tyoshikawa1106:20171228183341p:plain

インストーラのダウンロード

下記のリンク先からダウンロードできました。

https://sfdc.co/sfdx_cli_osx


画面に従って操作して簡単にインストールできます。
f:id:tyoshikawa1106:20171228181624p:plain:w300


正常にインストールできていれば次のコマンドを実行できます。

$ sfdx

f:id:tyoshikawa1106:20171228181957p:plain


DevHub環境にログイン

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

実行するとログインページに移動します。
f:id:tyoshikawa1106:20171228182814p:plain


ログインすると認証ページが表示されます。
f:id:tyoshikawa1106:20171228182849p:plain


これでSalesforce DXのログインが実行できました。
f:id:tyoshikawa1106:20171228182939p:plain:w200


GitHubからサンプルプロジェクトをダウンロードします。

$ mkdir my_sfdx_project
$ cd my_sfdx_project
$ git clone https://github.com/forcedotcom/sfdx-dreamhouse.git
$ cd sfdx-dreamhouse

f:id:tyoshikawa1106:20171228184026p:plain


ブランチの作成

$ git checkout -b my_branch

f:id:tyoshikawa1106:20171228184216p:plain


Salesforce DXコマンドの確認

$ sfdx force --help

f:id:tyoshikawa1106:20171228184319p:plain


configフォルダのproject-scratch-def.jsonファイルでスクラッチ環境の設定情報を管理しているようです。
f:id:tyoshikawa1106:20171228203814p:plain


次のコマンドでスクラッチ組織を作成できます。スクラッチ組織は開発用の環境です。

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

f:id:tyoshikawa1106:20171228204729p:plain


DevHubとして使用した組織のスクラッチ情報タブから現在存在するスクラッチ組織を確認できます。
f:id:tyoshikawa1106:20171228205359p:plain


有効期限も決まっているため無期限に利用できるわけではないようです。
f:id:tyoshikawa1106:20171228205555p:plain


スクラッチ組織作成後は下記のコマンドでアクセスできます。

$ sfdx force:org:open


スクラッチ組織にコードをデプロイするには下記のコマンドを実行します。

$ sfdx force:source:push

f:id:tyoshikawa1106:20171228210051p:plain


変更が反映されていることを確認できました。
f:id:tyoshikawa1106:20171228210159p:plain


テストデータ作成も簡単にできるようです。次のコマンドで実行権限を付与できるみたいです。

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


テストデータ作成は下記コマンドです。

$ sfdx force:data:tree:import --plan data/sample-data-plan.json


テストデータはdataフォルダのjsonファイルで定義されています。
f:id:tyoshikawa1106:20171228210735p:plain


たぶん正常に作成できました。
f:id:tyoshikawa1106:20171228210943p:plain


Salesforce DX Quick Startはこんな感じでした。

SFDC:プラットフォームイベントをつかった通知アプリの開発を試してみました

プラットフォームイベントをつかった通知アプリ開発を試してみました。マイドメインが有効化されたDE組織が必要になります。

プラットフォームイベントを定義

設定から新規作成できます。
f:id:tyoshikawa1106:20171228145610p:plain


作成画面はこんな感じです。
f:id:tyoshikawa1106:20171228145703p:plain


API名は「__e」になります。
f:id:tyoshikawa1106:20171228145752p:plain


カスタム項目を作成します。
f:id:tyoshikawa1106:20171228145843p:plain

Lightningコンポーネント作成

notificationConsole.cmpを作成します。

<aura:component implements="flexipage:availableForAllPageTypes" access="global">

  <aura:attribute name="notifications" type="Object[]"/>
  <aura:attribute name="isMuted" type="Boolean" default="false"/>

  <aura:handler name="init" value="{!this}" action="{!c.onInit}"/>

  <aura:registerEvent name="toastEvent" type="force:showToast"/>


  <div class="container">

    <!-- Header -->
    <div class="slds-p-around--x-small slds-border--bottom slds-theme--shade">
      <div class="slds-grid slds-grid--align-spread slds-grid--vertical-align-center">
        <div>
          <span class="slds-badge">{!v.notifications.length}</span>
        </div>
        <div>
          <lightning:buttonIcon onclick="{!c.onClear}" iconName="utility:delete" title="Clear notifications"
            alternativeText="Clear notifications" variant="border-filled"/>
          <lightning:buttonIcon onclick="{!c.onToggleMute}"
            iconName="{!v.isMuted ? 'utility:volume_off' : 'utility:volume_high'}"
            title="{!v.isMuted ? 'Unmute notifications' : 'Mute notifications'}"
            alternativeText="Toggle mute" variant="border-filled"/>
        </div>
      </div>
    </div>

    <!-- Notification list -->
    <div class="slds-container--fluid slds-scrollable--y content">
      <aura:iteration items="{!v.notifications}" var="notification">
        <div class="slds-p-around--small slds-border--top">
          <div class="slds-grid slds-grid--align-spread slds-has-flexi-truncate">
            <p>{!notification.message}</p>
            <p class="slds-text-color--weak slds-p-left--x-small">{!notification.time}</p>
          </div>
        </div>
      </aura:iteration>
    </div>

  </div>

</aura:component>


Controllerを作成します。

({
  onInit : function(component, event, helper) {
    component.set('v.notifications', [
      {time: '00:01', message: 'Greetings Trailblazer!'},
      {time: '00:02', message: 'Congratulations on building this first version of the app.'},
      {time: '00:03', message: 'Beware of the bears.'}
    ]);

    helper.displayToast(component, 'success', 'Ready to receive notifications.');
  },

  onClear : function(component, event, helper) {
    component.set('v.notifications', []);
    },

  onToggleMute : function(component, event, helper) {
    var isMuted = component.get('v.isMuted');
    component.set('v.isMuted', !isMuted);
    helper.displayToast(component, 'success', 'Notifications '+ ((!isMuted) ? 'muted' : 'unmuted') +'.');
    }
})


Helperを作成します。

({
  displayToast : function(component, type, message) {
    var toastEvent = $A.get('e.force:showToast');
    toastEvent.setParams({
      type: type,
      message: message
    });
    toastEvent.fire();
  }
})


Styleを作成します。

.THIS.container {
  height:100%;
}
.THIS .content {
  height:calc(100% - 49px);
}

Lightningコンソールの作成

App ManagerにアクセスしてSalesアプリケーションを編集する形で進めます。(Classicアプリケーションでは作業不可)
f:id:tyoshikawa1106:20171228150302p:plain


Utility Bar設定で先程のLightningコンポーネントを追加します。
f:id:tyoshikawa1106:20171228150606p:plain


f:id:tyoshikawa1106:20171228150800p:plain

Cometdのダウンロード

下記リンク先からダウンロードできます。

https://raw.githubusercontent.com/cometd/cometd/3.1.1/cometd-javascript/common/src/main/webapp/js/cometd/cometd.js


静的リソースへアップします。
f:id:tyoshikawa1106:20171228160150p:plain


NotificationControllerの作成

NotificationController.clsを作成します。

public class NotificationController {
    
    @AuraEnabled
    public static String getSessionId() {
      return UserInfo.getSessionId();
    }
}

Lightningコンポーネントの修正

<aura:component controller="NotificationController" implements="flexipage:availableForAllPageTypes" access="global">

  <ltng:require scripts="{!$Resource.cometd}" afterScriptsLoaded="{!c.onCometdLoaded}"/>
  <aura:attribute name="sessionId" type="String"/>
  <aura:attribute name="cometd" type="Object"/>
  <aura:attribute name="cometdSubscriptions" type="Object[]"/>
    
  <aura:attribute name="notifications" type="Object[]"/>
  <aura:attribute name="isMuted" type="Boolean" default="false"/>

  <aura:handler name="init" value="{!this}" action="{!c.onInit}"/>

  <aura:registerEvent name="toastEvent" type="force:showToast"/>


  <div class="container">

    <!-- Header -->
    <div class="slds-p-around--x-small slds-border--bottom slds-theme--shade">
      <div class="slds-grid slds-grid--align-spread slds-grid--vertical-align-center">
        <div>
          <span class="slds-badge">{!v.notifications.length}</span>
        </div>
        <div>
          <lightning:buttonIcon onclick="{!c.onClear}" iconName="utility:delete" title="Clear notifications"
            alternativeText="Clear notifications" variant="border-filled"/>
          <lightning:buttonIcon onclick="{!c.onToggleMute}"
            iconName="{!v.isMuted ? 'utility:volume_off' : 'utility:volume_high'}"
            title="{!v.isMuted ? 'Unmute notifications' : 'Mute notifications'}"
            alternativeText="Toggle mute" variant="border-filled"/>
        </div>
      </div>
    </div>

    <!-- Notification list -->
    <div class="slds-container--fluid slds-scrollable--y content">
      <aura:iteration items="{!v.notifications}" var="notification">
        <div class="slds-p-around--small slds-border--top">
          <div class="slds-grid slds-grid--align-spread slds-has-flexi-truncate">
            <p>{!notification.message}</p>
            <p class="slds-text-color--weak slds-p-left--x-small">{!notification.time}</p>
          </div>
        </div>
      </aura:iteration>
    </div>

  </div>

</aura:component>

Helperの修正

({
  connectCometd : function(component) {
    var helper = this;

    // Configure CometD
    var cometdUrl = window.location.protocol+'//'+window.location.hostname+'/cometd/40.0/';
    var cometd = component.get('v.cometd');
    cometd.configure({
      url: cometdUrl,
      requestHeaders: { Authorization: 'OAuth '+ component.get('v.sessionId')},
      appendMessageTypeToURL : false
    });
    cometd.websocketEnabled = false;

    // Establish CometD connection
    console.log('Connecting to CometD: '+ cometdUrl);
    cometd.handshake(function(handshakeReply) {
      if (handshakeReply.successful) {
        console.log('Connected to CometD.');
        // Subscribe to platform event
        var newSubscription = cometd.subscribe('/event/Notification__e',
          function(platformEvent) {
            console.log('Platform event received: '+ JSON.stringify(platformEvent));
            helper.onReceiveNotification(component, platformEvent);
          }
        );
        // Save subscription for later
        var subscriptions = component.get('v.cometdSubscriptions');
        subscriptions.push(newSubscription);
        component.set('v.cometdSubscriptions', subscriptions);
      }
      else
        console.error('Failed to connected to CometD.');
    });
      },

  disconnectCometd : function(component) {
    var cometd = component.get('v.cometd');

    // Unsuscribe all CometD subscriptions
    cometd.batch(function() {
      var subscriptions = component.get('v.cometdSubscriptions');
      subscriptions.forEach(function (subscription) {
        cometd.unsubscribe(subscription);
      });
    });
    component.set('v.cometdSubscriptions', []);

    // Disconnect CometD
    cometd.disconnect();
    console.log('CometD disconnected.');
  },

  onReceiveNotification : function(component, platformEvent) {
    var helper = this;
    // Extract notification from platform event
    var newNotification = {
      time : $A.localizationService.formatDateTime(
        platformEvent.data.payload.CreatedDate, 'HH:mm'),
      message : platformEvent.data.payload.Message__c
    };
    // Save notification in history
    var notifications = component.get('v.notifications');
    notifications.push(newNotification);
    component.set('v.notifications', notifications);
    // Display notification in a toast if not muted
    if (!component.get('v.isMuted'))
      helper.displayToast(component, 'info', newNotification.message);
  },

  displayToast : function(component, type, message) {
    var toastEvent = $A.get('e.force:showToast');
    toastEvent.setParams({
      type: type,
      message: message
    });
    toastEvent.fire();
  }
})

Controller.jsの修正

({
  onInit : function(component, event, helper) {
      component.set('v.cometdSubscriptions', []);
      component.set('v.notifications', []);
    
      // Disconnect CometD when leaving page
      window.addEventListener('unload', function(event) {
        helper.disconnectCometd(component);
      });
    
      // Retrieve session id
      var action = component.get('c.getSessionId');
      action.setCallback(this, function(response) {
        if (component.isValid() && response.getState() === 'SUCCESS') {
          component.set('v.sessionId', response.getReturnValue());
          if (component.get('v.cometd') != null)
            helper.connectCometd(component);
        }
        else
          console.error(response);
      });
      $A.enqueueAction(action);
    
      helper.displayToast(component, 'success', 'Ready to receive notifications.');
    },
    
    onCometdLoaded : function(component, event, helper) {
      var cometd = new org.cometd.CometD();
      component.set('v.cometd', cometd);
      if (component.get('v.sessionId') != null)
        helper.connectCometd(component);
    },


  onClear : function(component, event, helper) {
    component.set('v.notifications', []);
    },

  onToggleMute : function(component, event, helper) {
    var isMuted = component.get('v.isMuted');
    component.set('v.isMuted', !isMuted);
    helper.displayToast(component, 'success', 'Notifications '+ ((!isMuted) ? 'muted' : 'unmuted') +'.');
    }
})


これでコーディング部分は作業完了です。

動作確認

Bear Watch Heroku appというサイトから動作確認できます。

f:id:tyoshikawa1106:20171228160827p:plain

https://bear-watch.herokuapp.com/


Loginボタンをクリックしてアクセスの承認します。
f:id:tyoshikawa1106:20171228160920p:plain


ボタンをクリックすると処理が実行されます。
f:id:tyoshikawa1106:20171228160943p:plain


このようにSalesforce側の通知処理が実行できました。
f:id:tyoshikawa1106:20171228161353p:plain


処理が動かない場合は『cmd + shift + r』のキーで画面を再描画すると解決すると思います。

Apexトリガをつかった通知

NotificationController.clsの処理を変更します。

public class NotificationController {
    
    @AuraEnabled
    public static String getSessionId() {
      return UserInfo.getSessionId();
    }
    
    public static void publishNotifications(List<String> messages) {
      List<Notification__e> notifications = new List<Notification__e>();
      for (String message: messages) {
        notifications.add(new Notification__e(Message__c = message));
      }
    
      List<Database.SaveResult> results = EventBus.publish(notifications);
    
      // Inspect publishing results
      for (Database.SaveResult result : results) {
        if (!result.isSuccess()) {
          for (Database.Error error : result.getErrors()) {
            System.debug('Error returned: ' +
                   error.getStatusCode() +' - '+
                   error.getMessage());
          }
        }
      }
    }
}


TopicAssignmentオブジェクトのトリガを作成します。

trigger BearAlertTopicAssignmentTrigger on TopicAssignment (after insert) {

  // Get FeedItem posts only
  Set<Id> feedIds = new Set<Id>();
  for (TopicAssignment ta : Trigger.new){
    if (ta.EntityId.getSObjectType().getDescribe().getName().equals('FeedItem')) {
      feedIds.add(ta.EntityId);
    }
  }

  // Load FeedItem bodies
  Map<Id,FeedItem> feedItems = new Map<Id,FeedItem>([SELECT Body FROM FeedItem WHERE Id IN :feedIds]);

  // Create messages for each FeedItem that contains the BearAlert topic
  List<String> messages = new List<String>();
  for (TopicAssignment ta : [SELECT Id, EntityId, Topic.Name FROM TopicAssignment
      WHERE Id IN :Trigger.new AND Topic.Name = 'BearAlert']) {
    messages.add(feedItems.get(ta.EntityId).body.stripHtmlTags().abbreviate(255));
  }

  // Publish messages as notifications
  NotificationController.publishNotifications(messages);
}


これで準備完了です。Chatterに下記テキストを投稿します。

#BearAlert False alarm: It’s just a big dog!

f:id:tyoshikawa1106:20171228161908p:plain


先ほどと同じように通知が表示されます。


以上がプラットフォームイベントをつかった通知処理です。ユーティリティバーの通知はLightnign Experienceのみ利用可能となりますがすごく便利そうでした。