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

Remove permissions prop injection from main views #6921

Merged
merged 5 commits into from
Dec 6, 2021
Merged
Show file tree
Hide file tree
Changes from 2 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
54 changes: 29 additions & 25 deletions examples/simple/src/users/UserCreate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
TextInput,
Toolbar,
required,
usePermissions,
} from 'react-admin';

import Aside from './Aside';
Expand Down Expand Up @@ -38,32 +39,35 @@ const isValidName = async value =>
)
);

const UserCreate = ({ permissions, ...props }) => (
<Create {...props} aside={<Aside />}>
<TabbedForm toolbar={<UserEditToolbar permissions={permissions} />}>
<FormTab label="user.form.summary" path="">
<TextInput
source="name"
defaultValue="Slim Shady"
autoFocus
validate={[required(), isValidName]}
/>
</FormTab>
{permissions === 'admin' && (
<FormTab label="user.form.security" path="security">
<AutocompleteInput
source="role"
choices={[
{ id: '', name: 'None' },
{ id: 'admin', name: 'Admin' },
{ id: 'user', name: 'User' },
{ id: 'user_simple', name: 'UserSimple' },
]}
const UserCreate = () => {
const { permissions } = usePermissions();
return (
<Create aside={<Aside />}>
<TabbedForm toolbar={<UserEditToolbar permissions={permissions} />}>
<FormTab label="user.form.summary" path="">
<TextInput
source="name"
defaultValue="Slim Shady"
autoFocus
validate={[required(), isValidName]}
/>
</FormTab>
)}
</TabbedForm>
</Create>
);
{permissions === 'admin' && (
<FormTab label="user.form.security" path="security">
<AutocompleteInput
source="role"
choices={[
{ id: '', name: 'None' },
{ id: 'admin', name: 'Admin' },
{ id: 'user', name: 'User' },
{ id: 'user_simple', name: 'UserSimple' },
]}
/>
</FormTab>
)}
</TabbedForm>
</Create>
);
};

export default UserCreate;
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"eslint-plugin-jsx-a11y": "^6.3.1",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-react": "^7.20.6",
"eslint-plugin-react-hooks": "^4.1.0",
"eslint-plugin-react-hooks": "^4.3.0",
"express": "~4.16.3",
"full-icu": "~1.3.1",
"husky": "^2.3.0",
Expand Down
4 changes: 2 additions & 2 deletions packages/ra-core/src/auth/Authenticated.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { cloneElement, ReactElement } from 'react';

import useAuthenticated from './useAuthenticated';
import { useAuthenticated } from './useAuthenticated';

export interface AuthenticatedProps {
children: ReactElement<any>;
Expand Down Expand Up @@ -43,7 +43,7 @@ const Authenticated = (props: AuthenticatedProps) => {
location, // kept for backwards compatibility, unused
...rest
} = props;
useAuthenticated(authParams);
useAuthenticated({ params: authParams });
// render the child even though the useAuthenticated() call isn't finished (optimistic rendering)
// the above hook will log out if the authProvider doesn't validate that the user is authenticated
return cloneElement(children, rest);
Expand Down
2 changes: 1 addition & 1 deletion packages/ra-core/src/auth/WithPermissions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Children, ReactElement, ComponentType, createElement } from 'react';
import { Location } from 'history';

import warning from '../util/warning';
import useAuthenticated from './useAuthenticated';
import { useAuthenticated } from './useAuthenticated';
import usePermissionsOptimized from './usePermissionsOptimized';

export interface WithPermissionsChildrenParams {
Expand Down
7 changes: 3 additions & 4 deletions packages/ra-core/src/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ import useAuthProvider from './useAuthProvider';
import useAuthState from './useAuthState';
import usePermissions from './usePermissions';
import usePermissionsOptimized from './usePermissionsOptimized';
import useAuthenticated from './useAuthenticated';
import WithPermissions, { WithPermissionsProps } from './WithPermissions';
import useLogin from './useLogin';
import useLogout from './useLogout';
import useCheckAuth from './useCheckAuth';
import useGetIdentity from './useGetIdentity';
import useGetPermissions from './useGetPermissions';
import useLogoutIfAccessDenied from './useLogoutIfAccessDenied';
import convertLegacyAuthProvider from './convertLegacyAuthProvider';

export * from './types';
export * from './useAuthenticated';
export * from './useCheckAuth';

export {
AuthContext,
Expand All @@ -22,15 +23,13 @@ export {
// low-level hooks for calling a particular verb on the authProvider
useLogin,
useLogout,
useCheckAuth,
useGetIdentity,
useGetPermissions,
// hooks with state management
usePermissions,
usePermissionsOptimized,
useAuthState,
// hook with immediate effect
useAuthenticated,
useLogoutIfAccessDenied,
// components
Authenticated,
Expand Down
2 changes: 1 addition & 1 deletion packages/ra-core/src/auth/useAuthState.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect } from 'react';

import useCheckAuth from './useCheckAuth';
import { useCheckAuth } from './useCheckAuth';
import { useSafeSetState } from '../util/hooks';

interface State {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { useEffect } from 'react';
import useCheckAuth from './useCheckAuth';

const emptyParams = {};
import { useCheckAuth } from './useCheckAuth';

/**
* Restrict access to authenticated users.
Expand All @@ -28,9 +26,21 @@ const emptyParams = {};
* </Admin>
* );
*/
export default (params: any = emptyParams) => {
export const useAuthenticated = <ParamsType = any>(
Copy link
Member

Choose a reason for hiding this comment

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

this is a breaking change for users who used to pass params, and should be mentioned in the upgrade guide

options: UseAuthenticatedOptions<ParamsType> = {}
) => {
const { enabled = true, params = emptyParams } = options;
const checkAuth = useCheckAuth();
useEffect(() => {
checkAuth(params).catch(() => {});
}, [checkAuth, params]);
if (enabled) {
checkAuth(params).catch(() => {});
}
}, [checkAuth, enabled, params]);
};

export type UseAuthenticatedOptions<ParamsType> = {
enabled?: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be great to have documentation of the new enabled prop

params?: ParamsType;
};

const emptyParams = {};
2 changes: 1 addition & 1 deletion packages/ra-core/src/auth/useCheckAuth.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useState, useEffect } from 'react';
import expect from 'expect';
import { render, waitFor } from '@testing-library/react';

import useCheckAuth from './useCheckAuth';
import { useCheckAuth } from './useCheckAuth';
import AuthContext from './AuthContext';
import useLogout from './useLogout';
import useNotify from '../sideEffect/useNotify';
Expand Down
4 changes: 1 addition & 3 deletions packages/ra-core/src/auth/useCheckAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import useNotify from '../sideEffect/useNotify';
* return authenticated ? <Bar /> : <BarNotAuthenticated />;
* } // tip: use useAuthState() hook instead
*/
const useCheckAuth = (): CheckAuth => {
export const useCheckAuth = (): CheckAuth => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing export for type CheckAuth

const authProvider = useAuthProvider();
const notify = useNotify();
const logout = useLogout();
Expand Down Expand Up @@ -104,5 +104,3 @@ const getErrorMessage = (error, defaultMessage) =>
: typeof error === 'undefined' || !error.message
? defaultMessage
: error.message;

export default useCheckAuth;
14 changes: 1 addition & 13 deletions packages/ra-core/src/controller/create/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,6 @@
import {
CreateControllerResult,
useCreateController,
} from './useCreateController';

export * from './CreateBase';
export * from './CreateContext';
export * from './CreateContextProvider';
export * from './CreateController';

export * from './useCreateContext';
export * from '../show/useShowContext';

// We don't want to export CreateProps and EditProps as they should
// not be used outside ra-core, since it would conflict with ra-ui-materialui types,
// hence the named imports/exports
export type { CreateControllerResult as CreateControllerProps };
export { useCreateController };
export * from './useCreateController';
47 changes: 18 additions & 29 deletions packages/ra-core/src/controller/create/useCreateController.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,49 @@
import React from 'react';
import expect from 'expect';
import { act } from '@testing-library/react';
import { Location } from 'history';

import { getRecord } from './useCreateController';
import { getRecordFromLocation } from './useCreateController';
import { CreateController } from './CreateController';
import { renderWithRedux } from 'ra-test';
import { DataProviderContext } from '../../dataProvider';
import { DataProvider } from '../../types';

describe('useCreateController', () => {
describe('getRecord', () => {
const location = {
describe('getRecordFromLocation', () => {
const location: Location = {
key: 'a_key',
pathname: '/foo',
search: undefined,
state: undefined,
hash: undefined,
};

it('should return an empty record by default', () => {
Copy link
Member

Choose a reason for hiding this comment

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

why did you remove this one ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Because that's not the responsability of this function anymore

expect(getRecord(location, undefined)).toEqual({});
});

it('should return location state record when set', () => {
expect(
getRecord(
{
...location,
state: { record: { foo: 'bar' } },
},
undefined
)
getRecordFromLocation({
...location,
state: { record: { foo: 'bar' } },
})
).toEqual({ foo: 'bar' });
});

it('should return location search when set', () => {
expect(
getRecord(
{
...location,
search: '?source={"foo":"baz","array":["1","2"]}',
},
undefined
)
getRecordFromLocation({
...location,
search: '?source={"foo":"baz","array":["1","2"]}',
})
).toEqual({ foo: 'baz', array: ['1', '2'] });
});

it('should return location state record when both state and search are set', () => {
expect(
getRecord(
{
...location,
state: { record: { foo: 'bar' } },
search: '?foo=baz',
},
undefined
)
getRecordFromLocation({
...location,
state: { record: { foo: 'bar' } },
search: '?foo=baz',
})
).toEqual({ foo: 'bar' });
});
});
Expand Down
23 changes: 17 additions & 6 deletions packages/ra-core/src/controller/create/useCreateController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { useCallback, MutableRefObject } from 'react';
// @ts-ignore
import { parse } from 'query-string';
import { useLocation } from 'react-router-dom';
import { Location } from 'history';

import { useAuthenticated } from '../../auth';
import { useCreate } from '../../dataProvider';
import {
useNotify,
Expand Down Expand Up @@ -49,20 +51,23 @@ export const useCreateController = <
props: CreateControllerProps = {}
): CreateControllerResult<RecordType> => {
const {
record = {},
successMessage,
disableAuthentication,
Copy link
Member

Choose a reason for hiding this comment

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

You should update the Auth doc to mention this prop. Same for other controllers.

onSuccess,
onFailure,
record,
successMessage,
transform,
} = props;

useAuthenticated({ enabled: !disableAuthentication });
const resource = useResourceContext(props);
const { hasEdit, hasShow } = useResourceDefinition(props);
const location = useLocation();
const translate = useTranslate();
const notify = useNotify();
const redirect = useRedirect();
const recordToUse = getRecord(location, record);
const recordToUse =
record ?? getRecordFromLocation(location) ?? emptyRecord;
const version = useVersion();

if (process.env.NODE_ENV !== 'production' && successMessage) {
Expand Down Expand Up @@ -190,12 +195,13 @@ export const useCreateController = <
export interface CreateControllerProps<
RecordType extends Omit<Record, 'id'> = Record
> {
disableAuthentication?: boolean;
record?: Partial<RecordType>;
resource?: string;
onSuccess?: OnSuccess;
onFailure?: OnFailure;
transform?: TransformData;
successMessage?: string;
transform?: TransformData;
}

export interface CreateControllerResult<
Expand Down Expand Up @@ -230,7 +236,12 @@ export interface CreateControllerResult<
version: number;
}

export const getRecord = ({ state, search }, record: any = {}) => {
const emptyRecord = {};
/**
* Get the initial record from the location, whether it comes from the location
* state or is serialized in the url search part.
*/
export const getRecordFromLocation = ({ state, search }: Location) => {
if (state && state.record) {
return state.record;
}
Expand All @@ -252,7 +263,7 @@ export const getRecord = ({ state, search }, record: any = {}) => {
);
}
}
return record;
return null;
};

const getDefaultRedirectRoute = (hasShow, hasEdit) => {
Expand Down
Loading