Skip to content

Commit

Permalink
My Jetpack: Add Backup 'needs-attention' status when backups are fail…
Browse files Browse the repository at this point in the history
…ing. (#40454)

* Add red bubble alert if Backups failing.
  • Loading branch information
elliottprogrammer authored Dec 19, 2024
1 parent ae2a399 commit cff42cc
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 6 deletions.
2 changes: 1 addition & 1 deletion projects/packages/my-jetpack/.phan/baseline.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
'src/class-rest-zendesk-chat.php' => ['PhanParamTooMany'],
'src/class-wpcom-products.php' => ['PhanTypeMismatchReturnProbablyReal'],
'src/products/class-anti-spam.php' => ['PhanTypeMismatchArgumentNullable', 'PhanTypeMismatchPropertyDefault'],
'src/products/class-backup.php' => ['PhanTypeMismatchArgumentNullable', 'PhanTypeMismatchPropertyDefault'],
'src/products/class-backup.php' => ['PhanTypeMismatchArgumentNullable', 'PhanTypeMismatchPropertyDefault', 'PhanTypeSuspiciousNonTraversableForeach'],
'src/products/class-boost.php' => ['PhanTypeMismatchPropertyDefault'],
'src/products/class-creator.php' => ['PhanTypeMismatchPropertyDefault', 'PhanTypeMismatchReturnProbablyReal'],
'src/products/class-crm.php' => ['PhanTypeMismatchPropertyDefault'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ const ActionButton: FC< ActionButtonProps > = ( {
upgradeInInterstitial,
isOwned,
} ) => {
const troubleshootBackupsUrl =
'https://jetpack.com/support/backup/troubleshooting-jetpack-backup/';
const [ isDropdownOpen, setIsDropdownOpen ] = useState( false );
const [ currentAction, setCurrentAction ] = useState< ComponentProps< typeof Button > >( {} );
const { detail } = useProduct( slug );
Expand Down Expand Up @@ -208,6 +210,16 @@ const ActionButton: FC< ActionButtonProps > = ( {
PRODUCT_STATUSES.EXPIRED in primaryActionOverride &&
primaryActionOverride[ PRODUCT_STATUSES.EXPIRED ] ),
};
case PRODUCT_STATUSES.NEEDS_ATTENTION:
return {
...buttonState,
href: troubleshootBackupsUrl,
variant: 'primary',
label: __( 'Troubleshoot', 'jetpack-my-jetpack' ),
...( primaryActionOverride &&
PRODUCT_STATUSES.NEEDS_ATTENTION in primaryActionOverride &&
primaryActionOverride[ PRODUCT_STATUSES.NEEDS_ATTENTION ] ),
};
default:
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ const ProductCard: FC< ProductCardProps > = props => {
const { ownedProducts } = getMyJetpackWindowInitialState( 'lifecycleStats' );
const isOwned = ownedProducts?.includes( slug );

const isError = status === PRODUCT_STATUSES.EXPIRED;
const isError =
status === PRODUCT_STATUSES.EXPIRED || status === PRODUCT_STATUSES.NEEDS_ATTENTION;
const isWarning = status === PRODUCT_STATUSES.EXPIRING_SOON;
const isAbsent =
status === PRODUCT_STATUSES.ABSENT || status === PRODUCT_STATUSES.ABSENT_WITH_PLAN;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ const getStatusLabel: StatusStateFunction = ( status, isOwned ) => {
const inactiveText = __( 'Inactive', 'jetpack-my-jetpack' );
return isOwned ? needsPlanText : inactiveText;
}
case PRODUCT_STATUSES.NEEDS_ATTENTION:
return __( 'Needs attention', 'jetpack-my-jetpack' );
default:
return __( 'Inactive', 'jetpack-my-jetpack' );
}
Expand All @@ -62,6 +64,7 @@ const getStatusClassName: StatusStateFunction = ( status, isOwned ) => {
case PRODUCT_STATUSES.NEEDS_PLAN:
return isOwned ? styles.warning : styles.inactive;
case PRODUCT_STATUSES.EXPIRED:
case PRODUCT_STATUSES.NEEDS_ATTENTION:
return styles.error;
default:
return styles.inactive;
Expand Down
1 change: 1 addition & 0 deletions projects/packages/my-jetpack/_inc/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ export const PRODUCT_STATUSES = {
CAN_UPGRADE: 'can_upgrade',
EXPIRING_SOON: 'expiring',
EXPIRED: 'expired',
NEEDS_ATTENTION: 'needs_attention',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: changed

My Jetpack: Add 'Needs attention' status to Backup product card when Backups are failing.
2 changes: 2 additions & 0 deletions projects/packages/my-jetpack/src/class-products.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class Products {
const STATUS_NEEDS_PLAN = 'needs_plan';
const STATUS_NEEDS_ACTIVATION = 'needs_activation';
const STATUS_NEEDS_FIRST_SITE_CONNECTION = 'needs_first_site_connection';
const STATUS_NEEDS_ATTENTION = 'needs_attention';

/**
* List of statuses that display the module as disabled
Expand Down Expand Up @@ -107,6 +108,7 @@ class Products {
self::STATUS_NEEDS_PLAN,
self::STATUS_NEEDS_ACTIVATION,
self::STATUS_NEEDS_FIRST_SITE_CONNECTION,
self::STATUS_NEEDS_ATTENTION,
);

/**
Expand Down
97 changes: 93 additions & 4 deletions projects/packages/my-jetpack/src/products/class-backup.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php
/**
* Boost product
* Backup product
*
* @package my-jetpack
*/
Expand All @@ -11,7 +11,6 @@
use Automattic\Jetpack\My_Jetpack\Hybrid_Product;
use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
use Automattic\Jetpack\Redirect;
use Jetpack_Options;
use WP_Error;

/**
Expand Down Expand Up @@ -186,9 +185,15 @@ private static function get_state_from_wpcom() {
return $status;
}

$site_id = Jetpack_Options::get_option( 'id' );
$site_id = \Jetpack_Options::get_option( 'id' );

$response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/rewind', $site_id ) . '?force=wpcom', '2', array( 'timeout' => 2 ), null, 'wpcom' );
$response = Client::wpcom_json_api_request_as_blog(
sprintf( '/sites/%d/rewind', $site_id ) . '?force=wpcom',
'2',
array( 'timeout' => 2 ),
null,
'wpcom'
);

if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
$status = new WP_Error( 'rewind_state_fetch_failed' );
Expand All @@ -200,6 +205,90 @@ private static function get_state_from_wpcom() {
return $status;
}

/**
* Hits the wpcom api to retrieve the last 10 backup records.
*
* @return Object|WP_Error
*/
public static function get_latest_backups() {
static $backups = null;

if ( $backups !== null ) {
return $backups;
}

$site_id = \Jetpack_Options::get_option( 'id' );
$response = Client::wpcom_json_api_request_as_blog(
sprintf( '/sites/%d/rewind/backups', $site_id ) . '?force=wpcom',
'2',
array( 'timeout' => 2 ),
null,
'wpcom'
);

if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
$backups = new WP_Error( 'rewind_backups_fetch_failed' );
return $backups;
}

$body = wp_remote_retrieve_body( $response );
$backups = json_decode( $body );
return $backups;
}

/**
* Determines whether the module/plugin/product needs the users attention.
* Typically due to some sort of error where user troubleshooting is needed.
*
* @return boolean|array
*/
public static function does_module_need_attention() {
$backup_failed_status = false;
// First check the status of Rewind for failure.
$rewind_state = self::get_state_from_wpcom();
if ( ! is_wp_error( $rewind_state ) ) {
$backup_failure_reasons = array(
'unknown_error',
'no_site_found',
'missing_plan',
'no_connected_jetpack',
'no_connected_jetpack_with_credentials',
'multisite_not_supported',
'host_not_supported',
'vp_active_on_site',
);
if ( $rewind_state->state === 'unavailable' && ! empty( $rewind_state->reason ) && in_array( $rewind_state->reason, $backup_failure_reasons, true ) ) {
$backup_failed_status = array(
'status' => $rewind_state->reason,
'last_updated' => $rewind_state->last_updated,
);
}
}
// Next check for a failed last backup.
$latest_backups = self::get_latest_backups();
if ( ! is_wp_error( $latest_backups ) ) {
// Get the last/latest backup record.
$last_backup = null;
foreach ( $latest_backups as $backup ) {
if ( $backup->is_backup ) {
$last_backup = $backup;
break;
}
}

if ( $last_backup && isset( $last_backup->status ) ) {
if ( $last_backup->status === 'not-accessible' || $last_backup->status === 'error' || $last_backup->status === 'credential-error' ) {
$backup_failed_status = array(
'status' => $last_backup->status,
'last_updated' => $last_backup->last_updated,
);
}
}
}

return $backup_failed_status;
}

/**
* Return product bundles list
* that supports the product.
Expand Down
14 changes: 14 additions & 0 deletions projects/packages/my-jetpack/src/products/class-product.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Modules;
use Automattic\Jetpack\My_Jetpack\Products\Backup;
use Automattic\Jetpack\Plugins_Installer;
use Automattic\Jetpack\Status;
use Jetpack_Options;
Expand Down Expand Up @@ -716,6 +717,9 @@ public static function get_status() {
} elseif ( static::$requires_user_connection && ! ( new Connection_Manager() )->has_connected_owner() ) {
$status = Products::STATUS_USER_CONNECTION_ERROR;
} elseif ( static::has_paid_plan_for_product() ) {
if ( static::$slug === 'backup' && Backup::does_module_need_attention() ) {
$status = Products::STATUS_NEEDS_ATTENTION;
}
if ( static::is_paid_plan_expired() ) {
$status = Products::STATUS_EXPIRED;
} elseif ( static::is_paid_plan_expiring() ) {
Expand Down Expand Up @@ -987,4 +991,14 @@ public static function install_and_activate_standalone() {

return true;
}

/**
* Determines whether the module/plugin/product needs the users attention.
* Typically due to some sort of error where user troubleshooting is needed.
*
* @return boolean
*/
public static function does_module_need_attention() {
return false;
}
}

0 comments on commit cff42cc

Please sign in to comment.