api-platform / admin

A beautiful and fully-featured administration interface builder for hypermedia APIs
https://api-platform.com/docs/admin/
MIT License
480 stars 130 forks source link

Custom route for hydraDataProvider #478

Closed pamuche closed 1 year ago

pamuche commented 1 year ago

Hello,

I've got a problem with calling a custom API-route with hydraDataProvider. I know standard update route is

PUT {resource}/{id}

I'd like to update and clone the resource (to create a new issue from the resource for revision management). I overwrite the standard PUT method route in api-platform and now a new issue for every resource will be created when "editing" and saving the resource.

Now I need (in superadmin-cases) to save the resource without cloning and creating a new issue (the formerly standard way). For that I created a new route

PUT {resource}/{id}/edit-without-new-issue

But I didn't find a way to get it called within hydraProvider with all the hydra transformations (because it's for all standard CRUD/mercure cases). So I copied the dataprovider and cloned the UPDATE part calling it UPDATE_WITHOUT_NEW_ISSUE and use my custom route-url...

https://github.com/api-platform/admin/blob/414d297c692f0b0271ba313d393e8c32dfae0e0f/src/hydra/dataProvider.ts#L467-L478

Is there a way to get this a little bit nicer? I can provide a PR if you have an idea/interest for an custom route extention.

Thank you very much for this delicate piece of software

alanpoulain commented 1 year ago

Why not creating a wrapper/proxy around the Hydra data provider and adding your own method (updateWithoutNewIssue) with its own simplified implementation? I'm not sure we want to add this possibility inside the current implementation, it will add too much complexity into it IMO. Then in your component you can use this method with useDataProvider.

pamuche commented 1 year ago

Thank you for your quick response. Hmm, maybe I didn't really get the idea of Wrapper/Proxies right, but in my mind it isn't possible to use transformReactAdminDataToRequestBody in my own (extended) implementation. Maybe you have a good site to find some information for a wrapper you are talking about.

pamuche commented 1 year ago

These react-admin-, jsonld- transform... and convert... fuctions are very useful for this task. Maybe it is possible to provide them through an hook. Or do you see another way to get my responses/request properly formatted for my custom route. At the end, I only need to adjust the url...

alanpoulain commented 1 year ago

You have an example of a decorated data provider in the code: https://github.com/api-platform/admin/blob/main/src/openapi/dataProvider.ts It uses both methods from a data provider given in its parameters and from the admin data provider: https://github.com/api-platform/admin/blob/56aec68b3a4b2a9dd9d6b6f0b59230c7b3a5507b/src/openapi/dataProvider.ts#L66 You can do something like this too.

pamuche commented 1 year ago

Ah yes, this looks cleaner than my approach. I will dive into it. Meanwhile and in lack of time I solved my problem like this (knowing it isn't the cleanest way, but maybe helps for people need to call custom routes):

First created a custom Save Button, which adds an flag to the submitted header:

    <SaveButton type="button" onClick={() => setValue('edit-issue', "true")}/>

and then in dataProvider.ts I look for the setted value change the Url accordingly and then remove the value from the body again:

const fetchHydra = (url, options = {}) => {

  let newUrl = url
  let newHeaders = getHeaders()

  const bodyJSON = options.body && JSON.parse(options.body)

  if (bodyJSON && bodyJSON["edit-issue"]) {
    // newHeaders = {...getHeaders(), "edit-issue": true} // its also possible to set an header value
    newUrl = {...newUrl, href: newUrl.href + "/edit-issue"}
  }

  //cleans up body object
  bodyJSON && bodyJSON["edit-issue"] ? delete bodyJSON["edit-issue"] : ""
  const newOptions = bodyJSON ? {...options, body: JSON.stringify(bodyJSON)} : options

  return baseFetchHydra(newUrl, {
    ...newOptions,
    headers: newHeaders,
  });
}

Well, think this is not pretty clean, but maybe helps someone