Skip to content

Commit 92ec44c

Browse files
vianaswCGastrell
andauthored
Forms: Add form response webhooks (#46059)
* Add form webhook settings panel Added under a feature flag to test the webhook implementation. * Add a form webhook class This is an exploratory commit where I'm using a different class, based on the Post_To_Url one, to process and dispatch the webhooks on form submission. * changelog * Add webhook id input field I added the webhook id input to the settings section to explore how we could use it for advanced users that would like to programmatically manipulate form data before sending it to webhook. For that, we'll need to expose a filter which I'll bring in a follow up commit. * Add jetpack_forms_before_webhook_request I'm adding this filter here to allow developers to intercept and modify the form data that I'm sending in the webhook request. This should give enough fleixibility to advanced users. * Log response and errors to post meta table * Fix param type in documentation * Fix JetpackContactFormAttributes type declaration * Fix unit test * Forms: add comprehensive webhook functionality (#46099) * let salesforce process remain as it was. Split (just in case) postToUrl and migrate it to a webhook * add class consts for cleaner code * create and validate webhook setup with class consts, ditch non matching formats/methods * use class constants on request process * invert filter params order so data is returned by default even when no filter is set * add tests for Form_Webhooks class * make phan happy on test files * changelog * Fix webhook method validation * Forms: Add webhook logging and initialization flag (#46110) * add logging for skipped webhooks * only init webhooks class if webhooks are enabled * changelog * add tests for webhooks and contact form flags * fix phan issues * Add plan-based feature support for form webhooks (#46138) * add form-webhooks support on free JP plans * set flag form-webhooks based on current plan support * consider both filter and feature flag for enabling webhook settings * add webhooks feature support plans * add changelogs * add wpcomsh changelog --------- Co-authored-by: Christian Gastrell <[email protected]>
1 parent 03168e3 commit 92ec44c

21 files changed

+1307
-3
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: minor
2+
Type: added
3+
4+
Forms: add form response webhook support.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: minor
2+
Type: changed
3+
4+
Enable form webhooks feature based on current plan support
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: patch
2+
Type: added
3+
4+
Form Webhooks: add logging and filter flag for initialization
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: patch
2+
Type: changed
3+
4+
Form Webhooks: clean up and consolidate webhooks to take over the unused-yet-present postToUrl

projects/packages/forms/src/blocks/contact-form/attributes.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,8 @@ export default {
8585
type: 'array',
8686
default: [],
8787
},
88+
webhooks: {
89+
type: 'array',
90+
default: [],
91+
},
8892
};

projects/packages/forms/src/blocks/contact-form/class-contact-form-block.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public static function register_block() {
6262
public static function register_feature( $features ) {
6363
// Features that are only available to users with a paid plan.
6464
$features['multistep-form'] = Current_Plan::supports( 'multistep-form' );
65+
$features['form-webhooks'] = Current_Plan::supports( 'form-webhooks' );
6566

6667
return $features;
6768
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { TextControl, ToggleControl, ExternalLink } from '@wordpress/components';
2+
import { useState, useEffect, createInterpolateElement } from '@wordpress/element';
3+
import { __ } from '@wordpress/i18n';
4+
5+
const WebhooksSettings = ( { setAttributes, webhooks } ) => {
6+
// For now, we only support one webhook, but the data structure supports multiple
7+
const firstWebhook = webhooks?.[ 0 ] || null;
8+
9+
const [ localWebhookId, setLocalWebhookId ] = useState( firstWebhook?.webhook_id || '' );
10+
const [ localWebhookUrl, setLocalWebhookUrl ] = useState( firstWebhook?.url || '' );
11+
const [ localWebhookEnabled, setLocalWebhookEnabled ] = useState(
12+
firstWebhook?.enabled || false
13+
);
14+
15+
// Sync local state with attributes when webhook changes
16+
useEffect( () => {
17+
if ( firstWebhook ) {
18+
setLocalWebhookId( firstWebhook.webhook_id || '' );
19+
setLocalWebhookUrl( firstWebhook.url || '' );
20+
setLocalWebhookEnabled( firstWebhook.enabled || false );
21+
}
22+
}, [ firstWebhook ] );
23+
24+
const updateWebhook = ( id, url, enabled ) => {
25+
if ( ! url && ! enabled ) {
26+
// If URL is empty and webhook is disabled, remove it from the array
27+
setAttributes( { webhooks: [] } );
28+
return;
29+
}
30+
31+
const webhook = {
32+
webhook_id: id || '',
33+
url: url || '',
34+
format: 'json', // Default to json, no UI for changing this yet
35+
method: 'POST', // Default to POST, no UI for changing this yet
36+
enabled: enabled,
37+
};
38+
39+
setAttributes( { webhooks: [ webhook ] } );
40+
};
41+
42+
return (
43+
<>
44+
<ToggleControl
45+
label={ __( 'Enable webhook', 'jetpack-forms' ) }
46+
help={ createInterpolateElement(
47+
__(
48+
'Send form submission data to an external URL. <webhookDocsLink>Learn more about webhooks.</webhookDocsLink>',
49+
'jetpack-forms'
50+
),
51+
{
52+
webhookDocsLink: (
53+
<ExternalLink href="https://jetpack.com/support/jetpack-forms/webhooks/" />
54+
),
55+
}
56+
) }
57+
checked={ localWebhookEnabled }
58+
onChange={ value => {
59+
setLocalWebhookEnabled( value );
60+
updateWebhook( localWebhookId, localWebhookUrl, value );
61+
} }
62+
__nextHasNoMarginBottom={ true }
63+
/>
64+
{ localWebhookEnabled && (
65+
<>
66+
<TextControl
67+
label={ __( 'Webhook ID', 'jetpack-forms' ) }
68+
value={ localWebhookId }
69+
onChange={ value => {
70+
setLocalWebhookId( value );
71+
updateWebhook( value, localWebhookUrl, localWebhookEnabled );
72+
} }
73+
placeholder="webhook-1"
74+
help={ __( 'A unique identifier for this webhook.', 'jetpack-forms' ) }
75+
__nextHasNoMarginBottom={ true }
76+
__next40pxDefaultSize={ true }
77+
/>
78+
<TextControl
79+
label={ __( 'Webhook URL', 'jetpack-forms' ) }
80+
value={ localWebhookUrl }
81+
onChange={ value => {
82+
setLocalWebhookUrl( value );
83+
updateWebhook( localWebhookId, value, localWebhookEnabled );
84+
} }
85+
placeholder="https://example.com/webhook"
86+
help={ __(
87+
'Enter the URL where form submission data should be sent.',
88+
'jetpack-forms'
89+
) }
90+
type="url"
91+
__nextHasNoMarginBottom={ true }
92+
__next40pxDefaultSize={ true }
93+
/>
94+
</>
95+
) }
96+
</>
97+
);
98+
};
99+
100+
export default WebhooksSettings;

projects/packages/forms/src/blocks/contact-form/edit.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* External dependencies
33
*/
44
import { ThemeProvider } from '@automattic/jetpack-components';
5-
import { useModuleStatus } from '@automattic/jetpack-shared-extension-utils';
5+
import { useModuleStatus, hasFeatureFlag } from '@automattic/jetpack-shared-extension-utils';
66
import {
77
URLInput,
88
InspectorAdvancedControls,
@@ -50,6 +50,7 @@ import { childBlocks } from './child-blocks.js';
5050
import { ContactFormPlaceholder } from './components/jetpack-contact-form-placeholder.js';
5151
import ContactFormSkeletonLoader from './components/jetpack-contact-form-skeleton-loader.js';
5252
import NotificationsSettings from './components/notifications-settings.js';
53+
import WebhooksSettings from './components/webhooks-settings.js';
5354
import useFormBlockDefaults from './shared/hooks/use-form-block-defaults.js';
5455
import VariationPicker from './variation-picker.js';
5556
import './util/form-styles.js';
@@ -118,6 +119,14 @@ type CustomThankyouType =
118119
| 'message' // custom message
119120
| 'redirect'; // redirect to a new URL
120121

122+
type Webhook = {
123+
webhook_id: string;
124+
url: string;
125+
format: 'urlencoded' | 'json';
126+
method: 'POST' | 'GET' | 'PUT';
127+
enabled: boolean;
128+
};
129+
121130
type JetpackContactFormAttributes = {
122131
to: string;
123132
subject: string;
@@ -133,7 +142,9 @@ type JetpackContactFormAttributes = {
133142
disableGoBack: boolean;
134143
disableSummary: boolean;
135144
notificationRecipients: string[];
145+
webhooks: Webhook[];
136146
};
147+
137148
type JetpackContactFormEditProps = {
138149
name: string;
139150
attributes: JetpackContactFormAttributes;
@@ -166,8 +177,10 @@ function JetpackContactFormEdit( {
166177
disableGoBack,
167178
disableSummary,
168179
notificationRecipients,
180+
webhooks,
169181
} = attributes;
170182
const showFormIntegrations = useConfigValue( 'isIntegrationsEnabled' );
183+
const showWebhooks = useConfigValue( 'isWebhooksEnabled' ) && hasFeatureFlag( 'form-webhooks' );
171184
const instanceId = useInstanceId( JetpackContactFormEdit );
172185

173186
// Backward compatibility for the deprecated customThankyou attribute.
@@ -913,6 +926,15 @@ function JetpackContactFormEdit( {
913926
<IntegrationControls attributes={ attributes } setAttributes={ setAttributes } />
914927
</Suspense>
915928
) }
929+
{ showWebhooks && (
930+
<PanelBody
931+
title={ __( 'Webhooks', 'jetpack-forms' ) }
932+
className="jetpack-contact-form__panel"
933+
initialOpen={ false }
934+
>
935+
<WebhooksSettings webhooks={ webhooks } setAttributes={ setAttributes } />
936+
</PanelBody>
937+
) }
916938
<PanelBody
917939
title={ __( 'Responses storage', 'jetpack-forms' ) }
918940
className="jetpack-contact-form__panel jetpack-contact-form__responses-storage-panel"

projects/packages/forms/src/class-jetpack-forms.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,18 @@ public static function is_integrations_enabled() {
124124
*/
125125
return apply_filters( 'jetpack_forms_is_integrations_enabled', true );
126126
}
127+
128+
/**
129+
* Returns true if webhooks are enabled.
130+
*
131+
* @return boolean
132+
*/
133+
public static function is_webhooks_enabled() {
134+
/**
135+
* Whether to enable webhooks for Jetpack Forms.
136+
*
137+
* @param bool false Whether webhooks should be enabled. Default false.
138+
*/
139+
return apply_filters( 'jetpack_forms_webhooks_enabled', false );
140+
}
127141
}

projects/packages/forms/src/contact-form/class-contact-form-endpoint.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1494,6 +1494,7 @@ public function get_forms_config( WP_REST_Request $request ) { // phpcs:ignore V
14941494
'siteURL' => ( new Status() )->get_site_suffix(),
14951495
'hasFeedback' => ( new Forms_Dashboard() )->has_feedback(),
14961496
'isIntegrationsEnabled' => Jetpack_Forms::is_integrations_enabled(),
1497+
'isWebhooksEnabled' => Jetpack_Forms::is_webhooks_enabled(),
14971498
'dashboardURL' => Forms_Dashboard::get_forms_admin_url(),
14981499
// New data.
14991500
'canInstallPlugins' => current_user_can( 'install_plugins' ),

0 commit comments

Comments
 (0)