Merge pull request #655 from automatisch/refactor/integration-structure

Refactor integration structure
This commit is contained in:
Ömer Faruk Aydın
2022-10-28 20:14:18 +02:00
committed by GitHub
30 changed files with 103 additions and 145 deletions

View File

@@ -6,16 +6,15 @@ export default defineTrigger({
pollInterval: 15, pollInterval: 15,
key: 'new-albums', key: 'new-albums',
description: 'Triggers when you create a new album.', description: 'Triggers when you create a new album.',
dedupeStrategy: 'greatest',
substeps: [ substeps: [
{ {
key: 'chooseConnection', key: 'chooseConnection',
name: 'Choose connection' name: 'Choose connection',
}, },
{ {
key: 'testStep', key: 'testStep',
name: 'Test trigger' name: 'Test trigger',
} },
], ],
async run($) { async run($) {

View File

@@ -19,7 +19,7 @@ const extraFields = [
'url_t', 'url_t',
'url_s', 'url_s',
'url_m', 'url_m',
'url_o' 'url_o',
].join(','); ].join(',');
const newAlbums = async ($: IGlobalVariable) => { const newAlbums = async ($: IGlobalVariable) => {
@@ -42,17 +42,14 @@ const newAlbums = async ($: IGlobalVariable) => {
pages = photosets.pages; pages = photosets.pages;
for (const photoset of photosets.photoset) { for (const photoset of photosets.photoset) {
if ($.flow.isAlreadyProcessed(photoset.id) && !$.execution.testRun)
return;
$.pushTriggerItem({ $.pushTriggerItem({
raw: photoset, raw: photoset,
meta: { meta: {
internalId: photoset.id as string internalId: photoset.id as string,
} },
}) });
} }
} while (page <= pages && !$.execution.testRun); } while (page <= pages);
}; };
export default newAlbums; export default newAlbums;

View File

@@ -6,16 +6,15 @@ export default defineTrigger({
pollInterval: 15, pollInterval: 15,
key: 'newFavoritePhotos', key: 'newFavoritePhotos',
description: 'Triggers when you favorite a photo.', description: 'Triggers when you favorite a photo.',
dedupeStrategy: 'unique',
substeps: [ substeps: [
{ {
key: 'chooseConnection', key: 'chooseConnection',
name: 'Choose connection' name: 'Choose connection',
}, },
{ {
key: 'testStep', key: 'testStep',
name: 'Test trigger' name: 'Test trigger',
} },
], ],
async run($) { async run($) {

View File

@@ -48,17 +48,14 @@ const newPhotos = async ($: IGlobalVariable) => {
pages = photos.pages; pages = photos.pages;
for (const photo of photos.photo) { for (const photo of photos.photo) {
if ($.flow.isAlreadyProcessed(photo.date_faved) && !$.execution.testRun)
return;
$.pushTriggerItem({ $.pushTriggerItem({
raw: photo, raw: photo,
meta: { meta: {
internalId: photo.date_faved as string internalId: photo.date_faved as string,
} },
}) });
} }
} while (page <= pages && !$.execution.testRun); } while (page <= pages);
}; };
export default newPhotos; export default newPhotos;

View File

@@ -6,11 +6,10 @@ export default defineTrigger({
pollInterval: 15, pollInterval: 15,
key: 'newPhotosInAlbum', key: 'newPhotosInAlbum',
description: 'Triggers when you add a new photo in an album.', description: 'Triggers when you add a new photo in an album.',
dedupeStrategy: 'greatest',
substeps: [ substeps: [
{ {
key: 'chooseConnection', key: 'chooseConnection',
name: 'Choose connection' name: 'Choose connection',
}, },
{ {
key: 'chooseTrigger', key: 'chooseTrigger',
@@ -28,17 +27,17 @@ export default defineTrigger({
arguments: [ arguments: [
{ {
name: 'key', name: 'key',
value: 'listAlbums' value: 'listAlbums',
} },
] ],
} },
} },
] ],
}, },
{ {
key: 'testStep', key: 'testStep',
name: 'Test trigger' name: 'Test trigger',
} },
], ],
async run($) { async run($) {

View File

@@ -43,17 +43,14 @@ const newPhotosInAlbum = async ($: IGlobalVariable) => {
pages = photoset.pages; pages = photoset.pages;
for (const photo of photoset.photo) { for (const photo of photoset.photo) {
if ($.flow.isAlreadyProcessed(photo.id) && !$.execution.testRun)
return;
$.pushTriggerItem({ $.pushTriggerItem({
raw: photo, raw: photo,
meta: { meta: {
internalId: photo.id as string internalId: photo.id as string,
} },
}) });
} }
} while (page <= pages && !$.execution.testRun); } while (page <= pages);
}; };
export default newPhotosInAlbum; export default newPhotosInAlbum;

View File

@@ -6,16 +6,15 @@ export default defineTrigger({
pollInterval: 15, pollInterval: 15,
key: 'newPhotos', key: 'newPhotos',
description: 'Triggers when you add a new photo.', description: 'Triggers when you add a new photo.',
dedupeStrategy: 'greatest',
substeps: [ substeps: [
{ {
key: 'chooseConnection', key: 'chooseConnection',
name: 'Choose connection' name: 'Choose connection',
}, },
{ {
key: 'testStep', key: 'testStep',
name: 'Test trigger' name: 'Test trigger',
} },
], ],
async run($) { async run($) {

View File

@@ -48,17 +48,14 @@ const newPhotos = async ($: IGlobalVariable) => {
pages = photos.pages; pages = photos.pages;
for (const photo of photos.photo) { for (const photo of photos.photo) {
if ($.flow.isAlreadyProcessed(photo.id) && !$.execution.testRun)
return;
$.pushTriggerItem({ $.pushTriggerItem({
raw: photo, raw: photo,
meta: { meta: {
internalId: photo.id as string internalId: photo.id as string,
} },
}) });
} }
} while (page <= pages && !$.execution.testRun); } while (page <= pages);
}; };
export default newPhotos; export default newPhotos;

View File

@@ -34,9 +34,6 @@ const newIssues = async ($: IGlobalVariable) => {
for (const issue of response.data) { for (const issue of response.data) {
const issueId = issue.id; const issueId = issue.id;
if (issueId <= Number($.flow.lastInternalId) && !$.execution.testRun)
return;
const dataItem = { const dataItem = {
raw: issue, raw: issue,
meta: { meta: {
@@ -47,7 +44,7 @@ const newIssues = async ($: IGlobalVariable) => {
$.pushTriggerItem(dataItem); $.pushTriggerItem(dataItem);
} }
} }
} while (links.next && !$.execution.testRun); } while (links.next);
}; };
export default newIssues; export default newIssues;

View File

@@ -26,12 +26,6 @@ const newPullRequests = async ($: IGlobalVariable) => {
for (const pullRequest of response.data) { for (const pullRequest of response.data) {
const pullRequestId = pullRequest.id; const pullRequestId = pullRequest.id;
if (
pullRequestId <= Number($.flow.lastInternalId) &&
!$.execution.testRun
)
return;
const dataItem = { const dataItem = {
raw: pullRequest, raw: pullRequest,
meta: { meta: {
@@ -42,7 +36,7 @@ const newPullRequests = async ($: IGlobalVariable) => {
$.pushTriggerItem(dataItem); $.pushTriggerItem(dataItem);
} }
} }
} while (links.next && !$.execution.testRun); } while (links.next);
}; };
export default newPullRequests; export default newPullRequests;

View File

@@ -43,10 +43,4 @@ export default defineTrigger({
async run($) { async run($) {
await newStargazers($); await newStargazers($);
}, },
sort(stargazerA, stargazerB) {
return (
Number(stargazerB.meta.internalId) - Number(stargazerA.meta.internalId)
);
},
}); });

View File

@@ -48,9 +48,6 @@ const newStargazers = async ($: IGlobalVariable) => {
const { starred_at, user } = starEntry; const { starred_at, user } = starEntry;
const timestamp = DateTime.fromISO(starred_at).toMillis(); const timestamp = DateTime.fromISO(starred_at).toMillis();
if (timestamp <= Number($.flow.lastInternalId) && !$.execution.testRun)
return;
const dataItem = { const dataItem = {
raw: user, raw: user,
meta: { meta: {
@@ -61,7 +58,7 @@ const newStargazers = async ($: IGlobalVariable) => {
$.pushTriggerItem(dataItem); $.pushTriggerItem(dataItem);
} }
} }
} while (pathname && !$.execution.testRun); } while (pathname);
}; };
export default newStargazers; export default newStargazers;

View File

@@ -5,7 +5,6 @@ export default defineTrigger({
name: 'New watchers', name: 'New watchers',
key: 'newWatchers', key: 'newWatchers',
pollInterval: 15, pollInterval: 15,
dedupeStrategy: 'unique',
description: 'Triggers when a user watches a repository', description: 'Triggers when a user watches a repository',
substeps: [ substeps: [
{ {
@@ -44,8 +43,4 @@ export default defineTrigger({
async run($) { async run($) {
await newWatchers($); await newWatchers($);
}, },
sort() {
return -1;
},
}); });

View File

@@ -1,4 +1,4 @@
import { IGlobalVariable, ITriggerOutput } from '@automatisch/types'; import { IGlobalVariable } from '@automatisch/types';
import getRepoOwnerAndRepo from '../../common/get-repo-owner-and-repo'; import getRepoOwnerAndRepo from '../../common/get-repo-owner-and-repo';
import parseLinkHeader from '../../../../helpers/parse-header-link'; import parseLinkHeader from '../../../../helpers/parse-header-link';
@@ -34,9 +34,6 @@ const newWatchers = async ($: IGlobalVariable) => {
for (const watcher of response.data) { for (const watcher of response.data) {
const watcherId = watcher.id.toString(); const watcherId = watcher.id.toString();
if ($.flow.isAlreadyProcessed(watcherId) && !$.execution.testRun)
return;
const dataItem = { const dataItem = {
raw: watcher, raw: watcher,
meta: { meta: {
@@ -47,7 +44,7 @@ const newWatchers = async ($: IGlobalVariable) => {
$.pushTriggerItem(dataItem); $.pushTriggerItem(dataItem);
} }
} }
} while (pathname && !$.execution.testRun); } while (pathname);
}; };
export default newWatchers; export default newWatchers;

View File

@@ -1,5 +1,5 @@
import { IGlobalVariable } from '@automatisch/types'; import { IGlobalVariable } from '@automatisch/types';
import { XMLParser } from 'fast-xml-parser';[] import { XMLParser } from 'fast-xml-parser';
const newItemsInFeed = async ($: IGlobalVariable) => { const newItemsInFeed = async ($: IGlobalVariable) => {
const { data } = await $.http.get($.step.parameters.feedUrl as string); const { data } = await $.http.get($.step.parameters.feedUrl as string);
@@ -7,15 +7,12 @@ const newItemsInFeed = async ($: IGlobalVariable) => {
const parsedData = parser.parse(data); const parsedData = parser.parse(data);
for (const item of parsedData.rss.channel.item) { for (const item of parsedData.rss.channel.item) {
if ($.flow.isAlreadyProcessed(item.guid))
return;
const dataItem = { const dataItem = {
raw: item, raw: item,
meta: { meta: {
internalId: item.guid internalId: item.guid,
} },
} };
$.pushTriggerItem(dataItem); $.pushTriggerItem(dataItem);
} }

View File

@@ -1,4 +1,4 @@
import { IGlobalVariable, IActionOutput } from '@automatisch/types'; import { IGlobalVariable } from '@automatisch/types';
type FindMessageOptions = { type FindMessageOptions = {
query: string; query: string;

View File

@@ -31,17 +31,13 @@ const getUserFollowers = async (
if (response.data.meta.result_count > 0) { if (response.data.meta.result_count > 0) {
for (const follower of response.data.data) { for (const follower of response.data.data) {
if ($.flow.isAlreadyProcessed(follower.id as string)) {
return;
}
$.pushTriggerItem({ $.pushTriggerItem({
raw: follower, raw: follower,
meta: { internalId: follower.id as string }, meta: { internalId: follower.id as string },
}); });
} }
} }
} while (response.data.meta.next_token && !$.execution.testRun); } while (response.data.meta.next_token);
}; };
export default getUserFollowers; export default getUserFollowers;

View File

@@ -16,7 +16,7 @@ const fetchTweets = async ($: IGlobalVariable, username: string) => {
do { do {
const params: IJSONObject = { const params: IJSONObject = {
since_id: $.execution.testRun ? null : $.flow.lastInternalId, since_id: $.flow.lastInternalId,
pagination_token: response?.data?.meta?.next_token, pagination_token: response?.data?.meta?.next_token,
}; };
@@ -40,7 +40,7 @@ const fetchTweets = async ($: IGlobalVariable, username: string) => {
$.pushTriggerItem(dataItem); $.pushTriggerItem(dataItem);
}); });
} }
} while (response.data.meta.next_token && !$.execution.testRun); } while (response.data.meta.next_token);
return $.triggerOutput; return $.triggerOutput;
}; };

View File

@@ -20,8 +20,4 @@ export default defineTrigger({
async run($) { async run($) {
await getUserTweets($, { currentUser: true }); await getUserTweets($, { currentUser: true });
}, },
sort(tweet, nextTweet) {
return Number(nextTweet.meta.internalId) - Number(tweet.meta.internalId);
},
}); });

View File

@@ -6,7 +6,6 @@ export default defineTrigger({
key: 'myFollowers', key: 'myFollowers',
pollInterval: 15, pollInterval: 15,
description: 'Will be triggered when you have a new follower.', description: 'Will be triggered when you have a new follower.',
dedupeStrategy: 'unique',
substeps: [ substeps: [
{ {
key: 'chooseConnection', key: 'chooseConnection',

View File

@@ -33,8 +33,4 @@ export default defineTrigger({
async run($) { async run($) {
await searchTweets($); await searchTweets($);
}, },
sort(tweet, nextTweet) {
return Number(nextTweet.meta.internalId) - Number(tweet.meta.internalId);
},
}); });

View File

@@ -10,7 +10,7 @@ const searchTweets = async ($: IGlobalVariable) => {
do { do {
const params: IJSONObject = { const params: IJSONObject = {
query: searchTerm, query: searchTerm,
since_id: $.execution.testRun ? null : $.flow.lastInternalId, since_id: $.flow.lastInternalId,
pagination_token: response?.data?.meta?.next_token, pagination_token: response?.data?.meta?.next_token,
}; };
@@ -38,7 +38,7 @@ const searchTweets = async ($: IGlobalVariable) => {
$.pushTriggerItem(dataItem); $.pushTriggerItem(dataItem);
}); });
} }
} while (response.data.meta.next_token && !$.execution.testRun); } while (response.data.meta.next_token);
}; };
export default searchTweets; export default searchTweets;

View File

@@ -32,8 +32,4 @@ export default defineTrigger({
async run($) { async run($) {
await getUserTweets($, { currentUser: false }); await getUserTweets($, { currentUser: false });
}, },
sort(tweet, nextTweet) {
return Number(nextTweet.meta.internalId) - Number(tweet.meta.internalId);
},
}); });

View File

@@ -0,0 +1,17 @@
import { IJSONObject } from '@automatisch/types';
export default class BaseError extends Error {
error = {};
constructor(error?: string | IJSONObject) {
super();
try {
this.error = JSON.parse(error as string);
} catch {
this.error = typeof error === 'string' ? { error } : error;
}
this.name = this.constructor.name;
}
}

View File

@@ -0,0 +1,3 @@
import BaseError from './base';
export default class EarlyExitError extends BaseError {}

View File

@@ -0,0 +1,3 @@
import BaseError from './base';
export default class HttpError extends BaseError {}

View File

@@ -10,6 +10,7 @@ import {
ITriggerItem, ITriggerItem,
IActionItem, IActionItem,
} from '@automatisch/types'; } from '@automatisch/types';
import EarlyExitError from '../errors/early-exit';
type GlobalVariableOptions = { type GlobalVariableOptions = {
connection?: Connection; connection?: Connection;
@@ -25,9 +26,7 @@ const globalVariable = async (
): Promise<IGlobalVariable> => { ): Promise<IGlobalVariable> => {
const { connection, app, flow, step, execution, testRun = false } = options; const { connection, app, flow, step, execution, testRun = false } = options;
const lastInternalId = await flow?.lastInternalId(); const lastInternalId = testRun ? undefined : await flow?.lastInternalId();
const trigger = await step?.getTriggerCommand();
const nextStep = await step?.getNextStep(); const nextStep = await step?.getNextStep();
const $: IGlobalVariable = { const $: IGlobalVariable = {
@@ -74,7 +73,17 @@ const globalVariable = async (
}, },
}, },
pushTriggerItem: (triggerItem: ITriggerItem) => { pushTriggerItem: (triggerItem: ITriggerItem) => {
if (isAlreadyProcessed(triggerItem.meta.internalId) && !$.execution.testRun) {
// early exit as we do not want to process duplicate items in actual executions
throw new EarlyExitError();
}
$.triggerOutput.data.push(triggerItem); $.triggerOutput.data.push(triggerItem);
if ($.execution.testRun) {
// early exit after receiving one item as it is enough for test execution
throw new EarlyExitError();
}
}, },
setActionItem: (actionItem: IActionItem) => { setActionItem: (actionItem: IActionItem) => {
$.actionOutput.data = actionItem; $.actionOutput.data = actionItem;
@@ -87,27 +96,12 @@ const globalVariable = async (
beforeRequest: app.beforeRequest, beforeRequest: app.beforeRequest,
}); });
if (trigger) { const lastInternalIds =
if (trigger.dedupeStrategy === 'unique') { testRun || (flow && step.isAction) ? [] : await flow?.lastInternalIds(2000);
const lastInternalIds = testRun ? [] : await flow?.lastInternalIds();
const isAlreadyProcessed = (internalId: string) => { const isAlreadyProcessed = (internalId: string) => {
if (testRun) return false; return lastInternalIds?.includes(internalId);
};
return lastInternalIds?.includes(internalId);
};
$.flow.isAlreadyProcessed = isAlreadyProcessed;
} else if (trigger.dedupeStrategy === 'greatest') {
const isAlreadyProcessed = (internalId: string) => {
if (testRun) return false;
return Number(internalId) <= Number($.flow.lastInternalId);
};
$.flow.isAlreadyProcessed = isAlreadyProcessed;
}
}
return $; return $;
}; };

View File

@@ -2,6 +2,7 @@ import axios, { AxiosRequestConfig } from 'axios';
export { AxiosInstance as IHttpClient } from 'axios'; export { AxiosInstance as IHttpClient } from 'axios';
import { IHttpClientParams } from '@automatisch/types'; import { IHttpClientParams } from '@automatisch/types';
import { URL } from 'url'; import { URL } from 'url';
import HttpError from '../../errors/http-error';
const removeBaseUrlForAbsoluteUrls = ( const removeBaseUrlForAbsoluteUrls = (
requestConfig: AxiosRequestConfig requestConfig: AxiosRequestConfig
@@ -39,8 +40,7 @@ export default function createHttpClient({
instance.interceptors.response.use( instance.interceptors.response.use(
(response) => response, (response) => response,
(error) => { (error) => {
error.response.httpError = error.response.data; throw new HttpError(error.response.data);
throw error;
} }
); );

View File

@@ -1,5 +1,7 @@
import Flow from '../models/flow'; import Flow from '../models/flow';
import globalVariable from '../helpers/global-variable'; import globalVariable from '../helpers/global-variable';
import EarlyExitError from '../errors/early-exit';
import HttpError from '../errors/http-error';
type ProcessFlowOptions = { type ProcessFlowOptions = {
flowId: string; flowId: string;
@@ -23,13 +25,15 @@ export const processFlow = async (options: ProcessFlowOptions) => {
try { try {
await triggerCommand.run($); await triggerCommand.run($);
} catch (error) { } catch (error) {
if (error?.response?.httpError) { if (error instanceof EarlyExitError === false) {
$.triggerOutput.error = error.response.httpError; if (error instanceof HttpError) {
} else { $.triggerOutput.error = error.error;
try { } else {
$.triggerOutput.error = JSON.parse(error.message); try {
} catch { $.triggerOutput.error = JSON.parse(error.message);
$.triggerOutput.error = { error: error.message }; } catch {
$.triggerOutput.error = { error: error.message };
}
} }
} }
} }

View File

@@ -210,7 +210,6 @@ export interface ITrigger {
key: string; key: string;
pollInterval?: number; pollInterval?: number;
description: string; description: string;
dedupeStrategy?: 'greatest' | 'unique' | 'last';
substeps: ISubstep[]; substeps: ISubstep[];
getInterval?(parameters: IStep['parameters']): string; getInterval?(parameters: IStep['parameters']): string;
run($: IGlobalVariable): Promise<void>; run($: IGlobalVariable): Promise<void>;