ra-audit-log
Display event lists and audit logs, keep track of user actions, and get an overview of the activity of your admin.
Installation
npm install --save @react-admin/ra-audit-log
# or
yarn add @react-admin/ra-audit-log
Tip: ra-audit-log is part of the React-Admin Enterprise Edition, and hosted in a private npm registry. You need to subscribe to one of the Enterprise Edition plans to access this package.
Overview
This module introduces 2 new components:
<Timeline>shows a list of all recent changes in the admin. It's a great component for dashboards.<EventList>is a ready-to-useListcomponent for navigating in your admin history, complete with filters and pagination.
Setup
Event Format
This package expects you to store and expose one "event" for each change triggered by a user action.
Event records must contain the following properties:
iddateauthorresourceaction
Here is a typical event:
{
date: '2020-09-08 15:01:00',
author: 567,
resource: 'posts',
action: 'create',
payload: { title: 'foo', body: 'bar' }
}
We recommend that you create event logs server-side, i.e. add a new event record each time a user performs a mutation (create, update, delete, updateMany, deleteMany). Typically, you can achieve this using a proxy server, a database trigger, or a middleware in your API server. Then, it's your responsibility to expose an API for these events and to update your dataProvider so that it can access events like any other resource.
Events Resource
To let react-admin components query the backend for events, you should add a <Resource> for them:
import { Admin, Resource } from 'react-admin';
import dataProvider from './dataProvider';
export const App = () => (
<Admin dataProvider={dataProvider}>
{/* your Resources go here */}
+ <Resource name="events" />
</Admin>
)
Client-Side Tracking
You can choose to let ra-audit-log create events for you. In this case, each mutation performed by a user will trigger a call to dataProvider.create('events', [event details]). We call this feature client-side tracking.
To enable client-side tracking, build a new dataProvider by calling the addEventsForMutations helper function with your original dataProvider and your authProvider:
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
);
Your authProvider must have a getIdentity method which returns an object containing the id, the fullName and optionally the avatar (an image URL) for the current logged user:
const authProvider = {
login: ...,
logout: ...,
...,
getIdentity: () => Promise.resolve({ id: '1', fullName: 'test', avatar: 'https://myavatar.com' }),
}
When enabled, after each successful mutation, the client-side tracking wrapper adds a new event. For instance:
// when your app calls
dataProvider.update('orders', {
id: 123,
data: { status: 'delivered' },
previousData: { id: 123, status: 'received' },
});
// the wrapper calls
dataProvider.create('events', {
date: '2020-09-08 15:01:00',
author: 567,
resource: 'orders',
action: 'update',
payload: {
id: 123,
data: { status: 'delivered' },
previousData: { id: 123, status: 'received' },
},
});
In addition to the dataProvider to extend, the addEventsForMutations function accepts the following options:
name: the name of the event logs resource (defaults toevents)
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,
{ name: 'events' }
);
resources: the resources and mutations you want to track with events. It can be an array of resource names or an array of arrays defining both the resource and the dataProvider calls to track
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,
{
resources: [
// create an event for all known mutations on posts
'posts',
// create an event only for create and update on comments, but not for delete, deleteMany or updateMany
['comments', ['create', 'update']],
],
}
);
shouldAudit: as an alternative toresources, you can specify a function which, given a dataProvider method name (create,update, etc.) and the arguments it was called with, should returntrueto create an event. This allows to target custom dataProvider methods:
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,
{
shouldAudit: (action, ...args) => {
if (action === 'myCustomMethod') {
return true;
}
},
}
);
Translations
In order to have correct labels on the <Timeline> and <EventList> components, you have to set up the internationalization with the provided translations or your own. Check out Internationalization for detailed steps.
<Timeline>

The <Timeline> component displays a list of events, the most recent first, grouped by day. It's the ideal component for checking the recent activity of an admin.
It accepts the following props:
records: An array of eventsloaded: Optional. Boolean, indicating whether the data has been loaded at least once.children: Optional. Component rendered for each group of audit logs.groupLogs: Optional. A function called with the audit logs and the locale. Must return groups.skeleton: Optional. The element to display whileloadedistrue.
Basic Usage
It's your responsibility to get the events from the dataProvider and pass them to <Timeline>.
import { useGetList } from 'react-admin';
import { Timeline } from '@react-admin/ra-audit-log';
const Dashboard = () => {
const { data, ids, loaded } = useGetList(
'events',
{ page: 1, perPage: 25 },
{ field: 'date', order: 'desc' },
});
const records = ids.map(id => data[id]);
return (
<Timeline loaded={loaded} records={records} />
);
}
Custom Group Component
By default, <Timeline> uses the <TimelineGroup> component to render each group. You can use your own component instead by passing it as the <Timeline> child. In this component, use the useTimelineGroup hook to grab the group information.
import { useGetList } from 'react-admin';
import { Timeline, useTimelineGroup } from '@react-admin/ra-audit-log';
const Dashboard = () => {
const { data, ids, loaded } = useGetList(
'audit-logs',
{ page: 1, perPage: 25 },
{ field: 'date', order: 'desc' },
});
const records = ids.map(id => data[id]);
return (
<Timeline loaded={loaded} records={records}>
<MyTimelineGroup />
</Timeline>
);
}
const MyTimelineGroup = () => {
const { label, records } = useTimelineGroup();
return (
<article>
<h1>{label}</h1>
<ul>
{records.map(record => (
<li>{JSON.stringify(record)}</li>
))}
</ul>
</article>
);
};
Custom Item Component
<TimelineGroup> renders each event using the <TimelineItem> component. You can replace it with your own component instead, by passing it as a child to <TimelineGroup>. Use the useRecordContext hook to grab the event record.
import { useGetList, useRecordContext } from 'react-admin';
import { AuditLogLabel, Timeline, TimelineItem, TimelineGroup, useTimeline } from '@react-admin/ra-audit-log';
const Dashboard = () => {
const { data, ids, loaded } = useGetList(
'audit-logs',
{ page: 1, perPage: 25 },
{ field: 'date', order: 'desc' },
});
const records = ids.map(id => data[id]);
return (
<Timeline loaded={loaded} records={records}>
<MyTimelineGroup />
</Timeline>
);
}
const MyTimelineGroup = () => {
return (
<TimelineGroup>
<MyTimelineItem />
</TimelineGroup>
);
};
const MyTimelineItem = (props) => {
const record = useRecordContext(props.record);
return (
<ListItem>
<ListItemText
primary={record.author.fullName}
secondary={<AuditLogLabel />}
/>
</ListItem>
);
};
Custom skeleton
You may want to display more items in the skeleton while data is loading.
import { useGetList } from 'react-admin';
import { Timeline, TimelineSkeleton } from '@react-admin/ra-audit-log';
const Dashboard = () => {
const { data, ids, loaded } = useGetList(
'events',
{ page: 1, perPage: 25 },
{ field: 'date', order: 'desc' },
});
const records = ids.map(id => data[id]);
return (
<Timeline
loaded={loaded}
records={records}
skeleton={<TimelineSkeleton length={50} />}
/>
);
}
<RecordTimeline>

The <RecordTimeline> component is a specialized version of the <Timeline>. It accepts the same props except records and loaded. It will fetch the event related to the current record. This record can be either the one from the current RecordContext (set by react-admin components) or the one specified using the record prop.
It accepts the following props:
record: Optional. The record for which to fetch the eventsresource: Optional. The resource.eventResource: Optional. The resource for the events.children: Optional. Component rendered for each group of audit logs.groupLogs: Optional. A function called with the audit logs and the locale. Must return groups.skeleton: Optional. The element to display whileloadedistrue.
Usage
You can use the <RecordTimeline> as an aside for an <Edit> view:
import { Edit, SimpleForm, TextField } from 'react-admin';
import { RecordTimeline } from '@react-admin/ra-audit-log';
const ProductEdit = props => {
return (
<Edit {...props} aside={<RecordTimeline />}>
<SimpleForm>
<TextField source="name" />
</SimpleForm>
</Edit>
);
};
useRecordEvents
This hook cn be used to fetch events related to a record. It accept the following options:
record: Optional. The record for which to fetch the events. Will be inferred from the RecordContext if not providedresource: Optional. The resource of the record. Will be inferred from the ResourceContext if not providedeventResource: Optional. The resource for the events. Defaults toeventspage: Optional. The page of events to fetch. Defaults to 1perPage: Optional. The number of events to fetch. Defaults to 25sort: Optional. The field used to sort the events. Defaults todateorder: Optional. The order into which to sort the events. Defaults toDESC
It returns the same object as the useGetListhook, containing the following properties:
data: An object where keys are records identifiers and values the recordsids: An array of the records identifiersloaded: A boolean indicating whether the list has been fetched at least onceloading: A boolean indicating whether the list is currently loadingerror: An object containing an error when one occurs
<EventList>
The <EventList> component is a full-featured List component for events, pre-configured with a Datagrid and a filter sidebar.
Basic Usage
Use the <EventList> component as the list prop of the events <Resource>:
// In App.js
import { Admin, Resource } from 'react-admin';
import { EventList } from '@react-admin/ra-audit-log';
export const App = () => (
<Admin>
<Resource name="events" list={EventList} />
</Admin>
);
In addition to the usual List props, <EventList> accepts the following props:
eventResource: The name of the events resource. Defaults toevents.authorResource: You can optionally provide the resource name for the authors of the events.ra-audit-logwill use it to create links to authors.dateFilters: A map of date filters to replace the default ones.
Events Resource
<EventList> allows users to filter events by resource, exluding the events resource itself. By default, it excludes the events resource. If you used a different resource name for events, use the eventResource prop to change it:
// In App.js
import { Admin, Resource } from 'react-admin';
import { MyEventList } from './evenList';
export const App = () => (
<Admin>
<Resource name="audit-logs" list={MyEventList} />
</Admin>
);
// in ./eventList.js
import { EventList } from '@react-admin/ra-audit-log';
export const MyEventList = props => <EventList eventResource="audit-logs" />;
Authors Resource
You can provide a resource name for the authors of the events. <EventList> uses it to render links to each author in the list, and an AutocompleteArrayInput to filter them.
Without authors resource:

With authors resource:

Use the authorResource prop to set the author resource in the <EventList>:
// In App.js
import { Admin, Resource } from 'react-admin';
import { MyEventList } from './evenList';
export const App = () => (
<Admin>
<Resource name="events" list={MyEventList} />
</Admin>
);
// In ./eventList.js
import { EventList } from '@react-admin/ra-audit-log';
export const MyEventList = props => <EventList authorResource="users" />;
Date Filters
By default, @react-admin/ra-audit-log provides filters for the following date ranges:
- Today
- Last week
- Last month
- Last quarter
You can provide your own set of date filters by using the dateFilters prop:
// In App.js
import { Admin, Resource } from 'react-admin';
import { MyEventList } from './evenList';
export const App = () => (
<Admin>
<Resource name="events" list={MyEventList} />
</Admin>
);
// In ./eventList.js
import { DefaultDateFilters, EventList } from '@react-admin/ra-audit-log';
const dateFilters = {
...DefaultDateFilters,
'This Year': () => startOfYear(new Date()).toISOString(),
};
export const MyEventList = props => <EventList dateFilters={dateFilters} />;
Internationalization
ra-audit-log provides translations for English (raAuditLogLanguageEnglish) and French (raAuditLogLanguageFrench).
The interface in English looks like this:

The interface in French looks like this:

You should merge these translations with the other interface messages before passing them to your i18nProvider:
import { mergeTranslations } from 'react-admin';
import polyglotI18nProvider from 'ra-i18n-polyglot';
import englishMessages from 'ra-language-english';
import frenchMessages from 'ra-language-french';
import {
raAuditLogLanguageEnglish,
raAuditLogLanguageFrench,
} from '@react-admin/ra-audit-log';
const i18nProvider = polyglotI18nProvider(locale =>
locale === 'en'
? mergeTranslations(englishMessages, raAuditLogLanguageEnglish)
: mergeTranslations(frenchMessages, raAuditLogLanguageFrench)
);
The event field names are translatable. ra-audit-log uses the react-admin resource and field name translation system. This is an example of an English translation file:
// in i18n/en.js
export default {
resources: {
events: {
name: 'Log |||| Log',
fields: {
date: 'Date',
author: 'Author',
resource: 'Resource',
action: 'Action',
},
},
},
};
ra-audit-log also supports internationalization for the textual representation of the event action.
If available, ra-audit-log uses the translation with the key ra-audit-log.event.[resource].[action], where resource and action are replaced with the actual resource and action names. For example: ra-audit-log.event.posts.create.
If not available, it will fall back to the following translation key: ra-audit-log.event.[action], where action is replaced with the actual action name. For example: ra-audit-log.event.create.
In all cases, the following variables are available for interpolation in the translated string:
name: the name of the record targeted by the action if available. It will be the first available property on the record in this list:name,label,title,reference, orid.resource: the name of the event resourceids: the list of the record identifiers if the action was targeting many (deleteMany,updateManyfor example).all_fields: the complete list of the fields which were modified in anupdateaction.fields: the list of the fields which were modified in anupdateaction. Limited to a maximum of two. To be used with thesmart_countvariable.smart_count: The number of updated fields in addition of the two available in thefieldvariable. This allows to display messages like:Update field Name, Category and 5 others.
Building Blocks
Would you want to create your own <EventList> component, we provide some building blocks you might reuse:
-
<EventListFilter>: A component to pass to the listfilterprop. It includes the author, date, and resource filters. -
<AuthorInput>: An input used to filter events by authors. It's a basic TextInput unless theauthorResourceprop is provided. In this case, it's a<ReferenceArrayInput>with an<AutocompleteArrayInput>. -
<EventDateInput>: An input used to filter events by date. It provides the default date filters presented before. It accepts adateFiltersprop similar to the<EventList>. -
<ResourceInput>: An input used to filter events by resource. It's an<AutocompleteArrayInput>which lists all the resources declared in the<Admin>component but the events. -
<EventFilterList>: A component to pass to the listasideprop. It includes the author, date, and resource filters presented as a sidebar. -
<AuthorFilterList>: A filter sidebar part used to filter events by authors. It's a basic TextInput unless theauthorResourceprop is provided. In this case, it's a<ReferenceArrayInput>with an<AutocompleteArrayInput>. -
<EventFilterList>: A filter sidebar part used to filter events by date. It provides the default date filters presented before and displayed as filter list items. It accepts adateFiltersprop similar to the<EventList>. -
<ResourceFilterList>: A filter sidebar part used to filter events by resource. It's an<AutocompleteArrayInput>which lists all the resources declared in theAdmincomponent but the events. -
<ActionField>: A react-admin field that displays a formated text for the event action. -
<AuthorField>: A react-admin field that displays the event authors. If theauthorResourceprop is provided, it uses a<ReferenceField>and will show a link to the author. -
<AvatarField>: A react-admin field that displays the author avatar with a fallback to a material-ui icon. -
<ResourceField>: A react-admin field that displays the event resource. The resource name is translated like in react-admin. It will use the resource label option if available.
CHANGELOG
v2.3.1
2021-06-29
- (fix) Update peer dependencies ranges (support react 17)
v2.3.0
2021-06-28
- (feat)
<Timeline>now has askeletonprop which accept an element, allowing you to customize the skeleton UI displayed while loading. - (feat)
<RecordTimeline>now takes less space and draw less attention. - (feat)
<RecordTimeline>now displays the names of the fields which were updated for any update event record.
v2.0.2
2021-04-20
- (fix) Add missing date-fns dependency
v2.0.1
2021-04-12
- (fix) Make Resource and Event Inputs Resettable
v2.0.0
2021-03-22
- (feat) Introduce
<RecordTimeline>component - (feat) Introduce
useRecordEventshook
Breaking Change:
- (feat) Refactor
useEventLabelto accept avariantprop instead of theinlineprop
-useEventLabel({ inline: true })
+useEventLabel({ variant: 'inline' })
v1.0.3
2021-03-22
- (fix) Fix ra-audit-log published without built files
v1.0.2
2021-03-19
- (fix) Add a better screencast to showcase the package
v1.0.1
2021-03-18
- (fix) Fix Filters in Sidebar erase existing filters
- (fix) Fix Filters in Sidebar initial values
- (fix) Fix Filters in Sidebar display
v1.0.0
2021-03-17
- First release