Skip to content

Commit b77dc15

Browse files
authored
Merge pull request marmelab#11023 from marmelab/doc/ra-core-ee-de037912e2f36a4419cf0f58255606cb6dc54139
[Doc] Update RA Core EE documentation
2 parents c90b5b5 + 4246bdb commit b77dc15

12 files changed

+429
-1
lines changed

docs_headless/astro.config.mjs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,17 @@ export default defineConfig({
275275
enterpriseEntry('useDeletedRecordsListController'),
276276
],
277277
},
278+
{
279+
label: 'History',
280+
items: [
281+
enterpriseEntry('HistoryFeatures', 'Setting up'),
282+
enterpriseEntry('useAddRevisionAfterMutation'),
283+
enterpriseEntry('useApplyChangesBasedOnSearchParam'),
284+
enterpriseEntry('useDeleteRevisions'),
285+
enterpriseEntry('useGenerateChangeMessage'),
286+
enterpriseEntry('useGetRevisions'),
287+
],
288+
},
278289
{
279290
label: 'Recipes',
280291
items: ['caching', 'unittesting'],

docs_headless/src/content/docs/AuthProviderWriting.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ const authProvider = {
372372
| **Response format** | `{ id: string \| number, fullName?: string, avatar?: string }` |
373373
| **Error format** | `Error` |
374374

375-
Admin components often adapt their behavior based on the current user identity. For instance, a [lock system](https://react-admin-ee.marmelab.com/documentation/ra-core-ee#locks) may allow edition only if the lock owner is the current user. It is also common to display the current user name and avatar in the app main menu.
375+
Admin components often adapt their behavior based on the current user identity. For instance, a [lock system](./RealtimeFeatures.md#locks) may allow edition only if the lock owner is the current user. It is also common to display the current user name and avatar in the app main menu.
376376

377377
Ra-core delegates the storage of the connected user identity to the `authProvider`. If it exposes a `getIdentity()` method, ra-core will call it to read the user details.
378378

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
---
2+
title: History Setup
3+
---
4+
5+
`@react-admin/ra-core-ee` contains hooks and components to help you track the changes made in your admin. See the history of revisions, compare differences between any two versions, and revert to a previous state if needed.
6+
7+
## Installation
8+
9+
The history features require a valid [Enterprise Edition](https://marmelab.com/ra-enterprise/) subscription. Once subscribed, follow the [instructions to get access to the private npm repository](https://react-admin-ee.marmelab.com/setup).
10+
11+
You can then install the npm package providing the history features using your favorite package manager:
12+
13+
```sh
14+
npm install --save @react-admin/ra-core-ee
15+
# or
16+
yarn add @react-admin/ra-core-ee
17+
```
18+
19+
## Data Provider Requirements
20+
21+
`ra-core-ee` relies on the `dataProvider` to read, create and delete revisions. In order to use the history features, you must add 3 new methods to your data provider: `getRevisions`, `addRevision` and `deleteRevisions`.
22+
23+
```tsx
24+
const dataProviderWithRevisions = {
25+
...dataProvider,
26+
getRevisions: async (resource, params) => {
27+
const { recordId } = params;
28+
// ...
29+
return { data: revisions };
30+
},
31+
addRevision: async (resource, params) => {
32+
const { recordId, data, authorId, message, description } = params;
33+
// ...
34+
return { data: revision };
35+
},
36+
deleteRevisions: async resource => {
37+
const { recordId } = params;
38+
// ...
39+
return { data: deletedRevisionIds };
40+
},
41+
};
42+
```
43+
44+
**Tip**: Revisions are immutable, so you don't need to implement an `updateRevision` method.
45+
46+
A `revision` is an object with the following properties:
47+
48+
```js
49+
{
50+
id: 123, // the revision id
51+
resource: 'products', // the resource name
52+
recordId: 456, // the id of the record
53+
data: {
54+
id: 456,
55+
title: 'Lorem ipsum',
56+
teaser: 'Lorem ipsum dolor sit amet',
57+
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
58+
}, // the data of the record
59+
// metadata
60+
authorId: 789, // the id of the author
61+
date: '2021-10-01T00:00:00.000Z', // the date of the revision
62+
message: 'Updated title, teaser, body', // the commit message
63+
description: 'Added a teaser', // the commit description
64+
}
65+
```
66+
67+
You can read an example data provider implementation in the package source at `src/history/dataProvider/builder/addRevisionMethodsBasedOnSingleResource.ts`.
68+
69+
Instead of implementing these new methods yourself, you can use one of the provided builders to generate them:
70+
71+
- `addRevisionMethodsBasedOnSingleResource` stores the revisions for all resources in a single `revisions` resource:
72+
73+
```tsx
74+
// in src/dataProvider.ts
75+
import { addRevisionMethodsBasedOnSingleResource } from '@react-admin/ra-core-ee';
76+
import baseDataProvider from './baseDataProvider';
77+
78+
export const dataProvider = addRevisionMethodsBasedOnSingleResource(
79+
baseDataProvider,
80+
{ resourceName: 'revisions' }
81+
);
82+
```
83+
84+
- `addRevisionMethodsBasedOnRelatedResource` stores the revisions of each resource in a related resource (e.g. store the revisions of `products` in `products_history`):
85+
86+
```tsx
87+
// in src/dataProvider.ts
88+
import { addRevisionMethodsBasedOnRelatedResource } from '@react-admin/ra-core-ee';
89+
import baseDataProvider from './baseDataProvider';
90+
91+
export const dataProvider = addRevisionMethodsBasedOnRelatedResource(
92+
baseDataProvider,
93+
{ getRevisionResourceName: resource => `${resource}_history` }
94+
);
95+
```
96+
97+
Once your provider has the three revisions methods, pass it to the `<CoreAdmin>` component and you're ready to start using the history features of `ra-core-ee`.
98+
99+
```tsx
100+
// in src/App.tsx
101+
import { CoreAdmin } from 'ra-core';
102+
import { dataProvider } from './dataProvider';
103+
104+
const App = () => (
105+
<CoreAdmin dataProvider={dataProvider}>{/* ... */}</CoreAdmin>
106+
);
107+
```

docs_headless/src/content/docs/ReferenceManyInputBase.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
---
22
title: "<ReferenceManyInputBase>"
33
---
4+
45
Use `<ReferenceManyInputBase>` in an edition or creation views to edit one-to-many relationships, e.g. to edit the variants of a product in the product edition view.
56

67
`<ReferenceManyInputBase>` fetches the related records, and renders them in a sub-form. When users add, remove of update related records, the `<ReferenceManyInputBase>` component stores these changes locally. When the users actually submit the form, `<ReferenceManyInputBase>` computes a diff with the existing relationship, and sends the related changes (additions, deletions, and updates) to the server.
78

9+
This feature requires a valid is an [Enterprise Edition](https://marmelab.com/ra-enterprise/) subscription.
10+
811
## Usage
912

1013
An example one-to-many relationship can be found in ecommerce systems: a product has many variants.

docs_headless/src/content/docs/ReferenceManyToManyFieldBase.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ This component fetches a list of referenced records by lookup in an associative
66

77
Note: The `<ReferenceManyToManyFieldBase>` cannot currently display multiple records with the same id from the end reference resource, even though they might have different properties in the associative table.
88

9+
This feature requires a valid is an [Enterprise Edition](https://marmelab.com/ra-enterprise/) subscription.
10+
911
## Usage
1012

1113
Let's imagine that you're writing an app managing concerts for artists. The data model features a many-to-many relationship between the `bands` and `venues` tables through a `performances` associative table.

docs_headless/src/content/docs/ReferenceManyToManyInputBase.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ This component allows adding or removing relationships between two resources sha
66

77
**Note**: The `<ReferenceManyToManyInputBase>` cannot currently display multiple records with the same id from the end reference resource even though they might have different properties in the associative table.
88

9+
This feature requires a valid is an [Enterprise Edition](https://marmelab.com/ra-enterprise/) subscription.
10+
911
## Usage
1012

1113
Let's imagine that you're writing an app managing concerts for artists. The data model features a many-to-many relationship between the `bands` and `venues` tables through a `performances` associative table.

docs_headless/src/content/docs/ReferenceOneInputBase.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ title: "<ReferenceOneInputBase>"
33
---
44
Use `<ReferenceOneInputBase>` in an `<EditBase>` or `<CreateBase>` view to edit one-to-one relationships, e.g. to edit the details of a book in the book edition view.
55

6+
This feature requires a valid is an [Enterprise Edition](https://marmelab.com/ra-enterprise/) subscription.
7+
68
## Usage
79

810
Here is an example one-to-one relationship: a `book` has at most one `book_details` row associated to it.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
---
2+
title: "useAddRevisionAfterMutation"
3+
---
4+
5+
This hook registers a mutation [middleware](https://marmelab.com/ra-core/useregistermutationmiddleware/) that automatically creates a revision after a successful create or update operation.
6+
7+
This middleware reads the revision metadata from a field named after the `REVISION_FIELD` constant, then removes it from the form data before saving the record.
8+
9+
The `REVISION_FIELD` constant can be imported from `@react-admin/ra-core-ee`.
10+
11+
This feature requires a valid is an [Enterprise Edition](https://marmelab.com/ra-enterprise/) subscription.
12+
13+
## Usage
14+
15+
Below is an example showing how to override the [`SaveContext`](https://marmelab.com/ra-core/usesavecontext/) to provide the revision metadata when saving a form:
16+
17+
```tsx
18+
import React, { ReactNode, useMemo } from 'react';
19+
import {
20+
EditBase,
21+
SaveContextProvider,
22+
useSaveContext,
23+
type SaveContextValue,
24+
} from 'ra-core';
25+
import { SimpleForm, TextInput, DeleteButton } from 'my-react-admin-ui-library';
26+
import {
27+
useAddRevisionAfterMutation,
28+
useGenerateChangeMessage,
29+
REVISION_FIELD,
30+
} from '@react-admin/ra-core-ee';
31+
32+
export const ProductEdit = () => (
33+
<EditBase>
34+
<CreateRevisionOnSave>
35+
<SimpleForm>
36+
<TextInput source="reference" />
37+
<TextInput source="category" />
38+
</SimpleForm>
39+
</CreateRevisionOnSave>
40+
</EditBase>
41+
);
42+
43+
const CreateRevisionOnSave = ({ children }: { children: ReactNode }) => {
44+
const originalSaveContext = useSaveContext();
45+
useAddRevisionAfterMutation();
46+
const generateChangeMessage = useGenerateChangeMessage();
47+
48+
// Wrap the original save function to add the revision data before saving
49+
const saveContext = useMemo<SaveContextValue>(
50+
() => ({
51+
...originalSaveContext,
52+
save: async (record, callbacks) =>
53+
originalSaveContext.save!(
54+
{
55+
...record,
56+
// Store the revision metadata in a special field that will be removed by the middleware
57+
[REVISION_FIELD]: {
58+
message: generateChangeMessage({ data: record }),
59+
description: '',
60+
authorId: 'john',
61+
},
62+
},
63+
callbacks
64+
),
65+
}),
66+
[generateChangeMessage, originalSaveContext]
67+
);
68+
69+
return (
70+
<SaveContextProvider value={saveContext}>
71+
{children}
72+
</SaveContextProvider>
73+
);
74+
};
75+
```
76+
77+
**Tip:** This example also leverages the [`useGenerateChangeMessage`](./useGenerateChangeMessage.md) hook to automatically generate a revision message based on the changes made in the form.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
title: "useApplyChangesBasedOnSearchParam"
3+
---
4+
Monitors the URL for a `_change` search parameter and automatically applies those changes to the current form. This is useful for implementing a "revert to revision" functionality.
5+
6+
This feature requires a valid is an [Enterprise Edition](https://marmelab.com/ra-enterprise/) subscription.
7+
8+
## Usage
9+
10+
```tsx
11+
import { EditBase } from 'ra-core';
12+
import { SimpleForm, TextInput } from 'my-react-admin-ui-library';
13+
import { useApplyChangesBasedOnSearchParam } from '@react-admin/ra-core-ee';
14+
15+
const ApplyChangesBasedOnSearchParam = () => {
16+
const hasCustomParams = useApplyChangesBasedOnSearchParam();
17+
return hasCustomParams ? (
18+
<div
19+
style={{
20+
backgroundColor: '#fff3cd',
21+
color: '#856404',
22+
border: '1px solid #856404',
23+
padding: '0.75em 1em',
24+
}}
25+
role="alert"
26+
>
27+
This form has been pre-filled with the changes from a previous
28+
revision. You can still modify the data before saving it.
29+
</div>
30+
) : null;
31+
};
32+
33+
const ProductEdit = () => (
34+
<EditBase>
35+
<SimpleForm>
36+
<ApplyChangesBasedOnSearchParam />
37+
<TextInput source="name" />
38+
<TextInput source="description" />
39+
</SimpleForm>
40+
</EditBase>
41+
);
42+
```
43+
44+
**Usage:**
45+
Navigate to a URL like `/products/1?_change={"name":"New Name","description":"New Description"}` to pre-fill the form with the specified data.
46+
47+
**Returns:**
48+
49+
- `boolean`: `true` if the form was pre-filled from URL parameters and the user hasn't made changes yet. Useful to show a warning message.
50+
51+
The hook:
52+
53+
1. Reads the `_change` parameter from the URL
54+
2. Parses the JSON data
55+
3. Sets form values using `setValue` with `shouldDirty: true`
56+
4. Removes the search parameter from the URL
57+
5. Tracks whether the form has custom parameters and user modifications
58+
59+
**Tip:** Be sure to use this hook as a child of a form component such as `<Form>` so that it can access the form context.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
title: "useDeleteRevisions"
3+
---
4+
Provides a mutation function to delete all revisions for a specific record. This is useful notably to delete all revisions when the record itself is deleted.
5+
6+
This feature requires a valid is an [Enterprise Edition](https://marmelab.com/ra-enterprise/) subscription.
7+
8+
## Usage
9+
10+
Here is an example showing how to implement a `<DeleteWithRevisionsButton>` that deletes both the record and its revisions:
11+
12+
```tsx
13+
import { EditBase, Form, useResourceContext, useRecordContext } from 'ra-core';
14+
import { TextInput, DeleteButton } from 'my-react-admin-ui-library';
15+
import { useDeleteRevisions } from '@react-admin/ra-core-ee';
16+
17+
const DeleteWithRevisionsButton = () => {
18+
const resource = useResourceContext();
19+
const record = useRecordContext();
20+
const [deleteRevisions] = useDeleteRevisions();
21+
return (
22+
<DeleteButton
23+
mutationOptions={{
24+
onSettled: (_data, error) => {
25+
if (error) return;
26+
deleteRevisions(resource, { recordId: record?.id });
27+
},
28+
}}
29+
label="Delete with revisions"
30+
/>
31+
);
32+
};
33+
34+
export const ProductEdit = () => (
35+
<EditBase>
36+
<Form>
37+
<TextInput source="reference" />
38+
<TextInput source="category" />
39+
<DeleteWithRevisionsButton />
40+
</Form>
41+
</EditBase>
42+
);
43+
```
44+
45+
**Hook Parameters:**
46+
47+
- `resource?`: Resource name. Defaults to the current resource context.
48+
- `params?`: Default parameters with `recordId`
49+
- `options?`: Default mutation options
50+
51+
**Returns:**
52+
A tuple with:
53+
54+
1. `deleteRevisions`: Function to trigger the deletion
55+
2. `mutation`: React Query mutation result object
56+
57+
**`deleteRevisions` Parameters:**
58+
59+
- `resource?`: Resource name (overrides hook-time resource)
60+
- `params?`: Object with `recordId` (overrides hook-time params)
61+
- `options?`: Mutation options including `returnPromise: boolean` (overrides hook-time options)

0 commit comments

Comments
 (0)