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,164 @@
import { googleSlidesAuth } from '../../index';
import { createAction, DynamicPropsValue, Property } from "@activepieces/pieces-framework";
import { getSlide, PageElement, batchUpdate, TableCell, TextElement } from '../commons/common';
import { google } from 'googleapis';
import { OAuth2Client } from 'googleapis-common';
function extractPlaceholders(content: string, fields: Record<string, any>, placeholder_format: string) {
const regex = placeholder_format === '[[]]'
? /\[\[([^\]]+)\]\]/g
: /\{\{([^}]+)\}\}/g;
const matches = content.match(regex);
if (matches) {
matches.forEach((match: string) => {
const matchValue = placeholder_format === '[[]]'
? match.replace(/[[\]]/g, '')
: match.replace(/[{}]/g, '');
const varName = matchValue.trim();
fields[matchValue] = Property.ShortText({
displayName: varName.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
description: `Value for "${placeholder_format === '[[]]' ? `[[${varName}]]` : `{{${varName}}}`}"`,
required: false,
});
});
}
}
export const generateFromTemplate = createAction({
name: 'generate_from_template',
displayName: 'Generate from template',
description: 'Generate a new slide from a template',
auth: googleSlidesAuth,
props: {
template_presentation_id: Property.ShortText({
displayName: 'Template presentation ID',
description: 'The ID of the templated presentation',
required: true,
}),
placeholder_format: Property.StaticDropdown({
displayName: 'Placeholder Format',
description: 'Choose the format of placeholders in your template',
required: true,
defaultValue: '{{}}',
options: {
disabled: false,
options: [
{ label: 'Curly Braces {{}}', value: '{{}}' },
{ label: 'Square Brackets [[]]', value: '[[]]' }
],
},
}),
table_data: Property.DynamicProperties({
auth: googleSlidesAuth,
displayName: 'Table Data',
required: true,
refreshers: ['template_presentation_id', 'placeholder_format'],
props: async ({auth, template_presentation_id, placeholder_format}) => {
if (!template_presentation_id || !auth)
return {};
const presentation = await getSlide(auth["access_token"] as unknown as string, template_presentation_id as unknown as string);
if (!presentation)
return {}
const fields = {
title: Property.ShortText({
displayName: 'Presentation Title',
description: 'Title of the new presentation',
defaultValue: `Copy of: ${presentation.title}`,
required: true,
})
} as DynamicPropsValue;
presentation.slides?.forEach(slide => {
slide.pageElements?.forEach((element: PageElement) => {
if (element.shape?.text?.textElements) {
element.shape.text.textElements.forEach(textElement => {
const content = textElement?.textRun?.content;
if (content) {
extractPlaceholders(content, fields, placeholder_format as unknown as string);
}
});
}
if (element.table) {
element.table.tableRows?.forEach(row => {
row.tableCells?.forEach((cell: TableCell) => {
if (cell.text?.textElements) {
cell.text.textElements.forEach((textElement: TextElement) => {
const content = textElement?.textRun?.content;
if (content) {
extractPlaceholders(content, fields, placeholder_format as unknown as string);
}
});
}
});
});
}
});
});
return fields;
}
})
},
async run(context) {
const { access_token } = context.auth;
const { template_presentation_id, placeholder_format, table_data } = context.propsValue;
try {
const authClient = new OAuth2Client();
authClient.setCredentials(context.auth);
const drive = google.drive({ version: 'v3', auth: authClient });
const copyResponse = await drive.files.copy({
fileId: template_presentation_id as string,
requestBody: {
name: table_data["title"] || "New Presentation"
},
supportsAllDrives: true
});
const newPresentationId = copyResponse.data.id;
if (!newPresentationId)
return
const requests = Object.entries(table_data)
.map(([key, value]): { replaceAllText: unknown } => {
const placeholder = placeholder_format === '[[]]'
? `[[${key}]]`
: `{{${key}}}`;
return {
replaceAllText: {
containsText: {
text: placeholder,
matchCase: true
},
replaceText: value as string
}
};
});
if (requests.length > 0) {
await batchUpdate(
access_token,
newPresentationId,
requests
);
}
return {
presentationId: newPresentationId,
presentationUrl: `https://docs.google.com/presentation/d/${newPresentationId}/edit`
};
} catch (error) {
console.error('Error creating presentation:', error);
throw error;
}
}
});

View File

@@ -0,0 +1,22 @@
import { googleSlidesAuth } from '../../index';
import { createAction, Property } from "@activepieces/pieces-framework";
import { getSlide } from "../commons/common";
export const getPresentation = createAction({
name: 'get_presentation',
displayName: 'Get Presentation',
description: 'Get all slides from a presentation',
auth: googleSlidesAuth,
props: {
presentation_id: Property.ShortText({
displayName: 'Presentation ID',
description: 'The ID of the presentation',
required: true,
})
},
async run(context) {
const { presentation_id } = context.propsValue;
const { access_token } = context.auth;
return await getSlide(access_token, presentation_id);
},
});

View File

@@ -0,0 +1,51 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { batchUpdate, getSlide, PageElement } from '../commons/common';
import { googleSlidesAuth } from '../..';
export const refreshSheetsCharts = createAction({
name: 'refresh_sheets_charts',
displayName: 'Refresh Sheets Charts',
description: 'Refresh all Google Sheets charts in the presentation',
auth: googleSlidesAuth,
props: {
presentation_id: Property.ShortText({
displayName: 'Presentation ID',
description: 'The ID of the presentation, between /d and /edit',
required: true,
}),
},
async run(context) {
const { presentation_id } = context.propsValue;
const { access_token } = context.auth;
const presentation = await getSlide(access_token, presentation_id);
const requests: { refreshSheetsChart: { objectId: string; }; }[] = [];
presentation.slides?.forEach((slide) => {
slide.pageElements?.forEach((element: PageElement) => {
if (element.sheetsChart) {
const refreshRequest = {
refreshSheetsChart: {
objectId: element.objectId
}
};
requests.push(refreshRequest);
}
});
});
if (requests.length > 0) {
const result = await batchUpdate(access_token, presentation_id, requests);
return {
success: true,
message: `Successfully refreshed ${requests.length} Google Sheets charts`,
result: result
};
} else {
return {
success: false,
message: 'No Google Sheets charts found in the presentation'
};
}
}
});

View File

@@ -0,0 +1,113 @@
import { AuthenticationType, httpClient, HttpMethod } from "@activepieces/pieces-common";
export interface PageElement {
objectId: string;
table?: Table
sheetsChart?: {
spreadsheetId: string;
chartId: number;
};
shape?: Shape
}
interface Presentation {
slides?: Slide[];
}
interface Slide {
pageElements?: PageElement[];
}
interface Shape {
text?: Text;
}
interface Text {
textElements?: TextElement[];
}
export interface TextElement {
textRun?: TextRun;
}
interface TextRun {
content?: string;
}
interface Table {
tableRows?: TableRow[];
}
interface TableRow {
tableCells?: TableCell[];
}
export interface TableCell {
text?: Text;
}
export const googleSheetsCommon = {
baseUrl: 'https://slides.googleapis.com/v1/presentations/',
batchUpdate,
getSlide,
createSlide
};
export async function batchUpdate(access_token: string, slide_id: string, requests: any) {
return (
await httpClient.sendRequest<{
spreadsheetId: string;
}>({
method: HttpMethod.POST,
url: `https://slides.googleapis.com/v1/presentations/${slide_id}:batchUpdate`,
body: {
requests: requests
},
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: access_token,
},
})
).body;
}
export async function getSlide(access_token: string, slide_id: string) {
return (
await httpClient.sendRequest<{
presentationId: string;
title: string;
slides: {
pageElements: any; objectId: object;
}[];
}>({
method: HttpMethod.GET,
url: `https://slides.googleapis.com/v1/presentations/${slide_id}`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: access_token,
},
})
).body;
}
export async function createSlide(access_token: string, requests: any) {
return (
await httpClient.sendRequest<{
presentationId: string;
title: string;
spreadsheetId: string;
replies: any[];
}>({
method: HttpMethod.POST,
url: `https://slides.googleapis.com/v1/presentations`,
body: {
requests: requests
},
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: access_token,
},
})
).body;
}