Add Activepieces integration for workflow automation
- Add Activepieces fork with SmoothSchedule custom piece - Create integrations app with Activepieces service layer - Add embed token endpoint for iframe integration - Create Automations page with embedded workflow builder - Add sidebar visibility fix for embed mode - Add list inactive customers endpoint to Public API - Include SmoothSchedule triggers: event created/updated/cancelled - Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
import { callSalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
export const addContactToCampaign = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'add_contact_to_campaign',
|
||||
displayName: 'Add Contact to Campaign',
|
||||
description: 'Add a contact to a campaign.',
|
||||
props: {
|
||||
campaign_id: salesforcesCommon.campaign,
|
||||
contact_id: salesforcesCommon.contact,
|
||||
status: salesforcesCommon.status,
|
||||
},
|
||||
async run(context) {
|
||||
const { campaign_id, contact_id, status } = context.propsValue;
|
||||
|
||||
const body = {
|
||||
CampaignId: campaign_id,
|
||||
ContactId: contact_id,
|
||||
Status: status,
|
||||
};
|
||||
|
||||
const response = await callSalesforceApi(
|
||||
HttpMethod.POST,
|
||||
context.auth,
|
||||
'/services/data/v56.0/sobjects/CampaignMember',
|
||||
body
|
||||
);
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
import { callSalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
export const addFileToRecord = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'add_file_to_record',
|
||||
displayName: 'Add File to Record',
|
||||
description: 'Uploads a file and attaches it to an existing record.',
|
||||
props: {
|
||||
object: salesforcesCommon.object,
|
||||
record_id: salesforcesCommon.record,
|
||||
file: Property.File({
|
||||
displayName: 'File',
|
||||
description: 'The file to upload.',
|
||||
required: true,
|
||||
}),
|
||||
file_name: Property.ShortText({
|
||||
displayName: 'File Name',
|
||||
description: 'The name of the file, including its extension (e.g., "report.pdf").',
|
||||
required: true,
|
||||
})
|
||||
},
|
||||
async run(context) {
|
||||
const { record_id, file, file_name } = context.propsValue;
|
||||
|
||||
const body = {
|
||||
Title: file_name,
|
||||
PathOnClient: file_name,
|
||||
VersionData: file.base64,
|
||||
FirstPublishLocationId: record_id,
|
||||
};
|
||||
|
||||
const response = await callSalesforceApi(
|
||||
HttpMethod.POST,
|
||||
context.auth,
|
||||
'/services/data/v56.0/sobjects/ContentVersion',
|
||||
body
|
||||
);
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
import { callSalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
export const addLeadToCampaign = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'add_lead_to_campaign',
|
||||
displayName: 'Add Lead to Campaign',
|
||||
description: 'Adds an existing lead to an existing campaign.',
|
||||
props: {
|
||||
campaign_id: salesforcesCommon.campaign,
|
||||
lead_id: salesforcesCommon.lead,
|
||||
status: salesforcesCommon.status,
|
||||
},
|
||||
async run(context) {
|
||||
const { campaign_id, lead_id, status } = context.propsValue;
|
||||
|
||||
const body = {
|
||||
CampaignId: campaign_id,
|
||||
LeadId: lead_id,
|
||||
Status: status,
|
||||
};
|
||||
|
||||
const response = await callSalesforceApi(
|
||||
HttpMethod.POST,
|
||||
context.auth,
|
||||
'/services/data/v56.0/sobjects/CampaignMember',
|
||||
body
|
||||
);
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
import { callSalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
export const createAttachment = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'create_attachment',
|
||||
displayName: 'Create Attachment (Legacy)',
|
||||
description: 'Creates a legacy Attachment record. Salesforce recommends using "Add File to Record" for modern apps.',
|
||||
props: {
|
||||
object: salesforcesCommon.object,
|
||||
parent_id: salesforcesCommon.record,
|
||||
file: Property.File({
|
||||
displayName: 'File',
|
||||
description: 'The file to attach.',
|
||||
required: true,
|
||||
}),
|
||||
file_name: Property.ShortText({
|
||||
displayName: 'File Name',
|
||||
description: 'The name of the file, including its extension (e.g., "attachment.pdf").',
|
||||
required: true,
|
||||
})
|
||||
},
|
||||
async run(context) {
|
||||
const { parent_id, file, file_name } = context.propsValue;
|
||||
|
||||
const body = {
|
||||
ParentId: parent_id,
|
||||
Name: file_name,
|
||||
Body: file.base64,
|
||||
};
|
||||
|
||||
const response = await callSalesforceApi(
|
||||
HttpMethod.POST,
|
||||
context.auth,
|
||||
'/services/data/v56.0/sobjects/Attachment',
|
||||
body
|
||||
);
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
import { callSalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
export const createCase = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'create_case',
|
||||
displayName: 'Create Case',
|
||||
description: 'Creates a Case, which represents a customer issue or problem.',
|
||||
props: {
|
||||
Subject: Property.ShortText({
|
||||
displayName: 'Subject',
|
||||
required: true,
|
||||
}),
|
||||
Description: Property.LongText({
|
||||
displayName: 'Description',
|
||||
required: false,
|
||||
}),
|
||||
Status: salesforcesCommon.caseStatus,
|
||||
Priority: salesforcesCommon.casePriority,
|
||||
Origin: salesforcesCommon.caseOrigin,
|
||||
AccountId: salesforcesCommon.account,
|
||||
ContactId: salesforcesCommon.optionalContact,
|
||||
other_fields: Property.Json({
|
||||
displayName: 'Other Fields',
|
||||
description: 'Enter additional fields as a JSON object.',
|
||||
required: false
|
||||
})
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
Subject,
|
||||
Description,
|
||||
Status,
|
||||
Priority,
|
||||
Origin,
|
||||
AccountId,
|
||||
ContactId,
|
||||
other_fields
|
||||
} = context.propsValue;
|
||||
|
||||
const rawBody = {
|
||||
Subject,
|
||||
Description,
|
||||
Status,
|
||||
Priority,
|
||||
Origin,
|
||||
AccountId,
|
||||
ContactId,
|
||||
...(other_fields || {}),
|
||||
};
|
||||
|
||||
const cleanedBody = Object.entries(rawBody).reduce((acc, [key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
acc[key] = value;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, unknown>);
|
||||
|
||||
const response = await callSalesforceApi(
|
||||
HttpMethod.POST,
|
||||
context.auth,
|
||||
'/services/data/v56.0/sobjects/Case',
|
||||
cleanedBody
|
||||
);
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
import { callSalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
export const createContact = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'create_contact',
|
||||
displayName: 'Create Contact',
|
||||
description: 'Creates a new contact record.',
|
||||
props: {
|
||||
LastName: Property.ShortText({
|
||||
displayName: 'Last Name',
|
||||
required: true,
|
||||
}),
|
||||
FirstName: Property.ShortText({
|
||||
displayName: 'First Name',
|
||||
required: false,
|
||||
}),
|
||||
AccountId: salesforcesCommon.account,
|
||||
Email: Property.ShortText({
|
||||
displayName: 'Email',
|
||||
required: false,
|
||||
}),
|
||||
Phone: Property.ShortText({
|
||||
displayName: 'Phone',
|
||||
required: false,
|
||||
}),
|
||||
Title: Property.ShortText({
|
||||
displayName: 'Title',
|
||||
required: false,
|
||||
}),
|
||||
other_fields: Property.Json({
|
||||
displayName: 'Other Fields',
|
||||
description: 'Enter additional fields as a JSON object.',
|
||||
required: false
|
||||
})
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
LastName,
|
||||
FirstName,
|
||||
AccountId,
|
||||
Email,
|
||||
Phone,
|
||||
Title,
|
||||
other_fields
|
||||
} = context.propsValue;
|
||||
|
||||
|
||||
const rawBody = {
|
||||
LastName,
|
||||
FirstName,
|
||||
AccountId,
|
||||
Email,
|
||||
Phone,
|
||||
Title,
|
||||
...(other_fields || {}),
|
||||
};
|
||||
|
||||
const cleanedBody = Object.entries(rawBody).reduce((acc, [key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
acc[key] = value;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, unknown>);
|
||||
|
||||
const response = await callSalesforceApi(
|
||||
HttpMethod.POST,
|
||||
context.auth,
|
||||
'/services/data/v56.0/sobjects/Contact',
|
||||
cleanedBody
|
||||
);
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
import { callSalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
export const createLead = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'create_lead',
|
||||
displayName: 'Create Lead',
|
||||
description: 'Creates a new lead.',
|
||||
props: {
|
||||
LastName: Property.ShortText({
|
||||
displayName: 'Last Name',
|
||||
required: true,
|
||||
}),
|
||||
Company: Property.ShortText({
|
||||
displayName: 'Company',
|
||||
required: true,
|
||||
}),
|
||||
FirstName: Property.ShortText({
|
||||
displayName: 'First Name',
|
||||
required: false,
|
||||
}),
|
||||
Email: Property.ShortText({
|
||||
displayName: 'Email',
|
||||
required: false,
|
||||
}),
|
||||
Phone: Property.ShortText({
|
||||
displayName: 'Phone',
|
||||
required: false,
|
||||
}),
|
||||
LeadSource: salesforcesCommon.leadSource,
|
||||
other_fields: Property.Json({
|
||||
displayName: 'Other Fields',
|
||||
description: 'Enter additional fields as a JSON object (e.g., {"Title": "Manager", "Website": "http://example.com"}).',
|
||||
required: false
|
||||
})
|
||||
},
|
||||
async run(context) {
|
||||
|
||||
const {
|
||||
LastName,
|
||||
Company,
|
||||
FirstName,
|
||||
Email,
|
||||
Phone,
|
||||
LeadSource,
|
||||
other_fields
|
||||
} = context.propsValue;
|
||||
|
||||
|
||||
const rawBody = {
|
||||
LastName,
|
||||
Company,
|
||||
FirstName,
|
||||
Email,
|
||||
Phone,
|
||||
LeadSource,
|
||||
...(other_fields || {}),
|
||||
};
|
||||
|
||||
const cleanedBody = Object.entries(rawBody).reduce((acc, [key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
acc[key] = value;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, unknown>);
|
||||
|
||||
const response = await callSalesforceApi(
|
||||
HttpMethod.POST,
|
||||
context.auth,
|
||||
'/services/data/v56.0/sobjects/Lead',
|
||||
cleanedBody
|
||||
);
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { callSalesforceApi, salesforcesCommon } from '../common';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
|
||||
export const createNewObject = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'create_new_object',
|
||||
displayName: 'Create Object (Advanced)',
|
||||
description: 'Create new object',
|
||||
props: {
|
||||
object: salesforcesCommon.object,
|
||||
data: Property.Json({
|
||||
displayName: 'Data',
|
||||
description: 'Select mapped object',
|
||||
required: true,
|
||||
defaultValue: {},
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { data, object } = context.propsValue;
|
||||
const response = await callSalesforceApi(
|
||||
HttpMethod.POST,
|
||||
context.auth,
|
||||
`/services/data/v56.0/sobjects/${object}`,
|
||||
{
|
||||
...data,
|
||||
}
|
||||
);
|
||||
return response;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
import { callSalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
export const createNote = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'create_note',
|
||||
displayName: 'Create Note',
|
||||
description: 'Creates a note and attaches it to a record.',
|
||||
props: {
|
||||
object: salesforcesCommon.object,
|
||||
parent_id: salesforcesCommon.record,
|
||||
Title: Property.ShortText({
|
||||
displayName: 'Title',
|
||||
required: true,
|
||||
}),
|
||||
Body: Property.LongText({
|
||||
displayName: 'Body',
|
||||
description: 'The content of the note.',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { parent_id, Title, Body } = context.propsValue;
|
||||
|
||||
const body = {
|
||||
ParentId: parent_id,
|
||||
Title: Title,
|
||||
Body: Body,
|
||||
};
|
||||
|
||||
const response = await callSalesforceApi(
|
||||
HttpMethod.POST,
|
||||
context.auth,
|
||||
'/services/data/v56.0/sobjects/Note',
|
||||
body
|
||||
);
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
import { callSalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
export const createOpportunity = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'create_opportunity',
|
||||
displayName: 'Create Opportunity',
|
||||
description: 'Creates a new opportunity.',
|
||||
props: {
|
||||
Name: Property.ShortText({
|
||||
displayName: 'Name',
|
||||
required: true,
|
||||
}),
|
||||
CloseDate: Property.ShortText({
|
||||
displayName: 'Close Date',
|
||||
description: 'The expected close date in YYYY-MM-DD format.',
|
||||
required: true,
|
||||
}),
|
||||
StageName: salesforcesCommon.opportunityStage,
|
||||
AccountId: salesforcesCommon.account,
|
||||
Amount: Property.Number({
|
||||
displayName: 'Amount',
|
||||
required: false,
|
||||
}),
|
||||
other_fields: Property.Json({
|
||||
displayName: 'Other Fields',
|
||||
description: 'Enter additional fields as a JSON object.',
|
||||
required: false
|
||||
})
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
Name,
|
||||
CloseDate,
|
||||
StageName,
|
||||
AccountId,
|
||||
Amount,
|
||||
other_fields
|
||||
} = context.propsValue;
|
||||
|
||||
|
||||
const rawBody = {
|
||||
Name,
|
||||
CloseDate,
|
||||
StageName,
|
||||
AccountId,
|
||||
Amount,
|
||||
...(other_fields || {}),
|
||||
};
|
||||
|
||||
const cleanedBody = Object.entries(rawBody).reduce((acc, [key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
acc[key] = value;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, unknown>);
|
||||
|
||||
const response = await callSalesforceApi(
|
||||
HttpMethod.POST,
|
||||
context.auth,
|
||||
'/services/data/v56.0/sobjects/Opportunity',
|
||||
cleanedBody
|
||||
);
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
import { callSalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
export const createRecord = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'create_record',
|
||||
displayName: 'Create Record',
|
||||
description: 'Create a record of a given object.',
|
||||
props: {
|
||||
object: salesforcesCommon.object,
|
||||
data: Property.Json({
|
||||
displayName: 'Record Data',
|
||||
description: 'Enter the fields for the new record as a JSON object. For example: {"Name": "My New Account", "Industry": "Technology"}',
|
||||
required: true,
|
||||
defaultValue: {},
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { object, data } = context.propsValue;
|
||||
|
||||
if (!object) {
|
||||
throw new Error('Object is not defined. Please select an object.');
|
||||
}
|
||||
|
||||
const response = await callSalesforceApi(
|
||||
HttpMethod.POST,
|
||||
context.auth,
|
||||
`/services/data/v56.0/sobjects/${object}`,
|
||||
data as Record<string, unknown>
|
||||
);
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
import { callSalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
export const createTask = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'create_task',
|
||||
displayName: 'Create Task',
|
||||
description: 'Creates a new task.',
|
||||
props: {
|
||||
Subject: Property.ShortText({
|
||||
displayName: 'Subject',
|
||||
required: true,
|
||||
}),
|
||||
OwnerId: salesforcesCommon.owner,
|
||||
Status: salesforcesCommon.taskStatus,
|
||||
Priority: salesforcesCommon.taskPriority,
|
||||
Description: Property.LongText({
|
||||
displayName: 'Description',
|
||||
required: false,
|
||||
}),
|
||||
WhoId: Property.ShortText({
|
||||
displayName: 'Related To (Contact/Lead ID)',
|
||||
description: 'The ID of a Contact or Lead to relate the task to.',
|
||||
required: false,
|
||||
}),
|
||||
WhatId: Property.ShortText({
|
||||
displayName: 'Related To (Other Object ID)',
|
||||
description: 'The ID of an Account, Opportunity, or other object to relate the task to.',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
Subject,
|
||||
OwnerId,
|
||||
Status,
|
||||
Priority,
|
||||
Description,
|
||||
WhoId,
|
||||
WhatId
|
||||
} = context.propsValue;
|
||||
|
||||
const rawBody = {
|
||||
Subject,
|
||||
OwnerId,
|
||||
Status,
|
||||
Priority,
|
||||
Description,
|
||||
WhoId,
|
||||
WhatId
|
||||
};
|
||||
|
||||
const cleanedBody = Object.entries(rawBody).reduce((acc, [key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
acc[key] = value;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, unknown>);
|
||||
|
||||
const response = await callSalesforceApi(
|
||||
HttpMethod.POST,
|
||||
context.auth,
|
||||
'/services/data/v56.0/sobjects/Task',
|
||||
cleanedBody
|
||||
);
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
import { callSalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
export const deleteOpportunity = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'delete_opportunity',
|
||||
displayName: 'Delete Opportunity',
|
||||
description: 'Deletes an opportunity.',
|
||||
props: {
|
||||
opportunity_id: salesforcesCommon.opportunity,
|
||||
},
|
||||
async run(context) {
|
||||
const { opportunity_id } = context.propsValue;
|
||||
|
||||
await callSalesforceApi(
|
||||
HttpMethod.DELETE,
|
||||
context.auth,
|
||||
`/services/data/v56.0/sobjects/Opportunity/${opportunity_id}`,
|
||||
undefined
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
import { callSalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
export const deleteRecord = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'delete_record',
|
||||
displayName: 'Delete Record',
|
||||
description: 'Deletes an existing record in an object.',
|
||||
props: {
|
||||
object: salesforcesCommon.object,
|
||||
record_id: salesforcesCommon.record,
|
||||
},
|
||||
async run(context) {
|
||||
const { object, record_id } = context.propsValue;
|
||||
|
||||
if (!object) {
|
||||
throw new Error('Object is not defined. Please select an object.');
|
||||
}
|
||||
|
||||
await callSalesforceApi(
|
||||
HttpMethod.DELETE,
|
||||
context.auth,
|
||||
`/services/data/v56.0/sobjects/${object}/${record_id}`,
|
||||
undefined
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
import { callSalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
export const findChildRecords = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'find_child_records',
|
||||
displayName: 'Find Child Records',
|
||||
description: 'Finds child records related to a parent record.',
|
||||
props: {
|
||||
parent_object: salesforcesCommon.object,
|
||||
parent_id: salesforcesCommon.parentRecord,
|
||||
child_relationship: salesforcesCommon.childRelationship,
|
||||
},
|
||||
async run(context) {
|
||||
const { parent_id, child_relationship } = context.propsValue;
|
||||
|
||||
const response = await callSalesforceApi(
|
||||
HttpMethod.GET,
|
||||
context.auth,
|
||||
`/services/data/v56.0/ui-api/records/${parent_id}/child-relationships/${child_relationship}`,
|
||||
undefined
|
||||
);
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
import { querySalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
export const findRecord = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'find_record',
|
||||
displayName: 'Find Record',
|
||||
description: 'Finds a record by a field value.',
|
||||
props: {
|
||||
object: salesforcesCommon.object,
|
||||
field: salesforcesCommon.field,
|
||||
search_value: Property.ShortText({
|
||||
displayName: 'Search Value',
|
||||
description: 'The value to search for in the selected field.',
|
||||
required: true,
|
||||
})
|
||||
},
|
||||
async run(context) {
|
||||
const { object, field, search_value } = context.propsValue;
|
||||
|
||||
if (!object || !field) {
|
||||
throw new Error('Object and Field must be selected.');
|
||||
}
|
||||
|
||||
const escapedSearchValue = search_value.replace(/'/g, "\\'");
|
||||
|
||||
const query = `SELECT FIELDS(ALL) FROM ${object} WHERE ${field} = '${escapedSearchValue}' LIMIT 2000`;
|
||||
|
||||
const response = await querySalesforceApi(
|
||||
HttpMethod.GET,
|
||||
context.auth,
|
||||
query
|
||||
);
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
import { querySalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
export const findRecordsByQuery = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'find_records_by_query',
|
||||
displayName: 'Find Records by Query (Advanced)',
|
||||
description: 'Finds records in an object using a SOQL WHERE clause.',
|
||||
props: {
|
||||
object: salesforcesCommon.object,
|
||||
where_clause: Property.ShortText({
|
||||
displayName: 'WHERE Clause',
|
||||
description: "Enter the WHERE clause for your SOQL query. For example: `Name = 'Acme' AND Industry = 'Technology'`. Do not include the 'WHERE' keyword.",
|
||||
required: true,
|
||||
})
|
||||
},
|
||||
async run(context) {
|
||||
const { object, where_clause } = context.propsValue;
|
||||
|
||||
if (!object) {
|
||||
throw new Error('Object must be selected.');
|
||||
}
|
||||
|
||||
const query = `SELECT FIELDS(ALL) FROM ${object} WHERE ${where_clause} LIMIT 200`;
|
||||
|
||||
const response = await querySalesforceApi(
|
||||
HttpMethod.GET,
|
||||
context.auth,
|
||||
query
|
||||
);
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
import { querySalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
|
||||
interface QueryResult {
|
||||
records: any[];
|
||||
}
|
||||
|
||||
export const getRecordAttachments = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'get_record_attachments',
|
||||
displayName: 'Get Record Attachments',
|
||||
description: 'Get all attachments (both classic and modern Files) for a record.',
|
||||
props: {
|
||||
object: salesforcesCommon.object,
|
||||
record_id: salesforcesCommon.record,
|
||||
},
|
||||
async run(context) {
|
||||
const { record_id } = context.propsValue;
|
||||
|
||||
|
||||
const classicAttachmentsQuery = `SELECT Id, Name, BodyLength, ContentType FROM Attachment WHERE ParentId = '${record_id}'`;
|
||||
const classicAttachmentsResponse = await querySalesforceApi<QueryResult>(
|
||||
HttpMethod.GET,
|
||||
context.auth,
|
||||
classicAttachmentsQuery
|
||||
);
|
||||
const classicAttachments = classicAttachmentsResponse.body?.records || [];
|
||||
|
||||
|
||||
const filesQuery = `SELECT ContentDocument.Id, ContentDocument.Title FROM ContentDocumentLink WHERE LinkedEntityId = '${record_id}'`;
|
||||
const filesResponse = await querySalesforceApi<QueryResult>(
|
||||
HttpMethod.GET,
|
||||
context.auth,
|
||||
filesQuery
|
||||
);
|
||||
const files = filesResponse.body?.records || [];
|
||||
|
||||
const allAttachments = {
|
||||
classic_attachments: classicAttachments,
|
||||
files: files.map((file: any) => ({
|
||||
Id: file.ContentDocument.Id,
|
||||
Title: file.ContentDocument.Title,
|
||||
})),
|
||||
};
|
||||
|
||||
return allAttachments;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
import { callSalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
export const runReport = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'run_report',
|
||||
displayName: 'Run Report',
|
||||
description: 'Execute a Salesforce analytics report.',
|
||||
props: {
|
||||
report_id: salesforcesCommon.report,
|
||||
filters: Property.Json({
|
||||
displayName: 'Filters',
|
||||
description: 'Apply dynamic filters to the report run.',
|
||||
required: false,
|
||||
defaultValue: [
|
||||
{
|
||||
"column": "ACCOUNT.NAME",
|
||||
"operator": "equals",
|
||||
"value": "Acme"
|
||||
}
|
||||
]
|
||||
})
|
||||
},
|
||||
async run(context) {
|
||||
const { report_id, filters } = context.propsValue;
|
||||
|
||||
let body = undefined;
|
||||
if (filters && Array.isArray(filters) && filters.length > 0) {
|
||||
body = {
|
||||
reportMetadata: {
|
||||
reportFilters: filters,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const response = await callSalesforceApi(
|
||||
HttpMethod.POST,
|
||||
context.auth,
|
||||
`/services/data/v56.0/analytics/reports/${report_id}`,
|
||||
body
|
||||
);
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { salesforcesCommon } from '../common';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { querySalesforceApi } from '../common';
|
||||
import { salesforceAuth } from '../..';
|
||||
|
||||
export const runQuery = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'run_query',
|
||||
displayName: 'Run Query (Advanced)',
|
||||
description: 'Run a salesforce query',
|
||||
props: {
|
||||
query: Property.ShortText({
|
||||
displayName: 'Query',
|
||||
description: 'Enter the query',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { query } = context.propsValue;
|
||||
const response = await await querySalesforceApi<{
|
||||
records: { CreatedDate: string }[];
|
||||
}>(HttpMethod.GET, context.auth, query);
|
||||
return response;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
import { callSalesforceApi ,salesforcesCommon } from '../common';
|
||||
|
||||
export const sendEmail = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'send_email',
|
||||
displayName: 'Send Email',
|
||||
description: 'Sends an email to a Contact or Lead by creating an EmailMessage record.',
|
||||
props: {
|
||||
recipientId: salesforcesCommon.recipient,
|
||||
subject: Property.ShortText({
|
||||
displayName: 'Subject',
|
||||
required: true,
|
||||
}),
|
||||
body: Property.LongText({
|
||||
displayName: 'Body',
|
||||
description: 'The content of the email. Can be plain text or HTML.',
|
||||
required: true,
|
||||
}),
|
||||
relatedToId: Property.ShortText({
|
||||
displayName: 'Related To ID (Optional)',
|
||||
description: 'The ID of a record to relate the email to (e.g., an Account, Opportunity, or Case ID).',
|
||||
required: false,
|
||||
})
|
||||
},
|
||||
async run(context) {
|
||||
const { recipientId, subject, body, relatedToId } = context.propsValue;
|
||||
|
||||
const emailMessage = {
|
||||
ToIds: [recipientId],
|
||||
Subject: subject,
|
||||
HtmlBody: body,
|
||||
Status: '3',
|
||||
...(relatedToId && { RelatedToId: relatedToId }),
|
||||
};
|
||||
|
||||
const cleanedBody = Object.entries(emailMessage).reduce((acc, [key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
acc[key] = value;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, unknown>);
|
||||
|
||||
const response = await callSalesforceApi(
|
||||
HttpMethod.POST,
|
||||
context.auth,
|
||||
`/services/data/v56.0/sobjects/EmailMessage`,
|
||||
cleanedBody
|
||||
);
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
import { callSalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
export const updateContact = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'update_contact',
|
||||
displayName: 'Update Contact',
|
||||
description: 'Update an existing contact.',
|
||||
props: {
|
||||
contact_id: salesforcesCommon.contact,
|
||||
FirstName: Property.ShortText({
|
||||
displayName: 'First Name',
|
||||
required: false,
|
||||
}),
|
||||
LastName: Property.ShortText({
|
||||
displayName: 'Last Name',
|
||||
required: false,
|
||||
}),
|
||||
AccountId: salesforcesCommon.account,
|
||||
Email: Property.ShortText({
|
||||
displayName: 'Email',
|
||||
required: false,
|
||||
}),
|
||||
Phone: Property.ShortText({
|
||||
displayName: 'Phone',
|
||||
required: false,
|
||||
}),
|
||||
Title: Property.ShortText({
|
||||
displayName: 'Title',
|
||||
required: false,
|
||||
}),
|
||||
other_fields: Property.Json({
|
||||
displayName: 'Other Fields (Advanced)',
|
||||
description: 'Enter any additional fields to update as a JSON object.',
|
||||
required: false
|
||||
})
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
contact_id,
|
||||
FirstName,
|
||||
LastName,
|
||||
AccountId,
|
||||
Email,
|
||||
Phone,
|
||||
Title,
|
||||
other_fields
|
||||
} = context.propsValue;
|
||||
|
||||
const rawBody = {
|
||||
FirstName,
|
||||
LastName,
|
||||
AccountId,
|
||||
Email,
|
||||
Phone,
|
||||
Title,
|
||||
...(other_fields || {}),
|
||||
};
|
||||
|
||||
const cleanedBody = Object.entries(rawBody).reduce((acc, [key, value]) => {
|
||||
if (value !== undefined) {
|
||||
acc[key] = value;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, unknown>);
|
||||
|
||||
|
||||
if (Object.keys(cleanedBody).length === 0) {
|
||||
return { success: true, message: "No fields provided to update." };
|
||||
}
|
||||
|
||||
await callSalesforceApi(
|
||||
HttpMethod.PATCH,
|
||||
context.auth,
|
||||
`/services/data/v56.0/sobjects/Contact/${contact_id}`,
|
||||
cleanedBody
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,84 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
import { callSalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
export const updateLead = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'update_lead',
|
||||
displayName: 'Update Lead',
|
||||
description: 'Update an existing lead.',
|
||||
props: {
|
||||
lead_id: salesforcesCommon.lead,
|
||||
FirstName: Property.ShortText({
|
||||
displayName: 'First Name',
|
||||
required: false,
|
||||
}),
|
||||
LastName: Property.ShortText({
|
||||
displayName: 'Last Name',
|
||||
required: false,
|
||||
}),
|
||||
Company: Property.ShortText({
|
||||
displayName: 'Company',
|
||||
required: false,
|
||||
}),
|
||||
Email: Property.ShortText({
|
||||
displayName: 'Email',
|
||||
required: false,
|
||||
}),
|
||||
Phone: Property.ShortText({
|
||||
displayName: 'Phone',
|
||||
required: false,
|
||||
}),
|
||||
LeadSource: salesforcesCommon.leadSource,
|
||||
other_fields: Property.Json({
|
||||
displayName: 'Other Fields (Advanced)',
|
||||
description: 'Enter any additional fields to update as a JSON object.',
|
||||
required: false
|
||||
})
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
lead_id,
|
||||
FirstName,
|
||||
LastName,
|
||||
Company,
|
||||
Email,
|
||||
Phone,
|
||||
LeadSource,
|
||||
other_fields
|
||||
} = context.propsValue;
|
||||
|
||||
const rawBody = {
|
||||
FirstName,
|
||||
LastName,
|
||||
Company,
|
||||
Email,
|
||||
Phone,
|
||||
LeadSource,
|
||||
...(other_fields || {}),
|
||||
};
|
||||
|
||||
const cleanedBody = Object.entries(rawBody).reduce((acc, [key, value]) => {
|
||||
if (value !== undefined) {
|
||||
acc[key] = value;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, unknown>);
|
||||
|
||||
if (Object.keys(cleanedBody).length === 0) {
|
||||
return { success: true, message: "No fields provided to update." };
|
||||
}
|
||||
|
||||
await callSalesforceApi(
|
||||
HttpMethod.PATCH,
|
||||
context.auth,
|
||||
`/services/data/v56.0/sobjects/Lead/${lead_id}`,
|
||||
cleanedBody
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { callSalesforceApi, salesforcesCommon } from '../common';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
|
||||
export const UpdateObjectById = createAction({
|
||||
auth: salesforceAuth,
|
||||
|
||||
name: 'update_object_by_id',
|
||||
displayName: 'Update Object (Advanced)',
|
||||
description: 'Update object by Id',
|
||||
props: {
|
||||
object: salesforcesCommon.object,
|
||||
id: Property.ShortText({
|
||||
displayName: 'Id',
|
||||
description: 'Select the Id',
|
||||
required: true,
|
||||
}),
|
||||
data: Property.Json({
|
||||
displayName: 'Data',
|
||||
description: 'Select mapped object',
|
||||
required: true,
|
||||
defaultValue: {},
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { object, id, data } = context.propsValue;
|
||||
const response = await callSalesforceApi(
|
||||
HttpMethod.PATCH,
|
||||
context.auth,
|
||||
`/services/data/v56.0/sobjects/${object}/${id}`,
|
||||
{
|
||||
...data,
|
||||
}
|
||||
);
|
||||
return response;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
import { callSalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
export const updateRecord = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'update_record',
|
||||
displayName: 'Update Record',
|
||||
description: 'Updates an existing record.',
|
||||
props: {
|
||||
object: salesforcesCommon.object,
|
||||
record_id: salesforcesCommon.record,
|
||||
data: Property.Json({
|
||||
displayName: 'Data to Update',
|
||||
description: 'Enter the fields to update as a JSON object. For example: {"BillingCity": "San Francisco"}',
|
||||
required: true,
|
||||
defaultValue: {},
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { object, record_id, data } = context.propsValue;
|
||||
|
||||
if (!object) {
|
||||
throw new Error('Object is not defined. Please select an object.');
|
||||
}
|
||||
|
||||
await callSalesforceApi(
|
||||
HttpMethod.PATCH,
|
||||
context.auth,
|
||||
`/services/data/v56.0/sobjects/${object}/${record_id}`,
|
||||
data as Record<string, unknown>
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import {
|
||||
createBulkJob,
|
||||
getBulkJobInfo,
|
||||
notifyBulkJobComplete,
|
||||
salesforcesCommon,
|
||||
uploadToBulkJob,
|
||||
} from '../common';
|
||||
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
|
||||
export const upsertByExternalIdBulk = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'upsert_by_external_id_bulk',
|
||||
displayName: 'Bulk Upsert (Advanced)',
|
||||
description: 'Bulk upsert a record by external id',
|
||||
props: {
|
||||
object: salesforcesCommon.object,
|
||||
external_field: Property.ShortText({
|
||||
displayName: 'External Field',
|
||||
description: 'Select the External Field',
|
||||
required: true,
|
||||
}),
|
||||
records: Property.LongText({
|
||||
displayName: 'Records',
|
||||
description: 'Select the Records (CSV format)',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const records = context.propsValue.records;
|
||||
let jobId;
|
||||
if (!records) {
|
||||
throw new Error('Expect CSV of records to upsert');
|
||||
}
|
||||
const { object, external_field } = context.propsValue;
|
||||
|
||||
// create bulk job
|
||||
const create = await createBulkJob(HttpMethod.POST, context.auth, {
|
||||
object: object,
|
||||
externalIdFieldName: external_field,
|
||||
contentType: 'CSV',
|
||||
operation: 'upsert',
|
||||
lineEnding: 'CRLF',
|
||||
});
|
||||
if (create.status == 200) {
|
||||
jobId = create.body.id;
|
||||
} else {
|
||||
throw new Error(`job creation failed: ${JSON.stringify(create)}`);
|
||||
}
|
||||
// upload records to bulk job
|
||||
await uploadToBulkJob(HttpMethod.PUT, context.auth, jobId, records).catch(
|
||||
(e) => {
|
||||
throw new Error(`job upload failed: ${JSON.stringify(e)}`);
|
||||
}
|
||||
);
|
||||
|
||||
// notify upload complete
|
||||
await notifyBulkJobComplete(
|
||||
HttpMethod.PATCH,
|
||||
context.auth,
|
||||
{ state: 'UploadComplete' },
|
||||
jobId
|
||||
).catch((e) => {
|
||||
throw new Error(`job failed: ${JSON.stringify(e)}`);
|
||||
});
|
||||
|
||||
const response = await getBulkJobInfo(
|
||||
HttpMethod.GET,
|
||||
context.auth,
|
||||
jobId
|
||||
).catch((e) => {
|
||||
throw new Error(`job failed: ${JSON.stringify(e)}`);
|
||||
});
|
||||
return response;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { callSalesforceApi, salesforcesCommon } from '../common';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { salesforceAuth } from '../..';
|
||||
|
||||
export const upsertByExternalId = createAction({
|
||||
auth: salesforceAuth,
|
||||
name: 'upsert_by_external_id',
|
||||
displayName: 'Batch Upsert (Advanced)',
|
||||
description: 'Batch upsert a record by external id',
|
||||
props: {
|
||||
object: salesforcesCommon.object,
|
||||
external_field: Property.ShortText({
|
||||
displayName: 'External Field',
|
||||
description: 'Select the External Field',
|
||||
required: true,
|
||||
}),
|
||||
records: Property.Json({
|
||||
displayName: 'Records',
|
||||
description: 'Select the Records',
|
||||
required: true,
|
||||
defaultValue: {
|
||||
records: [],
|
||||
},
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const records = context.propsValue?.records?.records;
|
||||
if (!records) {
|
||||
throw new Error(
|
||||
'Expect records field inside json to be an array with records to upsert'
|
||||
);
|
||||
}
|
||||
const { object, external_field } = context.propsValue;
|
||||
const response = await callSalesforceApi(
|
||||
HttpMethod.PATCH,
|
||||
context.auth,
|
||||
`/services/data/v55.0/composite/sobjects/${object}/${external_field}`,
|
||||
{
|
||||
allOrNone: false,
|
||||
...context.propsValue.records,
|
||||
}
|
||||
);
|
||||
return response;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,712 @@
|
||||
import {
|
||||
AuthenticationType,
|
||||
HttpMethod,
|
||||
HttpMessageBody,
|
||||
HttpResponse,
|
||||
httpClient,
|
||||
} from '@activepieces/pieces-common';
|
||||
import { OAuth2PropertyValue, Property } from '@activepieces/pieces-framework';
|
||||
import { salesforceAuth } from '../..';
|
||||
|
||||
export const salesforcesCommon = {
|
||||
account: Property.Dropdown({
|
||||
auth: salesforceAuth,
|
||||
displayName: 'Account',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
const response = await querySalesforceApi<{ records: { Id: string, Name: string }[] }>(
|
||||
HttpMethod.GET,
|
||||
auth as OAuth2PropertyValue,
|
||||
`SELECT Id, Name FROM Account ORDER BY Name LIMIT 100`
|
||||
);
|
||||
return {
|
||||
disabled: false,
|
||||
options: response.body.records.map((record) => ({
|
||||
label: record.Name,
|
||||
value: record.Id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
object: Property.Dropdown<string,true,typeof salesforceAuth>({
|
||||
auth: salesforceAuth,
|
||||
displayName: 'Object',
|
||||
required: true,
|
||||
description: 'Select the Object',
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'connect your account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
const options = await getSalesforceObjects(auth as OAuth2PropertyValue);
|
||||
return {
|
||||
disabled: false,
|
||||
options: options.body['sobjects']
|
||||
.map((object: any) => {
|
||||
return {
|
||||
label: object.label,
|
||||
value: object.name,
|
||||
};
|
||||
})
|
||||
.sort((a: { label: string }, b: { label: string }) =>
|
||||
a.label.localeCompare(b.label)
|
||||
)
|
||||
.filter((object: { label: string }) => !object.label.startsWith('_')),
|
||||
};
|
||||
},
|
||||
}),
|
||||
record: Property.Dropdown({
|
||||
auth: salesforceAuth,
|
||||
displayName: 'Record',
|
||||
description: 'The record to select. The list shows the 20 most recently created records.',
|
||||
required: true,
|
||||
refreshers: ['object'],
|
||||
options: async ({ auth, object }) => {
|
||||
if (!auth || !object) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Select an object first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
const describeResponse = await getSalesforceFields(auth as OAuth2PropertyValue, object as string);
|
||||
const fields = describeResponse.body['fields'].map((f: any) => f.name);
|
||||
|
||||
|
||||
let displayField = 'Id';
|
||||
if (fields.includes('Name')) {
|
||||
displayField = 'Name';
|
||||
} else if (fields.includes('Subject')) {
|
||||
displayField = 'Subject';
|
||||
} else if (fields.includes('Title')) {
|
||||
displayField = 'Title';
|
||||
}
|
||||
|
||||
const response = await querySalesforceApi<{ records: { Id: string, [key: string]: any }[] }>(
|
||||
HttpMethod.GET,
|
||||
auth as OAuth2PropertyValue,
|
||||
`SELECT Id, ${displayField} FROM ${object} ORDER BY CreatedDate DESC LIMIT 20`
|
||||
);
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
options: response.body.records.map((record) => ({
|
||||
label: record[displayField] ?? record.Id,
|
||||
value: record.Id,
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
const fallbackResponse = await querySalesforceApi<{ records: { Id: string }[] }>(
|
||||
HttpMethod.GET,
|
||||
auth as OAuth2PropertyValue,
|
||||
`SELECT Id FROM ${object} LIMIT 20`
|
||||
);
|
||||
return {
|
||||
disabled: false,
|
||||
options: fallbackResponse.body.records.map((record) => ({
|
||||
label: record.Id,
|
||||
value: record.Id,
|
||||
})),
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
recipient: Property.Dropdown({
|
||||
auth: salesforceAuth,
|
||||
displayName: 'Recipient',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
const contactQuery = `SELECT Id, Name FROM Contact ORDER BY Name LIMIT 50`;
|
||||
const leadQuery = `SELECT Id, Name FROM Lead ORDER BY Name LIMIT 50`;
|
||||
|
||||
const [contactsResponse, leadsResponse] = await Promise.all([
|
||||
querySalesforceApi<{ records: { Id: string, Name: string }[] }>(HttpMethod.GET, auth as OAuth2PropertyValue, contactQuery),
|
||||
querySalesforceApi<{ records: { Id: string, Name: string }[] }>(HttpMethod.GET, auth as OAuth2PropertyValue, leadQuery)
|
||||
]);
|
||||
|
||||
const contactOptions = contactsResponse.body.records.map((record) => ({
|
||||
label: `${record.Name} (Contact)`,
|
||||
value: record.Id,
|
||||
}));
|
||||
|
||||
const leadOptions = leadsResponse.body.records.map((record) => ({
|
||||
label: `${record.Name} (Lead)`,
|
||||
value: record.Id,
|
||||
}));
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
options: [...contactOptions, ...leadOptions].sort((a, b) => a.label.localeCompare(b.label)),
|
||||
};
|
||||
},
|
||||
}),
|
||||
field: Property.Dropdown<string,true,typeof salesforceAuth>({
|
||||
auth: salesforceAuth,
|
||||
displayName: 'Field',
|
||||
description: 'Select the Field',
|
||||
required: true,
|
||||
refreshers: ['object'],
|
||||
options: async ({ auth, object }) => {
|
||||
if (auth === undefined || !object) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'connect your account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
const options = await getSalesforceFields(
|
||||
auth as OAuth2PropertyValue,
|
||||
object as string
|
||||
);
|
||||
return {
|
||||
disabled: false,
|
||||
options: options.body['fields'].map((field: any) => {
|
||||
return {
|
||||
label: field.label,
|
||||
value: field.name,
|
||||
};
|
||||
}),
|
||||
};
|
||||
},
|
||||
}),
|
||||
campaign: Property.Dropdown({
|
||||
auth: salesforceAuth,
|
||||
displayName: 'Campaign',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
const response = await querySalesforceApi<{ records: { Id: string, Name: string }[] }>(
|
||||
HttpMethod.GET,
|
||||
auth as OAuth2PropertyValue,
|
||||
"SELECT Id, Name FROM Campaign ORDER BY Name LIMIT 200"
|
||||
);
|
||||
return {
|
||||
disabled: false,
|
||||
options: response.body.records.map((record) => ({
|
||||
label: record.Name,
|
||||
value: record.Id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
contact: Property.Dropdown({
|
||||
auth: salesforceAuth,
|
||||
displayName: 'Contact',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
const response = await querySalesforceApi<{ records: { Id: string, Name: string, Email?: string }[] }>(
|
||||
HttpMethod.GET,
|
||||
auth as OAuth2PropertyValue,
|
||||
"SELECT Id, Name, Email FROM Contact ORDER BY Name LIMIT 200"
|
||||
);
|
||||
return {
|
||||
disabled: false,
|
||||
options: response.body.records.map((record) => ({
|
||||
label: record.Email ? `${record.Name} — ${record.Email}` : record.Name,
|
||||
value: record.Id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
lead: Property.Dropdown({
|
||||
auth: salesforceAuth,
|
||||
displayName: 'Lead',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
const response = await querySalesforceApi<{ records: { Id: string, Name: string }[] }>(
|
||||
HttpMethod.GET,
|
||||
auth as OAuth2PropertyValue,
|
||||
"SELECT Id, Name FROM Lead ORDER BY Name LIMIT 200"
|
||||
);
|
||||
return {
|
||||
disabled: false,
|
||||
options: response.body.records.map((record) => ({
|
||||
label: record.Name,
|
||||
value: record.Id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
status: Property.Dropdown({
|
||||
auth: salesforceAuth,
|
||||
displayName: 'Status',
|
||||
description: "The campaign member status (e.g., 'Sent', 'Responded').",
|
||||
required: true,
|
||||
refreshers: ['campaign_id'],
|
||||
options: async ({ auth, campaign_id }) => {
|
||||
if (!auth || !campaign_id) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Select a campaign first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
// Validate campaign_id to prevent SQL injection (Salesforce IDs are 15-18 alphanumeric characters)
|
||||
const campaignIdStr = String(campaign_id);
|
||||
if (!/^[a-zA-Z0-9]{15,18}$/.test(campaignIdStr)) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Invalid campaign ID',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
const response = await querySalesforceApi<{ records: { Label: string }[] }>(
|
||||
HttpMethod.GET,
|
||||
auth as OAuth2PropertyValue,
|
||||
`SELECT Label FROM CampaignMemberStatus WHERE CampaignId = '${campaignIdStr}'`
|
||||
);
|
||||
return {
|
||||
disabled: false,
|
||||
options: response.body.records.map((record) => ({
|
||||
label: record.Label,
|
||||
value: record.Label,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
leadSource: Property.Dropdown({
|
||||
auth: salesforceAuth,
|
||||
displayName: 'Lead Source',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
try {
|
||||
const describeResponse = await getSalesforceFields(auth as OAuth2PropertyValue, 'Lead');
|
||||
const leadSourceField = describeResponse.body['fields'].find((field: any) => field.name === 'LeadSource');
|
||||
|
||||
if (!leadSourceField || !leadSourceField.picklistValues) {
|
||||
return { disabled: true, placeholder: 'Lead Source field not found or not a picklist', options: [] };
|
||||
}
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
options: leadSourceField.picklistValues.map((value: any) => {
|
||||
return {
|
||||
label: value.label,
|
||||
value: value.value,
|
||||
};
|
||||
}),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: "Couldn't fetch lead sources",
|
||||
options: [],
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
owner: Property.Dropdown({
|
||||
auth: salesforceAuth,
|
||||
displayName: 'Owner',
|
||||
description: 'The owner of the task.',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return { disabled: true, placeholder: 'Connect your account first', options: [] };
|
||||
}
|
||||
const response = await querySalesforceApi<{ records: { Id: string, Name: string }[] }>(
|
||||
HttpMethod.GET,
|
||||
auth as OAuth2PropertyValue,
|
||||
"SELECT Id, Name FROM User WHERE IsActive = true ORDER BY Name"
|
||||
);
|
||||
return {
|
||||
disabled: false,
|
||||
options: response.body.records.map((record) => ({
|
||||
label: record.Name,
|
||||
value: record.Id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
opportunity: Property.Dropdown({
|
||||
auth: salesforceAuth,
|
||||
displayName: 'Opportunity',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return { disabled: true, placeholder: 'Connect your account first', options: [] };
|
||||
}
|
||||
const response = await querySalesforceApi<{ records: { Id: string, Name: string }[] }>(
|
||||
HttpMethod.GET,
|
||||
auth as OAuth2PropertyValue,
|
||||
"SELECT Id, Name FROM Opportunity ORDER BY CreatedDate DESC LIMIT 100"
|
||||
);
|
||||
return {
|
||||
disabled: false,
|
||||
options: response.body.records.map((record) => ({
|
||||
label: record.Name,
|
||||
value: record.Id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
report: Property.Dropdown({
|
||||
auth: salesforceAuth,
|
||||
displayName: 'Report',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return { disabled: true, placeholder: 'Connect your account first', options: [] };
|
||||
}
|
||||
const response = await querySalesforceApi<{ records: { Id: string, Name: string }[] }>(
|
||||
HttpMethod.GET,
|
||||
auth as OAuth2PropertyValue,
|
||||
"SELECT Id, Name FROM Report ORDER BY Name"
|
||||
);
|
||||
return {
|
||||
disabled: false,
|
||||
options: response.body.records.map((record) => ({
|
||||
label: record.Name,
|
||||
value: record.Id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
parentRecord: Property.Dropdown({
|
||||
auth: salesforceAuth,
|
||||
displayName: 'Parent Record',
|
||||
description: 'The parent record to find child records for. The list shows the 20 most recently created records.',
|
||||
required: true,
|
||||
refreshers: ['parent_object'],
|
||||
options: async ({ auth, parent_object }) => {
|
||||
if (!auth || !parent_object) {
|
||||
return { disabled: true, placeholder: 'Select a parent object first', options: [] };
|
||||
}
|
||||
const response = await querySalesforceApi<{ records: { Id: string, Name?: string }[] }>(
|
||||
HttpMethod.GET,
|
||||
auth as OAuth2PropertyValue,
|
||||
`SELECT Id, Name FROM ${parent_object} ORDER BY CreatedDate DESC LIMIT 20`
|
||||
);
|
||||
return {
|
||||
disabled: false,
|
||||
options: response.body.records.map((record) => ({
|
||||
label: record.Name ?? record.Id,
|
||||
value: record.Id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
|
||||
childRelationship: Property.Dropdown({
|
||||
auth: salesforceAuth,
|
||||
displayName: 'Child Relationship',
|
||||
description: 'The child relationship to retrieve records from.',
|
||||
required: true,
|
||||
refreshers: ['parent_object'],
|
||||
options: async ({ auth, parent_object }) => {
|
||||
if (!auth || !parent_object) {
|
||||
return { disabled: true, placeholder: 'Select a parent object first', options: [] };
|
||||
}
|
||||
try {
|
||||
const describeResponse = await getSalesforceFields(auth as OAuth2PropertyValue, parent_object as string);
|
||||
const relationships = describeResponse.body['childRelationships'];
|
||||
if (!relationships) {
|
||||
return { disabled: true, placeholder: 'No child relationships found for this object', options: [] };
|
||||
}
|
||||
return {
|
||||
disabled: false,
|
||||
options: relationships.map((rel: any) => ({
|
||||
label: `${rel.relationshipName} (${rel.childSObject})`,
|
||||
value: rel.relationshipName,
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return { disabled: true, placeholder: "Couldn't fetch child relationships", options: [] };
|
||||
}
|
||||
},
|
||||
}),
|
||||
optionalContact: Property.Dropdown({
|
||||
auth: salesforceAuth,
|
||||
displayName: 'Contact',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return { disabled: true, placeholder: 'Connect your account first', options: [] };
|
||||
}
|
||||
const response = await querySalesforceApi<{ records: { Id: string, Name: string, Email?: string }[] }>(
|
||||
HttpMethod.GET,
|
||||
auth as OAuth2PropertyValue,
|
||||
"SELECT Id, Name, Email FROM Contact ORDER BY Name LIMIT 200"
|
||||
);
|
||||
return {
|
||||
disabled: false,
|
||||
options: response.body.records.map((record) => ({
|
||||
label: record.Email ? `${record.Name} — ${record.Email}` : record.Name,
|
||||
value: record.Id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
taskStatus: createSalesforcePicklistDropdown({
|
||||
objectName: 'Task',
|
||||
fieldName: 'Status',
|
||||
displayName: 'Status',
|
||||
required: true
|
||||
}),
|
||||
|
||||
taskPriority: createSalesforcePicklistDropdown({
|
||||
objectName: 'Task',
|
||||
fieldName: 'Priority',
|
||||
displayName: 'Priority',
|
||||
required: true
|
||||
}),
|
||||
|
||||
caseStatus: createSalesforcePicklistDropdown({
|
||||
objectName: 'Case',
|
||||
fieldName: 'Status',
|
||||
displayName: 'Status',
|
||||
required: false
|
||||
}),
|
||||
|
||||
casePriority: createSalesforcePicklistDropdown({
|
||||
objectName: 'Case',
|
||||
fieldName: 'Priority',
|
||||
displayName: 'Priority',
|
||||
required: false
|
||||
}),
|
||||
|
||||
caseOrigin: createSalesforcePicklistDropdown({
|
||||
objectName: 'Case',
|
||||
fieldName: 'Origin',
|
||||
displayName: 'Origin',
|
||||
required: false
|
||||
}),
|
||||
opportunityStage: createSalesforcePicklistDropdown({
|
||||
objectName: 'Opportunity',
|
||||
fieldName: 'StageName',
|
||||
displayName: 'Stage',
|
||||
required: true
|
||||
}),
|
||||
|
||||
};
|
||||
|
||||
function createSalesforcePicklistDropdown(config: {
|
||||
objectName: string,
|
||||
fieldName: string,
|
||||
displayName: string,
|
||||
required: boolean,
|
||||
description?: string,
|
||||
}) {
|
||||
return Property.Dropdown({
|
||||
auth: salesforceAuth,
|
||||
displayName: config.displayName,
|
||||
description: config.description,
|
||||
required: config.required,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return { disabled: true, placeholder: 'Connect your account first', options: [] };
|
||||
}
|
||||
try {
|
||||
const describeResponse = await getSalesforceFields(auth as OAuth2PropertyValue, config.objectName);
|
||||
const field = describeResponse.body['fields'].find((field: any) => field.name === config.fieldName);
|
||||
if (!field || !field.picklistValues) {
|
||||
return { disabled: true, placeholder: `${config.fieldName} field not found or not a picklist`, options: [] };
|
||||
}
|
||||
return {
|
||||
disabled: false,
|
||||
options: field.picklistValues.map((value: any) => ({
|
||||
label: value.label,
|
||||
value: value.value,
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return { disabled: true, placeholder: `Couldn't fetch ${config.fieldName} values`, options: [] };
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export async function callSalesforceApi<T extends HttpMessageBody>(
|
||||
method: HttpMethod,
|
||||
authentication: OAuth2PropertyValue,
|
||||
url: string,
|
||||
body: Record<string, unknown> | undefined
|
||||
): Promise<HttpResponse<T>> {
|
||||
return await httpClient.sendRequest<T>({
|
||||
method: method,
|
||||
url: `${authentication.data['instance_url']}${url}`,
|
||||
body,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: authentication['access_token'],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function querySalesforceApi<T extends HttpMessageBody>(
|
||||
method: HttpMethod,
|
||||
authentication: OAuth2PropertyValue,
|
||||
query: string
|
||||
): Promise<HttpResponse<T>> {
|
||||
return await httpClient.sendRequest<T>({
|
||||
method: method,
|
||||
url: `${authentication.data['instance_url']}/services/data/v56.0/query`,
|
||||
queryParams: {
|
||||
q: query,
|
||||
},
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: authentication['access_token'],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function createBulkJob<T extends HttpMessageBody = any>(
|
||||
method: HttpMethod,
|
||||
authentication: OAuth2PropertyValue,
|
||||
jobDetails: HttpMessageBody
|
||||
): Promise<HttpResponse<T>> {
|
||||
return await httpClient.sendRequest<T>({
|
||||
method: method,
|
||||
url: `${authentication.data['instance_url']}/services/data/v58.0/jobs/ingest/`,
|
||||
body: jobDetails,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: authentication['access_token'],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function uploadToBulkJob<T extends HttpMessageBody>(
|
||||
method: HttpMethod,
|
||||
authentication: OAuth2PropertyValue,
|
||||
jobId: string,
|
||||
csv: string
|
||||
): Promise<HttpResponse<T>> {
|
||||
return await httpClient.sendRequest<T>({
|
||||
method: method,
|
||||
url: `${authentication.data['instance_url']}/services/data/v58.0/jobs/ingest/${jobId}/batches`,
|
||||
headers: {
|
||||
'Content-Type': 'text/csv',
|
||||
},
|
||||
body: csv as unknown as HttpMessageBody,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: authentication['access_token'],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function notifyBulkJobComplete<T extends HttpMessageBody>(
|
||||
method: HttpMethod,
|
||||
authentication: OAuth2PropertyValue,
|
||||
message: HttpMessageBody,
|
||||
jobId: string
|
||||
): Promise<HttpResponse<T>> {
|
||||
return await httpClient.sendRequest<T>({
|
||||
method: method,
|
||||
url: `${authentication.data['instance_url']}/services/data/v58.0/jobs/ingest/${jobId}`,
|
||||
body: message,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: authentication['access_token'],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getBulkJobInfo<T extends HttpMessageBody>(
|
||||
method: HttpMethod,
|
||||
authentication: OAuth2PropertyValue,
|
||||
jobId: string
|
||||
): Promise<HttpResponse<T>> {
|
||||
return await httpClient.sendRequest<T>({
|
||||
method: method,
|
||||
url: `${authentication.data['instance_url']}/services/data/v58.0/jobs/ingest/${jobId}`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: authentication['access_token'],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function getSalesforceObjects(
|
||||
authentication: OAuth2PropertyValue
|
||||
): Promise<HttpResponse<HttpMessageBody>> {
|
||||
return await httpClient.sendRequest<HttpMessageBody>({
|
||||
method: HttpMethod.GET,
|
||||
url: `${authentication.data['instance_url']}/services/data/v56.0/sobjects`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: authentication['access_token'],
|
||||
},
|
||||
});
|
||||
}
|
||||
// Write function to list all fields name inside salesforce object
|
||||
async function getSalesforceFields(
|
||||
authentication: OAuth2PropertyValue,
|
||||
object: string
|
||||
): Promise<HttpResponse<HttpMessageBody>> {
|
||||
return await httpClient.sendRequest<HttpMessageBody>({
|
||||
method: HttpMethod.GET,
|
||||
url: `${authentication.data['instance_url']}/services/data/v56.0/sobjects/${object}/describe`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: authentication['access_token'],
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import {
|
||||
DedupeStrategy,
|
||||
HttpMethod,
|
||||
Polling,
|
||||
pollingHelper,
|
||||
} from '@activepieces/pieces-common';
|
||||
import {
|
||||
AppConnectionValueForAuthProperty,
|
||||
OAuth2PropertyValue,
|
||||
TriggerStrategy,
|
||||
createTrigger,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { querySalesforceApi } from '../common';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import { salesforceAuth } from '../..';
|
||||
|
||||
export const newCaseAttachment = createTrigger({
|
||||
auth: salesforceAuth,
|
||||
name: 'new_case_attachment',
|
||||
displayName: 'New Case Attachment',
|
||||
description: 'Fires when a new Attachment or File is added to any Case record.',
|
||||
props: {},
|
||||
sampleData: {
|
||||
"Id": "00P7Q000002XyA4UAK",
|
||||
"ParentId": "5007Q000006g75iQAA",
|
||||
"Name": "sample_attachment.txt",
|
||||
"ContentType": "text/plain",
|
||||
"CreatedDate": "2025-10-10T12:00:00.000Z",
|
||||
"attachment_type": "Classic"
|
||||
},
|
||||
type: TriggerStrategy.POLLING,
|
||||
async test(ctx) {
|
||||
return await pollingHelper.test(polling, ctx);
|
||||
},
|
||||
async onEnable(ctx) {
|
||||
await pollingHelper.onEnable(polling, ctx);
|
||||
},
|
||||
async onDisable(ctx) {
|
||||
await pollingHelper.onDisable(polling, ctx);
|
||||
},
|
||||
async run(ctx) {
|
||||
return await pollingHelper.poll(polling, ctx);
|
||||
},
|
||||
});
|
||||
|
||||
const polling: Polling<AppConnectionValueForAuthProperty<typeof salesforceAuth>, Record<string, never>> = {
|
||||
strategy: DedupeStrategy.TIMEBASED,
|
||||
items: async ({ auth, lastFetchEpochMS }) => {
|
||||
const isoDate = dayjs(lastFetchEpochMS).toISOString();
|
||||
|
||||
const classicQuery = `
|
||||
SELECT FIELDS(ALL) FROM Attachment
|
||||
WHERE Parent.Type = 'Case' AND SystemModstamp > ${isoDate}
|
||||
LIMIT 200
|
||||
`;
|
||||
const classicResponse = await querySalesforceApi<{ records: { SystemModstamp: string }[] }>(HttpMethod.GET, auth, classicQuery);
|
||||
const classicAttachments = classicResponse.body?.['records'] || [];
|
||||
|
||||
const recentCasesQuery = `SELECT Id FROM Case WHERE SystemModstamp > ${isoDate} LIMIT 200`;
|
||||
const recentCasesResponse = await querySalesforceApi<{ records: { Id: string }[] }>(HttpMethod.GET, auth, recentCasesQuery);
|
||||
const recentCaseIds = recentCasesResponse.body?.['records'].map(c => `'${c.Id}'`);
|
||||
|
||||
let fileLinks: any[] = [];
|
||||
if (recentCaseIds && recentCaseIds.length > 0) {
|
||||
const filesQuery = `
|
||||
SELECT FIELDS(ALL) FROM ContentDocumentLink
|
||||
WHERE LinkedEntityId IN (${recentCaseIds.join(',')}) AND SystemModstamp > ${isoDate}
|
||||
LIMIT 200
|
||||
`;
|
||||
const filesResponse = await querySalesforceApi<{ records: { SystemModstamp: string }[] }>(HttpMethod.GET, auth, filesQuery);
|
||||
fileLinks = filesResponse.body?.['records'] || [];
|
||||
}
|
||||
|
||||
const classicItems = classicAttachments.map((item: any) => ({
|
||||
epochMilliSeconds: dayjs(item.SystemModstamp).valueOf(),
|
||||
data: { ...item, attachment_type: 'Classic' },
|
||||
}));
|
||||
|
||||
const fileItems = fileLinks.map((item: any) => ({
|
||||
epochMilliSeconds: dayjs(item.SystemModstamp).valueOf(),
|
||||
data: { ...item, attachment_type: 'File' },
|
||||
}));
|
||||
|
||||
const allItems = [...classicItems, ...fileItems];
|
||||
return allItems.sort((a, b) => a.epochMilliSeconds - b.epochMilliSeconds);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import {
|
||||
DedupeStrategy,
|
||||
HttpMethod,
|
||||
Polling,
|
||||
pollingHelper,
|
||||
} from '@activepieces/pieces-common';
|
||||
import {
|
||||
AppConnectionValueForAuthProperty,
|
||||
OAuth2PropertyValue,
|
||||
TriggerStrategy,
|
||||
createTrigger,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { querySalesforceApi } from '../common';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import { salesforceAuth } from '../..';
|
||||
|
||||
export const newContact = createTrigger({
|
||||
auth: salesforceAuth,
|
||||
name: 'new_contact',
|
||||
displayName: 'New Contact',
|
||||
description: 'Fires when a new Contact record is created in Salesforce.',
|
||||
props: {},
|
||||
sampleData: {
|
||||
"Id": "0037Q000005x4aXUAQ",
|
||||
"AccountId": "0017Q00000qM8c9QAC",
|
||||
"Name": "Jane Doe",
|
||||
"CreatedDate": "2025-10-10T12:00:00.000Z",
|
||||
},
|
||||
type: TriggerStrategy.POLLING,
|
||||
async test(ctx) {
|
||||
return await pollingHelper.test(polling, ctx);
|
||||
},
|
||||
async onEnable(ctx) {
|
||||
await pollingHelper.onEnable(polling, ctx);
|
||||
},
|
||||
async onDisable(ctx) {
|
||||
await pollingHelper.onDisable(polling, ctx);
|
||||
},
|
||||
async run(ctx) {
|
||||
return await pollingHelper.poll(polling, ctx);
|
||||
},
|
||||
});
|
||||
|
||||
const polling: Polling<AppConnectionValueForAuthProperty<typeof salesforceAuth>, Record<string, never>> = {
|
||||
strategy: DedupeStrategy.TIMEBASED,
|
||||
items: async ({ auth, lastFetchEpochMS }) => {
|
||||
const isoDate = dayjs(lastFetchEpochMS).toISOString();
|
||||
|
||||
const query = `
|
||||
SELECT FIELDS(ALL)
|
||||
FROM Contact
|
||||
WHERE CreatedDate > ${isoDate}
|
||||
ORDER BY CreatedDate ASC
|
||||
LIMIT 200
|
||||
`;
|
||||
|
||||
const response = await querySalesforceApi<{ records: { CreatedDate: string }[] }>(
|
||||
HttpMethod.GET,
|
||||
auth,
|
||||
query
|
||||
);
|
||||
|
||||
const records = response.body?.['records'] || [];
|
||||
|
||||
return records.map((record: { CreatedDate: string }) => ({
|
||||
epochMilliSeconds: dayjs(record.CreatedDate).valueOf(),
|
||||
data: record,
|
||||
}));
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
import {
|
||||
DedupeStrategy,
|
||||
HttpMethod,
|
||||
Polling,
|
||||
pollingHelper,
|
||||
} from '@activepieces/pieces-common';
|
||||
import {
|
||||
AppConnectionValueForAuthProperty,
|
||||
TriggerStrategy,
|
||||
createTrigger,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { querySalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import { salesforceAuth } from '../..';
|
||||
|
||||
export const newFieldHistoryEvent = createTrigger({
|
||||
auth: salesforceAuth,
|
||||
name: 'new_field_history_event',
|
||||
displayName: 'New Field History Event',
|
||||
description: 'Fires when a tracked field is updated on a specified object.',
|
||||
props: {
|
||||
object: salesforcesCommon.object,
|
||||
},
|
||||
sampleData: {
|
||||
"Id": "0177Q00000s912jQAA",
|
||||
"IsDeleted": false,
|
||||
"ParentId": "0017Q00000qM8c9QAC",
|
||||
"CreatedById": "0057Q000003h3VwQAI",
|
||||
"CreatedDate": "2025-10-10T12:00:00.000Z",
|
||||
"Field": "Industry",
|
||||
"OldValue": "Agriculture",
|
||||
"NewValue": "Technology"
|
||||
},
|
||||
type: TriggerStrategy.POLLING,
|
||||
async test(ctx) {
|
||||
return await pollingHelper.test(polling, ctx);
|
||||
},
|
||||
async onEnable(ctx) {
|
||||
await pollingHelper.onEnable(polling, ctx);
|
||||
},
|
||||
async onDisable(ctx) {
|
||||
await pollingHelper.onDisable(polling, ctx);
|
||||
},
|
||||
async run(ctx) {
|
||||
return await pollingHelper.poll(polling, ctx);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const polling: Polling<AppConnectionValueForAuthProperty<typeof salesforceAuth>, { object: string | undefined }> = {
|
||||
strategy: DedupeStrategy.TIMEBASED,
|
||||
items: async ({ auth, propsValue, lastFetchEpochMS }) => {
|
||||
const { object } = propsValue;
|
||||
|
||||
|
||||
if (!object) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const isoDate = dayjs(lastFetchEpochMS).toISOString();
|
||||
|
||||
const historyObject = object.endsWith('__c')
|
||||
? object.replace('__c', '__History')
|
||||
: `${object}History`;
|
||||
|
||||
const query = `
|
||||
SELECT FIELDS(ALL)
|
||||
FROM ${historyObject}
|
||||
WHERE CreatedDate > ${isoDate}
|
||||
ORDER BY CreatedDate ASC
|
||||
LIMIT 200
|
||||
`;
|
||||
|
||||
try {
|
||||
const response = await querySalesforceApi<{ records: { CreatedDate: string }[] }>(
|
||||
HttpMethod.GET,
|
||||
auth,
|
||||
query
|
||||
);
|
||||
|
||||
const records = response.body?.['records'] || [];
|
||||
|
||||
return records.map((record: { CreatedDate: string }) => ({
|
||||
epochMilliSeconds: dayjs(record.CreatedDate).valueOf(),
|
||||
data: record,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.warn(`Could not poll for history on ${historyObject}. It might not exist or have history tracking enabled.`);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
import {
|
||||
DedupeStrategy,
|
||||
HttpMethod,
|
||||
Polling,
|
||||
pollingHelper,
|
||||
} from '@activepieces/pieces-common';
|
||||
import {
|
||||
AppConnectionValueForAuthProperty,
|
||||
TriggerStrategy,
|
||||
createTrigger,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { querySalesforceApi } from '../common';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import { salesforceAuth } from '../..';
|
||||
|
||||
export const newLead = createTrigger({
|
||||
auth: salesforceAuth,
|
||||
name: 'new_lead',
|
||||
displayName: 'New Lead',
|
||||
description: 'Fires when a new Lead record is created in Salesforce.',
|
||||
props: {},
|
||||
sampleData: {
|
||||
"Id": "00Q7Q000003x4aXUAQ",
|
||||
"Company": "ACME Inc.",
|
||||
"Name": "John Doe",
|
||||
"CreatedDate": "2025-10-10T12:00:00.000Z",
|
||||
},
|
||||
type: TriggerStrategy.POLLING,
|
||||
async test(ctx) {
|
||||
return await pollingHelper.test(polling, ctx);
|
||||
},
|
||||
async onEnable(ctx) {
|
||||
await pollingHelper.onEnable(polling, ctx);
|
||||
},
|
||||
async onDisable(ctx) {
|
||||
await pollingHelper.onDisable(polling, ctx);
|
||||
},
|
||||
async run(ctx) {
|
||||
return await pollingHelper.poll(polling, ctx);
|
||||
},
|
||||
});
|
||||
|
||||
const polling: Polling<AppConnectionValueForAuthProperty<typeof salesforceAuth>, Record<string, never>> = {
|
||||
strategy: DedupeStrategy.TIMEBASED,
|
||||
items: async ({ auth, lastFetchEpochMS }) => {
|
||||
const isoDate = dayjs(lastFetchEpochMS).toISOString();
|
||||
|
||||
const query = `
|
||||
SELECT FIELDS(ALL)
|
||||
FROM Lead
|
||||
WHERE CreatedDate > ${isoDate}
|
||||
ORDER BY CreatedDate ASC
|
||||
LIMIT 200
|
||||
`;
|
||||
|
||||
const response = await querySalesforceApi<{ records: { CreatedDate: string }[] }>(
|
||||
HttpMethod.GET,
|
||||
auth,
|
||||
query
|
||||
);
|
||||
|
||||
const records = response.body?.['records'] || [];
|
||||
|
||||
return records.map((record: { CreatedDate: string }) => ({
|
||||
epochMilliSeconds: dayjs(record.CreatedDate).valueOf(),
|
||||
data: record,
|
||||
}));
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
import {
|
||||
TriggerStrategy,
|
||||
createTrigger,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { XMLParser } from 'fast-xml-parser';
|
||||
import { salesforceAuth } from '../..';
|
||||
|
||||
export const newOutboundMessage = createTrigger({
|
||||
auth: salesforceAuth,
|
||||
name: 'new_outbound_message',
|
||||
displayName: 'New Outbound Message',
|
||||
description: 'Fires when a new outbound message is received from Salesforce.',
|
||||
props: {},
|
||||
// See https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_om_outboundmessaging_notification.htm
|
||||
sampleData: {
|
||||
"soapenv:Envelope": {
|
||||
"@_xmlns:soapenv": "http://schemas.xmlsoap.org/soap/envelope/",
|
||||
"@_xmlns:xsd": "http://www.w3.org/2001/XMLSchema",
|
||||
"@_xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
||||
"soapenv:Body": {
|
||||
"notifications": {
|
||||
"@_xmlns": "http://soap.sforce.com/2005/09/outbound",
|
||||
"OrganizationId": "00Dxx0000001gEREAY",
|
||||
"ActionId": "04kxx0000000007EAA",
|
||||
"SessionId": "00Dxx0000001gER!AQcAQH0dMHZfz972_fL234234_tL2F0M567657_o.6T4x3O3O2_E_i_j_k_l_m_n_o_p",
|
||||
"EnterpriseUrl": "https://yourInstance.salesforce.com/services/Soap/c/56.0/00Dxx0000001gER",
|
||||
"PartnerUrl": "https://yourInstance.salesforce.com/services/Soap/u/56.0/00Dxx0000001gER",
|
||||
"Notification": {
|
||||
"Id": "04lxx0000000007EAA",
|
||||
"sObject": {
|
||||
"@_xsi:type": "sf:Account",
|
||||
"@_xmlns:sf": "urn:sobject.enterprise.soap.sforce.com",
|
||||
"Id": "001xx000003D8i1AAC",
|
||||
"Name": "New Account From Webhook"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
async onEnable() {
|
||||
// Webhook triggers don't require setup
|
||||
},
|
||||
async onDisable() {
|
||||
// Webhook triggers don't require cleanup
|
||||
},
|
||||
async run(context) {
|
||||
const requestBody = context.payload.body as string;
|
||||
|
||||
const parser = new XMLParser({
|
||||
attributeNamePrefix: "@_",
|
||||
textNodeName: "#text",
|
||||
ignoreAttributes: false,
|
||||
parseTagValue: true,
|
||||
parseAttributeValue: true,
|
||||
trimValues: true,
|
||||
isArray: (name) => {
|
||||
return name === "Notification";
|
||||
}
|
||||
});
|
||||
|
||||
const parsedData = parser.parse(requestBody);
|
||||
|
||||
const notifications = parsedData?.['soapenv:Envelope']?.['soapenv:Body']?.['notifications']?.['Notification'] || [];
|
||||
|
||||
|
||||
return [notifications];
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,119 @@
|
||||
import {
|
||||
DedupeStrategy,
|
||||
HttpMethod,
|
||||
Polling,
|
||||
pollingHelper,
|
||||
} from '@activepieces/pieces-common';
|
||||
import {
|
||||
AppConnectionValueForAuthProperty,
|
||||
Property,
|
||||
TriggerStrategy,
|
||||
createTrigger,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { querySalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import { salesforceAuth } from '../..';
|
||||
|
||||
export const newRecord = createTrigger({
|
||||
auth: salesforceAuth,
|
||||
name: 'new_record',
|
||||
displayName: 'New Record',
|
||||
description: 'Triggers when there is new record',
|
||||
props: {
|
||||
object: salesforcesCommon.object,
|
||||
conditions: Property.LongText({
|
||||
displayName: 'Conditions (Advanced)',
|
||||
description: 'Enter a SOQL query where clause i. e. IsDeleted = TRUE',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
sampleData: {},
|
||||
type: TriggerStrategy.POLLING,
|
||||
async test(ctx) {
|
||||
return await pollingHelper.test(polling, {
|
||||
auth: ctx.auth,
|
||||
store: ctx.store,
|
||||
propsValue: ctx.propsValue,
|
||||
files: ctx.files,
|
||||
});
|
||||
},
|
||||
async onEnable(ctx) {
|
||||
await pollingHelper.onEnable(polling, {
|
||||
auth: ctx.auth,
|
||||
store: ctx.store,
|
||||
propsValue: ctx.propsValue,
|
||||
});
|
||||
},
|
||||
async onDisable(ctx) {
|
||||
await pollingHelper.onDisable(polling, {
|
||||
auth: ctx.auth,
|
||||
store: ctx.store,
|
||||
propsValue: ctx.propsValue,
|
||||
});
|
||||
},
|
||||
async run(ctx) {
|
||||
return await pollingHelper.poll(polling, {
|
||||
auth: ctx.auth,
|
||||
store: ctx.store,
|
||||
propsValue: ctx.propsValue,
|
||||
files: ctx.files,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const polling: Polling<
|
||||
AppConnectionValueForAuthProperty<typeof salesforceAuth>,
|
||||
{ object: string | undefined; conditions: string | undefined }
|
||||
> = {
|
||||
strategy: DedupeStrategy.TIMEBASED,
|
||||
items: async ({ auth, propsValue, lastFetchEpochMS }) => {
|
||||
const items = await getRecords(
|
||||
auth,
|
||||
propsValue.object!,
|
||||
dayjs(lastFetchEpochMS).toISOString(),
|
||||
propsValue.conditions
|
||||
);
|
||||
return items.map((item) => ({
|
||||
epochMilliSeconds: dayjs(item.CreatedDate).valueOf(),
|
||||
data: item,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
const getRecords = async (
|
||||
authentication: AppConnectionValueForAuthProperty<typeof salesforceAuth>,
|
||||
object: string,
|
||||
startDate: string,
|
||||
conditions: string | undefined
|
||||
) => {
|
||||
const response = await querySalesforceApi<{
|
||||
records: { CreatedDate: string }[];
|
||||
}>(
|
||||
HttpMethod.GET,
|
||||
authentication,
|
||||
constructQuery(object, 200, 0, startDate, conditions)
|
||||
);
|
||||
return response.body['records'];
|
||||
};
|
||||
|
||||
function constructQuery(
|
||||
object: string,
|
||||
limit: number,
|
||||
offset: number,
|
||||
startDate: string,
|
||||
conditions: string | undefined
|
||||
) {
|
||||
return `
|
||||
SELECT
|
||||
FIELDS(ALL)
|
||||
FROM
|
||||
${object}
|
||||
WHERE CreatedDate > ${startDate} ${
|
||||
conditions != undefined ? `AND ${conditions}` : ''
|
||||
}
|
||||
ORDER BY CreatedDate ASC
|
||||
LIMIT ${limit}
|
||||
OFFSET ${offset}
|
||||
`;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
DedupeStrategy,
|
||||
HttpMethod,
|
||||
Polling,
|
||||
pollingHelper,
|
||||
} from '@activepieces/pieces-common';
|
||||
import {
|
||||
AppConnectionValueForAuthProperty,
|
||||
TriggerStrategy,
|
||||
createTrigger,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { querySalesforceApi } from '../common';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import { salesforceAuth } from '../..';
|
||||
|
||||
export const newUpdatedFile = createTrigger({
|
||||
auth: salesforceAuth,
|
||||
name: 'new_updated_file',
|
||||
displayName: 'New or Updated File',
|
||||
description: 'Fires when a file (ContentDocument) is created or updated. Does not fire for classic Attachments or Notes.',
|
||||
props: {},
|
||||
sampleData: {
|
||||
"Id": "0697Q000002qB9iQAE",
|
||||
"Title": "My Document.pdf",
|
||||
"LastModifiedDate": "2025-10-10T12:00:00.000Z",
|
||||
"Type": "ContentDocument"
|
||||
},
|
||||
type: TriggerStrategy.POLLING,
|
||||
async test(ctx) {
|
||||
return await pollingHelper.test(polling, ctx);
|
||||
},
|
||||
async onEnable(ctx) {
|
||||
await pollingHelper.onEnable(polling, ctx);
|
||||
},
|
||||
async onDisable(ctx) {
|
||||
await pollingHelper.onDisable(polling, ctx);
|
||||
},
|
||||
async run(ctx) {
|
||||
return await pollingHelper.poll(polling, ctx);
|
||||
},
|
||||
});
|
||||
|
||||
const polling: Polling<AppConnectionValueForAuthProperty<typeof salesforceAuth>, Record<string, never>> = {
|
||||
strategy: DedupeStrategy.TIMEBASED,
|
||||
items: async ({ auth, lastFetchEpochMS }) => {
|
||||
const isoDate = dayjs(lastFetchEpochMS).toISOString();
|
||||
|
||||
const query = `
|
||||
SELECT FIELDS(ALL)
|
||||
FROM ContentDocument
|
||||
WHERE SystemModstamp > ${isoDate}
|
||||
ORDER BY SystemModstamp ASC
|
||||
LIMIT 200
|
||||
`;
|
||||
|
||||
const response = await querySalesforceApi<{ records: { SystemModstamp: string }[] }>(HttpMethod.GET, auth, query);
|
||||
const records = response.body?.['records'] || [];
|
||||
|
||||
return records.map((record: any) => ({
|
||||
epochMilliSeconds: dayjs(record.SystemModstamp).valueOf(),
|
||||
data: { ...record, Type: 'ContentDocument' },
|
||||
}));
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,114 @@
|
||||
import {
|
||||
DedupeStrategy,
|
||||
HttpMethod,
|
||||
Polling,
|
||||
pollingHelper,
|
||||
} from '@activepieces/pieces-common';
|
||||
import {
|
||||
AppConnectionValueForAuthProperty,
|
||||
Property,
|
||||
TriggerStrategy,
|
||||
createTrigger,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { querySalesforceApi, salesforcesCommon } from '../common';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import { salesforceAuth } from '../..';
|
||||
|
||||
export const newOrUpdatedRecord = createTrigger({
|
||||
auth: salesforceAuth,
|
||||
name: 'new_or_updated_record',
|
||||
displayName: 'New or Updated Record',
|
||||
description: 'Triggers when there is new or updated record',
|
||||
props: {
|
||||
object: salesforcesCommon.object,
|
||||
conditions: Property.LongText({
|
||||
displayName: 'Conditions (Advanced)',
|
||||
description: 'Enter a SOQL query where clause i. e. IsDeleted = TRUE',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
sampleData: {},
|
||||
type: TriggerStrategy.POLLING,
|
||||
async test(ctx) {
|
||||
return await pollingHelper.test(polling, {
|
||||
auth: ctx.auth,
|
||||
store: ctx.store,
|
||||
propsValue: ctx.propsValue,
|
||||
files: ctx.files,
|
||||
});
|
||||
},
|
||||
async onEnable(ctx) {
|
||||
await pollingHelper.onEnable(polling, {
|
||||
auth: ctx.auth,
|
||||
store: ctx.store,
|
||||
propsValue: ctx.propsValue,
|
||||
});
|
||||
},
|
||||
async onDisable(ctx) {
|
||||
await pollingHelper.onDisable(polling, {
|
||||
auth: ctx.auth,
|
||||
store: ctx.store,
|
||||
propsValue: ctx.propsValue,
|
||||
});
|
||||
},
|
||||
async run(ctx) {
|
||||
return await pollingHelper.poll(polling, ctx);
|
||||
},
|
||||
});
|
||||
|
||||
const polling: Polling<
|
||||
AppConnectionValueForAuthProperty<typeof salesforceAuth>,
|
||||
{ object: string | undefined; conditions: string | undefined }
|
||||
> = {
|
||||
strategy: DedupeStrategy.TIMEBASED,
|
||||
items: async ({ auth, propsValue, lastFetchEpochMS }) => {
|
||||
const items = await getRecords(
|
||||
auth,
|
||||
propsValue.object!,
|
||||
dayjs(lastFetchEpochMS).toISOString(),
|
||||
propsValue.conditions
|
||||
);
|
||||
return items.map((item) => ({
|
||||
epochMilliSeconds: dayjs(item.LastModifiedDate).valueOf(),
|
||||
data: item,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
const getRecords = async (
|
||||
authentication: AppConnectionValueForAuthProperty<typeof salesforceAuth>,
|
||||
object: string,
|
||||
startDate: string,
|
||||
conditions: string | undefined
|
||||
) => {
|
||||
const response = await querySalesforceApi<{
|
||||
records: { LastModifiedDate: string }[];
|
||||
}>(
|
||||
HttpMethod.GET,
|
||||
authentication,
|
||||
constructQuery(object, 200, 0, startDate, conditions)
|
||||
);
|
||||
return response.body['records'];
|
||||
};
|
||||
|
||||
function constructQuery(
|
||||
object: string,
|
||||
limit: number,
|
||||
offset: number,
|
||||
startDate: string,
|
||||
conditions: string | undefined
|
||||
) {
|
||||
return `
|
||||
SELECT
|
||||
FIELDS(ALL)
|
||||
FROM
|
||||
${object}
|
||||
WHERE LastModifiedDate > ${startDate} ${
|
||||
conditions != undefined ? `AND ${conditions}` : ''
|
||||
}
|
||||
ORDER BY LastModifiedDate ASC
|
||||
LIMIT ${limit}
|
||||
OFFSET ${offset}
|
||||
`;
|
||||
}
|
||||
Reference in New Issue
Block a user