feat: add defineTrigger and defineAction

This commit is contained in:
Ali BARIN
2022-10-15 22:16:10 +02:00
parent ee9d095454
commit 92a2d68a81
23 changed files with 147 additions and 131 deletions

View File

@@ -4,7 +4,7 @@
"license": "AGPL-3.0", "license": "AGPL-3.0",
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.", "description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
"scripts": { "scripts": {
"dev": "ts-node-dev src/server.ts", "dev": "ts-node-dev --exit-child src/server.ts",
"worker": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/worker.ts", "worker": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/worker.ts",
"build": "tsc && yarn copy-statics", "build": "tsc && yarn copy-statics",
"build:watch": "nodemon --watch 'src/**/*.ts' --watch 'bin/**/*.ts' --exec yarn build --ext ts", "build:watch": "nodemon --watch 'src/**/*.ts' --watch 'bin/**/*.ts' --exec yarn build --ext ts",

View File

@@ -1,4 +1,5 @@
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { IJSONObject } from '@automatisch/types';
export default function getDateTimeObjectRepresentation(dateTime: DateTime) { export default function getDateTimeObjectRepresentation(dateTime: DateTime) {
const defaults = dateTime.toObject(); const defaults = dateTime.toObject();
@@ -10,5 +11,5 @@ export default function getDateTimeObjectRepresentation(dateTime: DateTime) {
pretty_time: dateTime.toLocaleString(DateTime.TIME_WITH_SECONDS), pretty_time: dateTime.toLocaleString(DateTime.TIME_WITH_SECONDS),
pretty_day_of_week: dateTime.toFormat('cccc'), pretty_day_of_week: dateTime.toFormat('cccc'),
day_of_week: dateTime.weekday, day_of_week: dateTime.weekday,
}; } as IJSONObject;
} }

View File

@@ -6,5 +6,4 @@ export default {
authDocUrl: "https://automatisch.io/docs/connections/scheduler", authDocUrl: "https://automatisch.io/docs/connections/scheduler",
primaryColor: "0059F7", primaryColor: "0059F7",
supportsConnections: false, supportsConnections: false,
requiresAuthentication: false,
}; };

View File

@@ -1,10 +1,11 @@
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { IGlobalVariable, IJSONValue } from '@automatisch/types'; import { IGlobalVariable, IJSONValue } from '@automatisch/types';
import defineTrigger from '../../../../helpers/define-trigger';
import cronTimes from '../../common/cron-times'; import cronTimes from '../../common/cron-times';
import getNextCronDateTime from '../../common/get-next-cron-date-time'; import getNextCronDateTime from '../../common/get-next-cron-date-time';
import getDateTimeObjectRepresentation from '../../common/get-date-time-object'; import getDateTimeObjectRepresentation from '../../common/get-date-time-object';
export default { export default defineTrigger({
name: 'Every day', name: 'Every day',
key: 'everyDay', key: 'everyDay',
description: 'Triggers every day.', description: 'Triggers every day.',
@@ -154,14 +155,14 @@ export default {
return cronTimes.everyDayExcludingWeekendsAt(parameters.hour as number); return cronTimes.everyDayExcludingWeekendsAt(parameters.hour as number);
}, },
async run($: IGlobalVariable) { async run($) {
const nextCronDateTime = getNextCronDateTime( const nextCronDateTime = getNextCronDateTime(
this.getInterval($.step.parameters) this.getInterval($.step.parameters)
); );
const dateTime = DateTime.now(); const dateTime = DateTime.now();
const dateTimeObjectRepresentation = getDateTimeObjectRepresentation( const dateTimeObjectRepresentation = getDateTimeObjectRepresentation(
$.execution.testRun ? nextCronDateTime : dateTime $.execution.testRun ? nextCronDateTime : dateTime
) as IJSONValue; );
const dataItem = { const dataItem = {
raw: dateTimeObjectRepresentation, raw: dateTimeObjectRepresentation,
@@ -172,4 +173,4 @@ export default {
return { data: [dataItem] }; return { data: [dataItem] };
}, },
}; });

View File

@@ -1,10 +1,11 @@
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { IGlobalVariable, IJSONValue } from '@automatisch/types'; import { IGlobalVariable } from '@automatisch/types';
import defineTrigger from '../../../../helpers/define-trigger';
import cronTimes from '../../common/cron-times'; import cronTimes from '../../common/cron-times';
import getNextCronDateTime from '../../common/get-next-cron-date-time'; import getNextCronDateTime from '../../common/get-next-cron-date-time';
import getDateTimeObjectRepresentation from '../../common/get-date-time-object'; import getDateTimeObjectRepresentation from '../../common/get-date-time-object';
export default { export default defineTrigger({
name: 'Every hour', name: 'Every hour',
key: 'everyHour', key: 'everyHour',
description: 'Triggers every hour.', description: 'Triggers every hour.',
@@ -48,14 +49,14 @@ export default {
return cronTimes.everyHourExcludingWeekends; return cronTimes.everyHourExcludingWeekends;
}, },
async run($: IGlobalVariable) { async run($) {
const nextCronDateTime = getNextCronDateTime( const nextCronDateTime = getNextCronDateTime(
this.getInterval($.step.parameters) this.getInterval($.step.parameters)
); );
const dateTime = DateTime.now(); const dateTime = DateTime.now();
const dateTimeObjectRepresentation = getDateTimeObjectRepresentation( const dateTimeObjectRepresentation = getDateTimeObjectRepresentation(
$.execution.testRun ? nextCronDateTime : dateTime $.execution.testRun ? nextCronDateTime : dateTime
) as IJSONValue; );
const dataItem = { const dataItem = {
raw: dateTimeObjectRepresentation, raw: dateTimeObjectRepresentation,
@@ -66,4 +67,4 @@ export default {
return { data: [dataItem] }; return { data: [dataItem] };
}, },
}; });

View File

@@ -1,10 +1,11 @@
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { IGlobalVariable, IJSONValue } from '@automatisch/types'; import { IGlobalVariable } from '@automatisch/types';
import defineTrigger from '../../../../helpers/define-trigger';
import cronTimes from '../../common/cron-times'; import cronTimes from '../../common/cron-times';
import getNextCronDateTime from '../../common/get-next-cron-date-time'; import getNextCronDateTime from '../../common/get-next-cron-date-time';
import getDateTimeObjectRepresentation from '../../common/get-date-time-object'; import getDateTimeObjectRepresentation from '../../common/get-date-time-object';
export default { export default defineTrigger({
name: 'Every month', name: 'Every month',
key: 'everyMonth', key: 'everyMonth',
description: 'Triggers every month.', description: 'Triggers every month.',
@@ -22,127 +23,127 @@ export default {
variables: false, variables: false,
options: [ options: [
{ {
label: 1, label: '1',
value: 1, value: 1,
}, },
{ {
label: 2, label: '2',
value: 2, value: 2,
}, },
{ {
label: 3, label: '3',
value: 3, value: 3,
}, },
{ {
label: 4, label: '4',
value: 4, value: 4,
}, },
{ {
label: 5, label: '5',
value: 5, value: 5,
}, },
{ {
label: 6, label: '6',
value: 6, value: 6,
}, },
{ {
label: 7, label: '7',
value: 7, value: 7,
}, },
{ {
label: 8, label: '8',
value: 8, value: 8,
}, },
{ {
label: 9, label: '9',
value: 9, value: 9,
}, },
{ {
label: 10, label: '10',
value: 10, value: 10,
}, },
{ {
label: 11, label: '11',
value: 11, value: 11,
}, },
{ {
label: 12, label: '12',
value: 12, value: 12,
}, },
{ {
label: 13, label: '13',
value: 13, value: 13,
}, },
{ {
label: 14, label: '14',
value: 14, value: 14,
}, },
{ {
label: 15, label: '15',
value: 15, value: 15,
}, },
{ {
label: 16, label: '16',
value: 16, value: 16,
}, },
{ {
label: 17, label: '17',
value: 17, value: 17,
}, },
{ {
label: 18, label: '18',
value: 18, value: 18,
}, },
{ {
label: 19, label: '19',
value: 19, value: 19,
}, },
{ {
label: 20, label: '20',
value: 20, value: 20,
}, },
{ {
label: 21, label: '21',
value: 21, value: 21,
}, },
{ {
label: 22, label: '22',
value: 22, value: 22,
}, },
{ {
label: 23, label: '23',
value: 23, value: 23,
}, },
{ {
label: 24, label: '24',
value: 24, value: 24,
}, },
{ {
label: 25, label: '25',
value: 25, value: 25,
}, },
{ {
label: 26, label: '26',
value: 26, value: 26,
}, },
{ {
label: 27, label: '27',
value: 27, value: 27,
}, },
{ {
label: 28, label: '28',
value: 28, value: 28,
}, },
{ {
label: 29, label: '29',
value: 29, value: 29,
}, },
{ {
label: 30, label: '30',
value: 30, value: 30,
}, },
{ {
label: 31, label: '31',
value: 31, value: 31,
}, },
], ],
@@ -270,14 +271,14 @@ export default {
return interval; return interval;
}, },
async run($: IGlobalVariable) { async run($) {
const nextCronDateTime = getNextCronDateTime( const nextCronDateTime = getNextCronDateTime(
this.getInterval($.step.parameters) this.getInterval($.step.parameters)
); );
const dateTime = DateTime.now(); const dateTime = DateTime.now();
const dateTimeObjectRepresentation = getDateTimeObjectRepresentation( const dateTimeObjectRepresentation = getDateTimeObjectRepresentation(
$.execution.testRun ? nextCronDateTime : dateTime $.execution.testRun ? nextCronDateTime : dateTime
) as IJSONValue; );
const dataItem = { const dataItem = {
raw: dateTimeObjectRepresentation, raw: dateTimeObjectRepresentation,
@@ -288,4 +289,4 @@ export default {
return { data: [dataItem] }; return { data: [dataItem] };
}, },
}; });

View File

@@ -1,10 +1,11 @@
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { IGlobalVariable, IJSONValue } from '@automatisch/types'; import { IGlobalVariable, IJSONValue } from '@automatisch/types';
import defineTrigger from '../../../../helpers/define-trigger';
import cronTimes from '../../common/cron-times'; import cronTimes from '../../common/cron-times';
import getNextCronDateTime from '../../common/get-next-cron-date-time'; import getNextCronDateTime from '../../common/get-next-cron-date-time';
import getDateTimeObjectRepresentation from '../../common/get-date-time-object'; import getDateTimeObjectRepresentation from '../../common/get-date-time-object';
export default { export default defineTrigger({
name: 'Every week', name: 'Every week',
key: 'everyWeek', key: 'everyWeek',
description: 'Triggers every week.', description: 'Triggers every week.',
@@ -174,14 +175,14 @@ export default {
return interval; return interval;
}, },
async run($: IGlobalVariable) { async run($) {
const nextCronDateTime = getNextCronDateTime( const nextCronDateTime = getNextCronDateTime(
this.getInterval($.step.parameters) this.getInterval($.step.parameters)
); );
const dateTime = DateTime.now(); const dateTime = DateTime.now();
const dateTimeObjectRepresentation = getDateTimeObjectRepresentation( const dateTimeObjectRepresentation = getDateTimeObjectRepresentation(
$.execution.testRun ? nextCronDateTime : dateTime $.execution.testRun ? nextCronDateTime : dateTime
) as IJSONValue; );
const dataItem = { const dataItem = {
raw: dateTimeObjectRepresentation, raw: dateTimeObjectRepresentation,
@@ -192,4 +193,4 @@ export default {
return { data: [dataItem] }; return { data: [dataItem] };
}, },
}; });

View File

@@ -28,7 +28,7 @@ const findMessage = async ($: IGlobalVariable, options: FindMessageOptions) => {
const message: IActionOutput = { const message: IActionOutput = {
data: { data: {
raw: data?.data?.messages.matches[0], raw: data?.messages.matches[0],
}, },
error: response?.integrationError || (!data.ok && data), error: response?.integrationError || (!data.ok && data),
}; };

View File

@@ -1,7 +1,7 @@
import { IGlobalVariable } from '@automatisch/types'; import defineAction from '../../../../helpers/define-action';
import findMessage from './find-message'; import findMessage from './find-message';
export default { export default defineAction({
name: 'Find message', name: 'Find message',
key: 'findMessage', key: 'findMessage',
description: 'Find a Slack message using the Slack Search feature.', description: 'Find a Slack message using the Slack Search feature.',
@@ -71,7 +71,7 @@ export default {
}, },
], ],
async run($: IGlobalVariable) { async run($) {
const parameters = $.step.parameters; const parameters = $.step.parameters;
const query = parameters.query as string; const query = parameters.query as string;
const sortBy = parameters.sortBy as string; const sortBy = parameters.sortBy as string;
@@ -87,4 +87,4 @@ export default {
return messages; return messages;
}, },
}; });

View File

@@ -1,7 +1,7 @@
import { IGlobalVariable } from '@automatisch/types'; import defineAction from '../../../../helpers/define-action';
import postMessage from './post-message'; import postMessage from './post-message';
export default { export default defineAction({
name: 'Send a message to channel', name: 'Send a message to channel',
key: 'sendMessageToChannel', key: 'sendMessageToChannel',
description: 'Send a message to a specific channel you specify.', description: 'Send a message to a specific channel you specify.',
@@ -48,7 +48,7 @@ export default {
}, },
], ],
async run($: IGlobalVariable) { async run($) {
const channelId = $.step.parameters.channel as string; const channelId = $.step.parameters.channel as string;
const text = $.step.parameters.message as string; const text = $.step.parameters.message as string;
@@ -56,4 +56,4 @@ export default {
return message; return message;
}, },
}; });

View File

@@ -1,7 +1,7 @@
import { IGlobalVariable } from '@automatisch/types'; import defineTrigger from '../../../../helpers/define-trigger';
import getUserTweets from '../../common/get-user-tweets'; import getUserTweets from '../../common/get-user-tweets';
export default { export default defineTrigger({
name: 'My Tweets', name: 'My Tweets',
key: 'myTweets', key: 'myTweets',
pollInterval: 15, pollInterval: 15,
@@ -17,9 +17,9 @@ export default {
}, },
], ],
async run($: IGlobalVariable) { async run($) {
return await getUserTweets($, { return await getUserTweets($, {
currentUser: true, currentUser: true,
}); });
}, },
}; });

View File

@@ -1,7 +1,7 @@
import { IGlobalVariable } from '@automatisch/types'; import defineTrigger from '../../../../helpers/define-trigger';
import myFollowers from './my-followers'; import myFollowers from './my-followers';
export default { export default defineTrigger({
name: 'New follower of me', name: 'New follower of me',
key: 'myFollowers', key: 'myFollowers',
pollInterval: 15, pollInterval: 15,
@@ -17,7 +17,7 @@ export default {
}, },
], ],
async run($: IGlobalVariable) { async run($) {
return await myFollowers($, $.flow.lastInternalId); return await myFollowers($, $.flow.lastInternalId);
}, },
}; });

View File

@@ -1,7 +1,7 @@
import { IGlobalVariable } from '@automatisch/types'; import defineTrigger from '../../../../helpers/define-trigger';
import searchTweets from './search-tweets'; import searchTweets from './search-tweets';
export default { export default defineTrigger({
name: 'Search Tweets', name: 'Search Tweets',
key: 'searchTweets', key: 'searchTweets',
pollInterval: 15, pollInterval: 15,
@@ -30,7 +30,7 @@ export default {
}, },
], ],
async run($: IGlobalVariable) { async run($) {
return await searchTweets($); return await searchTweets($);
}, },
}; });

View File

@@ -1,7 +1,7 @@
import { IGlobalVariable } from '@automatisch/types'; import defineTrigger from '../../../../helpers/define-trigger';
import getUserTweets from '../../common/get-user-tweets'; import getUserTweets from '../../common/get-user-tweets';
export default { export default defineTrigger({
name: 'User Tweets', name: 'User Tweets',
key: 'userTweets', key: 'userTweets',
pollInterval: 15, pollInterval: 15,
@@ -29,9 +29,9 @@ export default {
}, },
], ],
async run($: IGlobalVariable) { async run($) {
return await getUserTweets($, { return await getUserTweets($, {
currentUser: false, currentUser: false,
}); });
}, },
}; });

View File

@@ -9,10 +9,14 @@ const appInfoConverter = (rawAppData: IApp) => {
if (rawAppData.auth?.fields) { if (rawAppData.auth?.fields) {
rawAppData.auth.fields = rawAppData.auth.fields.map((field) => { rawAppData.auth.fields = rawAppData.auth.fields.map((field) => {
return { if (typeof field.value === 'string') {
...field, return {
value: field.value?.replace('{WEB_APP_URL}', appConfig.webAppUrl), ...field,
}; value: field.value.replace('{WEB_APP_URL}', appConfig.webAppUrl),
};
}
return field
}); });
} }

View File

@@ -0,0 +1,5 @@
import { IAction } from '@automatisch/types';
export default function defineAction(actionDefinition: IAction): IAction {
return actionDefinition;
}

View File

@@ -0,0 +1,5 @@
import { ITrigger } from '@automatisch/types';
export default function defineTrigger(triggerDefinition: ITrigger): ITrigger {
return triggerDefinition;
}

View File

@@ -16,17 +16,13 @@ async function getFileContent<C>(
path: string, path: string,
stripFuncs: boolean stripFuncs: boolean
): Promise<C> { ): Promise<C> {
try { const fileContent = await getDefaultExport(path);
const fileContent = await getDefaultExport(path);
if (stripFuncs) { if (stripFuncs) {
return stripFunctions(fileContent); return stripFunctions(fileContent);
}
return fileContent;
} catch (err) {
return null;
} }
return fileContent;
} }
async function getChildrenContentInDirectory<C>( async function getChildrenContentInDirectory<C>(
@@ -55,10 +51,12 @@ async function getChildrenContentInDirectory<C>(
const getApp = async (appKey: string, stripFuncs = true) => { const getApp = async (appKey: string, stripFuncs = true) => {
const appData: IApp = await getDefaultExport(`../apps/${appKey}`); const appData: IApp = await getDefaultExport(`../apps/${appKey}`);
appData.auth = await getFileContent<IAuth>( if (appData.supportsConnections) {
`../apps/${appKey}/auth`, appData.auth = await getFileContent<IAuth>(
stripFuncs `../apps/${appKey}/auth`,
); stripFuncs
);
}
appData.triggers = await getChildrenContentInDirectory<ITrigger>( appData.triggers = await getChildrenContentInDirectory<ITrigger>(
`${appKey}/triggers`, `${appKey}/triggers`,
stripFuncs stripFuncs

View File

@@ -48,7 +48,7 @@ export const processAction = async (options: ProcessActionOptions) => {
status: actionOutput.error ? 'failure' : 'success', status: actionOutput.error ? 'failure' : 'success',
dataIn: computedParameters, dataIn: computedParameters,
dataOut: actionOutput.error ? null : actionOutput.data.raw, dataOut: actionOutput.error ? null : actionOutput.data.raw,
errorDetails: actionOutput.error, errorDetails: actionOutput.error ? actionOutput.error : null,
}); });
return { flowId, stepId, executionId, executionStep }; return { flowId, stepId, executionId, executionStep };

View File

@@ -90,45 +90,45 @@ export interface IFieldDropdown {
label: string; label: string;
type: 'dropdown'; type: 'dropdown';
required: boolean; required: boolean;
readOnly: boolean; readOnly?: boolean;
value: string; value?: string | boolean;
placeholder: string | null; placeholder?: string | null;
description: string; description?: string;
docUrl: string; docUrl?: string;
clickToCopy: boolean; clickToCopy?: boolean;
variables?: boolean;
dependsOn?: string[];
options?: IFieldDropdownOption[];
source?: IFieldDropdownSource;
}
export interface IFieldDropdownSource {
type: string;
name: string; name: string;
variables: boolean; arguments: {
dependsOn: string[];
options: IFieldDropdownOption[];
source: {
type: string;
name: string; name: string;
arguments: { value: string;
name: string; }[];
value: string;
}[];
};
} }
export interface IFieldDropdownOption { export interface IFieldDropdownOption {
label: string; label: string;
value: boolean | string; value: boolean | string | number;
} }
export interface IFieldText { export interface IFieldText {
key: string; key: string;
label: string; label: string;
type: 'string'; type: 'string';
required: boolean; required?: boolean;
readOnly: boolean; readOnly?: boolean;
value: string; value?: string;
placeholder: string | null; placeholder?: string | null;
description: string; description?: string;
docUrl: string; docUrl?: string;
clickToCopy: boolean; clickToCopy?: boolean;
name: string; variables?: boolean;
variables: boolean; dependsOn?: string[];
dependsOn: string[];
} }
export type IField = IFieldDropdown | IFieldText; export type IField = IFieldDropdown | IFieldText;
@@ -202,11 +202,11 @@ export interface ITriggerDataItem {
export interface ITrigger { export interface ITrigger {
name: string; name: string;
key: string; key: string;
pollInterval: number; pollInterval?: number;
description: string; description: string;
dedupeStrategy: 'greatest' | 'unique' | 'last'; dedupeStrategy?: 'greatest' | 'unique' | 'last';
substeps: ISubstep[]; substeps: ISubstep[];
getInterval(parameters: IGlobalVariable['step']['parameters']): string; getInterval?(parameters: IGlobalVariable['step']['parameters']): string;
run($: IGlobalVariable): Promise<ITriggerOutput>; run($: IGlobalVariable): Promise<ITriggerOutput>;
} }
@@ -238,7 +238,7 @@ export interface IAuthentication {
export interface ISubstep { export interface ISubstep {
key: string; key: string;
name: string; name: string;
arguments: IField[]; arguments?: IField[];
} }
export type IHttpClientParams = { export type IHttpClientParams = {

View File

@@ -69,7 +69,7 @@ function generateValidationSchema(substeps: ISubstep[]) {
} }
// if the field depends on another field, add the dependsOn required validation // if the field depends on another field, add the dependsOn required validation
if (dependsOn?.length > 0) { if (Array.isArray(dependsOn) && dependsOn.length > 0) {
for (const dependsOnKey of dependsOn) { for (const dependsOnKey of dependsOn) {
const missingDependencyValueMessage = `We're having trouble loading '${key}' data as required field '${dependsOnKey}' is missing.`; const missingDependencyValueMessage = `We're having trouble loading '${key}' data as required field '${dependsOnKey}' is missing.`;

View File

@@ -22,7 +22,7 @@ type RawOption = {
}; };
const optionGenerator = (options: RawOption[]): IFieldDropdownOption[] => options?.map(({ name, value }) => ({ label: name as string, value: value })); const optionGenerator = (options: RawOption[]): IFieldDropdownOption[] => options?.map(({ name, value }) => ({ label: name as string, value: value }));
const getOption = (options: IFieldDropdownOption[], value: string) => options?.find(option => option.value === value); const getOption = (options: IFieldDropdownOption[], value?: string | boolean) => options?.find(option => option.value === value);
export default function InputCreator(props: InputCreatorProps): React.ReactElement { export default function InputCreator(props: InputCreatorProps): React.ReactElement {
const { const {

View File

@@ -4,13 +4,13 @@ import { useFormContext } from 'react-hook-form';
import set from 'lodash/set'; import set from 'lodash/set';
import type { UseFormReturn } from 'react-hook-form'; import type { UseFormReturn } from 'react-hook-form';
import isEqual from 'lodash/isEqual'; import isEqual from 'lodash/isEqual';
import type { IField, IFieldDropdown, IJSONObject } from '@automatisch/types'; import type { IField, IFieldDropdownSource, IJSONObject } from '@automatisch/types';
import { GET_DATA } from 'graphql/queries/get-data'; import { GET_DATA } from 'graphql/queries/get-data';
const variableRegExp = /({.*?})/g; const variableRegExp = /({.*?})/g;
function computeArguments(args: IFieldDropdown["source"]["arguments"], getValues: UseFormReturn["getValues"]): IJSONObject { function computeArguments(args: IFieldDropdownSource["arguments"], getValues: UseFormReturn["getValues"]): IJSONObject {
const initialValue = {}; const initialValue = {};
return args.reduce( return args.reduce(
(result, { name, value }) => { (result, { name, value }) => {