Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Significance: patch
Type: added
Comment: Backup: wire the modernized dashboard to real REST endpoints, add Gates for capability/connection checks, drop the mock-mode dev banner. No-op when the modernization filter is off.
1 change: 1 addition & 0 deletions projects/packages/backup/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"@wordpress/route": "0.17.0",
"@wordpress/theme": "0.17.0",
"@wordpress/ui": "0.17.0",
"@wordpress/url": "4.51.0",
"moment": "2.30.1",
"prop-types": "^15.8.1",
"react": "18.3.1",
Expand Down
5 changes: 4 additions & 1 deletion projects/packages/backup/routes/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
"private": true,
"dependencies": {
"@automattic/jetpack-components": "workspace:*",
"@tanstack/react-query": "5.101.2",
"@types/react": "18.3.28",
"@wordpress/admin-ui": "2.5.0",
"@wordpress/api-fetch": "7.51.0",
"@wordpress/components": "37.0.0",
"@wordpress/dataviews": "17.1.0",
"@wordpress/date": "5.51.0",
Expand All @@ -13,7 +15,8 @@
"@wordpress/icons": "15.1.0",
"@wordpress/route": "0.17.0",
"@wordpress/theme": "0.17.0",
"@wordpress/ui": "0.17.0"
"@wordpress/ui": "0.17.0",
"@wordpress/url": "4.51.0"
},
"route": {
"path": "/",
Expand Down
5 changes: 4 additions & 1 deletion projects/packages/backup/routes/download/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@
"private": true,
"dependencies": {
"@automattic/jetpack-components": "workspace:*",
"@tanstack/react-query": "5.101.2",
"@types/react": "18.3.28",
"@wordpress/admin-ui": "2.5.0",
"@wordpress/api-fetch": "7.51.0",
"@wordpress/components": "37.0.0",
"@wordpress/date": "5.51.0",
"@wordpress/element": "8.3.0",
"@wordpress/i18n": "6.24.0",
"@wordpress/icons": "15.1.0",
"@wordpress/route": "0.17.0",
"@wordpress/theme": "0.17.0",
"@wordpress/ui": "0.17.0"
"@wordpress/ui": "0.17.0",
"@wordpress/url": "4.51.0"
},
"route": {
"path": "/download/$rewindId",
Expand Down
5 changes: 4 additions & 1 deletion projects/packages/backup/routes/restore/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@
"private": true,
"dependencies": {
"@automattic/jetpack-components": "workspace:*",
"@tanstack/react-query": "5.101.2",
"@types/react": "18.3.28",
"@wordpress/admin-ui": "2.5.0",
"@wordpress/api-fetch": "7.51.0",
"@wordpress/components": "37.0.0",
"@wordpress/date": "5.51.0",
"@wordpress/element": "8.3.0",
"@wordpress/i18n": "6.24.0",
"@wordpress/icons": "15.1.0",
"@wordpress/route": "0.17.0",
"@wordpress/theme": "0.17.0",
"@wordpress/ui": "0.17.0"
"@wordpress/ui": "0.17.0",
"@wordpress/url": "4.51.0"
},
"route": {
"path": "/restore/$rewindId",
Expand Down
24 changes: 23 additions & 1 deletion projects/packages/backup/src/class-jetpack-backup.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ public static function initialize() {
Connection_Rest_Authentication::init();

add_action( 'rest_api_init', array( __CLASS__, 'register_rest_routes' ) );
add_action( 'rest_api_init', array( \Automattic\Jetpack\Backup\V0005\REST\Rest_Controller::class, 'register_routes' ) );

add_action( 'admin_menu', array( __CLASS__, 'maybe_load_wp_build' ), 1 );
add_action( 'admin_menu', array( __CLASS__, 'add_wp_admin_submenu' ), 1 ); // Akismet uses 4, so we need to use 1 to ensure both menus are added when only they exist.
Expand Down Expand Up @@ -969,6 +970,27 @@ public static function maybe_load_wp_build() {
}

add_action( 'current_screen', array( __CLASS__, 'alias_screen_id_for_wp_build' ) );
add_action( 'admin_print_scripts', array( __CLASS__, 'render_connection_initial_state' ), 1 );
}

/**
* Emit `window.JP_CONNECTION_INITIAL_STATE` inline on the modernized
* Backup admin page.
*
* The modernized enqueue path short-circuits before the legacy
* `Connection_Initial_State::render_script()` call, so without this
* the React `<Gates>` component never sees the connection state and
* sits on its loading skeleton forever. We emit the same JS payload
* the legacy path emits, just outside of a registered script handle
* (wp-build's handles aren't reliable here, and the global is
* page-scoped — any tag setting it works).
*
* @return void
*/
public static function render_connection_initial_state() {
echo '<script id="jetpack-backup-connection-initial-state">'
. Connection_Initial_State::render() // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- render() returns pre-escaped JSON.
. '</script>';
}

/**
Expand Down Expand Up @@ -1024,7 +1046,7 @@ public static function alias_screen_id_for_wp_build( $screen ) {
*
* @return bool
*/
private static function is_modernized() {
public static function is_modernized() {
return (bool) apply_filters( self::MODERNIZATION_FILTER, false );
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { DataViews } from '@wordpress/dataviews';
import { dateI18n } from '@wordpress/date';
import { useCallback, useMemo, useState } from '@wordpress/element';
import { useCallback, useMemo } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { Icon, cloud, image, post, plugins as pluginsIcon, color } from '@wordpress/icons';
import { Card, Stack, Text } from '@wordpress/ui';
import { useMockActivityLog } from '../../hooks/use-mock-activity-log';
import { ACTIVITY_LOG_DEFAULT_PER_PAGE, useActivityLog } from '../../hooks/use-activity-log';
import { isBackupItem } from '../../types/activity';
import './style.scss';
import type { ActivityItem, ActivityKind } from '../../types/activity';
Expand All @@ -21,6 +21,8 @@ const ICON_BY_KIND: Record< ActivityKind, typeof cloud > = {
type Props = {
selectedId: string | null;
onSelect: ( id: string ) => void;
view: View;
onChangeView: ( next: View ) => void;
};

/**
Expand Down Expand Up @@ -76,56 +78,31 @@ function DescriptionCell( { item }: { item: ActivityItem } ) {
);
}

const DEFAULT_PER_PAGE = 10;

/**
* Left pane of the modernized Overview, rendered as a DataViews list.
*
* DataViews gives us the cog/filter affordance, pagination controls,
* zebra striping, row separators, and search box for free — the legacy
* affordances `<ActivityList>` was hand-rolling get swapped out for the
* zebra striping, and row separators for free — the legacy affordances
* `<ActivityList>` was hand-rolling get swapped out for the
* design-system primitive.
*
* Selection lives in the parent (URL-persisted via `?selected=<id>`),
* with `<DataViews>` driven through its `selection` + `onChangeSelection`
* props so the row click is the single source of truth for "which row
* is highlighted".
* Controlled: view state (page, perPage) lives in the parent so the
* right-pane row lookup can subscribe to the same paginated query the
* list reads from. Selection is similarly URL-persisted in the parent.
*
* @param props - Component props.
* @param props.selectedId - Currently selected row id, or null when nothing is selected.
* @param props.onSelect - Callback invoked with the new selection id when a row is activated.
* @param props - Component props.
* @param props.selectedId - Currently selected row id, or null when nothing is selected.
* @param props.onSelect - Callback invoked with the new selection id when a row is activated.
* @param props.view - DataViews view state.
* @param props.onChangeView - Callback invoked when the view state changes.
* @return The rendered list.
*/
export default function ActivityList( { selectedId, onSelect }: Props ) {
// In DataViews' list layout, the `titleField`, `mediaField` and
// `descriptionField` fields are rendered implicitly — anything else
// in `fields` would render a second time as a generic row. Leave
// `fields` empty so the title + media + description are the only
// things shown per row.
const [ view, setView ] = useState< View >( {
type: 'list',
page: 1,
perPage: DEFAULT_PER_PAGE,
search: '',
filters: [],
titleField: 'title',
mediaField: 'icon',
descriptionField: 'description',
fields: [],
} );

// Drive the hook with DataViews' own view state so the hook is the
// single source of truth for the visible slice + counts. The mock
// hook's contract matches the real `useActivityLog` from the
// data-layer follow-up, so swapping the import line later is the
// only change required here.
export default function ActivityList( { selectedId, onSelect, view, onChangeView }: Props ) {
const page = view.page ?? 1;
const perPage = view.perPage ?? DEFAULT_PER_PAGE;
const search = view.search ?? '';
const { items, totalItems, totalPages, isLoading } = useMockActivityLog( {
const perPage = view.perPage ?? ACTIVITY_LOG_DEFAULT_PER_PAGE;
const { items, totalItems, totalPages, isLoading } = useActivityLog( {
page,
pageSize: perPage,
search,
} );

const fields: Field< ActivityItem >[] = useMemo(
Expand Down Expand Up @@ -182,15 +159,14 @@ export default function ActivityList( { selectedId, onSelect }: Props ) {
data={ items }
fields={ fields }
view={ view }
onChangeView={ setView }
onChangeView={ onChangeView }
paginationInfo={ { totalItems, totalPages } }
defaultLayouts={ { list: {} } }
getItemId={ getRowId }
selection={ selection }
onChangeSelection={ onChangeSelection }
isLoading={ isLoading }
search={ true }
searchLabel={ __( 'Search backups', 'jetpack-backup-pkg' ) }
search={ false }
/>
</Card.Root>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import JetpackFooter from '@automattic/jetpack-components/jetpack-footer';
import JetpackLogo from '@automattic/jetpack-components/jetpack-logo';
import { Page } from '@wordpress/admin-ui';
import { __ } from '@wordpress/i18n';
import DevModeBanner from '../dev-mode-banner';
import QueryClientProvider from '../../providers/query-client-provider';
import Gates from '../gates';
import './style.scss';
import type { ReactNode } from 'react';

Expand All @@ -17,9 +18,9 @@ const PRODUCT_NAME = 'VaultPress Backup'; // Product name; do not translate.
* Shared shell for every screen of the modernized Backup dashboard.
*
* Wraps a `<Page>` from `@wordpress/admin-ui` (the standard wp-admin
* chrome) with a Jetpack logo in the `visual` slot, the dev-mode banner,
* the page body, and `<JetpackFooter>` at the bottom — matching every
* other modernized Jetpack dashboard (Newsletter, VideoPress, Forms).
* chrome) with a Jetpack logo in the `visual` slot, the page body, and
* `<JetpackFooter>` at the bottom — matching every other modernized
* Jetpack dashboard (Newsletter, VideoPress, Forms).
*
* The page is laid out so the body grows to fill the viewport and the
* footer stays parked at the bottom when content is short, but scrolls
Expand All @@ -44,11 +45,14 @@ export default function DashboardLayout( { children, actions }: Props ) {
hasPadding={ false }
actions={ actions }
>
<DevModeBanner />
<div className="jpb-dashboard-body">
<div className="jpb-dashboard-body__inner">{ children }</div>
</div>
<JetpackFooter />
<QueryClientProvider>
<div className="jpb-dashboard-body">
<div className="jpb-dashboard-body__inner">
<Gates>{ children }</Gates>
</div>
</div>
<JetpackFooter />
</QueryClientProvider>
</Page>
);
}

This file was deleted.

This file was deleted.

Loading
Loading