Repository: api-platform/admin
Pull request #529 (2024-02-27 13:51:16)

pass useEmbedded to mercure manager's data transformer
Data provider does not pass `useEmbedded` setting to mercure manager's data transformer, default value (`false`) is always used  here.  

So if there is any embedded field on the list it is cleared (as side effect) by mercure update, ex:
```
  <NumberField
     label="Price"
     source="cost.price"
     options={{ style: "currency", currency: "EUR" }}
   />
```

fetched data:

```
      (...)
      "cost": {
        "@id": "/costs/375384",
        "@type": "https://schema.org/PriceSpecification",
        "price": 10,
        "priceCurrency": "EUR",
       },
```
But updated record that can't be properly rendered will be:

```
{
  (...)
  cost: '/costs/375384`,
}
```

This PR fix this passing `useEmbedded` prop to mercure dataTransformer.



Patch:
diff --git a/src/CreateGuesser.tsx b/src/CreateGuesser.tsx
index 77d8754..4faacde 100644
--- a/src/CreateGuesser.tsx
+++ b/src/CreateGuesser.tsx
@@ -59,6 +59,7 @@ export const IntrospectedCreateGuesser = ({
   warnWhenUnsavedChanges,
   sanitizeEmptyValues = true,
   formComponent,
+  viewComponent,
   children,
   ...props
 }: IntrospectedCreateGuesserProps) => {
@@ -162,7 +163,7 @@ export const IntrospectedCreateGuesser = ({
   const FormType = hasFormTab ? TabbedForm : SimpleForm;
 
   return (
-    <Create resource={resource} {...props}>
+    <Create resource={resource} component={viewComponent} {...props}>
       <FormType
         onSubmit={save}
         mode={mode}
diff --git a/src/EditGuesser.tsx b/src/EditGuesser.tsx
index 9876f97..7c21cb4 100644
--- a/src/EditGuesser.tsx
+++ b/src/EditGuesser.tsx
@@ -62,6 +62,7 @@ export const IntrospectedEditGuesser = ({
   toolbar,
   warnWhenUnsavedChanges,
   formComponent,
+  viewComponent,
   sanitizeEmptyValues = true,
   children,
   ...props
@@ -197,6 +198,7 @@ export const IntrospectedEditGuesser = ({
       id={id}
       mutationMode={mutationMode}
       redirect={redirectTo}
+      component={viewComponent}
       transform={(data: Partial<RaRecord>) => ({
         ...data,
         extraInformation: { hasFileField },
diff --git a/src/FieldGuesser.tsx b/src/FieldGuesser.tsx
index 90e123d..d715d81 100644
--- a/src/FieldGuesser.tsx
+++ b/src/FieldGuesser.tsx
@@ -23,6 +23,7 @@ import type {
   NumberFieldProps,
   ReferenceArrayFieldProps,
   ReferenceFieldProps,
+  SingleFieldListProps,
   TextFieldProps,
   UrlFieldProps,
 } from 'react-admin';
@@ -65,11 +66,13 @@ const renderField = (
     }
 
     const fieldName = schemaAnalyzer.getFieldNameFromSchema(field.reference);
+    const { linkType, ...rest } = props as ReferenceArrayFieldProps &
+      Pick<SingleFieldListProps, 'linkType'>;
     return (
       <ReferenceArrayField
-        {...(props as ReferenceArrayFieldProps)}
+        {...(rest as ReferenceArrayFieldProps)}
         reference={field.reference.name}>
-        <SingleFieldList>
+        <SingleFieldList linkType={linkType}>
           <ChipField source={fieldName} key={fieldName} />
         </SingleFieldList>
       </ReferenceArrayField>
diff --git a/src/ListGuesser.tsx b/src/ListGuesser.tsx
index 6583f90..9d24464 100644
--- a/src/ListGuesser.tsx
+++ b/src/ListGuesser.tsx
@@ -70,6 +70,7 @@ export const IntrospectedListGuesser = ({
   empty,
   hover,
   expand,
+  expandSingle,
   optimized,
   size,
   children,
@@ -122,6 +123,7 @@ export const IntrospectedListGuesser = ({
         empty={empty}
         hover={hover}
         expand={expand}
+        expandSingle={expandSingle}
         optimized={optimized}
         size={size}
         sx={datagridSx}>
diff --git a/src/ShowGuesser.tsx b/src/ShowGuesser.tsx
index a71cfcb..bcb9959 100644
--- a/src/ShowGuesser.tsx
+++ b/src/ShowGuesser.tsx
@@ -44,6 +44,7 @@ export const IntrospectedShowGuesser = ({
   writableFields,
   schema,
   schemaAnalyzer,
+  viewComponent,
   children,
   ...props
 }: IntrospectedShowGuesserProps) => {
@@ -70,7 +71,7 @@ export const IntrospectedShowGuesser = ({
   const ShowLayout = hasTab ? TabbedShowLayout : SimpleShowLayout;
 
   return (
-    <Show {...props}>
+    <Show component={viewComponent} {...props}>
       <ShowLayout>{fieldChildren}</ShowLayout>
     </Show>
   );
diff --git a/src/hydra/dataProvider.ts b/src/hydra/dataProvider.ts
index bf1ff01..3ece47d 100644
--- a/src/hydra/dataProvider.ts
+++ b/src/hydra/dataProvider.ts
@@ -388,7 +388,9 @@ function dataProvider(
         } = params as GetListParams | GetManyReferenceParams;
 
         if (order && field) {
-          url.searchParams.set(`order[${field}]`, order);
+          field.split(',').forEach((fieldName) => {
+            url.searchParams.set(`order[${fieldName.trim()}]`, order);
+          });
         }
         if (page) url.searchParams.set('page', page.toString());
         if (perPage) url.searchParams.set('itemsPerPage', perPage.toString());
@@ -701,8 +703,13 @@ function dataProvider(
     apiDocumentationParser,
     mercure: factoryParams.mercure ?? true,
   });
-  mercureManager.setDataTransformer(
-    transformJsonLdDocumentToReactAdminDocument,
+  mercureManager.setDataTransformer((jsonLdDocument) =>
+    transformJsonLdDocumentToReactAdminDocument(
+      jsonLdDocument,
+      true,
+      !disableCache,
+      useEmbedded,
+    ),
   );
 
   return {
diff --git a/src/hydra/fetchHydra.ts b/src/hydra/fetchHydra.ts
index dcc9f70..e64597c 100644
--- a/src/hydra/fetchHydra.ts
+++ b/src/hydra/fetchHydra.ts
@@ -4,7 +4,7 @@ import {
   getDocumentationUrlFromHeaders,
 } from '@api-platform/api-doc-parser';
 import jsonld from 'jsonld';
-import type { ContextDefinition, NodeObject } from 'jsonld';
+import type { NodeObject } from 'jsonld';
 import type { JsonLdObj } from 'jsonld/jsonld-spec';
 import type { HttpClientOptions, HydraHttpClientResponse } from '../types.js';
 
@@ -55,13 +55,11 @@ function fetchHydra(
         });
       };
 
-      return documentLoader(getDocumentationUrlFromHeaders(headers))
-        .then((response) =>
-          jsonld.expand(body, {
-            expandContext: response.document as ContextDefinition,
-          }),
-        )
-
+      return jsonld
+        .expand(body, {
+          base: getDocumentationUrlFromHeaders(headers),
+          documentLoader,
+        })
         .then((json) =>
           Promise.reject(
             new HttpError(
diff --git a/src/types.ts b/src/types.ts
index 30032f7..3a619a9 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -47,6 +47,7 @@ import type {
   ResourceProps,
   ShowProps,
   SimpleFormProps,
+  SingleFieldListProps,
   TabbedFormProps,
   TextFieldProps,
   TextInputProps,
@@ -370,6 +371,7 @@ type CreateFormProps = Omit<
   CreateProps & SimpleFormProps & TabbedFormProps,
   'children'
 > &
+  Partial<PickRename<CreateProps, 'component', 'viewComponent'>> &
   Partial<
     PickRename<SimpleFormProps & TabbedFormProps, 'component', 'formComponent'>
   > & {
@@ -388,6 +390,7 @@ type EditFormProps = Omit<
   EditProps & SimpleFormProps & TabbedFormProps,
   'children'
 > &
+  Partial<PickRename<EditProps, 'component', 'viewComponent'>> &
   Partial<
     PickRename<SimpleFormProps & TabbedFormProps, 'component', 'formComponent'>
   > & {
@@ -421,9 +424,10 @@ export type ListGuesserProps = Omit<
 type ShowFormProps = Omit<
   ShowProps & SimpleFormProps & TabbedFormProps,
   'children'
-> & {
-  children?: ReactNode;
-};
+> &
+  Partial<PickRename<ShowProps, 'component', 'viewComponent'>> & {
+    children?: ReactNode;
+  };
 
 export type IntrospectedShowGuesserProps = ShowFormProps &
   IntrospectedGuesserProps;
@@ -452,7 +456,7 @@ export type FieldProps =
   | UrlFieldProps
   | EmailFieldProps
   | ArrayFieldProps
-  | ReferenceArrayFieldProps
+  | (ReferenceArrayFieldProps & Pick<SingleFieldListProps, 'linkType'>)
   | EnumFieldProps
   | ReferenceFieldProps;
 


Test patch:
diff --git a/src/hydra/dataProvider.test.ts b/src/hydra/dataProvider.test.ts
index 1d07e17..874ab8a 100644
--- a/src/hydra/dataProvider.test.ts
+++ b/src/hydra/dataProvider.test.ts
@@ -603,4 +603,45 @@ describe('Transform a React Admin request to an Hydra request', () => {
       'http://localhost/entrypoint/comments?order%5Bid%5D=ASC&page=1&itemsPerPage=30',
     );
   });
+
+  test('React Admin get list with compound order', async () => {
+    mockFetchHydra.mockClear();
+    mockFetchHydra.mockReturnValueOnce(
+      Promise.resolve({
+        status: 200,
+        headers: new Headers(),
+        json: {
+          'hydra:member': [
+            { '@id': '/comments/423' },
+            { '@id': '/comments/976' },
+          ],
+          'hydra:totalItems': 2,
+          'hydra:view': {
+            '@id': '/comments?page=1',
+            '@type': 'hydra:PartialCollectionView',
+            'hydra:first': '/comments?page=1',
+            'hydra:last': '/comments?page=1',
+            'hydra:next': '/comments?page=1',
+          },
+        },
+      }),
+    );
+    const result = await dataProvider.current.getList('comments', {
+      pagination: { page: 1, perPage: 30 },
+      sort: { field: 'text, id', order: 'DESC' },
+      filter: false,
+    });
+    expect(result).toEqual({
+      data: [
+        { '@id': '/comments/423', id: '/comments/423' },
+        { '@id': '/comments/976', id: '/comments/976' },
+      ],
+      total: 2,
+    });
+    const url = mockFetchHydra.mock.calls?.[0]?.[0] ?? new URL('https://foo');
+    expect(url).toBeInstanceOf(URL);
+    expect(url.toString()).toEqual(
+      'http://localhost/entrypoint/comments?order%5Btext%5D=DESC&order%5Bid%5D=DESC&page=1&itemsPerPage=30',
+    );
+  });
 });