From 6c2babb0c95e1fb54a54f47d4d492be8b6d7257c Mon Sep 17 00:00:00 2001 From: efuller Date: Tue, 25 Jun 2024 10:43:08 -0400 Subject: [PATCH 01/13] Change return signature of get_campaign_summary --- src/email-providers/interface-email-provider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/email-providers/interface-email-provider.php b/src/email-providers/interface-email-provider.php index 05c597ba..e4453d26 100644 --- a/src/email-providers/interface-email-provider.php +++ b/src/email-providers/interface-email-provider.php @@ -86,7 +86,7 @@ public function send_campaign( string $campaign_id ): array|false; * @param string $campaign_id The campaign id. * @return array{ * response: mixed, - * http_status_code: int, + * success: boolean, * }|false The response from the API. */ public function get_campaign_summary( string $campaign_id ): array|false; From 4a32fd81aed2971287b2b71983f10cc24342d403 Mon Sep 17 00:00:00 2001 From: efuller Date: Tue, 25 Jun 2024 10:43:30 -0400 Subject: [PATCH 02/13] Adjust campaign monitor to adhere to new contract --- src/email-providers/class-campaign-monitor.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/email-providers/class-campaign-monitor.php b/src/email-providers/class-campaign-monitor.php index 1c9c5014..c131b13f 100644 --- a/src/email-providers/class-campaign-monitor.php +++ b/src/email-providers/class-campaign-monitor.php @@ -164,7 +164,7 @@ public function create_campaign( int $newsletter_id, array $list_ids, string $ca * @param string $campaign_id The campaign id. * @return array{ * response: mixed, - * http_status_code: int, + * success: boolean, * }|false The response from the API. */ public function send_campaign( string $campaign_id ): array|false { @@ -184,8 +184,8 @@ public function send_campaign( string $campaign_id ): array|false { ); return [ - 'response' => $result->response, - 'http_status_code' => $result->http_status_code, + 'response' => $result->response, + 'success' => 200 === $result->http_status_code, ]; } From 6c75e12db0dbc8841c5d8bc9145ceb387c5b2d2c Mon Sep 17 00:00:00 2001 From: efuller Date: Tue, 25 Jun 2024 10:44:23 -0400 Subject: [PATCH 03/13] Gather stats from SendGrid and return them --- src/class-rest-api-endpoints.php | 4 +++- src/email-providers/class-sendgrid.php | 28 ++++++++++++++++---------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/class-rest-api-endpoints.php b/src/class-rest-api-endpoints.php index adb6eed9..5f89ce29 100644 --- a/src/class-rest-api-endpoints.php +++ b/src/class-rest-api-endpoints.php @@ -190,6 +190,7 @@ public function get_status( WP_REST_Request $request ): array { 'Status' => __( 'Not sent', 'wp-newsletter-builder' ), ]; } + global $newsletter_builder_email_provider; if ( empty( $newsletter_builder_email_provider ) || ! $newsletter_builder_email_provider instanceof Email_Providers\Email_Provider ) { return [ @@ -197,11 +198,12 @@ public function get_status( WP_REST_Request $request ): array { ]; } $status = $newsletter_builder_email_provider->get_campaign_summary( $campaign_id ); - if ( ! empty( $status ) && is_array( $status['response'] ) && 200 === $status['http_status_code'] ) { + if ( ! empty( $status ) && is_array( $status['response'] ) && $status['success'] ) { $status['response']['Status'] = __( 'Sent' ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase wp_cache_set( $cache_key, $status['response'], '', 5 * MINUTE_IN_SECONDS ); return $status['response']; } + $send_result = get_post_meta( $post_id, 'nb_newsletter_send_result', true ); if ( ! empty( $send_result ) && is_array( $send_result ) ) { if ( is_string( $send_result['response'] ) && ( 200 > $send_result['http_status_code'] || 300 <= $send_result['http_status_code'] ) ) { diff --git a/src/email-providers/class-sendgrid.php b/src/email-providers/class-sendgrid.php index 4e23c71a..633fc1d5 100644 --- a/src/email-providers/class-sendgrid.php +++ b/src/email-providers/class-sendgrid.php @@ -241,27 +241,33 @@ public function send_campaign( string $campaign_id ): array|false { * @param string $campaign_id The campaign id. * @return array{ * response: mixed, - * http_status_code: int, + * success: boolean, * }|false The response from the API. - * - * @todo: Get recipients, total opened, unique opened. */ public function get_campaign_summary( string $campaign_id ): array|false { $sg = $this->get_client(); if ( empty( $sg ) ) { return false; } - $response = $sg->client->marketing()->singlesends()->_( $campaign_id )->get(); - $body = (object) json_decode( $response->body() ); + $campaign_response = $sg->client->marketing()->singlesends()->_( 'asdf' )->get(); + $stats_response = $sg->client->marketing()->stats()->singlesends()->_( $campaign_id )->get(); + + if ( 200 !== $stats_response->statusCode() || 200 !== $campaign_response->statusCode() ) { + return false; + } + + $campaign_body = (object) json_decode( $campaign_response->body() ); + $stats_body = (object) json_decode( $stats_response->body() ); + return [ 'response' => [ - 'Status' => $body->status, - 'Name' => $body->name, - 'Recipients' => 'N/A', - 'TotalOpened' => 'N/A', - 'UniqueOpened' => 'N/A', + 'Status' => $campaign_body?->status ?? '', + 'Name' => $campaign_body?->name ?? '', + 'Recipients' => $stats_body->results[0]?->stats?->delivered ?? '', + 'TotalOpened' => $stats_body->results[0]?->stats?->opens ?? '', + 'UniqueOpened' => $stats_body->results[0]?->stats?->unique_opens ?? '', ], - 'http_status_code' => $response->statusCode(), + 'success' => true, ]; } From 26edcc46fd4b65cfdfce09903cbe044f34172659 Mon Sep 17 00:00:00 2001 From: efuller Date: Tue, 25 Jun 2024 11:15:15 -0400 Subject: [PATCH 04/13] Remove debugging code --- src/email-providers/class-sendgrid.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/email-providers/class-sendgrid.php b/src/email-providers/class-sendgrid.php index 633fc1d5..f1cd0f33 100644 --- a/src/email-providers/class-sendgrid.php +++ b/src/email-providers/class-sendgrid.php @@ -249,7 +249,7 @@ public function get_campaign_summary( string $campaign_id ): array|false { if ( empty( $sg ) ) { return false; } - $campaign_response = $sg->client->marketing()->singlesends()->_( 'asdf' )->get(); + $campaign_response = $sg->client->marketing()->singlesends()->_( $campaign_id )->get(); $stats_response = $sg->client->marketing()->stats()->singlesends()->_( $campaign_id )->get(); if ( 200 !== $stats_response->statusCode() || 200 !== $campaign_response->statusCode() ) { From 3215c8a8002280885b07541db07912a819fb6eac Mon Sep 17 00:00:00 2001 From: efuller Date: Tue, 25 Jun 2024 11:16:03 -0400 Subject: [PATCH 05/13] Make error handling a bit better --- .../newsletterStatusPanel.tsx | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/plugins/newsletter-status/newsletterStatusPanel.tsx b/plugins/newsletter-status/newsletterStatusPanel.tsx index da9df381..9d92fec9 100644 --- a/plugins/newsletter-status/newsletterStatusPanel.tsx +++ b/plugins/newsletter-status/newsletterStatusPanel.tsx @@ -47,11 +47,31 @@ export default function NewsletterStatusPanel() { const { Status: statusString = '', Name = '', - Recipients = null, - TotalOpened = null, - UniqueOpened = null, + Recipients = '', + TotalOpened = '', + UniqueOpened = '', } = status; + if (!statusString || !Name) { + return ( + +

+ {__('Newsletter status not available. Try clicking the Refresh button.', 'wp-newsletter-builder')} +

+ +
+ ); + } + return ( Date: Tue, 25 Jun 2024 11:34:48 -0400 Subject: [PATCH 06/13] Extract getting newsletter stats out into a custom hook --- hooks/useNewsletterStatus/index.ts | 47 ++++++++++++++++++ .../newsletterStatusPanel.tsx | 48 ++++--------------- 2 files changed, 57 insertions(+), 38 deletions(-) create mode 100644 hooks/useNewsletterStatus/index.ts diff --git a/hooks/useNewsletterStatus/index.ts b/hooks/useNewsletterStatus/index.ts new file mode 100644 index 00000000..42897c0f --- /dev/null +++ b/hooks/useNewsletterStatus/index.ts @@ -0,0 +1,47 @@ +import { useCallback, useEffect, useState } from '@wordpress/element'; +import apiFetch from '@wordpress/api-fetch'; + +interface Status { + Bounced?: number; + Clicks?: number; + Forwards?: number; + Likes?: number; + Mentions?: number; + Name?: string; + Recipients?: number; + SpamComplaints?: number; + Status?: string; + TotalOpened?: number; + UniqueOpened?: number; + Unsubscribed?: number; + WebVersionTextURL?: string; + WebVersionURL?: string; + WorldviewURL?: string; +} + +function useNewsletterStatus(newsletterId: string) { + const [status, setStatus] = useState({}); + const [fetching, setFetching] = useState(false); + + const fetchStatus = useCallback(async () => { + setFetching(true); + const res = await apiFetch({ + path: `/wp-newsletter-builder/v1/status/${newsletterId}`, + }); + setStatus(res as Status); + setFetching(false); + }, [newsletterId]); + + useEffect(() => { + fetchStatus(); + }, [fetchStatus]); + + return { + validStatus: status.Status && status.Name, + status, + fetching, + fetchStatus, + }; +} + +export default useNewsletterStatus; diff --git a/plugins/newsletter-status/newsletterStatusPanel.tsx b/plugins/newsletter-status/newsletterStatusPanel.tsx index 9d92fec9..172c164f 100644 --- a/plugins/newsletter-status/newsletterStatusPanel.tsx +++ b/plugins/newsletter-status/newsletterStatusPanel.tsx @@ -1,48 +1,20 @@ -import React, { useCallback, useEffect, useState } from '@wordpress/element'; +import React from '@wordpress/element'; import { PluginDocumentSettingPanel } from '@wordpress/edit-post'; import { __ } from '@wordpress/i18n'; import { select } from '@wordpress/data'; -import apiFetch from '@wordpress/api-fetch'; import { Button } from '@wordpress/components'; import NewsletterSpinner from '@/components/newsletterSpinner'; - -interface Status { - Bounced?: number; - Clicks?: number; - Forwards?: number; - Likes?: number; - Mentions?: number; - Name?: string; - Recipients?: number; - SpamComplaints?: number; - Status?: string; - TotalOpened?: number; - UniqueOpened?: number; - Unsubscribed?: number; - WebVersionTextURL?: string; - WebVersionURL?: string; - WorldviewURL?: string; -} +import useNewsletterStatus from '@/hooks/useNewsletterStatus'; export default function NewsletterStatusPanel() { // @ts-ignore const postId = select('core/editor').getCurrentPostId(); - - const [status, setStatus] = useState({}); - const [fetching, setFetching] = useState(false); - - const fetchStatus = useCallback(async () => { - setFetching(true); - const res = await apiFetch({ - path: `/wp-newsletter-builder/v1/status/${postId}`, - }); - setStatus(res as Status); - setFetching(false); - }, [postId]); - - useEffect(() => { - fetchStatus(); - }, [fetchStatus]); + const { + status, + fetching, + fetchStatus, + validStatus, + } = useNewsletterStatus(postId); const { Status: statusString = '', @@ -52,7 +24,7 @@ export default function NewsletterStatusPanel() { UniqueOpened = '', } = status; - if (!statusString || !Name) { + if (!validStatus && !fetching) { return ( - {status ? ( + {validStatus ? ( <>
From 21b686c451d8f7c3ddf5339baaa5921851b6a988 Mon Sep 17 00:00:00 2001 From: efuller Date: Tue, 25 Jun 2024 12:00:41 -0400 Subject: [PATCH 07/13] Remove deprecated config option from phpstan.neon --- phpstan.neon | 3 --- 1 file changed, 3 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 1fd116f7..f9e2fb17 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -5,9 +5,6 @@ parameters: # Level 9 is the highest level level: max - # Disable generics in type hints. - checkGenericClassInNonGenericObjectType: false - paths: - blocks/ - entries/ From de7be8cbeee29088486e8c634b8a6f5fd6d51d81 Mon Sep 17 00:00:00 2001 From: efuller Date: Tue, 25 Jun 2024 12:00:54 -0400 Subject: [PATCH 08/13] Chores: phpstan fixes --- blocks/button/index.php | 2 +- blocks/divider/index.php | 2 +- blocks/heading/index.php | 2 +- blocks/list/index.php | 2 +- blocks/paragraph/index.php | 2 +- src/email-providers/class-sendgrid.php | 1 - 6 files changed, 5 insertions(+), 6 deletions(-) diff --git a/blocks/button/index.php b/blocks/button/index.php index 78ee2fc9..c04ee48e 100644 --- a/blocks/button/index.php +++ b/blocks/button/index.php @@ -12,7 +12,7 @@ * * @see https://developer.wordpress.org/reference/functions/register_block_type/ */ -function wp_newsletter_builder_button_block_init() { +function wp_newsletter_builder_button_block_init(): void { // Register the block by passing the location of block.json. register_block_type( __DIR__ diff --git a/blocks/divider/index.php b/blocks/divider/index.php index 1a37156d..41d6b842 100644 --- a/blocks/divider/index.php +++ b/blocks/divider/index.php @@ -12,7 +12,7 @@ * * @see https://developer.wordpress.org/reference/functions/register_block_type/ */ -function wp_newsletter_builder_divider_block_init() { +function wp_newsletter_builder_divider_block_init(): void { // Register the block by passing the location of block.json. register_block_type( __DIR__ diff --git a/blocks/heading/index.php b/blocks/heading/index.php index 62c586cd..f498b26e 100644 --- a/blocks/heading/index.php +++ b/blocks/heading/index.php @@ -12,7 +12,7 @@ * * @see https://developer.wordpress.org/reference/functions/register_block_type/ */ -function wp_newsletter_builder_heading_block_init() { +function wp_newsletter_builder_heading_block_init(): void { // Register the block by passing the location of block.json. register_block_type( __DIR__ diff --git a/blocks/list/index.php b/blocks/list/index.php index 6f484f99..ce89ee91 100644 --- a/blocks/list/index.php +++ b/blocks/list/index.php @@ -12,7 +12,7 @@ * * @see https://developer.wordpress.org/reference/functions/register_block_type/ */ -function wp_newsletter_builder_list_block_init() { +function wp_newsletter_builder_list_block_init(): void { // Register the block by passing the location of block.json. register_block_type( __DIR__ diff --git a/blocks/paragraph/index.php b/blocks/paragraph/index.php index d312b20a..86b12c69 100644 --- a/blocks/paragraph/index.php +++ b/blocks/paragraph/index.php @@ -12,7 +12,7 @@ * * @see https://developer.wordpress.org/reference/functions/register_block_type/ */ -function wp_newsletter_builder_paragraph_block_init() { +function wp_newsletter_builder_paragraph_block_init(): void { // Register the block by passing the location of block.json. register_block_type( __DIR__ diff --git a/src/email-providers/class-sendgrid.php b/src/email-providers/class-sendgrid.php index f1cd0f33..d25a11d8 100644 --- a/src/email-providers/class-sendgrid.php +++ b/src/email-providers/class-sendgrid.php @@ -422,7 +422,6 @@ private function get_content( $post_id ) { // Capture template output for the new query. ob_start(); - // @phpstan-ignore-next-line load_template( WP_PLUGIN_DIR . '/wp-newsletter-builder/single-nb_newsletter.php' ); $content = ob_get_clean(); From 30f0c879193e576b0c391ad51d2d9a70a39855f0 Mon Sep 17 00:00:00 2001 From: efuller Date: Tue, 25 Jun 2024 12:22:28 -0400 Subject: [PATCH 09/13] Chores: phpcs fixes --- src/email-providers/class-sendgrid.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/email-providers/class-sendgrid.php b/src/email-providers/class-sendgrid.php index d25a11d8..2f184711 100644 --- a/src/email-providers/class-sendgrid.php +++ b/src/email-providers/class-sendgrid.php @@ -257,17 +257,17 @@ public function get_campaign_summary( string $campaign_id ): array|false { } $campaign_body = (object) json_decode( $campaign_response->body() ); - $stats_body = (object) json_decode( $stats_response->body() ); + $stats_body = (object) json_decode( $stats_response->body() ); return [ - 'response' => [ + 'response' => [ 'Status' => $campaign_body?->status ?? '', 'Name' => $campaign_body?->name ?? '', 'Recipients' => $stats_body->results[0]?->stats?->delivered ?? '', 'TotalOpened' => $stats_body->results[0]?->stats?->opens ?? '', 'UniqueOpened' => $stats_body->results[0]?->stats?->unique_opens ?? '', ], - 'success' => true, + 'success' => true, ]; } From e8aa58bf3c20474d5dfa57a1a526b15f4de8d441 Mon Sep 17 00:00:00 2001 From: efuller Date: Tue, 25 Jun 2024 12:30:49 -0400 Subject: [PATCH 10/13] Rename useNewsLetterStatus to useNewsletterStats --- hooks/{useNewsletterStatus => useNewsletterStats}/index.ts | 4 ++-- plugins/newsletter-status/newsletterStatusPanel.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename hooks/{useNewsletterStatus => useNewsletterStats}/index.ts (91%) diff --git a/hooks/useNewsletterStatus/index.ts b/hooks/useNewsletterStats/index.ts similarity index 91% rename from hooks/useNewsletterStatus/index.ts rename to hooks/useNewsletterStats/index.ts index 42897c0f..2902f948 100644 --- a/hooks/useNewsletterStatus/index.ts +++ b/hooks/useNewsletterStats/index.ts @@ -19,7 +19,7 @@ interface Status { WorldviewURL?: string; } -function useNewsletterStatus(newsletterId: string) { +function useNewsletterStats(newsletterId: string) { const [status, setStatus] = useState({}); const [fetching, setFetching] = useState(false); @@ -44,4 +44,4 @@ function useNewsletterStatus(newsletterId: string) { }; } -export default useNewsletterStatus; +export default useNewsletterStats; diff --git a/plugins/newsletter-status/newsletterStatusPanel.tsx b/plugins/newsletter-status/newsletterStatusPanel.tsx index 172c164f..9c19bb7c 100644 --- a/plugins/newsletter-status/newsletterStatusPanel.tsx +++ b/plugins/newsletter-status/newsletterStatusPanel.tsx @@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n'; import { select } from '@wordpress/data'; import { Button } from '@wordpress/components'; import NewsletterSpinner from '@/components/newsletterSpinner'; -import useNewsletterStatus from '@/hooks/useNewsletterStatus'; +import useNewsletterStatus from '@/hooks/useNewsletterStats'; export default function NewsletterStatusPanel() { // @ts-ignore From a21d60b27172c254fbb7afd4b381f62772b8c0ac Mon Sep 17 00:00:00 2001 From: efuller Date: Tue, 25 Jun 2024 12:36:34 -0400 Subject: [PATCH 11/13] More renaming of status to stats --- hooks/useNewsletterStats/index.ts | 18 +++++++++--------- .../newsletterStatusPanel.tsx | 16 ++++++++-------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/hooks/useNewsletterStats/index.ts b/hooks/useNewsletterStats/index.ts index 2902f948..c7e16c40 100644 --- a/hooks/useNewsletterStats/index.ts +++ b/hooks/useNewsletterStats/index.ts @@ -1,7 +1,7 @@ import { useCallback, useEffect, useState } from '@wordpress/element'; import apiFetch from '@wordpress/api-fetch'; -interface Status { +interface Stats { Bounced?: number; Clicks?: number; Forwards?: number; @@ -20,27 +20,27 @@ interface Status { } function useNewsletterStats(newsletterId: string) { - const [status, setStatus] = useState({}); + const [stats, setStats] = useState({}); const [fetching, setFetching] = useState(false); - const fetchStatus = useCallback(async () => { + const fetchStats = useCallback(async () => { setFetching(true); const res = await apiFetch({ path: `/wp-newsletter-builder/v1/status/${newsletterId}`, }); - setStatus(res as Status); + setStats(res as Stats); setFetching(false); }, [newsletterId]); useEffect(() => { - fetchStatus(); - }, [fetchStatus]); + fetchStats(); + }, [fetchStats]); return { - validStatus: status.Status && status.Name, - status, + validStats: stats.Status && stats.Name, + stats, fetching, - fetchStatus, + fetchStats, }; } diff --git a/plugins/newsletter-status/newsletterStatusPanel.tsx b/plugins/newsletter-status/newsletterStatusPanel.tsx index 9c19bb7c..331f39df 100644 --- a/plugins/newsletter-status/newsletterStatusPanel.tsx +++ b/plugins/newsletter-status/newsletterStatusPanel.tsx @@ -10,10 +10,10 @@ export default function NewsletterStatusPanel() { // @ts-ignore const postId = select('core/editor').getCurrentPostId(); const { - status, + stats, fetching, - fetchStatus, - validStatus, + fetchStats, + validStats, } = useNewsletterStatus(postId); const { @@ -22,9 +22,9 @@ export default function NewsletterStatusPanel() { Recipients = '', TotalOpened = '', UniqueOpened = '', - } = status; + } = stats; - if (!validStatus && !fetching) { + if (!validStats && !fetching) { return (