From 62bf98c16a17c877324e218c877ad7c33265b3bd Mon Sep 17 00:00:00 2001 From: Rasmy Nguyen Date: Fri, 18 Oct 2024 16:33:07 -0400 Subject: [PATCH 01/17] feat(site-kit): add logging when site kit disconnects (#3472) This PR adds newspack manager logging whenever we detect a site kit disconnection via: - the delete_option_{option} hook for the has_connected_admin option. - the update_user_metadata hook for when the disconnected_reason user meta is updated. - a cron job that checks to see if there are any admin connection daily --- includes/class-logger.php | 37 +++++ includes/class-newspack.php | 1 + .../class-googlesitekit-logger.php | 141 ++++++++++++++++++ .../google-site-kit/class-googlesitekit.php | 11 +- 4 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 includes/plugins/google-site-kit/class-googlesitekit-logger.php diff --git a/includes/class-logger.php b/includes/class-logger.php index 4bf48a977f..6a31567744 100644 --- a/includes/class-logger.php +++ b/includes/class-logger.php @@ -76,4 +76,41 @@ private static function get_pid() { public static function error( $payload, $header = 'NEWSPACK' ) { return self::log( $payload, $header, 'error' ); } + + /** + * A logger for newspack manager logging. + * + * @param string $code The log code (i.e. newspack_google_login). + * @param string $message The message to log. + * @param array $data The data to log. + * Optional. Additional parameters. + * @type string $user_email The current users email address. + * @type file $file The name of the file to write the local log to. + * @param string $type The type of log. Defaults to 'error'. + * @param string $log_level The Log level. + */ + public static function newspack_log( $code, $message, $data, $type = 'error', $log_level = 2 ) { + $email = ''; + if ( isset( $data['user_email'] ) ) { + $email = $data['user_email']; + unset( $data['user_email'] ); + } + $file = $code; + if ( isset( $data['file'] ) ) { + $file = $data['file']; + unset( $data['file'] ); + } + do_action( + 'newspack_log', + $code, + $message, + [ + 'type' => $type, + 'data' => $data, + 'user_email' => $email, + 'file' => $file, + 'log_level' => $log_level, + ] + ); + } } diff --git a/includes/class-newspack.php b/includes/class-newspack.php index 0be50a7634..8f844d245c 100644 --- a/includes/class-newspack.php +++ b/includes/class-newspack.php @@ -155,6 +155,7 @@ private function includes() { include_once NEWSPACK_ABSPATH . 'includes/plugins/class-jetpack.php'; include_once NEWSPACK_ABSPATH . 'includes/plugins/class-gravityforms.php'; include_once NEWSPACK_ABSPATH . 'includes/plugins/google-site-kit/class-googlesitekit.php'; + include_once NEWSPACK_ABSPATH . 'includes/plugins/google-site-kit/class-googlesitekit-logger.php'; include_once NEWSPACK_ABSPATH . 'includes/plugins/class-mailchimp-for-woocommerce.php'; include_once NEWSPACK_ABSPATH . 'includes/plugins/class-onesignal.php'; include_once NEWSPACK_ABSPATH . 'includes/plugins/class-organic-profile-block.php'; diff --git a/includes/plugins/google-site-kit/class-googlesitekit-logger.php b/includes/plugins/google-site-kit/class-googlesitekit-logger.php new file mode 100644 index 0000000000..4c421f4868 --- /dev/null +++ b/includes/plugins/google-site-kit/class-googlesitekit-logger.php @@ -0,0 +1,141 @@ + $code, + 'user_email' => wp_get_current_user()->user_email, + ]; + if ( $backtrace ) { + $e = new \Exception(); + $data['backtrace'] = $e->getTraceAsString(); + } + Logger::newspack_log( $code, $message, $data, 'error', $log_level ); + } +} +GoogleSiteKit_Logger::init(); diff --git a/includes/plugins/google-site-kit/class-googlesitekit.php b/includes/plugins/google-site-kit/class-googlesitekit.php index 704740aec2..e6a25e50c2 100644 --- a/includes/plugins/google-site-kit/class-googlesitekit.php +++ b/includes/plugins/google-site-kit/class-googlesitekit.php @@ -70,6 +70,15 @@ public static function insert_ga4_analytics() { Date: Mon, 21 Oct 2024 09:03:33 +0000 Subject: [PATCH 02/17] chore(deps-dev): bump @wordpress/browserslist-config Bumps [@wordpress/browserslist-config](https://github.com/WordPress/gutenberg/tree/HEAD/packages/browserslist-config) from 6.9.0 to 6.10.0. - [Release notes](https://github.com/WordPress/gutenberg/releases) - [Changelog](https://github.com/WordPress/gutenberg/blob/trunk/packages/browserslist-config/CHANGELOG.md) - [Commits](https://github.com/WordPress/gutenberg/commits/@wordpress/browserslist-config@6.10.0/packages/browserslist-config) --- updated-dependencies: - dependency-name: "@wordpress/browserslist-config" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 54c2fabe33..ec960a49ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "@testing-library/react": "^12.1.4", "@types/qs": "^6.9.16", "@types/react": "^17.0.75", - "@wordpress/browserslist-config": "^6.9.0", + "@wordpress/browserslist-config": "^6.10.0", "eslint": "^8.57.0", "lint-staged": "^15.2.10", "newspack-scripts": "^5.5.2", @@ -6162,9 +6162,9 @@ } }, "node_modules/@wordpress/browserslist-config": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-6.9.0.tgz", - "integrity": "sha512-yv8KJrMZTvhY+PNWQ6CQVTUs/6sAVgim7AxGpgTkVzDYKvTeJKuZqeHzAWtHKTOG+ORIj/29XtpIOU85R9dkng==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-6.10.0.tgz", + "integrity": "sha512-X5BG4xWvr1Qq9S2x5ERCF7V4bpa24zbj8cWYbIJaGiCfi6vp6dFI1SbvuZPXfKyThyytTVYBvEIr6CSm6G8fuQ==", "dev": true, "engines": { "node": ">=18.12.0", diff --git a/package.json b/package.json index b0f26c177d..7253e8a093 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "@testing-library/react": "^12.1.4", "@types/qs": "^6.9.16", "@types/react": "^17.0.75", - "@wordpress/browserslist-config": "^6.9.0", + "@wordpress/browserslist-config": "^6.10.0", "eslint": "^8.57.0", "lint-staged": "^15.2.10", "newspack-scripts": "^5.5.2", From e3823e7b858c356b3e5e4a490e119b94e7f12285 Mon Sep 17 00:00:00 2001 From: Rasmy Nguyen Date: Mon, 21 Oct 2024 09:11:22 -0400 Subject: [PATCH 03/17] fix(site-kit): update logger cron to hourly interval (#3485) --- includes/plugins/google-site-kit/class-googlesitekit-logger.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/plugins/google-site-kit/class-googlesitekit-logger.php b/includes/plugins/google-site-kit/class-googlesitekit-logger.php index 4c421f4868..31412e0ef1 100644 --- a/includes/plugins/google-site-kit/class-googlesitekit-logger.php +++ b/includes/plugins/google-site-kit/class-googlesitekit-logger.php @@ -47,7 +47,7 @@ public static function cron_init() { if ( defined( 'NEWSPACK_CRON_DISABLE' ) && is_array( NEWSPACK_CRON_DISABLE ) && in_array( self::CRON_HOOK, NEWSPACK_CRON_DISABLE, true ) ) { self::cron_deactivate(); } elseif ( ! wp_next_scheduled( self::CRON_HOOK ) ) { - wp_schedule_event( time(), 'daily', self::CRON_HOOK ); + wp_schedule_event( time(), 'hourly', self::CRON_HOOK ); } } From 123408e1338491b5561b5e588d663d4184fa226b Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Wed, 23 Oct 2024 13:51:19 -0300 Subject: [PATCH 04/17] feat(connections): jetpack sso (#3486) --- includes/wizards/class-connections-wizard.php | 1 + src/wizards/connections/views/main/index.js | 2 + .../connections/views/main/jetpack-sso.js | 149 ++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 src/wizards/connections/views/main/jetpack-sso.js diff --git a/includes/wizards/class-connections-wizard.php b/includes/wizards/class-connections-wizard.php index ef5ed8f0ef..c5cb4293e9 100644 --- a/includes/wizards/class-connections-wizard.php +++ b/includes/wizards/class-connections-wizard.php @@ -66,6 +66,7 @@ public function enqueue_scripts_and_styles() { 'can_connect_fivetran' => OAuth::is_proxy_configured( 'fivetran' ), 'can_use_webhooks' => defined( 'NEWSPACK_EXPERIMENTAL_WEBHOOKS' ) && NEWSPACK_EXPERIMENTAL_WEBHOOKS, 'can_use_everlit' => Everlit_Configuration_Manager::is_enabled(), + 'can_use_jetpack_sso' => class_exists( 'Jetpack' ) && defined( 'NEWSPACK_MANAGER_FILE' ), ] ); \wp_enqueue_script( 'newspack-connections-wizard' ); diff --git a/src/wizards/connections/views/main/index.js b/src/wizards/connections/views/main/index.js index f17e15b1b6..74c589a6c8 100644 --- a/src/wizards/connections/views/main/index.js +++ b/src/wizards/connections/views/main/index.js @@ -14,6 +14,7 @@ import Plugins from './plugins'; import GoogleAuth from './google'; import Mailchimp from './mailchimp'; import FivetranConnection from './fivetran'; +import JetpackSSO from './jetpack-sso'; import Recaptcha from './recaptcha'; import Webhooks from './webhooks'; @@ -39,6 +40,7 @@ const Main = () => { /> ) } + { newspack_connections_data.can_use_webhooks && } diff --git a/src/wizards/connections/views/main/jetpack-sso.js b/src/wizards/connections/views/main/jetpack-sso.js new file mode 100644 index 0000000000..b6c5a3198d --- /dev/null +++ b/src/wizards/connections/views/main/jetpack-sso.js @@ -0,0 +1,149 @@ +/* globals newspack_connections_data */ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { BaseControl, CheckboxControl } from '@wordpress/components'; +import { useEffect, useState } from '@wordpress/element'; +import apiFetch from '@wordpress/api-fetch'; + +/** + * Internal dependencies + */ +import { + ActionCard, + Button, + Grid, + Notice, + SectionHeader, + SelectControl, +} from '../../../../components/src'; + +const JetpackSSO = () => { + const [ error, setError ] = useState( null ); + const [ isLoading, setIsLoading ] = useState( false ); + const [ settings, setSettings ] = useState( {} ); + const [ settingsToUpdate, setSettingsToUpdate ] = useState( {} ); + + useEffect( () => { + const fetchSettings = async () => { + setIsLoading( true ); + try { + const fetchedSettings = await apiFetch( { path: '/newspack-manager/v1/jetpack-sso' } ); + setSettings( fetchedSettings ); + setSettingsToUpdate( fetchedSettings ); + } catch ( e ) { + setError( e.message || __( 'Error fetching settings.', 'newspack-plugin' ) ); + } finally { + setIsLoading( false ); + } + }; + fetchSettings(); + }, [] ); + + const updateSettings = async data => { + setError( null ); + setIsLoading( true ); + try { + const newSettings = await apiFetch( { + path: '/newspack-manager/v1/jetpack-sso', + method: 'POST', + data, + } ); + setSettings( newSettings ); + setSettingsToUpdate( newSettings ); + } catch ( e ) { + setError( e?.message || __( 'Error updating settings.', 'newspack-plugin' ) ); + } finally { + setIsLoading( false ); + } + }; + if ( ! newspack_connections_data.can_use_jetpack_sso ) { + return null; + } + return ( + <> + + ( + <> + { __( + 'Improve security by requiring two-factor authentication via WordPress.com for users with higher capabilities.', + 'newspack-plugin' + ) } + + ) } + hasGreyHeader={ !! settings.force_2fa } + toggleChecked={ !! settings.force_2fa } + toggleOnChange={ () => updateSettings( { force_2fa: ! settings.force_2fa } ) } + actionContent={ + settings.force_2fa && ( + + ) + } + disabled={ isLoading } + > + { settings.force_2fa && ( + <> + { error && } + { settings.jetpack_sso_force_2fa && ( + <> + +

+ { __( + 'Customize which capabilties to enforce 2FA by untoggling the “Require accounts to use WordPress.com Two-Step Authentication” option in Jetpack settings.', + 'newspack-plugin' + ) } +

+ + ) } + + + + setSettingsToUpdate( { ...settingsToUpdate, force_2fa_cap: value } ) + } + options={ + Object.keys( settings.available_caps || {} ).map( cap => ( { + label: settings.available_caps[ cap ], + value: cap, + } ) ) + } + /> + + + + setSettingsToUpdate( { ...settingsToUpdate, obfuscate_account: value } ) } + label={ __( 'Obfuscate restricted accounts by throwing WP’s “user not found” errors on login form attempts.', 'newspack-plugin' ) } + /> + + + ) } +
+ + ); +}; + +export default JetpackSSO; From cd0b859fad407f5de6b840bbe2da8c596a7793a5 Mon Sep 17 00:00:00 2001 From: leogermani Date: Wed, 23 Oct 2024 16:20:48 -0300 Subject: [PATCH 05/17] feat: display list remote name on newsletter wizard (#3478) * feat: display list remote name on newsletter wizard * feat: restore line deleted by accident --- src/wizards/engagement/views/newsletters/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/wizards/engagement/views/newsletters/index.js b/src/wizards/engagement/views/newsletters/index.js index 97e1254f6f..32c614b2cc 100644 --- a/src/wizards/engagement/views/newsletters/index.js +++ b/src/wizards/engagement/views/newsletters/index.js @@ -352,8 +352,10 @@ export const SubscriptionLists = ( { lockedLists, onUpdate, initialProvider } ) isSmall simple hasWhiteHeader - title={ list.name } - description={ list?.type_label ? list.type_label : null } + title={ list.remote_name } + description={ + list?.type_label ? list.type_label : null + } disabled={ inFlight } toggleOnChange={ handleChange( index, 'active' ) } toggleChecked={ list.active } From 63e8ab22674e001b27e5158afc25c83793aed824 Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Fri, 25 Oct 2024 08:10:23 -0300 Subject: [PATCH 06/17] fix(webhooks): deprecate global endpoint (#3492) --- includes/data-events/class-api.php | 17 ++-- includes/data-events/class-webhooks.php | 44 ++++++--- src/components/src/modal/index.js | 28 +++--- .../connections/views/main/webhooks.js | 63 ++++++------ tests/unit-tests/webhooks.php | 97 +++++++++---------- 5 files changed, 131 insertions(+), 118 deletions(-) diff --git a/includes/data-events/class-api.php b/includes/data-events/class-api.php index 07052f6628..ba13147daa 100644 --- a/includes/data-events/class-api.php +++ b/includes/data-events/class-api.php @@ -102,7 +102,7 @@ public static function permission_callback() { if ( ! current_user_can( 'manage_options' ) ) { return new \WP_Error( 'newspack_rest_forbidden', - esc_html__( 'You cannot use this resource.', 'newspack' ), + esc_html__( 'You cannot use this resource.', 'newspack-plugin' ), [ 'status' => 403, ] @@ -129,9 +129,6 @@ private static function get_endpoint_args() { 'sanitize_callback' => 'sanitize_text_field', ], ], - 'global' => [ - 'type' => 'boolean', - ], 'disabled' => [ 'type' => 'boolean', ], @@ -168,7 +165,7 @@ public static function test_url( $request ) { [ 'success' => false, 'code' => false, - 'message' => esc_html__( 'Invalid URL.', 'newspack' ), + 'message' => esc_html__( 'Invalid URL.', 'newspack-plugin' ), ] ); } @@ -249,16 +246,16 @@ public static function upsert_endpoint( $request ) { if ( empty( $args['url'] ) || \esc_url_raw( $args['url'], [ 'https' ] ) !== $args['url'] ) { return new \WP_Error( 'newspack_webhooks_invalid_url', - esc_html__( 'Invalid URL.', 'newspack' ), + esc_html__( 'Invalid URL.', 'newspack-plugin' ), [ 'status' => 400, ] ); } - if ( ! $args['global'] && empty( $args['actions'] ) ) { + if ( empty( $args['actions'] ) ) { return new \WP_Error( 'newspack_webhooks_invalid_actions', - esc_html__( 'You must select actions to trigger this endpoint. Set it to global if this endpoint is meant for all actions.', 'newspack' ), + esc_html__( 'You must select actions to trigger this endpoint.', 'newspack-plugin' ), [ 'status' => 400, ] @@ -267,15 +264,13 @@ public static function upsert_endpoint( $request ) { if ( empty( $args['id'] ) ) { $endpoint = Webhooks::create_endpoint( $args['url'], - $args['actions'] ?? [], - $args['global'] + $args['actions'] ?? [] ); } else { $endpoint = Webhooks::update_endpoint( $args['id'], $args['url'], $args['actions'] ?? [], - $args['global'], $args['disabled'] ); } diff --git a/includes/data-events/class-webhooks.php b/includes/data-events/class-webhooks.php index b3a9fc21ec..dd241e4ce6 100644 --- a/includes/data-events/class-webhooks.php +++ b/includes/data-events/class-webhooks.php @@ -220,16 +220,38 @@ public static function send_late_requests() { } } + /** + * Get endpoint errors. + * + * @param string $url Endpoint URL. + * @param array $actions Array of action names. + * + * @return WP_Error + */ + private static function get_endpoint_errors( $url, $actions ) { + $errors = new WP_Error(); + if ( empty( $url ) ) { + $errors->add( 'newspack_webhooks_invalid_url', __( 'Invalid URL.', 'newspack' ) ); + } + if ( empty( $actions ) ) { + $errors->add( 'newspack_webhooks_empty_actions', __( 'The endpoint must have at least one action.', 'newspack' ) ); + } + return $errors; + } + /** * Create a webhook endpoint. * * @param string $url Endpoint URL. * @param array $actions Array of action names. - * @param bool $global Whether the endpoint should be triggered for all actions. * * @return array|WP_Error Endpoint or error. */ - public static function create_endpoint( $url, $actions = [], $global = false ) { + public static function create_endpoint( $url, $actions ) { + $errors = self::get_endpoint_errors( $url, $actions ); + if ( $errors->has_errors() ) { + return $errors; + } $endpoint = \wp_insert_term( $url, self::ENDPOINT_TAXONOMY, @@ -241,7 +263,6 @@ public static function create_endpoint( $url, $actions = [], $global = false ) { return $endpoint; } \update_term_meta( $endpoint['term_id'], 'actions', $actions ); - \update_term_meta( $endpoint['term_id'], 'global', $global ); return self::get_endpoint_by_term( $endpoint['term_id'] ); } @@ -251,12 +272,11 @@ public static function create_endpoint( $url, $actions = [], $global = false ) { * @param string $id An unique identifier for the endpoint. * @param string $url Endpoint URL. * @param array $actions Array of action names. - * @param bool $global Whether the endpoint should be triggered for all actions. * * @throws \InvalidArgumentException If the ID is invalid. * @return void */ - public static function register_system_endpoint( $id, $url, $actions = [], $global = false ) { + public static function register_system_endpoint( $id, $url, $actions = [] ) { if ( ! is_string( $id ) || ctype_digit( $id ) || ! preg_match( '/^[a-z0-9-_]+$/', $id ) || ! empty( self::$system_endpoints[ $id ] ) ) { throw new \InvalidArgumentException( 'Endpoint ID must be a unique string containing only lowercase letters, numbers, dashes and underscores, and it can not be only numerical.' ); @@ -266,7 +286,6 @@ public static function register_system_endpoint( $id, $url, $actions = [], $glob 'id' => $id, 'url' => $url, 'actions' => $actions, - 'global' => $global, 'label' => $id, 'disabled' => false, 'disabled_error' => null, @@ -282,12 +301,15 @@ public static function register_system_endpoint( $id, $url, $actions = [], $glob * @param int $id Endpoint ID. * @param string $url Endpoint URL. * @param array $actions Array of action names. - * @param bool $global Whether the endpoint should be triggered for all actions. * @param bool $disabled Whether the endpoint is disabled. * * @return array|WP_Error Endpoint or error. */ - public static function update_endpoint( $id, $url, $actions = [], $global = false, $disabled = false ) { + public static function update_endpoint( $id, $url, $actions = [], $disabled = false ) { + $errors = self::get_endpoint_errors( $url, $actions ); + if ( $errors->has_errors() ) { + return $errors; + } $endpoint = \get_term( $id, self::ENDPOINT_TAXONOMY, ARRAY_A ); if ( ! $endpoint ) { return new WP_Error( 'newspack_webhooks_endpoint_not_found', __( 'Webhook endpoint not found.', 'newspack' ) ); @@ -301,7 +323,6 @@ public static function update_endpoint( $id, $url, $actions = [], $global = fals ] ); \update_term_meta( $endpoint['term_id'], 'actions', $actions ); - \update_term_meta( $endpoint['term_id'], 'global', $global ); \update_term_meta( $endpoint['term_id'], 'disabled', $disabled ); return self::get_endpoint_by_term( $endpoint['term_id'] ); } @@ -387,7 +408,6 @@ public static function get_endpoint_by_term( $endpoint ) { 'id' => $endpoint->term_id, 'url' => $endpoint->name, 'actions' => (array) \get_term_meta( $endpoint->term_id, 'actions', true ), - 'global' => (bool) \get_term_meta( $endpoint->term_id, 'global', true ), 'label' => \get_term_meta( $endpoint->term_id, 'label', true ), 'bearer_token' => \get_term_meta( $endpoint->term_id, 'bearer_token', true ), 'disabled' => $disabled, @@ -514,7 +534,7 @@ private static function get_endpoints_for_action( $action_name ) { array_filter( $endpoints, function( $endpoint ) use ( $action_name ) { - return ! $endpoint['disabled'] && ( $endpoint['global'] || in_array( $action_name, $endpoint['actions'], true ) ); + return ! $endpoint['disabled'] && in_array( $action_name, $endpoint['actions'], true ); } ) ); @@ -832,7 +852,7 @@ private static function send_request( $request_id ) { } $code = \wp_remote_retrieve_response_code( $response ); $message = \wp_remote_retrieve_response_message( $response ); - + $response_body = wp_remote_retrieve_body( $response ); $response_body = json_decode( $response_body, true ); diff --git a/src/components/src/modal/index.js b/src/components/src/modal/index.js index 8fa4a4350b..514db445a1 100644 --- a/src/components/src/modal/index.js +++ b/src/components/src/modal/index.js @@ -5,7 +5,7 @@ /** * WordPress dependencies. */ -import { Component } from '@wordpress/element'; +import { forwardRef } from '@wordpress/element'; import { Modal as BaseComponent } from '@wordpress/components'; /** @@ -18,21 +18,15 @@ import './style.scss'; */ import classnames from 'classnames'; -class Modal extends Component { - /** - * Render. - */ - render() { - const { className, isWide, isNarrow, ...otherProps } = this.props; - const classes = classnames( - 'newspack-modal', - isWide && 'newspack-modal--wide', - isNarrow && 'newspack-modal--narrow', - className - ); +function Modal( { className, isWide, isNarrow, ...otherProps }, ref ) { + const classes = classnames( + 'newspack-modal', + isWide && 'newspack-modal--wide', + isNarrow && 'newspack-modal--narrow', + className + ); - return ; - } -} + return ; -export default Modal; +} +export default forwardRef( Modal ); diff --git a/src/wizards/connections/views/main/webhooks.js b/src/wizards/connections/views/main/webhooks.js index 85eb777874..c37fd6e7a7 100644 --- a/src/wizards/connections/views/main/webhooks.js +++ b/src/wizards/connections/views/main/webhooks.js @@ -8,7 +8,7 @@ import moment from 'moment'; */ import { sprintf, __ } from '@wordpress/i18n'; import { CheckboxControl, MenuItem } from '@wordpress/components'; -import { useEffect, useState } from '@wordpress/element'; +import { useEffect, useState, useRef } from '@wordpress/element'; import apiFetch from '@wordpress/api-fetch'; import { Icon, settings, check, close, reusableBlock, moreVertical } from '@wordpress/icons'; import { ESCAPE } from '@wordpress/keycodes'; @@ -152,6 +152,8 @@ const Webhooks = () => { const [ editing, setEditing ] = useState( false ); const [ editingError, setEditingError ] = useState( false ); + const modalRef = useRef( null ); + const fetchEndpoints = () => { setInFlight( true ); apiFetch( { path: '/newspack/v1/webhooks/endpoints' } ) @@ -200,9 +202,27 @@ const Webhooks = () => { setDeleting( false ); } ); }; + const validateEndpoint = endpoint => { + const errors = []; + if ( ! endpoint.url ) { + errors.push( __( 'URL is required.', 'newspack-plugin' ) ); + } + if ( ! endpoint.actions || ! endpoint.actions.length ) { + errors.push( __( 'At least one action is required.', 'newspack-plugin' ) ); + } + if ( errors.length ) { + setEditingError( { message: errors.join( ' ' ) } ); + } else { + setEditingError( false ); + } + return errors; + } const upsertEndpoint = endpoint => { + const errors = validateEndpoint( endpoint ); + if ( errors.length ) { + return; + } setInFlight( true ); - setEditingError( false ); apiFetch( { path: `/newspack/v1/webhooks/endpoints/${ endpoint.id || '' }`, method: 'POST', @@ -251,6 +271,12 @@ const Webhooks = () => { setTestError( false ); }, [ editing ] ); + useEffect( () => { + if ( editingError ) { + modalRef?.current?.querySelector('.components-modal__content')?.scrollTo( { top: 0, left: 0, behavior: 'smooth' } ); + } + }, [ editingError ] ); + return ( { false !== error && } @@ -266,7 +292,7 @@ const Webhooks = () => { />