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,85 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { assembledCommon } from '../common';
import { HttpMethod } from '@activepieces/pieces-common';
import { assembledAuth } from '../common/auth';
export const addShift = createAction({
auth: assembledAuth,
name: 'add_shift',
displayName: 'Add Shift on Assembled',
description: 'Add a new shift to a user\'s schedule in Assembled',
props: {
agent_id: Property.ShortText({
displayName: 'Agent ID',
description: 'Agent ID of the person to assign the shift to (use the agent_id field from /people endpoint, not the person id)',
required: true,
}),
start_time: Property.DateTime({
displayName: 'Start Time',
description: 'Start time of the shift',
required: true,
}),
end_time: Property.DateTime({
displayName: 'End Time',
description: 'End time of the shift',
required: true,
}),
shift_type: Property.StaticDropdown({
displayName: 'Shift Type',
description: 'Type of shift',
required: false,
defaultValue: 'regular',
options: {
options: [
{ label: 'Regular', value: 'regular' },
{ label: 'Overtime', value: 'overtime' },
{ label: 'On-call', value: 'on_call' },
{ label: 'Training', value: 'training' },
],
},
}),
activity_type_id: Property.ShortText({
displayName: 'Activity Type ID',
description: 'UUID of the activity type for this shift (get from /activity_types endpoint)',
required: true,
}),
notes: Property.LongText({
displayName: 'Notes',
description: 'Additional notes for the shift',
required: false,
}),
},
async run(context) {
const { agent_id, start_time, end_time, shift_type, activity_type_id, notes } = context.propsValue;
try {
// Convert to Unix timestamps as required by Assembled API
const startTimestamp = Math.floor(new Date(start_time).getTime() / 1000);
const endTimestamp = Math.floor(new Date(end_time).getTime() / 1000);
const shiftData = {
agent_id: agent_id,
type_id: activity_type_id,
start_time: startTimestamp,
end_time: endTimestamp,
description: notes || `${shift_type || 'regular'} shift`,
};
const response = await assembledCommon.makeRequest(
context.auth.secret_text,
HttpMethod.POST,
'/activities',
shiftData
);
return {
success: true,
shift_id: response.body.id,
message: 'Shift added successfully',
data: response.body,
};
} catch (error) {
throw new Error(`Failed to add shift: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
},
});

View File

@@ -0,0 +1,14 @@
import { createCustomApiCallAction } from '@activepieces/pieces-common';
import { assembledAuth } from '../common/auth';
export const customApiCall = createCustomApiCallAction({
auth: assembledAuth,
name: 'custom_api_call',
displayName: 'Custom API Call',
description: 'Make custom API calls to Assembled endpoints',
baseUrl: () => 'https://api.assembledhq.com/v0',
authMapping: async (auth) => ({
'Authorization': `Basic ${Buffer.from(auth.secret_text + ':').toString('base64')}`,
'Content-Type': 'application/json',
}),
});

View File

@@ -0,0 +1,22 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { assembledCommon } from '../common';
import { assembledAuth } from '../common/auth';
export const customGraphql = createAction({
name: 'custom_graphql',
displayName: 'Custom GraphQL',
description: 'Perform a custom GraphQL query',
auth: assembledAuth,
props: {
query: Property.LongText({ displayName: 'Query', required: true }),
variables: Property.Object({ displayName: 'Parameters', required: false }),
},
async run({ auth, propsValue }) {
const client = assembledCommon.makeClient(auth.secret_text);
const result = await client.rawRequest(
propsValue.query,
propsValue.variables
);
return result;
},
});

View File

@@ -0,0 +1,33 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { assembledCommon } from '../common';
import { HttpMethod } from '@activepieces/pieces-common';
import { assembledAuth } from '../common/auth';
export const deleteOOO = createAction({
auth: assembledAuth,
name: 'delete_OOO',
displayName: 'Delete OOO Request',
description: 'Cancel/delete a OOO request.',
props: {
OOO_id: Property.ShortText({
displayName: 'OOO ID',
required: true,
}),
},
async run(context) {
const { OOO_id } = context.propsValue;
const response = await assembledCommon.makeRequest(
context.auth.secret_text,
HttpMethod.POST,
`/time_off/${OOO_id}/cancel`,
{}
);
return {
success: true,
message: 'OOO request deleted successfully',
data: response.body,
};
},
});

View File

@@ -0,0 +1,41 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { assembledCommon } from '../common';
import { HttpMethod } from '@activepieces/pieces-common';
import { assembledAuth } from '../common/auth';
export const getUserSchedule = createAction({
auth: assembledAuth,
name: 'get_user_schedule',
displayName: 'Get User Schedule',
description: 'Retrieves user\'s schedule for specified period.',
props: {
user_id: Property.ShortText({
displayName: 'User ID',
required: true,
}),
start_date: Property.DateTime({
displayName: 'Start Date',
required: true,
}),
end_date: Property.DateTime({
displayName: 'End Date',
required: true,
}),
},
async run(context) {
const { user_id, start_date, end_date } = context.propsValue;
const params = new URLSearchParams({
start_date: assembledCommon.formatDate(start_date),
end_date: assembledCommon.formatDate(end_date),
});
const response = await assembledCommon.makeRequest(
context.auth.secret_text,
HttpMethod.GET,
`/users/${user_id}/schedule?${params.toString()}`
);
return response.body;
},
});

View File

@@ -0,0 +1,97 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { assembledCommon } from '../common';
import { HttpMethod } from '@activepieces/pieces-common';
import { assembledAuth } from '../common/auth';
export const OOO = createAction({
auth: assembledAuth,
name: 'OOO',
displayName: 'Create OOO Request',
description: 'Create an Out of Office request in Assembled.',
props: {
mock_mode: Property.Checkbox({
displayName: 'Mock Mode',
description: 'Use mock data for testing',
required: false,
defaultValue: true,
}),
user_id: Property.ShortText({
displayName: 'User ID',
description: 'ID of the user requesting time off',
required: true,
}),
start_date: Property.DateTime({
displayName: 'Start Date',
description: 'Start date of the OOO period',
required: true,
}),
end_date: Property.DateTime({
displayName: 'End Date',
description: 'End date of the OOO period',
required: true,
}),
activity_type_id: Property.ShortText({
displayName: 'Activity Type ID',
description: 'UUID of the activity type for time off (can be retrieved from activity types endpoints)',
required: true,
}),
all_day: Property.Checkbox({
displayName: 'All Day Event',
description: 'Whether this is an all-day OOO event',
required: false,
defaultValue: true,
}),
reason: Property.LongText({
displayName: 'Reason',
description: 'Reason for the OOO request',
required: false,
}),
},
async run(context) {
const { mock_mode, user_id, start_date, end_date, activity_type_id, all_day, reason } = context.propsValue;
// Mock response for testing
if (mock_mode) {
return {
success: true,
message: 'Mock OOO created successfully',
data: {
id: `mock_ooo_${Date.now()}`,
user_id,
start_date,
end_date,
activity_type_id: activity_type_id || 'mock-activity-type-id',
status: 'pending',
created_at: new Date().toISOString(),
}
};
}
try {
const oooData = {
user_id: user_id,
start_time: Math.floor(new Date(start_date).getTime() / 1000),
end_time: Math.floor(new Date(end_date).getTime() / 1000),
activity_type_id,
all_day: all_day ?? true,
description: reason || '',
};
const response = await assembledCommon.makeRequest(
context.auth.secret_text,
HttpMethod.POST,
'/time_off',
oooData
);
return {
success: true,
ooo_id: response.body.id,
message: 'OOO request created successfully',
data: response.body,
};
} catch (error) {
throw new Error(`Failed to create OOO request: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
},
});

View File

@@ -0,0 +1,95 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { assembledCommon } from '../common';
import { HttpMethod } from '@activepieces/pieces-common';
import { assembledAuth } from '../common/auth';
export const updateOOO = createAction({
auth: assembledAuth,
name: 'update_OOO',
displayName: 'Update OOO Request',
description: 'Updates an existing OOO request.',
props: {
OOO_id: Property.ShortText({
displayName: 'OOO ID',
required: true,
}),
start_date: Property.DateTime({
displayName: 'Start Date',
required: false,
}),
end_date: Property.DateTime({
displayName: 'End Date',
required: false,
}),
status: Property.StaticDropdown({
displayName: 'Status',
required: false,
options: {
options: [
{ label: 'Pending', value: 'pending' },
{ label: 'Approved', value: 'approved' },
{ label: 'Rejected', value: 'rejected' },
{ label: 'Cancelled', value: 'cancelled' },
],
},
}),
reason: Property.LongText({
displayName: 'Reason',
required: false,
}),
user_id: Property.ShortText({
displayName: 'User ID',
description: 'ID of the user for the time off request (required for creating new request)',
required: true,
}),
activity_type_id: Property.ShortText({
displayName: 'Activity Type ID',
description: 'UUID of the activity type for time off (required for creating new request)',
required: true,
}),
},
async run(context) {
const { OOO_id, start_date, end_date, status, reason, user_id, activity_type_id } = context.propsValue;
// no direct endpoint to update a time off request, so need to cancel and create new time off request
try {
// cancel the existing time off request
console.log(`Canceling existing time off request: ${OOO_id}`);
await assembledCommon.makeRequest(
context.auth.secret_text,
HttpMethod.POST,
`/time_off/${OOO_id}/cancel`
);
// create new time off request with updated details
console.log('Creating new time off request with updated details');
const newRequestData: Record<string, unknown> = {
user_id,
activity_type_id,
all_day: true,
};
if (start_date) newRequestData['start_time'] = Math.floor(new Date(start_date).getTime() / 1000);
if (end_date) newRequestData['end_time'] = Math.floor(new Date(end_date).getTime() / 1000);
if (reason) newRequestData['description'] = reason;
const response = await assembledCommon.makeRequest(
context.auth.secret_text,
HttpMethod.POST,
'/time_off',
newRequestData
);
return {
success: true,
message: 'Time off request updated successfully (canceled old and created new)',
canceled_request_id: OOO_id,
new_request_id: response.body.id,
data: response.body,
};
} catch (error) {
throw new Error(`Failed to update time off request: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
},
});