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,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;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
@@ -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'
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user