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,142 @@
import { microsoftTeamsAuth } from '../../index';
import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common';
import {
createTrigger,
AppConnectionValueForAuthProperty,
TriggerStrategy,
} from '@activepieces/pieces-framework';
import { microsoftTeamsCommon } from '../common';
import { Client, PageCollection } from '@microsoft/microsoft-graph-client';
import { ChatMessage } from '@microsoft/microsoft-graph-types';
import dayjs from 'dayjs';
import { isNil } from '@activepieces/shared';
type Props = {
teamId: string;
channelId: string;
};
export const newChannelMessageTrigger = createTrigger({
auth: microsoftTeamsAuth,
name: 'new-channel-message',
displayName: 'New Channel Message',
description: 'Triggers when a new message is posted in a channel.',
props: {
teamId: microsoftTeamsCommon.teamId,
channelId: microsoftTeamsCommon.channelId,
},
type: TriggerStrategy.POLLING,
async onEnable(context) {
await pollingHelper.onEnable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
async onDisable(context) {
await pollingHelper.onDisable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
async test(context) {
return await pollingHelper.test(polling, context);
},
async run(context) {
return await pollingHelper.poll(polling, context);
},
sampleData: {
replyToId: null,
etag: '1747831213175',
messageType: 'message',
createdDateTime: '2025-05-21T12:40:13.175Z',
lastModifiedDateTime: '2025-05-21T12:40:13.175Z',
lastEditedDateTime: null,
deletedDateTime: null,
subject: 'Test',
summary: null,
chatId: null,
importance: 'normal',
locale: 'en-us',
webUrl:'',
policyViolation: null,
eventDetail: null,
id: '1747831213175',
from: {
application: null,
device: null,
user: {
'@odata.type': '#microsoft.graph.teamworkUserIdentity',
id: '90b3720d-f459-42c1-a02e-a1ecb068',
displayName: 'Activepieces',
userIdentityType: 'aadUser',
tenantId: '9b37335a-d996-4a8d-9ae4-a3a04c94',
},
},
body: {
contentType: 'html',
content: '<p>Test Message</p>',
},
channelIdentity: {
teamId: '99cb9-7ebe-43ee-a69b-5f77ce8a4b4e',
channelId: '19:LiZnIkTo_1FmFY9OTsfym0q3bwo-y2UfV9FaYA1@thread.tacv2',
},
attachments: [],
mentions: [],
reactions: [],
},
});
const polling: Polling<AppConnectionValueForAuthProperty<typeof microsoftTeamsAuth>, Props> = {
strategy: DedupeStrategy.TIMEBASED,
async items({ auth, propsValue, lastFetchEpochMS, store }) {
const { teamId, channelId } = propsValue;
const client = Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve(auth.access_token),
},
});
const messages: ChatMessage[] = [];
if (lastFetchEpochMS === 0) {
const response: PageCollection = await client
.api(`/teams/${teamId}/channels/${channelId}/messages`)
.top(5)
.get();
if (!isNil(response.value)) {
messages.push(...response.value);
}
} else {
const requestUrl =
(await store.get<string>('deltalink')) ??
`/teams/${teamId}/channels/${channelId}/messages/delta`;
let nextLink: string | null = requestUrl;
// https://learn.microsoft.com/en-us/graph/api/chatmessage-delta?view=graph-rest-1.0&tabs=http
while (nextLink) {
const response: PageCollection = await client.api(nextLink).get();
const channelMessages = response.value as ChatMessage[];
if (Array.isArray(channelMessages)) {
messages.push(...channelMessages);
}
nextLink = response['@odata.nextLink'] ?? null;
if (response['@odata.deltaLink']) {
await store.put<string>('deltalink', response['@odata.deltaLink']);
}
}
}
return messages.map((message: ChatMessage) => {
return {
epochMilliSeconds: dayjs(message.createdDateTime).valueOf(),
data: message,
};
});
},
};

View File

@@ -0,0 +1,95 @@
import { microsoftTeamsAuth } from '../../index';
import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common';
import {
createTrigger,
AppConnectionValueForAuthProperty,
TriggerStrategy,
} from '@activepieces/pieces-framework';
import { microsoftTeamsCommon } from '../common';
import { Client, PageCollection } from '@microsoft/microsoft-graph-client';
import { Channel } from '@microsoft/microsoft-graph-types';
import dayjs from 'dayjs';
import { isNil } from '@activepieces/shared';
type Props = {
teamId: string;
};
export const newChannelTrigger = createTrigger({
auth: microsoftTeamsAuth,
name: 'new-channel',
displayName: 'New Channel',
description: 'Triggers when a new channel is created in a team.',
props: {
teamId: microsoftTeamsCommon.teamId,
},
type: TriggerStrategy.POLLING,
async onEnable(context) {
await pollingHelper.onEnable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
async onDisable(context) {
await pollingHelper.onDisable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
async test(context) {
return await pollingHelper.test(polling, context);
},
async run(context) {
return await pollingHelper.poll(polling, context);
},
sampleData: {
id: '19:561fbdbbfca848a484f0a6f00ce9dbbd@thread.tacv2',
createdDateTime: '2025-05-21T12:40:13.175Z',
displayName: 'General',
description: 'Auto-generated channel',
membershipType: 'standard',
isArchived: false,
},
});
const polling: Polling<AppConnectionValueForAuthProperty<typeof microsoftTeamsAuth>, Props> = {
strategy: DedupeStrategy.TIMEBASED,
async items({ auth, propsValue, lastFetchEpochMS }) {
const { teamId } = propsValue;
const client = Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve(auth.access_token),
},
});
const lastFetchDate = dayjs(lastFetchEpochMS).toISOString();
const channels: Channel[] = [];
const filter = lastFetchEpochMS === 0 ? '' : `?$filter=createdDateTime gt ${lastFetchDate}`;
let response: PageCollection = await client.api(`/teams/${teamId}/channels${filter}`).get();
while (response.value && response.value.length > 0) {
for (const channel of response.value as Channel[]) {
if (isNil(channel.createdDateTime)) {
continue;
}
channels.push(channel);
}
if (lastFetchEpochMS !== 0 && response['@odata.nextLink']) {
response = await client.api(response['@odata.nextLink']).get();
} else {
break;
}
}
return channels.map((channel: Channel) => {
return {
epochMilliSeconds: dayjs(channel.createdDateTime!).valueOf(),
data: channel,
};
});
},
};

View File

@@ -0,0 +1,138 @@
import { microsoftTeamsAuth } from '../../index';
import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common';
import {
createTrigger,
AppConnectionValueForAuthProperty,
TriggerStrategy,
} from '@activepieces/pieces-framework';
import { microsoftTeamsCommon } from '../common';
import { Client, PageCollection } from '@microsoft/microsoft-graph-client';
import { ChatMessage } from '@microsoft/microsoft-graph-types';
import dayjs from 'dayjs';
import { isNil } from '@activepieces/shared';
type Props = {
chatId: string;
};
export const newChatMessageTrigger = createTrigger({
auth: microsoftTeamsAuth,
name: 'new-chat-message',
displayName: 'New Chat Message',
description: 'Triggers when a new message is received in a chat.',
props: {
chatId: microsoftTeamsCommon.chatId,
},
type: TriggerStrategy.POLLING,
async onEnable(context) {
await pollingHelper.onEnable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
async onDisable(context) {
await pollingHelper.onDisable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
async test(context) {
return await pollingHelper.test(polling, context);
},
async run(context) {
return await pollingHelper.poll(polling, context);
},
sampleData: {
replyToId: null,
etag: '1747831213175',
messageType: 'message',
createdDateTime: '2025-05-21T12:40:13.175Z',
lastModifiedDateTime: '2025-05-21T12:40:13.175Z',
lastEditedDateTime: null,
deletedDateTime: null,
subject: null,
summary: null,
chatId: '19:example_chat_id@unq.gbl.spaces',
importance: 'normal',
locale: 'en-us',
webUrl: '',
policyViolation: null,
eventDetail: null,
id: '1747831213175',
from: {
application: null,
device: null,
user: {
'@odata.type': '#microsoft.graph.teamworkUserIdentity',
id: '90b3720d-f459-42c1-a02e-a1ecb068',
displayName: 'Activepieces',
userIdentityType: 'aadUser',
tenantId: '9b37335a-d996-4a8d-9ae4-a3a04c94',
},
},
body: {
contentType: 'html',
content: '<p>Test Message</p>',
},
attachments: [],
mentions: [],
reactions: [],
},
});
const polling: Polling<AppConnectionValueForAuthProperty<typeof microsoftTeamsAuth>, Props> = {
strategy: DedupeStrategy.TIMEBASED,
async items({ auth, propsValue, lastFetchEpochMS, store }) {
const { chatId } = propsValue;
const client = Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve(auth.access_token),
},
});
const messages: ChatMessage[] = [];
if (lastFetchEpochMS === 0) {
const response: PageCollection = await client
.api(`/chats/${chatId}/messages`)
.top(5)
.get();
if (!isNil(response.value)) {
messages.push(...(response.value as ChatMessage[]));
}
} else {
const requestUrl =
(await store.get<string>('deltalink')) ?? `/chats/${chatId}/messages/delta`;
let nextLink: string | null = requestUrl;
// https://learn.microsoft.com/graph/api/chatmessage-delta?view=graph-rest-1.0&tabs=http
while (nextLink) {
const response: PageCollection = await client.api(nextLink).get();
const chatMessages = response.value as ChatMessage[];
if (Array.isArray(chatMessages)) {
messages.push(...chatMessages);
}
nextLink = response['@odata.nextLink'] ?? null;
if (response['@odata.deltaLink']) {
await store.put<string>('deltalink', response['@odata.deltaLink']);
}
}
}
return messages.map((message: ChatMessage) => {
return {
epochMilliSeconds: dayjs(message.createdDateTime).valueOf(),
data: message,
};
});
},
};

View File

@@ -0,0 +1,96 @@
import { microsoftTeamsAuth } from '../../index';
import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common';
import {
createTrigger,
AppConnectionValueForAuthProperty,
TriggerStrategy,
} from '@activepieces/pieces-framework';
import { isNil } from '@activepieces/shared';
import { Client, PageCollection } from '@microsoft/microsoft-graph-client';
import { Chat, ChatType } from '@microsoft/microsoft-graph-types';
import dayjs from 'dayjs';
type Props = {
chatType?: ChatType;
};
export const newChatTrigger = createTrigger({
auth: microsoftTeamsAuth,
name: 'new-chat',
displayName: 'New Chat',
description: 'Triggers when a new chat is created.',
props: {},
type: TriggerStrategy.POLLING,
async onEnable(context) {
await pollingHelper.onEnable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue as Props,
});
},
async onDisable(context) {
await pollingHelper.onDisable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue as Props,
});
},
async test(context) {
return await pollingHelper.test(polling, context as any);
},
async run(context) {
return await pollingHelper.poll(polling, context as any);
},
sampleData: {
id: '19:example_chat_id@unq.gbl.spaces',
createdDateTime: '2025-05-21T12:40:13.175Z',
lastUpdatedDateTime: '2025-05-21T12:40:13.175Z',
chatType: 'oneOnOne',
webUrl: '',
isHiddenForAllMembers: false,
},
});
const polling: Polling<AppConnectionValueForAuthProperty<typeof microsoftTeamsAuth>, Props> = {
strategy: DedupeStrategy.TIMEBASED,
async items({ auth, lastFetchEpochMS }) {
const client = Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve(auth.access_token),
},
});
const lastFetchDate = dayjs(lastFetchEpochMS).toISOString();
const chats: Chat[] = [];
const filter =
lastFetchEpochMS === 0
? '$top=10'
: `$filter=createdDateTime gt ${lastFetchDate}`;
let response: PageCollection = await client.api(`/chats?${filter}`).get();
console.log('RESPONE');
console.log(JSON.stringify(response))
while (response.value && response.value.length > 0) {
for (const channel of response.value as Chat[]) {
if (isNil(channel.createdDateTime)) {
continue;
}
chats.push(channel);
}
if (lastFetchEpochMS !== 0 && response['@odata.nextLink']) {
response = await client.api(response['@odata.nextLink']).get();
} else {
break;
}
}
return chats.map((chat: Chat) => {
return {
epochMilliSeconds: dayjs(chat.createdDateTime!).valueOf(),
data: chat,
};
});
},
};