5
votes

Prise en charge de l'imbrication des ressources

Je me demande s'il est possible de configurer DataProvider / Resource / List pour prendre en charge les URL REST telles que api / users / 1 / roles ?

Pour l'API RESTful, il est très courant d'utiliser obtenir les enfants de certaines entités parentes, mais je ne peux pas comprendre comment configurer React Admin et y parvenir. J'utilise DataProvider personnalisé basé sur le backend de spécifications OData.

Je comprends que je peux obtenir les rôles de certains utilisateurs par requête filtrée sur api / roles? Filter = {userId: 1} ou quelque chose comme ça, mais mon problème est que mes utilisateurs et mes rôles sont dans une relation plusieurs-à-plusieurs afin que les références de relation soient stockées dans un tableau croisé dynamique. En d'autres termes, je n'ai pas de référence sur l'utilisateur dans le tableau des rôles, donc je ne peux pas les filtrer.

Est-ce que je supervise quelque chose ou y a-t-il une approche que je ne vois tout simplement pas?

MODIFIER: L'API REST est intégrée à la spécification OData et prend en charge les relations plusieurs-à-plusieurs avec une table pivot (ou intermédiaire) classique. Ce tableau n'est pas exposé dans l'API, mais est utilisé dans des URL comme celle ci-dessus. Je ne peux donc pas y accéder directement en tant que ressource.

Schéma pour l'utilisateur - Les relations entre les rôles sont également assez standard.

|----------|    |-----------|     |--------|
| USER     |    | User_Role |     | Role   |
|----------|    |-----------|     |--------|
| Id       |-\  | Id        |   /-| Id     |
| Login    |  \-| UserId    |  /  | Name   |
| Password |    | RoleId    |-/   | Code   |
|----------|    |-----------|     |--------|


1 commentaires

Pouvez-vous spécifier la conception de votre base de données afin que je puisse mieux comprendre la question? Je ne suis pas familier avec react-admin mais je pourrais vous dire à quoi pourrait ressembler l'API REST. Pouvez-vous changer la conception de votre base de données?


3 Réponses :


6
votes

TL; DR: Par défaut, React Admin ne prend pas en charge les ressources imbriquées, vous devez écrivez un fournisseur de données personnalisé .

Cette question a reçu une réponse sur un problème précédent: maremelab / react-admin # 261

Réponse détaillée

Le fournisseur de données par défaut dans React Admin est ra-data-simple-rest .

Comme expliqué dans sa documentation, cette bibliothèque ne prend pas en charge les ressources imbriquées car elle utilise uniquement le nom de la ressource et l'ID de ressource pour créer une URL de ressource:

 Fournisseur de données REST simple

Pour prendre en charge les ressources imbriquées, vous devez écrire votre propre fournisseur de données.

La prise en charge des ressources imbriquées est une récurrente fonctionnalité demande mais, à l’époque, l’équipe principale ne souhaite pas gérer cette charge de travail.

Je suggère fortement de rassembler vos forces et d'écrire un fournisseur de données externe et de le publier comme un fournisseur ra-data-odata . Ce serait un excellent ajout et nous serons honorés de vous aider avec ce package externe.


4 commentaires

Merci pour votre réponse, j'ai déjà presque fini avec le fournisseur de données personnalisé pour le dialecte OData. Pour que cela fonctionne vraiment, j'ai besoin d'implémenter des ressources imbriquées, de meilleurs champs de référence plusieurs-à-plusieurs et des entrées. En ce moment, je me sens découragé parce que je ne me sens pas à la hauteur de la tâche. Comme vous l'avez dit, c'est une charge de travail. Je ne veux rien promettre, mais je veux continuer avec ce projet génial, alors je devrai éventuellement fourcher react-admin et l'étendre au lieu de trouver des solutions de contournement que je déteste faire. Mais pour l'instant, je dois faire des compromis. Merci encore pour votre réponse :)


Oui, ça l'est! Avez-vous un exemple de départ, un référentiel OSS? Si vous le souhaitez, nous pouvons vous aider à rediriger les volontaires vers votre repo.


Pour l'instant, j'ai un dépôt privé, mais cela ne me dérange pas de le transférer sur Github. Pour le moment, c'est un peu sale à cause des solutions de contournement, mais je peux publier une ancienne version de mon fournisseur odata personnalisé qui fonctionne bien avec les filtres, le tri et tout ce que vous attendez pour les ressources imbriquées et les relations plusieurs-à-plusieurs. Pour l'API REST OData simple, ça devrait aller


C'est un bon début pour une version 0.1. N'hésitez pas à créer un référentiel sur GitHub et à créer un problème où vous me ferez un ping @Kmaschta



4
votes

La réponse à votre question était déjà ici , mais j'aimerais vous parler de ma solution de contournement pour que React-Admin travailler avec des relations plusieurs-à-plusieurs.

Comme indiqué dans la réponse mentionnée, vous devez étendre le DataProvider pour qu'il récupère les ressources d'une relation plusieurs-à-plusieurs. Cependant, vous devez utiliser le nouveau verbe REST, supposons que GET_MANY_MANY_REFERENCE quelque part sur votre application. Étant donné que différents services / API REST peuvent avoir différents formats de routes pour récupérer les ressources associées, je n'ai pas pris la peine d'essayer de créer un nouveau DataProvider, je sais que ce n'est pas une excellente solution, mais pour des délais courts, c'est très simple.

Ma solution était de s'inspirer de et de construire un nouveau composant pour les relations plusieurs-à-plusieurs. Ce composant récupère les enregistrements associés sur componentDidMount à l'aide de fetch API . La réponse utilise les données de réponse pour créer des objets, une donnée étant un objet avec des clés étant des identifiants d'enregistrement, et valorise l'objet d'enregistrement respectif, et un tableau d'id avec les identifiants des enregistrements. Ceci est transmis aux enfants avec d'autres variables d'état telles que page, sort, perPage, total, pour gérer la pagination et l'ordre des données. Sachez que changer l'ordre des données dans un Datagrid signifie qu'une nouvelle demande sera faite à l'API. Ce composant est divisé en un contrôleur et une vue, comme , où le contrôleur récupère les données, les gère et les transmet aux enfants et à la vue qui reçoit les données du contrôleur et les transmet aux enfants qui rendent son contenu. Cela m'a permis de rendre des données de relations plusieurs-à-plusieurs sur un Datagrid, même si, avec une certaine limitation, est un composant à agréger à mon projet et ne fonctionne qu'avec mon API actuelle si quelque chose change, je devrais changer le champ en, mais pour l'instant, cela fonctionne et peut être réutilisé avec mon application.

Les détails de l'implémentation sont les suivants:

//ReferenceManyManyField
export const ReferenceManyManyField = ({children, ...prop}) => {
  if(React.Children.count(children) !== 1) {
    throw new Error( '<ReferenceManyField> only accepts a single child (like <Datagrid>)' )
  }

  return <ReferenceManyManyFieldController {...props}>
    {controllerProps => (<ReferenceManyManyFieldView 
    {...props} 
    {...{children, ...controllerProps}} /> )}
  </ReferenceManyManyFieldController>

//ReferenceManyManyFieldController
class ReferenceManyManyFieldController extends Component {

  constructor(props){
    super(props)
    //State to manage sorting and pagination, <ReferecemanyField> uses some props from react-redux 
    //I discarded react-redux for simplicity/control however in the final solution react-redux might be incorporated
    this.state = {
      sort: props.sort,
      page: 1,
      perPage: props.perPage,
      total: 0
    }
  }

  componentWillMount() {
    this.fetchRelated()
  }

  //This could be a call to your custom dataProvider with a new REST verb
  fetchRelated({ record, resource, reference, showNotification, fetchStart, fetchEnd } = this.props){
    //fetchStart and fetchEnd are methods that signal an operation is being made and make active/deactivate loading indicator, dataProvider or sagas should do this
    fetchStart()
    dataProvider(GET_LIST,`${resource}/${record.id}/${reference}`,{
      sort: this.state.sort,
      pagination: {
        page: this.state.page,
        perPage: this.state.perPage
      }
    })
    .then(response => {
      const ids = []
      const data = response.data.reduce((acc, record) => {
        ids.push(record.id)
        return {...acc, [record.id]: record}
      }, {})
      this.setState({data, ids, total:response.total})
    })
    .catch(e => {
      console.error(e)
      showNotification('ra.notification.http_error')
    })
    .finally(fetchEnd)
  }

  //Set methods are here to manage pagination and ordering,
  //again <ReferenceManyField> uses react-redux to manage this
  setSort = field => {
    const order =
        this.state.sort.field === field &&
        this.state.sort.order === 'ASC'
            ? 'DESC'
            : 'ASC';
    this.setState({ sort: { field, order } }, this.fetchRelated);
  };

  setPage = page => this.setState({ page }, this.fetchRelated);

  setPerPage = perPage => this.setState({ perPage }, this.fetchRelated);

  render(){
    const { resource, reference, children, basePath } = this.props
    const { page, perPage, total } = this.state;

    //Changed basePath to be reference name so in children can nest other resources, not sure why the use of replace, maybe to maintain plurals, don't remember 
    const referenceBasePath = basePath.replace(resource, reference);

    return children({
      currentSort: this.state.sort,
      data: this.state.data,
      ids: this.state.ids,
      isLoading: typeof this.state.ids === 'undefined',
      page,
      perPage,
      referenceBasePath,
      setPage: this.setPage,
      setPerPage: this.setPerPage,
      setSort: this.setSort,
      total
    })
  }

}

ReferenceManyManyFieldController.defaultProps = {
  perPage: 25,
  sort: {field: 'id', order: 'DESC'}
}

//ReferenceManyManyFieldView
export const ReferenceManyManyFieldView = ({
  children,
  classes = {},
  className,
  currentSort,
  data,
  ids,
  isLoading,
  page,
  pagination,
  perPage,
  reference,
  referenceBasePath,
  setPerPage,
  setPage,
  setSort,
  total
}) => (
  isLoading ? 
    <LinearProgress className={classes.progress} />
  :
      <Fragment>
        {React.cloneElement(children, {
          className,
          resource: reference,
          ids,
          data,
          basePath: referenceBasePath,
          currentSort,
          setSort,
          total
        })}
        {pagination && React.cloneElement(pagination, {
          page,
          perPage,
          setPage,
          setPerPage,
          total
        })}
      </Fragment>
);

//Assuming the question example, the presentation of many-to-many relationship would be something like
const UserShow = ({...props}) => (
  <Show {...props}>
    <TabbedShowLayout>
      <Tab label='User Roles'>
        <ReferenceManyManyField source='users' reference='roles' addLabel={false} pagination={<Pagination/>}>
          <Datagrid>
            <TextField source='name'/>
            <TextField source='code'/>
          </Datagrid>
        </ReferenceManyManyField>
      </Tab>
    </TabbedShowLayout>
  </Show>
)
//Used <TabbedShowLayout> because is what I use in my project, not sure if works under <Show> or <SimpleShowLayout>, but I think it work since I use it in other contexts

Je pense que l'implémentation peut être améliorée et être plus compatible avec React-Admin. Dans d'autres champs de référence, la récupération des données est stockée à l'état react-redux, dans cette implémentation, ce n'est pas le cas. La relation n'est enregistrée nulle part à part le composant, ce qui fait que l'application ne fonctionne pas en mode hors connexion car il ne peut pas récupérer les données, pas même la commande n'est possible.


5 commentaires

Au contraire, je pense que votre solution de contournement est de savoir à quoi ressemblerait la mise en œuvre de la relation plusieurs-à-plusieurs avec react-admin. Néanmoins, merci de partager votre idée, cela a été très utile de comprendre ce qui doit être fait.


Quoi qu'il en soit, cela vous dérangerait-il si je jetais un coup d'œil sur votre solution? Peut-être que nous pourrons ensemble créer une extension pour react-admin déjà génial :)


Belle approche! C'est pourquoi nous comptons beaucoup sur la personnalisation, de manière à ce que les utilisateurs créatifs puissent faire des choses intelligentes. :)


Ajout de l'implémentation de ce que j'ai et d'un exemple d'utilisation. J'espère que cela aide pour le moment, mais je pense vraiment qu'il faut une mise à jour et une réflexion plus sérieuse sur la façon dont cela fonctionne avec React-Admin.


Un problème doit-il être ouvert dans le code source pour débattre de la manière dont la mise en œuvre peut être effectuée?



1
votes

J'avais une question très similaire. Ma solution était plutôt un hack mais un peu plus simple à mettre en œuvre si tout ce que vous voulez, c'est activer un ReferenceManyField . Seul le dataProvider doit être modifié:

Je répète ma solution ici modifiée pour la question actuelle:

Utilisation du stock ReferenceManyField :

      // Add the reference id to the filter params.
      let refResource;
      const match = /_nested_(.*)_id/g.exec(params.target);
      if (match != null) {
        refResource = `${match[1]}/${params.id}/${resource}`;
      } else {
        query[`filter[${params.target}]`] = params.id;
        refResource = resource;
      }

      url = `${apiUrl}/${refResource}?${stringify(query)}`;

I puis modifié mon dataProvider, qui est un fork de ra-jsonapi-client . J'ai changé index.js sous le cas GET_MANY_REFERENCE de ceci:

      // Add the reference id to the filter params.
      query[`filter[${params.target}]`] = params.id;

      url = `${apiUrl}/${resource}?${stringify(query)}`;

à ceci:

<Show {...props}>
    <TabbedShowLayout>
        <Tab label="Roles">
            <ReferenceManyField reference="roles" target="_nested_users_id" pagination={<Pagination/>} >
                <Datagrid>
                    <TextField source="role" />
                </Datagrid>
            </ReferenceManyField>
        </Tab>
    </TabbedShowLayout>
</Show>

Donc, fondamentalement, je remappe simplement les paramètres à l'url pour le cas spécial où la cible correspond à une expression régulière codée en dur.

ReferenceManyField aurait normalement amené le dataProvider à appeler api / roles? filter [_nested_users_id] = 1 et cette modification oblige le dataProvider à appeler api / users / 1 / roles au lieu. Il est transparent pour react-admin.

Pas élégant mais cela fonctionne et ne semble pas casser quoi que ce soit sur le front-end.


0 commentaires