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,185 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { zohoDeskAuth } from '../common/auth';
|
||||
import { zohoDeskApiCall } from '../common';
|
||||
import { departmentId, organizationId } from '../common/props';
|
||||
|
||||
export const createTicketAction = createAction({
|
||||
auth: zohoDeskAuth,
|
||||
name: 'create_ticket',
|
||||
displayName: 'Create Ticket',
|
||||
description: 'Creates a new ticket.',
|
||||
props: {
|
||||
orgId: organizationId({ displayName: 'Organization', required: true }),
|
||||
departmentId: departmentId({ displayName: 'Department', required: true }),
|
||||
contactId: Property.ShortText({
|
||||
displayName: 'Contact ID',
|
||||
required: false,
|
||||
description: 'ID of the contact raising the ticket',
|
||||
}),
|
||||
subject: Property.ShortText({
|
||||
displayName: 'Subject',
|
||||
required: true,
|
||||
description: 'Subject of the ticket',
|
||||
}),
|
||||
description: Property.LongText({
|
||||
displayName: 'Description',
|
||||
required: true,
|
||||
description: 'Description of the issue in the ticket',
|
||||
}),
|
||||
email: Property.ShortText({
|
||||
displayName: 'Email',
|
||||
required: false,
|
||||
description: 'Email address of the contact raising the ticket',
|
||||
}),
|
||||
phone: Property.ShortText({
|
||||
displayName: 'Phone',
|
||||
required: false,
|
||||
description: 'Phone number of the contact raising the ticket',
|
||||
}),
|
||||
status: Property.StaticDropdown({
|
||||
displayName: 'Status',
|
||||
required: false,
|
||||
description: 'Status of the ticket',
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Open', value: 'Open' },
|
||||
{ label: 'On Hold', value: 'On Hold' },
|
||||
{ label: 'Escalated', value: 'Escalated' },
|
||||
{ label: 'Closed', value: 'Closed' },
|
||||
],
|
||||
},
|
||||
defaultValue: 'Open',
|
||||
}),
|
||||
priority: Property.StaticDropdown({
|
||||
displayName: 'Priority',
|
||||
required: false,
|
||||
description: 'Priority of the ticket',
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Low', value: 'Low' },
|
||||
{ label: 'Medium', value: 'Medium' },
|
||||
{ label: 'High', value: 'High' },
|
||||
{ label: 'Urgent', value: 'Urgent' },
|
||||
],
|
||||
},
|
||||
defaultValue: 'Medium',
|
||||
}),
|
||||
category: Property.ShortText({
|
||||
displayName: 'Category',
|
||||
required: false,
|
||||
description: 'Category of the ticket',
|
||||
}),
|
||||
subCategory: Property.ShortText({
|
||||
displayName: 'Sub Category',
|
||||
required: false,
|
||||
description: 'Sub-category of the ticket',
|
||||
}),
|
||||
dueDate: Property.DateTime({
|
||||
displayName: 'Due Date',
|
||||
required: false,
|
||||
description: 'Due date for the ticket (ISO format)',
|
||||
}),
|
||||
channel: Property.StaticDropdown({
|
||||
displayName: 'Channel',
|
||||
required: false,
|
||||
description: 'Channel through which the ticket is created',
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Email', value: 'Email' },
|
||||
{ label: 'Phone', value: 'Phone' },
|
||||
{ label: 'Chat', value: 'Chat' },
|
||||
{ label: 'Web', value: 'Web' },
|
||||
{ label: 'Social', value: 'Social' },
|
||||
],
|
||||
},
|
||||
defaultValue: 'Web',
|
||||
}),
|
||||
assigneeId: Property.ShortText({
|
||||
displayName: 'Assignee ID',
|
||||
required: false,
|
||||
description: 'ID of the agent to whom the ticket is assigned',
|
||||
}),
|
||||
productId: Property.ShortText({
|
||||
displayName: 'Product ID',
|
||||
required: false,
|
||||
description: 'ID of the product to which the ticket belongs',
|
||||
}),
|
||||
classification: Property.ShortText({
|
||||
displayName: 'Classification',
|
||||
required: false,
|
||||
description: 'Classification of the ticket',
|
||||
}),
|
||||
language: Property.ShortText({
|
||||
displayName: 'Language',
|
||||
required: false,
|
||||
description: 'Language of the ticket',
|
||||
defaultValue: 'English',
|
||||
}),
|
||||
entitySkills: Property.Array({
|
||||
displayName: 'Entity Skills',
|
||||
required: false,
|
||||
description: 'Array of skill IDs associated with the ticket',
|
||||
}),
|
||||
customFields: Property.Object({
|
||||
displayName: 'Custom Fields',
|
||||
required: false,
|
||||
description: 'Custom fields in the ticket',
|
||||
}),
|
||||
},
|
||||
async run({ propsValue, auth }) {
|
||||
const {
|
||||
orgId,
|
||||
departmentId,
|
||||
contactId,
|
||||
subject,
|
||||
description,
|
||||
email,
|
||||
phone,
|
||||
status,
|
||||
priority,
|
||||
category,
|
||||
subCategory,
|
||||
dueDate,
|
||||
channel,
|
||||
assigneeId,
|
||||
productId,
|
||||
classification,
|
||||
language,
|
||||
entitySkills,
|
||||
customFields,
|
||||
} = propsValue;
|
||||
|
||||
const requestBody: Record<string, any> = {
|
||||
departmentId,
|
||||
subject,
|
||||
description,
|
||||
};
|
||||
|
||||
if (contactId) requestBody['contactId'] = contactId;
|
||||
if (email) requestBody['email'] = email;
|
||||
if (phone) requestBody['phone'] = phone;
|
||||
if (status) requestBody['status'] = status;
|
||||
if (priority) requestBody['priority'] = priority;
|
||||
if (category) requestBody['category'] = category;
|
||||
if (subCategory) requestBody['subCategory'] = subCategory;
|
||||
if (dueDate) requestBody['dueDate'] = dueDate;
|
||||
if (channel) requestBody['channel'] = channel;
|
||||
if (assigneeId) requestBody['assigneeId'] = assigneeId;
|
||||
if (productId) requestBody['productId'] = productId;
|
||||
if (classification) requestBody['classification'] = classification;
|
||||
if (language) requestBody['language'] = language;
|
||||
if (entitySkills && entitySkills.length > 0) requestBody['entitySkills'] = entitySkills;
|
||||
if (customFields) requestBody['cf'] = customFields;
|
||||
|
||||
const response = await zohoDeskApiCall({
|
||||
auth,
|
||||
method: HttpMethod.POST,
|
||||
resourceUri: '/tickets',
|
||||
orgId,
|
||||
body: requestBody,
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { zohoDeskApiCall } from '../common';
|
||||
import { zohoDeskAuth } from '../common/auth';
|
||||
import { organizationId } from '../common/props';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { isNil } from '@activepieces/shared';
|
||||
|
||||
export const findContactAction = createAction({
|
||||
auth: zohoDeskAuth,
|
||||
name: 'find-contact',
|
||||
displayName: 'Find Contact',
|
||||
description: 'Finds an existing contact by email.',
|
||||
props: {
|
||||
orgId: organizationId({ displayName: 'Organization', required: true }),
|
||||
email: Property.ShortText({
|
||||
displayName: 'Email',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const response = await zohoDeskApiCall({
|
||||
auth: context.auth,
|
||||
method: HttpMethod.GET,
|
||||
resourceUri: '/contacts/search',
|
||||
orgId: context.propsValue.orgId,
|
||||
query: {
|
||||
email: context.propsValue.email,
|
||||
},
|
||||
});
|
||||
|
||||
if (isNil(response) || response === '') {
|
||||
return {
|
||||
found: false,
|
||||
result: [],
|
||||
};
|
||||
}
|
||||
|
||||
const contacts = response as { data: Record<string, any>[] };
|
||||
|
||||
return {
|
||||
found: contacts.data.length > 0,
|
||||
result: contacts.data,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { zohoDeskAuth } from '../common/auth';
|
||||
import { zohoDeskApiCall } from '../common';
|
||||
import { organizationId } from '../common/props';
|
||||
|
||||
export const listTicketsAction = createAction({
|
||||
auth: zohoDeskAuth,
|
||||
name: 'list_tickets',
|
||||
description: 'List tickets',
|
||||
displayName: 'List tickets.',
|
||||
props: {
|
||||
orgId: organizationId({ displayName: 'Organization', required: true }),
|
||||
include: Property.StaticMultiSelectDropdown({
|
||||
displayName: 'include',
|
||||
required: false,
|
||||
description: 'Additional information related to the tickets.',
|
||||
options: {
|
||||
disabled: false,
|
||||
options: [
|
||||
{ label: 'contacts', value: 'contacts' },
|
||||
{ label: 'products', value: 'products' },
|
||||
{ label: 'departments', value: 'departments' },
|
||||
{ label: 'team', value: 'team' },
|
||||
{ label: 'isRead', value: 'isRead' },
|
||||
{ label: 'assignee', value: 'assignee' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
async run({ propsValue, auth }) {
|
||||
const queryParams: Record<string, string> = {};
|
||||
|
||||
if (propsValue.include) queryParams['include'] = propsValue.include.join(',');
|
||||
|
||||
const response = await zohoDeskApiCall({
|
||||
auth,
|
||||
method: HttpMethod.GET,
|
||||
resourceUri: '/tickets',
|
||||
orgId: propsValue.orgId,
|
||||
query: queryParams,
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import { PieceAuth, Property } from '@activepieces/pieces-framework';
|
||||
|
||||
export const zohoDeskAuth = PieceAuth.OAuth2({
|
||||
props: {
|
||||
location: Property.StaticDropdown({
|
||||
displayName: 'Location',
|
||||
description: 'The location of your Zoho Desk account.',
|
||||
required: true,
|
||||
options: {
|
||||
options: [
|
||||
{
|
||||
label: 'zoho.eu (Europe)',
|
||||
value: 'zoho.eu',
|
||||
},
|
||||
{
|
||||
label: 'zoho.com (United States)',
|
||||
value: 'zoho.com',
|
||||
},
|
||||
{
|
||||
label: 'zoho.com.au (Australia)',
|
||||
value: 'zoho.com.au',
|
||||
},
|
||||
{
|
||||
label: 'zoho.jp (Japan)',
|
||||
value: 'zoho.jp',
|
||||
},
|
||||
{
|
||||
label: 'zoho.in (India)',
|
||||
value: 'zoho.in',
|
||||
},
|
||||
{
|
||||
label: 'zohocloud.ca (Canada)',
|
||||
value: 'zohocloud.ca',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
description: 'Authentication for Zoho Desk',
|
||||
scope: [
|
||||
'Desk.tickets.ALL',
|
||||
'Desk.tasks.ALL',
|
||||
'Desk.settings.ALL',
|
||||
'Desk.events.ALL',
|
||||
'Desk.contacts.READ',
|
||||
'Desk.contacts.WRITE',
|
||||
'Desk.contacts.UPDATE',
|
||||
'Desk.contacts.CREATE',
|
||||
'Desk.basic.READ',
|
||||
'Desk.basic.CREATE',
|
||||
'Desk.search.READ',
|
||||
'Desk.articles.READ',
|
||||
'Desk.articles.CREATE',
|
||||
'Desk.articles.UPDATE',
|
||||
'Desk.articles.DELETE',
|
||||
],
|
||||
authUrl: 'https://accounts.{location}/oauth/v2/auth',
|
||||
tokenUrl: 'https://accounts.{location}/oauth/v2/token',
|
||||
required: true,
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
httpClient,
|
||||
HttpHeaders,
|
||||
HttpMessageBody,
|
||||
HttpMethod,
|
||||
HttpRequest,
|
||||
QueryParams,
|
||||
} from '@activepieces/pieces-common';
|
||||
import { PiecePropValueSchema } from '@activepieces/pieces-framework';
|
||||
import { zohoDeskAuth } from './auth';
|
||||
|
||||
export type ZohoDeskApiCallParams = {
|
||||
auth: PiecePropValueSchema<typeof zohoDeskAuth>;
|
||||
orgId?: string;
|
||||
method: HttpMethod;
|
||||
resourceUri: string;
|
||||
query?: Record<string, string | number | string[] | undefined>;
|
||||
body?: any;
|
||||
};
|
||||
|
||||
export async function zohoDeskApiCall<T extends HttpMessageBody>({
|
||||
auth,
|
||||
orgId,
|
||||
method,
|
||||
resourceUri,
|
||||
query,
|
||||
body,
|
||||
}: ZohoDeskApiCallParams): Promise<T> {
|
||||
const location = auth.props?.['location'] ?? 'zoho.com';
|
||||
const baseUrl = `https://desk.${location}/api/v1`;
|
||||
const qs: QueryParams = {};
|
||||
|
||||
if (query) {
|
||||
for (const [key, value] of Object.entries(query)) {
|
||||
if (value !== null && value !== undefined) {
|
||||
qs[key] = String(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const headers: HttpHeaders = {
|
||||
Authorization: `Zoho-oauthtoken ${auth.access_token}`,
|
||||
};
|
||||
|
||||
if (orgId) {
|
||||
headers['orgId'] = orgId;
|
||||
}
|
||||
|
||||
const request: HttpRequest = {
|
||||
method,
|
||||
url: baseUrl + resourceUri,
|
||||
headers,
|
||||
queryParams: qs,
|
||||
body,
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest<T>(request);
|
||||
return response.body;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { PiecePropValueSchema, Property } from '@activepieces/pieces-framework';
|
||||
import { zohoDeskApiCall } from '.';
|
||||
import { zohoDeskAuth } from './auth';
|
||||
|
||||
interface DropdownParams {
|
||||
displayName: string;
|
||||
description?: string;
|
||||
required: boolean;
|
||||
}
|
||||
|
||||
export const organizationId = (params: DropdownParams) =>
|
||||
Property.Dropdown({
|
||||
auth: zohoDeskAuth,
|
||||
displayName: params.displayName,
|
||||
description: params.description,
|
||||
refreshers: [],
|
||||
required: params.required,
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
placeholder: 'Please connect your account first.',
|
||||
options: [],
|
||||
disabled: true,
|
||||
};
|
||||
}
|
||||
const authValue = auth as PiecePropValueSchema<typeof zohoDeskAuth>;
|
||||
|
||||
const response = await zohoDeskApiCall<{ data: { id: string; companyName: string }[] }>({
|
||||
auth: authValue,
|
||||
method: HttpMethod.GET,
|
||||
resourceUri: '/organizations',
|
||||
});
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
options: response.data.map((org) => {
|
||||
return {
|
||||
label: org.companyName,
|
||||
value: org.id,
|
||||
};
|
||||
}),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export const departmentId = (params: DropdownParams) =>
|
||||
Property.Dropdown({
|
||||
auth: zohoDeskAuth,
|
||||
displayName: params.displayName,
|
||||
description: params.description,
|
||||
refreshers: ['orgId'],
|
||||
required: params.required,
|
||||
options: async ({ auth, orgId }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
placeholder: 'Please connect your account first.',
|
||||
options: [],
|
||||
disabled: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (!orgId) {
|
||||
return {
|
||||
placeholder: 'Please select organization first.',
|
||||
options: [],
|
||||
disabled: true,
|
||||
};
|
||||
}
|
||||
|
||||
const authValue = auth as PiecePropValueSchema<typeof zohoDeskAuth>;
|
||||
|
||||
const response = await zohoDeskApiCall<{ data: { id: string; name: string }[] }>({
|
||||
auth: authValue,
|
||||
method: HttpMethod.GET,
|
||||
resourceUri: `/departments`,
|
||||
orgId: orgId as string,
|
||||
});
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
options: response.data.map((department) => {
|
||||
return {
|
||||
label: department.name,
|
||||
value: department.id,
|
||||
};
|
||||
}),
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user