From 9dea7a7518053905eea9606a060a4ff9611a5cd1 Mon Sep 17 00:00:00 2001 From: Menachem Lazaroff Date: Mon, 8 Jun 2020 15:22:49 -0500 Subject: [PATCH 1/3] switch to using pushtopics --- .../main/default/lwc/datatable/datatable.js | 136 +++++++++--------- .../default/lwc/relatedList/relatedList.js | 12 +- .../lwc-utils/classes/DataTableService.cls | 29 ++++ 3 files changed, 101 insertions(+), 76 deletions(-) diff --git a/force-app/main/default/lwc/datatable/datatable.js b/force-app/main/default/lwc/datatable/datatable.js index fbda06f..7ee9054 100644 --- a/force-app/main/default/lwc/datatable/datatable.js +++ b/force-app/main/default/lwc/datatable/datatable.js @@ -6,6 +6,7 @@ import { updateRecord } from "lightning/uiRecordApi"; import { refreshApex } from "@salesforce/apex"; import wireTableCache from "@salesforce/apex/DataTableService.wireTableCache"; import getTableCache from "@salesforce/apex/DataTableService.getTableCache"; +import getPushTopic from "@salesforce/apex/DataTableService.getPushTopic"; import * as tableUtils from "c/tableServiceUtils"; import * as datatableUtils from "./datatableUtils"; import { @@ -28,7 +29,7 @@ export default class Datatable extends LightningElement { * See README.md ***/ // _wiredResults; - lastEventId = 0; + lastEventId = -1; subscription; wiredResults; _sObject = ""; @@ -59,7 +60,15 @@ export default class Datatable extends LightningElement { @api recordsPerBatch = 50; @api editable; @api showSoql; - @api enableLiveUpdates; + @api + get enableLiveUpdates() { + return this._enableLiveUpdates; + } + set enableLiveUpdates(value) { + this._enableLiveUpdates = value; + // eslint-disable-next-line no-self-assign + this.sObject = this.sObject; + } get sortedByFormatted() { let name = this._sortedBy; @@ -120,6 +129,17 @@ export default class Datatable extends LightningElement { } set sObject(value) { this._sObject = value; + if (this.enableLiveUpdates) { + getPushTopic({ sObjectApiName: value }) + .then( (channelName) => { + this.channelName = channelName + if (true) { //isEmpEnabled + return this.pushTopicSubscribe(channelName) + } + return undefined; + }); + } + this.tableRequest = "reset"; } @api // filter; @@ -199,18 +219,6 @@ export default class Datatable extends LightningElement { wiredObjectInfo({ error, data }) { if (data) { this.objectInfo = data; - - if (isEmpEnabled && this.enableLiveUpdates) { - let channelName = `/data/${this.sObject}`; - if (this.objectInfo.custom) { - channelName = - channelName.substring(0, channelName.length - 1) + "ChangeEvent"; - } else { - channelName = channelName + "ChangeEvent"; - } - - this.changeDataCaptureSubscribe(channelName); - } } else if (error) { this.error(error.statusText + ": " + error.body.message); } @@ -327,44 +335,41 @@ export default class Datatable extends LightningElement { } get where() { - let filter = this.filter; - let search; - if (this.search) { - let searchTerm = this.search.replace("'", "\\'"); - search = this.fields - .filter((field) => { - if (Object.prototype.hasOwnProperty.call(field, "searchable")) { - return field.searchable; - } - if (!this.objectInfo || !this.objectInfo.fields[field.fieldName]) { - return false; - } - let fieldType = this.objectInfo.fields[field.fieldName].dataType; - return ( - fieldType === "String" || - fieldType === "Email" || - fieldType === "Phone" - ); - }) - .map((field) => { - return field.fieldName + " LIKE '%" + searchTerm + "%'"; - }) - .join(" OR "); - if (search) { - search = "(" + search + ")"; - } - } - if (filter && search) { - filter += " AND " + search; - } else if (search) { - filter = search; - } - if (filter) { - return " WHERE " + filter; + let filterItems = [this.filter, this.searchQuery].filter(f => f); + + if (filterItems.length) { + return " WHERE " + filterItems.join(' AND '); } return ""; } + get searchQuery() { + let searchTerm = this.search.replace("'", "\\'"); + let search = this.fields + .filter((field) => { + if (Object.prototype.hasOwnProperty.call(field, "searchable")) { + return field.searchable; + } + if (!this.objectInfo || !this.objectInfo.fields[field.fieldName]) { + return false; + } + let fieldType = this.objectInfo.fields[field.fieldName].dataType; + return ( + fieldType === "String" || + fieldType === "Email" || + fieldType === "Phone" + ); + }) + .map((field) => { + return field.fieldName + " LIKE '%" + searchTerm + "%'"; + }) + .join(" OR "); + if (search) { + search = "(" + search + ")"; + } + return search; + } + get orderBy() { if (!this.sortedBy) this.error("Sort field is required"); let sortedDirection = @@ -479,7 +484,7 @@ export default class Datatable extends LightningElement { getRowValue(recordId) { let filter = - this.where + (this.filter ? " AND " : " WHERE ") + `Id='${recordId}'`; + this.where + (this.where ? " AND " : " WHERE ") + `Id='${recordId}'`; let query = this.buildQuery( this.fields, this.sObject, @@ -537,29 +542,21 @@ export default class Datatable extends LightningElement { } } - changeDataCaptureSubscribe(channelName) { + pushTopicSubscribe(channelName) { const messageCallback = (response) => { - console.log("New message received:", JSON.stringify(response)); console.log(JSON.parse(JSON.stringify(response))); - const payload = response.data.payload; - const eventHeader = payload.ChangeEventHeader; - if ( - this.lastEventId < response.data.event.replayId && - eventHeader.entityName === this.sObject - ) { - switch (eventHeader.changeType) { - case "CREATE": - eventHeader.recordIds.map((recordId) => - this.addRow(recordId, payload) - ); + const event = response.data.event; + const recordId = response.data.sobject.Id; + if (this.lastEventId < event.replayId) { + switch (event.type) { + case "created": + this.addRow(recordId); break; - case "UPDATE": - eventHeader.recordIds.map((recordId) => - this.updateRow(recordId, payload) - ); + case "updated": + this.updateRow(recordId); break; - case "DELETE": - eventHeader.recordIds.map((recordId) => this.removeRow(recordId)); + case "deleted": + this.removeRow(recordId); break; default: break; @@ -567,14 +564,13 @@ export default class Datatable extends LightningElement { } }; - subscribe(channelName, -1, messageCallback).then((response) => { + return subscribe(channelName, -1, messageCallback).then((response) => { console.log( "Successfully subscribed to : ", JSON.stringify(response.channel) ); if (this.subscription) { unsubscribe(this.subscription, (resp) => { - console.log("unsubscribe() response: ", JSON.stringify(resp)); console.log(JSON.parse(JSON.stringify(resp))); }); } diff --git a/force-app/main/default/lwc/relatedList/relatedList.js b/force-app/main/default/lwc/relatedList/relatedList.js index e13a10b..d7ce2e6 100644 --- a/force-app/main/default/lwc/relatedList/relatedList.js +++ b/force-app/main/default/lwc/relatedList/relatedList.js @@ -64,17 +64,17 @@ export default class RelatedList extends LightningElement { } get parentRecordId() { - if (this.parentRecordField && this.parentRecord && this.parentRecord.data) + if (!this.parentRecordField) { + return undefined; + } else if (this.parentRecord && this.parentRecord.data) { return getFieldValue(this.parentRecord.data, this.fullParentRecordField); + } return ""; } get parentRelationship() { - if (this.parentRecordField && this.childRecordField) { - if (this.parentRecordId) { - return " " + this.childRecordField + " = '" + this.parentRecordId + "'"; - } - return " " + this.childRecordField + " = ''"; // return empty string so the query returns no results + if (this.childRecordField && typeof this.parentRecordField !== undefined) { + return `${this.childRecordField}='${this.parentRecordId}'`; } return ""; } diff --git a/force-app/main/lwc-utils/classes/DataTableService.cls b/force-app/main/lwc-utils/classes/DataTableService.cls index b1677d3..4448862 100644 --- a/force-app/main/lwc-utils/classes/DataTableService.cls +++ b/force-app/main/lwc-utils/classes/DataTableService.cls @@ -227,4 +227,33 @@ public with sharing class DataTableService { return tableColumns; } + @AuraEnabled + public static String getPushTopic(String sObjectApiName) { + String name = 'easydt__'+sObjectApiName; + String query = 'SELECT Id FROM ' + sObjectApiName; + PushTopic pt; + PushTopic[] pushTopics = [SELECT Id,Name,IsActive FROM PushTopic WHERE Name=:name LIMIT 1]; + if (pushTopics.size() > 0) { + pt = pushTopics[0]; + + if (!pt.IsActive) { + pt.IsActive = true; + update pt; + } + } else { + pt = new PushTopic(); + pt.Name=name; + pt.ApiVersion=48; + pt.NotifyForOperationCreate=true; + pt.NotifyForOperationUpdate=true; + pt.NotifyForOperationDelete=true; + pt.NotifyForFields='All'; + pt.Query=query; + + insert pt; + } + + return '/topic/' + pt.Name; + } + } \ No newline at end of file From ccfb38a5426b3a90d242f50e28a26fb089610c55 Mon Sep 17 00:00:00 2001 From: Menachem Lazaroff Date: Mon, 8 Jun 2020 15:37:34 -0500 Subject: [PATCH 2/3] new package, update readme --- README.md | 8 ++++---- force-app/main/default/lwc/datatable/README.md | 2 +- sfdx-project.json | 5 +++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5018c1f..e10ffe5 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,11 @@ _No warranty is provided, express or implied_ -[Install unlocked package](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008SZyYAAW) version 0.8.0 +[Install unlocked package](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008SZzRAAW) version 0.9.0 ## Release Notes +### 0.9.0 +- Change live data updates to use PushTopic ### 0.8.0 - Add support for Change Data Capture. ### 0.7.0 @@ -41,9 +43,7 @@ To deploy authorize a dev hub in sfdx and run `$ sfdx force:org:create -f config ### [datatable](force-app/main/default/lwc/datatable) Takes as input an sObject and an array of fields and populates a datatable with records from the database. -To enable Change Data Capture (live updates) follow instructions here: https://developer.salesforce.com/docs/atlas.en-us.change_data_capture.meta/change_data_capture/cdc_select_objects.htm - -Note: Normal Change Data Capture limitations apply https://developer.salesforce.com/docs/atlas.en-us.change_data_capture.meta/change_data_capture/cdc_allocations.htm +Note: Streaming update support utilizes the PushTopic feature, which has a maximum of 50 PushTopic records per org. The datatable uses one for each object type that has live updates enabled. They can be deleted or deactivated if necessary - use `SELECT Id, IsActive FROM PushTopic WHERE Name LIKE 'easydt__%` to retrieve them via SOQL. ### [Custom Related List](force-app/main/default/lwc/relatedList) Related list for use on lightning app and record pages. Choose object, fields, etc. diff --git a/force-app/main/default/lwc/datatable/README.md b/force-app/main/default/lwc/datatable/README.md index da5fd3f..f3acc3e 100644 --- a/force-app/main/default/lwc/datatable/README.md +++ b/force-app/main/default/lwc/datatable/README.md @@ -56,7 +56,7 @@ Name | Type |Read only | Required | Description | Default value `enable-infinite-loading`|boolean|||automatically load more records when user reaches the end of the datatable|`false` `records-per-batch`|integer|||number of records to load when the end of the datable is reached|`50` `initial-records`|integer|||number of records to load initially|`this.recordsPerBatch` -`enableLiveUpdates`|boolean|||update records using Change Data Capture|`false` +`enableLiveUpdates`|boolean|||update records using PushTopic|`false` `selected-rows`|array|✔||array of selected IDs from datatable `query`|string|✔||generated query string used to retrieve data `record-count`|integer|✔||total number of records returned by current query diff --git a/sfdx-project.json b/sfdx-project.json index f6dd423..b6bbad8 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -4,7 +4,7 @@ "path": "force-app", "default": true, "package": "lwc", - "versionNumber": "0.6.0.NEXT" + "versionNumber": "0.9.0.NEXT" } ], "namespace": "easydt", @@ -23,6 +23,7 @@ "lwc@0.5.0-1": "04t6g000004O28tAAC", "lwc@0.6.0-0": "04t6g000004OWs4AAG", "lwc@0.7.0-0": "04t6g000008fW6kAAE", - "lwc@0.8.0-0": "04t6g000008SZyYAAW" + "lwc@0.8.0-0": "04t6g000008SZyYAAW", + "lwc@0.9.0-0": "04t6g000008SZzRAAW" } } \ No newline at end of file From 50e701d5aa82308abc6282b9a798373d6e475fa2 Mon Sep 17 00:00:00 2001 From: Menachem Lazaroff Date: Mon, 8 Jun 2020 15:47:00 -0500 Subject: [PATCH 3/3] fix relted list --- README.md | 4 ++-- force-app/main/default/lwc/relatedList/relatedList.js | 2 +- sfdx-project.json | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e10ffe5..eec34b3 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ _No warranty is provided, express or implied_ -[Install unlocked package](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008SZzRAAW) version 0.9.0 +[Install unlocked package](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008SZzWAAW) version 0.10.0 ## Release Notes -### 0.9.0 +### 0.10.0 - Change live data updates to use PushTopic ### 0.8.0 - Add support for Change Data Capture. diff --git a/force-app/main/default/lwc/relatedList/relatedList.js b/force-app/main/default/lwc/relatedList/relatedList.js index d7ce2e6..aecae7b 100644 --- a/force-app/main/default/lwc/relatedList/relatedList.js +++ b/force-app/main/default/lwc/relatedList/relatedList.js @@ -73,7 +73,7 @@ export default class RelatedList extends LightningElement { } get parentRelationship() { - if (this.childRecordField && typeof this.parentRecordField !== undefined) { + if (this.childRecordField && typeof this.parentRecordId !== undefined) { return `${this.childRecordField}='${this.parentRecordId}'`; } return ""; diff --git a/sfdx-project.json b/sfdx-project.json index b6bbad8..d8a097a 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -24,6 +24,7 @@ "lwc@0.6.0-0": "04t6g000004OWs4AAG", "lwc@0.7.0-0": "04t6g000008fW6kAAE", "lwc@0.8.0-0": "04t6g000008SZyYAAW", - "lwc@0.9.0-0": "04t6g000008SZzRAAW" + "lwc@0.9.0-0": "04t6g000008SZzRAAW", + "lwc@0.10.0-0": "04t6g000008SZzWAAW" } } \ No newline at end of file