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,47 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { omniAuth } from '../common/auth';
import { makeRequest } from '../common/client';
import { HttpMethod } from '@activepieces/pieces-common';
import { modelIdDropdown } from '../common/props';
export const createADocument = createAction({
auth: omniAuth,
name: 'createADocument',
displayName: 'Create a document',
description: 'Creates a new document',
props: {
modelId: modelIdDropdown,
name: Property.ShortText({
displayName: 'Document Name',
description: 'The name of the document',
required: true,
}),
queryPresentations: Property.Array({
displayName: 'Query Presentations',
description:
"An array of queryPresentation objects, each representing a query in the document's workbook",
required: false,
}),
},
async run(context) {
const { modelId, name, queryPresentations } = context.propsValue;
const body: Record<string, unknown> = {
modelId,
name,
};
if (queryPresentations) {
body['queryPresentations'] = queryPresentations;
}
const response = await makeRequest(
context.auth.secret_text,
HttpMethod.POST,
'/documents',
body
);
return response;
},
});

View File

@@ -0,0 +1,158 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { omniAuth } from '../common/auth';
import { makeRequest } from '../common/client';
import { HttpMethod } from '@activepieces/pieces-common';
export const createASchedule = createAction({
auth: omniAuth,
name: 'createASchedule',
displayName: 'Create a schedule',
description:
'Creates a scheduled task for a dashboard with support for email, SFTP, and webhook destinations',
props: {
identifier: Property.ShortText({
displayName: 'Dashboard Identifier',
description:
' The string after /dashboards is the dashboards ID; for example: https://blobsrus.omniapp.co/dashboards/12db1a0a',
required: true,
}),
name: Property.ShortText({
displayName: 'Schedule Name',
description: 'The name of the schedule/task',
required: true,
}),
schedule: Property.ShortText({
displayName: 'Schedule (Cron Expression)',
description:
'Cron expression for the schedule (e.g., "0 9 ? * * *" for 9 AM UTC daily) https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-scheduled-rule-pattern.html',
required: true,
}),
timezone: Property.ShortText({
displayName: 'Timezone',
description:
'Timezone for the schedule (e.g., "UTC", "America/New_York") Refer : https://data.iana.org/time-zones/tzdb-2021a/zone1970.tab',
required: true,
}),
format: Property.StaticDropdown({
displayName: 'Format',
description: 'Output format for the schedule',
options: {
disabled: false,
options: [
{ label: 'Link Only', value: 'link_only' },
{ label: 'PDF', value: 'pdf' },
{ label: 'PNG', value: 'png' },
{ label: 'CSV', value: 'csv' },
{ label: 'XLSX', value: 'xlsx' },
{ label: 'JSON', value: 'json' },
],
},
required: true,
}),
destinationType: Property.StaticDropdown({
displayName: 'Destination Type',
description: 'Type of destination for the schedule',
options: {
disabled: false,
options: [
{ label: 'Email', value: 'email' },
{ label: 'Webhook', value: 'webhook' },
{ label: 'SFTP', value: 'sftp' },
],
},
required: true,
}),
url: Property.ShortText({
displayName: 'URL',
description: 'Webhook URL (required when destinationType is webhook)',
required: false,
}),
recipients: Property.Array({
displayName: 'Recipients',
description: 'Email recipients (required when destinationType is email)',
required: false,
}),
subject: Property.ShortText({
displayName: 'Email Subject',
description: 'Subject line for email delivery',
required: false,
}),
paperFormat: Property.StaticDropdown({
displayName: 'Paper Format',
description: 'Paper format for PDF output',
options: {
disabled: false,
options: [
{ label: 'Letter', value: 'letter' },
{ label: 'A4', value: 'a4' },
],
},
required: false,
}),
paperOrientation: Property.StaticDropdown({
displayName: 'Paper Orientation',
description: 'Paper orientation for PDF output',
options: {
disabled: false,
options: [
{ label: 'Portrait', value: 'portrait' },
{ label: 'Landscape', value: 'landscape' },
],
},
required: false,
}),
},
async run(context) {
const {
identifier,
name,
schedule,
timezone,
format,
destinationType,
url,
recipients,
subject,
paperFormat,
paperOrientation,
} = context.propsValue;
const body: Record<string, unknown> = {
identifier,
name,
schedule,
timezone,
format,
destinationType,
};
if (url) {
body['url'] = url;
}
if (recipients) {
body['recipients'] = recipients;
}
if (subject) {
body['subject'] = subject;
}
if (paperFormat) {
body['paperFormat'] = paperFormat;
}
if (paperOrientation) {
body['paperOrientation'] = paperOrientation;
}
const response = await makeRequest(
context.auth.secret_text,
HttpMethod.POST,
'/schedules',
body
);
return response;
},
});

View File

@@ -0,0 +1,27 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { omniAuth } from '../common/auth';
import { makeRequest } from '../common/client';
import { HttpMethod } from '@activepieces/pieces-common';
import { documentIdDropdown } from '../common/props';
export const deleteADocument = createAction({
auth: omniAuth,
name: 'deleteADocument',
displayName: 'Delete a document',
description: 'Deletes the specified document and places it in the Trash',
props: {
documentId: documentIdDropdown,
},
async run(context) {
const { documentId } = context.propsValue;
const response = await makeRequest(
context.auth.secret_text,
HttpMethod.DELETE,
`/documents/${documentId}`,
{}
);
return response;
},
});

View File

@@ -0,0 +1,27 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { omniAuth } from '../common/auth';
import { makeRequest } from '../common/client';
import { HttpMethod } from '@activepieces/pieces-common';
import { scheduleIdDropdown } from '../common/props';
export const deleteASchedule = createAction({
auth: omniAuth,
name: 'deleteASchedule',
displayName: 'Delete a schedule',
description: 'Deletes a schedule using its UUID',
props: {
scheduleId: scheduleIdDropdown,
},
async run(context) {
const { scheduleId } = context.propsValue;
const response = await makeRequest(
context.auth.secret_text,
HttpMethod.DELETE,
`/schedules/${scheduleId}`,
{}
);
return response;
},
});

View File

@@ -0,0 +1,167 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { omniAuth } from '../common/auth';
import { makeRequest } from '../common/client';
import { HttpMethod } from '@activepieces/pieces-common';
import { scheduleIdDropdown } from '../common/props';
export const editSchedule = createAction({
auth: omniAuth,
name: 'editSchedule',
displayName: 'Edit schedule',
description:
'Updates a scheduled task. Only properties included will be updated',
props: {
scheduleId: scheduleIdDropdown,
name: Property.ShortText({
displayName: 'Schedule Name',
description: 'The name of the schedule/task',
required: true,
}),
schedule: Property.ShortText({
displayName: 'Schedule (Cron Expression)',
description:
'Cron expression for the schedule (e.g., "0 9 ? * * *" for 9 AM UTC daily) https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-scheduled-rule-pattern.html',
required: true,
}),
timezone: Property.ShortText({
displayName: 'Timezone',
description:
'Timezone for the schedule (e.g., "UTC", "America/New_York") Refer : https://data.iana.org/time-zones/tzdb-2021a/zone1970.tab',
required: true,
}),
format: Property.StaticDropdown({
displayName: 'Format',
description: 'Output format for the schedule',
options: {
disabled: false,
options: [
{ label: 'Link Only', value: 'link_only' },
{ label: 'PDF', value: 'pdf' },
{ label: 'PNG', value: 'png' },
{ label: 'CSV', value: 'csv' },
{ label: 'XLSX', value: 'xlsx' },
{ label: 'JSON', value: 'json' },
],
},
required: true,
}),
destinationType: Property.StaticDropdown({
displayName: 'Destination Type',
description: 'Type of destination for the schedule',
options: {
disabled: false,
options: [
{ label: 'Email', value: 'email' },
{ label: 'Webhook', value: 'webhook' },
{ label: 'SFTP', value: 'sftp' },
],
},
required: true,
}),
url: Property.ShortText({
displayName: 'URL',
description: 'Webhook URL (required when destinationType is webhook)',
required: false,
}),
recipients: Property.Array({
displayName: 'Recipients',
description: 'Email recipients (required when destinationType is email)',
required: false,
}),
subject: Property.ShortText({
displayName: 'Email Subject',
description: 'Subject line for email delivery',
required: false,
}),
paperFormat: Property.StaticDropdown({
displayName: 'Paper Format',
description: 'Paper format for PDF output',
options: {
disabled: false,
options: [
{ label: 'Letter', value: 'letter' },
{ label: 'A4', value: 'a4' },
],
},
required: false,
}),
paperOrientation: Property.StaticDropdown({
displayName: 'Paper Orientation',
description: 'Paper orientation for PDF output',
options: {
disabled: false,
options: [
{ label: 'Portrait', value: 'portrait' },
{ label: 'Landscape', value: 'landscape' },
],
},
required: false,
}),
},
async run(context) {
const {
scheduleId,
name,
schedule,
timezone,
format,
destinationType,
url,
recipients,
subject,
paperFormat,
paperOrientation,
} = context.propsValue;
const body: Record<string, unknown> = {};
if (name) {
body['name'] = name;
}
if (schedule) {
body['schedule'] = schedule;
}
if (timezone) {
body['timezone'] = timezone;
}
if (format) {
body['format'] = format;
}
if (destinationType) {
body['destinationType'] = destinationType;
}
if (url) {
body['url'] = url;
}
if (recipients) {
body['recipients'] = recipients;
}
if (subject) {
body['subject'] = subject;
}
if (paperFormat) {
body['paperFormat'] = paperFormat;
}
if (paperOrientation) {
body['paperOrientation'] = paperOrientation;
}
const response = await makeRequest(
context.auth.secret_text,
HttpMethod.PUT,
`/schedules/${scheduleId}`,
body
);
return response;
},
});

View File

@@ -0,0 +1,87 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { omniAuth } from '../common/auth';
import { makeRequest } from '../common/client';
import { HttpMethod } from '@activepieces/pieces-common';
import { modelIdDropdown } from '../common/props';
export const generateQuery = createAction({
auth: omniAuth,
name: 'generateQuery',
displayName: 'Generate query',
description:
'Transforms a natural language description into a structured Omni query',
props: {
modelId: modelIdDropdown,
prompt: Property.LongText({
displayName: 'Prompt',
description:
'The natural language description of the query you want to generate (e.g., "Show me all users who signed up in the last month")',
required: true,
}),
currentTopicName: Property.ShortText({
displayName: 'Current Topic Name',
description:
'The name of the topic to use as context for the query generation',
required: false,
}),
branchId: Property.ShortText({
displayName: 'Branch ID',
description:
'The ID of the model branch to use for query generation. If not provided, the main branch will be used',
required: false,
}),
contextQuery: Property.Json({
displayName: 'Context Query',
description:
'The query object to provide as context. This can be used to reference previous queries or provide additional context for the generation',
required: false,
}),
structured: Property.Checkbox({
displayName: 'Structured Output',
description:
'When enabled, the API will return a more strictly structured query format',
required: false,
defaultValue: false,
}),
},
async run(context) {
const {
modelId,
prompt,
currentTopicName,
branchId,
contextQuery,
structured,
} = context.propsValue;
const body: Record<string, unknown> = {
modelId,
prompt,
};
if (currentTopicName) {
body['currentTopicName'] = currentTopicName;
}
if (branchId) {
body['branchId'] = branchId;
}
if (contextQuery) {
body['contextQuery'] = contextQuery;
}
if (structured) {
body['structured'] = structured;
}
const response = await makeRequest(
context.auth.secret_text,
HttpMethod.POST,
'/ai/generate-query',
body
);
return response;
},
});

View File

@@ -0,0 +1,54 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { omniAuth } from '../common/auth';
import { makeRequest } from '../common/client';
import { HttpMethod } from '@activepieces/pieces-common';
import { documentIdDropdown } from '../common/props';
export const moveDocument = createAction({
auth: omniAuth,
name: 'moveDocument',
displayName: 'Move document',
description: 'Moves a document to a new folder or to the root level',
props: {
identifier: documentIdDropdown,
folderPath: Property.ShortText({
displayName: 'Folder Path',
description:
'The path of the destination folder. Use null to move the document to the root level (no folder)',
required: true,
}),
scope: Property.StaticDropdown({
displayName: 'Scope',
description:
'Optional sharing scope for the document. If not provided, the scope will be computed',
options: {
disabled: false,
options: [
{ label: 'Organization', value: 'organization' },
{ label: 'Restricted', value: 'restricted' },
],
},
required: false,
}),
},
async run(context) {
const { identifier, folderPath, scope } = context.propsValue;
const body: Record<string, unknown> = {
folderPath: folderPath === 'null' ? null : folderPath,
};
if (scope) {
body['scope'] = scope;
}
const response = await makeRequest(
context.auth.secret_text,
HttpMethod.PUT,
`/documents/${identifier}/move`,
body
);
return response;
},
});

View File

@@ -0,0 +1,113 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { omniAuth } from '../common/auth';
import { makeRequest } from '../common/client';
import { HttpMethod } from '@activepieces/pieces-common';
export const runQuery = createAction({
auth: omniAuth,
name: 'runQuery',
displayName: 'Run query',
description:
'Runs a query and returns the results as base64 encoded Apache Arrow table or in specified format',
props: {
query: Property.Json({
displayName: 'Query',
description:
"A JSON object representing the query to be run. You can retrieve a query's JSON object from an Omni workbook by opening the Inspector panel (Option + 9 on Mac, Alt + 9 on Windows) and copying the Query structure",
required: true,
}),
limit: Property.Number({
displayName: 'Limit',
description:
'Number of rows to be returned (defaults to 1000). Set to null for unlimited results',
required: false,
}),
cache: Property.StaticDropdown({
displayName: 'Cache Policy',
description: 'Optional cache policy to control how query caching behaves',
options: {
disabled: false,
options: [
{ label: 'Standard', value: 'Standard' },
{ label: 'Skip Requery (default)', value: 'SkipRequery' },
{ label: 'Skip Cache', value: 'SkipCache' },
],
},
required: false,
}),
formatResults: Property.Checkbox({
displayName: 'Format Results',
description:
'Apply formatting to numeric and currency values (currency symbols and thousand separators)',
required: false,
defaultValue: true,
}),
resultType: Property.StaticDropdown({
displayName: 'Result Type',
description:
'Specifies the format of query results. If omitted, returns base64 encoded Apache Arrow format',
options: {
disabled: false,
options: [
{ label: 'CSV', value: 'csv' },
{ label: 'JSON', value: 'json' },
{ label: 'XLSX', value: 'xlsx' },
],
},
required: false,
}),
planOnly: Property.Checkbox({
displayName: 'Plan Only',
description:
'If enabled, returns the query execution plan without running the query',
required: false,
defaultValue: false,
}),
userId: Property.ShortText({
displayName: 'User ID',
description: 'Optional UUID to run the query as a specific user',
required: false,
}),
},
async run(context) {
const { query, limit, cache, formatResults, resultType, planOnly, userId } =
context.propsValue;
const body: Record<string, unknown> = {
query,
};
if (limit !== undefined && limit !== null) {
body['limit'] = limit;
}
if (cache) {
body['cache'] = cache;
}
if (formatResults !== undefined) {
body['formatResults'] = formatResults;
}
if (resultType) {
body['resultType'] = resultType;
}
if (planOnly) {
body['planOnly'] = planOnly;
}
if (userId) {
body['userId'] = userId;
}
const response = await makeRequest(
context.auth.secret_text,
HttpMethod.POST,
'/query/run',
body
);
return response;
},
});

View File

@@ -0,0 +1,29 @@
import { PieceAuth } from '@activepieces/pieces-framework';
import { makeRequest } from './client';
import { HttpMethod } from '@activepieces/pieces-common';
export const omniAuth = PieceAuth.SecretText({
displayName: 'Omni API Key',
description: `
`,
required: true,
validate: async ({ auth }) => {
if (auth) {
try {
await makeRequest(auth as string, HttpMethod.GET, '/models', {});
return {
valid: true,
};
} catch (error) {
return {
valid: false,
error: 'Invalid Api Key',
};
}
}
return {
valid: false,
error: 'Invalid Api Key',
};
},
});

View File

@@ -0,0 +1,25 @@
import { HttpMethod, httpClient } from '@activepieces/pieces-common';
export const BASE_URL = `https://blobsrus.omniapp.co/api/v1`;
export async function makeRequest(
api_key: string,
method: HttpMethod,
path: string,
body?: unknown
) {
try {
const response = await httpClient.sendRequest({
method,
url: `${BASE_URL}${path}`,
headers: {
Authorization: `Bearer ${api_key}`,
'Content-Type': 'application/json',
},
body,
});
return response.body;
} catch (error: any) {
throw new Error(`Unexpected error: ${error.message || String(error)}`);
}
}

View File

@@ -0,0 +1,127 @@
import { HttpMethod } from '@activepieces/pieces-common';
import { makeRequest } from './client';
import { Property } from '@activepieces/pieces-framework';
import { omniAuth } from './auth';
export const modelIdDropdown = Property.Dropdown({
auth: omniAuth,
displayName: 'Model ID',
description: 'Select the model containing the database',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
options: [],
placeholder: 'Please connect your account first',
};
}
try {
const models = await makeRequest(
auth.secret_text,
HttpMethod.GET,
'/models',
{}
);
return {
disabled: false,
options: models.records.map((model: any) => ({
label: model.name,
value: model.id,
})),
};
} catch (error) {
return {
disabled: true,
options: [],
placeholder: 'Error loading teams',
};
}
},
});
export const documentIdDropdown = Property.Dropdown({
auth: omniAuth,
displayName: 'Document',
description: 'Select the document',
required: true,
refreshers: ['auth'],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
options: [],
placeholder: 'Please connect your account first',
};
}
try {
const documents = await makeRequest(
auth.secret_text,
HttpMethod.GET,
`/documents`,
{}
);
return {
disabled: false,
options: documents.records.map((document: any) => ({
label: document.name,
value: document.identifier,
})),
};
} catch (error) {
return {
disabled: true,
options: [],
placeholder: 'Error loading documents',
};
}
},
});
export const scheduleIdDropdown = Property.Dropdown({
auth: omniAuth,
displayName: 'Schedule',
description: 'Select the schedule',
required: true,
refreshers: ['auth'],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
options: [],
placeholder: 'Please connect your account first',
};
}
try {
const schedules = await makeRequest(
auth.secret_text,
HttpMethod.GET,
`/schedules`,
{}
);
return {
disabled: false,
options: schedules.records.map((schedule: any) => ({
label: schedule.name,
value: schedule.identifier,
})),
};
} catch (error) {
return {
disabled: true,
options: [],
placeholder: 'Error loading schedules',
};
}
},
});