feat: Capture unhandled errors by restructuring apps
This commit is contained in:
@@ -1,13 +1,16 @@
|
|||||||
import { IGlobalVariable, IJSONObject } from "@automatisch/types";
|
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||||
import type { AxiosResponse } from 'axios';
|
import type { AxiosResponse } from 'axios';
|
||||||
import parseLinkHeader from '../../../helpers/parse-header-link';
|
import parseLinkHeader from '../../../helpers/parse-header-link';
|
||||||
|
|
||||||
type TResponse = {
|
type TResponse = {
|
||||||
data: IJSONObject[],
|
data: IJSONObject[];
|
||||||
error?: IJSONObject,
|
error?: IJSONObject;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default async function paginateAll($: IGlobalVariable, request: Promise<AxiosResponse>) {
|
export default async function paginateAll(
|
||||||
|
$: IGlobalVariable,
|
||||||
|
request: Promise<AxiosResponse>
|
||||||
|
) {
|
||||||
const response = await request;
|
const response = await request;
|
||||||
const aggregatedResponse: TResponse = {
|
const aggregatedResponse: TResponse = {
|
||||||
data: [...response.data],
|
data: [...response.data],
|
||||||
@@ -21,8 +24,8 @@ export default async function paginateAll($: IGlobalVariable, request: Promise<A
|
|||||||
url: links.next.uri,
|
url: links.next.uri,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (nextPageResponse.integrationError) {
|
if (nextPageResponse.httpError) {
|
||||||
aggregatedResponse.error = nextPageResponse.integrationError;
|
aggregatedResponse.error = nextPageResponse.httpError;
|
||||||
|
|
||||||
links = null;
|
links = null;
|
||||||
} else {
|
} else {
|
||||||
|
@@ -1,12 +1,11 @@
|
|||||||
import {
|
import { IGlobalVariable, ITriggerOutput } from '@automatisch/types';
|
||||||
IGlobalVariable,
|
|
||||||
ITriggerOutput,
|
|
||||||
} 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';
|
||||||
|
|
||||||
function getPathname($: IGlobalVariable) {
|
function getPathname($: IGlobalVariable) {
|
||||||
const { repoOwner, repo } = getRepoOwnerAndRepo($.step.parameters.repo as string);
|
const { repoOwner, repo } = getRepoOwnerAndRepo(
|
||||||
|
$.step.parameters.repo as string
|
||||||
|
);
|
||||||
|
|
||||||
if (repoOwner && repo) {
|
if (repoOwner && repo) {
|
||||||
return `/repos/${repoOwner}/${repo}/issues`;
|
return `/repos/${repoOwner}/${repo}/issues`;
|
||||||
@@ -35,8 +34,8 @@ const newIssues = async ($: IGlobalVariable) => {
|
|||||||
const response = await $.http.get(pathname, { params });
|
const response = await $.http.get(pathname, { params });
|
||||||
links = parseLinkHeader(response.headers.link);
|
links = parseLinkHeader(response.headers.link);
|
||||||
|
|
||||||
if (response.integrationError) {
|
if (response.httpError) {
|
||||||
issues.error = response.integrationError;
|
issues.error = response.httpError;
|
||||||
return issues;
|
return issues;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +43,8 @@ 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 issues;
|
if (issueId <= Number($.flow.lastInternalId) && !$.execution.testRun)
|
||||||
|
return issues;
|
||||||
|
|
||||||
const dataItem = {
|
const dataItem = {
|
||||||
raw: issue,
|
raw: issue,
|
||||||
|
@@ -10,10 +10,12 @@ import parseLinkHeader from '../../../../helpers/parse-header-link';
|
|||||||
type TResponseDataItem = {
|
type TResponseDataItem = {
|
||||||
starred_at: string;
|
starred_at: string;
|
||||||
user: IJSONObject;
|
user: IJSONObject;
|
||||||
}
|
};
|
||||||
|
|
||||||
const fetchStargazers = async ($: IGlobalVariable) => {
|
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 firstPagePathname = `/repos/${repoOwner}/${repo}/stargazers`;
|
||||||
const requestConfig = {
|
const requestConfig = {
|
||||||
params: {
|
params: {
|
||||||
@@ -22,10 +24,13 @@ const fetchStargazers = async ($: IGlobalVariable) => {
|
|||||||
headers: {
|
headers: {
|
||||||
// needed to get `starred_at` time
|
// needed to get `starred_at` time
|
||||||
Accept: 'application/vnd.github.star+json',
|
Accept: 'application/vnd.github.star+json',
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
const firstPageResponse = await $.http.get<TResponseDataItem[]>(firstPagePathname, requestConfig);
|
const firstPageResponse = await $.http.get<TResponseDataItem[]>(
|
||||||
|
firstPagePathname,
|
||||||
|
requestConfig
|
||||||
|
);
|
||||||
const firstPageLinks = parseLinkHeader(firstPageResponse.headers.link);
|
const firstPageLinks = parseLinkHeader(firstPageResponse.headers.link);
|
||||||
|
|
||||||
// in case there is only single page to fetch
|
// in case there is only single page to fetch
|
||||||
@@ -36,12 +41,15 @@ const fetchStargazers = async ($: IGlobalVariable) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
do {
|
do {
|
||||||
const response = await $.http.get<TResponseDataItem[]>(pathname, requestConfig);
|
const response = await $.http.get<TResponseDataItem[]>(
|
||||||
|
pathname,
|
||||||
|
requestConfig
|
||||||
|
);
|
||||||
const links = parseLinkHeader(response.headers.link);
|
const links = parseLinkHeader(response.headers.link);
|
||||||
pathname = links.prev?.uri;
|
pathname = links.prev?.uri;
|
||||||
|
|
||||||
if (response.integrationError) {
|
if (response.httpError) {
|
||||||
stargazers.error = response.integrationError;
|
stargazers.error = response.httpError;
|
||||||
return stargazers;
|
return stargazers;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +58,8 @@ const fetchStargazers = 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 stargazers;
|
if (timestamp <= Number($.flow.lastInternalId) && !$.execution.testRun)
|
||||||
|
return stargazers;
|
||||||
|
|
||||||
const dataItem = {
|
const dataItem = {
|
||||||
raw: user,
|
raw: user,
|
||||||
@@ -65,13 +74,15 @@ const fetchStargazers = async ($: IGlobalVariable) => {
|
|||||||
} while (pathname && !$.execution.testRun);
|
} while (pathname && !$.execution.testRun);
|
||||||
|
|
||||||
return stargazers;
|
return stargazers;
|
||||||
}
|
};
|
||||||
|
|
||||||
const newStargazers = async ($: IGlobalVariable) => {
|
const newStargazers = async ($: IGlobalVariable) => {
|
||||||
const stargazers = await fetchStargazers($);
|
const stargazers = await fetchStargazers($);
|
||||||
|
|
||||||
stargazers.data.sort((stargazerA, stargazerB) => {
|
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;
|
return stargazers;
|
||||||
|
@@ -25,7 +25,7 @@ const findMessage = async ($: IGlobalVariable, options: FindMessageOptions) => {
|
|||||||
data: {
|
data: {
|
||||||
raw: data?.messages.matches[0],
|
raw: data?.messages.matches[0],
|
||||||
},
|
},
|
||||||
error: response?.integrationError || (!data.ok && data),
|
error: response?.httpError || (!data.ok && data),
|
||||||
};
|
};
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
|
@@ -16,7 +16,7 @@ const postMessage = async (
|
|||||||
data: {
|
data: {
|
||||||
raw: response?.data?.message,
|
raw: response?.data?.message,
|
||||||
},
|
},
|
||||||
error: response?.integrationError,
|
error: response?.httpError,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (response.data.ok === false) {
|
if (response.data.ok === false) {
|
||||||
|
@@ -15,8 +15,8 @@ export default {
|
|||||||
|
|
||||||
const response = await $.http.get('/conversations.list');
|
const response = await $.http.get('/conversations.list');
|
||||||
|
|
||||||
if (response.integrationError) {
|
if (response.httpError) {
|
||||||
channels.error = response.integrationError;
|
channels.error = response.httpError;
|
||||||
return channels;
|
return channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -40,7 +40,7 @@ export default defineAction({
|
|||||||
data: {
|
data: {
|
||||||
raw: response.data,
|
raw: response.data,
|
||||||
},
|
},
|
||||||
error: response?.integrationError,
|
error: response?.httpError,
|
||||||
};
|
};
|
||||||
|
|
||||||
return tweet;
|
return tweet;
|
||||||
|
@@ -33,8 +33,8 @@ const getUserFollowers = async (
|
|||||||
|
|
||||||
response = await $.http.get(requestPath);
|
response = await $.http.get(requestPath);
|
||||||
|
|
||||||
if (response.integrationError) {
|
if (response.httpError) {
|
||||||
followers.error = response.integrationError;
|
followers.error = response.httpError;
|
||||||
return followers;
|
return followers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -36,8 +36,8 @@ const fetchTweets = async ($: IGlobalVariable, username: string) => {
|
|||||||
|
|
||||||
response = await $.http.get(requestPath);
|
response = await $.http.get(requestPath);
|
||||||
|
|
||||||
if (response.integrationError) {
|
if (response.httpError) {
|
||||||
tweets.error = response.integrationError;
|
tweets.error = response.httpError;
|
||||||
return tweets;
|
return tweets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -33,4 +33,10 @@ export default defineTrigger({
|
|||||||
async run($) {
|
async run($) {
|
||||||
return await searchTweets($);
|
return await searchTweets($);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
sort($) {
|
||||||
|
$.output.data.sort((tweet, nextTweet) => {
|
||||||
|
return Number(tweet.meta.internalId) - Number(nextTweet.meta.internalId);
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
@@ -1,20 +1,12 @@
|
|||||||
import {
|
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||||
IGlobalVariable,
|
|
||||||
IJSONObject,
|
|
||||||
ITriggerOutput,
|
|
||||||
} from '@automatisch/types';
|
|
||||||
import qs from 'qs';
|
import qs from 'qs';
|
||||||
import { omitBy, isEmpty } from 'lodash';
|
import { omitBy, isEmpty } from 'lodash';
|
||||||
|
|
||||||
const fetchTweets = async ($: IGlobalVariable) => {
|
const searchTweets = async ($: IGlobalVariable) => {
|
||||||
const searchTerm = $.step.parameters.searchTerm as string;
|
const searchTerm = $.step.parameters.searchTerm as string;
|
||||||
|
|
||||||
let response;
|
let response;
|
||||||
|
|
||||||
const tweets: ITriggerOutput = {
|
|
||||||
data: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
const params: IJSONObject = {
|
const params: IJSONObject = {
|
||||||
query: searchTerm,
|
query: searchTerm,
|
||||||
@@ -30,14 +22,8 @@ const fetchTweets = async ($: IGlobalVariable) => {
|
|||||||
|
|
||||||
response = await $.http.get(requestPath);
|
response = await $.http.get(requestPath);
|
||||||
|
|
||||||
if (response.integrationError) {
|
|
||||||
tweets.error = response.integrationError;
|
|
||||||
return tweets;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.data.errors) {
|
if (response.data.errors) {
|
||||||
tweets.error = response.data.errors;
|
throw new Error(JSON.stringify(response.data.errors));
|
||||||
return tweets;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.data.meta.result_count > 0) {
|
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);
|
} 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;
|
export default searchTweets;
|
||||||
|
@@ -59,6 +59,10 @@ const globalVariable = async (
|
|||||||
id: execution?.id,
|
id: execution?.id,
|
||||||
testRun,
|
testRun,
|
||||||
},
|
},
|
||||||
|
output: {
|
||||||
|
data: [],
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
$.http = createHttpClient({
|
$.http = createHttpClient({
|
||||||
|
@@ -39,8 +39,8 @@ export default function createHttpClient({
|
|||||||
instance.interceptors.response.use(
|
instance.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
(error) => {
|
(error) => {
|
||||||
error.response.integrationError = error.response.data;
|
error.response.httpError = error.response.data;
|
||||||
return error.response;
|
throw error;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -10,12 +10,7 @@ class App {
|
|||||||
|
|
||||||
// Temporaryly restrict the apps we expose until
|
// Temporaryly restrict the apps we expose until
|
||||||
// their actions/triggers are implemented!
|
// their actions/triggers are implemented!
|
||||||
static temporaryList = [
|
static temporaryList = ['github', 'scheduler', 'slack', 'twitter'];
|
||||||
'github',
|
|
||||||
'scheduler',
|
|
||||||
'slack',
|
|
||||||
'twitter',
|
|
||||||
];
|
|
||||||
|
|
||||||
static async findAll(name?: string, stripFuncs = true): Promise<IApp[]> {
|
static async findAll(name?: string, stripFuncs = true): Promise<IApp[]> {
|
||||||
if (!name)
|
if (!name)
|
||||||
|
@@ -20,5 +20,23 @@ export const processFlow = async (options: ProcessFlowOptions) => {
|
|||||||
testRun: options.testRun,
|
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;
|
||||||
};
|
};
|
||||||
|
9
packages/types/index.d.ts
vendored
9
packages/types/index.d.ts
vendored
@@ -213,7 +213,8 @@ export interface ITrigger {
|
|||||||
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<void | ITriggerOutput>;
|
||||||
|
sort?($: IGlobalVariable): void | ITriggerOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IActionOutput {
|
export interface IActionOutput {
|
||||||
@@ -279,11 +280,15 @@ export type IGlobalVariable = {
|
|||||||
id: string;
|
id: string;
|
||||||
testRun: boolean;
|
testRun: boolean;
|
||||||
};
|
};
|
||||||
|
output: {
|
||||||
|
data: ITriggerDataItem[];
|
||||||
|
error?: IJSONObject;
|
||||||
|
}
|
||||||
process?: (triggerDataItem: ITriggerDataItem) => Promise<void>;
|
process?: (triggerDataItem: ITriggerDataItem) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare module 'axios' {
|
declare module 'axios' {
|
||||||
interface AxiosResponse {
|
interface AxiosResponse {
|
||||||
integrationError?: IJSONObject;
|
httpError?: IJSONObject;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user