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:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,132 @@
import { zuoraAuth } from '../../';
import { createAction, Property } from '@activepieces/pieces-framework';
import { getAccessToken } from '../common';
import {
AuthenticationType,
httpClient,
HttpMethod,
HttpRequest,
} from '@activepieces/pieces-common';
export const createInvoiceAction = createAction({
auth: zuoraAuth,
name: 'create-invoice',
displayName: 'Create Invoice',
description: 'Create a standalone invoice.',
props: {
accountNumber: Property.ShortText({
displayName: 'Customer Account Number',
description:
'The number of the customer account associated with the invoice.',
required: true,
}),
autoPay: Property.Checkbox({
displayName: 'Auto Pay?',
description:
'Whether invoices are automatically picked up for processing in the corresponding payment run.',
required: false,
}),
comments: Property.LongText({
displayName: 'Comments',
required: false,
}),
dueDate: Property.DateTime({
displayName: 'Due Date',
description: 'Provide YYYY-MM-DD format.',
required: true,
}),
invoiceDate: Property.DateTime({
displayName: 'Invoice Date',
required: true,
description: 'Provide YYYY-MM-DD format.',
}),
invoiceItems: Property.Array({
displayName: 'Invoice Items',
required: false,
properties: {
productRatePlanChargeId: Property.ShortText({
displayName: 'Product Rate Plan Charge ID',
description:
'The ID of the product rate plan charge that the invoice item is created from.You can use `Find Product Rate Plan` action to search associate rate plan charge.',
required: true,
}),
amount: Property.Number({
displayName: 'Amount',
required: true,
}),
description: Property.LongText({
displayName: 'Description',
required: false,
}),
purchaseOrderNumber: Property.ShortText({
displayName: 'Purchase Order Number',
required: false,
}),
quantity: Property.ShortText({
displayName: 'Quantity',
required: false,
}),
serviceStartDate: Property.ShortText({
displayName: 'Service Start Date',
description: 'Provide YYYY-MM-DD format.',
required: true,
}),
serviceEndDate: Property.ShortText({
displayName: 'Service End Date',
description: 'Provide YYYY-MM-DD format.',
required: false,
}),
},
}),
status: Property.StaticDropdown({
displayName: 'Status',
required: false,
defaultValue: 'Draft',
options: {
disabled: false,
options: [
{ label: 'Draft', value: 'Draft' },
{ label: 'Posted', value: 'Posted' },
],
},
}),
},
async run(context) {
const { accountNumber, autoPay, comments, dueDate, invoiceDate, status } =
context.propsValue;
const invoiceItems = context.propsValue.invoiceItems as InvoiceItemInput[];
const token = await getAccessToken(context.auth);
const body = {
accountNumber,
autoPay,
comments,
dueDate,
invoiceDate,
status,
invoiceItems,
};
const request: HttpRequest = {
method: HttpMethod.POST,
url: `${context.auth.props.environment}/v1/invoices`,
authentication: { type: AuthenticationType.BEARER_TOKEN, token },
body,
};
const response = await httpClient.sendRequest(request);
return response.body;
},
});
type InvoiceItemInput = {
productRatePlanChargeId: string;
amount: string;
description: string;
purchaseOrderNumber: string;
quantity: string;
serviceStartDate: string;
serviceEndDate: string;
};

View File

@@ -0,0 +1,38 @@
import { zuoraAuth } from '../../';
import { createAction, Property } from '@activepieces/pieces-framework';
import { getAccessToken } from '../common';
import {
AuthenticationType,
httpClient,
HttpMethod,
HttpRequest,
} from '@activepieces/pieces-common';
export const findAccountAction = createAction({
auth: zuoraAuth,
name: 'find-account',
displayName: 'Find Customer Account',
description: 'Retrieves account based on name.',
props: {
name: Property.ShortText({
displayName: 'Account Name',
required: true,
}),
},
async run(context) {
const name = context.propsValue.name;
const token = await getAccessToken(context.auth);
const request: HttpRequest = {
method: HttpMethod.GET,
url: `${context.auth.props.environment}/object-query/accounts`,
authentication: { type: AuthenticationType.BEARER_TOKEN, token },
queryParams: {
'filter[]': `name.EQ:${name}`,
},
};
const response = await httpClient.sendRequest(request);
return response.body;
},
});

View File

@@ -0,0 +1,44 @@
import { zuoraAuth } from '../..';
import { createAction, Property } from '@activepieces/pieces-framework';
import { getAccessToken } from '../common';
import {
AuthenticationType,
httpClient,
HttpMethod,
HttpRequest,
} from '@activepieces/pieces-common';
export const findProductRatePlanAction = createAction({
auth: zuoraAuth,
name: 'find-product-rate-plan',
displayName: 'Find Product Rate Plan',
description: 'Retrieves product rate plan with charges.',
props: {
name: Property.ShortText({
displayName: 'Product Rate Plan Name',
description: 'i.e. StealthCo Premium',
required: true,
}),
productid: Property.ShortText({
displayName: 'Product ID',
required: true,
}),
},
async run(context) {
const name = context.propsValue.name;
const productid = context.propsValue.productid;
const token = await getAccessToken(context.auth);
const request: HttpRequest = {
method: HttpMethod.GET,
url: `${context.auth.props.environment}/object-query/product-rate-plans?filter[]=name.EQ:${name}&filter[]=productid.EQ:${productid}`,
authentication: { type: AuthenticationType.BEARER_TOKEN, token },
queryParams: {
'expand[]': 'productrateplancharges',
},
};
const response = await httpClient.sendRequest(request);
return response.body;
},
});

View File

@@ -0,0 +1,38 @@
import { zuoraAuth } from '../../';
import { createAction, Property } from '@activepieces/pieces-framework';
import { getAccessToken } from '../common';
import {
AuthenticationType,
httpClient,
HttpMethod,
HttpRequest,
} from '@activepieces/pieces-common';
export const findProductAction = createAction({
auth: zuoraAuth,
name: 'find-product',
displayName: 'Find Product',
description: 'Retrieves product based on sku.',
props: {
sku: Property.ShortText({
displayName: 'Product SKU',
required: true,
}),
},
async run(context) {
const sku = context.propsValue.sku;
const token = await getAccessToken(context.auth);
const request: HttpRequest = {
method: HttpMethod.GET,
url: `${context.auth.props.environment}/object-query/products`,
authentication: { type: AuthenticationType.BEARER_TOKEN, token },
queryParams: {
'filter[]': `sku.EQ:${sku}`,
},
};
const response = await httpClient.sendRequest(request);
return response.body;
},
});

View File

@@ -0,0 +1,104 @@
import { zuoraAuth } from '../../';
import {
AppConnectionValueForAuthProperty,
DropdownOption,
PiecePropValueSchema,
Property,
} from '@activepieces/pieces-framework';
import {
AuthenticationType,
httpClient,
HttpMethod,
HttpRequest,
QueryParams,
} from '@activepieces/pieces-common';
export async function queryAccounts(
auth: AppConnectionValueForAuthProperty<typeof zuoraAuth>
) {
const token = await getAccessToken(auth);
const result: Record<string, any>[] = [];
let cursor;
do {
const qs: QueryParams = {
pageSize: '50',
'fields[]': 'id,name',
'sort[]': 'updated_time.desc',
};
if (cursor) {
qs['cursor'] = cursor;
}
const request: HttpRequest = {
method: HttpMethod.GET,
url: `${auth.props.environment}/v2/accounts`,
queryParams: qs,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token,
},
};
const response = await httpClient.sendRequest(request);
result.push(...response.body['data']);
cursor = response.body['nextPage'];
} while (cursor);
return result;
}
export async function getAccessToken(
auth: AppConnectionValueForAuthProperty<typeof zuoraAuth>
): Promise<string> {
const request: HttpRequest = {
method: HttpMethod.POST,
url: `${auth.props.environment}/oauth/token`,
body: new URLSearchParams({
client_id: auth.props.clientId,
client_secret: auth.props.clientSecret,
grant_type: 'client_credentials',
}),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
};
const response = await httpClient.sendRequest(request);
return response.body['access_token'];
}
export const zuoraCommonProps = {
account_id: (displayName: string, description: string, required: boolean) =>
Property.Dropdown({
auth: zuoraAuth,
displayName,
description,
required,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
options: [],
placeholder: 'Please connect your account first',
};
}
const authValue = auth as AppConnectionValueForAuthProperty<typeof zuoraAuth>;
const accounts = await queryAccounts(authValue);
const options: DropdownOption<string>[] = [];
for (const account of accounts) {
options.push({
label: account['name'] ?? account['id'],
value: account['id'],
});
}
return {
disabled: false,
options,
};
},
}),
};