@pipedream/salesforce_rest_api 1.11.4 → 1.11.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@ export default {
4
4
  key: "salesforce_rest_api-search-string",
5
5
  name: "Search Object Records",
6
6
  description: "Searches for records in an object using a parameterized search. [See the documentation](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_search_parameterized_get.htm)",
7
- version: "0.0.7",
7
+ version: "0.0.8",
8
8
  annotations: {
9
9
  destructiveHint: false,
10
10
  openWorldHint: true,
@@ -40,6 +40,13 @@ export default {
40
40
  ],
41
41
  },
42
42
  },
43
+ methods: {
44
+ // constructs a url that users can copy into a browser to view the record in Salesforce
45
+ createBrowserUrl(baseUrl, url) {
46
+ return `${baseUrl.replace(".my.salesforce.com", ".lightning.force.com")}/lightning/r/${url.match(/sobjects\/([^/]+)\/([^/]+)/).slice(1)
47
+ .join("/")}/view`;
48
+ },
49
+ },
43
50
  async run({ $ }) {
44
51
  const {
45
52
  sobjectType,
@@ -56,6 +63,7 @@ export default {
56
63
  },
57
64
  });
58
65
  const resultsFound = response.searchRecords.length;
66
+ const baseUrl = this.salesforce._baseApiUrl();
59
67
  response.searchRecords = response.searchRecords.map((record) => {
60
68
  const url = record?.attributes?.url;
61
69
  if (!url) return record;
@@ -63,7 +71,8 @@ export default {
63
71
  ...record,
64
72
  attributes: {
65
73
  ...record.attributes,
66
- url: `${this.salesforce._baseApiUrl()}${url}`,
74
+ url: `${baseUrl}${url}`, // api url
75
+ browserUrl: this.createBrowserUrl(baseUrl, url),
67
76
  },
68
77
  };
69
78
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pipedream/salesforce_rest_api",
3
- "version": "1.11.4",
3
+ "version": "1.11.6",
4
4
  "description": "Pipedream Salesforce (REST API) Components",
5
5
  "main": "salesforce_rest_api.app.mjs",
6
6
  "keywords": [
@@ -11,7 +11,7 @@
11
11
  "author": "Pipedream <support@pipedream.com> (https://pipedream.com/)",
12
12
  "dependencies": {
13
13
  "@pipedream/platform": "^3.1.1",
14
- "fast-xml-parser": "^5.3.4",
14
+ "fast-xml-parser": "^5.3.6",
15
15
  "handlebars": "^4.7.7",
16
16
  "lodash": "^4.17.23",
17
17
  "lodash-es": "^4.17.23",
@@ -7,7 +7,7 @@ export default {
7
7
  name: "Case Updated (Instant, of Selectable Type)",
8
8
  key: "salesforce_rest_api-case-updated-instant",
9
9
  description: "Emit new event when a case is updated. [See the documentation](https://sforce.co/3yPSJZy)",
10
- version: "0.0.7",
10
+ version: "0.0.8",
11
11
  props: {
12
12
  salesforce: common.props.salesforce,
13
13
  db: "$.service.db",
@@ -15,8 +15,10 @@ export default {
15
15
  if (!this.skipFirstRun) {
16
16
  const { recentItems } = await this.salesforce.listSObjectTypeIds(objectType);
17
17
  const ids = recentItems.map((item) => item.Id);
18
+
18
19
  for (const id of ids.slice(-25)) {
19
20
  const object = await this.salesforce.getSObject(objectType, id);
21
+
20
22
  const event = {
21
23
  body: {
22
24
  "New": object,
@@ -65,6 +67,89 @@ export default {
65
67
  },
66
68
  methods: {
67
69
  ...common.methods,
70
+ getHistoryObjectName(objectType) {
71
+ if (objectType.endsWith("__c")) {
72
+ return objectType.replace(/__c$/, "__History");
73
+ }
74
+ return `${objectType}History`;
75
+ },
76
+ getHistoryParentIdField(objectType) {
77
+ if (objectType.endsWith("__c")) {
78
+ return "ParentId";
79
+ }
80
+ return `${objectType}Id`;
81
+ },
82
+ async queryFieldHistory({
83
+ objectType, recordIds, fields, startTimestamp,
84
+ }) {
85
+ if (!recordIds?.length || !fields?.length) {
86
+ return new Set();
87
+ }
88
+
89
+ const validFields = fields.filter((f) => /^[a-zA-Z_]\w*$/.test(f));
90
+ if (!validFields.length) {
91
+ return new Set();
92
+ }
93
+
94
+ const historyObjectName = this.getHistoryObjectName(objectType);
95
+ const parentIdField = this.getHistoryParentIdField(objectType);
96
+ const fieldList = validFields.map((f) => `'${f}'`).join(", ");
97
+
98
+ const BATCH_SIZE = 200;
99
+ const recordsWithChanges = new Set();
100
+ let totalHistoryRecords = 0;
101
+ let successfulBatches = 0;
102
+ let failedBatches = 0;
103
+ const totalBatches = Math.ceil(recordIds.length / BATCH_SIZE);
104
+
105
+ for (let i = 0; i < recordIds.length; i += BATCH_SIZE) {
106
+ const batch = recordIds.slice(i, i + BATCH_SIZE);
107
+ const recordIdList = batch.map((id) => `'${id}'`).join(", ");
108
+ const batchNumber = Math.floor(i / BATCH_SIZE) + 1;
109
+
110
+ const query = `
111
+ SELECT ${parentIdField}, Field, OldValue, NewValue, CreatedDate
112
+ FROM ${historyObjectName}
113
+ WHERE ${parentIdField} IN (${recordIdList})
114
+ AND Field IN (${fieldList})
115
+ AND CreatedDate >= ${startTimestamp}
116
+ ORDER BY CreatedDate DESC
117
+ `;
118
+
119
+ try {
120
+ const { records } = await this.query({
121
+ query,
122
+ });
123
+
124
+ totalHistoryRecords += records.length;
125
+ for (const record of records) {
126
+ recordsWithChanges.add(record[parentIdField]);
127
+ }
128
+ successfulBatches++;
129
+ } catch (err) {
130
+ failedBatches++;
131
+ console.log(`Field history query batch ${batchNumber}/${totalBatches} failed for ${historyObjectName}: ${err.message}`);
132
+ }
133
+ }
134
+
135
+ // If all batches failed, fall back to emitting all records
136
+ if (successfulBatches === 0) {
137
+ console.log("All field history query batches failed.");
138
+ console.log("This usually means field history tracking is not enabled for this object or the selected fields.");
139
+ console.log("To enable field history tracking in Salesforce:");
140
+ console.log("1. Go to Setup → Object Manager → [Your Object] → Fields & Relationships");
141
+ console.log("2. Click 'Set History Tracking' and select the fields you want to track");
142
+ console.log("Falling back to emitting all updated records without field filtering.");
143
+ return null;
144
+ }
145
+
146
+ if (failedBatches > 0) {
147
+ console.log(`Warning: ${failedBatches}/${totalBatches} batches failed, results may be incomplete`);
148
+ }
149
+
150
+ console.log(`Field history query found ${totalHistoryRecords} change(s) for ${recordsWithChanges.size} record(s)`);
151
+ return recordsWithChanges;
152
+ },
68
153
  generateWebhookMeta(data) {
69
154
  const nameField = this.getNameField();
70
155
  const { New: newObject } = data.body;
@@ -157,9 +242,10 @@ export default {
157
242
 
158
243
  const fieldName = getNameField();
159
244
  const columns = getObjectTypeColumns();
245
+ const objectType = this.getObjectType();
160
246
 
161
247
  const events = await paginate({
162
- objectType: this.getObjectType(),
248
+ objectType,
163
249
  startTimestamp,
164
250
  endTimestamp,
165
251
  columns,
@@ -177,7 +263,42 @@ export default {
177
263
  latestDateCovered.setSeconds(0);
178
264
  setLatestDateCovered(latestDateCovered.toISOString());
179
265
 
180
- Array.from(events)
266
+ const { fields } = this;
267
+
268
+ if (!fields?.length) {
269
+ Array.from(events)
270
+ .reverse()
271
+ .forEach((item) => {
272
+ const meta = generateTimerMeta(item, fieldName);
273
+ emit(item, meta);
274
+ });
275
+ return;
276
+ }
277
+
278
+ const recordIds = events.map((event) => event.Id);
279
+ const recordsWithFieldChanges = await this.queryFieldHistory({
280
+ objectType,
281
+ recordIds,
282
+ fields,
283
+ startTimestamp,
284
+ });
285
+
286
+ if (recordsWithFieldChanges === null) {
287
+ console.log("Emitting all updated records due to field history unavailability");
288
+ Array.from(events)
289
+ .reverse()
290
+ .forEach((item) => {
291
+ const meta = generateTimerMeta(item, fieldName);
292
+ emit(item, meta);
293
+ });
294
+ return;
295
+ }
296
+
297
+ const filteredEvents = events.filter((item) => recordsWithFieldChanges.has(item.Id));
298
+
299
+ console.log(`Filtered ${events.length} updated records to ${filteredEvents.length} with watched field changes`);
300
+
301
+ Array.from(filteredEvents)
181
302
  .reverse()
182
303
  .forEach((item) => {
183
304
  const meta = generateTimerMeta(item, fieldName);
@@ -7,7 +7,7 @@ export default {
7
7
  name: "Email Template Updated (Instant, of Selectable Type)",
8
8
  key: "salesforce_rest_api-email-template-updated-instant",
9
9
  description: "Emit new event when an email template is updated. [See the documentation](https://sforce.co/3yPSJZy)",
10
- version: "0.0.7",
10
+ version: "0.0.8",
11
11
  props: {
12
12
  salesforce: common.props.salesforce,
13
13
  db: "$.service.db",
@@ -7,7 +7,7 @@ export default {
7
7
  name: "Knowledge Article Updated (Instant, of Selectable Type)",
8
8
  key: "salesforce_rest_api-knowledge-article-updated-instant",
9
9
  description: "Emit new event when a knowledge article is updated. [See the documentation](https://sforce.co/3yPSJZy)",
10
- version: "0.0.7",
10
+ version: "0.0.8",
11
11
  props: {
12
12
  salesforce: common.props.salesforce,
13
13
  db: "$.service.db",
@@ -7,7 +7,7 @@ export default {
7
7
  name: "New Case (Instant, of Selectable Type)",
8
8
  key: "salesforce_rest_api-new-case-instant",
9
9
  description: "Emit new event when a case is created. [See the documentation](https://sforce.co/3yPSJZy)",
10
- version: "0.0.7",
10
+ version: "0.0.8",
11
11
  props: {
12
12
  salesforce: common.props.salesforce,
13
13
  db: "$.service.db",
@@ -7,7 +7,7 @@ export default {
7
7
  name: "New Email Template (Instant, of Selectable Type)",
8
8
  key: "salesforce_rest_api-new-email-template-instant",
9
9
  description: "Emit new event when an email template is created. [See the documentation](https://sforce.co/3yPSJZy)",
10
- version: "0.0.7",
10
+ version: "0.0.8",
11
11
  props: {
12
12
  salesforce: common.props.salesforce,
13
13
  db: "$.service.db",
@@ -7,7 +7,7 @@ export default {
7
7
  name: "New Knowledge Article (Instant, of Selectable Type)",
8
8
  key: "salesforce_rest_api-new-knowledge-article-instant",
9
9
  description: "Emit new event when a knowledge article is created. [See the documentation](https://sforce.co/3yPSJZy)",
10
- version: "0.0.7",
10
+ version: "0.0.8",
11
11
  props: {
12
12
  salesforce: common.props.salesforce,
13
13
  db: "$.service.db",
@@ -6,7 +6,7 @@ export default {
6
6
  name: "New Outbound Message (Instant)",
7
7
  key: "salesforce_rest_api-new-outbound-message",
8
8
  description: "Emit new event when a new outbound message is received in Salesforce.",
9
- version: "0.1.12",
9
+ version: "0.1.13",
10
10
  dedupe: "unique",
11
11
  props: {
12
12
  db: "$.service.db",
@@ -6,7 +6,7 @@ export default {
6
6
  name: "New Record (Instant, of Selectable Type)",
7
7
  key: "salesforce_rest_api-new-record-instant",
8
8
  description: "Emit new event when a record of the selected object type is created. [See the documentation](https://sforce.co/3yPSJZy)",
9
- version: "0.2.7",
9
+ version: "0.2.8",
10
10
  props: {
11
11
  ...common.props,
12
12
  fieldsToObtain: {
@@ -7,7 +7,7 @@ export default {
7
7
  name: "New Deleted Record (Instant, of Selectable Type)",
8
8
  key: "salesforce_rest_api-record-deleted-instant",
9
9
  description: "Emit new event when a record of the selected object type is deleted. [See the documentation](https://sforce.co/3msDDEE)",
10
- version: "0.1.6",
10
+ version: "0.1.7",
11
11
  methods: {
12
12
  ...common.methods,
13
13
  generateWebhookMeta(data) {
@@ -7,7 +7,7 @@ export default {
7
7
  name: "New Updated Record (Instant, of Selectable Type)",
8
8
  key: "salesforce_rest_api-record-updated-instant",
9
9
  description: "Emit new event when a record of the selected type is updated. [See the documentation](https://sforce.co/3yPSJZy)",
10
- version: "0.2.7",
10
+ version: "0.2.8",
11
11
  props: {
12
12
  ...common.props,
13
13
  fields: {