-
Notifications
You must be signed in to change notification settings - Fork 844
MYJP-268 Update My Jetpack red bubble notifications #46396
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Changes from all commits
8ba66ea
692b471
a5bc52f
bdb63ad
09b6907
1e690ee
d30696e
4a9f43b
71f1af4
7e0455b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| /** | ||
| * My Jetpack Notification Bubble async loader. | ||
| * Fetches fresh alert data via REST API without blocking page load. | ||
| */ | ||
| import apiFetch from '@wordpress/api-fetch'; | ||
|
|
||
| // Minimal type for counting non-silent alerts. | ||
| type Alert = { | ||
| is_silent?: boolean; | ||
| }; | ||
|
|
||
| type AlertsResponse = Record< string, Alert >; | ||
|
|
||
| apiFetch< AlertsResponse >( { | ||
| path: 'my-jetpack/v1/red-bubble-notifications', | ||
| method: 'POST', | ||
| } ) | ||
| .then( alerts => { | ||
| const count = Object.values( alerts ).filter( a => ! a.is_silent ).length; | ||
| const menuItem = document.querySelector( '#toplevel_page_jetpack .wp-menu-name' ); | ||
|
|
||
| if ( ! menuItem ) { | ||
grzegorz-cp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return; | ||
| } | ||
|
|
||
| const bubble = menuItem.querySelector( '.awaiting-mod' ); | ||
|
|
||
| if ( count > 0 ) { | ||
| if ( bubble ) { | ||
| bubble.className = 'awaiting-mod'; | ||
grzegorz-cp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| bubble.textContent = String( count ); | ||
| } else { | ||
| const span = document.createElement( 'span' ); | ||
| span.className = 'awaiting-mod'; | ||
| span.textContent = String( count ); | ||
| menuItem.appendChild( document.createTextNode( ' ' ) ); | ||
| menuItem.appendChild( span ); | ||
| } | ||
| } else if ( bubble ) { | ||
| bubble.remove(); | ||
| } | ||
| } ) | ||
| .catch( ( error: Error ) => { | ||
| // eslint-disable-next-line no-console | ||
| console.error( '[My Jetpack] Failed to fetch notification alerts:', error ); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if this is bringing any value.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should for sure not implement console.error; one thing I'm thinking about is callback to the backend What do you think about it? |
||
| } ); | ||
|
Comment on lines
+1
to
+46
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Significance: patch | ||
| Type: changed | ||
|
|
||
| My Jetpack: Check red bubble notification async when cache is not available. | ||
grzegorz-cp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -688,12 +688,15 @@ | |||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Conditionally append the red bubble notification to the "Jetpack" menu item if there are alerts to show | ||||||
| * Conditionally append the red bubble notification to the "Jetpack" menu item if there are alerts to show. | ||||||
| * | ||||||
| * On My Jetpack page: Uses blocking behavior to fetch fresh data. | ||||||
| * On other admin pages: Uses cached data only to avoid blocking, with async JS fetch if cache is empty. | ||||||
| * | ||||||
| * @return void | ||||||
| */ | ||||||
| public static function maybe_show_red_bubble() { | ||||||
| global $menu; | ||||||
| global $menu, $pagenow; | ||||||
|
|
||||||
| // Don't show red bubble alerts for non-admin users | ||||||
| // These alerts are generally only actionable for admins | ||||||
|
|
@@ -708,14 +711,32 @@ | |||||
| return; | ||||||
| } | ||||||
|
|
||||||
| $rbn = new Red_Bubble_Notifications(); | ||||||
| // Check if we're on the My Jetpack page | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let us avoid adding PHPCS errors. This file already has too many of them.
Suggested change
|
||||||
| // phpcs:ignore WordPress.Security.NonceVerification.Recommended | ||||||
| $page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; | ||||||
| $is_my_jetpack_page = $pagenow === 'admin.php' && $page === 'my-jetpack'; | ||||||
|
|
||||||
| if ( $is_my_jetpack_page ) { | ||||||
| // On My Jetpack page: use blocking behavior for fresh data. | ||||||
| add_filter( 'my_jetpack_red_bubble_notification_slugs', array( Red_Bubble_Notifications::class, 'add_red_bubble_alerts' ) ); | ||||||
| $red_bubble_alerts = Red_Bubble_Notifications::get_red_bubble_alerts(); | ||||||
| } else { | ||||||
| // On other pages: use cached data only to avoid blocking. | ||||||
| $cached_alerts = Red_Bubble_Notifications::get_cached_alerts(); | ||||||
|
|
||||||
| if ( false === $cached_alerts ) { | ||||||
| // No cache - fetch asynchronously via JS. | ||||||
| add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_red_bubble_script' ) ); | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| $red_bubble_alerts = $cached_alerts; | ||||||
| } | ||||||
|
|
||||||
| // filters for the items in this file | ||||||
| add_filter( 'my_jetpack_red_bubble_notification_slugs', array( $rbn, 'add_red_bubble_alerts' ) ); | ||||||
| // Filter out silent alerts | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| $red_bubble_alerts = array_filter( | ||||||
| $rbn::get_red_bubble_alerts(), | ||||||
| $red_bubble_alerts, | ||||||
| function ( $alert ) { | ||||||
| // We don't want to show the red bubble for silent alerts | ||||||
| return empty( $alert['is_silent'] ); | ||||||
| } | ||||||
| ); | ||||||
|
|
@@ -731,6 +752,24 @@ | |||||
| } | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Enqueue the notification bubble script. | ||||||
| * Fetches fresh alert data via REST API without blocking page load. | ||||||
| * | ||||||
| * @return void | ||||||
| */ | ||||||
| public static function enqueue_red_bubble_script() { | ||||||
| Assets::register_script( | ||||||
| 'my_jetpack_notification_bubble', | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handles are usually with a hyphen instead of underscore.
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't seen a string with mixed hyphens and underscores in the codebase, but there are places where everything has underscores, even if it contains "my_jetpack", so I think it's better to keep the proposed value.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can search for |
||||||
| '../build/async-notification-bubble.js', | ||||||
| __FILE__, | ||||||
| array( | ||||||
| 'enqueue' => true, | ||||||
| 'in_footer' => true, | ||||||
| ) | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Get list of module names sorted by their recommendation score | ||||||
| * | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -368,6 +368,17 @@ public static function add_red_bubble_alerts( array $red_bubble_slugs ) { | |||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||
| * Get cached red bubble alerts without triggering expensive computation. | ||||||||||||||||||||||||||||
| * Returns the cached transient value or an empty array if not cached. | ||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||
| * @return array Cached alerts or empty array. | ||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||
| public static function get_cached_alerts() { | ||||||||||||||||||||||||||||
| $stored_alerts = get_transient( self::MY_JETPACK_RED_BUBBLE_TRANSIENT_KEY ); | ||||||||||||||||||||||||||||
| return $stored_alerts !== false ? $stored_alerts : array(); | ||||||||||||||||||||||||||||
|
Comment on lines
+373
to
+379
|
||||||||||||||||||||||||||||
| * Returns the cached transient value or an empty array if not cached. | |
| * | |
| * @return array Cached alerts or empty array. | |
| */ | |
| public static function get_cached_alerts() { | |
| $stored_alerts = get_transient( self::MY_JETPACK_RED_BUBBLE_TRANSIENT_KEY ); | |
| return $stored_alerts !== false ? $stored_alerts : array(); | |
| * Returns the cached transient value or false if not cached. | |
| * | |
| * @return array|false Cached alerts array or false if no cache exists. | |
| */ | |
| public static function get_cached_alerts() { | |
| return get_transient( self::MY_JETPACK_RED_BUBBLE_TRANSIENT_KEY ); |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new get_cached_alerts() method lacks test coverage. Consider adding unit tests to verify it correctly returns cached values when available and an empty array when the cache doesn't exist.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Significance: patch | ||
| Type: changed | ||
|
|
||
| My Jetpack: Check red bubble notification async when cache is not available. | ||
grzegorz-cp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Significance: patch | ||
| Type: changed | ||
|
|
||
| My Jetpack: Check red bubble notification async when cache is not available. | ||
grzegorz-cp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Significance: patch | ||
| Type: enhancement | ||
grzegorz-cp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| My Jetpack: Check red bubble notification async when cache is not available. | ||
grzegorz-cp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Significance: patch | ||
| Type: changed | ||
|
|
||
| My Jetpack: Check red bubble notification async when cache is not available. | ||
grzegorz-cp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Significance: patch | ||
| Type: changed | ||
|
|
||
| My Jetpack: Check red bubble notification async when cache is not available. | ||
grzegorz-cp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Significance: patch | ||
| Type: changed | ||
|
|
||
| My Jetpack: Check red bubble notification async when cache is not available. | ||
grzegorz-cp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Significance: patch | ||
| Type: changed | ||
|
|
||
| My Jetpack: Check red bubble notification async when cache is not available. | ||
grzegorz-cp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Significance: patch | ||
| Type: changed | ||
|
|
||
| My Jetpack: Check red bubble notification async when cache is not available. | ||
grzegorz-cp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the API returns null, undefined, or a non-object value, calling Object.values() will throw a runtime error. While apiFetch with proper typing should prevent this, consider adding a defensive check to ensure alerts is a valid object before processing it.