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,59 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { surrealdbAuth } from '../..';
|
||||
import surrealClient from '../common';
|
||||
|
||||
export const runQuery = createAction({
|
||||
auth: surrealdbAuth,
|
||||
name: 'run-query',
|
||||
displayName: 'Run Query',
|
||||
description: 'Run a query in SurrealDB.',
|
||||
props: {
|
||||
markdown: Property.MarkDown({
|
||||
value: `
|
||||
**NOTE:** Prevent SQL injection by using parameterized queries.
|
||||
`,
|
||||
}),
|
||||
query: Property.ShortText({
|
||||
displayName: 'Query',
|
||||
description: 'Provide a SurrealDB query string to execute.',
|
||||
required: true,
|
||||
}),
|
||||
queryMarkdown: Property.MarkDown({
|
||||
value: `
|
||||
**NOTE:** Query example: \`SELECT * FROM table_name WHERE name = $name\`. Then add the name parameter in the arguments.
|
||||
`,
|
||||
}),
|
||||
args: Property.Object({
|
||||
displayName: 'Arguments',
|
||||
description: "Add all arguments as names here, don't add the $ sign.",
|
||||
required: false,
|
||||
}),
|
||||
query_timeout: Property.Number({
|
||||
displayName: 'Query Timeout (ms)',
|
||||
description:
|
||||
'The maximum time to wait for a query to complete before timing out.',
|
||||
required: false,
|
||||
defaultValue: 30000,
|
||||
}),
|
||||
application_name: Property.ShortText({
|
||||
displayName: 'Application Name',
|
||||
description:
|
||||
'An identifier for the client application executing the query.',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
try {
|
||||
const { query, args } = context.propsValue;
|
||||
const response = await surrealClient.query(
|
||||
context.auth.props,
|
||||
query,
|
||||
args as Record<string, string>
|
||||
);
|
||||
return response.body;
|
||||
} catch (error) {
|
||||
throw new Error(`Query execution failed: ${(error as Error).message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
HttpMethod,
|
||||
HttpRequest,
|
||||
httpClient,
|
||||
} from '@activepieces/pieces-common';
|
||||
import { PiecePropValueSchema } from '@activepieces/pieces-framework';
|
||||
import { surrealdbAuth } from '..';
|
||||
|
||||
const query = async (
|
||||
auth: PiecePropValueSchema<typeof surrealdbAuth>,
|
||||
query: string,
|
||||
args?: Record<string, string>
|
||||
) => {
|
||||
const { url, username, password, namespace, database } = auth;
|
||||
|
||||
const sqlUrl = new URL('/sql', url);
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.POST,
|
||||
url: sqlUrl.toString(),
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
Accept: 'application/json',
|
||||
Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString(
|
||||
'base64'
|
||||
)}`,
|
||||
'surreal-ns': namespace,
|
||||
'surreal-db': database,
|
||||
},
|
||||
queryParams: args,
|
||||
body: query,
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest(request);
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
export default {
|
||||
query,
|
||||
};
|
||||
@@ -0,0 +1,192 @@
|
||||
import {
|
||||
createTrigger,
|
||||
TriggerStrategy,
|
||||
PiecePropValueSchema,
|
||||
Property,
|
||||
AppConnectionValueForAuthProperty,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import {
|
||||
DedupeStrategy,
|
||||
Polling,
|
||||
pollingHelper,
|
||||
} from '@activepieces/pieces-common';
|
||||
import dayjs from 'dayjs';
|
||||
import { surrealdbAuth } from '../..';
|
||||
import client from '../common';
|
||||
import crypto from 'crypto';
|
||||
|
||||
// replace auth with piece auth variable
|
||||
const polling: Polling<
|
||||
AppConnectionValueForAuthProperty<typeof surrealdbAuth>,
|
||||
{
|
||||
table: string;
|
||||
order_by: string;
|
||||
order_direction: 'ASC' | 'DESC' | undefined;
|
||||
}
|
||||
> = {
|
||||
strategy: DedupeStrategy.LAST_ITEM,
|
||||
items: async ({ auth, propsValue, lastItemId }) => {
|
||||
const lastItem = lastItemId as string;
|
||||
const query = constructQuery({
|
||||
order_by: propsValue.order_by,
|
||||
lastItem: lastItem,
|
||||
order_direction: propsValue.order_direction,
|
||||
});
|
||||
|
||||
const authProps = auth.props;
|
||||
const result = await client.query(authProps, query, {
|
||||
table: propsValue.table,
|
||||
});
|
||||
|
||||
const items = result.body[0].result.map(function (
|
||||
row: Record<string, any>
|
||||
) {
|
||||
const rowHash = crypto
|
||||
.createHash('md5')
|
||||
.update(JSON.stringify(row))
|
||||
.digest('hex');
|
||||
const isTimestamp = dayjs(row[propsValue.order_by]).isValid();
|
||||
const orderValue = isTimestamp
|
||||
? dayjs(row[propsValue.order_by]).toISOString()
|
||||
: row[propsValue.order_by];
|
||||
return {
|
||||
id: orderValue + '|' + rowHash,
|
||||
data: row,
|
||||
};
|
||||
});
|
||||
|
||||
return items;
|
||||
},
|
||||
};
|
||||
|
||||
function constructQuery({
|
||||
order_by,
|
||||
lastItem,
|
||||
order_direction,
|
||||
}: {
|
||||
order_by: string;
|
||||
order_direction: 'ASC' | 'DESC' | undefined;
|
||||
lastItem: string;
|
||||
}): string {
|
||||
const lastOrderKey = lastItem ? lastItem.split('|')[0] : null;
|
||||
if (lastOrderKey === null) {
|
||||
switch (order_direction) {
|
||||
case 'ASC':
|
||||
return `SELECT * FROM type::table($table) ORDER BY ${order_by} ASC LIMIT 5`;
|
||||
case 'DESC':
|
||||
return `SELECT * FROM type::table($table) ORDER BY ${order_by} DESC LIMIT 5`;
|
||||
default:
|
||||
throw new Error(
|
||||
JSON.stringify({
|
||||
message: 'Invalid order direction',
|
||||
order_direction: order_direction,
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
switch (order_direction) {
|
||||
case 'ASC':
|
||||
return `SELECT * FROM type::table($table) WHERE ${order_by} <= '${lastOrderKey}' ORDER BY ${order_by} ASC`;
|
||||
case 'DESC':
|
||||
return `SELECT * FROM type::table($table) WHERE ${order_by} >= '${lastOrderKey}' ORDER BY ${order_by} DESC`;
|
||||
default:
|
||||
throw new Error(
|
||||
JSON.stringify({
|
||||
message: 'Invalid order direction',
|
||||
order_direction: order_direction,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const newRow = createTrigger({
|
||||
name: 'new-row',
|
||||
displayName: 'New Row',
|
||||
description: 'Triggers when a new row is added to the defined table.',
|
||||
props: {
|
||||
description: Property.MarkDown({
|
||||
value: `**NOTE:** The trigger fetches the latest rows using the provided order by column (newest first), and then will keep polling until the previous last row is reached. It's suggested to add a created_at timestamp. \`DEFINE FIELD OVERWRITE createdAt ON schedule VALUE time::now() READONLY;\``,
|
||||
}),
|
||||
table: Property.Dropdown({
|
||||
displayName: 'Table name',
|
||||
required: true,
|
||||
refreshers: ['auth'],
|
||||
refreshOnSearch: false,
|
||||
auth: surrealdbAuth,
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Please authenticate first',
|
||||
};
|
||||
}
|
||||
const authProps = auth.props;
|
||||
try {
|
||||
const result = await client.query(authProps, 'INFO FOR DB');
|
||||
const options = Object.keys(result.body[0].result.tables).map(
|
||||
(row) => ({
|
||||
label: row,
|
||||
value: row,
|
||||
})
|
||||
);
|
||||
return {
|
||||
disabled: false,
|
||||
options,
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: JSON.stringify(e),
|
||||
};
|
||||
}
|
||||
},
|
||||
}),
|
||||
order_by: Property.ShortText({
|
||||
displayName: 'Column to order by',
|
||||
description: 'Use something like a created timestamp.',
|
||||
required: true,
|
||||
defaultValue: 'created_at',
|
||||
}),
|
||||
order_direction: Property.StaticDropdown<'ASC' | 'DESC'>({
|
||||
displayName: 'Order Direction',
|
||||
description:
|
||||
'The direction to sort by such that the newest rows are fetched first.',
|
||||
required: true,
|
||||
options: {
|
||||
options: [
|
||||
{
|
||||
label: 'Ascending',
|
||||
value: 'ASC',
|
||||
},
|
||||
{
|
||||
label: 'Descending',
|
||||
value: 'DESC',
|
||||
},
|
||||
],
|
||||
},
|
||||
defaultValue: 'DESC',
|
||||
}),
|
||||
},
|
||||
sampleData: {},
|
||||
type: TriggerStrategy.POLLING,
|
||||
auth: surrealdbAuth,
|
||||
async test(context) {
|
||||
return await pollingHelper.test(polling, context);
|
||||
},
|
||||
async onEnable(context) {
|
||||
const { store, auth, propsValue } = context;
|
||||
await pollingHelper.onEnable(polling, { store, propsValue, auth });
|
||||
},
|
||||
|
||||
async onDisable(context) {
|
||||
const { store, auth, propsValue } = context;
|
||||
await pollingHelper.onDisable(polling, { store, propsValue, auth });
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
return await pollingHelper.poll(polling, context);
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user