From 9a743fb4a8acf8db048be68bf2938990fbe83293 Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Fri, 21 Oct 2022 19:03:24 +0200 Subject: [PATCH] feat: Capture unhandled errors by restructuring apps --- .../src/apps/github/common/paginate-all.ts | 17 ++++++---- .../github/triggers/new-issues/new-issues.ts | 16 ++++----- .../triggers/new-stargazers/new-stargazers.ts | 33 ++++++++++++------ .../actions/find-message/find-message.ts | 2 +- .../send-a-message-to-channel/post-message.ts | 2 +- .../apps/slack/data/list-channels/index.ts | 4 +-- .../twitter/actions/create-tweet/index.ts | 2 +- .../apps/twitter/common/get-user-followers.ts | 4 +-- .../apps/twitter/common/get-user-tweets.ts | 4 +-- .../twitter/triggers/search-tweets/index.ts | 6 ++++ .../triggers/search-tweets/search-tweets.ts | 34 +++---------------- .../backend/src/helpers/global-variable.ts | 4 +++ .../backend/src/helpers/http-client/index.ts | 4 +-- packages/backend/src/models/app.ts | 7 +--- packages/backend/src/services/flow.ts | 20 ++++++++++- packages/types/index.d.ts | 9 +++-- 16 files changed, 92 insertions(+), 76 deletions(-) diff --git a/packages/backend/src/apps/github/common/paginate-all.ts b/packages/backend/src/apps/github/common/paginate-all.ts index 78a01de9..aa5ed1b8 100644 --- a/packages/backend/src/apps/github/common/paginate-all.ts +++ b/packages/backend/src/apps/github/common/paginate-all.ts @@ -1,13 +1,16 @@ -import { IGlobalVariable, IJSONObject } from "@automatisch/types"; +import { IGlobalVariable, IJSONObject } from '@automatisch/types'; import type { AxiosResponse } from 'axios'; import parseLinkHeader from '../../../helpers/parse-header-link'; type TResponse = { - data: IJSONObject[], - error?: IJSONObject, -} + data: IJSONObject[]; + error?: IJSONObject; +}; -export default async function paginateAll($: IGlobalVariable, request: Promise) { +export default async function paginateAll( + $: IGlobalVariable, + request: Promise +) { const response = await request; const aggregatedResponse: TResponse = { data: [...response.data], @@ -21,8 +24,8 @@ export default async function paginateAll($: IGlobalVariable, request: Promise { const response = await $.http.get(pathname, { params }); links = parseLinkHeader(response.headers.link); - if (response.integrationError) { - issues.error = response.integrationError; + if (response.httpError) { + issues.error = response.httpError; return issues; } @@ -44,7 +43,8 @@ const newIssues = async ($: IGlobalVariable) => { for (const issue of response.data) { const issueId = issue.id; - if (issueId <= Number($.flow.lastInternalId) && !$.execution.testRun) return issues; + if (issueId <= Number($.flow.lastInternalId) && !$.execution.testRun) + return issues; const dataItem = { raw: issue, diff --git a/packages/backend/src/apps/github/triggers/new-stargazers/new-stargazers.ts b/packages/backend/src/apps/github/triggers/new-stargazers/new-stargazers.ts index a22ee083..1831ef1e 100644 --- a/packages/backend/src/apps/github/triggers/new-stargazers/new-stargazers.ts +++ b/packages/backend/src/apps/github/triggers/new-stargazers/new-stargazers.ts @@ -10,10 +10,12 @@ import parseLinkHeader from '../../../../helpers/parse-header-link'; type TResponseDataItem = { starred_at: string; user: IJSONObject; -} +}; const fetchStargazers = async ($: IGlobalVariable) => { - const { repoOwner, repo } = getRepoOwnerAndRepo($.step.parameters.repo as string); + const { repoOwner, repo } = getRepoOwnerAndRepo( + $.step.parameters.repo as string + ); const firstPagePathname = `/repos/${repoOwner}/${repo}/stargazers`; const requestConfig = { params: { @@ -22,10 +24,13 @@ const fetchStargazers = async ($: IGlobalVariable) => { headers: { // needed to get `starred_at` time Accept: 'application/vnd.github.star+json', - } - } + }, + }; - const firstPageResponse = await $.http.get(firstPagePathname, requestConfig); + const firstPageResponse = await $.http.get( + firstPagePathname, + requestConfig + ); const firstPageLinks = parseLinkHeader(firstPageResponse.headers.link); // in case there is only single page to fetch @@ -36,12 +41,15 @@ const fetchStargazers = async ($: IGlobalVariable) => { }; do { - const response = await $.http.get(pathname, requestConfig); + const response = await $.http.get( + pathname, + requestConfig + ); const links = parseLinkHeader(response.headers.link); pathname = links.prev?.uri; - if (response.integrationError) { - stargazers.error = response.integrationError; + if (response.httpError) { + stargazers.error = response.httpError; return stargazers; } @@ -50,7 +58,8 @@ const fetchStargazers = async ($: IGlobalVariable) => { const { starred_at, user } = starEntry; const timestamp = DateTime.fromISO(starred_at).toMillis(); - if (timestamp <= Number($.flow.lastInternalId) && !$.execution.testRun) return stargazers; + if (timestamp <= Number($.flow.lastInternalId) && !$.execution.testRun) + return stargazers; const dataItem = { raw: user, @@ -65,13 +74,15 @@ const fetchStargazers = async ($: IGlobalVariable) => { } while (pathname && !$.execution.testRun); return stargazers; -} +}; const newStargazers = async ($: IGlobalVariable) => { const stargazers = await fetchStargazers($); stargazers.data.sort((stargazerA, stargazerB) => { - return Number(stargazerA.meta.internalId) - Number(stargazerB.meta.internalId); + return ( + Number(stargazerA.meta.internalId) - Number(stargazerB.meta.internalId) + ); }); return stargazers; diff --git a/packages/backend/src/apps/slack/actions/find-message/find-message.ts b/packages/backend/src/apps/slack/actions/find-message/find-message.ts index 72ae767e..2c88790f 100644 --- a/packages/backend/src/apps/slack/actions/find-message/find-message.ts +++ b/packages/backend/src/apps/slack/actions/find-message/find-message.ts @@ -25,7 +25,7 @@ const findMessage = async ($: IGlobalVariable, options: FindMessageOptions) => { data: { raw: data?.messages.matches[0], }, - error: response?.integrationError || (!data.ok && data), + error: response?.httpError || (!data.ok && data), }; return message; diff --git a/packages/backend/src/apps/slack/actions/send-a-message-to-channel/post-message.ts b/packages/backend/src/apps/slack/actions/send-a-message-to-channel/post-message.ts index a43695ab..3ac7017a 100644 --- a/packages/backend/src/apps/slack/actions/send-a-message-to-channel/post-message.ts +++ b/packages/backend/src/apps/slack/actions/send-a-message-to-channel/post-message.ts @@ -16,7 +16,7 @@ const postMessage = async ( data: { raw: response?.data?.message, }, - error: response?.integrationError, + error: response?.httpError, }; if (response.data.ok === false) { diff --git a/packages/backend/src/apps/slack/data/list-channels/index.ts b/packages/backend/src/apps/slack/data/list-channels/index.ts index 84cbcdd0..e95fed0b 100644 --- a/packages/backend/src/apps/slack/data/list-channels/index.ts +++ b/packages/backend/src/apps/slack/data/list-channels/index.ts @@ -15,8 +15,8 @@ export default { const response = await $.http.get('/conversations.list'); - if (response.integrationError) { - channels.error = response.integrationError; + if (response.httpError) { + channels.error = response.httpError; return channels; } diff --git a/packages/backend/src/apps/twitter/actions/create-tweet/index.ts b/packages/backend/src/apps/twitter/actions/create-tweet/index.ts index 17da5737..e167a752 100644 --- a/packages/backend/src/apps/twitter/actions/create-tweet/index.ts +++ b/packages/backend/src/apps/twitter/actions/create-tweet/index.ts @@ -40,7 +40,7 @@ export default defineAction({ data: { raw: response.data, }, - error: response?.integrationError, + error: response?.httpError, }; return tweet; diff --git a/packages/backend/src/apps/twitter/common/get-user-followers.ts b/packages/backend/src/apps/twitter/common/get-user-followers.ts index f63baabb..849b5f29 100644 --- a/packages/backend/src/apps/twitter/common/get-user-followers.ts +++ b/packages/backend/src/apps/twitter/common/get-user-followers.ts @@ -33,8 +33,8 @@ const getUserFollowers = async ( response = await $.http.get(requestPath); - if (response.integrationError) { - followers.error = response.integrationError; + if (response.httpError) { + followers.error = response.httpError; return followers; } diff --git a/packages/backend/src/apps/twitter/common/get-user-tweets.ts b/packages/backend/src/apps/twitter/common/get-user-tweets.ts index 5a13a070..2dcd3430 100644 --- a/packages/backend/src/apps/twitter/common/get-user-tweets.ts +++ b/packages/backend/src/apps/twitter/common/get-user-tweets.ts @@ -36,8 +36,8 @@ const fetchTweets = async ($: IGlobalVariable, username: string) => { response = await $.http.get(requestPath); - if (response.integrationError) { - tweets.error = response.integrationError; + if (response.httpError) { + tweets.error = response.httpError; return tweets; } diff --git a/packages/backend/src/apps/twitter/triggers/search-tweets/index.ts b/packages/backend/src/apps/twitter/triggers/search-tweets/index.ts index b2bb95fe..67bbd55c 100644 --- a/packages/backend/src/apps/twitter/triggers/search-tweets/index.ts +++ b/packages/backend/src/apps/twitter/triggers/search-tweets/index.ts @@ -33,4 +33,10 @@ export default defineTrigger({ async run($) { return await searchTweets($); }, + + sort($) { + $.output.data.sort((tweet, nextTweet) => { + return Number(tweet.meta.internalId) - Number(nextTweet.meta.internalId); + }); + }, }); diff --git a/packages/backend/src/apps/twitter/triggers/search-tweets/search-tweets.ts b/packages/backend/src/apps/twitter/triggers/search-tweets/search-tweets.ts index 4c7b8648..eac3eb38 100644 --- a/packages/backend/src/apps/twitter/triggers/search-tweets/search-tweets.ts +++ b/packages/backend/src/apps/twitter/triggers/search-tweets/search-tweets.ts @@ -1,20 +1,12 @@ -import { - IGlobalVariable, - IJSONObject, - ITriggerOutput, -} from '@automatisch/types'; +import { IGlobalVariable, IJSONObject } from '@automatisch/types'; import qs from 'qs'; import { omitBy, isEmpty } from 'lodash'; -const fetchTweets = async ($: IGlobalVariable) => { +const searchTweets = async ($: IGlobalVariable) => { const searchTerm = $.step.parameters.searchTerm as string; let response; - const tweets: ITriggerOutput = { - data: [], - }; - do { const params: IJSONObject = { query: searchTerm, @@ -30,14 +22,8 @@ const fetchTweets = async ($: IGlobalVariable) => { response = await $.http.get(requestPath); - if (response.integrationError) { - tweets.error = response.integrationError; - return tweets; - } - if (response.data.errors) { - tweets.error = response.data.errors; - return tweets; + throw new Error(JSON.stringify(response.data.errors)); } if (response.data.meta.result_count > 0) { @@ -49,22 +35,10 @@ const fetchTweets = async ($: IGlobalVariable) => { }, }; - tweets.data.push(dataItem); + $.output.data.push(dataItem); }); } } while (response.data.meta.next_token && !$.execution.testRun); - - return tweets; -}; - -const searchTweets = async ($: IGlobalVariable) => { - const tweets = await fetchTweets($); - - tweets.data.sort((tweet, nextTweet) => { - return Number(tweet.meta.internalId) - Number(nextTweet.meta.internalId); - }); - - return tweets; }; export default searchTweets; diff --git a/packages/backend/src/helpers/global-variable.ts b/packages/backend/src/helpers/global-variable.ts index 314ed368..34ddd074 100644 --- a/packages/backend/src/helpers/global-variable.ts +++ b/packages/backend/src/helpers/global-variable.ts @@ -59,6 +59,10 @@ const globalVariable = async ( id: execution?.id, testRun, }, + output: { + data: [], + error: null, + }, }; $.http = createHttpClient({ diff --git a/packages/backend/src/helpers/http-client/index.ts b/packages/backend/src/helpers/http-client/index.ts index a98820a0..c6346e9f 100644 --- a/packages/backend/src/helpers/http-client/index.ts +++ b/packages/backend/src/helpers/http-client/index.ts @@ -39,8 +39,8 @@ export default function createHttpClient({ instance.interceptors.response.use( (response) => response, (error) => { - error.response.integrationError = error.response.data; - return error.response; + error.response.httpError = error.response.data; + throw error; } ); diff --git a/packages/backend/src/models/app.ts b/packages/backend/src/models/app.ts index 0aa34d35..4fdbf247 100644 --- a/packages/backend/src/models/app.ts +++ b/packages/backend/src/models/app.ts @@ -10,12 +10,7 @@ class App { // Temporaryly restrict the apps we expose until // their actions/triggers are implemented! - static temporaryList = [ - 'github', - 'scheduler', - 'slack', - 'twitter', - ]; + static temporaryList = ['github', 'scheduler', 'slack', 'twitter']; static async findAll(name?: string, stripFuncs = true): Promise { if (!name) diff --git a/packages/backend/src/services/flow.ts b/packages/backend/src/services/flow.ts index c11473a2..7ef3886f 100644 --- a/packages/backend/src/services/flow.ts +++ b/packages/backend/src/services/flow.ts @@ -20,5 +20,23 @@ export const processFlow = async (options: ProcessFlowOptions) => { testRun: options.testRun, }); - return await triggerCommand.run($); + try { + await triggerCommand.run($); + } catch (error) { + if (error?.response?.httpError) { + $.output.error = error.response.httpError; + } else { + try { + $.output.error = JSON.parse(error.message); + } catch { + $.output.error = { error: error.message }; + } + } + } + + if (triggerCommand?.sort) { + triggerCommand.sort($); + } + + return $.output; }; diff --git a/packages/types/index.d.ts b/packages/types/index.d.ts index 487557dc..41e17b90 100644 --- a/packages/types/index.d.ts +++ b/packages/types/index.d.ts @@ -213,7 +213,8 @@ export interface ITrigger { dedupeStrategy?: 'greatest' | 'unique' | 'last'; substeps: ISubstep[]; getInterval?(parameters: IGlobalVariable['step']['parameters']): string; - run($: IGlobalVariable): Promise; + run($: IGlobalVariable): Promise; + sort?($: IGlobalVariable): void | ITriggerOutput; } export interface IActionOutput { @@ -279,11 +280,15 @@ export type IGlobalVariable = { id: string; testRun: boolean; }; + output: { + data: ITriggerDataItem[]; + error?: IJSONObject; + } process?: (triggerDataItem: ITriggerDataItem) => Promise; }; declare module 'axios' { interface AxiosResponse { - integrationError?: IJSONObject; + httpError?: IJSONObject; } }