プラットフォームイベントをつかった通知アプリ開発を試してみました。マイドメインが有効化されたDE組織が必要になります。
プラットフォームイベントを定義
設定から新規作成できます。
![f:id:tyoshikawa1106:20171228145610p:plain f:id:tyoshikawa1106:20171228145610p:plain]()
作成画面はこんな感じです。
![f:id:tyoshikawa1106:20171228145703p:plain f:id:tyoshikawa1106:20171228145703p:plain]()
API名は「__e」になります。
![f:id:tyoshikawa1106:20171228145752p:plain f:id:tyoshikawa1106:20171228145752p:plain]()
カスタム項目を作成します。
![f:id:tyoshikawa1106:20171228145843p: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 f:id:tyoshikawa1106:20171228150302p:plain]()
Utility Bar設定で先程のLightningコンポーネントを追加します。
![f:id:tyoshikawa1106:20171228150606p:plain f:id:tyoshikawa1106:20171228150606p:plain]()
![f:id:tyoshikawa1106:20171228150800p:plain f:id:tyoshikawa1106:20171228150800p: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 f:id:tyoshikawa1106:20171228160827p:plain]()
https://bear-watch.herokuapp.com/
Loginボタンをクリックしてアクセスの承認します。
![f:id:tyoshikawa1106:20171228160920p:plain f:id:tyoshikawa1106:20171228160920p:plain]()
ボタンをクリックすると処理が実行されます。
![f:id:tyoshikawa1106:20171228160943p:plain f:id:tyoshikawa1106:20171228160943p:plain]()
このようにSalesforce側の通知処理が実行できました。
![f:id:tyoshikawa1106:20171228161353p:plain 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 f:id:tyoshikawa1106:20171228161908p:plain]()
先ほどと同じように通知が表示されます。
以上がプラットフォームイベントをつかった通知処理です。ユーティリティバーの通知はLightnign Experienceのみ利用可能となりますがすごく便利そうでした。