React Admin Enterprise Edition - September 2021 Update

Gildas Garcia
Gildas GarciaSeptember 22, 2021
#react#react-admin

The Enterprise Edition of react-admin speeds up the development of complex admin apps by offering 14 additional modules, ranging from editable datagrid to tree structure handling. In the past 6 months, we've added new functionality to existing modules, in reaction to feedback from our customers. We've also added 2 entirely new modules! Here is a summary of the most important changes of the past 6 months.

First, let's introduce the 2 new modules:

  • ra-audit-log: Keep track of user actions, and get an overview of the activity of your admin.
  • ra-rbac: Manage roles and fine-grained permissions.

Next: check out these exciting new features added to existing modules:

  • TypeScript and JavaScript Examples! All packages documentation now have examples in both JavaScript and TypeScript! Check it out on the ra-enterprise site
  • ra-preferences: no-code components. Give your users superpowers by allowing them to deeply customize their admin! We also added the ability to synchronize the preferences with your backend.
  • ra-tree - Lazy Load Support!
  • ra-calendar: Custom side effects for all operations.
  • ra-editable-datagrid: Custom side effects for all operations.
  • ra-tour: Translation support for the tour steps content.
  • ra-navigation: Translation support for breadcrumb items labels.
  • ra-realtime: Allows to customize side effects when a list event is received

Now, let's go into the details.

ra-audit-log: Track Changes on Every Resource

ra-audit-log is a new module that allows tracking every action made by an admin. It's a great way to get an overview of the activity of your users.

ra-audit-log demo

It supports two scenarios:

  1. Your backend doesn't support auditing. In that case, ra-audit-log tracks changes client-side and sends them to your API via the dataProvider.
  2. Your backend already keeps track of the changes. In this case, ra-audit-log provides components to display those changes in a user-friendly way.

In the first case, adding auditing features to your admin can be as easy as wrapping your dataProvider with the addEventsForMutations function:

// in dataProvider.js
import { addEventsForMutations } from '@react-admin/ra-audit-log';
import simpleRestProvider from 'ra-data-simple-rest';
import authProvider from './authProvider';

const dataProvider = addEventsForMutations(
    simpleRestProvider('http://path.to.my.api/'),
    authProvider,
);

// in authProvider.js
const authProvider = {
    // login: () => {},
    // logout: () => {},
    // You must provide the getIdentity function
    getIdentity: () =>
        Promise.resolve({
            id: '1',
            fullName: 'test',
            // Avatar is optional
            avatar: 'https://myavatar.com',
        }),
};

// in App.js
import { Admin, Resource } from 'react-admin';
import dataProvider from './dataProvider';

export const App = () => (
    <Admin dataProvider={dataProvider}>
        {/* your Resources go here */}
        <Resource name="events" />
    </Admin>
);

In this case, each mutation (created, update, delete) performed by a user will trigger a call to dataProvider.create('events', [event details]). This is of course very configurable and you can choose which resources are tracked, and for which mutations.

Whether you opted for client-side tracking or you already have this kind of logs in your backend, ra-audit-log provides components to display events in a user-friendly way. For instance, the <Timeline> is a great way to display the changes made by users:

Timeline component showing a list of logs

Besides, we even provide a specialized <RecordTimeline> component that also fetches the events for a given record. This is well suited for asides:

Timeline component showing a list of logs of a specific record

Finally, ra-audit-log provides the <EventList> component. A full-featured <List> for events, pre-configured with a <Datagrid> and a filter sidebar.

Timeline component showing a list of logs of a specific record

All these components are configurable and you can find their documentation in the ra-audit-log package page.

ra-rbac: Manage Roles and Fine-grained Permissions

When building large admin applications, you often needed to manage roles and fine-grained permissions. ra-rbac provides a way to do that.

ra-rbac demo

You can define permissions for pages, fields, buttons, etc. Roles and permissions are managed by the authProvider, which means you can use any data source you want (including an ActiveDirectory server).

The above demo uses the following set of permissions:

const roles = {
    accountant: [
        { action: ['list', 'show'], resource: 'products' },
        { action: 'read', resource: 'products.*' },
        { type: 'deny', action: 'read', resource: 'products.description' },
        { action: 'list', resource: 'categories' },
        { action: 'read', resource: 'categories.*' },
        { action: ['list', 'show'], resource: 'customers' },
        { action: 'read', resource: 'customers.*' },
        { action: '*', resource: 'invoices' },
    ],
    contentEditor: [
        {
            action: ['list', 'create', 'edit', 'delete', 'export'],
            resource: 'products',
        },
        { action: 'read', resource: 'products.*' },
        { type: 'deny', action: 'read', resource: 'products.stock' },
        { type: 'deny', action: 'read', resource: 'products.sales' },
        { action: 'write', resource: 'products.*' },
        { type: 'deny', action: 'write', resource: 'products.stock' },
        { type: 'deny', action: 'write', resource: 'products.sales' },
        { action: 'list', resource: 'categories' },
        { action: ['list', 'edit'], resource: 'customers' },
        { action: ['list', 'edit'], resource: 'reviews' },
    ],
    stockManager: [
        { action: ['list', 'edit', 'export'], resource: 'products' },
        { action: 'read', resource: 'products.*' },
        {
            type: 'deny',
            action: 'read',
            resource: 'products.description',
        },
        { action: 'write', resource: 'products.stock' },
        { action: 'write', resource: 'products.sales' },
        { action: 'list', resource: 'categories' },
    ],
    administrator: [{ action: '*', resource: '*' }],
};

There are a few key differences between React-admin and ra-rbac regarding the permissions:

  • Pessimistic Strategy: while React-admin treats permissions in an optimistic way (rendering everything until it fetches the permissions), ra-rbac has a pessimistic approach. While permissions are loading, ra-rbac won't render the components that require permissions, assuming that these components are restricted by default.
  • Principle Of Least Privilege: A user with no permissions has access to nothing. By default, any restricted action is accessible to nobody. Permissions are additive, each permission granting access to a subset of the application.
  • Roles and Permissions: ra-rbac lets you define fine-grained permissions for an action on a resource. You can also define groups of permissions, which we call roles, for easier user management.

To facilitate its integration into React-admin, ra-rbac provides its own version of some components:

  • <WithPermissions>: renders its child only if the user has the right permissions (a combination of a resource and an action):
import { WithPermissions } from '@react-admin/ra-rbac';

const RecordToolbar = ({ resource }) => (
    <Toolbar>
        <WithPermissions action="edit" resource={resource}>
            <EditButton />
        </WithPermissions>
        <WithPermissions action="show" resource={resource}>
            <ShowButton />
        </WithPermissions>
        <WithPermissions action="delete" resource={resource}>
            <DeleteButton />
        </WithPermissions>
    </Toolbar>
);
  • <Resource>: automatically restrict access to the views based on the user's permissions.
  • View components such as <List>, <Show>, <Edit> and <Create>: add RBAC control to actions and bulk actions.
import { List } from '@react-admin/ra-rbac';

const authProvider = {
    // ...
    getPermissions: () =>
        Promise.resolve({
            permissions: [
                { action: 'list', resource: 'products' },
                { action: 'create', resource: 'products' },
                { action: 'delete', resource: 'products' },
                // action 'export' is missing
            ],
        }),
};

export const PostList = props => <List {...props}>...</List>;
// user will see the following actions on top of the list:
// - create
// user will see the following bulk actions upon selection:
// - delete
  • UI components such as <Datagrid>, <SimpleShowLayout>, <SimpleForm> and many more that facilitate permissions-based selection of the fields to display.
import { List, DatagridProps } from '@react-admin/ra-rbac';
import { Datagrid } from '@react-admin/ra-rbac';

const authProvider = {
    // ...
    getPermissions: () =>
        Promise.resolve({
            permissions: [
                { action: 'list', resource: 'products' },
                { action: 'read', resource: 'products.thumbnail' },
                { action: 'read', resource: 'products.reference' },
                { action: 'read', resource: 'products.category_id' },
                { action: 'read', resource: 'products.width' },
                { action: 'read', resource: 'products.height' },
                { action: 'read', resource: 'products.price' },
                { action: 'read', resource: 'products.description' },
            ],
        }),
};

const ProductList = (props: DatagridProps) => (
    <List {...props}>
        {/* ra-rbac Datagrid */}
        <Datagrid>
            <ImageField source="thumbnail" />
            <TextField source="reference" />
            <ReferenceField source="category_id" reference="categories">
                <TextField source="name" />
            </ReferenceField>
            <NumberField source="width" />
            <NumberField source="height" />
            <NumberField source="price" />
            <TextField source="description" />
            {
                // these two columns are not visible to the user
            }
            <NumberField source="stock" />
            <NumberField source="sales" />
        </Datagrid>
    </List>
);

ra-rbac also provide hooks and functions to ease restrictions based on roles and permissions inside your components:

  • useCanAccess: fetch the role definitions, then checks whether the requested action and resource are allowed for the current user.
import { useCanAccess } from '@react-admin/ra-rbac';

const DeleteUserButton = ({ record }) => {
    const { loading, canAccess } = useCanAccess({
        action: 'delete',
        resource: 'users',
        record,
    });
    if (loading || !canAccess) return null;
    return <DeleteButton record={record} resource="users" />;
};
  • usePermissions: a replacement for react-admin's usePermissions that returns an array of permissions, resulting in the merge of the user permissions and the permissions from the user roles.
const authProvider = {
    // ...
    getPermissions: () =>
        Promise.resolve({
            permissions: [
                {
                    action: ['read', 'write'],
                    resource: 'users',
                    record: { id: '123' },
                },
            ],
            roles: ['reader'],
        }),
    getRoles: () =>
        Promise.resolve({
            admin: [{ action: '*', resource: '*' }],
            reader: [{ action: 'read', resource: '*' }],
        }),
};

const { loading, permissions } = usePermissions();
// {
//      loading: false,
//      permissions: [
//          { action: "read", resource: "*" },
//          { action: ["read", "write"], resource: "users", record: { "id": "123" } },
//      ],
// };
  • canAccess: a helper function that checks if the current permissions allow the user to execute an action on a resource (and optionally a record).
import { List, Datagrid, TextField } from 'react-admin';
import { canAccess } from '@react-admin/ra-rbac';

const authProvider = {
    checkAuth: () => Promise.resolve(),
    login: () => Promise.resolve(),
    logout: () => Promise.resolve(),
    checkError: () => Promise.resolve(),
    getPermissions: () =>
        Promise.resolve({
            permissions: [
                { action: 'list', resource: 'products' },
                { action: 'read', resource: 'products.price' },
            ],
        }),
};

const ProductList = props => {
    const { loading, permissions } = usePermissions();
    if (loading) return null;
    return (
        <List {...props}>
            <Datagrid>
                <TextField source="id" />
                <TextField source="reference" />
                <TextField source="width" />
                <TextField source="height" />
                {canAccess({
                    permissions,
                    action: 'read',
                    resource: 'products.price',
                }) && <TextField source="price" />}
                {/* this column will not render */}
                {canAccess({
                    permissions,
                    action: 'read',
                    resource: 'products.stock',
                }) && <TextField source="stock" />}
            </Datagrid>
        </List>
    );
};

TypeScript and JavaScript Examples

Some of our customers use TypeScript, and some don't. For the latter category, we've updated the Enterprise modules documentation to display code examples both in TypeScript and in JavaScript. Check it out on the ra-enterprise site. Your language of choice is persisted locally, too!

A documentation extract screencast showing both TypeScript and JavaScript examples.

ra-preferences: no-code Components

It can be hard to predict what your users really need on an application. Besides, their needs evolve over time. We believe that providing end-users a way to customize the application can be a game-changer, as it removes the need for developer time and accelerates the time to market.

This is why ra-preferences now provides our first iteration of components that users can configure through the UI:

  • <Datagrid> lets end users show/hide columns, and even add computed columns
  • <List> lets end users apply default filters and change the view title
  • <SimpleForm> lets end users adjust the input style and density, and the redirection after save
  • <SimpleList> lets end users change the text displayed on each line, using a simple templating engine

A screencast showing how users can configure views

In addition to those components, ra-preferences also provides the building blocks for you to create your own configurable components.

Conclusion

If you're an Enterprise Edition customer, you get all these updates for free! Just run yarn upgrade to get the latest version of the @react-admin/XXX modules.

If you're not an Enterprise Edition customer yet, check out our plans: from 125€/month, you can dramatically speed up your development by using these modules that we've carefully crafted, tested, and documented, instead of writing your own code. A single one of these modules would already provide enough value to justify the subscription - but by subscribing, you get access to all 12 of them. And unlimited professional support!

Did you like this article? Share it!