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,35 @@
|
||||
import { workableAuth } from '../../index';
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { getAccountSubdomain } from '../common/get-subdomain';
|
||||
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
|
||||
|
||||
export const getCandidate = createAction({
|
||||
auth: workableAuth,
|
||||
name: 'getCandidate',
|
||||
displayName: 'Get Candidate',
|
||||
description: "Gets candidate's information.",
|
||||
props: {
|
||||
id: Property.ShortText({
|
||||
displayName: "Candidate's Id",
|
||||
required: true
|
||||
})
|
||||
},
|
||||
async run(context) {
|
||||
// Action logic here
|
||||
const candidateId = context.propsValue.id;
|
||||
const accessToken = context.auth.secret_text;
|
||||
const account = await getAccountSubdomain(accessToken);
|
||||
|
||||
//get candidate information
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: `https://${account}.workable.com/spi/v3/candidates/${candidateId}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { workableAuth } from '../../index';
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
|
||||
import { getAccountSubdomain } from '../common/get-subdomain';
|
||||
|
||||
export const getJob = createAction({
|
||||
auth: workableAuth,
|
||||
name: 'getJob',
|
||||
displayName: 'Get Job',
|
||||
description: 'Gets specific job deatils.',
|
||||
props: {
|
||||
shortcode: Property.ShortText({
|
||||
displayName: "Shortcode",
|
||||
description: "Shortcode of specific job",
|
||||
required: true
|
||||
})
|
||||
},
|
||||
async run(context) {
|
||||
// Action logic here
|
||||
const shortcode = context.propsValue?.shortcode;
|
||||
const accessToken = context.auth.secret_text;
|
||||
|
||||
// get account subdomain
|
||||
const account = await getAccountSubdomain(accessToken);
|
||||
|
||||
const url = `https://${account}.workable.com/spi/v3/jobs/${shortcode}`;
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: url,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,79 @@
|
||||
import { workableAuth } from '../../index';
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { getAccountSubdomain } from '../common/get-subdomain';
|
||||
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
|
||||
|
||||
export const getMembers = createAction({
|
||||
auth: workableAuth,
|
||||
name: 'getMembers',
|
||||
displayName: 'Get Members',
|
||||
description: 'Gets members of hiring team.',
|
||||
props: {
|
||||
limit: Property.Number({
|
||||
displayName: "Limit",
|
||||
description: "Default is 50",
|
||||
required: false
|
||||
}),
|
||||
role: Property.ShortText({
|
||||
displayName: "Role",
|
||||
description: "Filter for member of specified role",
|
||||
required: false
|
||||
}),
|
||||
shortcode: Property.ShortText({
|
||||
displayName: "Shortcode",
|
||||
description: "Shortcode of specific job",
|
||||
required: false
|
||||
}),
|
||||
email: Property.ShortText({
|
||||
displayName: "Member's email",
|
||||
description: "Filter for specific member by email",
|
||||
required: false
|
||||
}),
|
||||
name: Property.ShortText({
|
||||
displayName: "Member's name",
|
||||
description: "Filter for members of specified name (Exact Match)",
|
||||
required: false
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
// Action logic here
|
||||
const limit = context.propsValue['limit'];
|
||||
const role = context.propsValue['role'];
|
||||
const shortcode = context.propsValue['shortcode'];
|
||||
const email = context.propsValue['email'];
|
||||
const name = context.propsValue['name'];
|
||||
|
||||
const accessToken = context.auth.secret_text;
|
||||
|
||||
const queryParams: Record<string, any> = {};
|
||||
|
||||
if(limit !== undefined && limit !== null) {
|
||||
queryParams["limit"] = limit;
|
||||
}
|
||||
if(role && role.trim() !== ''){
|
||||
queryParams["role"] = role;
|
||||
}
|
||||
if(shortcode && shortcode.trim() !== ""){
|
||||
queryParams["shortcode"] = shortcode;
|
||||
}
|
||||
if(email && email.trim() !== ""){
|
||||
queryParams["email"] = email;
|
||||
}
|
||||
if(name && name.trim() !== ''){
|
||||
queryParams["name"] = name;
|
||||
}
|
||||
|
||||
const subdomain = await getAccountSubdomain(accessToken);
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: `https://${subdomain}.workable.com/spi/v3/members`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/json"
|
||||
},
|
||||
queryParams
|
||||
})
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { workableAuth } from '../../index';
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { getAccountSubdomain } from '../common/get-subdomain';
|
||||
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
|
||||
|
||||
export const getStages = createAction({
|
||||
auth: workableAuth,
|
||||
name: 'getStages',
|
||||
displayName: 'Get Stages',
|
||||
description: 'Gets stages in your recruitment pipeline stages.',
|
||||
props: {
|
||||
shortcode: Property.ShortText({
|
||||
displayName: "Shortcode",
|
||||
description: "Shortcode of specific job",
|
||||
required: true
|
||||
})
|
||||
},
|
||||
async run(context) {
|
||||
// Action logic here
|
||||
const shortcode = context.propsValue.shortcode;
|
||||
|
||||
const accessToken = context.auth.secret_text;
|
||||
const account = await getAccountSubdomain(accessToken);
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: `https://${account}.workable.com/spi/v3/jobs/${shortcode}/stages`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/json"
|
||||
}
|
||||
});
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
import { workableAuth } from '../../index';
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { getAccountSubdomain } from '../common/get-subdomain';
|
||||
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
|
||||
|
||||
export const moveCandidate = createAction({
|
||||
auth: workableAuth,
|
||||
name: 'moveCandidate',
|
||||
displayName: 'Move Candidate',
|
||||
description: 'Moves candidate to the specified stage.',
|
||||
props: {
|
||||
id: Property.ShortText({
|
||||
displayName: "Candidate's Id",
|
||||
description: "Id of candidate",
|
||||
required: true
|
||||
}),
|
||||
member_id: Property.ShortText({
|
||||
displayName: "Member's Id",
|
||||
description: "This person's Id is used to move the candidate to the next stage",
|
||||
required: true
|
||||
}),
|
||||
target_stage: Property.ShortText({
|
||||
displayName: "Target stage name",
|
||||
description: "Slug of stage target should be moved to",
|
||||
required: true
|
||||
})
|
||||
},
|
||||
async run(context) {
|
||||
// Action logic here
|
||||
const id = context.propsValue.id;
|
||||
const member_id = context.propsValue.member_id;
|
||||
const target_stage = context.propsValue.target_stage;
|
||||
|
||||
const accessToken = context.auth.secret_text;
|
||||
const subdomain = await getAccountSubdomain(accessToken);
|
||||
|
||||
const payload: Record<string, any> = {
|
||||
member_id: member_id,
|
||||
target_stage: target_stage
|
||||
}
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.POST,
|
||||
url: `https://${subdomain}.workable.com/spi/v3/candidates/${id}/move`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/json"
|
||||
},
|
||||
body: payload
|
||||
})
|
||||
|
||||
return response;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
import { workableAuth } from '../../index';
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { getAccountSubdomain } from '../common/get-subdomain';
|
||||
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
|
||||
|
||||
export const rateCandidate = createAction({
|
||||
auth: workableAuth,
|
||||
name: 'rateCandidate',
|
||||
displayName: 'Rate candidate',
|
||||
description: 'Rates the candidate on workable.',
|
||||
props: {
|
||||
id: Property.ShortText({
|
||||
displayName: "Candidate's Id",
|
||||
description: "Id of candidate",
|
||||
required: true
|
||||
}),
|
||||
member_id: Property.ShortText({
|
||||
displayName: "Member's Id",
|
||||
description: "Id of member that is adding the rating",
|
||||
required: true
|
||||
}),
|
||||
comment: Property.LongText({
|
||||
displayName: "Comment",
|
||||
description: "Comment about the scoring of the candidate",
|
||||
required: true
|
||||
}),
|
||||
scale: Property.StaticDropdown({
|
||||
displayName: "Rating scale",
|
||||
description: "Select scale to rate candidate on",
|
||||
required: true,
|
||||
options: {
|
||||
options: [
|
||||
{label: "Thumbs", value: "thumbs"},
|
||||
{label: "Stars", value: "stars"},
|
||||
{label: "Numbers", value: "numbers"}
|
||||
]
|
||||
}
|
||||
}),
|
||||
grade: Property.Number({
|
||||
displayName: "Grade",
|
||||
description: "Thumbs scale: 0-2, Stars scale: 0-4, Numbers scale: 0-9",
|
||||
required: true
|
||||
})
|
||||
},
|
||||
async run(context) {
|
||||
// Action logic here
|
||||
const id = context.propsValue.id;
|
||||
const member_id = context.propsValue.member_id;
|
||||
const comment = context.propsValue.comment;
|
||||
const scale = context.propsValue.scale;
|
||||
const grade = context.propsValue.grade;
|
||||
|
||||
const accessToken = context.auth.secret_text;
|
||||
const subdomain = await getAccountSubdomain(accessToken);
|
||||
|
||||
const body: Record<string, any> = {
|
||||
comment,
|
||||
scale,
|
||||
member_id,
|
||||
grade
|
||||
};
|
||||
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.POST,
|
||||
url: `https://${subdomain}.workable.com/spi/v3/candidates/${id}/ratings`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/json"
|
||||
},
|
||||
body: body
|
||||
|
||||
})
|
||||
return response;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import { httpClient, HttpMethod } from "@activepieces/pieces-common";
|
||||
|
||||
export async function getAccountSubdomain(accessToken: string){
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: "https://workable.com/spi/v3/accounts",
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: 'application/json'
|
||||
}
|
||||
});
|
||||
console.log("this is response" + response.body.accounts[0].subdomain);
|
||||
|
||||
return response.body.accounts[0]?.subdomain;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { HttpMethod, httpClient, HttpRequest} from '@activepieces/pieces-common';
|
||||
|
||||
export const workableCommon = {
|
||||
subscribeWebhook: async (
|
||||
subdomain: string,
|
||||
accessToken: string,
|
||||
webhookUrl: string,
|
||||
event: string,
|
||||
args: Record<string, any> = {}
|
||||
) => {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.POST,
|
||||
url: `https://${subdomain}.workable.com/spi/v3/subscriptions`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
body: {
|
||||
target: webhookUrl,
|
||||
event,
|
||||
args,
|
||||
}
|
||||
};
|
||||
const response = await httpClient.sendRequest<{id: string}>(request);
|
||||
return response.body;
|
||||
},
|
||||
|
||||
unsubscribeWebhook: async (
|
||||
subdomain: string,
|
||||
accessToken: string,
|
||||
subscriptionId: string
|
||||
) => {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.DELETE,
|
||||
url: `https://${subdomain}.workable.com/spi/v3/subscriptions/${subscriptionId}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: 'application/json'
|
||||
}
|
||||
}
|
||||
|
||||
return await httpClient.sendRequest(request);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
|
||||
import { workableAuth } from '../../index';
|
||||
import { createTrigger, Property, TriggerStrategy, WebhookResponse } from '@activepieces/pieces-framework';
|
||||
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
|
||||
import { getAccountSubdomain } from '../common/get-subdomain';
|
||||
import { workableCommon } from '../common/webhooks';
|
||||
|
||||
interface WebhookInformation {
|
||||
webhookId: string;
|
||||
}
|
||||
|
||||
interface WorkableWebhookPayload {
|
||||
data: any;
|
||||
event_type: string;
|
||||
fired_at: string;
|
||||
id: string;
|
||||
resource_type: string;
|
||||
}
|
||||
|
||||
export const newCandidate = createTrigger({
|
||||
auth: workableAuth,
|
||||
name: 'newCandidate',
|
||||
displayName: 'New Candidate',
|
||||
description: 'Triggers when new candidate submits application. Can be filtered by specific job and/or hiring pipeline stage.',
|
||||
props: {
|
||||
shortcode: Property.ShortText({
|
||||
displayName: "Shortcode",
|
||||
description: "Shortcode of specific job. Leave empty to trigger for all jobs.",
|
||||
required: false
|
||||
}),
|
||||
stage_slug: Property.ShortText({
|
||||
displayName: "Stage Slug",
|
||||
description: "Stage slug to filter by specific hiring pipeline stage (e.g., 'applied', 'phone_screen', 'interview', 'offer'). Leave empty to trigger for all stages.",
|
||||
required: false
|
||||
})
|
||||
},
|
||||
sampleData: {
|
||||
id: '123',
|
||||
name: 'John Doe',
|
||||
firstname: 'John',
|
||||
lastname: 'Doe',
|
||||
headline: 'Software Engineer',
|
||||
account: {
|
||||
subdomain: 'example',
|
||||
name: 'Example Inc.'
|
||||
},
|
||||
job: {
|
||||
shortcode: 'ENG123',
|
||||
title: 'Software Engineer'
|
||||
},
|
||||
stage: 'applied',
|
||||
disqualified: false,
|
||||
disqualification_reason: '',
|
||||
sourced: false,
|
||||
profile_url: 'https://example.workable.com/candidates/123',
|
||||
email: 'john.doe@example.com',
|
||||
domain: 'example.com',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
},
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
async onEnable(context){
|
||||
const accessToken = context.auth.secret_text;
|
||||
const subdomain = await getAccountSubdomain(accessToken);
|
||||
|
||||
const shortcode = context.propsValue.shortcode || '';
|
||||
const stageSlug = context.propsValue.stage_slug || '';
|
||||
const webhookUrl = context.webhookUrl;
|
||||
const event = "candidate_created";
|
||||
|
||||
const subscription = await workableCommon.subscribeWebhook(
|
||||
subdomain,
|
||||
accessToken,
|
||||
webhookUrl,
|
||||
event,
|
||||
{job_shortcode: shortcode, stage_slug: stageSlug}
|
||||
);
|
||||
|
||||
await context.store?.put<WebhookInformation>('_new_candidate_created', {
|
||||
webhookId: subscription.id,
|
||||
})
|
||||
},
|
||||
async onDisable(context){
|
||||
// implement webhook deletion logic
|
||||
const accessToken = context.auth.secret_text;
|
||||
|
||||
const webhookInfo = await context.store.get<WebhookInformation>('_new_candidate_created');
|
||||
|
||||
if(webhookInfo?.webhookId) {
|
||||
const subdomain = await getAccountSubdomain(accessToken);
|
||||
await workableCommon.unsubscribeWebhook(subdomain, accessToken, webhookInfo.webhookId);
|
||||
}
|
||||
},
|
||||
async test(context){
|
||||
const accessToken = context.auth.secret_text;
|
||||
const subdomain = await getAccountSubdomain(accessToken);
|
||||
const shortcode = context.propsValue.shortcode || '';
|
||||
const stageSlug = context.propsValue.stage_slug || '';
|
||||
|
||||
const queryParams: any = {};
|
||||
if (shortcode) {
|
||||
queryParams.shortcode = shortcode;
|
||||
}
|
||||
if (stageSlug) {
|
||||
queryParams.stage = stageSlug;
|
||||
}
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: `https://${subdomain}.workable.com/spi/v3/candidates/`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/json"
|
||||
},
|
||||
queryParams: queryParams
|
||||
});
|
||||
|
||||
const candidates = response.body.candidates?.slice(0, 3) ?? [];
|
||||
return candidates;
|
||||
},
|
||||
async run(context){
|
||||
const payload = context.payload.body as WorkableWebhookPayload;
|
||||
return [payload.data]
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user