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,18 @@
import { createPiece } from '@activepieces/pieces-framework';
import { PieceCategory } from '@activepieces/shared';
import { logrocketAuth } from './lib/common/auth';
import { requestHighlights } from './lib/actions/request-highlights';
import { identifyUser } from './lib/actions/identify-user';
import { highlightsReady } from './lib/triggers/highlights-ready';
export const logrocket = createPiece({
displayName: 'LogRocket',
description: 'Get AI-generated summaries of user sessions to understand customer behavior and troubleshoot issues faster.',
auth: logrocketAuth,
minimumSupportedRelease: '0.36.1',
logoUrl: 'https://cdn.activepieces.com/pieces/logrocket.png',
categories: [PieceCategory.DEVELOPER_TOOLS],
authors: ["onyedikachi-david"],
actions: [requestHighlights, identifyUser],
triggers: [highlightsReady],
});

View File

@@ -0,0 +1,94 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { logrocketAuth } from '../common/auth';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
export const identifyUser = createAction({
auth: logrocketAuth,
name: 'identifyUser',
displayName: 'Identify User',
description: 'Create or update user information and traits in LogRocket to contextualize user behavior.',
props: {
orgId: Property.ShortText({
displayName: 'Organization ID',
description: 'Your LogRocket organization ID (found in Settings > General Settings, or in your App ID before the slash)',
required: true,
}),
appId: Property.ShortText({
displayName: 'App ID',
description: 'Your LogRocket app/project ID (found in Settings > General Settings, or in your App ID after the slash)',
required: true,
}),
userId: Property.ShortText({
displayName: 'User ID',
description: 'The unique identifier for the user',
required: true,
}),
name: Property.ShortText({
displayName: 'Name',
description: 'The user\'s name (max 1024 characters)',
required: false,
}),
email: Property.ShortText({
displayName: 'Email',
description: 'The user\'s email address (max 1024 characters)',
required: false,
}),
timestamp: Property.DateTime({
displayName: 'Timestamp',
description: 'Unix timestamp in milliseconds indicating when this data was collected (optional)',
required: false,
}),
traits: Property.Object({
displayName: 'Traits',
description: 'User traits as key-value pairs (e.g., {"plan": "free", "age": 43}). Values will be converted to strings.',
required: false,
}),
},
async run(context) {
const { orgId, appId, userId, name, email, timestamp, traits } = context.propsValue;
const apiKey = context.auth;
const body: Record<string, unknown> = {};
if (name) {
body['name'] = name;
}
if (email) {
body['email'] = email;
}
if (timestamp) {
body['timestamp'] = new Date(timestamp).getTime();
}
if (traits) {
body['traits'] = traits;
}
if (Object.keys(body).length === 0) {
throw new Error('At least one of name, email, timestamp, or traits must be provided');
}
try {
const response = await httpClient.sendRequest({
method: HttpMethod.PUT,
url: `https://api.logrocket.com/v1/orgs/${orgId}/apps/${appId}/users/${userId}`,
headers: {
Authorization: `token ${apiKey}`,
'Content-Type': 'application/json',
},
body,
});
return response.body;
} catch (error) {
throw new Error(
`Failed to identify user: ${
error instanceof Error ? error.message : 'Unknown error'
}`
);
}
},
});

View File

@@ -0,0 +1,104 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { logrocketAuth } from '../common/auth';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
export const requestHighlights = createAction({
auth: logrocketAuth,
name: 'requestHighlights',
displayName: 'Request Highlights',
description: 'Request session highlights for a user. Results will be sent to the webhook URL when ready.',
props: {
orgId: Property.ShortText({
displayName: 'Organization ID',
description: 'Your LogRocket organization ID (found in Settings > General Settings, or in your App ID before the slash)',
required: true,
}),
projectId: Property.ShortText({
displayName: 'Project ID',
description: 'Your LogRocket project ID (found in Settings > General Settings, or in your App ID after the slash)',
required: true,
}),
userEmail: Property.ShortText({
displayName: 'User Email',
description: 'The email of the user to get highlights for',
required: false,
}),
userId: Property.ShortText({
displayName: 'User ID',
description: 'The ID of the user from LogRocket.identify() calls',
required: false,
}),
timeRangeStart: Property.DateTime({
displayName: 'Start Time',
description: 'Start of the time range for sessions (optional)',
required: false,
}),
timeRangeEnd: Property.DateTime({
displayName: 'End Time',
description: 'End of the time range for sessions (optional)',
required: false,
}),
webhookUrl: Property.ShortText({
displayName: 'Webhook URL',
description: 'URL where highlights results will be posted when ready. You can get this from the "Highlights Ready" trigger.',
required: true,
}),
},
async run(context) {
const { orgId, projectId, userEmail, userId, timeRangeStart, timeRangeEnd, webhookUrl } = context.propsValue;
const apiKey = context.auth;
if (!userEmail && !userId) {
throw new Error('Either userEmail or userId must be provided');
}
const body: Record<string, unknown> = {
webhookURL: webhookUrl,
};
if (userEmail) {
body['userEmail'] = userEmail;
}
if (userId) {
body['userID'] = userId;
}
if (timeRangeStart && timeRangeEnd) {
const startMs = new Date(timeRangeStart).getTime();
const endMs = new Date(timeRangeEnd).getTime();
if (startMs >= endMs) {
throw new Error('Start time must be before end time');
}
body['timeRange'] = {
startMs,
endMs,
};
} else if (timeRangeStart || timeRangeEnd) {
throw new Error('Both start time and end time must be provided if using time range');
}
try {
const response = await httpClient.sendRequest({
method: HttpMethod.POST,
url: `https://api.logrocket.com/v1/orgs/${orgId}/apps/${projectId}/highlights/`,
headers: {
Authorization: `token ${apiKey}`,
'Content-Type': 'application/json',
},
body,
});
return response.body;
} catch (error) {
throw new Error(
`Failed to request highlights: ${
error instanceof Error ? error.message : 'Unknown error'
}`
);
}
},
});

View File

@@ -0,0 +1,17 @@
import { PieceAuth } from '@activepieces/pieces-framework';
export const logrocketAuth = PieceAuth.SecretText({
displayName: 'API Key',
description: `
To get your LogRocket API key:
1. **Login to your LogRocket Dashboard**
2. **Go to Settings > General Settings**
3. **Copy your API key** from the API Key section
4. **Paste it here**
Your API key is used to authenticate requests to the LogRocket API.
`,
required: true,
});

View File

@@ -0,0 +1,64 @@
import {
createTrigger,
Property,
TriggerStrategy,
} from '@activepieces/pieces-framework';
import { logrocketAuth } from '../common/auth';
export const highlightsReady = createTrigger({
auth: logrocketAuth,
name: 'highlightsReady',
displayName: 'Highlights Ready',
description: 'Trigger when session highlights are ready. Use this webhook URL when requesting highlights.',
props: {
markdown: Property.MarkDown({
value: `
## Webhook Configuration
This trigger receives highlights results from LogRocket when they're ready.
**To use this trigger:**
1. Copy the webhook URL below
2. Use it as the **Webhook URL** field when using the "Request Highlights" action
3. When highlights are ready, this trigger will fire with the results
**Webhook URL:**
\`\`\`text
{{webhookUrl}}
\`\`\`
`,
}),
},
sampleData: {
result: {
highlights: 'The user [checks out their cart](https://app.logrocket.com/orgID/appID/s/5-14de95d6-d5b8-1a62-0a3a-4858caf874b0/0?t=1715564734801) and [encounters an error loading settings](https://app.logrocket.com/orgID/appID/s/5-3cde95d6-a5b8-4a62-9b3a-6aa834f8747c/0?t=1715564554783).',
sessions: [
{
recordingID: '5-3cde95d6-a5b8-4a62-9b3a-6aa834f8747c',
sessionID: 0,
highlights: 'The user [encounters an error loading settings](https://app.logrocket.com/orgID/appID/s/5-3cde95d6-a5b8-4a62-9b3a-6aa834f8747c/0?t=1715564554783) and [the issue persists](https://app.logrocket.com/orgID/appID/s/5-3cde95d6-a5b8-4a62-9b3a-6aa834f8747c/0?t=1715564734801).',
},
{
recordingID: '5-14de95d6-d5b8-1a62-0a3a-4858caf874b0',
sessionID: 0,
highlights: 'The user [adds an item to their cart](https://app.logrocket.com/orgID/appID/s/5-14de95d6-d5b8-1a62-0a3a-4858caf874b0/0?t=1715564554783) and [proceeds to checkout](https://app.logrocket.com/orgID/appID/s/5-14de95d6-d5b8-1a62-0a3a-4858caf874b0/0?t=1715564734801)',
},
],
},
status: 'READY',
appID: 'orgID/appID',
requestID: '0cc12cad4b5b8b93760edf7fb5731ac66fbc8a766f9431df6c1e72b214ed6a65',
},
type: TriggerStrategy.WEBHOOK,
async onEnable(context) {
// No need to register webhook - LogRocket will POST to this URL when highlights are ready
},
async onDisable(context) {
// No need to unregister webhook
},
async run(context) {
return [context.payload.body];
},
});