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;