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,33 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { assertNotNullOrUndefined } from '@activepieces/shared';
|
||||
import { figmaCommon } from '../common';
|
||||
import { figmaGetRequest } from '../common/utils';
|
||||
import { figmaAuth } from '../../';
|
||||
|
||||
export const getCommentsAction = createAction({
|
||||
auth: figmaAuth,
|
||||
name: 'get_comments',
|
||||
displayName: 'Get File Comments',
|
||||
description: 'Get file comments',
|
||||
props: {
|
||||
file_key: Property.ShortText({
|
||||
displayName: 'File Key',
|
||||
description: 'The Figma file key (copy from Figma file URL)',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const token = context.auth.access_token;
|
||||
const fileKey = context.propsValue.file_key;
|
||||
|
||||
assertNotNullOrUndefined(token, 'token');
|
||||
assertNotNullOrUndefined(fileKey, 'file_key');
|
||||
|
||||
const url = `${figmaCommon.baseUrl}/${figmaCommon.comments}`.replace(
|
||||
':file_key',
|
||||
fileKey
|
||||
);
|
||||
|
||||
return figmaGetRequest({ token, url });
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { assertNotNullOrUndefined } from '@activepieces/shared';
|
||||
import { figmaCommon } from '../common';
|
||||
import { figmaGetRequest } from '../common/utils';
|
||||
import { figmaAuth } from '../../';
|
||||
|
||||
export const getFileAction = createAction({
|
||||
auth: figmaAuth,
|
||||
name: 'get_file',
|
||||
displayName: 'Get File',
|
||||
description: 'Get file',
|
||||
props: {
|
||||
file_key: Property.ShortText({
|
||||
displayName: 'File Key',
|
||||
description: 'The Figma file key (copy from Figma file URL)',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const token = context.auth.access_token;
|
||||
const fileKey = context.propsValue.file_key;
|
||||
|
||||
assertNotNullOrUndefined(token, 'token');
|
||||
assertNotNullOrUndefined(fileKey, 'file_key');
|
||||
|
||||
const url = `${figmaCommon.baseUrl}/${figmaCommon.files}`.replace(
|
||||
':file_key',
|
||||
fileKey
|
||||
);
|
||||
|
||||
return figmaGetRequest({ token, url });
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { assertNotNullOrUndefined } from '@activepieces/shared';
|
||||
import { figmaCommon } from '../common';
|
||||
import { figmaPostRequestWithMessage } from '../common/utils';
|
||||
import { figmaAuth } from '../../';
|
||||
|
||||
export const postCommentAction = createAction({
|
||||
auth: figmaAuth,
|
||||
name: 'post_comment',
|
||||
displayName: 'Post File Comment',
|
||||
description: 'Post file comment',
|
||||
props: {
|
||||
file_key: Property.ShortText({
|
||||
displayName: 'File Key',
|
||||
description: 'The Figma file key (copy from Figma file URL)',
|
||||
required: true,
|
||||
}),
|
||||
message: Property.LongText({
|
||||
displayName: 'Comment',
|
||||
description: 'Your comment',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const token = context.auth.access_token;
|
||||
const { file_key, message } = context.propsValue;
|
||||
|
||||
assertNotNullOrUndefined(token, 'token');
|
||||
assertNotNullOrUndefined(file_key, 'file_key');
|
||||
assertNotNullOrUndefined(message, 'comment');
|
||||
|
||||
const url = `${figmaCommon.baseUrl}/${figmaCommon.comments}`.replace(
|
||||
':file_key',
|
||||
file_key
|
||||
);
|
||||
|
||||
return figmaPostRequestWithMessage({ token, url, message });
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
export const figmaCommon = {
|
||||
baseUrl: 'https://api.figma.com',
|
||||
files: 'v1/files/:file_key',
|
||||
comments: 'v1/files/:file_key/comments',
|
||||
webhooks: 'v2/webhooks',
|
||||
webhook: 'v2/webhooks/:webhook_id',
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
type User = {
|
||||
handle: string;
|
||||
img_url: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
type Pos = {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
|
||||
export type Comment = {
|
||||
id: string;
|
||||
uuid: string;
|
||||
file_key: string;
|
||||
parent_id: string;
|
||||
user: User;
|
||||
created_at: string;
|
||||
resolved_at: string;
|
||||
message: string;
|
||||
reactions: unknown;
|
||||
client_meta: Pos;
|
||||
order_id: number;
|
||||
};
|
||||
@@ -0,0 +1,139 @@
|
||||
import {
|
||||
httpClient,
|
||||
HttpMethod,
|
||||
HttpRequest,
|
||||
AuthenticationType,
|
||||
} from '@activepieces/pieces-common';
|
||||
|
||||
export const figmaGetRequest = async ({
|
||||
token,
|
||||
url,
|
||||
}: FigmaGetRequestParams) => {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: url,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token,
|
||||
},
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest(request);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
request_body: request.body,
|
||||
response_body: response.body,
|
||||
};
|
||||
};
|
||||
|
||||
export const figmaPostRequestWithMessage = async ({
|
||||
token,
|
||||
url,
|
||||
message,
|
||||
}: FigmaPostRequestWithMessageParams) => {
|
||||
const request: HttpRequest<FigmaPostRequestWithMessageBody> = {
|
||||
method: HttpMethod.POST,
|
||||
url: url,
|
||||
body: {
|
||||
message: message,
|
||||
},
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token,
|
||||
},
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest(request);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
request_body: request.body,
|
||||
response_body: response.body,
|
||||
};
|
||||
};
|
||||
|
||||
export const figmaWebhookPostRequest = async ({
|
||||
token,
|
||||
url,
|
||||
eventType,
|
||||
teamId,
|
||||
endpoint,
|
||||
passcode,
|
||||
}: FigmaWebhookPostRequestParams) => {
|
||||
const request: HttpRequest<FigmaWebhookPostRequestBody> = {
|
||||
method: HttpMethod.POST,
|
||||
url: url,
|
||||
body: {
|
||||
event_type: eventType,
|
||||
team_id: teamId,
|
||||
endpoint: endpoint,
|
||||
passcode: passcode,
|
||||
},
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token,
|
||||
},
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest(request);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
request_body: request.body,
|
||||
response_body: response.body,
|
||||
};
|
||||
};
|
||||
|
||||
export const figmaDeleteRequest = async ({
|
||||
token,
|
||||
url,
|
||||
}: FigmaGetRequestParams) => {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.DELETE,
|
||||
url: url,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token,
|
||||
},
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest(request);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
request_body: request.body,
|
||||
response_body: response.body,
|
||||
};
|
||||
};
|
||||
|
||||
type FigmaGetRequestParams = {
|
||||
token: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
type FigmaPostRequestWithMessageParams = {
|
||||
token: string;
|
||||
url: string;
|
||||
message: string;
|
||||
};
|
||||
|
||||
type FigmaPostRequestWithMessageBody = {
|
||||
message: string;
|
||||
};
|
||||
|
||||
type FigmaWebhookPostRequestParams = {
|
||||
token: string;
|
||||
url: string;
|
||||
eventType: string;
|
||||
teamId: string;
|
||||
endpoint: string;
|
||||
passcode: string;
|
||||
};
|
||||
|
||||
type FigmaWebhookPostRequestBody = {
|
||||
event_type: string;
|
||||
team_id: string;
|
||||
endpoint: string;
|
||||
passcode: string;
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
import {
|
||||
createTrigger,
|
||||
Property,
|
||||
TriggerStrategy,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { assertNotNullOrUndefined } from '@activepieces/shared';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { figmaCommon } from '../common';
|
||||
import { figmaWebhookPostRequest, figmaDeleteRequest } from '../common/utils';
|
||||
import { figmaAuth } from '../../';
|
||||
|
||||
type TriggerData = {
|
||||
webhookId: string;
|
||||
};
|
||||
|
||||
const TRIGGER_DATA_STORE_KEY = 'figma_new_comment_trigger_data';
|
||||
|
||||
export const newCommentTrigger = createTrigger({
|
||||
auth: figmaAuth,
|
||||
name: 'new_comment',
|
||||
displayName: 'New Comment (Figma Professional plan only)',
|
||||
description: 'Triggers when a new comment is posted',
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
sampleData: [
|
||||
{
|
||||
id: '12345',
|
||||
team_id: '1234567890',
|
||||
event_type: 'FILE_COMMENT',
|
||||
client_id: null,
|
||||
endpoint: 'http://localhost:1234/webhook',
|
||||
passcode: 'figma-passcode',
|
||||
status: 'ACTIVE',
|
||||
description: null,
|
||||
protocol_version: '2',
|
||||
},
|
||||
],
|
||||
props: {
|
||||
team_id: Property.ShortText({
|
||||
displayName: 'Team ID',
|
||||
description:
|
||||
'Naviate to team page, copy the Id from the URL after the word team/',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
|
||||
async onEnable(context): Promise<void> {
|
||||
const token = context.auth.access_token;
|
||||
const teamId = context.propsValue['team_id'];
|
||||
|
||||
assertNotNullOrUndefined(token, 'token');
|
||||
assertNotNullOrUndefined(teamId, 'teamId');
|
||||
|
||||
const url = `${figmaCommon.baseUrl}/${figmaCommon.webhooks}`;
|
||||
const eventType = 'FILE_COMMENT';
|
||||
const passcode = `figma_passcode_${nanoid()}`;
|
||||
const endpoint = context.webhookUrl;
|
||||
|
||||
const { response_body } = await figmaWebhookPostRequest({
|
||||
token,
|
||||
url,
|
||||
eventType,
|
||||
teamId,
|
||||
endpoint,
|
||||
passcode,
|
||||
});
|
||||
|
||||
await context.store?.put<TriggerData>(TRIGGER_DATA_STORE_KEY, {
|
||||
webhookId: response_body['id'],
|
||||
});
|
||||
},
|
||||
|
||||
async onDisable(context): Promise<void> {
|
||||
const token = context.auth.access_token;
|
||||
|
||||
assertNotNullOrUndefined(token, 'token');
|
||||
|
||||
const triggerData = await context.store?.get<TriggerData>(
|
||||
TRIGGER_DATA_STORE_KEY
|
||||
);
|
||||
if (triggerData !== null && triggerData !== undefined) {
|
||||
const url = `${figmaCommon.baseUrl}/${figmaCommon.webhook}`.replace(
|
||||
':webhook_id',
|
||||
triggerData.webhookId
|
||||
);
|
||||
await figmaDeleteRequest({ token, url });
|
||||
}
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
const payloadBody = context.payload.body as Record<string, unknown>;
|
||||
if ('event_type' in payloadBody && payloadBody['event_type'] === 'PING') {
|
||||
return [];
|
||||
}
|
||||
return [payloadBody];
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user