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,60 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { CopperAuth } from '../common/constants';
|
||||
import {
|
||||
companyDropdown,
|
||||
leadDropdown,
|
||||
opportunityDropdown,
|
||||
} from '../common/props';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
|
||||
export const convertLead = createAction({
|
||||
auth: CopperAuth,
|
||||
name: 'convertLead',
|
||||
displayName: 'Convert Lead',
|
||||
description:
|
||||
'Converts a lead into a person (optionally with company/opportunity).',
|
||||
props: {
|
||||
leadId: leadDropdown(['auth']),
|
||||
companyId: companyDropdown({ refreshers: ['auth'] }),
|
||||
opportunityId: opportunityDropdown({ refreshers: ['auth'] }),
|
||||
},
|
||||
async run(context) {
|
||||
const { leadId, companyId, opportunityId } = context.propsValue;
|
||||
|
||||
const opportunity = opportunityId
|
||||
? JSON.parse(opportunityId as string)
|
||||
: null;
|
||||
const company = companyId ? JSON.parse(companyId as string) : null;
|
||||
const lead = JSON.parse(leadId as string);
|
||||
|
||||
const payload = {
|
||||
person: {
|
||||
name: lead.name,
|
||||
},
|
||||
company: {
|
||||
...(company
|
||||
? {
|
||||
id: company.id,
|
||||
}
|
||||
: {
|
||||
name: '',
|
||||
}),
|
||||
},
|
||||
...(opportunity
|
||||
? {
|
||||
opportunity: {
|
||||
name: opportunity.name,
|
||||
pipeline_id: opportunity.pipeline_id,
|
||||
pipeline_stage_id: opportunity.pipeline_stage_id,
|
||||
monetary_value: opportunity.monetary_value || undefined,
|
||||
assignee_id: opportunity.assignee_id,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
|
||||
return await CopperApiService.convertLead(context.auth, lead.id, {
|
||||
details: payload,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,118 @@
|
||||
import {
|
||||
createAction,
|
||||
InputPropertyMap,
|
||||
Property,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { CopperAuth, CopperAuthType } from '../common/constants';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
import { ActivityTypesDropdown } from '../common/props';
|
||||
|
||||
export const createActivity = createAction({
|
||||
auth: CopperAuth,
|
||||
name: 'createActivity',
|
||||
displayName: 'Create Activity',
|
||||
description: 'Logs an activity related to CRM entities.',
|
||||
props: {
|
||||
entity: Property.StaticDropdown({
|
||||
displayName: 'Parent Entity',
|
||||
description: 'Select parent entity',
|
||||
required: true,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Person', value: 'person' },
|
||||
{ label: 'Company', value: 'company' },
|
||||
{ label: 'Lead', value: 'lead' },
|
||||
{ label: 'Opportunity', value: 'opportunity' },
|
||||
{ label: 'Project', value: 'project' },
|
||||
{ label: 'Task', value: 'task' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
entityItemId: Property.Dropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Parent Entity Resource',
|
||||
description: 'Select Resource',
|
||||
required: true,
|
||||
refreshers: ['auth', 'entity'],
|
||||
async options(propsValue: Record<string, unknown>) {
|
||||
const auth = propsValue['auth'] as CopperAuthType | undefined;
|
||||
const entity = propsValue['entity'] as
|
||||
| 'person'
|
||||
| 'company'
|
||||
| 'lead'
|
||||
| 'opportunity'
|
||||
| 'project'
|
||||
| 'task';
|
||||
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (!entity) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Select a Parent Entity first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
const fetchFnMap = {
|
||||
person: CopperApiService.fetchPeople,
|
||||
company: CopperApiService.fetchCompanies,
|
||||
lead: CopperApiService.fetchLeads,
|
||||
opportunity: CopperApiService.fetchOpportunities,
|
||||
task: CopperApiService.fetchTasks,
|
||||
project: CopperApiService.fetchProjects,
|
||||
};
|
||||
|
||||
const fetchFn = fetchFnMap[entity];
|
||||
|
||||
try {
|
||||
const items = await fetchFn(auth);
|
||||
|
||||
return {
|
||||
options: items.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch entity items', e);
|
||||
return {
|
||||
options: [],
|
||||
placeholder: 'Unable to load entity items',
|
||||
};
|
||||
}
|
||||
},
|
||||
}),
|
||||
details: Property.ShortText({
|
||||
displayName: 'Details',
|
||||
description: 'The details of the project',
|
||||
required: false,
|
||||
}),
|
||||
type: ActivityTypesDropdown("user"),
|
||||
},
|
||||
async run(context) {
|
||||
const { type, entity, entityItemId, details } = context.propsValue;
|
||||
|
||||
const activityType = JSON.parse(type as string);
|
||||
|
||||
const payload = {
|
||||
parent: {
|
||||
type: entity,
|
||||
id: entityItemId,
|
||||
},
|
||||
type: {
|
||||
category: activityType.category,
|
||||
id: activityType.id,
|
||||
},
|
||||
details,
|
||||
};
|
||||
|
||||
return await CopperApiService.createActivity(context.auth, payload);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { CopperAuth, isNonEmptyStr } from '../common/constants';
|
||||
import { primaryContactsDropdown } from '../common/props';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
|
||||
export const createCompany = createAction({
|
||||
auth: CopperAuth,
|
||||
name: 'createCompany',
|
||||
displayName: 'Create Company',
|
||||
description: 'Adds a new company.',
|
||||
props: {
|
||||
name: Property.ShortText({ displayName: 'Full Name', required: true }),
|
||||
email_domain: Property.ShortText({
|
||||
displayName: 'Email Domain',
|
||||
description: 'E.g. democompany.com',
|
||||
required: false,
|
||||
}),
|
||||
details: Property.ShortText({
|
||||
displayName: 'Details',
|
||||
required: false,
|
||||
}),
|
||||
phone_numbers: Property.Array({
|
||||
displayName: 'Phone Numbers',
|
||||
required: false,
|
||||
properties: {
|
||||
number: Property.ShortText({
|
||||
displayName: 'Number',
|
||||
required: true,
|
||||
}),
|
||||
category: Property.ShortText({
|
||||
displayName: 'Category (e.g., mobile, work, personal)',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
defaultValue: [],
|
||||
}),
|
||||
address_street: Property.ShortText({
|
||||
displayName: 'Street',
|
||||
required: false,
|
||||
}),
|
||||
address_city: Property.ShortText({ displayName: 'City', required: false }),
|
||||
address_state: Property.ShortText({
|
||||
displayName: 'State/Region',
|
||||
required: false,
|
||||
}),
|
||||
address_postal_code: Property.ShortText({
|
||||
displayName: 'Postal Code',
|
||||
required: false,
|
||||
}),
|
||||
address_country: Property.ShortText({
|
||||
displayName: 'Country',
|
||||
required: false,
|
||||
}),
|
||||
primaryContactId: primaryContactsDropdown({ refreshers: ['auth'] }),
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
name,
|
||||
email_domain,
|
||||
details,
|
||||
address_street,
|
||||
address_city,
|
||||
address_state,
|
||||
address_postal_code,
|
||||
address_country,
|
||||
phone_numbers,
|
||||
primaryContactId,
|
||||
} = context.propsValue as any;
|
||||
|
||||
const normPhones = (Array.isArray(phone_numbers) ? phone_numbers : [])
|
||||
.map((p) => ({
|
||||
number: String(p?.number ?? '').trim(),
|
||||
category: isNonEmptyStr(p?.category)
|
||||
? String(p.category).trim()
|
||||
: undefined,
|
||||
}))
|
||||
.filter((p) => isNonEmptyStr(p.number));
|
||||
|
||||
const address = [
|
||||
address_street,
|
||||
address_city,
|
||||
address_state,
|
||||
address_postal_code,
|
||||
address_country,
|
||||
].some(isNonEmptyStr)
|
||||
? {
|
||||
street: isNonEmptyStr(address_street)
|
||||
? address_street.trim()
|
||||
: undefined,
|
||||
city: isNonEmptyStr(address_city) ? address_city.trim() : undefined,
|
||||
state: isNonEmptyStr(address_state)
|
||||
? address_state.trim()
|
||||
: undefined,
|
||||
postal_code: isNonEmptyStr(address_postal_code)
|
||||
? address_postal_code.trim()
|
||||
: undefined,
|
||||
country: isNonEmptyStr(address_country)
|
||||
? address_country.trim()
|
||||
: undefined,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const body: any = {
|
||||
name,
|
||||
email_domain,
|
||||
details,
|
||||
...(normPhones.length ? { phone_numbers: normPhones } : {}),
|
||||
...(address ? { address } : {}),
|
||||
primary_contact_id: primaryContactId,
|
||||
};
|
||||
|
||||
return await CopperApiService.createCompany(context.auth, body);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,112 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
|
||||
import { CopperAuth, isNonEmptyStr } from '../common/constants';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
|
||||
export const createLead = createAction({
|
||||
auth: CopperAuth,
|
||||
name: 'createLead',
|
||||
displayName: 'Create Lead',
|
||||
description: 'Adds a new lead.',
|
||||
props: {
|
||||
name: Property.ShortText({ displayName: 'Full Name', required: true }),
|
||||
email: Property.ShortText({
|
||||
displayName: 'Email',
|
||||
required: true,
|
||||
}),
|
||||
category: Property.ShortText({
|
||||
displayName: 'Category of the email address (e.g., work, personal)',
|
||||
required: true,
|
||||
}),
|
||||
phone_numbers: Property.Array({
|
||||
displayName: 'Phone Numbers',
|
||||
required: false,
|
||||
properties: {
|
||||
number: Property.ShortText({
|
||||
displayName: 'Number',
|
||||
required: true,
|
||||
}),
|
||||
category: Property.ShortText({
|
||||
displayName: 'Category (e.g., mobile, work, personal)',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
defaultValue: [],
|
||||
}),
|
||||
address_street: Property.ShortText({
|
||||
displayName: 'Street',
|
||||
required: false,
|
||||
}),
|
||||
address_city: Property.ShortText({ displayName: 'City', required: false }),
|
||||
address_state: Property.ShortText({
|
||||
displayName: 'State/Region',
|
||||
required: false,
|
||||
}),
|
||||
address_postal_code: Property.ShortText({
|
||||
displayName: 'Postal Code',
|
||||
required: false,
|
||||
}),
|
||||
address_country: Property.ShortText({
|
||||
displayName: 'Country',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
name,
|
||||
email,
|
||||
category,
|
||||
address_street,
|
||||
address_city,
|
||||
address_state,
|
||||
address_postal_code,
|
||||
address_country,
|
||||
phone_numbers,
|
||||
} = context.propsValue as any;
|
||||
|
||||
const normPhones = (Array.isArray(phone_numbers) ? phone_numbers : [])
|
||||
.map((p) => ({
|
||||
number: String(p?.number ?? '').trim(),
|
||||
category: isNonEmptyStr(p?.category)
|
||||
? String(p.category).trim()
|
||||
: undefined,
|
||||
}))
|
||||
.filter((p) => isNonEmptyStr(p.number));
|
||||
|
||||
const address = [
|
||||
address_street,
|
||||
address_city,
|
||||
address_state,
|
||||
address_postal_code,
|
||||
address_country,
|
||||
].some(isNonEmptyStr)
|
||||
? {
|
||||
street: isNonEmptyStr(address_street)
|
||||
? address_street.trim()
|
||||
: undefined,
|
||||
city: isNonEmptyStr(address_city) ? address_city.trim() : undefined,
|
||||
state: isNonEmptyStr(address_state)
|
||||
? address_state.trim()
|
||||
: undefined,
|
||||
postal_code: isNonEmptyStr(address_postal_code)
|
||||
? address_postal_code.trim()
|
||||
: undefined,
|
||||
country: isNonEmptyStr(address_country)
|
||||
? address_country.trim()
|
||||
: undefined,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const body: any = {
|
||||
name,
|
||||
email: {
|
||||
email,
|
||||
category
|
||||
},
|
||||
...(normPhones.length ? { phone_numbers: normPhones } : {}),
|
||||
...(address ? { address } : {}),
|
||||
};
|
||||
|
||||
return await CopperApiService.createLead(context.auth, body);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { CopperAuth } from '../common/constants';
|
||||
import { pipelinesDropdown, primaryContactsDropdown } from '../common/props';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
|
||||
export const createOpportunity = createAction({
|
||||
auth: CopperAuth,
|
||||
name: 'createOpportunity',
|
||||
displayName: 'Create Opportunity',
|
||||
description: 'Adds a new opportunity.',
|
||||
props: {
|
||||
name: Property.ShortText({
|
||||
displayName: 'Name',
|
||||
description: 'The name of the opportunity',
|
||||
required: true,
|
||||
}),
|
||||
pipelineId: pipelinesDropdown({ refreshers: ['auth'] }),
|
||||
pipelineStageId: Property.Dropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Pipeline Stage',
|
||||
description: 'Select a stage',
|
||||
refreshers: ['auth', 'pipelineId'],
|
||||
required: false,
|
||||
async options({ auth, pipelineId }: any) {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (!pipelineId) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Select a pipeline first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
const pipeline = JSON.parse(pipelineId);
|
||||
|
||||
const stages = pipeline.stages;
|
||||
|
||||
return {
|
||||
options: stages.map((stage: any) => ({
|
||||
label: stage.name,
|
||||
value: stage.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
primaryContactId: primaryContactsDropdown({ refreshers: ['auth'] }),
|
||||
},
|
||||
async run(context) {
|
||||
const { name, primaryContactId, pipelineId, pipelineStageId } =
|
||||
context.propsValue;
|
||||
|
||||
const pipeline = JSON.parse(pipelineId as string);
|
||||
|
||||
return await CopperApiService.createOpportunity(context.auth, {
|
||||
name,
|
||||
primary_contact_id: primaryContactId,
|
||||
pipeline_id: pipeline.id,
|
||||
pipeline_stage_id: pipelineStageId,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,133 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { CopperAuth, isNonEmptyStr } from '../common/constants';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
|
||||
export const createPerson = createAction({
|
||||
auth: CopperAuth,
|
||||
name: 'createPerson',
|
||||
displayName: 'Create Person',
|
||||
description: 'Adds a new person/contact.',
|
||||
props: {
|
||||
name: Property.ShortText({ displayName: 'Full Name', required: true }),
|
||||
emails: Property.Array({
|
||||
displayName: 'Emails',
|
||||
required: true,
|
||||
properties: {
|
||||
email: Property.ShortText({
|
||||
displayName: 'Email',
|
||||
required: true,
|
||||
}),
|
||||
category: Property.ShortText({
|
||||
displayName: 'Category (e.g., work, personal)',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
defaultValue: [],
|
||||
}),
|
||||
phone_numbers: Property.Array({
|
||||
displayName: 'Phone Numbers',
|
||||
required: false,
|
||||
properties: {
|
||||
number: Property.ShortText({
|
||||
displayName: 'Number',
|
||||
required: true,
|
||||
}),
|
||||
category: Property.ShortText({
|
||||
displayName: 'Category (e.g., mobile, work, personal)',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
defaultValue: [],
|
||||
}),
|
||||
address_street: Property.ShortText({
|
||||
displayName: 'Street',
|
||||
required: false,
|
||||
}),
|
||||
address_city: Property.ShortText({ displayName: 'City', required: false }),
|
||||
address_state: Property.ShortText({
|
||||
displayName: 'State/Region',
|
||||
required: false,
|
||||
}),
|
||||
address_postal_code: Property.ShortText({
|
||||
displayName: 'Postal Code',
|
||||
required: false,
|
||||
}),
|
||||
address_country: Property.ShortText({
|
||||
displayName: 'Country',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
name,
|
||||
emails,
|
||||
address_street,
|
||||
address_city,
|
||||
address_state,
|
||||
address_postal_code,
|
||||
address_country,
|
||||
phone_numbers,
|
||||
} = context.propsValue as any;
|
||||
|
||||
const normEmails = (Array.isArray(emails) ? emails : [])
|
||||
.map((row) => ({
|
||||
email: String(row?.email ?? '')
|
||||
.trim()
|
||||
.toLowerCase(),
|
||||
category: isNonEmptyStr(row?.category)
|
||||
? String(row.category).trim()
|
||||
: undefined,
|
||||
}))
|
||||
.filter((e) => isNonEmptyStr(e.email));
|
||||
|
||||
if (normEmails.length === 0)
|
||||
throw new Error('Please provide at least one valid email.');
|
||||
|
||||
const seen = new Set<string>();
|
||||
const dedupedEmails = normEmails.filter(
|
||||
(e) => !seen.has(e.email) && (seen.add(e.email), true)
|
||||
);
|
||||
|
||||
const normPhones = (Array.isArray(phone_numbers) ? phone_numbers : [])
|
||||
.map((p) => ({
|
||||
number: String(p?.number ?? '').trim(),
|
||||
category: isNonEmptyStr(p?.category)
|
||||
? String(p.category).trim()
|
||||
: undefined,
|
||||
}))
|
||||
.filter((p) => isNonEmptyStr(p.number));
|
||||
|
||||
const address = [
|
||||
address_street,
|
||||
address_city,
|
||||
address_state,
|
||||
address_postal_code,
|
||||
address_country,
|
||||
].some(isNonEmptyStr)
|
||||
? {
|
||||
street: isNonEmptyStr(address_street)
|
||||
? address_street.trim()
|
||||
: undefined,
|
||||
city: isNonEmptyStr(address_city) ? address_city.trim() : undefined,
|
||||
state: isNonEmptyStr(address_state)
|
||||
? address_state.trim()
|
||||
: undefined,
|
||||
postal_code: isNonEmptyStr(address_postal_code)
|
||||
? address_postal_code.trim()
|
||||
: undefined,
|
||||
country: isNonEmptyStr(address_country)
|
||||
? address_country.trim()
|
||||
: undefined,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const body: any = {
|
||||
name,
|
||||
emails: dedupedEmails,
|
||||
...(normPhones.length ? { phone_numbers: normPhones } : {}),
|
||||
...(address ? { address } : {}),
|
||||
};
|
||||
|
||||
return await CopperApiService.createPerson(context.auth, body);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { CopperAuth } from '../common/constants';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
|
||||
export const createProject = createAction({
|
||||
auth: CopperAuth,
|
||||
name: 'createProject',
|
||||
displayName: 'Create Project',
|
||||
description: 'Adds a new project.',
|
||||
props: {
|
||||
name: Property.ShortText({
|
||||
displayName: 'Name',
|
||||
description: 'The name of the project',
|
||||
required: true,
|
||||
}),
|
||||
details: Property.ShortText({
|
||||
displayName: 'Details',
|
||||
description: 'The details of the project',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { name, details } = context.propsValue;
|
||||
|
||||
return await CopperApiService.createProject(context.auth, {
|
||||
name,
|
||||
details
|
||||
})
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,168 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { CopperAuth, CopperAuthType, toUnix } from '../common/constants';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
import { ActivityTypesDropdown, usersDropdown } from '../common/props';
|
||||
|
||||
export const createTask = createAction({
|
||||
auth: CopperAuth,
|
||||
name: 'createTask',
|
||||
displayName: 'Create Task',
|
||||
description: 'Adds a new task under a person, lead, or opportunity.',
|
||||
props: {
|
||||
name: Property.ShortText({ displayName: 'Task Name', required: true }),
|
||||
details: Property.ShortText({
|
||||
displayName: 'Details',
|
||||
description: 'Details fo this task',
|
||||
required: false,
|
||||
}),
|
||||
custom_activity_type_id: ActivityTypesDropdown('user'),
|
||||
assigneeId: usersDropdown({ refreshers: ['auth'] }),
|
||||
entity: Property.StaticDropdown({
|
||||
displayName: 'Related Record Type',
|
||||
description:
|
||||
'Choose the type of Copper record this task should be linked to (e.g. Person, Company, Lead, Opportunity, or Project).',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Person', value: 'person' },
|
||||
{ label: 'Company', value: 'company' },
|
||||
{ label: 'Lead', value: 'lead' },
|
||||
{ label: 'Opportunity', value: 'opportunity' },
|
||||
{ label: 'Project', value: 'project' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
entityItemId: Property.Dropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Related Record',
|
||||
description:
|
||||
'Select the specific record (from the chosen type above) that this task should be attached to. For example, pick the Person or Opportunity the task relates to.',
|
||||
required: false,
|
||||
refreshers: ['auth', 'entity'],
|
||||
async options(propsValue: Record<string, unknown>) {
|
||||
const auth = propsValue['auth'] as CopperAuthType | undefined;
|
||||
const entity = propsValue['entity'] as
|
||||
| 'person'
|
||||
| 'company'
|
||||
| 'lead'
|
||||
| 'opportunity'
|
||||
| 'project';
|
||||
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (!entity) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Select a Parent Entity first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
const fetchFnMap = {
|
||||
person: CopperApiService.fetchPeople,
|
||||
company: CopperApiService.fetchCompanies,
|
||||
lead: CopperApiService.fetchLeads,
|
||||
opportunity: CopperApiService.fetchOpportunities,
|
||||
project: CopperApiService.fetchProjects,
|
||||
} as const;
|
||||
|
||||
const fetchFn = fetchFnMap[entity];
|
||||
try {
|
||||
const items = await fetchFn(auth);
|
||||
return {
|
||||
options: items.map((item: any) => ({
|
||||
label: item.name ?? item.title ?? `#${item.id}`,
|
||||
value: item.id,
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch entity items', e);
|
||||
return {
|
||||
options: [],
|
||||
placeholder: 'Unable to load entity items',
|
||||
};
|
||||
}
|
||||
},
|
||||
}),
|
||||
due_date: Property.DateTime({
|
||||
displayName: 'Due Date/Time',
|
||||
required: false,
|
||||
description:
|
||||
'Enter date and time in 24-hour format, e.g. `2025-09-09 11:40` (11:40 AM) or `2025-09-09 13:00` (1:00 PM).',
|
||||
}),
|
||||
reminder_date: Property.DateTime({
|
||||
displayName: 'Reminder Date/Time',
|
||||
required: false,
|
||||
description:
|
||||
'Enter date and time in 24-hour format, e.g. `2025-09-09 11:40` (11:40 AM) or `2025-09-09 13:00` (1:00 PM)',
|
||||
}),
|
||||
priority: Property.StaticDropdown({
|
||||
displayName: 'Priority',
|
||||
required: false,
|
||||
options: {
|
||||
disabled: false,
|
||||
options: [
|
||||
{ label: 'None', value: 'None' },
|
||||
{ label: 'Low', value: 'Low' },
|
||||
{ label: 'Medium', value: 'Medium' },
|
||||
{ label: 'High', value: 'High' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
tags: Property.Array({
|
||||
displayName: 'Tags',
|
||||
required: false,
|
||||
properties: {
|
||||
tag: Property.ShortText({ displayName: 'Tag', required: true }),
|
||||
},
|
||||
defaultValue: [],
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
name,
|
||||
details,
|
||||
custom_activity_type_id,
|
||||
assigneeId,
|
||||
priority,
|
||||
tags,
|
||||
due_date,
|
||||
reminder_date,
|
||||
entity,
|
||||
entityItemId,
|
||||
} = context.propsValue;
|
||||
|
||||
const custom_activity_type = JSON.parse(custom_activity_type_id as string);
|
||||
|
||||
const tagList: string[] = Array.isArray(tags)
|
||||
? tags.map((t: any) => String(t?.tag ?? '').trim()).filter(Boolean)
|
||||
: [];
|
||||
|
||||
const payload = {
|
||||
name,
|
||||
details,
|
||||
custom_activity_type_id: custom_activity_type.id,
|
||||
...(assigneeId ? {assignee_id: assigneeId}: {}),
|
||||
due_date: toUnix(due_date),
|
||||
reminder_date: toUnix(reminder_date),
|
||||
priority,
|
||||
tags: tagList,
|
||||
...(entity &&
|
||||
entityItemId && {
|
||||
related_resource: {
|
||||
type: entity,
|
||||
id: entityItemId,
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
return await CopperApiService.createTask(context.auth, payload);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,285 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { CopperAuth, toUnix } from '../common/constants';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
import { MultiContactTypesDropdown, multiUsersDropdown } from '../common/props';
|
||||
|
||||
export const searchForACompany = createAction({
|
||||
auth: CopperAuth,
|
||||
name: 'searchForACompany',
|
||||
displayName: 'Search for a Company',
|
||||
description: 'Lookup a company.',
|
||||
props: {
|
||||
name: Property.ShortText({
|
||||
displayName: 'Name',
|
||||
description: 'Full name of the Company to search for.',
|
||||
required: false,
|
||||
}),
|
||||
phone_number: Property.ShortText({
|
||||
displayName: 'Phone Number',
|
||||
description: 'Phone Number of the Company to search for.',
|
||||
required: false,
|
||||
}),
|
||||
email_domains: Property.ShortText({
|
||||
displayName: 'Email Domain',
|
||||
required: false,
|
||||
description: 'Email Domain of the Company to search for.',
|
||||
}),
|
||||
contact_type_ids: MultiContactTypesDropdown({}),
|
||||
assignee_ids: multiUsersDropdown({ refreshers: ['auth'] }),
|
||||
city: Property.ShortText({
|
||||
displayName: 'City',
|
||||
description: 'The city in which Company must be located.',
|
||||
required: false,
|
||||
}),
|
||||
state: Property.ShortText({
|
||||
displayName: 'State',
|
||||
description: 'The state or province in which Company must be located.',
|
||||
required: false,
|
||||
}),
|
||||
postal_code: Property.ShortText({
|
||||
displayName: 'Postal Code',
|
||||
description: 'The postal code in which Company must be located.',
|
||||
required: false,
|
||||
}),
|
||||
country: Property.ShortText({
|
||||
displayName: 'Country',
|
||||
description:
|
||||
'The two character country code where Company must be located.',
|
||||
required: false,
|
||||
}),
|
||||
tags: Property.Array({
|
||||
displayName: 'Tags',
|
||||
description:
|
||||
'Filter Company to those that match at least one of the tags specified.',
|
||||
required: false,
|
||||
defaultValue: [],
|
||||
}),
|
||||
socials: Property.Array({
|
||||
displayName: 'Socials',
|
||||
description:
|
||||
'Filter Company to those that match at least one of the social accounts specified.',
|
||||
required: false,
|
||||
defaultValue: [],
|
||||
}),
|
||||
followed: Property.StaticDropdown({
|
||||
displayName: 'Followed',
|
||||
description: 'Filter by followed state',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{
|
||||
label: 'followed',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: 'not followed',
|
||||
value: '2',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
age: Property.Number({
|
||||
displayName: 'Age',
|
||||
description: 'The maximum age in seconds that Company must be.',
|
||||
required: false,
|
||||
}),
|
||||
page_size: Property.Number({
|
||||
displayName: 'Page Size',
|
||||
description: 'Default 50. Max 200.',
|
||||
required: false,
|
||||
defaultValue: 50,
|
||||
}),
|
||||
page_number: Property.Number({
|
||||
displayName: 'Page Number',
|
||||
required: false,
|
||||
defaultValue: 1,
|
||||
}),
|
||||
sort_by: Property.StaticDropdown({
|
||||
displayName: 'Sort By',
|
||||
description: 'The field on which to sort the results',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{
|
||||
label: 'Name',
|
||||
value: 'name',
|
||||
},
|
||||
{
|
||||
label: 'Phone',
|
||||
value: 'phone',
|
||||
},
|
||||
{
|
||||
label: 'Contact',
|
||||
value: 'contact',
|
||||
},
|
||||
{
|
||||
label: 'Contact First Name',
|
||||
value: 'contact_first_name',
|
||||
},
|
||||
{
|
||||
label: 'Contact Last Name',
|
||||
value: 'contact_last_name',
|
||||
},
|
||||
{
|
||||
label: 'Date Modified',
|
||||
value: 'date_modified',
|
||||
},
|
||||
{
|
||||
label: 'Date Created',
|
||||
value: 'date_created',
|
||||
},
|
||||
{
|
||||
label: 'Email Domain',
|
||||
value: 'email_domain',
|
||||
},
|
||||
{
|
||||
label: 'City',
|
||||
value: 'city',
|
||||
},
|
||||
{
|
||||
label: 'State',
|
||||
value: 'state',
|
||||
},
|
||||
{
|
||||
label: 'Country',
|
||||
value: 'country',
|
||||
},
|
||||
{
|
||||
label: 'Zip',
|
||||
value: 'zip',
|
||||
},
|
||||
{
|
||||
label: 'Assignee',
|
||||
value: 'assignee',
|
||||
},
|
||||
{
|
||||
label: 'Contact Group',
|
||||
value: 'contact_group',
|
||||
},
|
||||
{
|
||||
label: 'Last Interaction',
|
||||
value: 'last_interaction',
|
||||
},
|
||||
{
|
||||
label: 'Interaction Count',
|
||||
value: 'interaction_count',
|
||||
},
|
||||
{
|
||||
label: 'Primary Website',
|
||||
value: 'primary_website',
|
||||
},
|
||||
{
|
||||
label: 'Socials',
|
||||
value: 'socials',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
sort_direction: Property.StaticDropdown({
|
||||
displayName: 'Sort Direction',
|
||||
description: 'The direction in which to sort the result',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{
|
||||
label: 'Ascending',
|
||||
value: 'asc',
|
||||
},
|
||||
{
|
||||
label: 'Descending',
|
||||
value: 'desc',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
minimum_interaction_count: Property.Number({
|
||||
displayName: 'Minimum Interaction Count',
|
||||
required: false,
|
||||
description: 'The minimum number of interactions Company must have had.',
|
||||
}),
|
||||
maximum_interaction_count: Property.Number({
|
||||
displayName: 'Maximum Interaction Count',
|
||||
required: false,
|
||||
description: 'The maximum number of interactions Company must have had.',
|
||||
}),
|
||||
minimum_interaction_date: Property.DateTime({
|
||||
displayName: 'Minimum Interaction Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the earliest date of the last interaction.',
|
||||
}),
|
||||
maximum_interaction_date: Property.DateTime({
|
||||
displayName: 'Maximum Interaction Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the latest date of the last interaction.',
|
||||
}),
|
||||
minimum_created_date: Property.DateTime({
|
||||
displayName: 'Minimum Created Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the earliest date Company are created.',
|
||||
}),
|
||||
maximum_created_date: Property.DateTime({
|
||||
displayName: 'Maximum Created Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the latest date Company are Created.',
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
name,
|
||||
phone_number,
|
||||
email_domains,
|
||||
contact_type_ids,
|
||||
assignee_ids,
|
||||
city,
|
||||
state,
|
||||
postal_code,
|
||||
country,
|
||||
tags,
|
||||
socials,
|
||||
followed,
|
||||
age,
|
||||
page_size,
|
||||
page_number,
|
||||
sort_by,
|
||||
sort_direction,
|
||||
minimum_interaction_count,
|
||||
maximum_interaction_count,
|
||||
minimum_interaction_date,
|
||||
maximum_interaction_date,
|
||||
minimum_created_date,
|
||||
maximum_created_date,
|
||||
} = context.propsValue;
|
||||
|
||||
const payload = {
|
||||
name,
|
||||
phone_number,
|
||||
email_domains,
|
||||
contact_type_ids,
|
||||
assignee_ids,
|
||||
city,
|
||||
state,
|
||||
postal_code,
|
||||
country,
|
||||
tags,
|
||||
socials,
|
||||
followed,
|
||||
age,
|
||||
page_size,
|
||||
page_number,
|
||||
sort_by,
|
||||
sort_direction,
|
||||
minimum_interaction_count,
|
||||
maximum_interaction_count,
|
||||
minimum_interaction_date: toUnix(minimum_interaction_date),
|
||||
maximum_interaction_date: toUnix(maximum_interaction_date),
|
||||
minimum_created_date: toUnix(minimum_created_date),
|
||||
maximum_created_date: toUnix(maximum_created_date),
|
||||
};
|
||||
|
||||
return await CopperApiService.fetchCompanies(context.auth, payload)
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,311 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { CopperAuth, toUnix } from '../common/constants';
|
||||
import {
|
||||
MultiCustomerSourceDropdown,
|
||||
MultiLeadStatusDropdown,
|
||||
multiUsersDropdown,
|
||||
} from '../common/props';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
|
||||
export const searchForALead = createAction({
|
||||
auth: CopperAuth,
|
||||
name: 'searchForALead',
|
||||
displayName: 'Search for a Lead',
|
||||
description: 'Lookup a lead using match criteria.',
|
||||
props: {
|
||||
name: Property.ShortText({
|
||||
displayName: 'Name',
|
||||
description: 'Full name of the Lead to search for.',
|
||||
required: false,
|
||||
}),
|
||||
phone_number: Property.ShortText({
|
||||
displayName: 'Phone Number',
|
||||
description: 'Phone Number of the Lead to search for.',
|
||||
required: false,
|
||||
}),
|
||||
emails: Property.ShortText({
|
||||
displayName: 'Emails',
|
||||
required: false,
|
||||
description: 'Emails of the Lead to search for. ',
|
||||
}),
|
||||
assignee_ids: multiUsersDropdown({ refreshers: ['auth'] }),
|
||||
status_ids: MultiLeadStatusDropdown({}),
|
||||
customer_source_ids: MultiCustomerSourceDropdown({}),
|
||||
city: Property.ShortText({
|
||||
displayName: 'City',
|
||||
description: 'The city in which Lead must be located.',
|
||||
required: false,
|
||||
}),
|
||||
state: Property.ShortText({
|
||||
displayName: 'State',
|
||||
description: 'The state or province in which Lead must be located.',
|
||||
required: false,
|
||||
}),
|
||||
postal_code: Property.ShortText({
|
||||
displayName: 'Postal Code',
|
||||
description: 'The postal code in which Lead must be located.',
|
||||
required: false,
|
||||
}),
|
||||
country: Property.ShortText({
|
||||
displayName: 'Country',
|
||||
description: 'The two character country code where Lead must be located.',
|
||||
required: false,
|
||||
}),
|
||||
tags: Property.Array({
|
||||
displayName: 'Tags',
|
||||
description:
|
||||
'Filter Lead to those that match at least one of the tags specified.',
|
||||
required: false,
|
||||
defaultValue: [],
|
||||
}),
|
||||
socials: Property.Array({
|
||||
displayName: 'Socials',
|
||||
description:
|
||||
'Filter Lead to those that match at least one of the social accounts specified.',
|
||||
required: false,
|
||||
defaultValue: [],
|
||||
}),
|
||||
followed: Property.StaticDropdown({
|
||||
displayName: 'Followed',
|
||||
description: 'Filter by followed state',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{
|
||||
label: 'followed',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: 'not followed',
|
||||
value: '2',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
age: Property.Number({
|
||||
displayName: 'Age',
|
||||
description: 'The maximum age in seconds that Lead must be.',
|
||||
required: false,
|
||||
}),
|
||||
page_size: Property.Number({
|
||||
displayName: 'Page Size',
|
||||
description: 'Default 50. Max 200.',
|
||||
required: false,
|
||||
defaultValue: 50,
|
||||
}),
|
||||
page_number: Property.Number({
|
||||
displayName: 'Page Number',
|
||||
required: false,
|
||||
defaultValue: 1,
|
||||
}),
|
||||
sort_by: Property.StaticDropdown({
|
||||
displayName: 'Sort By',
|
||||
description: 'The field on which to sort the results',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{
|
||||
label: 'Name',
|
||||
value: 'name',
|
||||
},
|
||||
{
|
||||
label: 'Company Name',
|
||||
value: 'company_name',
|
||||
},
|
||||
{
|
||||
label: 'Title',
|
||||
value: 'title',
|
||||
},
|
||||
{
|
||||
label: 'Value',
|
||||
value: 'value',
|
||||
},
|
||||
{
|
||||
label: 'Email',
|
||||
value: 'email',
|
||||
},
|
||||
{
|
||||
label: 'Phone',
|
||||
value: 'phone',
|
||||
},
|
||||
{
|
||||
label: 'Date Modified',
|
||||
value: 'date_modified',
|
||||
},
|
||||
{
|
||||
label: 'Date Created',
|
||||
value: 'date_created',
|
||||
},
|
||||
{
|
||||
label: 'City',
|
||||
value: 'city',
|
||||
},
|
||||
{
|
||||
label: 'State',
|
||||
value: 'state',
|
||||
},
|
||||
{
|
||||
label: 'Country',
|
||||
value: 'country',
|
||||
},
|
||||
{
|
||||
label: 'Zip',
|
||||
value: 'zip',
|
||||
},
|
||||
{
|
||||
label: 'Inactive Days',
|
||||
value: 'inactive_days',
|
||||
},
|
||||
{
|
||||
label: 'Socials',
|
||||
value: 'socials',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
sort_direction: Property.StaticDropdown({
|
||||
displayName: 'Sort Direction',
|
||||
description: 'The direction in which to sort the result',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{
|
||||
label: 'Ascending',
|
||||
value: 'asc',
|
||||
},
|
||||
{
|
||||
label: 'Descending',
|
||||
value: 'desc',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
include_converted_leads: Property.Checkbox({
|
||||
displayName: 'Include Converted Leads',
|
||||
description: 'Specify if response should contain converted leads.',
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
}),
|
||||
minimum_monetary_value: Property.Number({
|
||||
displayName: 'Minimum Monetary Value',
|
||||
required: false,
|
||||
description: 'The minimum monetary value Leads must have.',
|
||||
}),
|
||||
maximum_monetary_value: Property.Number({
|
||||
displayName: 'Maximum Monetary Value',
|
||||
required: false,
|
||||
description: 'The maximum monetary value Leads must have.',
|
||||
}),
|
||||
minimum_interaction_count: Property.Number({
|
||||
displayName: 'Minimum Interaction Count',
|
||||
required: false,
|
||||
description: 'The minimum number of interactions Lead must have had.',
|
||||
}),
|
||||
maximum_interaction_count: Property.Number({
|
||||
displayName: 'Maximum Interaction Count',
|
||||
required: false,
|
||||
description: 'The maximum number of interactions Lead must have had.',
|
||||
}),
|
||||
minimum_interaction_date: Property.DateTime({
|
||||
displayName: 'Minimum Interaction Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the earliest date of the last interaction.',
|
||||
}),
|
||||
maximum_interaction_date: Property.DateTime({
|
||||
displayName: 'Maximum Interaction Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the latest date of the last interaction.',
|
||||
}),
|
||||
minimum_created_date: Property.DateTime({
|
||||
displayName: 'Minimum Created Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the earliest date Lead are created.',
|
||||
}),
|
||||
maximum_created_date: Property.DateTime({
|
||||
displayName: 'Maximum Created Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the latest date Lead are Created.',
|
||||
}),
|
||||
minimum_modified_date: Property.DateTime({
|
||||
displayName: 'Minimum Modified Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the earliest date Lead are Modified.',
|
||||
}),
|
||||
maximum_modified_date: Property.DateTime({
|
||||
displayName: 'Maximum Modified Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the latest date Lead are Modified.',
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
name,
|
||||
phone_number,
|
||||
emails,
|
||||
assignee_ids,
|
||||
status_ids,
|
||||
customer_source_ids,
|
||||
city,
|
||||
state,
|
||||
postal_code,
|
||||
country,
|
||||
tags,
|
||||
socials,
|
||||
followed,
|
||||
age,
|
||||
page_size,
|
||||
page_number,
|
||||
sort_by,
|
||||
sort_direction,
|
||||
minimum_interaction_count,
|
||||
maximum_interaction_count,
|
||||
minimum_interaction_date,
|
||||
maximum_interaction_date,
|
||||
minimum_created_date,
|
||||
maximum_created_date,
|
||||
minimum_monetary_value,
|
||||
maximum_monetary_value,
|
||||
minimum_modified_date,
|
||||
maximum_modified_date,
|
||||
} = context.propsValue;
|
||||
|
||||
const payload = {
|
||||
name,
|
||||
phone_number,
|
||||
emails,
|
||||
assignee_ids,
|
||||
status_ids,
|
||||
customer_source_ids,
|
||||
city,
|
||||
state,
|
||||
postal_code,
|
||||
country,
|
||||
tags,
|
||||
socials,
|
||||
followed,
|
||||
age,
|
||||
page_size,
|
||||
page_number,
|
||||
sort_by,
|
||||
sort_direction,
|
||||
minimum_interaction_count,
|
||||
maximum_interaction_count,
|
||||
minimum_interaction_date: toUnix(minimum_interaction_date),
|
||||
maximum_interaction_date: toUnix(maximum_interaction_date),
|
||||
minimum_created_date: toUnix(minimum_created_date),
|
||||
maximum_created_date: toUnix(maximum_created_date),
|
||||
minimum_monetary_value,
|
||||
maximum_monetary_value,
|
||||
minimum_modified_date: toUnix(minimum_modified_date),
|
||||
maximum_modified_date: toUnix(maximum_modified_date),
|
||||
};
|
||||
|
||||
return await CopperApiService.fetchLeads(context.auth, payload);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,269 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { CopperAuth, toUnix } from '../common/constants';
|
||||
import {
|
||||
multiCompanyDropdown,
|
||||
MultiContactTypesDropdown,
|
||||
multiOpportunityDropdown,
|
||||
multiUsersDropdown,
|
||||
} from '../common/props';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
|
||||
export const searchForAPerson = createAction({
|
||||
auth: CopperAuth,
|
||||
name: 'searchForAPerson',
|
||||
displayName: 'Search for a Person',
|
||||
description: 'Lookup a person using match criteria.',
|
||||
props: {
|
||||
name: Property.ShortText({
|
||||
displayName: 'Name',
|
||||
description: 'Full name of the People to search for.',
|
||||
required: false,
|
||||
}),
|
||||
phone_number: Property.ShortText({
|
||||
displayName: 'Phone Number',
|
||||
description: 'Phone Number of the People to search for.',
|
||||
required: false,
|
||||
}),
|
||||
emails: Property.Array({
|
||||
displayName: 'Emails',
|
||||
required: false,
|
||||
description: 'Emails of the People to search for.',
|
||||
defaultValue: [],
|
||||
}),
|
||||
contact_type_ids: MultiContactTypesDropdown({}),
|
||||
assignee_ids: multiUsersDropdown({ refreshers: ['auth'] }),
|
||||
company_ids: multiCompanyDropdown({ refreshers: ['auth'] }),
|
||||
opportunity_ids: multiOpportunityDropdown({ refreshers: ['auth'] }),
|
||||
city: Property.ShortText({
|
||||
displayName: 'City',
|
||||
description: 'The city in which People must be located.',
|
||||
required: false,
|
||||
}),
|
||||
state: Property.ShortText({
|
||||
displayName: 'State',
|
||||
description: 'The state or province in which People must be located.',
|
||||
required: false,
|
||||
}),
|
||||
postal_code: Property.ShortText({
|
||||
displayName: 'Postal Code',
|
||||
description: 'The postal code in which People must be located.',
|
||||
required: false,
|
||||
}),
|
||||
country: Property.ShortText({
|
||||
displayName: 'Country',
|
||||
description:
|
||||
'The two character country code where People must be located.',
|
||||
required: false,
|
||||
}),
|
||||
tags: Property.Array({
|
||||
displayName: 'Tags',
|
||||
description:
|
||||
'Filter People to those that match at least one of the tags specified.',
|
||||
required: false,
|
||||
defaultValue: [],
|
||||
}),
|
||||
socials: Property.Array({
|
||||
displayName: 'Socials',
|
||||
description:
|
||||
'Filter People to those that match at least one of the social accounts specified.',
|
||||
required: false,
|
||||
defaultValue: [],
|
||||
}),
|
||||
followed: Property.StaticDropdown({
|
||||
displayName: 'Followed',
|
||||
description: 'Filter by followed state',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{
|
||||
label: 'followed',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: 'not followed',
|
||||
value: '2',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
age: Property.Number({
|
||||
displayName: 'Age',
|
||||
description: 'The maximum age in seconds that People must be.',
|
||||
required: false,
|
||||
}),
|
||||
page_size: Property.Number({
|
||||
displayName: 'Page Size',
|
||||
description: 'Default 50. Max 200.',
|
||||
required: false,
|
||||
defaultValue: 50,
|
||||
}),
|
||||
page_number: Property.Number({
|
||||
displayName: 'Page Number',
|
||||
required: false,
|
||||
defaultValue: 1,
|
||||
}),
|
||||
sort_by: Property.StaticDropdown({
|
||||
displayName: 'Sort By',
|
||||
description: 'The field on which to sort the results',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{
|
||||
label: 'Name',
|
||||
value: 'name',
|
||||
},
|
||||
{
|
||||
label: 'Title',
|
||||
value: 'title',
|
||||
},
|
||||
{
|
||||
label: 'Email',
|
||||
value: 'email',
|
||||
},
|
||||
{
|
||||
label: 'Phone',
|
||||
value: 'phone',
|
||||
},
|
||||
{
|
||||
label: 'Date Modified',
|
||||
value: 'date_modified',
|
||||
},
|
||||
{
|
||||
label: 'Date Created',
|
||||
value: 'date_created',
|
||||
},
|
||||
{
|
||||
label: 'City',
|
||||
value: 'city',
|
||||
},
|
||||
{
|
||||
label: 'State',
|
||||
value: 'state',
|
||||
},
|
||||
{
|
||||
label: 'Country',
|
||||
value: 'country',
|
||||
},
|
||||
{
|
||||
label: 'Zip',
|
||||
value: 'zip',
|
||||
},
|
||||
{
|
||||
label: 'Socials',
|
||||
value: 'socials',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
sort_direction: Property.StaticDropdown({
|
||||
displayName: 'Sort Direction',
|
||||
description: 'The direction in which to sort the result',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{
|
||||
label: 'Ascending',
|
||||
value: 'asc',
|
||||
},
|
||||
{
|
||||
label: 'Descending',
|
||||
value: 'desc',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
minimum_interaction_count: Property.Number({
|
||||
displayName: 'Minimum Interaction Count',
|
||||
required: false,
|
||||
description: 'The minimum number of interactions People must have had.',
|
||||
}),
|
||||
maximum_interaction_count: Property.Number({
|
||||
displayName: 'Maximum Interaction Count',
|
||||
required: false,
|
||||
description: 'The maximum number of interactions People must have had.',
|
||||
}),
|
||||
minimum_interaction_date: Property.DateTime({
|
||||
displayName: 'Minimum Interaction Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the earliest date of the last interaction.',
|
||||
}),
|
||||
maximum_interaction_date: Property.DateTime({
|
||||
displayName: 'Maximum Interaction Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the latest date of the last interaction.',
|
||||
}),
|
||||
minimum_created_date: Property.DateTime({
|
||||
displayName: 'Minimum Created Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the earliest date People are created.',
|
||||
}),
|
||||
maximum_created_date: Property.DateTime({
|
||||
displayName: 'Maximum Created Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the latest date People are Created.',
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
name,
|
||||
phone_number,
|
||||
emails,
|
||||
contact_type_ids,
|
||||
assignee_ids,
|
||||
company_ids,
|
||||
opportunity_ids,
|
||||
city,
|
||||
state,
|
||||
postal_code,
|
||||
country,
|
||||
tags,
|
||||
socials,
|
||||
followed,
|
||||
age,
|
||||
page_size,
|
||||
page_number,
|
||||
sort_by,
|
||||
sort_direction,
|
||||
minimum_interaction_count,
|
||||
maximum_interaction_count,
|
||||
minimum_interaction_date,
|
||||
maximum_interaction_date,
|
||||
minimum_created_date,
|
||||
maximum_created_date,
|
||||
} = context.propsValue;
|
||||
|
||||
const payload = {
|
||||
name,
|
||||
phone_number,
|
||||
emails,
|
||||
contact_type_ids,
|
||||
assignee_ids,
|
||||
company_ids,
|
||||
opportunity_ids,
|
||||
city,
|
||||
state,
|
||||
postal_code,
|
||||
country,
|
||||
tags,
|
||||
socials,
|
||||
followed,
|
||||
age,
|
||||
page_size,
|
||||
page_number,
|
||||
sort_by,
|
||||
sort_direction,
|
||||
minimum_interaction_count,
|
||||
maximum_interaction_count,
|
||||
minimum_interaction_date: toUnix(minimum_interaction_date),
|
||||
maximum_interaction_date: toUnix(maximum_interaction_date),
|
||||
minimum_created_date: toUnix(minimum_created_date),
|
||||
maximum_created_date: toUnix(maximum_created_date),
|
||||
};
|
||||
|
||||
return await CopperApiService.fetchPeople(context.auth, payload);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,180 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { CopperAuth, toUnix } from '../common/constants';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
import { multiUsersDropdown } from '../common/props';
|
||||
|
||||
export const searchForAProject = createAction({
|
||||
auth: CopperAuth,
|
||||
name: 'searchForAProject',
|
||||
displayName: 'Search for a Project',
|
||||
description: 'Lookup a project.',
|
||||
props: {
|
||||
name: Property.ShortText({
|
||||
displayName: 'Name',
|
||||
description: 'Full name of the Opportunity to search for.',
|
||||
required: false,
|
||||
}),
|
||||
assignee_ids: multiUsersDropdown({ refreshers: ['auth'] }),
|
||||
statuses: Property.StaticMultiSelectDropdown({
|
||||
displayName: 'Status',
|
||||
description: 'Filter by Opportunity status',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{
|
||||
label: 'Open',
|
||||
value: 'Open',
|
||||
},
|
||||
{
|
||||
label: 'Completed',
|
||||
value: 'Completed',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
tags: Property.Array({
|
||||
displayName: 'Tags',
|
||||
description:
|
||||
'Filter People to those that match at least one of the tags specified.',
|
||||
required: false,
|
||||
defaultValue: [],
|
||||
}),
|
||||
followed: Property.StaticDropdown({
|
||||
displayName: 'Followed',
|
||||
description: 'Filter by followed state',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{
|
||||
label: 'followed',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: 'not followed',
|
||||
value: '2',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
page_size: Property.Number({
|
||||
displayName: 'Page Size',
|
||||
description: 'Default 50. Max 200.',
|
||||
required: false,
|
||||
defaultValue: 50,
|
||||
}),
|
||||
page_number: Property.Number({
|
||||
displayName: 'Page Number',
|
||||
required: false,
|
||||
defaultValue: 1,
|
||||
}),
|
||||
sort_by: Property.StaticDropdown({
|
||||
displayName: 'Sort By',
|
||||
description: 'The field on which to sort the results',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{
|
||||
label: 'Name',
|
||||
value: 'name',
|
||||
},
|
||||
{
|
||||
label: 'Assigned To',
|
||||
value: 'assigned_to',
|
||||
},
|
||||
{
|
||||
label: 'Related To',
|
||||
value: 'related_to',
|
||||
},
|
||||
{
|
||||
label: 'Status',
|
||||
value: 'status',
|
||||
},
|
||||
{
|
||||
label: 'Date Modified',
|
||||
value: 'date_modified',
|
||||
},
|
||||
{
|
||||
label: 'Date Created',
|
||||
value: 'date_created',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
sort_direction: Property.StaticDropdown({
|
||||
displayName: 'Sort Direction',
|
||||
description: 'The direction in which to sort the result',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{
|
||||
label: 'Ascending',
|
||||
value: 'asc',
|
||||
},
|
||||
{
|
||||
label: 'Descending',
|
||||
value: 'desc',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
minimum_created_date: Property.DateTime({
|
||||
displayName: 'Minimum Created Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the earliest date Opportunity are created.',
|
||||
}),
|
||||
maximum_created_date: Property.DateTime({
|
||||
displayName: 'Maximum Created Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the latest date Opportunity are Created.',
|
||||
}),
|
||||
minimum_modified_date: Property.DateTime({
|
||||
displayName: 'Minimum Modified Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the earliest date Opportunity are Modified.',
|
||||
}),
|
||||
maximum_modified_date: Property.DateTime({
|
||||
displayName: 'Maximum Modified Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the latest date Opportunity are Modified.',
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
name,
|
||||
assignee_ids,
|
||||
statuses,
|
||||
tags,
|
||||
followed,
|
||||
page_size,
|
||||
page_number,
|
||||
sort_by,
|
||||
sort_direction,
|
||||
minimum_created_date,
|
||||
maximum_created_date,
|
||||
minimum_modified_date,
|
||||
maximum_modified_date,
|
||||
} = context.propsValue;
|
||||
|
||||
const payload = {
|
||||
name,
|
||||
assignee_ids: assignee_ids || [],
|
||||
statuses,
|
||||
followed,
|
||||
page_size,
|
||||
page_number,
|
||||
sort_by,
|
||||
tags,
|
||||
sort_direction,
|
||||
minimum_created_date: toUnix(minimum_created_date),
|
||||
maximum_created_date: toUnix(maximum_created_date),
|
||||
minimum_modified_date: toUnix(minimum_modified_date),
|
||||
maximum_modified_date: toUnix(maximum_modified_date),
|
||||
};
|
||||
|
||||
return await CopperApiService.fetchProjects(context.auth, payload);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,157 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { CopperAuth, CopperAuthType, toUnix } from '../common/constants';
|
||||
import { MultiActivityTypesDropdown } from '../common/props';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
|
||||
export const searchForAnActivity = createAction({
|
||||
auth: CopperAuth,
|
||||
name: 'searchForAnActivity',
|
||||
displayName: 'Search for an Activity',
|
||||
description: 'Find an existing activity by type/criteria.',
|
||||
props: {
|
||||
entity: Property.StaticDropdown({
|
||||
displayName: 'Parent Entity',
|
||||
description: 'Select parent entity',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Person', value: 'person' },
|
||||
{ label: 'Company', value: 'company' },
|
||||
{ label: 'Lead', value: 'lead' },
|
||||
{ label: 'Opportunity', value: 'opportunity' },
|
||||
{ label: 'Project', value: 'project' },
|
||||
{ label: 'Task', value: 'task' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
entityItemId: Property.Dropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Parent Entity Resource',
|
||||
description: 'Select Resource',
|
||||
required: false,
|
||||
refreshers: ['auth', 'entity'],
|
||||
async options(propsValue: Record<string, unknown>) {
|
||||
const auth = propsValue['auth'] as CopperAuthType | undefined;
|
||||
const entity = propsValue['entity'] as
|
||||
| 'person'
|
||||
| 'company'
|
||||
| 'lead'
|
||||
| 'opportunity'
|
||||
| 'project'
|
||||
| 'task';
|
||||
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (!entity) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Select a Parent Entity first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
const fetchFnMap = {
|
||||
person: CopperApiService.fetchPeople,
|
||||
company: CopperApiService.fetchCompanies,
|
||||
lead: CopperApiService.fetchLeads,
|
||||
opportunity: CopperApiService.fetchOpportunities,
|
||||
task: CopperApiService.fetchTasks,
|
||||
project: CopperApiService.fetchProjects,
|
||||
};
|
||||
|
||||
const fetchFn = fetchFnMap[entity];
|
||||
|
||||
try {
|
||||
const items = await fetchFn(auth);
|
||||
|
||||
return {
|
||||
options: items.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch entity items', e);
|
||||
return {
|
||||
options: [],
|
||||
placeholder: 'Unable to load entity items',
|
||||
};
|
||||
}
|
||||
},
|
||||
}),
|
||||
activity_types: MultiActivityTypesDropdown({}),
|
||||
page_size: Property.Number({
|
||||
displayName: 'Page Size',
|
||||
description: 'Default 50. Max 200.',
|
||||
required: false,
|
||||
defaultValue: 50,
|
||||
}),
|
||||
page_number: Property.Number({
|
||||
displayName: 'Page Number',
|
||||
required: false,
|
||||
defaultValue: 1,
|
||||
}),
|
||||
minimum_activity_date: Property.DateTime({
|
||||
displayName: 'Minimum Activity Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 11:40. The timestamp of the earliest activity date.',
|
||||
}),
|
||||
maximum_activity_date: Property.DateTime({
|
||||
displayName: 'Maximum Activity Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the latest activity date.',
|
||||
}),
|
||||
full_result: Property.Checkbox({
|
||||
displayName: 'Full Result',
|
||||
description:
|
||||
'(Optional) If set, search performance improves but duplicate activity logs may be returned',
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
}),
|
||||
},
|
||||
async run(ctx) {
|
||||
const {
|
||||
page_size,
|
||||
page_number,
|
||||
activity_types,
|
||||
entity,
|
||||
entityItemId,
|
||||
minimum_activity_date,
|
||||
maximum_activity_date,
|
||||
full_result,
|
||||
} = ctx.propsValue;
|
||||
|
||||
const parsed_activity_types = (activity_types || []).map(
|
||||
(activity: any) => {
|
||||
const parsed_activity = JSON.parse(activity);
|
||||
return {
|
||||
id: parsed_activity.id,
|
||||
category: parsed_activity.category,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const payload = {
|
||||
...(entity &&
|
||||
entityItemId && {
|
||||
parent: { id: entityItemId, type: entity },
|
||||
}),
|
||||
activity_types: parsed_activity_types,
|
||||
page_number,
|
||||
page_size,
|
||||
minimum_activity_date: toUnix(minimum_activity_date),
|
||||
maximum_activity_date: toUnix(maximum_activity_date),
|
||||
full_result,
|
||||
};
|
||||
|
||||
return await CopperApiService.fetchActivities(ctx.auth, payload);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,381 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { CopperAuth, toUnix } from '../common/constants';
|
||||
import {
|
||||
multiCompanyDropdown,
|
||||
MultiCustomerSourceDropdown,
|
||||
MultiLossReasonsDropdown,
|
||||
multiPipelinesDropdown,
|
||||
multiPrimaryContactsDropdown,
|
||||
multiUsersDropdown,
|
||||
pipelinesDropdown,
|
||||
} from '../common/props';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
|
||||
export const searchForAnOpportunity = createAction({
|
||||
auth: CopperAuth,
|
||||
name: 'searchForAnOpportunity',
|
||||
displayName: 'Search for an Opportunity',
|
||||
description: 'Lookup an opportunity.',
|
||||
props: {
|
||||
name: Property.ShortText({
|
||||
displayName: 'Name',
|
||||
description: 'Full name of the Opportunity to search for.',
|
||||
required: false,
|
||||
}),
|
||||
assignee_ids: multiUsersDropdown({ refreshers: ['auth'] }),
|
||||
company_ids: multiCompanyDropdown({ refreshers: ['auth'] }),
|
||||
status_ids: Property.StaticMultiSelectDropdown({
|
||||
displayName: 'Status',
|
||||
description: 'Filter by Opportunity status',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{
|
||||
label: 'Open',
|
||||
value: '0',
|
||||
},
|
||||
{
|
||||
label: 'Won',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: 'Lost',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
label: 'Abandoned',
|
||||
value: '3',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
priorities: Property.StaticMultiSelectDropdown({
|
||||
displayName: 'Priority',
|
||||
required: false,
|
||||
options: {
|
||||
disabled: false,
|
||||
options: [
|
||||
{ label: 'None', value: 'None' },
|
||||
{ label: 'Low', value: 'Low' },
|
||||
{ label: 'Medium', value: 'Medium' },
|
||||
{ label: 'High', value: 'High' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
pipeline_ids: multiPipelinesDropdown({ refreshers: ['auth'] }),
|
||||
pipeline_stage_ids: Property.MultiSelectDropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Pipeline Stage',
|
||||
description: 'Select a stage',
|
||||
refreshers: ['auth', 'pipeline_ids'],
|
||||
required: false,
|
||||
async options({ auth, pipeline_ids }: any) {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (!pipeline_ids) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Select a pipeline first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
const stages = pipeline_ids.flatMap((pipeline: any) => {
|
||||
const stages = JSON.parse(pipeline).stages ?? [];
|
||||
return stages;
|
||||
});
|
||||
|
||||
return {
|
||||
options: stages.map((stage: any) => ({
|
||||
label: stage.name,
|
||||
value: stage.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
primary_contact_ids: multiPrimaryContactsDropdown({ refreshers: ['auth'] }),
|
||||
customer_source_ids: MultiCustomerSourceDropdown({}),
|
||||
loss_reason_ids: MultiLossReasonsDropdown({}),
|
||||
tags: Property.Array({
|
||||
displayName: 'Tags',
|
||||
description:
|
||||
'Filter People to those that match at least one of the tags specified.',
|
||||
required: false,
|
||||
defaultValue: [],
|
||||
}),
|
||||
followed: Property.StaticDropdown({
|
||||
displayName: 'Followed',
|
||||
description: 'Filter by followed state',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{
|
||||
label: 'followed',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: 'not followed',
|
||||
value: '2',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
page_size: Property.Number({
|
||||
displayName: 'Page Size',
|
||||
description: 'Default 50. Max 200.',
|
||||
required: false,
|
||||
defaultValue: 50,
|
||||
}),
|
||||
page_number: Property.Number({
|
||||
displayName: 'Page Number',
|
||||
required: false,
|
||||
defaultValue: 1,
|
||||
}),
|
||||
sort_by: Property.StaticDropdown({
|
||||
displayName: 'Sort By',
|
||||
description: 'The field on which to sort the results',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{
|
||||
label: 'Assignee',
|
||||
value: 'assignee',
|
||||
},
|
||||
{
|
||||
label: 'Company Name',
|
||||
value: 'company_name',
|
||||
},
|
||||
{
|
||||
label: 'Customer Source ID',
|
||||
value: 'customer_source_id',
|
||||
},
|
||||
{
|
||||
label: 'Date Created',
|
||||
value: 'date_created',
|
||||
},
|
||||
{
|
||||
label: 'Date Modified',
|
||||
value: 'date_modified',
|
||||
},
|
||||
{
|
||||
label: 'Inactive Days',
|
||||
value: 'inactive_days',
|
||||
},
|
||||
{
|
||||
label: 'Interaction Count',
|
||||
value: 'interaction_count',
|
||||
},
|
||||
{
|
||||
label: 'Last Interaction',
|
||||
value: 'last_interaction',
|
||||
},
|
||||
{
|
||||
label: 'Monetary Unit',
|
||||
value: 'monetary_unit',
|
||||
},
|
||||
{
|
||||
label: 'Monetary Value',
|
||||
value: 'monetary_value',
|
||||
},
|
||||
{
|
||||
label: 'Name',
|
||||
value: 'name',
|
||||
},
|
||||
{
|
||||
label: 'Primary Contact',
|
||||
value: 'primary_contact',
|
||||
},
|
||||
{
|
||||
label: 'Priority',
|
||||
value: 'priority',
|
||||
},
|
||||
{
|
||||
label: 'Stage',
|
||||
value: 'stage',
|
||||
},
|
||||
{
|
||||
label: 'Status',
|
||||
value: 'status',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
sort_direction: Property.StaticDropdown({
|
||||
displayName: 'Sort Direction',
|
||||
description: 'The direction in which to sort the result',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{
|
||||
label: 'Ascending',
|
||||
value: 'asc',
|
||||
},
|
||||
{
|
||||
label: 'Descending',
|
||||
value: 'desc',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
minimum_monetary_value: Property.Number({
|
||||
displayName: 'Minimum Monetary Value',
|
||||
required: false,
|
||||
description: 'The minimum monetary value Opportunities must have.',
|
||||
}),
|
||||
maximum_monetary_value: Property.Number({
|
||||
displayName: 'Maximum Monetary Value',
|
||||
required: false,
|
||||
description: 'The maximum monetary value Opportunities must have.',
|
||||
}),
|
||||
minimum_interaction_count: Property.Number({
|
||||
displayName: 'Minimum Interaction Count',
|
||||
required: false,
|
||||
description:
|
||||
'The minimum number of interactions Opportunity must have had.',
|
||||
}),
|
||||
maximum_interaction_count: Property.Number({
|
||||
displayName: 'Maximum Interaction Count',
|
||||
required: false,
|
||||
description:
|
||||
'The maximum number of interactions Opportunity must have had.',
|
||||
}),
|
||||
minimum_close_date: Property.DateTime({
|
||||
displayName: 'Minimum Interaction Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the earliest date close date.',
|
||||
}),
|
||||
maximum_close_date: Property.DateTime({
|
||||
displayName: 'Maximum Interaction Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the latest date close date.',
|
||||
}),
|
||||
minimum_interaction_date: Property.DateTime({
|
||||
displayName: 'Minimum Interaction Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the earliest date of the last interaction.',
|
||||
}),
|
||||
maximum_interaction_date: Property.DateTime({
|
||||
displayName: 'Maximum Interaction Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the latest date of the last interaction.',
|
||||
}),
|
||||
minimum_stage_change_date: Property.DateTime({
|
||||
displayName: 'Minimum Stage Change Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the earliest date of a stage change.',
|
||||
}),
|
||||
maximum_stage_change_date: Property.DateTime({
|
||||
displayName: 'Maximum Stage Change Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the latest date of a stage change.',
|
||||
}),
|
||||
minimum_created_date: Property.DateTime({
|
||||
displayName: 'Minimum Created Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the earliest date Opportunity are created.',
|
||||
}),
|
||||
maximum_created_date: Property.DateTime({
|
||||
displayName: 'Maximum Created Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the latest date Opportunity are Created.',
|
||||
}),
|
||||
minimum_modified_date: Property.DateTime({
|
||||
displayName: 'Minimum Modified Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the earliest date Opportunity are Modified.',
|
||||
}),
|
||||
maximum_modified_date: Property.DateTime({
|
||||
displayName: 'Maximum Modified Date',
|
||||
required: false,
|
||||
description:
|
||||
'24-hour format, e.g. 2025-09-10 13:00. The timestamp of the latest date Opportunity are Modified.',
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
name,
|
||||
assignee_ids,
|
||||
status_ids,
|
||||
customer_source_ids,
|
||||
pipeline_ids,
|
||||
pipeline_stage_ids,
|
||||
company_ids,
|
||||
tags,
|
||||
followed,
|
||||
page_size,
|
||||
page_number,
|
||||
sort_by,
|
||||
sort_direction,
|
||||
priorities,
|
||||
minimum_interaction_count,
|
||||
maximum_interaction_count,
|
||||
minimum_interaction_date,
|
||||
maximum_interaction_date,
|
||||
minimum_created_date,
|
||||
maximum_created_date,
|
||||
minimum_monetary_value,
|
||||
maximum_monetary_value,
|
||||
minimum_modified_date,
|
||||
maximum_modified_date,
|
||||
minimum_close_date,
|
||||
minimum_stage_change_date,
|
||||
maximum_close_date,
|
||||
maximum_stage_change_date,
|
||||
primary_contact_ids,
|
||||
loss_reason_ids,
|
||||
} = context.propsValue;
|
||||
|
||||
const payload = {
|
||||
name,
|
||||
assignee_ids,
|
||||
status_ids,
|
||||
customer_source_ids,
|
||||
pipeline_ids: (pipeline_ids ?? []).flatMap((pipeline: any) => {
|
||||
const id = JSON.parse(pipeline).id ?? [];
|
||||
return id;
|
||||
}),
|
||||
pipeline_stage_ids,
|
||||
company_ids,
|
||||
primary_contact_ids,
|
||||
loss_reason_ids,
|
||||
tags,
|
||||
priorities,
|
||||
followed,
|
||||
page_size,
|
||||
page_number,
|
||||
sort_by,
|
||||
sort_direction,
|
||||
minimum_interaction_count,
|
||||
maximum_interaction_count,
|
||||
minimum_interaction_date: toUnix(minimum_interaction_date),
|
||||
maximum_interaction_date: toUnix(maximum_interaction_date),
|
||||
minimum_created_date: toUnix(minimum_created_date),
|
||||
maximum_created_date: toUnix(maximum_created_date),
|
||||
minimum_monetary_value,
|
||||
maximum_monetary_value,
|
||||
minimum_modified_date: toUnix(minimum_modified_date),
|
||||
maximum_modified_date: toUnix(maximum_modified_date),
|
||||
minimum_close_date: toUnix(minimum_close_date),
|
||||
minimum_stage_change_date: toUnix(minimum_stage_change_date),
|
||||
maximum_close_date: toUnix(maximum_close_date),
|
||||
maximum_stage_change_date: toUnix(maximum_stage_change_date),
|
||||
};
|
||||
|
||||
return await CopperApiService.fetchOpportunities(context.auth, payload);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,151 @@
|
||||
import {
|
||||
createAction,
|
||||
InputPropertyMap,
|
||||
Property,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { CopperAuth, isNonEmptyStr } from '../common/constants';
|
||||
import { companyDropdown, primaryContactsDropdown } from '../common/props';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
|
||||
export const updateCompany = createAction({
|
||||
auth: CopperAuth,
|
||||
name: 'updateCompany',
|
||||
displayName: 'Update Company',
|
||||
description: 'Updates a company record.',
|
||||
props: {
|
||||
companyId: companyDropdown({ refreshers: ['auth'], required: true }),
|
||||
fields: Property.DynamicProperties({
|
||||
auth: CopperAuth,
|
||||
displayName: '',
|
||||
description: '',
|
||||
refreshers: ['auth', 'companyId'],
|
||||
required: false,
|
||||
props: async ({ auth, companyId }: any): Promise<InputPropertyMap> => {
|
||||
if (!auth || !companyId) return {};
|
||||
|
||||
const company = JSON.parse(companyId);
|
||||
|
||||
return {
|
||||
name: Property.ShortText({
|
||||
displayName: 'Full Name',
|
||||
required: true,
|
||||
defaultValue: company.name,
|
||||
}),
|
||||
email_domain: Property.ShortText({
|
||||
displayName: 'Email Domain',
|
||||
description: 'E.g. democompany.com',
|
||||
required: false,
|
||||
defaultValue: company.email_domain,
|
||||
}),
|
||||
details: Property.ShortText({
|
||||
displayName: 'Details',
|
||||
required: false,
|
||||
defaultValue: company.details,
|
||||
}),
|
||||
phone_numbers: Property.Array({
|
||||
displayName: 'Phone Numbers',
|
||||
required: false,
|
||||
properties: {
|
||||
number: Property.ShortText({
|
||||
displayName: 'Number',
|
||||
required: true,
|
||||
}),
|
||||
category: Property.ShortText({
|
||||
displayName: 'Category (e.g., mobile, work, personal)',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
defaultValue: company.phone_numbers,
|
||||
}),
|
||||
address_street: Property.ShortText({
|
||||
displayName: 'Street',
|
||||
required: false,
|
||||
defaultValue: company.address?.street,
|
||||
}),
|
||||
address_city: Property.ShortText({
|
||||
displayName: 'City',
|
||||
required: false,
|
||||
defaultValue: company.address?.city,
|
||||
}),
|
||||
address_state: Property.ShortText({
|
||||
displayName: 'State/Region',
|
||||
required: false,
|
||||
defaultValue: company.address?.state,
|
||||
}),
|
||||
address_postal_code: Property.ShortText({
|
||||
displayName: 'Postal Code',
|
||||
required: false,
|
||||
defaultValue: company.address?.postal_code,
|
||||
}),
|
||||
address_country: Property.ShortText({
|
||||
displayName: 'Country',
|
||||
required: false,
|
||||
defaultValue: company.address?.country,
|
||||
}),
|
||||
};
|
||||
},
|
||||
}),
|
||||
primaryContactId: primaryContactsDropdown({ refreshers: ['auth'] }),
|
||||
},
|
||||
async run(context) {
|
||||
const { fields, companyId, primaryContactId } = context.propsValue;
|
||||
|
||||
const company = JSON.parse(companyId as string);
|
||||
|
||||
const {
|
||||
name,
|
||||
email_domain,
|
||||
details,
|
||||
address_street,
|
||||
address_city,
|
||||
address_state,
|
||||
address_postal_code,
|
||||
address_country,
|
||||
phone_numbers,
|
||||
} = fields as any;
|
||||
|
||||
const normPhones = (Array.isArray(phone_numbers) ? phone_numbers : [])
|
||||
.map((p) => ({
|
||||
number: String(p?.number ?? '').trim(),
|
||||
category: isNonEmptyStr(p?.category)
|
||||
? String(p.category).trim()
|
||||
: undefined,
|
||||
}))
|
||||
.filter((p) => isNonEmptyStr(p.number));
|
||||
|
||||
const address = [
|
||||
address_street,
|
||||
address_city,
|
||||
address_state,
|
||||
address_postal_code,
|
||||
address_country,
|
||||
].some(isNonEmptyStr)
|
||||
? {
|
||||
street: isNonEmptyStr(address_street)
|
||||
? address_street.trim()
|
||||
: undefined,
|
||||
city: isNonEmptyStr(address_city) ? address_city.trim() : undefined,
|
||||
state: isNonEmptyStr(address_state)
|
||||
? address_state.trim()
|
||||
: undefined,
|
||||
postal_code: isNonEmptyStr(address_postal_code)
|
||||
? address_postal_code.trim()
|
||||
: undefined,
|
||||
country: isNonEmptyStr(address_country)
|
||||
? address_country.trim()
|
||||
: undefined,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const body: any = {
|
||||
name,
|
||||
email_domain,
|
||||
details,
|
||||
...(normPhones.length ? { phone_numbers: normPhones } : {}),
|
||||
...(address ? { address } : {}),
|
||||
primary_contact_id: primaryContactId,
|
||||
};
|
||||
|
||||
return await CopperApiService.updateCompany(context.auth, company.id, body);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,150 @@
|
||||
import {
|
||||
createAction,
|
||||
InputPropertyMap,
|
||||
Property,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { CopperAuth, isNonEmptyStr } from '../common/constants';
|
||||
import { leadDropdown, peopleDropdown } from '../common/props';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
|
||||
export const updateLead = createAction({
|
||||
auth: CopperAuth,
|
||||
name: 'updateLead',
|
||||
displayName: 'Update Lead',
|
||||
description: 'Updates an existing lead.',
|
||||
props: {
|
||||
leadId: leadDropdown(['auth']),
|
||||
fields: Property.DynamicProperties({
|
||||
displayName: '',
|
||||
description: '',
|
||||
refreshers: ['auth', 'leadId'],
|
||||
required: false,
|
||||
auth: CopperAuth,
|
||||
props: async ({ auth, leadId }) => {
|
||||
if (!auth || !leadId) return {};
|
||||
const lead = JSON.parse(leadId as string);
|
||||
const map:InputPropertyMap= {
|
||||
name: Property.ShortText({
|
||||
displayName: 'Full Name',
|
||||
required: true,
|
||||
defaultValue: lead.name,
|
||||
}),
|
||||
email: Property.ShortText({
|
||||
displayName: 'Email',
|
||||
required: true,
|
||||
defaultValue: lead.email.email,
|
||||
}),
|
||||
category: Property.ShortText({
|
||||
displayName: 'Category of the email address (e.g., work, personal)',
|
||||
required: true,
|
||||
defaultValue: lead.email.category,
|
||||
}),
|
||||
phone_numbers: Property.Array({
|
||||
displayName: 'Phone Numbers',
|
||||
required: false,
|
||||
properties: {
|
||||
number: Property.ShortText({
|
||||
displayName: 'Number',
|
||||
required: true,
|
||||
}),
|
||||
category: Property.ShortText({
|
||||
displayName: 'Category (e.g., mobile, work, personal)',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
defaultValue: lead.phone_numbers,
|
||||
}),
|
||||
address_street: Property.ShortText({
|
||||
displayName: 'Street',
|
||||
required: false,
|
||||
defaultValue: lead.address?.street,
|
||||
}),
|
||||
address_city: Property.ShortText({
|
||||
displayName: 'City',
|
||||
required: false,
|
||||
defaultValue: lead.address?.city,
|
||||
}),
|
||||
address_state: Property.ShortText({
|
||||
displayName: 'State/Region',
|
||||
required: false,
|
||||
defaultValue: lead.address?.state,
|
||||
}),
|
||||
address_postal_code: Property.ShortText({
|
||||
displayName: 'Postal Code',
|
||||
required: false,
|
||||
defaultValue: lead.address?.postal_code,
|
||||
}),
|
||||
address_country: Property.ShortText({
|
||||
displayName: 'Country',
|
||||
required: false,
|
||||
defaultValue: lead.address?.country,
|
||||
}),
|
||||
};
|
||||
return map;
|
||||
},
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const fields = (context.propsValue as any).fields;
|
||||
const leadId = (context.propsValue as any).leadId;
|
||||
|
||||
const lead = JSON.parse(leadId);
|
||||
|
||||
const {
|
||||
name,
|
||||
email,
|
||||
category,
|
||||
address_street,
|
||||
address_city,
|
||||
address_state,
|
||||
address_postal_code,
|
||||
address_country,
|
||||
phone_numbers,
|
||||
} = fields;
|
||||
|
||||
const normPhones = (Array.isArray(phone_numbers) ? phone_numbers : [])
|
||||
.map((p) => ({
|
||||
number: String(p?.number ?? '').trim(),
|
||||
category: isNonEmptyStr(p?.category)
|
||||
? String(p.category).trim()
|
||||
: undefined,
|
||||
}))
|
||||
.filter((p) => isNonEmptyStr(p.number));
|
||||
|
||||
const address = [
|
||||
address_street,
|
||||
address_city,
|
||||
address_state,
|
||||
address_postal_code,
|
||||
address_country,
|
||||
].some(isNonEmptyStr)
|
||||
? {
|
||||
street: isNonEmptyStr(address_street)
|
||||
? address_street.trim()
|
||||
: undefined,
|
||||
city: isNonEmptyStr(address_city) ? address_city.trim() : undefined,
|
||||
state: isNonEmptyStr(address_state)
|
||||
? address_state.trim()
|
||||
: undefined,
|
||||
postal_code: isNonEmptyStr(address_postal_code)
|
||||
? address_postal_code.trim()
|
||||
: undefined,
|
||||
country: isNonEmptyStr(address_country)
|
||||
? address_country.trim()
|
||||
: undefined,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const body: any = {
|
||||
name,
|
||||
email: {
|
||||
email,
|
||||
category,
|
||||
},
|
||||
...(normPhones.length ? { phone_numbers: normPhones } : {}),
|
||||
...(address ? { address } : {}),
|
||||
};
|
||||
|
||||
return await CopperApiService.updateLead(context.auth, lead.id, body);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,109 @@
|
||||
import {
|
||||
createAction,
|
||||
Property,
|
||||
InputPropertyMap,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { CopperAuth } from '../common/constants';
|
||||
import {
|
||||
opportunityDropdown,
|
||||
pipelinesDropdown,
|
||||
primaryContactsDropdown,
|
||||
} from '../common/props';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
|
||||
export const updateOpportunity = createAction({
|
||||
auth: CopperAuth,
|
||||
name: 'updateOpportunity',
|
||||
displayName: 'Update Opportunity',
|
||||
description: 'Updates an opportunity using match criteria.',
|
||||
props: {
|
||||
opportunityId: opportunityDropdown({
|
||||
refreshers: ['auth'],
|
||||
required: true,
|
||||
}),
|
||||
updateFields: Property.DynamicProperties({
|
||||
auth: CopperAuth,
|
||||
displayName: '',
|
||||
description: '',
|
||||
required: false,
|
||||
refreshers: ['auth', 'opportunityId'],
|
||||
props: async ({
|
||||
auth,
|
||||
opportunityId,
|
||||
}: any): Promise<InputPropertyMap> => {
|
||||
if (!auth || !opportunityId) return {};
|
||||
|
||||
const opportunity = JSON.parse(opportunityId);
|
||||
|
||||
return {
|
||||
name: Property.ShortText({
|
||||
displayName: 'Name',
|
||||
description: 'The name of the opportunity',
|
||||
required: true,
|
||||
defaultValue: opportunity.name,
|
||||
}),
|
||||
};
|
||||
},
|
||||
}),
|
||||
pipelineId: pipelinesDropdown({ refreshers: ['auth'] }),
|
||||
pipelineStageId: Property.Dropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Pipeline Stage',
|
||||
description: 'Select a stage',
|
||||
refreshers: ['auth', 'pipelineId'],
|
||||
required: false,
|
||||
async options({ auth, pipelineId }: any) {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (!pipelineId) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Select a pipeline first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
const pipeline = JSON.parse(pipelineId);
|
||||
|
||||
const stages = pipeline.stages;
|
||||
|
||||
return {
|
||||
options: stages.map((stage: any) => ({
|
||||
label: stage.name,
|
||||
value: stage.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
primaryContactId: primaryContactsDropdown({ refreshers: ['auth'] }),
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
updateFields,
|
||||
primaryContactId,
|
||||
pipelineId,
|
||||
pipelineStageId,
|
||||
opportunityId,
|
||||
} = context.propsValue;
|
||||
|
||||
const pipeline = JSON.parse(pipelineId as string);
|
||||
const opportunity = JSON.parse(opportunityId as string);
|
||||
|
||||
return await CopperApiService.updateOpportunity(
|
||||
context.auth,
|
||||
opportunity.id,
|
||||
{
|
||||
name: (updateFields as any).name,
|
||||
primary_contact_id: primaryContactId,
|
||||
pipeline_id: pipeline.id,
|
||||
pipeline_stage_id: pipelineStageId,
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,172 @@
|
||||
import {
|
||||
createAction,
|
||||
InputPropertyMap,
|
||||
Property,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { CopperAuth, isNonEmptyStr } from '../common/constants';
|
||||
import { peopleDropdown } from '../common/props';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
|
||||
export const updatePerson = createAction({
|
||||
auth: CopperAuth,
|
||||
name: 'updatePerson',
|
||||
displayName: 'Update Person',
|
||||
description: 'Updates a person based on matching criteria.',
|
||||
props: {
|
||||
personId: peopleDropdown(['auth']),
|
||||
fields: Property.DynamicProperties({
|
||||
displayName: '',
|
||||
description: '',
|
||||
refreshers: ['auth', 'personId'],
|
||||
required: false,
|
||||
auth: CopperAuth,
|
||||
props: async ({ auth, personId }) => {
|
||||
if (!auth || !personId) return {};
|
||||
|
||||
const person = JSON.parse(personId as string);
|
||||
|
||||
const map:InputPropertyMap= {
|
||||
name: Property.ShortText({
|
||||
displayName: 'Full Name',
|
||||
required: true,
|
||||
defaultValue: person.name,
|
||||
}),
|
||||
emails: Property.Array({
|
||||
displayName: 'Emails',
|
||||
required: true,
|
||||
properties: {
|
||||
email: Property.ShortText({
|
||||
displayName: 'Email',
|
||||
required: true,
|
||||
}),
|
||||
category: Property.ShortText({
|
||||
displayName: 'Category (e.g., work, personal)',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
defaultValue: person.emails,
|
||||
}),
|
||||
phone_numbers: Property.Array({
|
||||
displayName: 'Phone Numbers',
|
||||
required: false,
|
||||
properties: {
|
||||
number: Property.ShortText({
|
||||
displayName: 'Number',
|
||||
required: true,
|
||||
}),
|
||||
category: Property.ShortText({
|
||||
displayName: 'Category (e.g., mobile, work, personal)',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
defaultValue: person.phone_numbers,
|
||||
}),
|
||||
address_street: Property.ShortText({
|
||||
displayName: 'Street',
|
||||
required: false,
|
||||
defaultValue: person.address?.street,
|
||||
}),
|
||||
address_city: Property.ShortText({
|
||||
displayName: 'City',
|
||||
required: false,
|
||||
defaultValue: person.address?.city,
|
||||
}),
|
||||
address_state: Property.ShortText({
|
||||
displayName: 'State/Region',
|
||||
required: false,
|
||||
defaultValue: person.address?.state,
|
||||
}),
|
||||
address_postal_code: Property.ShortText({
|
||||
displayName: 'Postal Code',
|
||||
required: false,
|
||||
defaultValue: person.address?.postal_code,
|
||||
}),
|
||||
address_country: Property.ShortText({
|
||||
displayName: 'Country',
|
||||
required: false,
|
||||
defaultValue: person.address?.country,
|
||||
}),
|
||||
} ;
|
||||
return map;
|
||||
},
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const fields = (context.propsValue as any).fields;
|
||||
const personId = (context.propsValue as any).personId;
|
||||
|
||||
const person = JSON.parse(personId);
|
||||
|
||||
const {
|
||||
name,
|
||||
emails,
|
||||
address_street,
|
||||
address_city,
|
||||
address_state,
|
||||
address_postal_code,
|
||||
address_country,
|
||||
phone_numbers,
|
||||
} = fields;
|
||||
|
||||
const normEmails = (Array.isArray(emails) ? emails : [])
|
||||
.map((row) => ({
|
||||
email: String(row?.email ?? '')
|
||||
.trim()
|
||||
.toLowerCase(),
|
||||
category: isNonEmptyStr(row?.category)
|
||||
? String(row.category).trim()
|
||||
: undefined,
|
||||
}))
|
||||
.filter((e) => isNonEmptyStr(e.email));
|
||||
|
||||
if (normEmails.length === 0)
|
||||
throw new Error('Please provide at least one valid email.');
|
||||
|
||||
const seen = new Set<string>();
|
||||
const dedupedEmails = normEmails.filter(
|
||||
(e) => !seen.has(e.email) && (seen.add(e.email), true)
|
||||
);
|
||||
|
||||
const normPhones = (Array.isArray(phone_numbers) ? phone_numbers : [])
|
||||
.map((p) => ({
|
||||
number: String(p?.number ?? '').trim(),
|
||||
category: isNonEmptyStr(p?.category)
|
||||
? String(p.category).trim()
|
||||
: undefined,
|
||||
}))
|
||||
.filter((p) => isNonEmptyStr(p.number));
|
||||
|
||||
const address = [
|
||||
address_street,
|
||||
address_city,
|
||||
address_state,
|
||||
address_postal_code,
|
||||
address_country,
|
||||
].some(isNonEmptyStr)
|
||||
? {
|
||||
street: isNonEmptyStr(address_street)
|
||||
? address_street.trim()
|
||||
: undefined,
|
||||
city: isNonEmptyStr(address_city) ? address_city.trim() : undefined,
|
||||
state: isNonEmptyStr(address_state)
|
||||
? address_state.trim()
|
||||
: undefined,
|
||||
postal_code: isNonEmptyStr(address_postal_code)
|
||||
? address_postal_code.trim()
|
||||
: undefined,
|
||||
country: isNonEmptyStr(address_country)
|
||||
? address_country.trim()
|
||||
: undefined,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const body: any = {
|
||||
name,
|
||||
emails: dedupedEmails,
|
||||
...(normPhones.length ? { phone_numbers: normPhones } : {}),
|
||||
...(address ? { address } : {}),
|
||||
};
|
||||
|
||||
return await CopperApiService.updatePerson(context.auth, person.id, body);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
createAction,
|
||||
InputPropertyMap,
|
||||
Property,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { CopperAuth } from '../common/constants';
|
||||
import { projectsDropdown } from '../common/props';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
|
||||
export const updateProject = createAction({
|
||||
auth: CopperAuth,
|
||||
name: 'updateProject',
|
||||
displayName: 'Update Project',
|
||||
description: 'Updates a project record.',
|
||||
props: {
|
||||
projectId: projectsDropdown({ refreshers: ['auth'], required: true }),
|
||||
updateFields: Property.DynamicProperties({
|
||||
displayName: '',
|
||||
description: '',
|
||||
refreshers: ['auth', 'projectId'],
|
||||
auth: CopperAuth,
|
||||
required: false,
|
||||
props: async ({ auth, projectId }: any): Promise<InputPropertyMap> => {
|
||||
if (!auth || !projectId) return {};
|
||||
|
||||
const project = JSON.parse(projectId);
|
||||
|
||||
const map:InputPropertyMap= {
|
||||
name: Property.ShortText({
|
||||
displayName: 'Name',
|
||||
description: 'The name of the project',
|
||||
required: true,
|
||||
defaultValue: project.name,
|
||||
}),
|
||||
details: Property.ShortText({
|
||||
displayName: 'Details',
|
||||
description: 'The details of the project',
|
||||
required: false,
|
||||
defaultValue: project.details,
|
||||
}),
|
||||
};
|
||||
return map;
|
||||
},
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { projectId, updateFields } = context.propsValue;
|
||||
|
||||
const { name, details } = updateFields as any;
|
||||
|
||||
const project = JSON.parse(projectId as string);
|
||||
|
||||
return await CopperApiService.updateProject(context.auth, project.id, {
|
||||
name,
|
||||
details,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import { AppConnectionValueForAuthProperty, PieceAuth, Property } from '@activepieces/pieces-framework';
|
||||
import { CopperApiService } from './requests';
|
||||
import { AppConnectionType } from '@activepieces/shared';
|
||||
|
||||
export const BASE_URL = 'https://api.copper.com/developer_api';
|
||||
|
||||
export const CopperAuth = PieceAuth.CustomAuth({
|
||||
description: '',
|
||||
required: true,
|
||||
props: {
|
||||
email: Property.ShortText({
|
||||
displayName: 'Email Address',
|
||||
description: 'Email Address of the Token Owner',
|
||||
required: true,
|
||||
}),
|
||||
apiKey: Property.ShortText({
|
||||
displayName: 'API Key',
|
||||
description: 'Your API Key in settings > integrations',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
validate: async ({ auth }) => {
|
||||
try {
|
||||
await CopperApiService.fetchCurrentUser({props: auth, type: AppConnectionType.CUSTOM_AUTH})
|
||||
return {
|
||||
valid: true,
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
valid: false,
|
||||
error: "Invalid API Credentials, please check your credentials and try again"
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const API_ENDPOINTS = {
|
||||
USERS: '/users',
|
||||
WEBHOOKS: '/webhooks',
|
||||
PEOPLE: '/people',
|
||||
LEADS: '/leads',
|
||||
COMPANIES: '/companies',
|
||||
OPPORTUNITIES: '/opportunities',
|
||||
TASKS: '/tasks',
|
||||
PIPELINES: '/pipelines',
|
||||
PROJECTS: '/projects',
|
||||
};
|
||||
|
||||
export type CopperAuthType = AppConnectionValueForAuthProperty<typeof CopperAuth>;
|
||||
|
||||
export const isNonEmptyStr = (v: any) => typeof v === 'string' && v.trim().length > 0;
|
||||
|
||||
export const toUnix = (iso?: string | null) =>
|
||||
iso ? Math.floor(new Date(iso).getTime() / 1000) : undefined;
|
||||
|
||||
export type CopperActivity = {
|
||||
id: number;
|
||||
name?: string;
|
||||
details?: string;
|
||||
assignee_id?: number;
|
||||
custom_activity_type_id?: number;
|
||||
parent?: { type: 'person'|'company'|'lead'|'opportunity'|'project'; id: number };
|
||||
activity_date?: number; // unix seconds
|
||||
date_created: number; // unix seconds
|
||||
date_modified?: number; // unix seconds
|
||||
tags?: string[];
|
||||
};
|
||||
@@ -0,0 +1,766 @@
|
||||
import { Property } from '@activepieces/pieces-framework';
|
||||
import { CopperApiService } from './requests';
|
||||
import { CopperAuth } from './constants';
|
||||
|
||||
export const peopleDropdown = (refreshers: string[]) =>
|
||||
Property.Dropdown({
|
||||
displayName: 'Person',
|
||||
description: 'select a person',
|
||||
required: true,
|
||||
refreshers,
|
||||
auth: CopperAuth,
|
||||
async options({ auth }) {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const people = await CopperApiService.fetchPeople(auth);
|
||||
|
||||
return {
|
||||
options: people.map((person: any) => ({
|
||||
label: person.name,
|
||||
value: JSON.stringify(person),
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch campaigns', e);
|
||||
return {
|
||||
options: [],
|
||||
placeholder: 'Unable to load campaigns',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const leadDropdown = (refreshers: string[]) =>
|
||||
Property.Dropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Lead',
|
||||
description: 'select a Lead',
|
||||
required: true,
|
||||
refreshers,
|
||||
async options({ auth }) {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const leads = await CopperApiService.fetchLeads(auth);
|
||||
|
||||
return {
|
||||
options: leads.map((lead: any) => ({
|
||||
label: lead.name,
|
||||
value: JSON.stringify(lead),
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch leads', e);
|
||||
return {
|
||||
options: [],
|
||||
placeholder: 'Unable to load leads',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const companyDropdown = ({
|
||||
refreshers,
|
||||
required = false,
|
||||
}: {
|
||||
refreshers: string[];
|
||||
required?: boolean;
|
||||
}) =>
|
||||
Property.Dropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Company',
|
||||
description: 'select a Company',
|
||||
required,
|
||||
refreshers,
|
||||
async options({ auth }: any) {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const companies = await CopperApiService.fetchCompanies(auth);
|
||||
|
||||
return {
|
||||
options: companies.map((company: any) => ({
|
||||
label: company.name,
|
||||
value: JSON.stringify(company),
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch companies', e);
|
||||
return {
|
||||
options: [],
|
||||
placeholder: 'Unable to load companies',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const multiCompanyDropdown = ({
|
||||
refreshers,
|
||||
required = false,
|
||||
}: {
|
||||
refreshers: string[];
|
||||
required?: boolean;
|
||||
}) =>
|
||||
Property.MultiSelectDropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Company',
|
||||
description: 'select Companies',
|
||||
required,
|
||||
refreshers,
|
||||
async options({ auth }) {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const companies = await CopperApiService.fetchCompanies(auth);
|
||||
|
||||
return {
|
||||
options: companies.map((company: any) => ({
|
||||
label: company.name,
|
||||
value: company.id,
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch companies', e);
|
||||
return {
|
||||
options: [],
|
||||
placeholder: 'Unable to load companies',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const primaryContactsDropdown = ({
|
||||
refreshers,
|
||||
required = false,
|
||||
}: {
|
||||
refreshers: string[];
|
||||
required?: boolean;
|
||||
}) =>
|
||||
Property.Dropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Primary Contact',
|
||||
description: 'select a primary contact',
|
||||
required,
|
||||
refreshers,
|
||||
async options({ auth }) {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const primaryContacts = await CopperApiService.fetchPeople(auth);
|
||||
|
||||
return {
|
||||
options: primaryContacts.map((contact: any) => ({
|
||||
label: contact.name,
|
||||
value: contact.id,
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch opportunities', e);
|
||||
return {
|
||||
options: [],
|
||||
placeholder: 'Unable to load opportunities',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const multiPrimaryContactsDropdown = ({
|
||||
refreshers,
|
||||
required = false,
|
||||
}: {
|
||||
refreshers: string[];
|
||||
required?: boolean;
|
||||
}) =>
|
||||
Property.MultiSelectDropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Primary Contacts',
|
||||
description: 'select primary contacts',
|
||||
required,
|
||||
refreshers,
|
||||
async options({ auth }) {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const primaryContacts = await CopperApiService.fetchPeople(auth);
|
||||
|
||||
return {
|
||||
options: primaryContacts.map((contact: any) => ({
|
||||
label: contact.name,
|
||||
value: contact.id,
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch opportunities', e);
|
||||
return {
|
||||
options: [],
|
||||
placeholder: 'Unable to load opportunities',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const usersDropdown = ({
|
||||
refreshers,
|
||||
required = false,
|
||||
}: {
|
||||
refreshers: string[];
|
||||
required?: boolean;
|
||||
}) =>
|
||||
Property.Dropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Assignee',
|
||||
description: 'select a user to assign to',
|
||||
required,
|
||||
refreshers,
|
||||
async options({ auth }) {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const users = await CopperApiService.fetchUsers(auth);
|
||||
|
||||
return {
|
||||
options: users.map((user: any) => ({
|
||||
label: user.name,
|
||||
value: user.id,
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch opportunities', e);
|
||||
return {
|
||||
options: [],
|
||||
placeholder: 'Unable to load opportunities',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const multiUsersDropdown = ({
|
||||
refreshers,
|
||||
required = false,
|
||||
}: {
|
||||
refreshers: string[];
|
||||
required?: boolean;
|
||||
}) =>
|
||||
Property.MultiSelectDropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Assignee',
|
||||
description: 'select assignees',
|
||||
required,
|
||||
refreshers,
|
||||
async options({ auth }) {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const users = await CopperApiService.fetchUsers(auth);
|
||||
|
||||
return {
|
||||
options: users.map((user: any) => ({
|
||||
label: user.name,
|
||||
value: user.id,
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch opportunities', e);
|
||||
return {
|
||||
options: [],
|
||||
placeholder: 'Unable to load opportunities',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const opportunityDropdown = ({
|
||||
refreshers,
|
||||
required = false,
|
||||
}: {
|
||||
refreshers: string[];
|
||||
required?: boolean;
|
||||
}) =>
|
||||
Property.Dropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Opportunity',
|
||||
description: 'select an Opportunity',
|
||||
required,
|
||||
refreshers,
|
||||
async options({ auth }) {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const opportunities = await CopperApiService.fetchOpportunities(auth);
|
||||
|
||||
return {
|
||||
options: opportunities.map((opportunity: any) => ({
|
||||
label: opportunity.name,
|
||||
value: JSON.stringify(opportunity),
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch opportunities', e);
|
||||
return {
|
||||
options: [],
|
||||
placeholder: 'Unable to load opportunities',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const multiOpportunityDropdown = ({
|
||||
refreshers,
|
||||
required = false,
|
||||
}: {
|
||||
refreshers: string[];
|
||||
required?: boolean;
|
||||
}) =>
|
||||
Property.MultiSelectDropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Opportunity',
|
||||
description: 'select Opportunities',
|
||||
required,
|
||||
refreshers,
|
||||
async options({ auth }) {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const opportunities = await CopperApiService.fetchOpportunities(auth);
|
||||
|
||||
return {
|
||||
options: opportunities.map((opportunity: any) => ({
|
||||
label: opportunity.name,
|
||||
value: opportunity.id,
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch opportunities', e);
|
||||
return {
|
||||
options: [],
|
||||
placeholder: 'Unable to load opportunities',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const pipelinesDropdown = ({
|
||||
refreshers,
|
||||
required = false,
|
||||
}: {
|
||||
refreshers: string[];
|
||||
required?: boolean;
|
||||
}) =>
|
||||
Property.Dropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Pipeline',
|
||||
description: 'select a Pipeline',
|
||||
required,
|
||||
refreshers,
|
||||
async options({ auth }) {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const pipelines = await CopperApiService.fetchPipelines(auth);
|
||||
|
||||
return {
|
||||
options: pipelines.map((pipeline: any) => ({
|
||||
label: pipeline.name,
|
||||
value: JSON.stringify(pipeline),
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch pipelines', e);
|
||||
return {
|
||||
options: [],
|
||||
placeholder: 'Unable to load pipelines',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const multiPipelinesDropdown = ({
|
||||
refreshers,
|
||||
required = false,
|
||||
}: {
|
||||
refreshers: string[];
|
||||
required?: boolean;
|
||||
}) =>
|
||||
Property.MultiSelectDropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Pipeline',
|
||||
description: 'select a Pipeline',
|
||||
required,
|
||||
refreshers,
|
||||
async options({ auth }) {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const pipelines = await CopperApiService.fetchPipelines(auth);
|
||||
|
||||
return {
|
||||
options: pipelines.map((pipeline: any) => ({
|
||||
label: pipeline.name,
|
||||
value: JSON.stringify(pipeline),
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch pipelines', e);
|
||||
return {
|
||||
options: [],
|
||||
placeholder: 'Unable to load pipelines',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const projectsDropdown = ({
|
||||
refreshers,
|
||||
required = false,
|
||||
}: {
|
||||
refreshers: string[];
|
||||
required?: boolean;
|
||||
}) =>
|
||||
Property.Dropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Project',
|
||||
description: 'select a Project',
|
||||
required,
|
||||
refreshers,
|
||||
async options({ auth }) {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const projects = await CopperApiService.fetchProjects(auth);
|
||||
|
||||
return {
|
||||
options: projects.map((project: any) => ({
|
||||
label: project.name,
|
||||
value: JSON.stringify(project),
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch projects', e);
|
||||
return {
|
||||
options: [],
|
||||
placeholder: 'Unable to load projects',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const ActivityTypesDropdown = (entity?: 'user' | 'system') =>
|
||||
Property.Dropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Activity Type',
|
||||
description: 'Select activity Type',
|
||||
required: true,
|
||||
refreshers: ['auth'],
|
||||
async options({ auth }) {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await CopperApiService.fetchActivityTypes(auth);
|
||||
|
||||
const items =
|
||||
entity && response[entity]
|
||||
? response[entity]
|
||||
: [...response.user, ...response.system];
|
||||
|
||||
return {
|
||||
options: items.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: JSON.stringify(item),
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch activity types', e);
|
||||
return {
|
||||
options: [],
|
||||
placeholder: 'Unable to load activity types',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const MultiActivityTypesDropdown = ({
|
||||
entity,
|
||||
required = false
|
||||
}: {
|
||||
entity?: 'user' | 'system';
|
||||
required?: boolean;
|
||||
}) =>
|
||||
Property.MultiSelectDropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Activity Type',
|
||||
description: 'Select activity Type',
|
||||
required,
|
||||
refreshers: ['auth'],
|
||||
async options({ auth }) {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await CopperApiService.fetchActivityTypes(auth);
|
||||
|
||||
const items =
|
||||
entity && response[entity]
|
||||
? response[entity]
|
||||
: [...response.user, ...response.system];
|
||||
|
||||
return {
|
||||
options: items.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: JSON.stringify(item),
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch activity types', e);
|
||||
return {
|
||||
options: [],
|
||||
placeholder: 'Unable to load activity types',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const MultiContactTypesDropdown = ({
|
||||
required = false
|
||||
}: {
|
||||
required?: boolean;
|
||||
}) =>
|
||||
Property.MultiSelectDropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Contact Type',
|
||||
description: 'Select contact Type',
|
||||
required,
|
||||
refreshers: ['auth'],
|
||||
async options({ auth }) {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await CopperApiService.fetchContactTypes(auth);
|
||||
|
||||
return {
|
||||
options: response.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch contact types', e);
|
||||
return {
|
||||
options: [],
|
||||
placeholder: 'Unable to load contact types',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const MultiLeadStatusDropdown = ({
|
||||
required = false
|
||||
}: {
|
||||
required?: boolean;
|
||||
}) =>
|
||||
Property.MultiSelectDropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Lead Status',
|
||||
description: 'Select lead status',
|
||||
required,
|
||||
refreshers: ['auth'],
|
||||
async options({ auth }) {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await CopperApiService.fetchLeadStatuses(auth);
|
||||
|
||||
return {
|
||||
options: response.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch lead statuses', e);
|
||||
return {
|
||||
options: [],
|
||||
placeholder: 'Unable to load lead statuses',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const MultiCustomerSourceDropdown = ({
|
||||
required = false
|
||||
}: {
|
||||
required?: boolean;
|
||||
}) =>
|
||||
Property.MultiSelectDropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Customer Source',
|
||||
description: 'Select customer source.',
|
||||
required,
|
||||
refreshers: ['auth'],
|
||||
async options({ auth }) {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await CopperApiService.fetchCustomerSources(auth);
|
||||
|
||||
return {
|
||||
options: response.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch customer sources', e);
|
||||
return {
|
||||
options: [],
|
||||
placeholder: 'Unable to load customer sources',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const MultiLossReasonsDropdown = ({
|
||||
required = false
|
||||
}: {
|
||||
required?: boolean;
|
||||
}) =>
|
||||
Property.MultiSelectDropdown({
|
||||
auth: CopperAuth,
|
||||
displayName: 'Loss Reason',
|
||||
description: 'Select loss reason.',
|
||||
required,
|
||||
refreshers: ['auth'],
|
||||
async options({ auth }) {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your Copper account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await CopperApiService.fetchLossReasons(auth);
|
||||
|
||||
return {
|
||||
options: response.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
})),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch loss reasons', e);
|
||||
return {
|
||||
options: [],
|
||||
placeholder: 'Unable to load loss reasons',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,269 @@
|
||||
import { HttpMethod, httpClient } from '@activepieces/pieces-common';
|
||||
import { API_ENDPOINTS, BASE_URL, CopperAuthType } from './constants';
|
||||
|
||||
async function fireHttpRequest({
|
||||
method,
|
||||
path,
|
||||
auth,
|
||||
body,
|
||||
}: {
|
||||
method: HttpMethod;
|
||||
path: string;
|
||||
auth: CopperAuthType;
|
||||
body?: any;
|
||||
}) {
|
||||
return await httpClient
|
||||
.sendRequest({
|
||||
method,
|
||||
url: `${BASE_URL}${path}`,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'X-PW-AccessToken': auth.props.apiKey,
|
||||
'X-PW-Application': 'developer_api',
|
||||
'X-PW-UserEmail': auth.props.email,
|
||||
},
|
||||
body,
|
||||
})
|
||||
.then((res) => res.body)
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
export const CopperApiService = {
|
||||
async fetchCurrentUser(auth: CopperAuthType) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.GET,
|
||||
path: `/v1${API_ENDPOINTS.USERS}/me`,
|
||||
auth,
|
||||
});
|
||||
},
|
||||
async fetchLeads(auth: CopperAuthType, payload?: any) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.POST,
|
||||
path: `/v1${API_ENDPOINTS.LEADS}/search`,
|
||||
auth,
|
||||
body: payload
|
||||
});
|
||||
},
|
||||
async createTask(auth: CopperAuthType, payload: any) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.POST,
|
||||
path: `/v1${API_ENDPOINTS.TASKS}`,
|
||||
auth,
|
||||
body: payload,
|
||||
});
|
||||
},
|
||||
async createProject(auth: CopperAuthType, payload: any) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.POST,
|
||||
path: `/v1${API_ENDPOINTS.PROJECTS}`,
|
||||
auth,
|
||||
body: payload,
|
||||
});
|
||||
},
|
||||
async updateProject(auth: CopperAuthType, projectId: string, payload: any) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.PUT,
|
||||
path: `/v1${API_ENDPOINTS.PROJECTS}/${projectId}`,
|
||||
auth,
|
||||
body: payload,
|
||||
});
|
||||
},
|
||||
async fetchProjects(auth: CopperAuthType, payload?: any) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.POST,
|
||||
path: `/v1${API_ENDPOINTS.PROJECTS}/search`,
|
||||
auth,
|
||||
body: payload
|
||||
});
|
||||
},
|
||||
async createCompany(auth: CopperAuthType, payload: any) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.POST,
|
||||
path: `/v1${API_ENDPOINTS.COMPANIES}`,
|
||||
auth,
|
||||
body: payload,
|
||||
});
|
||||
},
|
||||
async updateCompany(auth: CopperAuthType, companyId: string, payload: any) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.PUT,
|
||||
path: `/v1${API_ENDPOINTS.COMPANIES}/${companyId}`,
|
||||
auth,
|
||||
body: payload,
|
||||
});
|
||||
},
|
||||
async fetchCompanies(auth: CopperAuthType, payload?: any) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.POST,
|
||||
path: `/v1${API_ENDPOINTS.COMPANIES}/search`,
|
||||
auth,
|
||||
body: payload
|
||||
});
|
||||
},
|
||||
async fetchActivityTypes(auth: CopperAuthType) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.GET,
|
||||
path: `/v1/activity_types`,
|
||||
auth,
|
||||
});
|
||||
},
|
||||
async fetchContactTypes(auth: CopperAuthType) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.GET,
|
||||
path: `/v1/contact_types`,
|
||||
auth,
|
||||
});
|
||||
},
|
||||
async fetchLeadStatuses(auth: CopperAuthType) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.GET,
|
||||
path: `/v1/lead_statuses`,
|
||||
auth,
|
||||
});
|
||||
},
|
||||
async fetchCustomerSources(auth: CopperAuthType) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.GET,
|
||||
path: `/v1/customer_sources`,
|
||||
auth,
|
||||
});
|
||||
},
|
||||
async fetchLossReasons(auth: CopperAuthType) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.GET,
|
||||
path: `/v1/loss_reasons`,
|
||||
auth,
|
||||
});
|
||||
},
|
||||
async createActivity(auth: CopperAuthType, payload: any) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.POST,
|
||||
path: `/v1/activities`,
|
||||
auth,
|
||||
body: payload,
|
||||
});
|
||||
},
|
||||
async fetchActivities(auth: CopperAuthType, payload: any) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.POST,
|
||||
path: `/v1/activities/search`,
|
||||
auth,
|
||||
body: payload,
|
||||
});
|
||||
},
|
||||
async createOpportunity(auth: CopperAuthType, payload: any) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.POST,
|
||||
path: `/v1${API_ENDPOINTS.OPPORTUNITIES}`,
|
||||
auth,
|
||||
body: payload,
|
||||
});
|
||||
},
|
||||
async updateOpportunity(
|
||||
auth: CopperAuthType,
|
||||
opportunityId: string,
|
||||
payload: any
|
||||
) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.PUT,
|
||||
path: `/v1${API_ENDPOINTS.OPPORTUNITIES}/${opportunityId}`,
|
||||
auth,
|
||||
body: payload,
|
||||
});
|
||||
},
|
||||
async fetchOpportunities(auth: CopperAuthType, payload?: any) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.POST,
|
||||
path: `/v1${API_ENDPOINTS.OPPORTUNITIES}/search`,
|
||||
auth,
|
||||
body: payload
|
||||
});
|
||||
},
|
||||
async fetchTasks(auth: CopperAuthType) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.POST,
|
||||
path: `/v1${API_ENDPOINTS.TASKS}/search`,
|
||||
auth,
|
||||
});
|
||||
},
|
||||
async fetchPipelines(auth: CopperAuthType) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.GET,
|
||||
path: `/v1${API_ENDPOINTS.PIPELINES}`,
|
||||
auth,
|
||||
});
|
||||
},
|
||||
async createLead(auth: CopperAuthType, payload: any) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.POST,
|
||||
path: `/v1${API_ENDPOINTS.LEADS}`,
|
||||
auth,
|
||||
body: payload,
|
||||
});
|
||||
},
|
||||
async updateLead(auth: CopperAuthType, leadId: string, payload: any) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.PUT,
|
||||
path: `/v1${API_ENDPOINTS.LEADS}/${leadId}`,
|
||||
auth,
|
||||
body: payload,
|
||||
});
|
||||
},
|
||||
async convertLead(auth: CopperAuthType, leadId: string, payload: any) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.POST,
|
||||
path: `/v1${API_ENDPOINTS.LEADS}/${leadId}/convert`,
|
||||
auth,
|
||||
body: payload,
|
||||
});
|
||||
},
|
||||
async createPerson(auth: CopperAuthType, payload: any) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.POST,
|
||||
path: `/v1${API_ENDPOINTS.PEOPLE}`,
|
||||
auth,
|
||||
body: payload,
|
||||
});
|
||||
},
|
||||
async updatePerson(auth: CopperAuthType, personId: string, payload: any) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.PUT,
|
||||
path: `/v1${API_ENDPOINTS.PEOPLE}/${personId}`,
|
||||
auth,
|
||||
body: payload,
|
||||
});
|
||||
},
|
||||
async fetchPeople(auth: CopperAuthType, payload?: any) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.POST,
|
||||
path: `/v1${API_ENDPOINTS.PEOPLE}/search`,
|
||||
auth,
|
||||
body: payload
|
||||
});
|
||||
},
|
||||
async fetchUsers(auth: CopperAuthType) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.POST,
|
||||
path: `/v1${API_ENDPOINTS.USERS}/search`,
|
||||
auth,
|
||||
});
|
||||
},
|
||||
async createWebhook(auth: CopperAuthType, payload: any) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.POST,
|
||||
path: `/v1${API_ENDPOINTS.WEBHOOKS}`,
|
||||
auth,
|
||||
body: payload,
|
||||
});
|
||||
},
|
||||
async deleteWebhook(auth: CopperAuthType, webhookId: string) {
|
||||
return await fireHttpRequest({
|
||||
method: HttpMethod.DELETE,
|
||||
path: `/v1${API_ENDPOINTS.WEBHOOKS}/${webhookId}`,
|
||||
auth,
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
import {
|
||||
createTrigger,
|
||||
TriggerStrategy,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import {
|
||||
DedupeStrategy,
|
||||
Polling,
|
||||
pollingHelper,
|
||||
} from '@activepieces/pieces-common';
|
||||
import { CopperActivity, CopperAuth, CopperAuthType } from '../common/constants';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
|
||||
const polling: Polling<
|
||||
CopperAuthType,
|
||||
Record<string, never>
|
||||
> = {
|
||||
strategy: DedupeStrategy.TIMEBASED,
|
||||
|
||||
items: async ({ auth, lastFetchEpochMS }) => {
|
||||
const minCreatedUnix =
|
||||
lastFetchEpochMS != null
|
||||
? Math.max(0, Math.floor(lastFetchEpochMS / 1000) - 1)
|
||||
: undefined;
|
||||
|
||||
const collected: CopperActivity[] = [];
|
||||
const pageSize = 100;
|
||||
let page = 1;
|
||||
let hasMore = true;
|
||||
|
||||
while (hasMore) {
|
||||
const batch = await CopperApiService.fetchActivities(auth, {
|
||||
minimum_activity_date: minCreatedUnix,
|
||||
page_size: pageSize,
|
||||
page_number: page,
|
||||
});
|
||||
if (!batch.length) break;
|
||||
|
||||
collected.push(...batch);
|
||||
if (batch.length < pageSize) hasMore=false;
|
||||
page += 1;
|
||||
}
|
||||
|
||||
const out = collected.map((a) => ({
|
||||
epochMilliSeconds: (a.activity_date ?? 0) * 1000,
|
||||
data: a,
|
||||
}));
|
||||
|
||||
out.sort((a, b) => a.epochMilliSeconds - b.epochMilliSeconds);
|
||||
return out;
|
||||
},
|
||||
};
|
||||
|
||||
export const newActivity = createTrigger({
|
||||
auth: CopperAuth,
|
||||
name: 'newActivity',
|
||||
displayName: 'New Activity',
|
||||
description: 'Triggers when a new activity is logged',
|
||||
props: {},
|
||||
sampleData: {},
|
||||
type: TriggerStrategy.POLLING,
|
||||
async test(context) {
|
||||
return await pollingHelper.test(polling, context);
|
||||
},
|
||||
async onEnable(context) {
|
||||
const { store, auth, propsValue } = context;
|
||||
await pollingHelper.onEnable(polling, { store, auth, propsValue });
|
||||
},
|
||||
|
||||
async onDisable(context) {
|
||||
const { store, auth, propsValue } = context;
|
||||
await pollingHelper.onDisable(polling, { store, auth, propsValue });
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
return await pollingHelper.poll(polling, context);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
|
||||
import { CopperAuth } from '../common/constants';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
|
||||
const CACHE_KEY = 'copper_new_lead_trigger_key';
|
||||
|
||||
export const newLead = createTrigger({
|
||||
auth: CopperAuth,
|
||||
name: 'newLead',
|
||||
displayName: 'New Lead',
|
||||
description: 'Triggers when a new lead is created.',
|
||||
props: {},
|
||||
sampleData: {},
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
async onEnable(context) {
|
||||
const response = await CopperApiService.createWebhook(context.auth, {
|
||||
target: context.webhookUrl,
|
||||
type: 'lead',
|
||||
event: 'new',
|
||||
});
|
||||
|
||||
await context.store.put(CACHE_KEY, {
|
||||
webhookId: response.id,
|
||||
});
|
||||
},
|
||||
async onDisable(context) {
|
||||
const cachedWebhookData = (await context.store.get(CACHE_KEY)) as any;
|
||||
|
||||
if (cachedWebhookData) {
|
||||
await CopperApiService.deleteWebhook(
|
||||
context.auth,
|
||||
cachedWebhookData.webhookId
|
||||
).then(async () => {
|
||||
await context.store.delete(CACHE_KEY);
|
||||
});
|
||||
}
|
||||
},
|
||||
async run(context) {
|
||||
return [context.payload.body];
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
|
||||
import { CopperAuth } from '../common/constants';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
|
||||
const CACHE_KEY = 'copper_new_person_trigger_key';
|
||||
|
||||
export const newPerson = createTrigger({
|
||||
auth: CopperAuth,
|
||||
name: 'newPerson',
|
||||
displayName: 'New Person',
|
||||
description: 'Triggers when a new person/contact is created.',
|
||||
props: {},
|
||||
sampleData: {},
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
async onEnable(context) {
|
||||
const response = await CopperApiService.createWebhook(context.auth, {
|
||||
target: context.webhookUrl,
|
||||
type: 'person',
|
||||
event: 'new',
|
||||
});
|
||||
|
||||
await context.store.put(CACHE_KEY, {
|
||||
webhookId: response.id,
|
||||
});
|
||||
},
|
||||
async onDisable(context) {
|
||||
const cachedWebhookData = (await context.store.get(CACHE_KEY)) as any;
|
||||
|
||||
if (cachedWebhookData) {
|
||||
await CopperApiService.deleteWebhook(
|
||||
context.auth,
|
||||
cachedWebhookData.webhookId
|
||||
).then(async () => {
|
||||
await context.store.delete(CACHE_KEY);
|
||||
});
|
||||
}
|
||||
},
|
||||
async run(context) {
|
||||
return [context.payload.body];
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
|
||||
import { CopperAuth } from '../common/constants';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
|
||||
const CACHE_KEY = 'copper_new_task_trigger_key';
|
||||
|
||||
export const newTask = createTrigger({
|
||||
auth: CopperAuth,
|
||||
name: 'newTask',
|
||||
displayName: 'New Task',
|
||||
description: 'Triggers when a new task is created.',
|
||||
props: {},
|
||||
sampleData: {},
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
async onEnable(context) {
|
||||
const response = await CopperApiService.createWebhook(context.auth, {
|
||||
target: context.webhookUrl,
|
||||
type: 'task',
|
||||
event: 'new',
|
||||
});
|
||||
|
||||
await context.store.put(CACHE_KEY, {
|
||||
webhookId: response.id,
|
||||
});
|
||||
},
|
||||
async onDisable(context) {
|
||||
const cachedWebhookData = (await context.store.get(CACHE_KEY)) as any;
|
||||
|
||||
if (cachedWebhookData) {
|
||||
await CopperApiService.deleteWebhook(
|
||||
context.auth,
|
||||
cachedWebhookData.webhookId
|
||||
).then(async () => {
|
||||
await context.store.delete(CACHE_KEY);
|
||||
});
|
||||
}
|
||||
},
|
||||
async run(context) {
|
||||
return [context.payload.body];
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
|
||||
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
|
||||
import { CopperAuth } from '../common/constants';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
|
||||
const CACHE_KEY = 'copper_updated_lead_status_trigger_key';
|
||||
|
||||
export const updatedLeadStatus = createTrigger({
|
||||
auth: CopperAuth,
|
||||
name: 'updatedLeadStatus',
|
||||
displayName: 'Updated Lead Status',
|
||||
description: 'Triggers when the status of a lead changes.',
|
||||
props: {},
|
||||
sampleData: {},
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
async onEnable(context) {
|
||||
const response = await CopperApiService.createWebhook(context.auth, {
|
||||
target: context.webhookUrl,
|
||||
type: 'lead',
|
||||
event: 'update',
|
||||
});
|
||||
|
||||
await context.store.put(CACHE_KEY, {
|
||||
webhookId: response.id,
|
||||
});
|
||||
},
|
||||
async onDisable(context) {
|
||||
const cachedWebhookData = (await context.store.get(CACHE_KEY)) as any;
|
||||
|
||||
if (cachedWebhookData) {
|
||||
await CopperApiService.deleteWebhook(
|
||||
context.auth,
|
||||
cachedWebhookData.webhookId
|
||||
).then(async () => {
|
||||
await context.store.delete(CACHE_KEY);
|
||||
});
|
||||
}
|
||||
},
|
||||
async run(context) {
|
||||
const body = context.payload.body as any;
|
||||
const ids = Array.isArray(body?.ids) ? body.ids : [];
|
||||
const updatedAttrs = body?.updated_attributes ?? {};
|
||||
|
||||
const statusChanged =
|
||||
Array.isArray(updatedAttrs.status) &&
|
||||
updatedAttrs.status.length === 2 &&
|
||||
updatedAttrs.status[0] !== updatedAttrs.status[1];
|
||||
|
||||
if (!statusChanged) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const events = ids.map((id: number | string) => ({
|
||||
id,
|
||||
change_type: 'status_change',
|
||||
previous_status: updatedAttrs.status?.[0] ?? null,
|
||||
current_status: updatedAttrs.status?.[1] ?? null,
|
||||
subscription_id: body.subscription_id,
|
||||
timestamp: body.timestamp,
|
||||
}));
|
||||
|
||||
return events;
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,41 @@
|
||||
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
import { CopperAuth } from '../common/constants';
|
||||
|
||||
const CACHE_KEY = 'copper_updated_lead_trigger_key';
|
||||
|
||||
export const updatedLead = createTrigger({
|
||||
auth: CopperAuth,
|
||||
name: 'updatedLead',
|
||||
displayName: 'Updated Lead',
|
||||
description: 'Triggers when a lead is modified.',
|
||||
props: {},
|
||||
sampleData: {},
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
async onEnable(context) {
|
||||
const response = await CopperApiService.createWebhook(context.auth, {
|
||||
target: context.webhookUrl,
|
||||
type: 'lead',
|
||||
event: 'update',
|
||||
});
|
||||
|
||||
await context.store.put(CACHE_KEY, {
|
||||
webhookId: response.id,
|
||||
});
|
||||
},
|
||||
async onDisable(context) {
|
||||
const cachedWebhookData = (await context.store.get(CACHE_KEY)) as any;
|
||||
|
||||
if (cachedWebhookData) {
|
||||
await CopperApiService.deleteWebhook(
|
||||
context.auth,
|
||||
cachedWebhookData.webhookId
|
||||
).then(async () => {
|
||||
await context.store.delete(CACHE_KEY);
|
||||
});
|
||||
}
|
||||
},
|
||||
async run(context) {
|
||||
return [context.payload.body];
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
import { CopperAuth } from '../common/constants';
|
||||
|
||||
const CACHE_KEY = 'copper_updated_opportunity_stage_trigger_key';
|
||||
|
||||
export const updatedOpportunityStage = createTrigger({
|
||||
auth: CopperAuth,
|
||||
name: 'updatedOpportunityStage',
|
||||
displayName: 'Updated Opportunity Stage',
|
||||
description: 'Triggers when an opportunity stage changes',
|
||||
props: {},
|
||||
sampleData: {},
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
async onEnable(context) {
|
||||
const response = await CopperApiService.createWebhook(context.auth, {
|
||||
target: context.webhookUrl,
|
||||
type: 'opportunity',
|
||||
event: 'update',
|
||||
});
|
||||
|
||||
await context.store.put(CACHE_KEY, {
|
||||
webhookId: response.id,
|
||||
});
|
||||
},
|
||||
async onDisable(context) {
|
||||
const cachedWebhookData = (await context.store.get(CACHE_KEY)) as any;
|
||||
|
||||
if (cachedWebhookData) {
|
||||
await CopperApiService.deleteWebhook(
|
||||
context.auth,
|
||||
cachedWebhookData.webhookId
|
||||
).then(async () => {
|
||||
await context.store.delete(CACHE_KEY);
|
||||
});
|
||||
}
|
||||
},
|
||||
async run(context) {
|
||||
const body = context.payload.body as any;
|
||||
const ids = Array.isArray(body.ids) ? body.ids : [];
|
||||
const updatedAttrs = body.updated_attributes;
|
||||
|
||||
const idChanged =
|
||||
Array.isArray(updatedAttrs.stage_id) &&
|
||||
updatedAttrs.stage_id[0] !== updatedAttrs.stage_id[1];
|
||||
const labelChanged =
|
||||
Array.isArray(updatedAttrs.stage) &&
|
||||
updatedAttrs.stage[0] !== updatedAttrs.stage[1];
|
||||
|
||||
const isStageMove = idChanged && labelChanged;
|
||||
if (!isStageMove) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const events = ids.map((id: any) => ({
|
||||
id,
|
||||
change_type: 'stage_change',
|
||||
previous_stage_id: updatedAttrs.stage_id?.[0] ?? null,
|
||||
current_stage_id: updatedAttrs.stage_id?.[1] ?? null,
|
||||
previous_stage_label: updatedAttrs.stage?.[0] ?? null,
|
||||
current_stage_label: updatedAttrs.stage?.[1] ?? null,
|
||||
previous_last_stage_at: updatedAttrs.last_stage_at?.[0] ?? null,
|
||||
current_last_stage_at: updatedAttrs.last_stage_at?.[1] ?? null,
|
||||
previous_days_in_stage: updatedAttrs.days_in_stage?.[0] ?? null,
|
||||
current_days_in_stage: updatedAttrs.days_in_stage?.[1] ?? null,
|
||||
subscription_id: body.subscription_id,
|
||||
timestamp: body.timestamp,
|
||||
}));
|
||||
|
||||
return events;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
import { CopperAuth } from '../common/constants';
|
||||
|
||||
const CACHE_KEY = 'copper_updated_opportunity_status_trigger_key';
|
||||
|
||||
export const updatedOpportunityStatus = createTrigger({
|
||||
auth: CopperAuth,
|
||||
name: 'updatedOpportunityStatus',
|
||||
displayName: 'Updated Opportunity Status',
|
||||
description: "Triggers when an opportunity's status changes.",
|
||||
props: {},
|
||||
sampleData: {},
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
async onEnable(context) {
|
||||
const response = await CopperApiService.createWebhook(context.auth, {
|
||||
target: context.webhookUrl,
|
||||
type: 'opportunity',
|
||||
event: 'update',
|
||||
});
|
||||
|
||||
await context.store.put(CACHE_KEY, {
|
||||
webhookId: response.id,
|
||||
});
|
||||
},
|
||||
async onDisable(context) {
|
||||
const cachedWebhookData = (await context.store.get(CACHE_KEY)) as any;
|
||||
|
||||
if (cachedWebhookData) {
|
||||
await CopperApiService.deleteWebhook(
|
||||
context.auth,
|
||||
cachedWebhookData.webhookId
|
||||
).then(async () => {
|
||||
await context.store.delete(CACHE_KEY);
|
||||
});
|
||||
}
|
||||
},
|
||||
async run(context) {
|
||||
const body = context.payload.body as any;
|
||||
const ids = Array.isArray(body?.ids) ? body.ids : [];
|
||||
const updatedAttrs = body?.updated_attributes ?? {};
|
||||
|
||||
const statusChanged =
|
||||
Array.isArray(updatedAttrs.status) &&
|
||||
updatedAttrs.status.length === 2 &&
|
||||
updatedAttrs.status[0] !== updatedAttrs.status[1];
|
||||
|
||||
if (!statusChanged) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const events = ids.map((id: number | string) => ({
|
||||
id,
|
||||
change_type: 'status_change',
|
||||
previous_status: updatedAttrs.status?.[0] ?? null,
|
||||
current_status: updatedAttrs.status?.[1] ?? null,
|
||||
subscription_id: body.subscription_id,
|
||||
timestamp: body.timestamp,
|
||||
}));
|
||||
|
||||
return events;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
|
||||
import { CopperAuth } from '../common/constants';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
|
||||
const CACHE_KEY = 'copper_updated_opportunity_trigger_key';
|
||||
|
||||
export const updatedOpportunity = createTrigger({
|
||||
auth: CopperAuth,
|
||||
name: 'updatedOpportunity',
|
||||
displayName: 'Updated Opportunity',
|
||||
description: 'Triggers when an opportunity changes.',
|
||||
props: {},
|
||||
sampleData: {},
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
async onEnable(context) {
|
||||
const response = await CopperApiService.createWebhook(context.auth, {
|
||||
target: context.webhookUrl,
|
||||
type: 'opportunity',
|
||||
event: 'update',
|
||||
});
|
||||
|
||||
await context.store.put(CACHE_KEY, {
|
||||
webhookId: response.id,
|
||||
});
|
||||
},
|
||||
async onDisable(context) {
|
||||
const cachedWebhookData = (await context.store.get(CACHE_KEY)) as any;
|
||||
|
||||
if (cachedWebhookData) {
|
||||
await CopperApiService.deleteWebhook(
|
||||
context.auth,
|
||||
cachedWebhookData.webhookId
|
||||
).then(async () => {
|
||||
await context.store.delete(CACHE_KEY);
|
||||
});
|
||||
}
|
||||
},
|
||||
async run(context) {
|
||||
return [context.payload.body];
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
|
||||
import { CopperAuth } from '../common/constants';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
|
||||
const CACHE_KEY = 'copper_updated_project_trigger_key';
|
||||
|
||||
export const updatedProject = createTrigger({
|
||||
auth: CopperAuth,
|
||||
name: 'updatedProject',
|
||||
displayName: 'Updated Project',
|
||||
description: 'Triggers when a project is updated.',
|
||||
props: {},
|
||||
sampleData: {},
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
async onEnable(context) {
|
||||
const response = await CopperApiService.createWebhook(context.auth, {
|
||||
target: context.webhookUrl,
|
||||
type: 'project',
|
||||
event: 'update',
|
||||
});
|
||||
|
||||
await context.store.put(CACHE_KEY, {
|
||||
webhookId: response.id,
|
||||
});
|
||||
},
|
||||
async onDisable(context) {
|
||||
const cachedWebhookData = (await context.store.get(CACHE_KEY)) as any;
|
||||
|
||||
if (cachedWebhookData) {
|
||||
await CopperApiService.deleteWebhook(
|
||||
context.auth,
|
||||
cachedWebhookData.webhookId
|
||||
).then(async () => {
|
||||
await context.store.delete(CACHE_KEY);
|
||||
});
|
||||
}
|
||||
},
|
||||
async run(context) {
|
||||
return [context.payload.body];
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
|
||||
import { CopperApiService } from '../common/requests';
|
||||
import { CopperAuth } from '../common/constants';
|
||||
|
||||
const CACHE_KEY = 'copper_updated_task_trigger_key';
|
||||
|
||||
export const updatedTask = createTrigger({
|
||||
auth: CopperAuth,
|
||||
name: 'updatedTask',
|
||||
displayName: 'Updated Task',
|
||||
description: 'Triggers when a task is updated.',
|
||||
props: {},
|
||||
sampleData: {},
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
async onEnable(context) {
|
||||
const response = await CopperApiService.createWebhook(context.auth, {
|
||||
target: context.webhookUrl,
|
||||
type: 'task',
|
||||
event: 'update',
|
||||
});
|
||||
|
||||
await context.store.put(CACHE_KEY, {
|
||||
webhookId: response.id,
|
||||
});
|
||||
},
|
||||
async onDisable(context) {
|
||||
const cachedWebhookData = (await context.store.get(CACHE_KEY)) as any;
|
||||
|
||||
if (cachedWebhookData) {
|
||||
await CopperApiService.deleteWebhook(
|
||||
context.auth,
|
||||
cachedWebhookData.webhookId
|
||||
).then(async () => {
|
||||
await context.store.delete(CACHE_KEY);
|
||||
});
|
||||
}
|
||||
},
|
||||
async run(context) {
|
||||
return [context.payload.body];
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user