From bd5e753e9816437f569a9b88b045d9a3d1e56b02 Mon Sep 17 00:00:00 2001 From: Francesco Bigiarini Date: Tue, 12 Nov 2024 16:46:22 +0100 Subject: [PATCH 01/51] Add Google Photos Picker new WPCOM endpoints --- ...om-rest-api-v2-endpoint-external-media.php | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-external-media.php b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-external-media.php index 5c6a39d4d9994..31d2a93a785b9 100644 --- a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-external-media.php +++ b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-external-media.php @@ -169,6 +169,46 @@ public function register_routes() { 'permission_callback' => array( $this, 'permission_callback' ), ) ); + + register_rest_route( + $this->namespace, + $this->rest_base . '/connection/(?Pgoogle_photos)/picker_status', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_picker_status' ), + 'permission_callback' => array( $this, 'permission_callback' ), + ) + ); + + register_rest_route( + $this->namespace, + $this->rest_base . '/session/(?Pgoogle_photos)', + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'create_session' ), + 'permission_callback' => array( $this, 'permission_callback' ), + ) + ); + + register_rest_route( + $this->namespace, + $this->rest_base . '/session/(?Pgoogle_photos)/(?P.*)', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_session' ), + 'permission_callback' => array( $this, 'permission_callback' ), + ) + ); + + register_rest_route( + $this->namespace, + $this->rest_base . '/session/(?Pgoogle_photos)/(?P.*)', + array( + 'methods' => WP_REST_Server::DELETABLE, + 'callback' => array( $this, 'delete_session' ), + 'permission_callback' => array( $this, 'permission_callback' ), + ) + ); } /** @@ -430,6 +470,108 @@ public function delete_connection( WP_REST_Request $request ) { return json_decode( wp_remote_retrieve_body( $response ), true ); } + /** + * Gets the status of the Google Photos Picker. + * + * @param \WP_REST_Request $request Full details about the request. + * @return array|\WP_Error|mixed + */ + public function get_picker_status( \WP_REST_Request $request ) { + $service = rawurlencode( $request->get_param( 'service' ) ); + $wpcom_path = sprintf( '/meta/external-media/connection/%s/picker_status', $service ); + + if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + $internal_request = new WP_REST_Request( 'GET', '/' . $this->namespace . $wpcom_path ); + $internal_request->set_query_params( $request->get_params() ); + + return rest_do_request( $internal_request ); + } + + $response = Client::wpcom_json_api_request_as_user( $wpcom_path ); + + return json_decode( wp_remote_retrieve_body( $response ), true ); + } + + /** + * Creates a new session for services that require it (e.g., Google Photos Picker) + * + * @param WP_REST_Request $request Full details about the request. + * @return array|WP_Error + */ + public function create_session( $request ) { + $service = rawurlencode( $request->get_param( 'service' ) ); + $wpcom_path = sprintf( '/meta/external-media/session/%s', $service ); + + if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + $internal_request = new WP_REST_Request( 'POST', '/' . $this->namespace . $wpcom_path ); + $internal_request->set_query_params( $request->get_params() ); + + return rest_do_request( $internal_request ); + } + + $response = Client::wpcom_json_api_request_as_user( + $wpcom_path, + '2', + array( + 'method' => 'POST', + ) + ); + + return json_decode( wp_remote_retrieve_body( $response ), true ); + } + + /** + * Gets the status of an existing session + * + * @param WP_REST_Request $request Full details about the request. + * @return array|WP_Error + */ + public function get_session( $request ) { + $service = rawurlencode( $request->get_param( 'service' ) ); + $session_id = rawurlencode( $request->get_param( 'session_id' ) ); + $wpcom_path = sprintf( '/meta/external-media/session/%s/%d', $service, $session_id ); + + if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + $internal_request = new WP_REST_Request( 'GET', '/' . $this->namespace . $wpcom_path ); + $internal_request->set_query_params( $request->get_params() ); + + return rest_do_request( $internal_request ); + } + + $response = Client::wpcom_json_api_request_as_user( $wpcom_path ); + + return json_decode( wp_remote_retrieve_body( $response ), true ); + } + + /** + * Deletes an existing session + * + * @param WP_REST_Request $request Full details about the request. + * @return array|WP_Error + */ + public function delete_session( $request ) { + $service = rawurlencode( $request->get_param( 'service' ) ); + $session_id = rawurlencode( $request->get_param( 'session_id' ) ); + $wpcom_path = sprintf( '/meta/external-media/session/%s/%d', $service, $session_id ); + + if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + $internal_request = new WP_REST_Request( 'DELETE', '/' . $this->namespace . $wpcom_path ); + $internal_request->set_query_params( $request->get_params() ); + + return rest_do_request( $internal_request ); + } + + $response = Client::wpcom_json_api_request_as_user( + $wpcom_path, + '2', + array( + 'method' => 'DELETE', + ) + ); + + return json_decode( wp_remote_retrieve_body( $response ), true ); + } + /** * Filter callback to provide a shorter file name for google images. * From c9b92d779077dfe1c537cb56a53bf34e64622dde Mon Sep 17 00:00:00 2001 From: Francesco Bigiarini Date: Tue, 12 Nov 2024 16:49:19 +0100 Subject: [PATCH 02/51] changelog --- .../plugins/jetpack/changelog/add-new-external-media-endpoint | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/plugins/jetpack/changelog/add-new-external-media-endpoint diff --git a/projects/plugins/jetpack/changelog/add-new-external-media-endpoint b/projects/plugins/jetpack/changelog/add-new-external-media-endpoint new file mode 100644 index 0000000000000..f473e1afabe56 --- /dev/null +++ b/projects/plugins/jetpack/changelog/add-new-external-media-endpoint @@ -0,0 +1,4 @@ +Significance: minor +Type: enhancement + +Add Google Photos Picker new WPCOM endpoints From a512d13ddda1a5a2bbd91bda0249aa1b4beceb7a Mon Sep 17 00:00:00 2001 From: Rafael Agostini Date: Tue, 12 Nov 2024 11:39:50 -0500 Subject: [PATCH 03/51] Backup: Add next daily backup schedule time (#39914) * Set up react-query, moment and typescript * changelog * Add endpoint to fetch site backup schedule time * Setup react-query (queryClientProvider) * Add useScheduledTimeQuery hook to fetch schedule time * Add hook to parse and return the next backup schedule time * Add component to render the next scheduled backup message * Fix record Tracks event * Removing changelog for migration and core plugin * Bump `Automattic\Jetpack\Backup` namespace to V0005 * changelog * Bump `Automattic\Jetpack\Transport_Helper` to V0005 * Remove `between` to make `Modify` fit in the same line * Fix pnpm lock --- pnpm-lock.yaml | 118 +++++++++++++++++- .../add-jetpack-backup-schedule-time | 4 + .../src/class-helper-script-manager-impl.php | 2 +- .../src/class-helper-script-manager.php | 2 +- .../src/class-throw-on-errors.php | 2 +- .../test-class-helper-script-manager-impl.php | 2 +- .../tests/php/test-class-throw-on-errors.php | 2 +- projects/packages/backup/actions.php | 4 +- .../add-jetpack-backup-schedule-time | 4 + projects/packages/backup/package.json | 4 + .../backup/src/class-initial-state.php | 2 +- .../src/class-jetpack-backup-upgrades.php | 2 +- .../backup/src/class-jetpack-backup.php | 40 +++++- .../backup/src/class-rest-controller.php | 2 +- .../backup/src/js/components/Backups.js | 4 +- .../src/js/components/backups-style.scss | 15 +++ .../js/components/next-scheduled-backup.tsx | 66 ++++++++++ .../use-next-backup-schedule.ts | 79 ++++++++++++ .../use-scheduled-time-query.ts | 31 +++++ projects/packages/backup/src/js/index.js | 17 ++- .../backup/tests/php/test-rest-controller.php | 4 +- .../tests/php/test-storage-addon-upsell.php | 2 +- projects/packages/backup/tsconfig.json | 4 + .../packages/transport-helper/actions.php | 4 +- .../add-jetpack-backup-schedule-time | 4 + .../src/class-rest-controller.php | 4 +- .../tests/php/test-rest-controller.php | 4 +- .../add-jetpack-backup-schedule-time | 4 + projects/plugins/backup/jetpack-backup.php | 4 +- .../add-jetpack-backup-schedule-time | 4 + ...i-delete-backup-helper-script-endpoint.php | 2 +- ...-install-backup-helper-script-endpoint.php | 2 +- .../php/general/test_jetpack-admin-menu.php | 2 +- projects/plugins/jetpack/uninstall.php | 2 +- .../add-jetpack-backup-schedule-time | 4 + .../migration/src/class-wpcom-migration.php | 2 +- 36 files changed, 420 insertions(+), 34 deletions(-) create mode 100644 projects/packages/backup-helper-script-manager/changelog/add-jetpack-backup-schedule-time create mode 100644 projects/packages/backup/changelog/add-jetpack-backup-schedule-time create mode 100644 projects/packages/backup/src/js/components/next-scheduled-backup.tsx create mode 100644 projects/packages/backup/src/js/hooks/scheduled-backups/use-next-backup-schedule.ts create mode 100644 projects/packages/backup/src/js/hooks/scheduled-backups/use-scheduled-time-query.ts create mode 100644 projects/packages/backup/tsconfig.json create mode 100644 projects/packages/transport-helper/changelog/add-jetpack-backup-schedule-time create mode 100644 projects/plugins/backup/changelog/add-jetpack-backup-schedule-time create mode 100644 projects/plugins/jetpack/changelog/add-jetpack-backup-schedule-time create mode 100644 projects/plugins/migration/changelog/add-jetpack-backup-schedule-time diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 06138f17aa57a..91dd7d170e112 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1725,12 +1725,15 @@ importers: '@automattic/jetpack-connection': specifier: workspace:* version: link:../../js-packages/connection + '@tanstack/react-query': + specifier: 5.20.5 + version: 5.20.5(react@18.3.1) '@wordpress/api-fetch': specifier: 7.11.0 version: 7.11.0 '@wordpress/components': specifier: 28.11.0 - version: 28.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 28.11.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@wordpress/data': specifier: 10.11.0 version: 10.11.0(react@18.3.1) @@ -1743,6 +1746,9 @@ importers: '@wordpress/i18n': specifier: 5.11.0 version: 5.11.0 + moment: + specifier: 2.29.4 + version: 2.29.4 prop-types: specifier: ^15.8.1 version: 15.8.1 @@ -1773,10 +1779,13 @@ importers: version: 10.4.0 '@testing-library/react': specifier: 16.0.1 - version: 16.0.1(@testing-library/dom@10.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 16.0.1(@testing-library/dom@10.4.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@testing-library/user-event': specifier: 14.5.2 version: 14.5.2(@testing-library/dom@10.4.0) + '@types/react': + specifier: 18.3.3 + version: 18.3.3 '@wordpress/browserslist-config': specifier: 6.11.0 version: 6.11.0 @@ -1795,6 +1804,9 @@ importers: sass-loader: specifier: 12.4.0 version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + typescript: + specifier: 5.0.4 + version: 5.0.4 webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -7455,6 +7467,9 @@ packages: '@types/react@18.3.12': resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==} + '@types/react@18.3.3': + resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} + '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -15285,6 +15300,22 @@ snapshots: transitivePeerDependencies: - supports-color + '@emotion/react@11.13.3(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@emotion/babel-plugin': 11.12.0 + '@emotion/cache': 11.13.1 + '@emotion/serialize': 1.3.2 + '@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1) + '@emotion/utils': 1.4.1 + '@emotion/weak-memoize': 0.4.0 + hoist-non-react-statics: 3.3.2 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + transitivePeerDependencies: + - supports-color + '@emotion/react@11.13.3(react@18.3.1)': dependencies: '@babel/runtime': 7.26.0 @@ -15324,6 +15355,21 @@ snapshots: transitivePeerDependencies: - supports-color + '@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@emotion/babel-plugin': 11.12.0 + '@emotion/is-prop-valid': 1.3.1 + '@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1) + '@emotion/serialize': 1.3.2 + '@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1) + '@emotion/utils': 1.4.1 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + transitivePeerDependencies: + - supports-color + '@emotion/styled@11.13.0(@emotion/react@11.13.3(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.26.0 @@ -17148,6 +17194,15 @@ snapshots: optionalDependencies: '@types/react': 18.3.12 + '@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@testing-library/dom': 10.4.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.26.0 @@ -17378,6 +17433,11 @@ snapshots: '@types/prop-types': 15.7.13 csstype: 3.1.3 + '@types/react@18.3.3': + dependencies: + '@types/prop-types': 15.7.13 + csstype: 3.1.3 + '@types/resolve@1.20.2': {} '@types/resolve@1.20.6': {} @@ -18206,6 +18266,60 @@ snapshots: - '@types/react' - supports-color + '@wordpress/components@28.11.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@ariakit/react': 0.4.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@babel/runtime': 7.25.7 + '@emotion/cache': 11.13.1 + '@emotion/css': 11.13.4 + '@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1) + '@emotion/serialize': 1.3.2 + '@emotion/styled': 11.13.0(@emotion/react@11.13.3(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) + '@emotion/utils': 1.4.1 + '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@types/gradient-parser': 0.1.3 + '@types/highlight-words-core': 1.2.1 + '@use-gesture/react': 10.3.1(react@18.3.1) + '@wordpress/a11y': 4.11.0 + '@wordpress/compose': 7.11.0(react@18.3.1) + '@wordpress/date': 5.11.0 + '@wordpress/deprecated': 4.11.0 + '@wordpress/dom': 4.11.0 + '@wordpress/element': 6.11.0 + '@wordpress/escape-html': 3.11.0 + '@wordpress/hooks': 4.11.0 + '@wordpress/html-entities': 4.11.0 + '@wordpress/i18n': 5.11.0 + '@wordpress/icons': 10.11.0(react@18.3.1) + '@wordpress/is-shallow-equal': 5.11.0 + '@wordpress/keycodes': 4.11.0 + '@wordpress/primitives': 4.11.0(react@18.3.1) + '@wordpress/private-apis': 1.11.0 + '@wordpress/rich-text': 7.11.0(react@18.3.1) + '@wordpress/warning': 3.11.0 + change-case: 4.1.2 + clsx: 2.1.1 + colord: 2.9.3 + date-fns: 3.6.0 + deepmerge: 4.3.1 + fast-deep-equal: 3.1.3 + framer-motion: 11.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + gradient-parser: 0.1.5 + highlight-words-core: 1.2.3 + is-plain-object: 5.0.0 + memize: 2.1.0 + path-to-regexp: 6.3.0 + re-resizable: 6.10.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-colorful: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-dom: 18.3.1(react@18.3.1) + remove-accents: 0.5.0 + uuid: 9.0.1 + transitivePeerDependencies: + - '@emotion/is-prop-valid' + - '@types/react' + - supports-color + '@wordpress/components@28.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@ariakit/react': 0.4.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1) diff --git a/projects/packages/backup-helper-script-manager/changelog/add-jetpack-backup-schedule-time b/projects/packages/backup-helper-script-manager/changelog/add-jetpack-backup-schedule-time new file mode 100644 index 0000000000000..798d9040ba827 --- /dev/null +++ b/projects/packages/backup-helper-script-manager/changelog/add-jetpack-backup-schedule-time @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Backup: added next daily backup schedule time on admin page diff --git a/projects/packages/backup-helper-script-manager/src/class-helper-script-manager-impl.php b/projects/packages/backup-helper-script-manager/src/class-helper-script-manager-impl.php index adab13b8167bb..9cd06aa577197 100644 --- a/projects/packages/backup-helper-script-manager/src/class-helper-script-manager-impl.php +++ b/projects/packages/backup-helper-script-manager/src/class-helper-script-manager-impl.php @@ -9,7 +9,7 @@ // order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide // to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin // are installed, or in some other cases). -namespace Automattic\Jetpack\Backup\V0004; +namespace Automattic\Jetpack\Backup\V0005; use Exception; use WP_Error; diff --git a/projects/packages/backup-helper-script-manager/src/class-helper-script-manager.php b/projects/packages/backup-helper-script-manager/src/class-helper-script-manager.php index 5833abaffb662..650f2e3c368d1 100644 --- a/projects/packages/backup-helper-script-manager/src/class-helper-script-manager.php +++ b/projects/packages/backup-helper-script-manager/src/class-helper-script-manager.php @@ -9,7 +9,7 @@ // order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide // to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin // are installed, or in some other cases). -namespace Automattic\Jetpack\Backup\V0004; +namespace Automattic\Jetpack\Backup\V0005; /** * Manage installation, deletion and cleanup of Helper Scripts to assist with backing up Jetpack Sites. diff --git a/projects/packages/backup-helper-script-manager/src/class-throw-on-errors.php b/projects/packages/backup-helper-script-manager/src/class-throw-on-errors.php index 1f59694f4c487..2b14f255b0669 100644 --- a/projects/packages/backup-helper-script-manager/src/class-throw-on-errors.php +++ b/projects/packages/backup-helper-script-manager/src/class-throw-on-errors.php @@ -8,7 +8,7 @@ // order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide // to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin // are installed, or in some other cases). -namespace Automattic\Jetpack\Backup\V0004; +namespace Automattic\Jetpack\Backup\V0005; use Exception; use Throwable; diff --git a/projects/packages/backup-helper-script-manager/tests/php/test-class-helper-script-manager-impl.php b/projects/packages/backup-helper-script-manager/tests/php/test-class-helper-script-manager-impl.php index 7165a52ec8cb5..da7e02fe30473 100644 --- a/projects/packages/backup-helper-script-manager/tests/php/test-class-helper-script-manager-impl.php +++ b/projects/packages/backup-helper-script-manager/tests/php/test-class-helper-script-manager-impl.php @@ -4,7 +4,7 @@ // order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide // to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin // are installed, or in some other cases). -namespace Automattic\Jetpack\Backup\V0004; +namespace Automattic\Jetpack\Backup\V0005; use Exception; use WorDBless\BaseTestCase; diff --git a/projects/packages/backup-helper-script-manager/tests/php/test-class-throw-on-errors.php b/projects/packages/backup-helper-script-manager/tests/php/test-class-throw-on-errors.php index fa0482c4de1dc..080556272a798 100644 --- a/projects/packages/backup-helper-script-manager/tests/php/test-class-throw-on-errors.php +++ b/projects/packages/backup-helper-script-manager/tests/php/test-class-throw-on-errors.php @@ -8,7 +8,7 @@ // order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide // to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin // are installed, or in some other cases). -namespace Automattic\Jetpack\Backup\V0004; +namespace Automattic\Jetpack\Backup\V0005; use Exception; use PHPUnit\Framework\TestCase; diff --git a/projects/packages/backup/actions.php b/projects/packages/backup/actions.php index 9c5f041de4da0..7608526a2af53 100644 --- a/projects/packages/backup/actions.php +++ b/projects/packages/backup/actions.php @@ -23,10 +23,10 @@ } // Clean up expired Helper Scripts from a scheduled event. -$add_action( 'jetpack_backup_cleanup_helper_scripts', array( 'Automattic\\Jetpack\\Backup\\V0004\\Helper_Script_Manager', 'cleanup_expired_helper_scripts' ) ); +$add_action( 'jetpack_backup_cleanup_helper_scripts', array( 'Automattic\\Jetpack\\Backup\\V0005\\Helper_Script_Manager', 'cleanup_expired_helper_scripts' ) ); // Register REST routes. -$add_action( 'rest_api_init', array( 'Automattic\\Jetpack\\Backup\\V0004\\REST_Controller', 'register_rest_routes' ) ); +$add_action( 'rest_api_init', array( 'Automattic\\Jetpack\\Backup\\V0005\\REST_Controller', 'register_rest_routes' ) ); // Set up package version hook. $add_filter( 'jetpack_package_versions', 'Automattic\\Jetpack\\Backup\\Package_Version::send_package_version_to_tracker' ); diff --git a/projects/packages/backup/changelog/add-jetpack-backup-schedule-time b/projects/packages/backup/changelog/add-jetpack-backup-schedule-time new file mode 100644 index 0000000000000..0fc3f5bafb036 --- /dev/null +++ b/projects/packages/backup/changelog/add-jetpack-backup-schedule-time @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Backup: added next daily backup schedule time on admin page diff --git a/projects/packages/backup/package.json b/projects/packages/backup/package.json index 9ca9d21581b5c..c8d2abb35fac6 100644 --- a/projects/packages/backup/package.json +++ b/projects/packages/backup/package.json @@ -32,12 +32,14 @@ "@automattic/jetpack-api": "workspace:*", "@automattic/jetpack-components": "workspace:*", "@automattic/jetpack-connection": "workspace:*", + "@tanstack/react-query": "5.20.5", "@wordpress/api-fetch": "7.11.0", "@wordpress/components": "28.11.0", "@wordpress/data": "10.11.0", "@wordpress/date": "5.11.0", "@wordpress/element": "6.11.0", "@wordpress/i18n": "5.11.0", + "moment": "2.29.4", "prop-types": "^15.8.1", "react": "18.3.1", "react-dom": "18.3.1" @@ -51,12 +53,14 @@ "@testing-library/dom": "10.4.0", "@testing-library/react": "16.0.1", "@testing-library/user-event": "14.5.2", + "@types/react": "18.3.3", "@wordpress/browserslist-config": "6.11.0", "concurrently": "7.6.0", "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", "sass": "1.64.1", "sass-loader": "12.4.0", + "typescript": "5.0.4", "webpack": "5.94.0", "webpack-cli": "4.9.1" } diff --git a/projects/packages/backup/src/class-initial-state.php b/projects/packages/backup/src/class-initial-state.php index 164c291f6a1da..98a508472166f 100644 --- a/projects/packages/backup/src/class-initial-state.php +++ b/projects/packages/backup/src/class-initial-state.php @@ -9,7 +9,7 @@ // order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide // to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin // are installed, or in some other cases). -namespace Automattic\Jetpack\Backup\V0004; +namespace Automattic\Jetpack\Backup\V0005; use Automattic\Jetpack\Connection\Plugin_Storage as Connection_Plugin_Storage; use Automattic\Jetpack\Status; diff --git a/projects/packages/backup/src/class-jetpack-backup-upgrades.php b/projects/packages/backup/src/class-jetpack-backup-upgrades.php index eddf0ba54ec24..be18aede8284a 100644 --- a/projects/packages/backup/src/class-jetpack-backup-upgrades.php +++ b/projects/packages/backup/src/class-jetpack-backup-upgrades.php @@ -9,7 +9,7 @@ // order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide // to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin // are installed, or in some other cases). -namespace Automattic\Jetpack\Backup\V0004; +namespace Automattic\Jetpack\Backup\V0005; use function get_option; use function update_option; diff --git a/projects/packages/backup/src/class-jetpack-backup.php b/projects/packages/backup/src/class-jetpack-backup.php index b6e0fd3bafad7..3468d85ea7310 100644 --- a/projects/packages/backup/src/class-jetpack-backup.php +++ b/projects/packages/backup/src/class-jetpack-backup.php @@ -9,7 +9,7 @@ // order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide // to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin // are installed, or in some other cases). -namespace Automattic\Jetpack\Backup\V0004; +namespace Automattic\Jetpack\Backup\V0005; if ( ! defined( 'ABSPATH' ) ) { exit; @@ -17,7 +17,7 @@ use Automattic\Jetpack\Admin_UI\Admin_Menu; use Automattic\Jetpack\Assets; -use Automattic\Jetpack\Backup\V0004\Initial_State as Backup_Initial_State; +use Automattic\Jetpack\Backup\V0005\Initial_State as Backup_Initial_State; use Automattic\Jetpack\Config; use Automattic\Jetpack\Connection\Client; use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State; @@ -341,6 +341,17 @@ public static function register_rest_routes() { ) ); + // Get backup schedule time + register_rest_route( + 'jetpack/v4', + '/site/backup/schedule', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => __CLASS__ . '::get_site_backup_schedule_time', + 'permission_callback' => __CLASS__ . '::backups_permissions_callback', + ) + ); + // Get site policies register_rest_route( 'jetpack/v4', @@ -785,6 +796,31 @@ public static function enqueue_backup() { ); } + /** + * Get site backup schedule time + * + * @return string|WP_Error A JSON object with the backup schedule time if the request was successful, or a WP_Error otherwise. + */ + public static function get_site_backup_schedule_time() { + $blog_id = Jetpack_Options::get_option( 'id' ); + + $response = Client::wpcom_json_api_request_as_user( + '/sites/' . $blog_id . '/rewind/scheduled', + 'v2', + array(), + null, + 'wpcom' + ); + + if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { + return null; + } + + return rest_ensure_response( + json_decode( $response['body'], true ) + ); + } + /** * Removes plugin from the connection manager * If it's the last plugin using the connection, the site will be disconnected. diff --git a/projects/packages/backup/src/class-rest-controller.php b/projects/packages/backup/src/class-rest-controller.php index f0b4f30d855e2..02494c943de7f 100644 --- a/projects/packages/backup/src/class-rest-controller.php +++ b/projects/packages/backup/src/class-rest-controller.php @@ -10,7 +10,7 @@ // order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide // to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin // are installed, or in some other cases). -namespace Automattic\Jetpack\Backup\V0004; +namespace Automattic\Jetpack\Backup\V0005; use Automattic\Jetpack\Connection\Client; use Automattic\Jetpack\Connection\Rest_Authentication; diff --git a/projects/packages/backup/src/js/components/Backups.js b/projects/packages/backup/src/js/components/Backups.js index bfe641cdb4f18..37d091508c355 100644 --- a/projects/packages/backup/src/js/components/Backups.js +++ b/projects/packages/backup/src/js/components/Backups.js @@ -25,6 +25,7 @@ import PostsIcon from './icons/posts.svg'; import ThemesIcon from './icons/themes.svg'; import UploadsIcon from './icons/uploads.svg'; import WarningIcon from './icons/warning.svg'; +import NextScheduledBackup from './next-scheduled-backup'; /* eslint react/react-in-jsx-scope: 0 */ export const Backups = () => { @@ -157,7 +158,8 @@ const CompleteBackup = ( { latestTime, stats } ) => { />

{ __( 'Latest Backup', 'jetpack-backup-pkg' ) }

-

{ formatDateString( latestTime ) }

+
{ formatDateString( latestTime ) }
+ ) } diff --git a/projects/packages/backup/src/js/components/backups-style.scss b/projects/packages/backup/src/js/components/backups-style.scss index a3cd4464e77c0..11c11e2fae561 100644 --- a/projects/packages/backup/src/js/components/backups-style.scss +++ b/projects/packages/backup/src/js/components/backups-style.scss @@ -122,6 +122,21 @@ } } +.backup__latest-time { + color: #1d2327; + font-size: var(--font-title-large); + font-weight: 700; + line-height: 40px; + margin-bottom: 4px; + margin-top: 0; + white-space: nowrap; +} + +.backup__next-scheduled-time { + font-size: 13px; + margin-bottom: 24px; +} + .backup__restore-point-link { font-size: 14px; line-height: 22px; diff --git a/projects/packages/backup/src/js/components/next-scheduled-backup.tsx b/projects/packages/backup/src/js/components/next-scheduled-backup.tsx new file mode 100644 index 0000000000000..8eb0ecb8c3f79 --- /dev/null +++ b/projects/packages/backup/src/js/components/next-scheduled-backup.tsx @@ -0,0 +1,66 @@ +import { getRedirectUrl, LoadingPlaceholder } from '@automattic/jetpack-components'; +import { ExternalLink } from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import { useCallback } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; +import { FunctionComponent } from 'react'; +import { useNextBackupSchedule } from '../hooks/scheduled-backups/use-next-backup-schedule'; +import useAnalytics from '../hooks/useAnalytics'; +import { STORE_ID } from '../store'; + +type Props = { + siteId: number; +}; + +interface StoreSelectors { + getCalypsoSlug: () => string; +} + +const NextScheduledBackup: FunctionComponent< Props > = () => { + const { tracks } = useAnalytics(); + + const domain = useSelect( select => { + const selectors: StoreSelectors = select( STORE_ID ); + return selectors.getCalypsoSlug(); + }, [] ); + + const { hasLoaded, nextBackupDate, timeRange } = useNextBackupSchedule(); + + const onModifyClick = useCallback( () => { + tracks.recordEvent( 'jetpack_backup_schedule_modify_click' ); + }, [ tracks ] ); + + if ( ! hasLoaded ) { + return ( +
+ +
+ ); + } + + if ( ! nextBackupDate || ! timeRange ) { + return null; + } + + return ( +
+ + { sprintf( + /* translators: %1$s is the formatted date (e.g., Oct 22); %2$s is a time range, such as 10:00-10:59 AM. */ + __( 'Next full backup: %1$s, %2$s.', 'jetpack-backup-pkg' ), + nextBackupDate.format( 'MMM D' ), + timeRange + ) } + { ' ' } + + { __( 'Modify', 'jetpack-backup-pkg' ) } + +
+ ); +}; + +export default NextScheduledBackup; diff --git a/projects/packages/backup/src/js/hooks/scheduled-backups/use-next-backup-schedule.ts b/projects/packages/backup/src/js/hooks/scheduled-backups/use-next-backup-schedule.ts new file mode 100644 index 0000000000000..04784d45ec2b1 --- /dev/null +++ b/projects/packages/backup/src/js/hooks/scheduled-backups/use-next-backup-schedule.ts @@ -0,0 +1,79 @@ +import { getSettings } from '@wordpress/date'; +import moment from 'moment'; +import useScheduledTimeQuery from './use-scheduled-time-query'; + +/** + * Converts a given hour into a time range string, either in UTC or local time. + * The resulting time range shows a start time and an end time 59 minutes later. + * + * - For local times, the start time is displayed in 12-hour format without AM/PM, + * while the end time includes AM/PM. + * - For UTC, the time range is displayed in 24-hour format for both start and end times. + * + * @param {number} hour - The hour of the day (0-23) for which to generate the time range. + * @param {boolean} isUtc - Whether to generate the time range in UTC (true) or local time (false). + * + * @return {string} - A formatted string representing the time range. + */ +const convertHourToRange = ( hour: number, isUtc: boolean = false ): string => { + const time = isUtc + ? moment.utc().startOf( 'day' ).hour( hour ) + : moment().startOf( 'day' ).hour( hour ); + + const startTimeFormat = isUtc ? 'HH:mm' : 'h:mm'; + const endTimeFormat = isUtc ? 'HH:mm' : 'h:mm A'; + + const startTime = time.format( startTimeFormat ); + const endTime = time.add( 59, 'minutes' ).format( endTimeFormat ); + + return `${ startTime }-${ endTime }`; +}; + +export const useNextBackupSchedule = () => { + const { data, isSuccess } = useScheduledTimeQuery(); + + const getNextBackupDate = () => { + if ( ! data || data.scheduledHour === null ) { + return null; + } + + const currentTime = moment(); + const backupTimeUtc = moment.utc().startOf( 'day' ).hour( data.scheduledHour ); + + let nextBackupDate = backupTimeUtc; + const localDateSettings = getSettings(); + + if ( localDateSettings.timezone && localDateSettings.timezone.offset ) { + nextBackupDate = nextBackupDate.utcOffset( localDateSettings.timezone.offset ); + } else { + nextBackupDate = backupTimeUtc.local(); + } + + const nextBackupDateEnd = nextBackupDate.clone().add( 59, 'minutes' ).add( 59, 'seconds' ); + + // Only move to the next day if the current time is after the backup window + if ( currentTime.isAfter( nextBackupDateEnd ) ) { + nextBackupDate.add( 1, 'day' ); // Move to next day + } + + return nextBackupDate; + }; + + const nextBackupDate = getNextBackupDate(); + + if ( ! nextBackupDate ) { + return { + hasLoaded: isSuccess, + date: null, + timeRange: null, + }; + } + + const timeRange = convertHourToRange( nextBackupDate.hour() ); + + return { + hasLoaded: isSuccess, + nextBackupDate, + timeRange, + }; +}; diff --git a/projects/packages/backup/src/js/hooks/scheduled-backups/use-scheduled-time-query.ts b/projects/packages/backup/src/js/hooks/scheduled-backups/use-scheduled-time-query.ts new file mode 100644 index 0000000000000..f565b5b9facac --- /dev/null +++ b/projects/packages/backup/src/js/hooks/scheduled-backups/use-scheduled-time-query.ts @@ -0,0 +1,31 @@ +import { useQuery, UseQueryResult } from '@tanstack/react-query'; +import apiFetch from '@wordpress/api-fetch'; + +export interface ScheduledTimeApi { + ok: boolean; + scheduled_hour: number; + scheduled_by: string | null; +} + +export interface ScheduledTime { + scheduledHour: number; + scheduledBy: string | null; +} + +const useScheduledTimeQuery = (): UseQueryResult< ScheduledTime, Error > => { + const queryKey = [ 'jetpack-backup-scheduled-time' ]; + + return useQuery< ScheduledTimeApi, Error, ScheduledTime >( { + queryKey, + queryFn: async () => + apiFetch( { path: `/jetpack/v4/site/backup/schedule` } ) as Promise< ScheduledTimeApi >, + refetchIntervalInBackground: false, + refetchOnWindowFocus: false, + select: data => ( { + scheduledHour: data.scheduled_hour, + scheduledBy: data.scheduled_by, + } ), + } ); +}; + +export default useScheduledTimeQuery; diff --git a/projects/packages/backup/src/js/index.js b/projects/packages/backup/src/js/index.js index a0b1e27644e48..6dd375feee33a 100644 --- a/projects/packages/backup/src/js/index.js +++ b/projects/packages/backup/src/js/index.js @@ -1,10 +1,19 @@ import { ThemeProvider } from '@automattic/jetpack-components'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { createReduxStore, register } from '@wordpress/data'; import * as WPElement from '@wordpress/element'; import React from 'react'; import Admin from './components/Admin'; import { STORE_ID, storeConfig } from './store'; +const queryClient = new QueryClient( { + defaultOptions: { + queries: { + staleTime: Infinity, + }, + }, +} ); + const store = createReduxStore( STORE_ID, storeConfig ); register( store ); @@ -19,9 +28,11 @@ function render() { } const component = ( - - - + + + + + ); WPElement.createRoot( container ).render( component ); } diff --git a/projects/packages/backup/tests/php/test-rest-controller.php b/projects/packages/backup/tests/php/test-rest-controller.php index 91e60c6659218..bcc3fc7b61fe1 100644 --- a/projects/packages/backup/tests/php/test-rest-controller.php +++ b/projects/packages/backup/tests/php/test-rest-controller.php @@ -4,7 +4,7 @@ // order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide // to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin // are installed, or in some other cases). -namespace Automattic\Jetpack\Backup\V0004; +namespace Automattic\Jetpack\Backup\V0005; use Automattic\Jetpack\Connection\Rest_Authentication as Connection_Rest_Authentication; use PHPUnit\Framework\TestCase; @@ -65,7 +65,7 @@ public function set_up() { wp_set_current_user( 0 ); // Register REST routes. - add_action( 'rest_api_init', array( 'Automattic\\Jetpack\\Backup\\V0004\\REST_Controller', 'register_rest_routes' ) ); + add_action( 'rest_api_init', array( 'Automattic\\Jetpack\\Backup\\V0005\\REST_Controller', 'register_rest_routes' ) ); do_action( 'rest_api_init' ); } diff --git a/projects/packages/backup/tests/php/test-storage-addon-upsell.php b/projects/packages/backup/tests/php/test-storage-addon-upsell.php index 890f6faf94245..e4d7fa1a2af1d 100644 --- a/projects/packages/backup/tests/php/test-storage-addon-upsell.php +++ b/projects/packages/backup/tests/php/test-storage-addon-upsell.php @@ -4,7 +4,7 @@ // order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide // to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin // are installed, or in some other cases). -namespace Automattic\Jetpack\Backup\V0004; +namespace Automattic\Jetpack\Backup\V0005; use PHPUnit\Framework\TestCase; diff --git a/projects/packages/backup/tsconfig.json b/projects/packages/backup/tsconfig.json new file mode 100644 index 0000000000000..a2ecf0d6e8fd0 --- /dev/null +++ b/projects/packages/backup/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "jetpack-js-tools/tsconfig.base.json", + "include": [ "./src/js" ] +} diff --git a/projects/packages/transport-helper/actions.php b/projects/packages/transport-helper/actions.php index 9621437e18fa8..c3d7e01ba266e 100644 --- a/projects/packages/transport-helper/actions.php +++ b/projects/packages/transport-helper/actions.php @@ -23,10 +23,10 @@ } // Clean up expired Jetpack Helper Scripts from a scheduled event. -$add_action( 'jetpack_cleanup_helper_scripts', array( 'Automattic\\Jetpack\\Backup\\V0004\\Helper_Script_Manager', 'cleanup_expired_helper_scripts' ) ); +$add_action( 'jetpack_cleanup_helper_scripts', array( 'Automattic\\Jetpack\\Backup\\V0005\\Helper_Script_Manager', 'cleanup_expired_helper_scripts' ) ); // Register REST routes. -$add_action( 'rest_api_init', array( 'Automattic\\Jetpack\\Transport_Helper\\V0004\\REST_Controller', 'register_rest_routes' ) ); +$add_action( 'rest_api_init', array( 'Automattic\Jetpack\Transport_Helper\V0005\\REST_Controller', 'register_rest_routes' ) ); // Set up package version hook. $add_filter( 'jetpack_package_versions', 'Automattic\\Jetpack\\Transport_Helper\\Package_Version::send_package_version_to_tracker' ); diff --git a/projects/packages/transport-helper/changelog/add-jetpack-backup-schedule-time b/projects/packages/transport-helper/changelog/add-jetpack-backup-schedule-time new file mode 100644 index 0000000000000..798d9040ba827 --- /dev/null +++ b/projects/packages/transport-helper/changelog/add-jetpack-backup-schedule-time @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Backup: added next daily backup schedule time on admin page diff --git a/projects/packages/transport-helper/src/class-rest-controller.php b/projects/packages/transport-helper/src/class-rest-controller.php index 9bdf49ebb4775..f15962eb80163 100644 --- a/projects/packages/transport-helper/src/class-rest-controller.php +++ b/projects/packages/transport-helper/src/class-rest-controller.php @@ -10,9 +10,9 @@ // order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide // to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin // are installed, or in some other cases). -namespace Automattic\Jetpack\Transport_Helper\V0004; +namespace Automattic\Jetpack\Transport_Helper\V0005; -use Automattic\Jetpack\Backup\V0004\Helper_Script_Manager; +use Automattic\Jetpack\Backup\V0005\Helper_Script_Manager; use Automattic\Jetpack\Connection\Rest_Authentication; use WP_Error; use WP_REST_Request; diff --git a/projects/packages/transport-helper/tests/php/test-rest-controller.php b/projects/packages/transport-helper/tests/php/test-rest-controller.php index 0653706dd0e18..f496db19f8614 100644 --- a/projects/packages/transport-helper/tests/php/test-rest-controller.php +++ b/projects/packages/transport-helper/tests/php/test-rest-controller.php @@ -4,7 +4,7 @@ // order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide // to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin // are installed, or in some other cases). -namespace Automattic\Jetpack\Transport_Helper\V0004; +namespace Automattic\Jetpack\Transport_Helper\V0005; use Automattic\Jetpack\Connection\Rest_Authentication as Connection_Rest_Authentication; use PHPUnit\Framework\TestCase; @@ -63,7 +63,7 @@ public function set_up() { wp_set_current_user( 0 ); // Register REST routes. - add_action( 'rest_api_init', array( 'Automattic\\Jetpack\\Transport_Helper\\V0004\\REST_Controller', 'register_rest_routes' ) ); + add_action( 'rest_api_init', array( 'Automattic\Jetpack\Transport_Helper\V0005\\REST_Controller', 'register_rest_routes' ) ); do_action( 'rest_api_init' ); } diff --git a/projects/plugins/backup/changelog/add-jetpack-backup-schedule-time b/projects/plugins/backup/changelog/add-jetpack-backup-schedule-time new file mode 100644 index 0000000000000..0fc3f5bafb036 --- /dev/null +++ b/projects/plugins/backup/changelog/add-jetpack-backup-schedule-time @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Backup: added next daily backup schedule time on admin page diff --git a/projects/plugins/backup/jetpack-backup.php b/projects/plugins/backup/jetpack-backup.php index b0932ee566591..1de96ecfafacc 100644 --- a/projects/plugins/backup/jetpack-backup.php +++ b/projects/plugins/backup/jetpack-backup.php @@ -29,7 +29,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -use Automattic\Jetpack\Backup\V0004\Jetpack_Backup as My_Jetpack_Backup; +use Automattic\Jetpack\Backup\V0005\Jetpack_Backup as My_Jetpack_Backup; use Automattic\Jetpack\My_Jetpack\Initializer as My_Jetpack_Initializer; if ( ! defined( 'ABSPATH' ) ) { @@ -179,7 +179,7 @@ function ( $actions ) { } ); -register_deactivation_hook( __FILE__, array( 'Automattic\\Jetpack\\Backup\\V0004\\Jetpack_Backup', 'plugin_deactivation' ) ); +register_deactivation_hook( __FILE__, array( 'Automattic\\Jetpack\\Backup\\V0005\\Jetpack_Backup', 'plugin_deactivation' ) ); // Main plugin class. My_Jetpack_Backup::initialize(); diff --git a/projects/plugins/jetpack/changelog/add-jetpack-backup-schedule-time b/projects/plugins/jetpack/changelog/add-jetpack-backup-schedule-time new file mode 100644 index 0000000000000..33fd28e9c13cc --- /dev/null +++ b/projects/plugins/jetpack/changelog/add-jetpack-backup-schedule-time @@ -0,0 +1,4 @@ +Significance: patch +Type: other + +Backup: added next daily backup schedule time on admin page diff --git a/projects/plugins/jetpack/json-endpoints/jetpack/class-jetpack-json-api-delete-backup-helper-script-endpoint.php b/projects/plugins/jetpack/json-endpoints/jetpack/class-jetpack-json-api-delete-backup-helper-script-endpoint.php index 420f0b4e4bced..738907df9bc76 100644 --- a/projects/plugins/jetpack/json-endpoints/jetpack/class-jetpack-json-api-delete-backup-helper-script-endpoint.php +++ b/projects/plugins/jetpack/json-endpoints/jetpack/class-jetpack-json-api-delete-backup-helper-script-endpoint.php @@ -6,7 +6,7 @@ * @package automattic/jetpack */ -use Automattic\Jetpack\Backup\V0004\Helper_Script_Manager; +use Automattic\Jetpack\Backup\V0005\Helper_Script_Manager; /** * API endpoint /sites/%s/delete-backup-helper-script diff --git a/projects/plugins/jetpack/json-endpoints/jetpack/class-jetpack-json-api-install-backup-helper-script-endpoint.php b/projects/plugins/jetpack/json-endpoints/jetpack/class-jetpack-json-api-install-backup-helper-script-endpoint.php index 2ec111fb63621..f59748b19ac42 100644 --- a/projects/plugins/jetpack/json-endpoints/jetpack/class-jetpack-json-api-install-backup-helper-script-endpoint.php +++ b/projects/plugins/jetpack/json-endpoints/jetpack/class-jetpack-json-api-install-backup-helper-script-endpoint.php @@ -6,7 +6,7 @@ * @package automattic/jetpack */ -use Automattic\Jetpack\Backup\V0004\Helper_Script_Manager; +use Automattic\Jetpack\Backup\V0005\Helper_Script_Manager; /** * API endpoint /sites/%s/install-backup-helper-script diff --git a/projects/plugins/jetpack/tests/php/general/test_jetpack-admin-menu.php b/projects/plugins/jetpack/tests/php/general/test_jetpack-admin-menu.php index c4d0a6983443a..b350e54652e0d 100644 --- a/projects/plugins/jetpack/tests/php/general/test_jetpack-admin-menu.php +++ b/projects/plugins/jetpack/tests/php/general/test_jetpack-admin-menu.php @@ -3,7 +3,7 @@ * @package automattic/jetpack */ -use Automattic\Jetpack\Backup\V0004\Jetpack_Backup; +use Automattic\Jetpack\Backup\V0005\Jetpack_Backup; use Automattic\Jetpack\Stats_Admin\Dashboard; use Automattic\Jetpack\VideoPress\Admin_UI; use Automattic\Jetpack\WordAds\Dashboard as WordAdsDashboard; diff --git a/projects/plugins/jetpack/uninstall.php b/projects/plugins/jetpack/uninstall.php index 848bf6266f44d..d5b09a4090b17 100644 --- a/projects/plugins/jetpack/uninstall.php +++ b/projects/plugins/jetpack/uninstall.php @@ -5,7 +5,7 @@ * @package automattic/jetpack */ -use Automattic\Jetpack\Backup\V0004\Helper_Script_Manager; +use Automattic\Jetpack\Backup\V0005\Helper_Script_Manager; use Automattic\Jetpack\Connection\Manager as Connection_Manager; use Automattic\Jetpack\Sync\Sender; diff --git a/projects/plugins/migration/changelog/add-jetpack-backup-schedule-time b/projects/plugins/migration/changelog/add-jetpack-backup-schedule-time new file mode 100644 index 0000000000000..798d9040ba827 --- /dev/null +++ b/projects/plugins/migration/changelog/add-jetpack-backup-schedule-time @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Backup: added next daily backup schedule time on admin page diff --git a/projects/plugins/migration/src/class-wpcom-migration.php b/projects/plugins/migration/src/class-wpcom-migration.php index 035aec5f06e89..956b41282ddcf 100644 --- a/projects/plugins/migration/src/class-wpcom-migration.php +++ b/projects/plugins/migration/src/class-wpcom-migration.php @@ -12,7 +12,7 @@ } use Automattic\Jetpack\Assets; -use Automattic\Jetpack\Backup\V0004\Jetpack_Backup; +use Automattic\Jetpack\Backup\V0005\Jetpack_Backup; use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State; use Automattic\Jetpack\Connection\Manager as Connection_Manager; use Automattic\Jetpack\Connection\Rest_Authentication as Connection_Rest_Authentication; From f05867ce40dbeea21838a227fcc16c7362c2ee36 Mon Sep 17 00:00:00 2001 From: Jeremy Herve Date: Tue, 12 Nov 2024 17:44:02 +0100 Subject: [PATCH 04/51] Repo Gardening: allow escalating issues to multiple groups (#39973) * Repo Gardening: allow escalating issues to multiple groups Up until now, once we had sent one Slack notification about an issue, we couldn't send another to a different team. This was problematic given that we attempt to warn about escalated issues to 2 different teams: - Product Ambassador HEs in the Triage Issues task - Dev teams in the Update Board task By splitting things into 2 different labels ("[Status] Priority Review Triggered" for dev teams, "[Status] Escalated to Product Ambassadors" for Product ambassadors), we can now send notifications to two different teams. It is not the perfect solution since ideally, we'd want to allow notifications per Slack channel instead of per team, but: - There is currently no better way to warn about an issue already escalated than to add a label. - We cannot really use the Slack channel ID in the label name, that would be odd. * Simplify check before we send slack notification This was added in #30100, but it ends up blocking legitimate messages from being sent. * Move checking for labels out of the Slack notification function This should be handled by each task calling the function, for more flexibility, and a simpler notification function. * Remove Update Board task We'll merge it into the existing triage issues task. It is not very intuitive to have this being two different tasks, when both perform actions that can be described as issue triage. It also makes the codebase more approachable, and will allow us to extract / centralize some of the logic that was used in both tasks. * Extract more label detection functions * Extract AI labeling into its own file It should make it a bit easier for folks to contribute. * Add new Project board management "sub-task" for triageIssues * Document the new actions performed by the Triage issues task * Extract method used to find the priority of an issue * Bring it all together in the update triageIssues task * Fix argument order * Add missing argument * Remove unnecessary state check (we check earlier) & add logging * Remove files that are no longer used * Add label when issue is automatically triaged to project board See pfVjQF-55-p2#comment-119 * Update label name See pfVjQF-55-p2#comment-127 * Switch to major version bump See https://github.com/Automattic/jetpack/pull/39973#discussion_r1833195889 * Update to new label value format See https://github.com/Automattic/jetpack/pull/39973#discussion_r1836832119 * Simplify check for existing labels on issues See https://github.com/Automattic/jetpack/pull/39973#discussion_r1836851347 This is similar to what was already done in 148b28cd23ca31c9a574cbcecc0d9f9a4e887997 * Better clarify where issue priority comes from, and use it Let's not attempt to add a label to an issue when it is already on the issue. See https://github.com/Automattic/jetpack/pull/39973#discussion_r1836829024 --- .../github-actions/repo-gardening/README.md | 7 +- .../changelog/rm-update-board-task | 4 + .../changelog/update-escalate-issue-multiple | 4 + .../repo-gardening/src/index.js | 6 - .../src/tasks/triage-issues/ai-labeling.js | 177 +++++++++++++ .../automattic-label-team-assignments.js | 0 .../tasks/triage-issues/get-issue-priority.js | 57 ++++ .../src/tasks/triage-issues/index.js | 223 +++------------- .../src/tasks/triage-issues/readme.md | 64 ++++- .../update-board.js} | 248 ++++++++---------- .../src/tasks/update-board/readme.md | 41 --- .../src/utils/labels/has-escalated-label.js | 38 --- .../src/utils/labels/has-priority-labels.js | 27 -- .../utils/slack/notify-important-issues.js | 83 +++--- 14 files changed, 492 insertions(+), 487 deletions(-) create mode 100644 projects/github-actions/repo-gardening/changelog/rm-update-board-task create mode 100644 projects/github-actions/repo-gardening/changelog/update-escalate-issue-multiple create mode 100644 projects/github-actions/repo-gardening/src/tasks/triage-issues/ai-labeling.js rename projects/github-actions/repo-gardening/src/tasks/{update-board => triage-issues}/automattic-label-team-assignments.js (100%) create mode 100644 projects/github-actions/repo-gardening/src/tasks/triage-issues/get-issue-priority.js rename projects/github-actions/repo-gardening/src/tasks/{update-board/index.js => triage-issues/update-board.js} (67%) delete mode 100644 projects/github-actions/repo-gardening/src/tasks/update-board/readme.md delete mode 100644 projects/github-actions/repo-gardening/src/utils/labels/has-escalated-label.js delete mode 100644 projects/github-actions/repo-gardening/src/utils/labels/has-priority-labels.js diff --git a/projects/github-actions/repo-gardening/README.md b/projects/github-actions/repo-gardening/README.md index 8b4369ae04c85..194b9f640b30c 100644 --- a/projects/github-actions/repo-gardening/README.md +++ b/projects/github-actions/repo-gardening/README.md @@ -18,7 +18,6 @@ Here is the current list of tasks handled by this action: - Triage Issues (`triageIssues`): Adds labels to issues based on issue content, and send Slack notifications depending on Priority. - Gather support references (`gatherSupportReferences`): Adds a new comment with a list of all support references on the issue, and escalates that issue via a Slack message if needed. - Reply to customers Reminder ( `replyToCustomersReminder` ): sends a Slack message about closed issues to remind Automatticians to update customers. -- Update Board (`updateBoard`): this task updates specific columns in a GitHub Project board, based on labels applied to an issue. Some of the tasks are may not satisfy your needs. If that's the case, you can use the `tasks` option to limit the action to the list of tasks you need in your repo. See the example below to find out more. @@ -84,9 +83,9 @@ The action relies on the following parameters. - (Optional) `slack_he_triage_channel` is the Slack public channel ID where messages for the HE Triage team will be posted. The value should be stored in a secret. - (Optional) `slack_quality_channel` is the Slack public channel ID where issues needing extra triage / escalation will be sent. The value should be stored in a secret. - (Optional) `reply_to_customers_threshold`. It is optional, and defaults to 10. It is the minimum number of support references needed to trigger an alert that we need to reply to customers. -- (Optional) `triage_projects_token` is a [personal access token](https://github.com/settings/tokens/new) with `repo` and `project` scopes. The token should be stored in a secret. This is required if you want to use the `updateBoard` task. -- (Optional) `project_board_url` is the URL of a GitHub Project Board. We'll automate some of the work on that board in the `updateBoard` task. -- (Optional) `labels_team_assignments` is a list of features you can provide, with matching team names, as specified in the "Team" field of your GitHub Project Board used for the `updateBoard` task, and lists of labels in use in your repository. +- (Optional) `triage_projects_token` is a [personal access token](https://github.com/settings/tokens/new) with `repo` and `project` scopes. The token should be stored in a secret. This is required if you want to use the `triageIssues` task. +- (Optional) `project_board_url` is the URL of a GitHub Project Board. We'll automate some of the work on that board in the `triageIssues` task. +- (Optional) `labels_team_assignments` is a list of features you can provide, with matching team names, as specified in the "Team" field of your GitHub Project Board used for the `triageIssues` task, and lists of labels in use in your repository. - (Optional) `openai_api_key` is the API key for OpenAI. This is required if you want to use the `triageIssues` task to automatically add labels to your issues. **Note**: this option is only available for Automattic-hosted repositories. #### How to create a Slack bot and get your SLACK_TOKEN diff --git a/projects/github-actions/repo-gardening/changelog/rm-update-board-task b/projects/github-actions/repo-gardening/changelog/rm-update-board-task new file mode 100644 index 0000000000000..e0738bc8e32a5 --- /dev/null +++ b/projects/github-actions/repo-gardening/changelog/rm-update-board-task @@ -0,0 +1,4 @@ +Significance: major +Type: changed + +Board Triage: remove updateBoard task. It will now be part of the existing triageIssues task. diff --git a/projects/github-actions/repo-gardening/changelog/update-escalate-issue-multiple b/projects/github-actions/repo-gardening/changelog/update-escalate-issue-multiple new file mode 100644 index 0000000000000..de823de98b339 --- /dev/null +++ b/projects/github-actions/repo-gardening/changelog/update-escalate-issue-multiple @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Issue escalation: allow escalating the issue to multiple teams. diff --git a/projects/github-actions/repo-gardening/src/index.js b/projects/github-actions/repo-gardening/src/index.js index 69b89af4c98bc..7c60a9ad74a83 100644 --- a/projects/github-actions/repo-gardening/src/index.js +++ b/projects/github-actions/repo-gardening/src/index.js @@ -11,18 +11,12 @@ const notifyDesign = require( './tasks/notify-design' ); const notifyEditorial = require( './tasks/notify-editorial' ); const replyToCustomersReminder = require( './tasks/reply-to-customers-reminder' ); const triageIssues = require( './tasks/triage-issues' ); -const updateBoard = require( './tasks/update-board' ); const wpcomCommitReminder = require( './tasks/wpcom-commit-reminder' ); const debug = require( './utils/debug' ); const ifNotClosed = require( './utils/if-not-closed' ); const ifNotFork = require( './utils/if-not-fork' ); const automations = [ - { - event: 'issues', - action: [ 'labeled', 'opened' ], - task: updateBoard, - }, { event: 'pull_request_target', action: [ 'opened', 'synchronize', 'edited' ], diff --git a/projects/github-actions/repo-gardening/src/tasks/triage-issues/ai-labeling.js b/projects/github-actions/repo-gardening/src/tasks/triage-issues/ai-labeling.js new file mode 100644 index 0000000000000..02f87c5b8d6be --- /dev/null +++ b/projects/github-actions/repo-gardening/src/tasks/triage-issues/ai-labeling.js @@ -0,0 +1,177 @@ +const { getInput } = require( '@actions/core' ); +const debug = require( '../../utils/debug' ); +const getAvailableLabels = require( '../../utils/labels/get-available-labels' ); +const getLabels = require( '../../utils/labels/get-labels' ); +const sendOpenAiRequest = require( '../../utils/openai/send-request' ); + +/* global GitHub, WebhookPayloadIssue */ + +/** + * Request a list of matching labels from Open AI that can be applied to the issue, + * based on the issue contents. + * + * @param {GitHub} octokit - Initialized Octokit REST client. + * @param {string} owner - Repository owner. + * @param {string} repo - Repository name. + * @param {string} title - Issue title. + * @param {string} body - Issue body. + * + * @return {Promise} Promise resolving to an object of labels to apply to the issue, and their explanations. + */ +async function fetchOpenAiLabelsSuggestions( octokit, owner, repo, title, body ) { + const suggestions = { labels: [], explanations: {} }; + + // Get all the Feature and Feature Group labels in the repo. + const pattern = /^(\[Feature\]|\[Feature Group\])/; + const repoLabels = await getAvailableLabels( octokit, owner, repo, pattern ); + + // If no labels are found, bail. + if ( repoLabels.length === 0 ) { + debug( + 'triage-issues > auto-label: No labels found in the repository. Aborting OpenAI request.' + ); + return suggestions; + } + + const prompt = `You must analyse the content below, composed of 2 data points pulled from a GitHub issue: + +- a title +- the issue body + +Here is the issue title. It is the most important part of the text you must analyse: + +- ${ title } + +Here is the issue body: + +********************** + +${ body } + +********************** + +You must analyze this content, and suggest labels related to the content. +The labels you will suggest must all come from the list below. +Each item on the list of labels below follows the following format: -