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,44 @@
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { snowflakeAuth } from '../../';
|
||||
import {
|
||||
configureConnection,
|
||||
connect,
|
||||
destroy,
|
||||
execute,
|
||||
snowflakeCommonProps,
|
||||
} from '../common';
|
||||
|
||||
export const insertRowAction = createAction({
|
||||
name: 'insert-row',
|
||||
displayName: 'Insert Row',
|
||||
description: 'Insert a row into a table.',
|
||||
auth: snowflakeAuth,
|
||||
props: {
|
||||
database: snowflakeCommonProps.database,
|
||||
schema: snowflakeCommonProps.schema,
|
||||
table: snowflakeCommonProps.table,
|
||||
table_column_values: snowflakeCommonProps.table_column_values,
|
||||
},
|
||||
async run(context) {
|
||||
const tableName = context.propsValue.table;
|
||||
const tableColumnValues = context.propsValue.table_column_values;
|
||||
|
||||
const columns = Object.keys(tableColumnValues).join(',');
|
||||
const valuePlaceholders = Object.keys(tableColumnValues)
|
||||
.map(() => '?')
|
||||
.join(', ');
|
||||
const statement = `INSERT INTO ${tableName}(${columns}) VALUES(${valuePlaceholders})`;
|
||||
|
||||
const connection = configureConnection(context.auth.props);
|
||||
await connect(connection);
|
||||
|
||||
const response = await execute(
|
||||
connection,
|
||||
statement,
|
||||
Object.values(tableColumnValues)
|
||||
);
|
||||
await destroy(connection);
|
||||
|
||||
return response;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,159 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import snowflake, { Statement, SnowflakeError } from 'snowflake-sdk';
|
||||
import { snowflakeAuth } from '../../index';
|
||||
import { configureConnection } from '../common';
|
||||
|
||||
type QueryResult = unknown[] | undefined;
|
||||
type QueryResults = { query: string; result: QueryResult }[];
|
||||
|
||||
const DEFAULT_APPLICATION_NAME = 'ActivePieces';
|
||||
const DEFAULT_QUERY_TIMEOUT = 30000;
|
||||
|
||||
export const runMultipleQueries = createAction({
|
||||
name: 'runMultipleQueries',
|
||||
displayName: 'Run Multiple Queries',
|
||||
description: 'Run Multiple Queries',
|
||||
auth: snowflakeAuth,
|
||||
props: {
|
||||
sqlTexts: Property.Array({
|
||||
displayName: 'SQL queries',
|
||||
description:
|
||||
'Array of SQL queries to execute in order, in the same transaction. Use :1, :2… placeholders to use binding parameters. ' +
|
||||
'Avoid using "?" to avoid unexpected behaviors when having multiple queries.',
|
||||
required: true,
|
||||
}),
|
||||
binds: Property.Array({
|
||||
displayName: 'Parameters',
|
||||
description:
|
||||
'Binding parameters shared across all queries to prevent SQL injection attacks. ' +
|
||||
'Use :1, :2, etc. to reference parameters in order. ' +
|
||||
'Avoid using "?" to avoid unexpected behaviors when having multiple queries. ' +
|
||||
'Unused parameters are allowed.',
|
||||
required: false,
|
||||
}),
|
||||
useTransaction: Property.Checkbox({
|
||||
displayName: 'Use Transaction',
|
||||
description:
|
||||
'When enabled, all queries will be executed in a single transaction. If any query fails, all changes will be rolled back.',
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
}),
|
||||
timeout: Property.Number({
|
||||
displayName: 'Query timeout (ms)',
|
||||
description:
|
||||
'An integer indicating the maximum number of milliseconds to wait for a query to complete before timing out.',
|
||||
required: false,
|
||||
defaultValue: DEFAULT_QUERY_TIMEOUT,
|
||||
}),
|
||||
application: Property.ShortText({
|
||||
displayName: 'Application name',
|
||||
description:
|
||||
'A string indicating the name of the client application connecting to the server.',
|
||||
required: false,
|
||||
defaultValue: DEFAULT_APPLICATION_NAME,
|
||||
}),
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
const connection = configureConnection(
|
||||
context.auth.props,
|
||||
context.propsValue.application,
|
||||
context.propsValue.timeout
|
||||
);
|
||||
|
||||
return new Promise<QueryResults>((resolve, reject) => {
|
||||
connection.connect(async function (err: SnowflakeError | undefined) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
const { sqlTexts, binds, useTransaction } = context.propsValue;
|
||||
const queryResults: QueryResults = [];
|
||||
|
||||
function handleError(err: SnowflakeError) {
|
||||
if (useTransaction) {
|
||||
connection.execute({
|
||||
sqlText: 'ROLLBACK',
|
||||
complete: () => {
|
||||
connection.destroy(() => {
|
||||
reject(err);
|
||||
});
|
||||
},
|
||||
});
|
||||
} else {
|
||||
connection.destroy(() => {
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function executeQueriesSequentially() {
|
||||
try {
|
||||
if (useTransaction) {
|
||||
await new Promise<void>((resolveBegin, rejectBegin) => {
|
||||
connection.execute({
|
||||
sqlText: 'BEGIN',
|
||||
complete: (err: SnowflakeError | undefined) => {
|
||||
if (err) rejectBegin(err);
|
||||
else resolveBegin();
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
for (const sqlText of sqlTexts) {
|
||||
const result = await new Promise<QueryResult>(
|
||||
(resolveQuery, rejectQuery) => {
|
||||
connection.execute({
|
||||
sqlText: sqlText as string,
|
||||
binds: binds as snowflake.Binds,
|
||||
complete: (
|
||||
err: SnowflakeError | undefined,
|
||||
stmt: Statement,
|
||||
rows: QueryResult
|
||||
) => {
|
||||
if (err) {
|
||||
rejectQuery(err);
|
||||
return;
|
||||
}
|
||||
resolveQuery(rows);
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
queryResults.push({
|
||||
query: sqlText as string,
|
||||
result,
|
||||
});
|
||||
}
|
||||
|
||||
if (useTransaction) {
|
||||
await new Promise<void>((resolveCommit, rejectCommit) => {
|
||||
connection.execute({
|
||||
sqlText: 'COMMIT',
|
||||
complete: (err: SnowflakeError | undefined) => {
|
||||
if (err) rejectCommit(err);
|
||||
else resolveCommit();
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
connection.destroy((err: SnowflakeError | undefined) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(queryResults);
|
||||
});
|
||||
} catch (err) {
|
||||
handleError(err as SnowflakeError); // Reject with the original error!
|
||||
}
|
||||
}
|
||||
|
||||
executeQueriesSequentially();
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import snowflake from 'snowflake-sdk';
|
||||
import { snowflakeAuth } from '../../index';
|
||||
import { configureConnection } from '../common';
|
||||
|
||||
const DEFAULT_APPLICATION_NAME = 'ActivePieces';
|
||||
const DEFAULT_QUERY_TIMEOUT = 30000;
|
||||
|
||||
export const runQuery = createAction({
|
||||
name: 'runQuery',
|
||||
displayName: 'Run Query',
|
||||
description: 'Run Query',
|
||||
auth: snowflakeAuth,
|
||||
props: {
|
||||
sqlText: Property.ShortText({
|
||||
displayName: 'SQL query',
|
||||
description: 'Use :1, :2… or ? placeholders to use binding parameters.',
|
||||
required: true,
|
||||
}),
|
||||
binds: Property.Array({
|
||||
displayName: 'Parameters',
|
||||
description:
|
||||
'Binding parameters for the SQL query (to prevent SQL injection attacks)',
|
||||
required: false,
|
||||
}),
|
||||
timeout: Property.Number({
|
||||
displayName: 'Query timeout (ms)',
|
||||
description:
|
||||
'An integer indicating the maximum number of milliseconds to wait for a query to complete before timing out.',
|
||||
required: false,
|
||||
defaultValue: DEFAULT_QUERY_TIMEOUT,
|
||||
}),
|
||||
application: Property.ShortText({
|
||||
displayName: 'Application name',
|
||||
description:
|
||||
'A string indicating the name of the client application connecting to the server.',
|
||||
required: false,
|
||||
defaultValue: DEFAULT_APPLICATION_NAME,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const connection = configureConnection(
|
||||
context.auth.props,
|
||||
context.propsValue.application,
|
||||
context.propsValue.timeout
|
||||
);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
connection.connect(function (err, conn) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
|
||||
const { sqlText, binds } = context.propsValue;
|
||||
|
||||
connection.execute({
|
||||
sqlText,
|
||||
binds: binds as snowflake.Binds,
|
||||
complete: (err, stmt, rows) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
connection.destroy((err, conn) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
resolve(rows);
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,266 @@
|
||||
import {
|
||||
DynamicPropsValue,
|
||||
PiecePropValueSchema,
|
||||
Property,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { snowflakeAuth } from '../..';
|
||||
import snowflake from 'snowflake-sdk';
|
||||
|
||||
const DEFAULT_APPLICATION_NAME = 'ActivePieces';
|
||||
const DEFAULT_QUERY_TIMEOUT = 30000;
|
||||
|
||||
function formatPrivateKey(privateKey: string): string {
|
||||
const privateKeyLines = privateKey
|
||||
.replace('-----BEGIN PRIVATE KEY-----', '')
|
||||
.replace('-----END PRIVATE KEY-----', '')
|
||||
.trim()
|
||||
.split(' ');
|
||||
|
||||
return [
|
||||
'-----BEGIN PRIVATE KEY-----',
|
||||
...privateKeyLines,
|
||||
'-----END PRIVATE KEY-----',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export function configureConnection(
|
||||
auth: PiecePropValueSchema<typeof snowflakeAuth>,
|
||||
application = DEFAULT_APPLICATION_NAME,
|
||||
timeout = DEFAULT_QUERY_TIMEOUT
|
||||
) {
|
||||
const connectionOptions: snowflake.ConnectionOptions = {
|
||||
application: application,
|
||||
timeout: timeout,
|
||||
username: auth.username,
|
||||
role: auth.role,
|
||||
database: auth.database,
|
||||
warehouse: auth.warehouse,
|
||||
account: auth.account,
|
||||
};
|
||||
|
||||
if (auth.privateKey) {
|
||||
connectionOptions.privateKey = formatPrivateKey(auth.privateKey);
|
||||
connectionOptions.authenticator = 'SNOWFLAKE_JWT';
|
||||
} else {
|
||||
connectionOptions.password = auth.password;
|
||||
}
|
||||
|
||||
return snowflake.createConnection(connectionOptions);
|
||||
}
|
||||
|
||||
export async function connect(conn: snowflake.Connection) {
|
||||
return await new Promise<void>((resolve, reject) => {
|
||||
conn.connect((error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function destroy(conn: snowflake.Connection) {
|
||||
return await new Promise<void>((resolve, reject) => {
|
||||
conn.destroy((error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function execute(
|
||||
conn: snowflake.Connection,
|
||||
sqlText: string,
|
||||
binds: snowflake.Binds
|
||||
) {
|
||||
return await new Promise<any[] | undefined>((resolve, reject) => {
|
||||
conn.execute({
|
||||
sqlText,
|
||||
binds,
|
||||
complete: (error, _, result) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export const snowflakeCommonProps = {
|
||||
database: Property.Dropdown({
|
||||
auth: snowflakeAuth,
|
||||
displayName: 'Database',
|
||||
refreshers: [],
|
||||
required: true,
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Please connect your account first',
|
||||
};
|
||||
}
|
||||
|
||||
const authValue = auth;
|
||||
|
||||
const connection = configureConnection(authValue.props);
|
||||
|
||||
await connect(connection);
|
||||
|
||||
const response = await execute(connection, 'SHOW DATABASES', []);
|
||||
|
||||
await destroy(connection);
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
options: response
|
||||
? response.map((db: any) => {
|
||||
return {
|
||||
label: db.name,
|
||||
value: db.name,
|
||||
};
|
||||
})
|
||||
: [],
|
||||
};
|
||||
},
|
||||
}),
|
||||
schema: Property.Dropdown({
|
||||
auth: snowflakeAuth,
|
||||
displayName: 'Schema',
|
||||
refreshers: ['database'],
|
||||
required: true,
|
||||
options: async ({ auth, database }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Please connect your account first',
|
||||
};
|
||||
}
|
||||
if (!database) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Please select database first',
|
||||
};
|
||||
}
|
||||
|
||||
const authValue = auth;
|
||||
|
||||
const connection = configureConnection(authValue.props);
|
||||
|
||||
await connect(connection);
|
||||
|
||||
const response = await execute(
|
||||
connection,
|
||||
`SHOW SCHEMAS IN DATABASE ${database}`,
|
||||
[]
|
||||
);
|
||||
|
||||
await destroy(connection);
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
options: response
|
||||
? response.map((schema: any) => {
|
||||
return {
|
||||
label: schema.name,
|
||||
value: schema.name,
|
||||
};
|
||||
})
|
||||
: [],
|
||||
};
|
||||
},
|
||||
}),
|
||||
table: Property.Dropdown({
|
||||
auth: snowflakeAuth,
|
||||
displayName: 'Table',
|
||||
refreshers: ['database', 'schema'],
|
||||
required: true,
|
||||
options: async ({ auth, database, schema }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Please connect your account first',
|
||||
};
|
||||
}
|
||||
if (!database) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Please select database first',
|
||||
};
|
||||
}
|
||||
if (!schema) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Please select schema first',
|
||||
};
|
||||
}
|
||||
|
||||
const authValue = auth;
|
||||
|
||||
const connection = configureConnection(authValue.props);
|
||||
|
||||
await connect(connection);
|
||||
|
||||
const response = await execute(
|
||||
connection,
|
||||
`SHOW TABLES IN SCHEMA ${database}.${schema}`,
|
||||
[]
|
||||
);
|
||||
|
||||
await destroy(connection);
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
options: response
|
||||
? response.map((table: any) => {
|
||||
return {
|
||||
label: table.name,
|
||||
value: `${database}.${schema}.${table.name}`,
|
||||
};
|
||||
})
|
||||
: [],
|
||||
};
|
||||
},
|
||||
}),
|
||||
table_column_values: Property.DynamicProperties({
|
||||
auth: snowflakeAuth,
|
||||
displayName: 'Rows',
|
||||
required: true,
|
||||
refreshers: ['database', 'schema', 'table'],
|
||||
props: async ({ auth, table }) => {
|
||||
if (!auth) return {};
|
||||
if (!table) return {};
|
||||
|
||||
const authValue = auth;
|
||||
|
||||
const connection = configureConnection(authValue.props);
|
||||
await connect(connection);
|
||||
const response = await execute(connection, `DESCRIBE TABLE ${table}`, []);
|
||||
await destroy(connection);
|
||||
|
||||
const fields: DynamicPropsValue = {};
|
||||
|
||||
if (response) {
|
||||
for (const column of response) {
|
||||
fields[column.name] = Property.ShortText({
|
||||
displayName: column.name,
|
||||
required: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return fields;
|
||||
},
|
||||
}),
|
||||
};
|
||||
Reference in New Issue
Block a user