Add messages, separate components, finish actions

This commit is contained in:
inpsyde-maticluznar 2024-12-19 13:10:19 +01:00
parent 40b1b0d280
commit ed77ad63ca
No known key found for this signature in database
GPG key ID: D005973F231309F6
13 changed files with 417 additions and 187 deletions

View file

@ -1,6 +1,6 @@
import { Button } from '@wordpress/components';
import SettingsBlock from './SettingsBlock';
import { Header, Title, Action, Description } from './SettingsBlockElements';
import { Action, Description, Header, Title } from './SettingsBlockElements';
const ButtonSettingsBlock = ( { title, description, ...props } ) => (
<SettingsBlock { ...props } className="ppcp-r-settings-block__button">
@ -9,6 +9,9 @@ const ButtonSettingsBlock = ( { title, description, ...props } ) => (
<Description>{ description }</Description>
</Header>
<Action>
{ props.actionProps?.message && (
<p>{ props.actionProps.message }</p>
) }
<Button
isBusy={ props.actionProps?.isBusy }
variant={ props.actionProps?.buttonType }

View file

@ -1,150 +0,0 @@
import { __ } from '@wordpress/i18n';
import {
Header,
Title,
Description,
AccordionSettingsBlock,
ToggleSettingsBlock,
ButtonSettingsBlock,
} from '../../../../ReusableComponents/SettingsBlocks';
import SettingsBlock from '../../../../ReusableComponents/SettingsBlocks/SettingsBlock';
import { CommonHooks } from '../../../../../data';
import { useState } from '@wordpress/element';
const Troubleshooting = ( { updateFormValue, settings } ) => {
const { webhooks, registerWebhooks, simulateWebhooks } =
CommonHooks.useWebhooks();
const [ resubscribing, setResubscribing ] = useState( false );
const resubscribeWebhooks = async () => {
setResubscribing( true );
await registerWebhooks();
setResubscribing( false );
};
return (
<AccordionSettingsBlock
className="ppcp-r-settings-block--troubleshooting"
title={ __( 'Troubleshooting', 'woocommerce-paypal-payments' ) }
description={ __(
'Access tools to help debug and resolve issues.',
'woocommerce-paypal-payments'
) }
actionProps={ {
callback: updateFormValue,
key: 'payNowExperience',
value: settings.payNowExperience,
} }
>
<ToggleSettingsBlock
title={ __( 'Logging', 'woocommerce-paypal-payments' ) }
description={ __(
'Log additional debugging information in the WooCommerce logs that can assist technical staff to determine issues.',
'woocommerce-paypal-payments'
) }
actionProps={ {
callback: updateFormValue,
key: 'logging',
value: settings.logging,
} }
/>
<SettingsBlock
components={ [
() => (
<>
<Header>
<Title>
{ __(
'Subscribed PayPal webhooks',
'woocommerce-paypal-payments'
) }
</Title>
<Description>
{ __(
'The following PayPal webhooks are subscribed. More information about the webhooks is available in the',
'woocommerce-paypal-payments'
) }{ ' ' }
<a href="https://woocommerce.com/document/woocommerce-paypal-payments/#webhook-status">
{ __(
'Webhook Status documentation',
'woocommerce-paypal-payments'
) }
</a>
.
</Description>
</Header>
<HooksTable data={ webhooks } />
</>
),
] }
/>
<ButtonSettingsBlock
title={ __(
'Resubscribe webhooks',
'woocommerce-paypal-payments'
) }
description={ __(
'Click to remove the current webhook subscription and subscribe again, for example, if the website domain or URL structure changed.',
'woocommerce-paypal-payments'
) }
actionProps={ {
buttonType: 'secondary',
isBusy: resubscribing,
callback: () => resubscribeWebhooks(),
value: __(
'Resubscribe webhooks',
'woocommerce-paypal-payments'
),
} }
/>
<ButtonSettingsBlock
title={ __(
'Simulate webhooks',
'woocommerce-paypal-payments'
) }
actionProps={ {
buttonType: 'secondary',
callback: () => simulateWebhooks(),
value: __(
'Simulate webhooks',
'woocommerce-paypal-payments'
),
} }
/>
</AccordionSettingsBlock>
);
};
const HooksTable = ( { data } ) => {
return (
<table className="ppcp-r-table">
<thead>
<tr>
<th className="ppcp-r-table__hooks-url">
{ __( 'URL', 'woocommerce-paypal-payments' ) }
</th>
<th className="ppcp-r-table__hooks-events">
{ __(
'Tracked events',
'woocommerce-paypal-payments'
) }
</th>
</tr>
</thead>
<tbody>
<tr>
<td className="ppcp-r-table__hooks-url">{ data?.[ 0 ] }</td>
<td
className="ppcp-r-table__hooks-events"
dangerouslySetInnerHTML={ { __html: data?.[ 1 ] } }
></td>
</tr>
</tbody>
</table>
);
};
export default Troubleshooting;

View file

@ -0,0 +1,75 @@
import { __ } from '@wordpress/i18n';
import {
AccordionSettingsBlock,
Description,
Header,
Title,
ToggleSettingsBlock,
} from '../../../../../ReusableComponents/SettingsBlocks';
import SettingsBlock from '../../../../../ReusableComponents/SettingsBlocks/SettingsBlock';
import { CommonHooks } from '../../../../../../data';
import TroubleshootingSimulationBlock from './TroubleshootingSimulationBlock';
import TroubleshootingResubscribeBlock from './TroubleshootingResubscribeBlock';
import TroubleshootingTableBlock from './TroubleshootingTableBlock';
const Troubleshooting = ( { updateFormValue, settings } ) => {
const { webhooks } = CommonHooks.useWebhooks();
return (
<AccordionSettingsBlock
className="ppcp-r-settings-block--troubleshooting"
title={ __( 'Troubleshooting', 'woocommerce-paypal-payments' ) }
description={ __(
'Access tools to help debug and resolve issues.',
'woocommerce-paypal-payments'
) }
actionProps={ {
callback: updateFormValue,
key: 'payNowExperience',
value: settings.payNowExperience,
} }
>
<ToggleSettingsBlock
title={ __( 'Logging', 'woocommerce-paypal-payments' ) }
description={ __(
'Log additional debugging information in the WooCommerce logs that can assist technical staff to determine issues.',
'woocommerce-paypal-payments'
) }
actionProps={ {
callback: updateFormValue,
key: 'logging',
value: settings.logging,
} }
/>
<SettingsBlock>
<Header>
<Title>
{ __(
'Subscribed PayPal webhooks',
'woocommerce-paypal-payments'
) }
</Title>
<Description>
{ __(
'The following PayPal webhooks are subscribed. More information about the webhooks is available in the',
'woocommerce-paypal-payments'
) }{ ' ' }
<a href="https://woocommerce.com/document/woocommerce-paypal-payments/#webhook-status">
{ __(
'Webhook Status documentation',
'woocommerce-paypal-payments'
) }
</a>
.
</Description>
</Header>
<TroubleshootingTableBlock webhooks={ webhooks } />
<TroubleshootingResubscribeBlock />
<TroubleshootingSimulationBlock />
</SettingsBlock>
</AccordionSettingsBlock>
);
};
export default Troubleshooting;

View file

@ -0,0 +1,67 @@
import { useState } from '@wordpress/element';
import { useDispatch } from '@wordpress/data';
import { STORE_NAME } from '../../../../../../data/common';
import { ButtonSettingsBlock } from '../../../../../ReusableComponents/SettingsBlocks';
import { __ } from '@wordpress/i18n';
const TroubleshootingResubscribeBlock = () => {
const [ resubscribingState, setResubscribingState ] = useState( {
resubscribing: false,
message: '',
} );
const { resubscribeWebhooks } = useDispatch( STORE_NAME );
const startResubscribingWebhooks = async () => {
setResubscribingState( ( prevState ) => ( {
...prevState,
resubscribing: true,
} ) );
try {
await resubscribeWebhooks();
} catch ( error ) {
setResubscribingState( ( prevState ) => ( {
...prevState,
resubscribing: false,
message: __(
'Operation failed. Check WooCommerce logs for more details.',
'woocommerce-paypal-payments'
),
} ) );
return;
}
setResubscribingState( ( prevState ) => ( {
...prevState,
resubscribing: false,
message: __(
'Webhooks were successfully re-subscribed.',
'woocommerce-paypal-payments'
),
} ) );
};
return (
<ButtonSettingsBlock
title={ __(
'Resubscribe webhooks',
'woocommerce-paypal-payments'
) }
description={ __(
'Click to remove the current webhook subscription and subscribe again, for example, if the website domain or URL structure changed.',
'woocommerce-paypal-payments'
) }
actionProps={ {
buttonType: 'secondary',
isBusy: resubscribingState.resubscribing,
message: resubscribingState.message,
callback: () => startResubscribingWebhooks(),
value: __(
'Resubscribe webhooks',
'woocommerce-paypal-payments'
),
} }
/>
);
};
export default TroubleshootingResubscribeBlock;

View file

@ -0,0 +1,130 @@
import { useCallback, useState } from '@wordpress/element';
import apiFetch from '@wordpress/api-fetch';
import { REST_WEBHOOKS_SIMULATE } from '../../../../../../data/common/constants';
import { __ } from '@wordpress/i18n';
import { ButtonSettingsBlock } from '../../../../../ReusableComponents/SettingsBlocks';
const TroubleshootingSimulationBlock = () => {
const [ simulationState, setSimulationState ] = useState( {
simulating: false,
message: '',
status: '',
} );
function sleep( ms ) {
return new Promise( ( resolve ) => setTimeout( resolve, ms ) );
}
const startWebhookSimulation = useCallback( async () => {
return apiFetch( {
method: 'POST',
path: REST_WEBHOOKS_SIMULATE,
} );
}, [] );
const checkWebhookSimulationState = useCallback( async () => {
return apiFetch( {
path: REST_WEBHOOKS_SIMULATE,
} );
}, [] );
const startSimulation = async () => {
const delay = 2000;
const retriesBeforeErrorMessage = 15;
const maxRetries = 30;
setSimulationState( ( prevState ) => ( {
...prevState,
simulating: true,
message: __(
'Waiting for the webhook to arrive…',
'woocommerce-paypal-payments'
),
} ) );
try {
await startWebhookSimulation();
} catch ( error ) {
setSimulationState( ( prevState ) => ( {
...prevState,
simulating: false,
message: __(
'Operation failed. Check WooCommerce logs for more details.',
'woocommerce-paypal-payments'
),
} ) );
return;
}
for ( let i = 0; i < maxRetries; i++ ) {
await sleep( delay );
const simulationStateResponse = await checkWebhookSimulationState();
try {
if ( ! simulationStateResponse.success ) {
console.error(
'Simulation state query failed: ' +
simulationStateResponse?.data
);
continue;
}
const state = simulationStateResponse?.data?.state;
if ( state === 'received' ) {
setSimulationState( ( prevState ) => ( {
...prevState,
...{
message:
'✔️ ' +
__(
'The webhook was received successfully.',
'woocommerce-paypal-payments'
),
simulating: false,
},
} ) );
return;
}
} catch ( exc ) {
console.error( exc );
}
if ( i === retriesBeforeErrorMessage ) {
setSimulationState( ( prevState ) => ( {
...prevState,
...{
message: __(
'Looks like the webhook cannot be received. Check that your website is accessible from the internet.',
'woocommerce-paypal-payments'
),
status: STATUS_ERROR,
},
} ) );
}
}
setSimulationState( { ...simulationState, ...{ simulating: false } } );
};
return (
<>
<ButtonSettingsBlock
title={ __(
'Simulate webhooks',
'woocommerce-paypal-payments'
) }
actionProps={ {
buttonType: 'secondary',
isBusy: simulationState.simulating,
message: simulationState.message,
callback: () => startSimulation(),
value: __(
'Simulate webhooks',
'woocommerce-paypal-payments'
),
} }
/>
</>
);
};
export default TroubleshootingSimulationBlock;

View file

@ -0,0 +1,34 @@
import { __ } from '@wordpress/i18n';
const TroubleshootingTableBlock = ( { webhooks } ) => {
return (
<table className="ppcp-r-table">
<thead>
<tr>
<th className="ppcp-r-table__hooks-url">
{ __( 'URL', 'woocommerce-paypal-payments' ) }
</th>
<th className="ppcp-r-table__hooks-events">
{ __(
'Tracked events',
'woocommerce-paypal-payments'
) }
</th>
</tr>
</thead>
<tbody>
<tr>
<td className="ppcp-r-table__hooks-url">
{ webhooks?.[ 0 ] }
</td>
<td
className="ppcp-r-table__hooks-events"
dangerouslySetInnerHTML={ { __html: webhooks?.[ 1 ] } }
></td>
</tr>
</tbody>
</table>
);
};
export default TroubleshootingTableBlock;

View file

@ -5,7 +5,7 @@ import {
ContentWrapper,
} from '../../../ReusableComponents/SettingsBlocks';
import Sandbox from './Blocks/Sandbox';
import Troubleshooting from './Blocks/Troubleshooting';
import Troubleshooting from './Blocks/Troubleshooting/Troubleshooting';
import PaypalSettings from './Blocks/PaypalSettings';
import OtherSettings from './Blocks/OtherSettings';

View file

@ -25,4 +25,7 @@ export default {
DO_REFRESH_MERCHANT: 'COMMON:DO_REFRESH_MERCHANT',
DO_REFRESH_FEATURES: 'DO_REFRESH_FEATURES',
DO_WEBHOOKS_DATA: 'COMMON:DO_WEBHOOKS_DATA',
DO_RESUBSCRIBE_WEBHOOKS: 'COMMON:DO_RESUBSCRIBE_WEBHOOKS',
DO_WEBHOOK_SIMULATION_START: 'COMMON:DO_WEBHOOK_SIMULATION_START',
DO_WEBHOOK_SIMULATION_STATE: 'COMMON:DO_WEBHOOK_SIMULATION_STATE',
};

View file

@ -7,7 +7,7 @@
* @file
*/
import { dispatch, select } from '@wordpress/data';
import { select } from '@wordpress/data';
import ACTION_TYPES from './action-types';
import { STORE_NAME } from './constants';
@ -134,11 +134,6 @@ export const setClientSecret = ( clientSecret ) => ( {
payload: { clientSecret },
} );
export const setWebhooks = ( webhooks ) => ( {
type: ACTION_TYPES.SET_PERSISTENT,
payload: { webhooks },
} );
/**
* Side effect. Saves the persistent details to the WP database.
*
@ -219,3 +214,30 @@ export const refreshFeatureStatuses = function* () {
return result;
};
/**
* Persistent. Changes the "webhooks" value.
*
* @param {string} webhooks
* @return {Action} The action.
*/
export const setWebhooks = ( webhooks ) => ( {
type: ACTION_TYPES.SET_PERSISTENT,
payload: { webhooks },
} );
/**
* Side effect
* Refreshes subscribed webhooks via a REST request
*
* @return {Action} The action.
*/
export const resubscribeWebhooks = function* () {
const result = yield { type: ACTION_TYPES.DO_RESUBSCRIBE_WEBHOOKS };
if ( result && result.success ) {
yield hydrate( result );
}
return result;
};

View file

@ -53,8 +53,26 @@ export const REST_MANUAL_CONNECTION_PATH = '/wc/v3/wc_paypal/connect_manual';
* @type {string}
*/
export const REST_CONNECTION_URL_PATH = '/wc/v3/wc_paypal/login_link';
/**
* REST path to fetch webhooks data or resubscribe webhooks,
*
* Used by: Controls
* See: WebhookSettingsEndpoint.php
*
* @type {string}
*/
export const REST_WEBHOOKS = '/wc/v3/wc_paypal/webhook_settings';
export const REST_WEBHOOKS_SIMULATE = '/wc/v3/wc_paypal/webhooks_simulate';
/**
* REST path to start webhook simulation and observe the state,
*
* Used by: Controls
* See: WebhookSettingsEndpoint.php
*
* @type {string}
*/
export const REST_WEBHOOKS_SIMULATE = '/wc/v3/wc_paypal/webhook_simulate';
/**
* REST path to refresh the feature status.

View file

@ -10,11 +10,12 @@
import apiFetch from '@wordpress/api-fetch';
import {
REST_PERSIST_PATH,
REST_MANUAL_CONNECTION_PATH,
REST_CONNECTION_URL_PATH,
REST_HYDRATE_MERCHANT_PATH,
REST_MANUAL_CONNECTION_PATH,
REST_PERSIST_PATH,
REST_REFRESH_FEATURES_PATH,
REST_WEBHOOKS,
} from './constants';
import ACTION_TYPES from './action-types';
@ -115,4 +116,11 @@ export const controls = {
};
}
},
async [ ACTION_TYPES.DO_RESUBSCRIBE_WEBHOOKS ]() {
return await apiFetch( {
method: 'POST',
path: REST_WEBHOOKS,
} );
},
};

View file

@ -9,11 +9,9 @@
import { useDispatch, useSelect } from '@wordpress/data';
import { useCallback } from '@wordpress/element';
import { REST_WEBHOOKS, REST_WEBHOOKS_SIMULATE } from './constants';
import { REST_WEBHOOKS, STORE_NAME } from './constants';
import apiFetch from '@wordpress/api-fetch';
import { STORE_NAME } from './constants';
const useTransient = ( key ) =>
useSelect(
( select ) => select( STORE_NAME ).transientData()?.[ key ],
@ -87,19 +85,6 @@ const useHooks = () => {
} );
setWebhooks( response?.data?.webhooks );
},
registerWebhooks: async () => {
const response = await apiFetch( {
method: 'POST',
path: REST_WEBHOOKS,
} );
setWebhooks( response?.data?.webhooks );
},
simulateWebhooks: async () => {
const response = await apiFetch( {
path: REST_WEBHOOKS_SIMULATE,
} );
console.log( response );
},
connectToSandbox,
connectToProduction,
connectViaIdAndSecret,
@ -150,8 +135,20 @@ export const useWooSettings = () => {
};
export const useWebhooks = () => {
const { webhooks, setWebhooks, registerWebhooks } = useHooks();
return { webhooks, setWebhooks, registerWebhooks };
const {
webhooks,
setWebhooks,
registerWebhooks,
startWebhookSimulation,
checkWebhookSimulationState,
} = useHooks();
return {
webhooks,
setWebhooks,
registerWebhooks,
startWebhookSimulation,
checkWebhookSimulationState,
};
};
export const useMerchantInfo = () => {
const { merchant } = useHooks();

View file

@ -16,7 +16,7 @@ use WP_REST_Server;
class WebhookSettingsEndpoint extends RestEndpoint {
protected $rest_base = 'webhook_settings';
protected string $rest_simulate_base = 'webhooks_simulate';
protected string $rest_simulate_base = 'webhook_simulate';
private array $webhooksData;
private WebhookRegistrar $webhookRegistrar;
@ -44,7 +44,7 @@ class WebhookSettingsEndpoint extends RestEndpoint {
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array($this, 'register_webhooks'),
'callback' => array($this, 'resubscribe_webhooks'),
'permission_callback' => array($this, 'check_permission')
)
)
@ -52,11 +52,16 @@ class WebhookSettingsEndpoint extends RestEndpoint {
register_rest_route(
$this->namespace,
'/' . $this->rest_simulate_base,
'/' . $this->rest_simulate_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'simulate_webhooks' ),
'callback' => array( $this, 'check_simulated_webhook_state' ),
'permission_callback' => array( $this, 'check_permission' ),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'simulate_webhooks_start' ),
'permission_callback' => array( $this, 'check_permission' ),
)
)
@ -67,15 +72,33 @@ class WebhookSettingsEndpoint extends RestEndpoint {
return $this->return_success( ["webhooks" => $this->webhooksData['data'][0]] );
}
public function register_webhooks(): WP_REST_Response{
public function resubscribe_webhooks(): WP_REST_Response{
if ( ! $this->webhookRegistrar->register() ) {
return $this->return_error('Webhook subscription failed.');
}
return $this->return_success(["webhooks" => $this->webhooksData['data'][0]]);
}
public function simulate_webhooks(): WP_REST_Response{
$this->return_success(['success' => true]);
public function simulate_webhooks_start(): WP_REST_Response{
try {
$this->webhookSimulation->start();
return $this->return_success([]);
} catch ( \Exception $error ) {
return $this->return_error($error->getMessage());
}
}
public function check_simulated_webhook_state(): WP_REST_Response
{
try {
$state = $this->webhookSimulation->get_state();
return $this->return_success(array(
'state' => $state
));
} catch ( \Exception $error ) {
return $this->return_error($error->getMessage());
}
}
}