Skip to content

Commit d66b56e

Browse files
committed
Merge branch 'master' into next
2 parents cf71dae + b77dc15 commit d66b56e

22 files changed

+524
-97
lines changed

docs/EditDialog.md

Lines changed: 47 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -433,75 +433,63 @@ const sexChoices = [
433433
{ id: 'female', name: 'Female' },
434434
];
435435

436-
const CustomerForm = (props: any) => (
437-
<SimpleForm defaultValues={{ firstname: 'John', name: 'Doe' }} {...props}>
436+
const CustomerForm = () => (
437+
<SimpleForm>
438438
<TextInput source="first_name" validate={required()} />
439439
<TextInput source="last_name" validate={required()} />
440-
<DateInput source="dob" label="born" validate={required()} />
440+
<DateInput source="dob" label="Born" validate={required()} />
441441
<SelectInput source="sex" choices={sexChoices} />
442442
</SimpleForm>
443443
);
444444

445-
const EmployerSimpleFormWithFullyControlledDialogs = () => {
446-
const record = useRecordContext();
447-
448-
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
449-
const openEditDialog = useCallback(() => {
450-
setIsEditDialogOpen(true);
445+
const EmployerEdit = () => {
446+
const [record, setRecord] = React.useState<any>(undefined);
447+
const closeDialog = React.useCallback(() => {
448+
setRecord(undefined);
451449
}, []);
452-
const closeEditDialog = useCallback(() => {
453-
setIsEditDialogOpen(false);
454-
}, []);
455-
456450
return (
457-
<SimpleForm>
458-
<TextInput source="name" validate={required()} />
459-
<TextInput source="address" validate={required()} />
460-
<TextInput source="city" validate={required()} />
461-
<ReferenceManyField
462-
label="Customers"
463-
reference="customers"
464-
target="employer_id"
465-
>
466-
<DataTable>
467-
<DataTable.Col source="id" />
468-
<DataTable.Col source="first_name" />
469-
<DataTable.Col source="last_name" />
470-
<DataTable.Col source="dob" label="born" field={DateField} />
471-
<DataTable.Col source="sex">
472-
<SelectField source="sex" choices={sexChoices} />
473-
</DataTable.Col>
474-
<DataTable.Col>
475-
<Button
476-
label="Edit customer"
477-
onClick={() => openEditDialog()}
478-
size="medium"
479-
variant="contained"
480-
sx={{ mb: 4 }}
481-
/>
482-
<DataTable.Col>
483-
</DataTable>
484-
</ReferenceManyField>
485-
<EditDialog
486-
fullWidth
487-
maxWidth="md"
488-
record={{ employer_id: record?.id }} // pre-populates the employer_id to link the new customer to the current employer
489-
isOpen={isEditDialogOpen}
490-
open={openEditDialog}
491-
close={closeEditDialog}
492-
resource="customers"
493-
>
494-
<CustomerForm />
495-
</EditDialog>
496-
</SimpleForm>
451+
<Edit>
452+
<SimpleForm>
453+
<TextInput source="name" validate={required()} />
454+
<TextInput source="address" validate={required()} />
455+
<TextInput source="city" validate={required()} />
456+
<ReferenceManyField
457+
label="Customers"
458+
reference="customers"
459+
target="employer_id"
460+
>
461+
<DataTable>
462+
<DataTable.Col source="id" />
463+
<DataTable.Col source="first_name" />
464+
<DataTable.Col source="last_name" />
465+
<DataTable.Col label="born">
466+
<DateField source="dob" label="Born" />
467+
</DataTable.Col>
468+
<DataTable.Col source="sex">
469+
<SelectField source="sex" choices={sexChoices} />
470+
</DataTable.Col>
471+
<DataTable.Col render={record => (
472+
<Button
473+
label="Edit customer"
474+
onClick={() => setRecord(record)}
475+
/>
476+
)} />
477+
</DataTable>
478+
</ReferenceManyField>
479+
<EditDialog
480+
record={record}
481+
resource="customers"
482+
isOpen={!!record}
483+
close={closeDialog}
484+
fullWidth
485+
maxWidth="md"
486+
>
487+
<CustomerForm />
488+
</EditDialog>
489+
</SimpleForm>
490+
</Edit>
497491
);
498492
};
499-
500-
const EmployerEdit = () => (
501-
<Edit>
502-
<EmployerSimpleFormWithFullyControlledDialogs />
503-
</Edit>
504-
);
505493
```
506494

507495
{% endraw %}

docs/ReferenceManyField.md

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -653,18 +653,14 @@ const EmployerEdit = () => (
653653
<DataTable>
654654
<DataTable.Col source="first_name" />
655655
<DataTable.Col source="last_name" />
656-
<DataTable.Col
657-
render={record => (
658-
<EditInDialogButton>
659-
<SimpleForm
660-
record={{ employer_id: record.id }}
661-
>
662-
<TextInput source="first_name" />
663-
<TextInput source="last_name" />
664-
</SimpleForm>
665-
</EditInDialogButton>
666-
)}
667-
/>
656+
<DataTable.Col>
657+
<EditInDialogButton>
658+
<SimpleForm>
659+
<TextInput source="first_name" />
660+
<TextInput source="last_name" />
661+
</SimpleForm>
662+
</EditInDialogButton>
663+
</DataTable.Col>
668664
</DataTable>
669665
</ReferenceManyField>
670666
</SimpleForm>

docs_headless/astro.config.mjs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,17 @@ export default defineConfig({
280280
enterpriseEntry('useDeletedRecordsListController'),
281281
],
282282
},
283+
{
284+
label: 'History',
285+
items: [
286+
enterpriseEntry('HistoryFeatures', 'Setting up'),
287+
enterpriseEntry('useAddRevisionAfterMutation'),
288+
enterpriseEntry('useApplyChangesBasedOnSearchParam'),
289+
enterpriseEntry('useDeleteRevisions'),
290+
enterpriseEntry('useGenerateChangeMessage'),
291+
enterpriseEntry('useGetRevisions'),
292+
],
293+
},
283294
{
284295
label: 'Recipes',
285296
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/LockOnMount.md

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,15 @@ yarn add @react-admin/ra-core-ee
1818

1919
```tsx
2020
import { EditBase, Form } from 'ra-core';
21-
import { LockOnMount, useLockCallbacks } from '@react-admin/ra-core-ee';
21+
import { LockOnMount } from '@react-admin/ra-core-ee';
22+
import { TextInput } from 'my-react-admin-ui-lib';
2223

2324
const PostEdit = () => (
2425
<EditBase>
25-
<PostEditForm />
26-
<LockOnMount />
26+
<Form>
27+
<TextInput source="title" />
28+
<LockOnMount />
29+
</Form>
2730
</EditBase>
2831
);
29-
30-
const PostEditForm = () => {
31-
const { isPending, isLocked } = useLockCallbacks();
32-
33-
if (isPending) {
34-
return <p>Loading...</p>;
35-
}
36-
37-
return <Form disabled={isLocked}>{/* ... */}</Form>;
38-
};
3932
```

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.

0 commit comments

Comments
 (0)