Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Core Data: Support entity queries in the 'useResourcePermissions' hook #63653

Merged
merged 8 commits into from
Jul 24, 2024
Merged
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
2 changes: 2 additions & 0 deletions package-lock.json

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

22 changes: 8 additions & 14 deletions packages/block-library/src/navigation-link/link-ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,15 +147,18 @@ function LinkUIBlockInserter( { clientId, onBack, onSelectBlock } ) {
}

function UnforwardedLinkUI( props, ref ) {
const { label, url, opensInNewTab, type, kind } = props.link;
const postType = type || 'page';

const [ addingBlock, setAddingBlock ] = useState( false );
const [ focusAddBlockButton, setFocusAddBlockButton ] = useState( false );
const { saveEntityRecord } = useDispatch( coreStore );
const pagesPermissions = useResourcePermissions( 'pages' );
const postsPermissions = useResourcePermissions( 'posts' );
const permissions = useResourcePermissions( {
kind: 'postType',
name: postType,
} );
Comment on lines +156 to +159
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removes unnecessary checks for both entities, which later might have been disregarded. Reduces the possible OPTIONS request to one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice one 👍 I appreciate the resulting cleanup!


async function handleCreate( pageTitle ) {
const postType = props.link.type || 'page';

const page = await saveEntityRecord( 'postType', postType, {
title: pageTitle,
status: 'draft',
Expand All @@ -180,15 +183,6 @@ function UnforwardedLinkUI( props, ref ) {
};
}

const { label, url, opensInNewTab, type, kind } = props.link;

let userCanCreate = false;
if ( ! type || type === 'page' ) {
userCanCreate = pagesPermissions.canCreate;
} else if ( type === 'post' ) {
userCanCreate = postsPermissions.canCreate;
}

// Memoize link value to avoid overriding the LinkControl's internal state.
// This is a temporary fix. See https://github.com/WordPress/gutenberg/issues/50976#issuecomment-1568226407.
const link = useMemo(
Expand Down Expand Up @@ -241,7 +235,7 @@ function UnforwardedLinkUI( props, ref ) {
hasRichPreviews
value={ link }
showInitialSuggestions
withCreateSuggestion={ userCanCreate }
withCreateSuggestion={ permissions.canCreate }
createSuggestion={ handleCreate }
createSuggestionButtonText={ ( searchTerm ) => {
let format;
Expand Down
69 changes: 54 additions & 15 deletions packages/block-library/src/navigation/test/use-navigation-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import { store as coreStore } from '@wordpress/core-data';
*/
import useNavigationMenu from '../use-navigation-menu';

const BASE_ENTITY = {
kind: 'postType',
name: 'wp_navigation',
id: undefined,
};

function createRegistryWithStores() {
// Create a registry and register used stores.
const registry = createRegistry();
Expand Down Expand Up @@ -63,37 +69,70 @@ function resolveRecords( registry, menus ) {

function resolveReadPermission( registry, allowed ) {
const dispatch = registry.dispatch( coreStore );
dispatch.receiveUserPermission( 'create/navigation', allowed );
dispatch.startResolution( 'canUser', [ 'read', 'navigation' ] );
dispatch.finishResolution( 'canUser', [ 'read', 'navigation' ] );
dispatch.receiveUserPermission( 'read/postType/wp_navigation', allowed );
dispatch.startResolution( 'canUser', [ 'read', BASE_ENTITY ] );
dispatch.finishResolution( 'canUser', [ 'read', BASE_ENTITY ] );
}

function resolveReadRecordPermission( registry, ref, allowed ) {
const dispatch = registry.dispatch( coreStore );
dispatch.receiveUserPermission( 'create/navigation', allowed );
dispatch.startResolution( 'canUser', [ 'read', 'navigation', ref ] );
dispatch.finishResolution( 'canUser', [ 'read', 'navigation', ref ] );
dispatch.receiveUserPermission(
`read/postType/wp_navigation/${ ref }`,
allowed
);
dispatch.startResolution( 'canUser', [
'read',
{ ...BASE_ENTITY, id: ref },
] );
dispatch.finishResolution( 'canUser', [
'read',
{ ...BASE_ENTITY, id: ref },
] );
}

function resolveCreatePermission( registry, allowed ) {
const dispatch = registry.dispatch( coreStore );
dispatch.receiveUserPermission( 'create/navigation', allowed );
dispatch.startResolution( 'canUser', [ 'create', 'navigation' ] );
dispatch.finishResolution( 'canUser', [ 'create', 'navigation' ] );
dispatch.receiveUserPermission( 'create/postType/wp_navigation', allowed );
dispatch.startResolution( 'canUser', [
'create',
{ kind: 'postType', name: 'wp_navigation' },
] );
dispatch.finishResolution( 'canUser', [
'create',
{ kind: 'postType', name: 'wp_navigation' },
] );
}

function resolveUpdatePermission( registry, ref, allowed ) {
const dispatch = registry.dispatch( coreStore );
dispatch.receiveUserPermission( `update/navigation/${ ref }`, allowed );
dispatch.startResolution( 'canUser', [ 'update', 'navigation', ref ] );
dispatch.finishResolution( 'canUser', [ 'update', 'navigation', ref ] );
dispatch.receiveUserPermission(
`update/postType/wp_navigation/${ ref }`,
allowed
);
dispatch.startResolution( 'canUser', [
'update',
{ ...BASE_ENTITY, id: ref },
] );
dispatch.finishResolution( 'canUser', [
'update',
{ ...BASE_ENTITY, id: ref },
] );
}

function resolveDeletePermission( registry, ref, allowed ) {
const dispatch = registry.dispatch( coreStore );
dispatch.receiveUserPermission( `delete/navigation/${ ref }`, allowed );
dispatch.startResolution( 'canUser', [ 'delete', 'navigation', ref ] );
dispatch.finishResolution( 'canUser', [ 'delete', 'navigation', ref ] );
dispatch.receiveUserPermission(
`delete/postType/wp_navigation/${ ref }`,
allowed
);
dispatch.startResolution( 'canUser', [
'delete',
{ ...BASE_ENTITY, id: ref },
] );
dispatch.finishResolution( 'canUser', [
'delete',
{ ...BASE_ENTITY, id: ref },
] );
}

describe( 'useNavigationMenus', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import { useSelect } from '@wordpress/data';
import { PRELOADED_NAVIGATION_MENUS_QUERY } from './constants';

export default function useNavigationMenu( ref ) {
const permissions = useResourcePermissions( 'navigation', ref );
const permissions = useResourcePermissions( {
kind: 'postType',
name: 'wp_navigation',
id: ref,
} );

const {
navigationMenu,
Expand Down
15 changes: 11 additions & 4 deletions packages/core-data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1173,7 +1173,10 @@ _Usage_
import { useResourcePermissions } from '@wordpress/core-data';

function PagesList() {
const { canCreate, isResolving } = useResourcePermissions( 'pages' );
const { canCreate, isResolving } = useResourcePermissions( {
kind: 'postType',
name: 'page',
} );

if ( isResolving ) {
return 'Loading ...';
Expand All @@ -1196,7 +1199,11 @@ import { useResourcePermissions } from '@wordpress/core-data';

function Page( { pageId } ) {
const { canCreate, canUpdate, canDelete, isResolving } =
useResourcePermissions( 'pages', pageId );
useResourcePermissions( {
kind: 'postType',
name: 'page',
id: pageId,
} );

if ( isResolving ) {
return 'Loading ...';
Expand All @@ -1222,8 +1229,8 @@ the store state using `canUser()`, or resolved if missing.

_Parameters_

- _resource_ `string`: The resource in question, e.g. media.
- _id_ `IdType`: ID of a specific resource entry, if needed, e.g. 10.
- _resource_ `string | EntityResource`: Entity resource to check. Accepts entity object `{ kind: 'root', name: 'media', id: 1 }` or REST base as a string - `media`.
- _id_ `IdType`: Optional ID of the resource to check, e.g. 10. Note: This argument is discouraged when using an entity object as a resource to check permissions and will be ignored.

_Returns_

Expand Down
1 change: 1 addition & 0 deletions packages/core-data/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"@wordpress/sync": "file:../sync",
"@wordpress/undo-manager": "file:../undo-manager",
"@wordpress/url": "file:../url",
"@wordpress/warning": "file:../warning",
"change-case": "^4.1.2",
"equivalent-key-map": "^0.2.2",
"fast-deep-equal": "^3.1.3",
Expand Down
93 changes: 93 additions & 0 deletions packages/core-data/src/hooks/test/use-resource-permissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,97 @@ describe( 'useResourcePermissions', () => {
} )
);
} );

it( 'retrieves the relevant permissions for a id-less entity', async () => {
let data;
const TestComponent = () => {
data = useResourcePermissions( {
kind: 'root',
name: 'media',
} );
return <div />;
};
render(
<RegistryProvider value={ registry }>
<TestComponent />
</RegistryProvider>
);
expect( data ).toEqual( {
status: 'IDLE',
isResolving: false,
hasResolved: false,
canCreate: false,
canRead: false,
} );

await waitFor( () =>
expect( data ).toEqual( {
status: 'SUCCESS',
isResolving: false,
hasResolved: true,
canCreate: true,
canRead: false,
} )
);
} );

it( 'retrieves the relevant permissions for an entity', async () => {
let data;
const TestComponent = () => {
data = useResourcePermissions( {
kind: 'root',
name: 'media',
id: 1,
} );
return <div />;
};
render(
<RegistryProvider value={ registry }>
<TestComponent />
</RegistryProvider>
);
expect( data ).toEqual( {
status: 'IDLE',
isResolving: false,
hasResolved: false,
canCreate: false,
canRead: false,
canUpdate: false,
canDelete: false,
} );

await waitFor( () =>
expect( data ).toEqual( {
status: 'SUCCESS',
isResolving: false,
hasResolved: true,
canCreate: true,
canRead: false,
canUpdate: false,
canDelete: false,
} )
);
} );

it( 'should warn when called with incorrect arguments signature', () => {
const TestComponent = () => {
useResourcePermissions(
{
kind: 'root',
name: 'media',
},
1
);
return null;
};
render(
<RegistryProvider value={ registry }>
<TestComponent />
</RegistryProvider>
);

expect( console ).toHaveWarnedWith(
`When 'resource' is an entity object, passing 'id' as a separate argument isn't supported.`
);
} );
} );
Loading
Loading