Skip to content

Latest commit

 

History

History
426 lines (352 loc) · 12 KB

README.md

File metadata and controls

426 lines (352 loc) · 12 KB

@ra-data-prisma/dataprovider

Data provider for react admin

Usage

yarn add @ra-data-prisma/dataprovider

Make sure your backend API is compatible by using the other package in this repo backend. Alternativly, you can connect to a backend that was created using typegraphql-prisma. See Chapter typegraphql-prisma

Add the dataprovider to your react-admin app:

import React, { Component } from "react"

import { Admin, Resource, Datagrid, TextField, Login } from "react-admin"
import { useDataProvider } from "@ra-data-prisma/dataprovider"
import { UserList, UserEdit, UserCreate } from "./resources/user"

import useAuthProvider from "./useAuthProvider"


const AdminApp = () => {
  const dataProvider = useDataProvider({
    clientOptions: { uri: "/graphql" }
    aliasPrefix: "admin" // 👈 set this, if you use a aliasPrefix on your backend as well (recommended)
    filters: {} // custom filters
    queryDialect: "nexus-prisma" // customize query dialect, defaults to nexus-prisma
  })
  const authProvider = useAuthProvider()

  if (!dataProvider) {
    return <div>Loading</div>
  }

  return (
    <Admin
      dataProvider={dataProvider}
      authProvider={authProvider}
    >
      <Resource
        name="User"
        list={UserList}
        edit={UserEdit}
        create={UserCreate}
      />
    </Admin>
  )
}

export default AdminApp

Features

aliasPrefix

Set aliasPrefix if you have set it on the backend as well (see backend ).

Search & Filtering

this dataprovider supports all filtering and searching and adds some convenience to it:

  • intelligent handling of strings and ints: you don't have to specify equals or contains, we do that for you. The filter can just be {firstname: "albert"} and we create the correct graphql query for that
  • extended react-admin input fields: you can directly use comparison operators on NumberInputs, TextInputs and DateInputs. For example, implementing "Created since" filter (DateInput on a field createdAt) would become
    <DateInput label="Created since" source="createdAt_gt" />
    • available comparisons (default comparison is the one which would be used if omitted):
      • ints, floats and datetimes - gt, gte, lt, lte, equals (default = equals)
      • strings - gt, gte, lt, lte, equals, contains, startsWith, endsWith (default = contains)
  • case insensitive: If your Prisma version supports it (>= 2.8.0), we automatically query strings as case insensitive. If your Prisma version doesn't support it, we emulate it with query for multiple variations of the search term: as-is, fully lowercase, first letter uppercase. This does work in many cases (searching for terms and names), but fails in some.
  • q query. q is a convention in react-admin for general search. We implement this client side. A query on q will search all string fields and int fields on a resource. It additionaly splits multiple search terms and does an AND search
  • if you need more sophisticated search, you can use normal nexus-prisma graphql queries. You can even mix it with q and the intelligent short notation

Custom filters

if you have a complex query, you can define custom filters:

 const dataProvider = useDataProvider({
    clientOptions: { uri: "/graphql" }

    filters: {
      onlyMillenials: (value?: boolean) =>
        value === true
          ? {
              AND: [
                {
                  yearOfBirth: {
                    gte: 1981,
                  },
                },
                {
                  yearOfBirth: {
                    lte: 1996,
                  },
                },
              ],
            }
          : undefined,
    },
  })

Then you can use that in a react-admin filter:

const UserFilter = (props) => (
  <Filter {...props}>
    <TextInput label="Search" source="q" alwaysOn />
    <BooleanInput
      label="Show only millenials"
      source="onlyMillenials"
      alwaysOn
    />
  </Filter>
);

Notice if you want to omit the filter, return null or undefined.

Relations

If you have relations, you can use ReferenceArrayField/Input or Referenceinput/Field. Make sure that the reference Model is also compatible (by calling addCrudResolvers("MyReferenceModel") from @ra-data-prisma/backend on your backend).

Sorting by relations

<List />s can be sorted by relations. Enable it in the backend

some examples:

show a list of cities with the country

export const CityList = (props) => (
  <List {...props}>
    <Datagrid>
      <TextField source="id" />
      <TextField source="name" />
      <ReferenceField label="Country" source="country" reference="Country">
        <TextField source="name" />
      </ReferenceField>
      <EditButton />
    </Datagrid>
  </List>
);

show all user roles in the user list

export const UserList = (props) => (
  <List {...props}>
    <Datagrid>
      <TextField source="id" />
      <TextField source="username" />
      <ReferenceArrayField
        alwaysOn
        label="Roles"
        source="roles"
        reference="UserRole"
      >
        <SingleFieldList>
          <ChipField source="name" />
        </SingleFieldList>
      </ReferenceArrayField>
      <EditButton />
    </Datagrid>
  </List>
);

edit the roles for a user

export const UserEdit = (props) => (
  <Edit title={<UserTitle />} {...props} undoable={false}>
    <SimpleForm variant="outlined">
      <TextInput source="userName" />
      <ReferenceArrayInput
        label="Roles"
        source="roles"
        reference="UserRole"
        allowEmpty
        fullWidth
      >
        <SelectArrayInput optionText="id" />
      </ReferenceArrayInput>
    </SimpleForm>
  </Edit>
);

Customize fetching & virtual Resources

react-admin has no mechanism to tell the dataprovider which fields are requested for any resources, we therefore load all fields for a resource. Ff a field points to an existing Resource, we only fetch the id of that resource.

But sometimes you need to customize this behaviour, e.g.:

  • you want to load a nested resource with all properties for easier export
  • you have large/slow resolvers in some resources and don't want to load these for performance reasons

We therefore provide a way to customize the loaded field-set by defining fragments, blacklists and whitelists.

Additionaly you can use that to create "virtual resources" that show other fields.

Basic usage

using one fragment for both one and many:

buildGraphQLProvider({
  clientOptions: { uri: "/api/graphql" } as any,
  resourceViews: {
    <local resource name>: {
      resource: <backend resource name>,
      fragment: <fragment to use>
    },
  },
});

You can also specify different fragments for one and many (or ommit one of those):

buildGraphQLProvider({
  clientOptions: { uri: "/api/graphql" } as any,
  resourceViews: {
    <local resource name>: {
      resource: <backend resource name>,
      fragment: {
        one: <fragment for one>
        many: <fragment for many>
      }
    },
  },
});
  • <backend resource name>: the name of an existing Resource that is defined on the backend
  • <local resource name>: if you use the same name as <backend resource name>, you basically customize that Resource. You can chose a different name to create a "virtual" resource.
  • <fragment for ...>: Specifies which fields are loaded when fetching one or many records. See below for available fragment types
  • if you only specify one or many, it will fall back to the default behavior if either would be used.

Gotcha: when using different fragments for one and many, the detail view might be loaded with certain fields being undefined. See this discussion

Fragment type blacklist / whitelist

Use this if you want to exclude certain fields (blacklist) or only include certain fields:

// whitelist
buildGraphQLProvider({
  clientOptions: { uri: "/api/graphql" } as any,
  resourceViews: {
    Users: {
      resource: "Users",
      fragment: {
        many: {
          type: "whitelist",
          fields: ["id", "firstName", "lastName"],
        },
      },
    },
  },
});
// blacklist
buildGraphQLProvider({
  clientOptions: { uri: "/api/graphql" } as any,
  resourceViews: {
    Users: {
      resource: "Users",
      fragment: {
        many: {
          type: "blacklist",
          fields: ["roles", "avatarImage"],
        },
      },
    },
  },
});

Fragment type DocumentNode

You can use graphql Fragments (DocumentNode) to presicely select fields. This is more verbose than using blacklists / whitelists, but enables you to deeply select fields. Additionaly your IDE can typecheck the fragment (e.g. when using the apollo extension in vscode).

import gql from "graphql-tag";

buildGraphQLProvider({
  clientOptions: { uri: "/api/graphql" } as any,
  resourceViews: {
    Users: {
      resource: "Users",
      fragment: {
        many: gql`
          fragment OneUserWithTwitter on User {
            id
            firstName
            lastName
            userSocialMedia {
              twitter
            }
          }
        `,
      },
    },
  },
});

Virtual Resources

You can use a different name for a resource, that does not exist on the backend:

// real world example
buildGraphQLProvider({
  clientOptions: { uri: "/api/graphql" } as any,
  resourceViews: {
    ParticipantsToInvoice: {
      resource: "ChallengeParticipation",
      fragment: {
        one: gql`
          fragment OneBilling on ChallengeParticipation {
            challenge {
              title
            }
            user {
              email
              firstname
              lastname
              school {
                name
                address
                city {
                  name
                  zipCode
                  canton {
                    id
                  }
                }
              }
            }
            teamsCount
            teams {
              name
            }
          }
        `,
        many: gql`
          fragment ManyBillings on ChallengeParticipation {
            challenge {
              title
            }
            user {
              email
              firstname
              lastname
              school {
                name
                address
              }
            }
            teams {
              name
            }
          }
        `,
      },
    },
  },
});

Now you have a new virtual resource ParticipantsToInvoice that can be used to display a List or for one record. (notice: update/create/delete is currently not specified, so use it read-only) and it will have exactly this data.

There are two ways you can use this new virtual resource. If you want to use it with React-Admin's query hooks (useQuery, useGetList, useGetOne), you need to add this as a new <Resource>:

<Admin>
  // ...
  <Resource name="ParticipantsToInvoice" />
</Admin>

These hooks rely on Redux store and will throw an error if the resource isn't defined.

However, if you directly use data provider calls, you can use it with defined <Resource> but also without as it directly calls data provider.

const dataProvider = useDataProvider(options);
const { data } = await dataProvider.getList("ParticipantsToInvoice", {
  pagination: { page: 1, perPage: 10 },
  sort: { field: "id", order: "ASC" },
  filter: {},
});

Usage with typegraphql-prisma

(beta)

You can use the dataprovider to connect to a backend that was created using https://www.npmjs.com/package/typegraphql-prisma. It has a slightly different dialect. Pass the following option to the dataprovider:

const dataProvider = useDataProvider({
    clientOptions: { uri: "/graphql" }
    queryDialect: "typegraphql" // 👈
})