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,360 @@
|
||||
import {
|
||||
createAction,
|
||||
DynamicPropsValue,
|
||||
Property,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { LEVER_BASE_URL, LeverAuth, leverAuth } from '../..';
|
||||
import {
|
||||
AuthenticationType,
|
||||
httpClient,
|
||||
HttpMethod,
|
||||
} from '@activepieces/pieces-common';
|
||||
import { LeverFieldMapping } from '../common';
|
||||
|
||||
export const addFeedbackToOpportunity = createAction({
|
||||
name: 'addFeedbackToOpportunity',
|
||||
displayName: 'Add feedback to opportunity',
|
||||
description: 'Provide feedback to a candidate after an interview',
|
||||
auth: leverAuth,
|
||||
props: {
|
||||
performAs: Property.Dropdown({
|
||||
auth: leverAuth,
|
||||
displayName: 'Feedback author',
|
||||
required: true,
|
||||
refreshers: ['auth'],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please connect first.',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
const users = [];
|
||||
let cursor = undefined;
|
||||
do {
|
||||
const queryParams: Record<string, string> = {
|
||||
include: 'name',
|
||||
};
|
||||
if (cursor) {
|
||||
queryParams['offset'] = cursor;
|
||||
}
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: `${LEVER_BASE_URL}/users`,
|
||||
queryParams: queryParams,
|
||||
authentication: {
|
||||
type: AuthenticationType.BASIC,
|
||||
username: auth.props.apiKey,
|
||||
password: '',
|
||||
},
|
||||
});
|
||||
cursor = response.body.next;
|
||||
const usersPage = response.body.data.map(
|
||||
(user: { id: string; name: string }) => {
|
||||
return {
|
||||
label: user.name,
|
||||
value: user.id,
|
||||
};
|
||||
}
|
||||
);
|
||||
users.push(...usersPage);
|
||||
} while (cursor !== undefined);
|
||||
|
||||
return {
|
||||
options: users,
|
||||
};
|
||||
},
|
||||
}),
|
||||
opportunityId: Property.ShortText({
|
||||
displayName: 'Opportunity ID',
|
||||
required: true,
|
||||
}),
|
||||
panelId: Property.Dropdown({
|
||||
auth: leverAuth,
|
||||
displayName: 'Interview panel',
|
||||
description: 'If you select one, you must select an interview too',
|
||||
required: false,
|
||||
refreshers: ['auth', 'opportunityId'],
|
||||
options: async ({ auth, opportunityId }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please connect first.',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
if (!opportunityId) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please select a candidate (opportunity).',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: `${LEVER_BASE_URL}/opportunities/${opportunityId}/panels?expand=stage&include=id&include=stage&include=start`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BASIC,
|
||||
username: auth.props.apiKey,
|
||||
password: '',
|
||||
},
|
||||
});
|
||||
return {
|
||||
options: response.body.data.map(
|
||||
(panel: { id: string; start: number; stage: { text: string } }) => {
|
||||
const interviewDate = new Date(panel.start);
|
||||
return {
|
||||
label: `${interviewDate.toLocaleDateString()} - ${
|
||||
panel.stage.text
|
||||
}`,
|
||||
value: panel.id,
|
||||
};
|
||||
}
|
||||
),
|
||||
};
|
||||
},
|
||||
}),
|
||||
interviewId: Property.Dropdown({
|
||||
auth: leverAuth,
|
||||
displayName: 'Interview',
|
||||
description: 'Mandatory is you select an interview panel',
|
||||
required: false,
|
||||
refreshers: ['auth', 'opportunityId', 'panelId'],
|
||||
options: async ({ auth, opportunityId, panelId }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please connect first.',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
if (!opportunityId) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please select a candidate (opportunity).',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
if (!panelId) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please select an interview panel.',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: `${LEVER_BASE_URL}/opportunities/${opportunityId}/panels/${panelId}?include=interviews`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BASIC,
|
||||
username: auth.props.apiKey,
|
||||
password: '',
|
||||
},
|
||||
});
|
||||
return {
|
||||
options: response.body.data.interviews.map(
|
||||
(interview: { id: string; subject: string }) => {
|
||||
return { label: interview.subject, value: interview.id };
|
||||
}
|
||||
),
|
||||
};
|
||||
},
|
||||
}),
|
||||
feedbackTemplateId: Property.Dropdown({
|
||||
auth: leverAuth,
|
||||
displayName: 'Feedback template',
|
||||
description: 'Ignored if you select an interview panel and an interview',
|
||||
required: false,
|
||||
refreshers: ['auth'],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please connect first.',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: `${LEVER_BASE_URL}/feedback_templates`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BASIC,
|
||||
username: auth.props.apiKey,
|
||||
password: '',
|
||||
},
|
||||
});
|
||||
return {
|
||||
options: response.body.data.map(
|
||||
(template: { id: string; text: string }) => {
|
||||
return {
|
||||
label: template.text,
|
||||
value: template.id,
|
||||
};
|
||||
}
|
||||
),
|
||||
};
|
||||
},
|
||||
}),
|
||||
feedbackFields: Property.DynamicProperties({
|
||||
auth: leverAuth,
|
||||
displayName: 'Fields',
|
||||
required: true,
|
||||
refreshers: [
|
||||
'auth',
|
||||
'opportunityId',
|
||||
'panelId',
|
||||
'interviewId',
|
||||
'feedbackTemplateId',
|
||||
],
|
||||
props: async ({
|
||||
auth,
|
||||
opportunityId,
|
||||
panelId,
|
||||
interviewId,
|
||||
feedbackTemplateId,
|
||||
}) => {
|
||||
if (
|
||||
!auth ||
|
||||
!opportunityId ||
|
||||
!(feedbackTemplateId || (panelId && interviewId))
|
||||
) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder:
|
||||
'Please connect your Lever account first and select an interview or a feedback template',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
const fields: DynamicPropsValue = {};
|
||||
const templateId =
|
||||
panelId && interviewId
|
||||
? await getFeedbackTemplateForInterview(
|
||||
opportunityId,
|
||||
panelId,
|
||||
interviewId,
|
||||
auth
|
||||
)
|
||||
: feedbackTemplateId;
|
||||
|
||||
try {
|
||||
const feedbackTemplateResponse = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: `${LEVER_BASE_URL}/feedback_templates/${templateId}`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BASIC,
|
||||
username: auth.props.apiKey,
|
||||
password: '',
|
||||
},
|
||||
});
|
||||
feedbackTemplateResponse.body.data.fields.map(
|
||||
(field: {
|
||||
id: string;
|
||||
text: string;
|
||||
description: string;
|
||||
required: boolean;
|
||||
type: string;
|
||||
options?: { text: string; optionId: string }[];
|
||||
scores?: { text: string; description: string }[];
|
||||
}) => {
|
||||
const mappedField =
|
||||
LeverFieldMapping[field.type] || LeverFieldMapping['default'];
|
||||
mappedField.buildActivepieceType(fields, field);
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(
|
||||
'Unexpected error while building dynamic properties',
|
||||
e
|
||||
);
|
||||
}
|
||||
return fields;
|
||||
},
|
||||
}),
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const templateId =
|
||||
propsValue.panelId && propsValue.interviewId
|
||||
? await getFeedbackTemplateForInterview(
|
||||
propsValue.opportunityId,
|
||||
propsValue.panelId,
|
||||
propsValue.interviewId,
|
||||
auth
|
||||
)
|
||||
: propsValue.feedbackTemplateId;
|
||||
|
||||
const feedbackTemplateResponse = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: `${LEVER_BASE_URL}/feedback_templates/${templateId}`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BASIC,
|
||||
username: auth.props.apiKey,
|
||||
password: '',
|
||||
},
|
||||
});
|
||||
|
||||
const templateFields = feedbackTemplateResponse.body.data.fields;
|
||||
const groupedValues = Object.entries(propsValue.feedbackFields).reduce<
|
||||
Record<string, DynamicPropsValue[]>
|
||||
>((values, [fieldId, fieldValue]: [string, DynamicPropsValue]) => {
|
||||
const canonicalId = fieldId.substring(0, 36);
|
||||
values[canonicalId] = values[canonicalId] ?? [];
|
||||
values[canonicalId].push(fieldValue);
|
||||
return values;
|
||||
}, {});
|
||||
|
||||
const payload = {
|
||||
baseTemplateId: templateId,
|
||||
panel: propsValue.panelId,
|
||||
interview: propsValue.interviewId,
|
||||
fieldValues: Object.entries(groupedValues).map(([fieldId, values]) => {
|
||||
const templateField = templateFields.find(
|
||||
(tf: { id: string }) => tf.id === fieldId
|
||||
);
|
||||
const mappedField =
|
||||
templateField.type in LeverFieldMapping
|
||||
? LeverFieldMapping[templateField.type]
|
||||
: LeverFieldMapping['default'];
|
||||
return mappedField.buildLeverType(fieldId, values);
|
||||
}),
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.POST,
|
||||
url: `${LEVER_BASE_URL}/opportunities/${propsValue.opportunityId}/feedback?perform_as=${propsValue.performAs}`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BASIC,
|
||||
username: auth.props.apiKey,
|
||||
password: '',
|
||||
},
|
||||
body: payload,
|
||||
});
|
||||
|
||||
return response.body.data;
|
||||
},
|
||||
});
|
||||
|
||||
async function getFeedbackTemplateForInterview(
|
||||
opportunityId: string | DynamicPropsValue,
|
||||
panelId: string | DynamicPropsValue,
|
||||
interviewId: string | DynamicPropsValue,
|
||||
auth: LeverAuth
|
||||
) {
|
||||
const interviewResponse = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: `${LEVER_BASE_URL}/opportunities/${opportunityId}/panels/${panelId}?include=interviews`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BASIC,
|
||||
username: auth.props.apiKey,
|
||||
password: '',
|
||||
},
|
||||
});
|
||||
const interview = interviewResponse.body.data.interviews.find(
|
||||
(interview: { id: string }) =>
|
||||
interview.id === (interviewId as unknown as string)
|
||||
);
|
||||
return interview.feedbackTemplate;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import qs from 'qs';
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { LEVER_BASE_URL, leverAuth } from '../..';
|
||||
import {
|
||||
AuthenticationType,
|
||||
httpClient,
|
||||
HttpMethod,
|
||||
} from '@activepieces/pieces-common';
|
||||
|
||||
export const getOpportunity = createAction({
|
||||
name: 'getOpportunity',
|
||||
displayName: 'Get opportunity',
|
||||
description:
|
||||
"Retrieve a single opportunity, i.e. an individual's unique candidacy or journey for a given job position",
|
||||
auth: leverAuth,
|
||||
props: {
|
||||
opportunityId: Property.ShortText({
|
||||
displayName: 'Opportunity ID',
|
||||
required: true,
|
||||
}),
|
||||
expand: Property.Array({
|
||||
displayName: 'Expand',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: `${LEVER_BASE_URL}/opportunities/${
|
||||
propsValue.opportunityId
|
||||
}?${decodeURIComponent(
|
||||
qs.stringify({ expand: propsValue.expand }, { arrayFormat: 'repeat' })
|
||||
)}`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BASIC,
|
||||
username: auth.props.apiKey,
|
||||
password: '',
|
||||
},
|
||||
});
|
||||
return response.body.data;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { LEVER_BASE_URL, LeverAuth, leverAuth } from '../..';
|
||||
import {
|
||||
AuthenticationType,
|
||||
httpClient,
|
||||
HttpMethod,
|
||||
} from '@activepieces/pieces-common';
|
||||
|
||||
export const listOpportunityFeedback = createAction({
|
||||
name: 'listOpportunityFeedback',
|
||||
displayName: 'List opportunity feedback',
|
||||
description:
|
||||
'Get all feedback for a given opportunity, optionally for a given template',
|
||||
auth: leverAuth,
|
||||
props: {
|
||||
opportunityId: Property.ShortText({
|
||||
displayName: 'Opportunity ID',
|
||||
required: true,
|
||||
}),
|
||||
template: Property.Dropdown({
|
||||
auth: leverAuth,
|
||||
displayName: 'Feedback template',
|
||||
required: false,
|
||||
refreshers: ['auth'],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please connect first.',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: `${LEVER_BASE_URL}/feedback_templates?include=text`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BASIC,
|
||||
username: auth.props.apiKey,
|
||||
password: '',
|
||||
},
|
||||
});
|
||||
return {
|
||||
options: response.body.data.map(
|
||||
(template: { text: string; id: string }) => {
|
||||
return { label: template.text, value: template.id };
|
||||
}
|
||||
),
|
||||
};
|
||||
},
|
||||
}),
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: `${LEVER_BASE_URL}/opportunities/${propsValue.opportunityId}/feedback`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BASIC,
|
||||
username: auth.props.apiKey,
|
||||
password: '',
|
||||
},
|
||||
});
|
||||
const feedback = response.body.data;
|
||||
if (propsValue.template) {
|
||||
return feedback.filter(
|
||||
(form: { baseTemplateId: string }) =>
|
||||
form.baseTemplateId === propsValue.template
|
||||
);
|
||||
}
|
||||
return feedback;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { LEVER_BASE_URL, LeverAuth, leverAuth } from '../..';
|
||||
import {
|
||||
AuthenticationType,
|
||||
httpClient,
|
||||
HttpMethod,
|
||||
} from '@activepieces/pieces-common';
|
||||
|
||||
export const listOpportunityForms = createAction({
|
||||
name: 'listOpportunityForms',
|
||||
displayName: 'List opportunity forms',
|
||||
description:
|
||||
'Get all forms for a given opportunity, optionally for a given form template',
|
||||
auth: leverAuth,
|
||||
props: {
|
||||
opportunityId: Property.ShortText({
|
||||
displayName: 'Opportunity ID',
|
||||
required: true,
|
||||
}),
|
||||
template: Property.Dropdown({
|
||||
auth: leverAuth,
|
||||
displayName: 'Form template',
|
||||
required: false,
|
||||
refreshers: ['auth'],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please connect first.',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: `${LEVER_BASE_URL}/form_templates?include=text`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BASIC,
|
||||
username: auth.props.apiKey,
|
||||
password: '',
|
||||
},
|
||||
});
|
||||
return {
|
||||
options: response.body.data.map(
|
||||
(template: { text: string; id: string }) => {
|
||||
return { label: template.text, value: template.id };
|
||||
}
|
||||
),
|
||||
};
|
||||
},
|
||||
}),
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: `${LEVER_BASE_URL}/opportunities/${propsValue.opportunityId}/forms`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BASIC,
|
||||
username: auth.props.apiKey,
|
||||
password: '',
|
||||
},
|
||||
});
|
||||
const forms = response.body.data;
|
||||
if (propsValue.template) {
|
||||
return forms.filter(
|
||||
(form: { baseTemplateId: string }) =>
|
||||
form.baseTemplateId === propsValue.template
|
||||
);
|
||||
}
|
||||
return forms;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import {
|
||||
AuthenticationType,
|
||||
httpClient,
|
||||
HttpMethod,
|
||||
} from '@activepieces/pieces-common';
|
||||
import { LEVER_BASE_URL, LeverAuth, leverAuth } from '../..';
|
||||
|
||||
export const updateOpportunityStage = createAction({
|
||||
name: 'updateOpportunityStage',
|
||||
displayName: 'Update opportunity stage',
|
||||
description: "Change an Opportunity's current stage",
|
||||
auth: leverAuth,
|
||||
props: {
|
||||
opportunityId: Property.ShortText({
|
||||
displayName: 'Opportunity ID',
|
||||
required: true,
|
||||
}),
|
||||
stage: Property.Dropdown({
|
||||
auth: leverAuth,
|
||||
displayName: 'Stage',
|
||||
required: true,
|
||||
refreshers: ['auth'],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please connect first.',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: `${LEVER_BASE_URL}/stages`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BASIC,
|
||||
username: auth.props.apiKey,
|
||||
password: '',
|
||||
},
|
||||
});
|
||||
return {
|
||||
options: response.body.data.map(
|
||||
(stage: { text: string; id: string }) => {
|
||||
return { label: stage.text, value: stage.id };
|
||||
}
|
||||
),
|
||||
};
|
||||
},
|
||||
}),
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.PUT,
|
||||
url: `${LEVER_BASE_URL}/opportunities/${propsValue.opportunityId}/stage`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BASIC,
|
||||
username: auth.props.apiKey,
|
||||
password: '',
|
||||
},
|
||||
body: { stage: propsValue.stage },
|
||||
});
|
||||
|
||||
return response.body.data;
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user