diff --git a/components/salesforce_rest_api/actions/get-case/get-case.mjs b/components/salesforce_rest_api/actions/get-case/get-case.mjs new file mode 100644 index 0000000000000..06c33203e0f71 --- /dev/null +++ b/components/salesforce_rest_api/actions/get-case/get-case.mjs @@ -0,0 +1,36 @@ +import salesforce from "../../salesforce_rest_api.app.mjs"; + +export default { + key: "salesforce_rest_api-get-case", + name: "Get Case", + description: "Retrieves a case by its ID. [See the documentation](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_get_field_values.htm)", + version: "0.0.1", + type: "action", + props: { + salesforce, + caseId: { + propDefinition: [ + salesforce, + "recordId", + () => ({ + objType: "Case", + }), + ], + label: "Case ID", + description: "The case ID to retrieve", + }, + }, + async run({ $ }) { + const fields = (await this.salesforce.getFieldsForObjectType("Case")).map(({ name }) => name); + + let query = `SELECT ${fields.join(", ")} FROM Case WHERE Id = '${this.caseId}'`; + + const { records } = await this.salesforce.query({ + $, + query, + }); + + $.export("$summary", `Successfully retrieved case with ID ${this.caseId}`); + return records[0]; + }, +}; diff --git a/components/salesforce_rest_api/actions/get-user/get-user.mjs b/components/salesforce_rest_api/actions/get-user/get-user.mjs new file mode 100644 index 0000000000000..4f0b8a51962ca --- /dev/null +++ b/components/salesforce_rest_api/actions/get-user/get-user.mjs @@ -0,0 +1,34 @@ +import salesforce from "../../salesforce_rest_api.app.mjs"; + +export default { + key: "salesforce_rest_api-get-user", + name: "Get User", + description: "Retrieves a user by their ID. [See the documentation](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_get_field_values.htm)", + version: "0.0.1", + type: "action", + props: { + salesforce, + userId: { + propDefinition: [ + salesforce, + "recordId", + () => ({ + objType: "User", + }), + ], + }, + }, + async run({ $ }) { + const fields = (await this.salesforce.getFieldsForObjectType("User")).map(({ name }) => name); + + let query = `SELECT ${fields.join(", ")} FROM User WHERE Id = '${this.userId}'`; + + const { records } = await this.salesforce.query({ + $, + query, + }); + + $.export("$summary", `Sucessfully retrieved user with ID ${this.userId}`); + return records[0]; + }, +}; diff --git a/components/salesforce_rest_api/actions/list-case-comments/list-case-comments.mjs b/components/salesforce_rest_api/actions/list-case-comments/list-case-comments.mjs new file mode 100644 index 0000000000000..2080bd18339ce --- /dev/null +++ b/components/salesforce_rest_api/actions/list-case-comments/list-case-comments.mjs @@ -0,0 +1,32 @@ +import salesforce from "../../salesforce_rest_api.app.mjs"; + +export default { + key: "salesforce_rest_api-list-case-comments", + name: "List Case Comments", + description: "Lists all comments for a case. [See the documentation](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_get_field_values.htm)", + version: "0.0.1", + type: "action", + props: { + salesforce, + caseId: { + propDefinition: [ + salesforce, + "recordId", + () => ({ + objType: "Case", + }), + ], + }, + }, + async run({ $ }) { + const fields = (await this.salesforce.getFieldsForObjectType("CaseComment")).map(({ name }) => name); + let query = `SELECT ${fields.join(", ")} FROM CaseComment WHERE ParentId = '${this.caseId}'`; + + const { records } = await this.salesforce.query({ + $, + query, + }); + $.export("$summary", `Sucessfully retrieved ${records.length} comments for case with ID ${this.caseId}`); + return records; + }, +}; diff --git a/components/salesforce_rest_api/actions/list-email-messages/list-email-messages.mjs b/components/salesforce_rest_api/actions/list-email-messages/list-email-messages.mjs new file mode 100644 index 0000000000000..00feecf859654 --- /dev/null +++ b/components/salesforce_rest_api/actions/list-email-messages/list-email-messages.mjs @@ -0,0 +1,38 @@ +import salesforce from "../../salesforce_rest_api.app.mjs"; + +export default { + key: "salesforce_rest_api-list-email-messages", + name: "List Email Messages", + description: "Lists all email messages for a case. [See the documentation](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_get_field_values.htm)", + version: "0.0.1", + type: "action", + props: { + salesforce, + caseId: { + propDefinition: [ + salesforce, + "recordId", + () => ({ + objType: "Case", + }), + ], + label: "Case ID", + description: "The ID of the case to retrieve email messages for", + optional: true, + }, + }, + async run({ $ }) { + const fields = (await this.salesforce.getFieldsForObjectType("EmailMessage")).map(({ name }) => name); + let query = `SELECT ${fields.join(", ")} FROM EmailMessage`; + if (this.caseId) { + query += ` WHERE RelatedToId = '${this.caseId}'`; + } + + const { records } = await this.salesforce.query({ + $, + query, + }); + $.export("$summary", `Sucessfully retrieved ${records.length} email messages for case with ID ${this.caseId}`); + return records; + }, +}; diff --git a/components/salesforce_rest_api/actions/list-email-templates/list-email-templates.mjs b/components/salesforce_rest_api/actions/list-email-templates/list-email-templates.mjs new file mode 100644 index 0000000000000..d61c43d2e5712 --- /dev/null +++ b/components/salesforce_rest_api/actions/list-email-templates/list-email-templates.mjs @@ -0,0 +1,23 @@ +import salesforce from "../../salesforce_rest_api.app.mjs"; + +export default { + key: "salesforce_rest_api-list-email-templates", + name: "List Email Templates", + description: "Lists all email templates. [See the documentation](https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_emailtemplate.htm)", + version: "0.0.1", + type: "action", + props: { + salesforce, + }, + async run({ $ }) { + const fields = (await this.salesforce.getFieldsForObjectType("EmailTemplate")).map(({ name }) => name); + const query = `SELECT ${fields.join(", ")} FROM EmailTemplate`; + + const { records } = await this.salesforce.query({ + $, + query, + }); + $.export("$summary", `Sucessfully retrieved ${records.length} email templates`); + return records; + }, +}; diff --git a/components/salesforce_rest_api/actions/list-knowledge-articles/list-knowledge-articles.mjs b/components/salesforce_rest_api/actions/list-knowledge-articles/list-knowledge-articles.mjs new file mode 100644 index 0000000000000..05dbcc74fb4a2 --- /dev/null +++ b/components/salesforce_rest_api/actions/list-knowledge-articles/list-knowledge-articles.mjs @@ -0,0 +1,23 @@ +import salesforce from "../../salesforce_rest_api.app.mjs"; + +export default { + key: "salesforce_rest_api-list-knowledge-articles", + name: "List Knowledge Articles", + description: "Lists all knowledge articles. [See the documentation](https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_knowledgearticle.htm)", + version: "0.0.1", + type: "action", + props: { + salesforce, + }, + async run({ $ }) { + const fields = (await this.salesforce.getFieldsForObjectType("KnowledgeArticle")).map(({ name }) => name); + const query = `SELECT ${fields.join(", ")} FROM KnowledgeArticle`; + + const { records } = await this.salesforce.query({ + $, + query, + }); + $.export("$summary", `Sucessfully retrieved ${records.length} knowledge articles`); + return records; + }, +}; diff --git a/components/salesforce_rest_api/actions/send-email/send-email.mjs b/components/salesforce_rest_api/actions/send-email/send-email.mjs new file mode 100644 index 0000000000000..83f3580c461da --- /dev/null +++ b/components/salesforce_rest_api/actions/send-email/send-email.mjs @@ -0,0 +1,60 @@ +import salesforce from "../../salesforce_rest_api.app.mjs"; + +export default { + key: "salesforce_rest_api-send-email", + name: "Send Email", + description: "Sends an email. [See the documentation](https://developer.salesforce.com/docs/atlas.en-us.api_action.meta/api_action/actions_obj_email_simple.htm)", + version: "0.0.1", + type: "action", + props: { + salesforce, + emailAddress: { + type: "string", + label: "Email Address", + description: "The email address to send the email to", + }, + emailSubject: { + type: "string", + label: "Subject", + description: "The subject of the email", + }, + emailBody: { + type: "string", + label: "Body", + description: "The body of the email", + }, + logEmailOnSend: { + type: "boolean", + label: "Log Email on Send", + description: "Indicates whether to log the email on the specified records’ activity time lines", + optional: true, + }, + }, + methods: { + sendEmail(opts = {}) { + return this.salesforce._makeRequest({ + url: `${this.salesforce._baseApiVersionUrl()}/actions/standard/emailSimple`, + method: "POST", + ...opts, + }); + }, + }, + async run({ $ }) { + const response = await this.sendEmail({ + $, + data: { + inputs: [ + { + emailAddresses: this.emailAddress, + emailSubject: this.emailSubject, + emailBody: this.emailBody, + senderType: "CurrentUser", + logEmailOnSend: this.logEmailOnSend, + }, + ], + }, + }); + $.export("$summary", `Email sent to ${this.emailAddress}`); + return response; + }, +}; diff --git a/components/salesforce_rest_api/actions/update-email-template/update-email-template.mjs b/components/salesforce_rest_api/actions/update-email-template/update-email-template.mjs new file mode 100644 index 0000000000000..7065121424ad9 --- /dev/null +++ b/components/salesforce_rest_api/actions/update-email-template/update-email-template.mjs @@ -0,0 +1,81 @@ +import { + convertFieldsToProps, getAdditionalFields, +} from "../../common/props-utils.mjs"; +import salesforce from "../../salesforce_rest_api.app.mjs"; +import { additionalFields } from "../common/base-create-update.mjs"; + +export default { + key: "salesforce_rest_api-update-email-template", + name: "Update Email Template", + description: "Updates an email template. [See the documentation](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_update_fields.htm)", + version: "0.0.1", + type: "action", + props: { + salesforce, + recordId: { + propDefinition: [ + salesforce, + "recordId", + () => ({ + objType: "EmailTemplate", + }), + ], + description: "The email template to update.", + }, + fieldsToUpdate: { + propDefinition: [ + salesforce, + "fieldsToUpdate", + () => ({ + objType: "EmailTemplate", + }), + ], + reloadProps: true, + }, + }, + methods: { + getAdditionalFields, + convertFieldsToProps, + }, + async additionalProps() { + const { fieldsToUpdate } = this; + const fields = await this.salesforce.getFieldsForObjectType("EmailTemplate"); + + const selectedFields = fields.filter(({ name }) => fieldsToUpdate.includes(name)); + const selectedFieldProps = this.convertFieldsToProps(selectedFields); + + return { + docsInfo: { + type: "alert", + alertType: "info", + content: "[See the documentation](https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_emailtemplate.htm) for information on all available fields.", + }, + ...selectedFieldProps, + additionalFields, + }; + }, + async run({ $ }) { + /* eslint-disable no-unused-vars */ + const { + salesforce, + recordId, + fieldsToUpdate, + getAdditionalFields: getData, + convertFieldsToProps, + docsInfo, + additionalFields, + ...data + } = this; + /* eslint-enable no-unused-vars */ + const response = await this.salesforce.updateRecord("EmailTemplate", { + $, + id: recordId, + data: { + ...data, + ...getData(), + }, + }); + $.export("$summary", `Successfully updated Email Template record (ID: ${recordId})`); + return response; + }, +}; diff --git a/components/salesforce_rest_api/package.json b/components/salesforce_rest_api/package.json index 72086b4a1c7d9..eeea29b8909b2 100644 --- a/components/salesforce_rest_api/package.json +++ b/components/salesforce_rest_api/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/salesforce_rest_api", - "version": "1.6.1", + "version": "1.7.0", "description": "Pipedream Salesforce (REST API) Components", "main": "salesforce_rest_api.app.mjs", "keywords": [ diff --git a/components/salesforce_rest_api/sources/case-updated-instant/case-updated-instant.mjs b/components/salesforce_rest_api/sources/case-updated-instant/case-updated-instant.mjs new file mode 100644 index 0000000000000..66c17cdcd437b --- /dev/null +++ b/components/salesforce_rest_api/sources/case-updated-instant/case-updated-instant.mjs @@ -0,0 +1,43 @@ +import common from "../common/common-updated-record.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; + +export default { + ...common, + type: "source", + name: "Case Updated (Instant, of Selectable Type)", + key: "salesforce_rest_api-case-updated-instant", + description: "Emit new event when a case is updated. [See the documentation](https://sforce.co/3yPSJZy)", + version: "0.0.1", + props: { + salesforce: common.props.salesforce, + db: "$.service.db", + http: { + type: "$.interface.http", + customResponse: true, + }, + timer: { + type: "$.interface.timer", + description: "The timer is only used as a fallback if instant event delivery (webhook) is not available.", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + fieldsToObtain: { + propDefinition: [ + common.props.salesforce, + "fieldsToObtain", + () => ({ + objType: "Case", + }), + ], + optional: true, + description: "Select the field(s) to be retrieved for the records. Only applicable if the source is running on a timer. If running on a webhook, or if not specified, all fields will be retrieved.", + }, + }, + methods: { + ...common.methods, + getObjectType() { + return "Case"; + }, + }, +}; diff --git a/components/salesforce_rest_api/sources/common/common-new-record.mjs b/components/salesforce_rest_api/sources/common/common-new-record.mjs new file mode 100644 index 0000000000000..157458a695b6b --- /dev/null +++ b/components/salesforce_rest_api/sources/common/common-new-record.mjs @@ -0,0 +1,157 @@ +import startCase from "lodash/startCase.js"; +import { v4 as uuidv4 } from "uuid"; +import common from "../common/common.mjs"; + +export default { + ...common, + hooks: { + ...common.hooks, + async deploy() { + const objectType = this.getObjectType(); + const nameField = await this.salesforce.getNameFieldForObjectType(objectType); + this.setNameField(nameField); + + // emit historical events + const { recentItems } = await this.salesforce.listSObjectTypeIds(objectType); + const ids = recentItems.map((item) => item.Id); + for (const id of ids.slice(-25)) { + const object = await this.salesforce.getSObject(objectType, id); + const event = { + body: { + "New": object, + "UserId": id, + }, + }; + this.processWebhookEvent(event); + } + }, + async activate() { + // Attempt to create the webhook + const secretToken = uuidv4(); + let webhookData; + const objectType = this.getObjectType(); + try { + webhookData = await this.createWebhook({ + endpointUrl: this.http.endpoint, + sObjectType: objectType, + event: this.getEventType(), + secretToken, + fieldsToCheck: this.getFieldsToCheck(), + fieldsToCheckMode: this.getFieldsToCheckMode(), + skipValidation: true, // neccessary for custom objects + }); + console.log("Webhook created successfully"); + } catch (err) { + console.log("Error creating webhook:", err); + console.log("The source will operate on the polling schedule instead."); + + const latestDateCovered = this.getLatestDateCovered(); + if (!latestDateCovered) { + const now = new Date().toISOString(); + this.setLatestDateCovered(now); + } + + await this.timerActivateHook?.(); + } + this._setSecretToken(secretToken); + this._setWebhookData(webhookData); + + const nameField = await this.salesforce.getNameFieldForObjectType(objectType); + this.setNameField(nameField); + }, + }, + methods: { + ...common.methods, + generateTimerMeta(item, fieldName) { + const { objectType } = this; + const { + CreatedDate: createdDate, + [fieldName]: name, + Id: id, + } = item; + const entityType = startCase(objectType); + const summary = `New ${entityType} created: ${name ?? id}`; + const ts = Date.parse(createdDate); + return { + id, + summary, + ts, + }; + }, + generateWebhookMeta(data) { + const nameField = this.getNameField(); + const { New: newObject } = data.body; + const { + CreatedDate: createdDate, + Id: id, + [nameField]: name, + } = newObject; + const summary = `New ${this.getObjectType()} created: ${name ?? id}`; + const ts = Date.parse(createdDate); + return { + id, + summary, + ts, + }; + }, + getEventType() { + return "new"; + }, + async processTimerEvent(eventData) { + const { + paginate, + setLatestDateCovered, + getObjectTypeColumns, + getNameField, + generateTimerMeta, + $emit: emit, + } = this; + + const { + startTimestamp, + endTimestamp, + } = eventData; + + const fieldName = getNameField(); + const columns = getObjectTypeColumns(); + + const events = await paginate({ + objectType: this.getObjectType(), + startTimestamp, + endTimestamp, + columns, + }); + + const [ + latestEvent, + ] = events; + + if (latestEvent?.CreatedDate) { + const latestDateCovered = new Date(latestEvent.CreatedDate); + latestDateCovered.setSeconds(0); + setLatestDateCovered(latestDateCovered.toISOString()); + } + + Array.from(events) + .reverse() + .forEach((item) => { + const meta = generateTimerMeta(item, fieldName); + emit(item, meta); + }); + }, + async timerActivateHook() { + const { + getObjectTypeDescription, + setObjectTypeColumns, + } = this; + + let columns = this.fieldsToObtain; + if (!columns?.length) { + const { fields } = await getObjectTypeDescription(this.getObjectType()); + columns = fields.map(({ name }) => name); + } + + setObjectTypeColumns(columns); + }, + }, +}; diff --git a/components/salesforce_rest_api/sources/common/common-updated-record.mjs b/components/salesforce_rest_api/sources/common/common-updated-record.mjs new file mode 100644 index 0000000000000..3a4faf12c647e --- /dev/null +++ b/components/salesforce_rest_api/sources/common/common-updated-record.mjs @@ -0,0 +1,198 @@ +import startCase from "lodash/startCase.js"; +import common from "../common/common.mjs"; +import constants from "../../common/constants.mjs"; +import { v4 as uuidv4 } from "uuid"; + +export default { + ...common, + hooks: { + ...common.hooks, + async deploy() { + const objectType = this.getObjectType(); + const nameField = await this.salesforce.getNameFieldForObjectType(objectType); + this.setNameField(nameField); + + // emit historical events + const { recentItems } = await this.salesforce.listSObjectTypeIds(objectType); + const ids = recentItems.map((item) => item.Id); + for (const id of ids.slice(-25)) { + const object = await this.salesforce.getSObject(objectType, id); + const event = { + body: { + "New": object, + "UserId": id, + }, + }; + const meta = this.generateWebhookMeta(event); + this.$emit(event.body, meta); + } + }, + async activate() { + // Attempt to create the webhook + const secretToken = uuidv4(); + let webhookData; + const objectType = this.getObjectType(); + try { + webhookData = await this.createWebhook({ + endpointUrl: this.http.endpoint, + sObjectType: objectType, + event: this.getEventType(), + secretToken, + fieldsToCheck: this.getFieldsToCheck(), + fieldsToCheckMode: this.getFieldsToCheckMode(), + skipValidation: true, // neccessary for custom objects + }); + console.log("Webhook created successfully"); + } catch (err) { + console.log("Error creating webhook:", err); + console.log("The source will operate on the polling schedule instead."); + + const latestDateCovered = this.getLatestDateCovered(); + if (!latestDateCovered) { + const now = new Date().toISOString(); + this.setLatestDateCovered(now); + } + + await this.timerActivateHook?.(); + } + this._setSecretToken(secretToken); + this._setWebhookData(webhookData); + + const nameField = await this.salesforce.getNameFieldForObjectType(objectType); + this.setNameField(nameField); + }, + }, + methods: { + ...common.methods, + generateWebhookMeta(data) { + const nameField = this.getNameField(); + const { New: newObject } = data.body; + const { + LastModifiedDate: lastModifiedDate, + Id: id, + [nameField]: name, + } = newObject; + const summary = `${this.getObjectType()} updated: ${name}`; + const ts = Date.parse(lastModifiedDate); + const compositeId = `${id}-${ts}`; + return { + id: compositeId, + summary, + ts, + }; + }, + generateTimerMeta(item, fieldName) { + const { + LastModifiedDate: lastModifiedDate, + [fieldName]: name, + Id: id, + } = item; + + const entityType = startCase(this.getObjectType()); + const summary = `${entityType} updated: ${name}`; + const ts = Date.parse(lastModifiedDate); + return { + id: `${id}-${ts}`, + summary, + ts, + }; + }, + getEventType() { + return "updated"; + }, + isEventRelevant(changedFields) { + const { fields } = this; + return fields?.length + ? Object.keys(changedFields).some((key) => fields.includes(key)) + : true; + }, + getChangedFields(body) { + return Object.entries(body.New).filter(([ + key, + value, + ]) => { + const oldValue = body.Old[key]; + return ( + value !== undefined + && oldValue !== undefined + && JSON.stringify(value) !== JSON.stringify(oldValue) + ); + }) + .reduce((obj, [ + key, + value, + ]) => { + obj[key] = { + old: body.Old[key], + new: value, + }; + return obj; + }, {}); + }, + processWebhookEvent(event) { + const { body } = event; + const changedFields = this.getChangedFields(body); + if (this.isEventRelevant(changedFields)) { + const meta = this.generateWebhookMeta(event); + this.$emit({ + ...body, + changedFields, + }, meta); + } + }, + async processTimerEvent(eventData) { + const { + getNameField, + getObjectTypeColumns, + paginate, + setLatestDateCovered, + generateTimerMeta, + $emit: emit, + } = this; + + const { + startTimestamp, + endTimestamp, + } = eventData; + + const fieldName = getNameField(); + const columns = getObjectTypeColumns(); + + const events = await paginate({ + objectType: this.getObjectType(), + startTimestamp, + endTimestamp, + columns, + dateFieldName: constants.FIELD_NAME.LAST_MODIFIED_DATE, + }); + + const [ + latestEvent, + ] = events; + + if (latestEvent?.LastModifiedDate) { + const latestDateCovered = new Date(latestEvent.LastModifiedDate); + latestDateCovered.setSeconds(0); + setLatestDateCovered(latestDateCovered.toISOString()); + } + + Array.from(events) + .reverse() + .forEach((item) => { + const meta = generateTimerMeta(item, fieldName); + emit(item, meta); + }); + }, + async timerActivateHook() { + const { + getObjectTypeDescription, + setObjectTypeColumns, + } = this; + + const { fields } = await getObjectTypeDescription(this.getObjectType()); + const columns = fields.map(({ name }) => name); + + setObjectTypeColumns(columns); + }, + }, +}; diff --git a/components/salesforce_rest_api/sources/common-webhook-methods.mjs b/components/salesforce_rest_api/sources/common/common-webhook-methods.mjs similarity index 100% rename from components/salesforce_rest_api/sources/common-webhook-methods.mjs rename to components/salesforce_rest_api/sources/common/common-webhook-methods.mjs diff --git a/components/salesforce_rest_api/sources/common.mjs b/components/salesforce_rest_api/sources/common/common.mjs similarity index 98% rename from components/salesforce_rest_api/sources/common.mjs rename to components/salesforce_rest_api/sources/common/common.mjs index 38dbf81babe83..4e304965e4cc7 100644 --- a/components/salesforce_rest_api/sources/common.mjs +++ b/components/salesforce_rest_api/sources/common/common.mjs @@ -1,6 +1,6 @@ import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; -import salesforce from "../salesforce_rest_api.app.mjs"; -import constants from "../common/constants.mjs"; +import salesforce from "../../salesforce_rest_api.app.mjs"; +import constants from "../../common/constants.mjs"; import { v4 as uuidv4 } from "uuid"; import commonWebhookMethods from "./common-webhook-methods.mjs"; diff --git a/components/salesforce_rest_api/sources/email-template-updated-instant/email-template-updated-instant.mjs b/components/salesforce_rest_api/sources/email-template-updated-instant/email-template-updated-instant.mjs new file mode 100644 index 0000000000000..eccc9400d7dc7 --- /dev/null +++ b/components/salesforce_rest_api/sources/email-template-updated-instant/email-template-updated-instant.mjs @@ -0,0 +1,43 @@ +import common from "../common/common-updated-record.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; + +export default { + ...common, + type: "source", + name: "Email Template Updated (Instant, of Selectable Type)", + key: "salesforce_rest_api-email-template-updated-instant", + description: "Emit new event when an email template is updated. [See the documentation](https://sforce.co/3yPSJZy)", + version: "0.0.1", + props: { + salesforce: common.props.salesforce, + db: "$.service.db", + http: { + type: "$.interface.http", + customResponse: true, + }, + timer: { + type: "$.interface.timer", + description: "The timer is only used as a fallback if instant event delivery (webhook) is not available.", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + fieldsToObtain: { + propDefinition: [ + common.props.salesforce, + "fieldsToObtain", + () => ({ + objType: "EmailTemplate", + }), + ], + optional: true, + description: "Select the field(s) to be retrieved for the records. Only applicable if the source is running on a timer. If running on a webhook, or if not specified, all fields will be retrieved.", + }, + }, + methods: { + ...common.methods, + getObjectType() { + return "EmailTemplate"; + }, + }, +}; diff --git a/components/salesforce_rest_api/sources/knowledge-article-updated-instant/knowledge-article-updated-instant.mjs b/components/salesforce_rest_api/sources/knowledge-article-updated-instant/knowledge-article-updated-instant.mjs new file mode 100644 index 0000000000000..432527ce0d736 --- /dev/null +++ b/components/salesforce_rest_api/sources/knowledge-article-updated-instant/knowledge-article-updated-instant.mjs @@ -0,0 +1,43 @@ +import common from "../common/common-updated-record.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; + +export default { + ...common, + type: "source", + name: "Knowledge Article Updated (Instant, of Selectable Type)", + key: "salesforce_rest_api-knowledge-article-updated-instant", + description: "Emit new event when a knowledge article is updated. [See the documentation](https://sforce.co/3yPSJZy)", + version: "0.0.1", + props: { + salesforce: common.props.salesforce, + db: "$.service.db", + http: { + type: "$.interface.http", + customResponse: true, + }, + timer: { + type: "$.interface.timer", + description: "The timer is only used as a fallback if instant event delivery (webhook) is not available.", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + fieldsToObtain: { + propDefinition: [ + common.props.salesforce, + "fieldsToObtain", + () => ({ + objType: "KnowledgeArticle", + }), + ], + optional: true, + description: "Select the field(s) to be retrieved for the records. Only applicable if the source is running on a timer. If running on a webhook, or if not specified, all fields will be retrieved.", + }, + }, + methods: { + ...common.methods, + getObjectType() { + return "KnowledgeArticle"; + }, + }, +}; diff --git a/components/salesforce_rest_api/sources/new-case-instant/new-case-instant.mjs b/components/salesforce_rest_api/sources/new-case-instant/new-case-instant.mjs new file mode 100644 index 0000000000000..9c480289e8610 --- /dev/null +++ b/components/salesforce_rest_api/sources/new-case-instant/new-case-instant.mjs @@ -0,0 +1,43 @@ +import common from "../common/common-new-record.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; + +export default { + ...common, + type: "source", + name: "New Case (Instant, of Selectable Type)", + key: "salesforce_rest_api-new-case-instant", + description: "Emit new event when a case is created. [See the documentation](https://sforce.co/3yPSJZy)", + version: "0.0.1", + props: { + salesforce: common.props.salesforce, + db: "$.service.db", + http: { + type: "$.interface.http", + customResponse: true, + }, + timer: { + type: "$.interface.timer", + description: "The timer is only used as a fallback if instant event delivery (webhook) is not available.", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + fieldsToObtain: { + propDefinition: [ + common.props.salesforce, + "fieldsToObtain", + () => ({ + objType: "Case", + }), + ], + optional: true, + description: "Select the field(s) to be retrieved for the records. Only applicable if the source is running on a timer. If running on a webhook, or if not specified, all fields will be retrieved.", + }, + }, + methods: { + ...common.methods, + getObjectType() { + return "Case"; + }, + }, +}; diff --git a/components/salesforce_rest_api/sources/new-email-template-instant/new-email-template-instant.mjs b/components/salesforce_rest_api/sources/new-email-template-instant/new-email-template-instant.mjs new file mode 100644 index 0000000000000..19def8175c8b4 --- /dev/null +++ b/components/salesforce_rest_api/sources/new-email-template-instant/new-email-template-instant.mjs @@ -0,0 +1,43 @@ +import common from "../common/common-new-record.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; + +export default { + ...common, + type: "source", + name: "New Email Template (Instant, of Selectable Type)", + key: "salesforce_rest_api-new-email-template-instant", + description: "Emit new event when an email template is created. [See the documentation](https://sforce.co/3yPSJZy)", + version: "0.0.1", + props: { + salesforce: common.props.salesforce, + db: "$.service.db", + http: { + type: "$.interface.http", + customResponse: true, + }, + timer: { + type: "$.interface.timer", + description: "The timer is only used as a fallback if instant event delivery (webhook) is not available.", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + fieldsToObtain: { + propDefinition: [ + common.props.salesforce, + "fieldsToObtain", + () => ({ + objType: "EmailTemplate", + }), + ], + optional: true, + description: "Select the field(s) to be retrieved for the records. Only applicable if the source is running on a timer. If running on a webhook, or if not specified, all fields will be retrieved.", + }, + }, + methods: { + ...common.methods, + getObjectType() { + return "EmailTemplate"; + }, + }, +}; diff --git a/components/salesforce_rest_api/sources/new-knowledge-article-instant/new-knowledge-article-instant.mjs b/components/salesforce_rest_api/sources/new-knowledge-article-instant/new-knowledge-article-instant.mjs new file mode 100644 index 0000000000000..9ca6363c8eea7 --- /dev/null +++ b/components/salesforce_rest_api/sources/new-knowledge-article-instant/new-knowledge-article-instant.mjs @@ -0,0 +1,43 @@ +import common from "../common/common-new-record.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; + +export default { + ...common, + type: "source", + name: "New Knowledge Article (Instant, of Selectable Type)", + key: "salesforce_rest_api-new-knowledge-article-instant", + description: "Emit new event when a knowledge article is created. [See the documentation](https://sforce.co/3yPSJZy)", + version: "0.0.1", + props: { + salesforce: common.props.salesforce, + db: "$.service.db", + http: { + type: "$.interface.http", + customResponse: true, + }, + timer: { + type: "$.interface.timer", + description: "The timer is only used as a fallback if instant event delivery (webhook) is not available.", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + fieldsToObtain: { + propDefinition: [ + common.props.salesforce, + "fieldsToObtain", + () => ({ + objType: "KnowledgeArticle", + }), + ], + optional: true, + description: "Select the field(s) to be retrieved for the records. Only applicable if the source is running on a timer. If running on a webhook, or if not specified, all fields will be retrieved.", + }, + }, + methods: { + ...common.methods, + getObjectType() { + return "KnowledgeArticle"; + }, + }, +}; diff --git a/components/salesforce_rest_api/sources/new-record-instant/new-record-instant.mjs b/components/salesforce_rest_api/sources/new-record-instant/new-record-instant.mjs index 382ba6a826ba5..14c2dde66c1ab 100644 --- a/components/salesforce_rest_api/sources/new-record-instant/new-record-instant.mjs +++ b/components/salesforce_rest_api/sources/new-record-instant/new-record-instant.mjs @@ -1,5 +1,4 @@ -import startCase from "lodash/startCase.js"; -import common from "../common.mjs"; +import common from "../common/common-new-record.mjs"; export default { ...common, @@ -7,7 +6,7 @@ export default { name: "New Record (Instant, of Selectable Type)", key: "salesforce_rest_api-new-record-instant", description: "Emit new event when a record of the selected object type is created. [See the documentation](https://sforce.co/3yPSJZy)", - version: "0.2.0", + version: "0.2.1", props: { ...common.props, fieldsToObtain: { @@ -22,123 +21,10 @@ export default { description: "Select the field(s) to be retrieved for the records. Only applicable if the source is running on a timer. If running on a webhook, or if not specified, all fields will be retrieved.", }, }, - hooks: { - ...common.hooks, - async deploy() { - const objectType = this.objectType; - const nameField = await this.salesforce.getNameFieldForObjectType(objectType); - this.setNameField(nameField); - - // emit historical events - const { recentItems } = await this.salesforce.listSObjectTypeIds(objectType); - const ids = recentItems.map((item) => item.Id); - for (const id of ids.slice(-25)) { - const object = await this.salesforce.getSObject(objectType, id); - const event = { - body: { - "New": object, - "UserId": id, - }, - }; - this.processWebhookEvent(event); - } - }, - }, methods: { ...common.methods, - generateTimerMeta(item, fieldName) { - const { objectType } = this; - const { - CreatedDate: createdDate, - [fieldName]: name, - Id: id, - } = item; - const entityType = startCase(objectType); - const summary = `New ${entityType} created: ${name ?? id}`; - const ts = Date.parse(createdDate); - return { - id, - summary, - ts, - }; - }, - generateWebhookMeta(data) { - const nameField = this.getNameField(); - const { New: newObject } = data.body; - const { - CreatedDate: createdDate, - Id: id, - [nameField]: name, - } = newObject; - const entityType = startCase(this.objectType).toLowerCase(); - const summary = `New ${entityType} created: ${name ?? id}`; - const ts = Date.parse(createdDate); - return { - id, - summary, - ts, - }; - }, - getEventType() { - return "new"; - }, - async processTimerEvent(eventData) { - const { - paginate, - objectType, - setLatestDateCovered, - getObjectTypeColumns, - getNameField, - generateTimerMeta, - $emit: emit, - } = this; - - const { - startTimestamp, - endTimestamp, - } = eventData; - - const fieldName = getNameField(); - const columns = getObjectTypeColumns(); - - const events = await paginate({ - objectType, - startTimestamp, - endTimestamp, - columns, - }); - - const [ - latestEvent, - ] = events; - - if (latestEvent?.CreatedDate) { - const latestDateCovered = new Date(latestEvent.CreatedDate); - latestDateCovered.setSeconds(0); - setLatestDateCovered(latestDateCovered.toISOString()); - } - - Array.from(events) - .reverse() - .forEach((item) => { - const meta = generateTimerMeta(item, fieldName); - emit(item, meta); - }); - }, - async timerActivateHook() { - const { - objectType, - getObjectTypeDescription, - setObjectTypeColumns, - } = this; - - let columns = this.fieldsToObtain; - if (!columns?.length) { - const { fields } = await getObjectTypeDescription(objectType); - columns = fields.map(({ name }) => name); - } - - setObjectTypeColumns(columns); + getObjectType() { + return this.objectType; }, }, }; diff --git a/components/salesforce_rest_api/sources/record-deleted-instant/record-deleted-instant.mjs b/components/salesforce_rest_api/sources/record-deleted-instant/record-deleted-instant.mjs index 46cd9b7485fdc..8b3b239ea4b31 100644 --- a/components/salesforce_rest_api/sources/record-deleted-instant/record-deleted-instant.mjs +++ b/components/salesforce_rest_api/sources/record-deleted-instant/record-deleted-instant.mjs @@ -1,5 +1,5 @@ import startCase from "lodash/startCase.js"; -import common from "../common.mjs"; +import common from "../common/common.mjs"; export default { ...common, @@ -7,7 +7,7 @@ export default { name: "New Deleted Record (Instant, of Selectable Type)", key: "salesforce_rest_api-record-deleted-instant", description: "Emit new event when a record of the selected object type is deleted. [See the documentation](https://sforce.co/3msDDEE)", - version: "0.1.0", + version: "0.1.1", methods: { ...common.methods, generateWebhookMeta(data) { diff --git a/components/salesforce_rest_api/sources/record-updated-instant/record-updated-instant.mjs b/components/salesforce_rest_api/sources/record-updated-instant/record-updated-instant.mjs index 46db6b7992e6f..c0e98b34421a0 100644 --- a/components/salesforce_rest_api/sources/record-updated-instant/record-updated-instant.mjs +++ b/components/salesforce_rest_api/sources/record-updated-instant/record-updated-instant.mjs @@ -1,6 +1,4 @@ -import startCase from "lodash/startCase.js"; -import common from "../common.mjs"; -import constants from "../../common/constants.mjs"; +import common from "../common/common-updated-record.mjs"; const { salesforce } = common.props; export default { @@ -9,7 +7,7 @@ export default { name: "New Updated Record (Instant, of Selectable Type)", key: "salesforce_rest_api-record-updated-instant", description: "Emit new event when a record of the selected type is updated. [See the documentation](https://sforce.co/3yPSJZy)", - version: "0.2.0", + version: "0.2.1", props: { ...common.props, fields: { @@ -29,140 +27,8 @@ export default { }, methods: { ...common.methods, - generateWebhookMeta(data) { - const nameField = this.getNameField(); - const { New: newObject } = data.body; - const { - LastModifiedDate: lastModifiedDate, - Id: id, - [nameField]: name, - } = newObject; - const entityType = startCase(this.objectType); - const summary = `${entityType} updated: ${name}`; - const ts = Date.parse(lastModifiedDate); - const compositeId = `${id}-${ts}`; - return { - id: compositeId, - summary, - ts, - }; - }, - generateTimerMeta(item, fieldName) { - const { objectType } = this; - - const { - LastModifiedDate: lastModifiedDate, - [fieldName]: name, - Id: id, - } = item; - - const entityType = startCase(objectType); - const summary = `${entityType} updated: ${name}`; - const ts = Date.parse(lastModifiedDate); - return { - id: `${id}-${ts}`, - summary, - ts, - }; - }, - getEventType() { - return "updated"; - }, - isEventRelevant(changedFields) { - const { fields } = this; - return fields?.length - ? Object.keys(changedFields).some((key) => fields.includes(key)) - : true; - }, - getChangedFields(body) { - return Object.entries(body.New).filter(([ - key, - value, - ]) => { - const oldValue = body.Old[key]; - return ( - value !== undefined - && oldValue !== undefined - && JSON.stringify(value) !== JSON.stringify(oldValue) - ); - }) - .reduce((obj, [ - key, - value, - ]) => { - obj[key] = { - old: body.Old[key], - new: value, - }; - return obj; - }, {}); - }, - processWebhookEvent(event) { - const { body } = event; - const changedFields = this.getChangedFields(body); - if (this.isEventRelevant(changedFields)) { - const meta = this.generateWebhookMeta(event); - this.$emit({ - ...body, - changedFields, - }, meta); - } - }, - async processTimerEvent(eventData) { - const { - getNameField, - getObjectTypeColumns, - paginate, - objectType, - setLatestDateCovered, - generateTimerMeta, - $emit: emit, - } = this; - - const { - startTimestamp, - endTimestamp, - } = eventData; - - const fieldName = getNameField(); - const columns = getObjectTypeColumns(); - - const events = await paginate({ - objectType, - startTimestamp, - endTimestamp, - columns, - dateFieldName: constants.FIELD_NAME.LAST_MODIFIED_DATE, - }); - - const [ - latestEvent, - ] = events; - - if (latestEvent?.LastModifiedDate) { - const latestDateCovered = new Date(latestEvent.LastModifiedDate); - latestDateCovered.setSeconds(0); - setLatestDateCovered(latestDateCovered.toISOString()); - } - - Array.from(events) - .reverse() - .forEach((item) => { - const meta = generateTimerMeta(item, fieldName); - emit(item, meta); - }); - }, - async timerActivateHook() { - const { - objectType, - getObjectTypeDescription, - setObjectTypeColumns, - } = this; - - const { fields } = await getObjectTypeDescription(objectType); - const columns = fields.map(({ name }) => name); - - setObjectTypeColumns(columns); + getObjectType() { + return this.objectType; }, }, };