Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix : hydra #585

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ If you already have a project in progress, you can develop directly from it.

The instructions below explain how to install the source version of API Platform Admin in your project and contribute a patch.

Your client should already use `@api-platform/admin` and its bootstrap file (usually: `src/App.tsx`) should at least contains:
Your client should already use `@api-platform/admin` and its bootstrap file (usually: `src/App.tsx`) should at least contains:

```tsx
import React from 'react';
Expand Down Expand Up @@ -97,9 +97,9 @@ yarn dev --force

#### Running Admin Through Storybook

If you do not have an existing project, you can use [Storybook](https://storybook.js.org/) to visualize changes in the source code, and test them.
If you do not have an existing project, you can use [Storybook](https://storybook.js.org/) to visualize changes in the source code, and test them.

This development stack consists of two Docker containers:
This development stack consists of two Docker containers:
- `pwa`: containing the `<Admin>` sources and Storybook;
- `php`: holding the API sources.

Expand All @@ -119,7 +119,7 @@ Now you can go to http://localhost:3000/ to see the Storybook instance in action

To run a command directly inside a container, run:
```shell
# Run a command in the php container
# Run a command in the php container
docker compose exec -T php your-command

# Run a command in the pwa container
Expand All @@ -137,9 +137,9 @@ yarn test
yarn test-storybook --url http://127.0.0.1:3000/
```

If you add a new feature, don't forget to add tests for it.
If you add a new feature, don't forget to add tests for it.
- Functionnal tests are written with [Jest](https://jestjs.io/) and [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/);
- End-to-end tests are written with [Storybook play funcitons](https://storybook.js.org/docs/writing-stories/play-function/).
- End-to-end tests are written with [Storybook play functions](https://storybook.js.org/docs/writing-stories/play-function/).

### Matching Coding Standards

Expand Down
2 changes: 1 addition & 1 deletion api/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"php": ">=8.2",
"ext-ctype": "*",
"ext-iconv": "*",
"api-platform/core": "^3.2",
"api-platform/core": "^4.0.4",
"doctrine/doctrine-bundle": "^2.7",
"doctrine/doctrine-migrations-bundle": "^3.2",
"doctrine/orm": "^3.0",
Expand Down
4 changes: 0 additions & 4 deletions api/config/packages/api_platform.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,3 @@ api_platform:
cache_headers:
vary: ['Content-Type', 'Authorization', 'Origin']
extra_properties:
standard_put: true
rfc_7807_compliant_errors: true
event_listeners_backward_compatibility_layer: false
keep_legacy_inflector: false
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
"license": "MIT",
"sideEffects": false,
"dependencies": {
"@api-platform/api-doc-parser": "^0.16.2",
"jsonld": "^8.1.0",
"@api-platform/api-doc-parser": "^0.16.4",
"jsonld": "^8.3.2",
"lodash.isplainobject": "^4.0.6",
"react-admin": "^5.0.3"
},
Expand Down
4 changes: 1 addition & 3 deletions src/dataProvider/adminDataProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ export default (
introspect: (_resource = '', _params = {}) =>
apiSchema
? Promise.resolve({ data: apiSchema })
: apiDocumentationParser(docEntrypointUrl.toString(), {
headers: { accept: 'application/ld+json' },
})
: apiDocumentationParser(docEntrypointUrl.toString())
.then(({ api }: ApiDocumentationParserResponse) => {
if (api.resources && api.resources.length > 0) {
apiSchema = { ...api, resources: api.resources };
Expand Down
57 changes: 57 additions & 0 deletions src/hydra/dataProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -645,4 +645,61 @@ describe('Transform a React Admin request to an Hydra request', () => {
'http://localhost/entrypoint/comments?order%5Btext%5D=DESC&order%5Bid%5D=DESC&page=1&itemsPerPage=30',
);
});

test('React Admin get list without hydra prefix', async () => {
mockFetchHydra.mockClear();
mockFetchHydra.mockReturnValue(
Promise.resolve({
status: 200,
headers: new Headers(),
json: { member: [], totalItems: 3 },
}),
);
await dataProvider.current.getList('resource', {
pagination: {
page: 1,
perPage: 30,
},
sort: {
order: 'ASC',
field: '',
},
filter: {
simple: 'foo',
nested: { param: 'bar' },
sub_nested: { sub: { param: true } },
array: ['/iri/1', '/iri/2'],
nested_array: { nested: ['/nested_iri/1', '/nested_iri/2'] },
exists: { foo: true },
nested_date: { date: { before: '2000' } },
nested_range: { range: { between: '12.99..15.99' } },
},
searchParams: { pagination: 'true' },
});
const searchParams = Array.from(
mockFetchHydra.mock.calls?.[0]?.[0]?.searchParams.entries() ?? [],
);
expect(searchParams[0]).toEqual(['pagination', 'true']);
expect(searchParams[1]).toEqual(['page', '1']);
expect(searchParams[2]).toEqual(['itemsPerPage', '30']);
expect(searchParams[3]).toEqual(['simple', 'foo']);
expect(searchParams[4]).toEqual(['nested.param', 'bar']);
expect(searchParams[5]).toEqual(['sub_nested.sub.param', 'true']);
expect(searchParams[6]).toEqual(['array[0]', '/iri/1']);
expect(searchParams[7]).toEqual(['array[1]', '/iri/2']);
expect(searchParams[8]).toEqual([
'nested_array.nested[0]',
'/nested_iri/1',
]);
expect(searchParams[9]).toEqual([
'nested_array.nested[1]',
'/nested_iri/2',
]);
expect(searchParams[10]).toEqual(['exists[foo]', 'true']);
expect(searchParams[11]).toEqual(['nested_date.date[before]', '2000']);
expect(searchParams[12]).toEqual([
'nested_range.range[between]',
'12.99..15.99',
]);
});
});
50 changes: 36 additions & 14 deletions src/hydra/dataProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import type {
DataProviderType,
HydraCollection,
HydraDataProviderFactoryParams,
HydraHttpClientResponse,
HydraHttpClientResponse, HydraView,
MercureOptions,
SearchParams,
} from '../types.js';
Expand Down Expand Up @@ -160,6 +160,16 @@ const defaultParams: Required<
disableCache: false,
};

function normalizeHydraKey(json: JsonLdObj, key: string): JsonLdObj {
if (json[`hydra:${key}`]) {
const copy = JSON.parse(JSON.stringify(json));
copy[key] = copy[`hydra:${key}`];
delete copy[`hydra:${key}`];
return copy;
}
return json;
}

/**
* Maps react-admin queries to a Hydra powered REST API
*
Expand Down Expand Up @@ -545,22 +555,22 @@ function dataProvider(

switch (type) {
case GET_LIST:
case GET_MANY_REFERENCE:
case GET_MANY_REFERENCE: {
if (!response.json) {
return Promise.reject(
new Error(`An empty response was received for "${type}".`),
);
}
if (!('hydra:member' in response.json)) {
const json = normalizeHydraKey(response.json, 'member');
if (!json.member) {
return Promise.reject(
new Error(`Response doesn't have a "hydra:member" field.`),
new Error("Response doesn't have a member field."),
);
}
// TODO: support other prefixes than "hydra:"
// eslint-disable-next-line no-case-declarations
const hydraCollection = response.json as HydraCollection;
let hydraCollection = json as HydraCollection;
return Promise.resolve(
hydraCollection['hydra:member'].map((document) =>
hydraCollection.member.map((document: JsonLdObj) =>
transformJsonLdDocumentToReactAdminDocument(
document,
true,
Expand All @@ -577,17 +587,29 @@ function dataProvider(
),
)
.then((data) => {
if (hydraCollection['hydra:totalItems'] !== undefined) {
hydraCollection = normalizeHydraKey(
hydraCollection,
'totalItems',
) as HydraCollection;
if (hydraCollection.totalItems !== undefined) {
return {
data,
total: hydraCollection['hydra:totalItems'],
total: hydraCollection.totalItems,
};
}
if (hydraCollection['hydra:view']) {
hydraCollection = normalizeHydraKey(
hydraCollection,
'view',
) as HydraCollection;
if (hydraCollection.view) {
let hydraView = normalizeHydraKey(
hydraCollection.view,
'next',
) as HydraView;
hydraView = normalizeHydraKey(hydraView, 'previous') as HydraView;
const pageInfo = {
hasNextPage: !!hydraCollection['hydra:view']['hydra:next'],
hasPreviousPage:
!!hydraCollection['hydra:view']['hydra:previous'],
hasNextPage: !!hydraView.next,
hasPreviousPage: !!hydraView.previous,
};
return {
data,
Expand All @@ -599,7 +621,7 @@ function dataProvider(
data,
};
});

}
case DELETE:
return Promise.resolve({ data: { id: (params as DeleteParams).id } });

Expand Down
16 changes: 8 additions & 8 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,17 @@ export type DataTransformer = (parsedData: any) => ApiPlatformAdminRecord;
export type Hydra = JsonLdObj | HydraCollection;

export interface HydraView extends JsonLdObj {
'@type': 'hydra:PartialCollectionView';
'hydra:first': string;
'hydra:last': string;
'hydra:next': string;
'hydra:previous': string;
'@type': string;
first: string;
last: string;
next: string;
previous: string;
}

export interface HydraCollection extends JsonLdObj {
'hydra:member': JsonLdObj[];
'hydra:totalItems'?: number;
'hydra:view'?: HydraView;
member: JsonLdObj[];
totalItems?: number;
view?: HydraView;
}

export interface HttpClientOptions {
Expand Down