Skip to content

Commit 067b0dd

Browse files
authored
Merge pull request #1393 from OpenFn/support-trigger-webhook-response
deploy: support webhook_response in triggers
2 parents b0eb0bc + d43bab9 commit 067b0dd

15 files changed

Lines changed: 463 additions & 32 deletions

.changeset/frank-canyons-report.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@openfn/deploy': minor
3+
---
4+
5+
Add support for webhook_response_config

.changeset/salty-areas-juggle.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@openfn/lexicon': minor
3+
---
4+
5+
Support `cron_cursor_job_id`, `webhook_reply` and `webhook_response_config` in Provisioner types

.changeset/shy-needles-attack.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@openfn/cli': minor
3+
---
4+
5+
Support webhook responses in sync & deploy

.changeset/wide-seals-tease.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@openfn/project': patch
3+
---
4+
5+
Support more trigger keys

packages/deploy/src/stateTransform.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,13 @@ function mergeTriggers(
164164
if (specTrigger.type === 'webhook' && specTrigger.webhook_reply) {
165165
trigger.webhook_reply = specTrigger.webhook_reply;
166166
}
167+
if (
168+
specTrigger.type === 'webhook' &&
169+
specTrigger.webhook_response_config
170+
) {
171+
trigger.webhook_response_config =
172+
specTrigger.webhook_response_config;
173+
}
167174

168175
if (specTrigger.type === 'cron') {
169176
trigger.cron_expression = specTrigger.cron_expression;
@@ -202,6 +209,13 @@ function mergeTriggers(
202209
if (specTrigger!.type === 'webhook' && specTrigger!.webhook_reply) {
203210
trigger.webhook_reply = specTrigger!.webhook_reply;
204211
}
212+
if (
213+
specTrigger!.type === 'webhook' &&
214+
specTrigger!.webhook_response_config
215+
) {
216+
trigger.webhook_response_config =
217+
specTrigger!.webhook_response_config;
218+
}
205219

206220
if (specTrigger!.type === 'cron') {
207221
trigger.cron_expression = specTrigger!.cron_expression;

packages/deploy/src/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,19 @@ export type SpecKafkaConfiguration = {
3838
connect_timeout: number;
3939
};
4040

41+
export type WebhookResponseConfig = {
42+
error_code: number | null;
43+
success_code: number | null;
44+
};
45+
4146
export type WebhookReply = 'before_start' | 'after_completion';
4247

4348
export type SpecTrigger = {
4449
type: string;
4550
cron_expression?: string;
4651
cron_cursor_job?: string;
4752
webhook_reply?: WebhookReply;
53+
webhook_response_config?: WebhookResponseConfig | null;
4854
enabled?: boolean;
4955
kafka_configuration?: SpecKafkaConfiguration;
5056
};
@@ -55,6 +61,7 @@ export type StateTrigger = {
5561
cron_expression?: string;
5662
cron_cursor_job_id?: string | null;
5763
webhook_reply?: WebhookReply;
64+
webhook_response_config?: WebhookResponseConfig | null;
5865
delete?: boolean;
5966
enabled?: boolean;
6067
kafka_configuration?: StateKafkaConfiguration;

packages/deploy/test/stateTransform.test.ts

Lines changed: 134 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,125 @@ test('toNextState omits webhook_reply on existing trigger when not specified', (
639639
t.false('webhook_reply' in result.workflows.w.triggers.t);
640640
});
641641

642+
test('toNextState sets webhook_response_config when specified', (t) => {
643+
const state = { workflows: {} };
644+
const spec = {
645+
name: 'my project',
646+
workflows: {
647+
w: {
648+
name: 'workflow',
649+
jobs: {},
650+
triggers: {
651+
t: {
652+
type: 'webhook',
653+
webhook_response_config: { success_code: 200, error_code: 400 },
654+
},
655+
},
656+
edges: {},
657+
},
658+
},
659+
};
660+
661+
const result = mergeSpecIntoState(state, spec);
662+
t.deepEqual(result.workflows.w.triggers.t.webhook_response_config, {
663+
success_code: 200,
664+
error_code: 400,
665+
});
666+
});
667+
668+
test('toNextState omits webhook_response_config when not specified', (t) => {
669+
const state = { workflows: {} };
670+
const spec = {
671+
name: 'my project',
672+
workflows: {
673+
w: {
674+
name: 'workflow',
675+
jobs: {},
676+
triggers: {
677+
t: { type: 'webhook' },
678+
},
679+
edges: {},
680+
},
681+
},
682+
};
683+
684+
const result = mergeSpecIntoState(state, spec);
685+
t.false('webhook_response_config' in result.workflows.w.triggers.t);
686+
});
687+
688+
test('toNextState sets webhook_response_config on existing trigger', (t) => {
689+
const triggerId = 'aaa-bbb-ccc';
690+
const state = {
691+
workflows: {
692+
w: {
693+
id: 'wf-1',
694+
name: 'workflow',
695+
jobs: {},
696+
triggers: {
697+
t: { id: triggerId, type: 'webhook', enabled: true },
698+
},
699+
edges: {},
700+
},
701+
},
702+
};
703+
const spec = {
704+
name: 'my project',
705+
workflows: {
706+
w: {
707+
name: 'workflow',
708+
jobs: {},
709+
triggers: {
710+
t: {
711+
type: 'webhook',
712+
webhook_response_config: { success_code: 201, error_code: 500 },
713+
},
714+
},
715+
edges: {},
716+
},
717+
},
718+
};
719+
720+
const result = mergeSpecIntoState(state, spec);
721+
t.is(result.workflows.w.triggers.t.id, triggerId);
722+
t.deepEqual(result.workflows.w.triggers.t.webhook_response_config, {
723+
success_code: 201,
724+
error_code: 500,
725+
});
726+
});
727+
728+
test('toNextState omits webhook_response_config on existing trigger when not specified', (t) => {
729+
const triggerId = 'aaa-bbb-ccc';
730+
const state = {
731+
workflows: {
732+
w: {
733+
id: 'wf-1',
734+
name: 'workflow',
735+
jobs: {},
736+
triggers: {
737+
t: { id: triggerId, type: 'webhook', enabled: true },
738+
},
739+
edges: {},
740+
},
741+
},
742+
};
743+
const spec = {
744+
name: 'my project',
745+
workflows: {
746+
w: {
747+
name: 'workflow',
748+
jobs: {},
749+
triggers: {
750+
t: { type: 'webhook' },
751+
},
752+
edges: {},
753+
},
754+
},
755+
};
756+
757+
const result = mergeSpecIntoState(state, spec);
758+
t.false('webhook_response_config' in result.workflows.w.triggers.t);
759+
});
760+
642761
test('toNextState sets cron_cursor_job_id on existing trigger', (t) => {
643762
const triggerId = 'aaa-bbb-ccc';
644763
const jobId = 'job-uuid-111';
@@ -663,10 +782,18 @@ test('toNextState sets cron_cursor_job_id on existing trigger', (t) => {
663782
w: {
664783
name: 'workflow',
665784
jobs: {
666-
'job-a': { name: 'job a', adaptor: '@openfn/language-http', body: 'fn()' },
785+
'job-a': {
786+
name: 'job a',
787+
adaptor: '@openfn/language-http',
788+
body: 'fn()',
789+
},
667790
},
668791
triggers: {
669-
t: { type: 'cron', cron_expression: '0 * * * *', cron_cursor_job: 'job-a' },
792+
t: {
793+
type: 'cron',
794+
cron_expression: '0 * * * *',
795+
cron_cursor_job: 'job-a',
796+
},
670797
},
671798
edges: {},
672799
},
@@ -702,7 +829,11 @@ test('toNextState omits cron_cursor_job_id on existing trigger when not specifie
702829
w: {
703830
name: 'workflow',
704831
jobs: {
705-
'job-a': { name: 'job a', adaptor: '@openfn/language-http', body: 'fn()' },
832+
'job-a': {
833+
name: 'job a',
834+
adaptor: '@openfn/language-http',
835+
body: 'fn()',
836+
},
706837
},
707838
triggers: {
708839
t: { type: 'cron', cron_expression: '0 * * * *' },

packages/lexicon/lightning.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,10 +309,18 @@ export namespace Provisioner {
309309
delete?: boolean;
310310
};
311311

312+
export type WebhookResponseConfig = {
313+
error_code: number | null;
314+
success_code: number | null;
315+
};
316+
312317
export type Trigger = {
313318
id: string;
314319
type: string;
315320
cron_expression?: string;
321+
cron_cursor_job_id?: string | null;
322+
webhook_reply?: 'before_start' | 'after_completion';
323+
webhook_response_config?: WebhookResponseConfig | null;
316324
delete?: boolean;
317325
enabled?: boolean;
318326
kafka_configuration?: KafkaConfiguration;

packages/lexicon/portability.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,11 @@ export interface Trigger extends Step {
7878

7979
enabled?: boolean;
8080

81-
webhook_reply?: string;
81+
webhook_reply?: 'before_start' | 'after_completion';
82+
webhook_response_config?: {
83+
error_code?: number;
84+
success_code?: number;
85+
};
8286
cron_cursor_job_id?: string;
8387

8488
/** Allow arbitrary properties on trigger nodes (as configuration options) */

packages/project/src/parse/from-app-state.ts

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Provisioner } from '@openfn/lexicon/lightning';
66
import { Project } from '../Project';
77
import renameKeys from '../util/rename-keys';
88
import slugify from '../util/slugify';
9+
import omitNil from '../util/omit-nil';
910
import ensureJson from '../util/ensure-json';
1011
import getCredentialName from '../util/get-credential-name';
1112

@@ -120,31 +121,45 @@ export const mapWorkflow = (
120121
// TODO what do we do if the condition is disabled?
121122
// I don't think that's the same as edge condition false?
122123
Object.values(workflow.triggers).forEach((trigger: Provisioner.Trigger) => {
123-
const { type, enabled, ...otherProps } = trigger;
124+
const {
125+
type,
126+
enabled,
127+
cron_expression,
128+
cron_cursor_job_id,
129+
webhook_reply,
130+
webhook_response_config,
131+
...otherProps
132+
} = trigger;
124133
if (!mapped.start) {
125134
mapped.start = type;
126135
}
127136

128137
const connectedEdges = Object.values(edges).filter(
129138
(e) => e.source_trigger_id === trigger.id
130139
);
131-
mapped.steps.push({
132-
id: type,
133-
type,
134-
enabled,
135-
openfn: renameKeys(otherProps, { id: 'uuid' }),
136-
next: connectedEdges.reduce((obj: any, edge) => {
137-
const target = Object.values(jobs).find(
138-
(j) => j.id === edge.target_job_id
139-
);
140-
if (!target) {
141-
throw new Error(`Failed to find ${edge.target_job_id}`);
142-
}
143-
// we use the name, not the id, to reference
144-
obj[slugify(target.name)] = mapEdge(edge);
145-
return obj;
146-
}, {}),
147-
} as l.Trigger);
140+
mapped.steps.push(
141+
omitNil({
142+
id: type,
143+
type,
144+
enabled,
145+
cron_expression,
146+
cron_cursor_job_id,
147+
webhook_reply,
148+
webhook_response_config,
149+
openfn: renameKeys(otherProps, { id: 'uuid' }),
150+
next: connectedEdges.reduce((obj: any, edge) => {
151+
const target = Object.values(jobs).find(
152+
(j) => j.id === edge.target_job_id
153+
);
154+
if (!target) {
155+
throw new Error(`Failed to find ${edge.target_job_id}`);
156+
}
157+
// we use the name, not the id, to reference
158+
obj[slugify(target.name)] = mapEdge(edge);
159+
return obj;
160+
}, {}),
161+
}) as l.Trigger
162+
);
148163
});
149164

150165
Object.values(workflow.jobs).forEach((step: Provisioner.Job) => {

0 commit comments

Comments
 (0)