-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexecuteAction.ts
More file actions
209 lines (192 loc) · 6.83 KB
/
Copy pathexecuteAction.ts
File metadata and controls
209 lines (192 loc) · 6.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
import { createWebhookPayload } from "@/core/action/webhook/createWebhookPayload";
import { stepInclude } from "@/core/flow/flowPrismaTypes";
import { finalizeActionAndRequest } from "@/core/request/updateState/finalizeActionAndRequest";
import { resultGroupInclude } from "@/core/result/resultPrismaTypes";
import { ActionType } from "@/graphql/generated/resolver-types";
import { decrypt } from "@/prisma/encrypt";
import { calculateBackoffMs } from "@/utils/calculateBackoffMs";
import { evolveFlow } from "./evolveFlow";
import { evolveGroup } from "./evolveGroup";
import { groupWatchFlow } from "./groupWatchFlow";
import { triggerNextStep } from "./triggerNextStep";
import { prisma } from "../../../prisma/client";
import { callWebhook } from "../webhook/callWebhook";
export interface ExecuteActionReturn {
nextRequestStepId: string | null;
responseComplete: boolean;
}
// Executes an action if it exists
// since the action is the last execution component of a request step,
// this function is also responsible for determining the final request_step and request statuses
// actions are designed to be retried later if they fail
// returns boolean on whether action will need to be rerun
export const executeAction = async ({
requestStepId,
// only includes results from the current ste
}: {
requestStepId: string;
}): Promise<ExecuteActionReturn> => {
const defaultReturn: ExecuteActionReturn = {
nextRequestStepId: null,
responseComplete: false,
};
try {
const maxActionRetries = 10;
// const nextRequestStepId: string | null = null;
const reqStep = await prisma.requestStep.findFirstOrThrow({
where: {
id: requestStepId,
},
include: {
Step: {
include: stepInclude,
},
ResultGroups: {
include: resultGroupInclude,
orderBy: { index: "asc" },
},
Actions: true,
},
});
const action = reqStep.Step.ActionConfigSet?.ActionConfigs[0];
if (!action) {
await finalizeActionAndRequest({ requestStepId, finalizeRequest: true });
return defaultReturn;
}
const actionFilter = action.ActionConfigFilter;
// if the action filter isn't passed, end the request step and request
if (actionFilter) {
const result = reqStep.ResultGroups.find(
(r) => r.resultConfigId === actionFilter.resultConfigId,
);
const passesFilter = result?.Result[0].ResultItems.some(
(val) => val.fieldOptionId === actionFilter.optionId,
);
if (!passesFilter) {
// end request step without taking an action
await finalizeActionAndRequest({ requestStepId, finalizeRequest: true });
return defaultReturn;
}
}
const actionExecution = reqStep.Actions.find((a) => a.actionConfigId === action.id);
if (actionExecution) {
// this should never evaluate to true, but just in case so action doesn't run again
if (actionExecution.complete) {
await finalizeActionAndRequest({ requestStepId, finalizeRequest: true });
return defaultReturn;
}
// if the action has been attempted too many times, mark it as final
if ((actionExecution?.retryAttempts ?? 0) > maxActionRetries) {
return await prisma.$transaction(async (transaction): Promise<ExecuteActionReturn> => {
await prisma.action.update({
where: {
actionConfigId_requestStepId: {
actionConfigId: action.id,
requestStepId: requestStepId,
},
},
data: {
complete: false,
},
});
await finalizeActionAndRequest({ requestStepId, transaction, finalizeRequest: true });
return defaultReturn;
});
}
// check if action is ready to be retried again, if not return existing incomplete result
if (actionExecution.nextRetryAt && actionExecution.nextRetryAt > new Date()) {
return defaultReturn;
}
}
try {
return await prisma.$transaction(async (transaction): Promise<ExecuteActionReturn> => {
let nextRequestStepId: string | null = null;
let runResultsForNextStep = false;
switch (action.type) {
case ActionType.CallWebhook: {
if (!action.ActionConfigWebhook) throw Error("");
const payload = await createWebhookPayload({ requestStepId });
const uri = decrypt(action.ActionConfigWebhook.uri);
await callWebhook({ uri, payload });
break;
}
case ActionType.TriggerStep: {
const res = await triggerNextStep({ requestStepId, transaction });
nextRequestStepId = res.nextRequestStepId;
runResultsForNextStep = res.responseComplete;
break;
}
case ActionType.EvolveFlow: {
await evolveFlow({ requestStepId, transaction });
break;
}
case ActionType.GroupWatchFlow: {
await groupWatchFlow({ requestStepId, transaction });
break;
}
case ActionType.EvolveGroup:
await evolveGroup({ requestStepId, transaction });
break;
default:
break;
}
await transaction.action.upsert({
where: {
actionConfigId_requestStepId: {
actionConfigId: action.id,
requestStepId: requestStepId,
},
},
update: {
complete: true,
lastAttemptedAt: new Date(),
},
create: {
actionConfigId: action.id,
requestStepId,
complete: true,
lastAttemptedAt: new Date(),
},
});
await finalizeActionAndRequest({
requestStepId,
transaction,
// Don't finalize request if there's another step to trigger
finalizeRequest: !nextRequestStepId,
});
return {
nextRequestStepId,
responseComplete: runResultsForNextStep,
} as ExecuteActionReturn;
});
} catch (e) {
const retryAttempts = actionExecution?.retryAttempts ?? 1;
const nextRetryAt = new Date(Date.now() + calculateBackoffMs(retryAttempts));
await prisma.action.upsert({
where: {
actionConfigId_requestStepId: {
actionConfigId: action.id,
requestStepId: requestStepId,
},
},
create: {
actionConfigId: action.id,
requestStepId,
complete: false,
lastAttemptedAt: new Date(),
nextRetryAt,
retryAttempts,
},
update: {
lastAttemptedAt: new Date(),
retryAttempts,
nextRetryAt,
},
});
return defaultReturn;
}
} catch (error) {
console.error("Error in executeAction:", error);
return defaultReturn;
}
};