Skip to content

Commit e2831ca

Browse files
committed
Merge branch '2.5'
* 2.5: (48 commits) show XML syntax for @ApiProperty(identifier=true) (#1250) Document how to run api-platform/docker-compose-prod locally (#1014) Wrong ContextAwareDataPersisterInterface use (#1177) Update Swagger documentation to use V3 (#1188) Data Provider Exception vs Restricted interface (#1197) Upgrade Xdebug to 3.0.2 (#1178) Update elasticsearch.md (#1203) update nuxtjs.md, add missing dependencies and instructions (#1208) Update operations.md (#1210) Update operations.md (#1214) Update operations.md (#1215) Update data-persisters.md (#1217) fixed typo in localhost url for next generator (#1246) Update testing JWT Authentication with ApiTestCase (#1242) Testing the API - update command to run tests (#1243) Fix MERCURE_JWT_TOKEN variable name Update security.md Revert "fix: allow accessing the JSON-LD context with JWT (#1225)" Decorate a Serializer when JSON data is provided docs: use function for getting headers (#1228) ...
2 parents a69ccbd + 639a7e4 commit e2831ca

32 files changed

+777
-150
lines changed

admin/authentication-support.md

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,43 +11,57 @@ In short, you have to tweak data provider and api documentation parser, like thi
1111

1212
import React from "react";
1313
import { Redirect, Route } from "react-router-dom";
14-
import { HydraAdmin, hydraDataProvider as baseHydraDataProvider, fetchHydra as baseFetchHydra } from "@api-platform/admin";
14+
import { HydraAdmin, hydraDataProvider as baseHydraDataProvider, fetchHydra as baseFetchHydra, useIntrospection } from "@api-platform/admin";
1515
import parseHydraDocumentation from "@api-platform/api-doc-parser/lib/hydra/parseHydraDocumentation";
1616
import authProvider from "./authProvider";
1717

1818
const entrypoint = process.env.REACT_APP_API_ENTRYPOINT;
19-
const fetchHeaders = { Authorization: `Bearer ${window.localStorage.getItem("token")}` };
20-
const fetchHydra = (url, options = {}) => baseFetchHydra(url, {
19+
const getHeaders = () => localStorage.getItem("token") ? {
20+
Authorization: `Bearer ${localStorage.getItem("token")}`,
21+
} : {};
22+
const fetchHydra = (url, options = {}) =>
23+
baseFetchHydra(url, {
2124
...options,
22-
headers: new Headers(fetchHeaders),
23-
});
24-
const apiDocumentationParser = entrypoint => parseHydraDocumentation(entrypoint, { headers: new Headers(fetchHeaders) })
25-
.then(
26-
({ api }) => ({ api }),
27-
(result) => {
28-
switch (result.status) {
29-
case 401:
30-
return Promise.resolve({
31-
api: result.api,
32-
customRoutes: [
33-
<Route path="/" render={() => {
34-
return window.localStorage.getItem("token") ? window.location.reload() : <Redirect to="/login" />
35-
}} />
36-
],
37-
});
38-
39-
default:
40-
return Promise.reject(result);
41-
}
42-
},
43-
);
25+
headers: getHeaders,
26+
});
27+
const RedirectToLogin = () => {
28+
const introspect = useIntrospection();
29+
30+
if (localStorage.getItem("token")) {
31+
introspect();
32+
return <></>;
33+
}
34+
return <Redirect to="/login" />;
35+
};
36+
const apiDocumentationParser = async (entrypoint) => {
37+
try {
38+
const { api } = await parseHydraDocumentation(entrypoint, { headers: getHeaders });
39+
return { api };
40+
} catch (result) {
41+
if (result.status === 401) {
42+
// Prevent infinite loop if the token is expired
43+
localStorage.removeItem("token");
44+
45+
return {
46+
api: result.api,
47+
customRoutes: [
48+
<Route path="/" component={RedirectToLogin} />
49+
],
50+
};
51+
}
52+
53+
throw result;
54+
}
55+
};
4456
const dataProvider = baseHydraDataProvider(entrypoint, fetchHydra, apiDocumentationParser);
4557

4658
export default () => (
47-
<HydraAdmin
48-
dataProvider={ dataProvider }
49-
authProvider={ authProvider }
50-
entrypoint={ entrypoint }
51-
/>
59+
<HydraAdmin
60+
dataProvider={ dataProvider }
61+
authProvider={ authProvider }
62+
entrypoint={ entrypoint }
63+
/>
5264
);
5365
```
66+
67+
For the implementation of the auth provider, you can find a working example in the [API Platform's demo application](https://github.com/api-platform/demo/blob/master/admin/src/authProvider.js).

admin/components.md

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
# Components
2+
3+
## Resource Components
4+
5+
### AdminGuesser
6+
7+
`<AdminGuesser>` creates a complete Admin Context and Interface, rendering automatically an [`<AdminUI>` component](https://marmelab.com/react-admin/Admin.html#unplugging-the-admin-using-admincontext-and-adminui) for resources exposed by a web API documented with any format supported by `@api-platform/api-doc-parser` (for Hydra documented APIs, use the [`<HydraAdmin>`component](admin/components.md#hydraadmin) instead). It also creates a [`schemaAnalyzer`](admin/components.md#schemaAnalyzer) context, where the schemaAnalyzer service (for getting information about the provided API documentation) is stored.
8+
The `<AdminGuesser>` renders all exposed resources by default, but you can choose what resource you want to render by passing [`<ResourceGuesser>` components](admin/components/#resourceguesser) as children.
9+
Deprecated resources are hidden by default, but you can add them back using an explicit `<ResourceGuesser>` component.
10+
11+
```javascript
12+
//App.js
13+
import { AdminGuesser, ResourceGuesser } from '@api-platform/admin';
14+
15+
const App = () => (
16+
<AdminGuesser
17+
entrypoint={entrypoint}
18+
dataProvider={dataProvider}
19+
authProvider={authProvider}>
20+
<ResourceGuesser
21+
name"books"
22+
list={BooksList}
23+
show={BooksShow}
24+
edit={BooksEdit}
25+
create={BooksCreate} />
26+
<ResourceGuesser name"authors" />
27+
</AdminGuesser>
28+
)
29+
30+
export default App;
31+
```
32+
33+
**Props**
34+
| Name | Type | Value | required | Description |
35+
|-------------------|--------------------|----------------|----------|----------------------------------------------------------------------------------|
36+
| dataProvider | object or function | - | yes | communicates with your API |
37+
| schemaAnalyzer | object | schemaAnalyzer | yes | retrieves resource type according to [Schema.org](https://schema.org) vocabulary |
38+
| children | node or function | - | no | - |
39+
| theme | object | theme | no | theme of your Admin App |
40+
| includeDeprecated | boolean | true or false | no | displays or not deprecated resources |
41+
42+
### ResourceGuesser
43+
44+
Based on React-Admin's [`<Resource>` component](https://marmelab.com/react-admin/Resource.html), the ResourceGuesser provides default props [`<CreateGuesser>`](admin/components.md#createguesser), [`<ListGuesser>`](admin/components.md#listguesser), [`<EditGuesser>`](admin/components.md#editguesser) and [`<ShowGuesser>`](admin/components.md#showguesser). Otherwise you can pass it your own CRUD components using `create`, `list`, `edit`, `show` props.
45+
46+
```javascript
47+
// App.js
48+
import { AdminGuesser, ResourceGuesser } from '@api-platform/admin';
49+
50+
const App = () => (
51+
<AdminGuesser
52+
entrypoint={entrypoint}
53+
dataProvider={dataProvider}
54+
schemaAnalyzer={schemaAnalyzer}
55+
>
56+
<ResourceGuesser
57+
name="books"
58+
list={BooksList}
59+
show={BooksShow}
60+
create={BooksCreate}
61+
edit={BooksEdit} />
62+
<ResourceGuesser name="reviews" />
63+
</AdminGuesser>
64+
);
65+
66+
export default App;
67+
```
68+
69+
**Props**
70+
| Name | Type | Value | required | Description |
71+
|------|--------|-------|----------|--------------------------|
72+
| name | string | - | yes | endpoint of the resource |
73+
74+
You can also use props accepted by React-Admin's [`<Resource>` component](https://marmelab.com/react-admin/Resource.html). For example, the props `list`, `show`, `create` & `edit`.
75+
76+
## Page Components
77+
78+
### ListGuesser
79+
80+
Based on React-Admin's [`<List>`](https://marmelab.com/react-admin/List.html), ListGuesser displays a list of resources in a [`<Datagrid>`](https://marmelab.com/react-admin/List.html#the-datagrid-component), according to children passed to it (usually [`<FieldGuesser>`](admin/components/#fieldguesser) or any [`field` component](https://marmelab.com/react-admin/Fields.html#basic-fields) available in React-Admin).
81+
Use `hasShow` and `hasEdit` props if you want to display `show` and `edit` buttons (both set to `true` by default).
82+
By default, `<ListGuesser>` comes with [`<Pagination>`](admin/components.md#pagination).
83+
84+
```javascript
85+
// BooksList.js
86+
import { FieldGuesser, ListGuesser } from '@api-platform/admin';
87+
import { ReferenceField, TextField } from 'react-admin';
88+
89+
export const BooksList = props => (
90+
<ListGuesser {...props}>
91+
<FieldGuesser source="author" />
92+
<FieldGuesser source="title" />
93+
<FieldGuesser source="rating" />
94+
<FieldGuesser source="description" />
95+
<FieldGuesser source="publicationDate" />
96+
</ListGuesser>
97+
);
98+
```
99+
**Props**
100+
| Name | Type | Value | required | Description |
101+
|----------|------------------|-------|----------|-----------------------------------------|
102+
| children | node or function | - | no | - |
103+
| resource | string | - | yes | endpoint of the resource |
104+
| filters | element | - | no | filters that can be applied to the list |
105+
106+
107+
You can also use props accepted by React-Admin's [`<List>`](https://marmelab.com/react-admin/List.html).
108+
109+
### CreateGuesser
110+
111+
Displays a creation page for a single item. Uses React-Admin's [`<Create>`](https://marmelab.com/react-admin/Edit.html) and [`<SimpleForm>`](https://marmelab.com/react-admin/CreateEdit.html#the-simpleform-component).
112+
For simple inputs, you can pass as children API Platform Admin's [`<InputGuesser>`](admin/components.md#inputguesser), or any React-Admin's [`Input components`](https://marmelab.com/react-admin/Inputs.html#input-components) for more complex inputs.
113+
114+
```javascript
115+
// BooksCreate.js
116+
import { CreateGuesser, InputGuesser } from '@api-platform/admin';
117+
118+
export const BooksCreate = props => (
119+
<CreateGuesser {...props}>
120+
<InputGuesser source="author" />
121+
<InputGuesser source="title" />
122+
<InputGuesser source="rating" />
123+
<InputGuesser source="description" />
124+
<InputGuesser source="publicationDate" />
125+
</CreateGuesser>
126+
);
127+
```
128+
129+
**Props**
130+
| Name | Type | Value | required | Description |
131+
| Name | Type | Value | required | Description |
132+
|----------|------------------|-------|----------|--------------------------|
133+
| children | node or function | - | no | - |
134+
| resource | string | - | yes | endpoint of the resource |
135+
136+
You can also use props accepted by React-Admin's [`<Create>`](https://marmelab.com/react-admin/Edit.html).
137+
138+
### EditGuesser
139+
140+
Displays an edition page for a single item. Uses React-Admin's [`<Edit>`](https://marmelab.com/react-admin/Edit.html) and [`<SimpleForm>`](https://marmelab.com/react-admin/CreateEdit.html#the-simpleform-component) components.
141+
For simple inputs, you can use API Platform Admin's [`<InputGuesser>`](admin/components.md#inputguesser), or any React-Admin's [`Input components`](https://marmelab.com/react-admin/Inputs.html#input-components) for more complex inputs.
142+
143+
```javascript
144+
// BooksEdit.js
145+
import { EditGuesser, InputGuesser } from '@api-platform/admin';
146+
147+
export const BooksEdit = props => (
148+
<EditGuesser {...props}>
149+
<InputGuesser source="author" />
150+
<InputGuesser source="title" />
151+
<InputGuesser source="rating" />
152+
<InputGuesser source="description" />
153+
<InputGuesser source="publicationDate" />
154+
</EditGuesser>
155+
);
156+
```
157+
158+
**Props**
159+
| Name | Type | Value | required | Description |
160+
|----------|------------------|-------|----------|--------------------------|
161+
| children | node or function | - | no | - |
162+
| resource | string | - | yes | endpoint of the resource |
163+
164+
You can also use props accepted by React-Admin's [`<Edit>`](https://marmelab.com/react-admin/Edit.html).
165+
166+
### ShowGuesser
167+
168+
Displays a detailed page for one item. Based on React-Admin's [`<Show>` component](https://marmelab.com/react-admin/Show.html). You can pass [`<FieldGuesser>`](admin/components.md#fieldguesser) as children for simple fields, or use any of React-Admin's [`Basic Fields`](https://marmelab.com/react-admin/Fields.html#basic-fields) for more complex fields.
169+
170+
```javascript
171+
// BooksShow.js
172+
import { FieldGuesser, ShowGuesser } from '@api-platform/admin';
173+
174+
export const BooksShow = props => (
175+
<ShowGuesser {...props}>
176+
<FieldGuesser source="author" />
177+
<FieldGuesser source="title" />
178+
<FieldGuesser source="rating" />
179+
<FieldGuesser source="description" />
180+
<FieldGuesser source="publicationDate" />
181+
</ShowGuesser>
182+
);
183+
```
184+
185+
**Props**
186+
| Name | Type | Value | required | Description |
187+
|----------|------------------|-------|----------|--------------------------|
188+
| children | node or function | - | no | - |
189+
| resource | string | - | yes | endpoint of the resource |
190+
191+
You can also use props accepted by React-Admin's [`<Show>` component](https://marmelab.com/react-admin/Show.html).
192+
193+
## Hydra
194+
195+
### HydraAdmin
196+
197+
Creates a complete Admin Context and Interface, as [`<AdminGuesser>`](/admin/components.md#adminguesser), but configured specially for [Hydra](https://www.hydra-cg.com/). If you want to use other formats (see supported formats: `@api-platform/api-doc-parser`) use the [`<AdminGuesser>`](admin/components.md#adminguesser) instead.
198+
199+
```javascript
200+
// App.js
201+
import { HydraAdmin, ResourceGuesser } from '@api-platform/admin';
202+
203+
const App = () => (
204+
<HydraAdmin
205+
entrypoint={entrypoint}
206+
dataProvider={dataProvider}
207+
authProvider={authProvider}
208+
>
209+
<ResourceGuesser name="books" />
210+
{ /* ... */ }
211+
</HydraAdmin>
212+
);
213+
214+
export default App;
215+
```
216+
217+
**Props**
218+
| Name | Type | Value | required | Description |
219+
|------------|--------|-------|----------|-----------------------|
220+
| entrypoint | string | - | yes | entrypoint of the API |
221+
222+
### dataProvider
223+
224+
Based on React-Admin's `Create`, `Delete`, `getList`, `getManyReference`, `GetOne`, `Update` methods, the `dataProvider` is used by API Platform Admin to communicate with the API. In addition, the specific `introspect` method parses your API documentation.
225+
Note that the `dataProvider` can be overrided to fit your API needs.
226+
227+
### schemaAnalyzer
228+
229+
Analyses your resources and retrieves their types according to the [Schema.org](https://schema.org) vocabulary.
230+
231+
## Other Components
232+
233+
### Pagination
234+
235+
Set by default in the [`<ListGuesser/>` component](/admin/components.md#listguesser), the `<Pagination/> component` uses React-Admin's [`<Pagination>` component](https://marmelab.com/react-admin/List.html#pagination). By default, it renders 30 items per page and displays a navigation UI. If you want to change the number of items per page or disable the pagination, see the [`Pagination` documentation](https://api-platform.com/docs/core/pagination/#pagination).
236+
237+
### FieldGuesser
238+
239+
Renders fields according to their types, using [`schemaAnalyzer`](/admin/components.md#schemaanalyzer). Based on React-Admin's [`<ReferenceField>` component](https://marmelab.com/react-admin/Fields.html#referencefield).
240+
241+
```javascript
242+
// BooksShow.js
243+
import { FieldGuesser, ShowGuesser } from '@api-platform/admin';
244+
245+
export const BooksShow = props => (
246+
<ShowGuesser {...props}>
247+
<FieldGuesser source="author" />
248+
<FieldGuesser source="title" />
249+
<FieldGuesser source="rating" />
250+
<FieldGuesser source="description" />
251+
<FieldGuesser source="publicationDate" />
252+
</ShowGuesser>
253+
)
254+
```
255+
**Props**
256+
| Name | Type | Value | required | Description |
257+
|--------|--------|-------|----------|--------------------------|
258+
| source | string | - | yes | endpoint of the resource |
259+
260+
You can also use props accepted by React-Admin's [`Basic Fields`](https://marmelab.com/react-admin/Fields.html#basic-fields).
261+
262+
### InputGuesser
263+
264+
Uses React-Admin's [`<ReferenceInput>`](https://marmelab.com/react-admin/Inputs.html#referenceinput) to generate inputs according to your API documentation (e.g. number HTML input for numbers, checkbox for booleans, selectbox for relationships...)
265+
266+
**Props**
267+
| Name | Type | Value | required | Description |
268+
|--------|--------|-------|----------|--------------------------|
269+
| source | string | - | yes | endpoint of the resource |

admin/file-upload.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Handling File Upload
2+
3+
If you need to handle the file upload in the server part, please follow [the related documentation](../core/file-upload.md).
4+
5+
This documentation assumes you have a `/media_objects` endpoint accepting `multipart/form-data`-encoded data.
6+
7+
To manage the upload in the admin part, you need to customize [the create form](customizing.md#customizing-the-create-form) or [the edit form](customizing.md#customizing-the-edit-form).
8+
9+
Add a [FileInput](https://marmelab.com/react-admin/Inputs.html#fileinput) as a child of the guesser. For example, for the create form:
10+
11+
```js
12+
import {
13+
HydraAdmin,
14+
ResourceGuesser,
15+
CreateGuesser,
16+
InputGuesser
17+
} from "@api-platform/admin";
18+
import { FileField, FileInput } from "react-admin";
19+
20+
const MediaObjectsCreate = props => (
21+
<CreateGuesser {...props}>
22+
<FileInput source="file">
23+
<FileField source="src" title="title" />
24+
</FileInput>
25+
</CreateGuesser>
26+
);
27+
28+
export default () => (
29+
<HydraAdmin entrypoint="https://demo.api-platform.com">
30+
<ResourceGuesser name="media_objects" create={MediaObjectsCreate} />
31+
{/* ... */}
32+
</HydraAdmin>
33+
);
34+
```
35+
36+
And that's it!
37+
You don't need to decorate the data provider if you are using the Hydra one: it detects that you have used a `FileInput` and uses a `multipart/form-data` request instead of a JSON-LD one.

0 commit comments

Comments
 (0)