Coder Social home page Coder Social logo

ra-data-opencrud's Introduction

ra-data-opencrud

Prisma on steroids: easily build backoffices with Prisma/GraphCMS plugged on react-admin!

Work in progress

If you wanna give it a try anyway, here's a quick preview on codesandbox. The API is hosted on Prisma's public servers, which means the API is limited to 10 API calls per seconds. Be aware that it might not be working because of that, or that performances may be poor.

Edit ra-data-prisma

Summary

What is react admin ? And what's ra-data-opencrud ?

Find out more about the benefits of using react-admin with Prisma here.

Installation

Install with:

npm install --save graphql ra-data-opencrud

or

yarn add graphql ra-data-opencrud

Usage

This example assumes a Post type is defined in your datamodel.

// in App.js
import React, { Component } from 'react';
import buildOpenCrudProvider from 'ra-data-opencrud';
import { Admin, Resource, Delete } from 'react-admin';

import { PostCreate, PostEdit, PostList } from './posts';

const client = new ApolloClient();

class App extends Component {
    constructor() {
        super();
        this.state = { dataProvider: null };
    }
    componentDidMount() {
        buildOpenCrudProvider({ clientOptions: { uri: 'your_prisma_or_graphcms_endpoint' }})
            .then(dataProvider => this.setState({ dataProvider }));
    }

    render() {
        const { dataProvider } = this.state;

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

        return (
            <Admin dataProvider={dataProvider}>
                <Resource name="Post" list={PostList} edit={PostEdit} create={PostCreate} remove={Delete} />
            </Admin>
        );
    }
}

export default App;

And that's it, buildOpenCrudProvider will create a default ApolloClient for you and run an introspection query on your Prisma/GraphCMS endpoint, listing all potential resources.

Options

Customize the Apollo client

You can either supply the client options by calling buildOpenCrudProvider like this:

buildOpenCrudProvider({ clientOptions: { uri: 'your_prisma_or_graphcms_endpoint', ...otherApolloOptions } });

Or supply your client directly with:

buildOpenCrudProvider({ client: myClient });

Overriding a specific query

The default behavior might not be optimized especially when dealing with references. You can override a specific query by decorating the buildQuery function:

With a whole query

// in src/dataProvider.js
import buildOpenCrudProvider, { buildQuery } from 'ra-data-opencrud';

const enhanceBuildQuery = introspection => (fetchType, resource, params) => {
    const builtQuery = buildQuery(introspection)(fetchType, resource, params);

    if (resource === 'Command' && fetchType === 'GET_ONE') {
        return {
            // Use the default query variables and parseResponse
            ...builtQuery,
            // Override the query
            query: gql`
                query Command($id: ID!) {
                    data: Command(id: $id) {
                        id
                        reference
                        customer {
                            id
                            firstName
                            lastName
                        }
                    }
                }`,
        };
    }

    return builtQuery;
}

export default buildOpenCrudProvider({ buildQuery: enhanceBuildQuery })

Or using fragments

You can also override a query using the same API graphql-binding offers.

buildQuery accept a fourth parameter which is a fragment that will be used as the final query.

// in src/dataProvider.js
import buildOpenCrudProvider, { buildQuery } from 'ra-data-opencrud';

const enhanceBuildQuery = introspection => (fetchType, resource, params) => {
    if (resource === 'Command' && fetchType === 'GET_ONE') {
        // If you need auto-completion from your IDE, you can also use gql and provide a valid fragment
        return buildQuery(introspection)(fetchType, resource, params, `{
            id
            reference
            customer { id firstName lastName }
        }`);
    }

    return buildQuery(introspection)(fetchType, resource, params);
}

export default buildOpenCrudProvider({ buildQuery: enhanceBuildQuery })

As this approach can become really cumbersome, you can find a more elegant way to pass fragments in the example under /examples/prisma-ecommerce

Customize the introspection

These are the default options for introspection:

const introspectionOptions = {
    include: [], // Either an array of types to include or a function which will be called for every type discovered through introspection
    exclude: [], // Either an array of types to exclude or a function which will be called for every type discovered through introspection
}

// Including types
const introspectionOptions = {
    include: ['Post', 'Comment'],
};

// Excluding types
const introspectionOptions = {
    exclude: ['CommandItem'],
};

// Including types with a function
const introspectionOptions = {
    include: type => ['Post', 'Comment'].includes(type.name),
};

// Including types with a function
const introspectionOptions = {
    exclude: type => !['Post', 'Comment'].includes(type.name),
};

Note: exclude and include are mutualy exclusives and include will take precendance.

Note: When using functions, the type argument will be a type returned by the introspection query. Refer to the introspection documentation for more information.

Pass the introspection options to the buildApolloProvider function:

buildApolloProvider({ introspection: introspectionOptions });

Tips and workflow

Performance issues

As react-admin was originally made for REST endpoints, it cannot always take full advantage of GraphQL's benefits.

Although react-admin already has a load of bult-in optimizations (Read more here and here), it is not yet well suited when fetching n-to-many relations (multiple requests will be sent).

To counter that limitation, as shown above, you can override queries to directly provide all the fields that you will need to display your view.

Suggested workflow

As overriding all queries can be cumbersome, this should be done progressively.

  • Start by using react-admin the way you're supposed to (using <ReferenceField /> and <ReferenceManyField /> when trying to access references)
  • Detect the hot-spots
  • Override the queries on those hot-spots by providing all the fields necessary (as shown above)
  • Replace the <ReferenceField /> by simple fields (such as <TextField />) by accessing the resource in the following way: <TextField source="product.name" />
  • Replace the <ReferenceManyField /> by <ArrayField /> using the same technique as above

Contributing

Use the example under examples/prisma-ecommerce as a playground for improving ra-data-opencrud.

To easily enhance ra-data-opencrud and get the changes reflected on examples/prisma-ecommerce, do the following:

  • cd ra-data-opencrud
  • yarn link
  • cd examples/prisma-ecommerce
  • yarn link ra-data-opencrud

Once this is done, the ra-data-opencrud dependency will be replaced by the one on the repository. One last thing, don't forget to transpile the library with babel by running the following command on the root folder

yarn watch

You should now be good to go ! Run the tests with this command:

jest

ra-data-opencrud's People

Contributors

ecwyne avatar weakky avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

ra-data-opencrud's Issues

Optimistic updates of references not working

In the prisma e-commerce example, if you go to the product list screen, edit the first product, change the brand and hit save, you'll see in the list view that the brand shows the old value until 3 seconds later when the new data is refreshed and you see the updated value.

The same appears to be true anytime you update a reference using the opencrud data provider -- but if you switch to one of the rest data providers, the optimistic update works as expected. I haven't had time to fully investigate yet, but it looks like the use of the derived source.id field is somehow screwing up the optimistic update system -- if I hack it to use source_id instead, it fixes it...

How upload image

How can I upload images ? I added the "picture" field. when I try to select an image and click on the save, I get the error Cannot read property 'name' of null.

 <ImageInput source="images" label="Related pictures" accept="image/*">
        <ImageField source="create.src" />
 </ImageInput>

my mutation arguments:
data: {name:"string", image: {create: src:"string"}}

ReferenceError: process is undefined

The error: ReferenceError: process is undefined

The cause: Installing peer dependency graphql with the version provided in ra-data-opencrud's package.json.

  "peerDependencies": {
    "graphql": "^0.13.2", <------
    "react-admin": "^2.3.0"
  },

The fix: Install the peer dependency graphql with the latest version (currently ^14.3.1).

I don't guarantee that it works perfectly as there may be breaking changes between the major versions, but it at least resolves this specific error for me.

fails react-scripts build

Hi there. Thanks for making this module.

I'm trying to do a production build of my react-admin app using ra-data-opencrud, but it's failing because the package is not being transpiled to ES5 before publishing.

npm run build

> [email protected] build /Users/x/development/y/web/home
> react-scripts build

Creating an optimized production build...
Failed to compile.

Failed to minify the code from this file:

 	./node_modules/ra-data-opencrud/lib/utils/getFinalType.js:7

Read more here: http://bit.ly/2tRViJ9

Can you please transpile your code to ES5 and publish so common build tools don't bork on it?

Thanks!

does not work with create react app v2

This module uses a version of the modulegraphql which relies on .mjs ES Modules. This is not supported by create react app v2 so it is not usable with the latest, standard, stable version of create-react-app.

referenceInput do not work with react-admin v3

given a reference input:

   <ReferenceInput
          label="Parent page"
          source="parentPage.id"
          reference="Page"
        >
          <SelectInput optionText="path" />
        </ReferenceInput>

this does not work with the upcoming v3 of react-admin because:

  • it defaults to filterToQuery={(searchText) => ({ q: searchText })} and the q property is not handled with this dataprovider. Solution is to pass a custom filterToQuery, e.g. filterToQuery={(searchText) => ({ path: searchText })}
  • it will call connections with where: {ids_in: ""}}, which throws an error in the backend

Allow soft-deletion ?

In prisma-ecommerce, I have several models that I need to be soft-deleted only, such as Products so that Orders remains intact.

I see two ways of achieving this:

  • Either we add another mutation type PRISMA_SOFT_DELETE (along with the present PRISMA_CONNECT, PRISMA_CREATE etc..), and behave accordingly

  • Or we look for a special field in models (eg: deletedAt), and always soft-delete those fields.

Note: we will have to filter all subsequent queries to remove soft-deleted nodes.
(where: { deletedAt: null }

Any feedback would be much appreciated.

drag & drop with treeview does not work

while tree-view seems to work, the drag& drop does not work. It does not throw an error, the item just jumps back.

i think the update query is wrong in that case, but i have to investigate

Update isn't working

When I try to update a data from my existing Prisma database (Not e-commerce example), I'm having an error.

I can create a data but I can't update it.

Reason: 'createdAt' Field 'createdAt' is not defined in the input type 'ProductUpdateInput'. (line 1, column 24):
Reason: 'updatedAt' Field 'updatedAt' is not defined in the input type 'ProductUpdateInput'. (line 1, column 24):

From what I understand, ra-data-opencrud is trying to update the updatedAt and/or createdAt fields. Prisma docs states that createdAt and updatedAt fields can't be updated via mutations.

https://www.prisma.io/docs/reference/service-configuration/data-model/data-modelling-(sdl)-eiroozae8u#example

The id, createdAt and updatedAt fields are managed by Prisma and read-only in the exposed GraphQL API (meaning they can not be altered via mutations).

Here is the full error

Warning: Missing translation for key: "GraphQL error: Variable '$data' expected value of type 'ProductUpdateInput!' but got: {"image":null,"title":"Tittt","meta":"Met","description":"Desc","price":13,"code":"ASD","stock":123,"designer":{"connect":{"id":"cjgsgsl141l630b59t3oniy9x","username":"batuhanw"}},"createdAt":"2018-11-10T18:59:31.000Z","updatedAt":"2018-11-10T18:59:31.000Z"}. Reason: 'updatedAt' Field 'updatedAt' is not defined in the input type 'ProductUpdateInput'. (line 1, column 24):
mutation updateProduct($data: ProductUpdateInput!, $where: ProductWhereUniqueInput!) {
                       ^
GraphQL error: Variable '$data' expected value of type 'ProductUpdateInput!' but got: {"image":null,"title":"Tittt","meta":"Met","description":"Desc","price":13,"code":"ASD","stock":123,"designer":{"connect":{"id":"cjgsgsl141l630b59t3oniy9x","username":"batuhanw"}},"createdAt":"2018-11-10T18:59:31.000Z","updatedAt":"2018-11-10T18:59:31.000Z"}. Reason: 'createdAt' Field 'createdAt' is not defined in the input type 'ProductUpdateInput'. (line 1, column 24):
mutation updateProduct($data: ProductUpdateInput!, $where: ProductWhereUniqueInput!) {

Brands List Edit Screen: GraphQL error

Hello,
Nice library. Thank you.
Got these 2 GQL Errors when updating the name of a brand on the Brands List Edit Screen:

  • GraphQL error: Variable '$data' expected value of type 'BrandUpdateInput!' but got: {"name":"Brand001","category":{"id":"cjl7a29pw00260874qadv56z7"},"shop":{"id":"cjl7a1ppr001x0874asov67js"}}. Reason: 'category.id' Field 'id' is not defined in the input type 'CategoryUpdateOneInput'. (line 1, column 22):
    mutation updateBrand($data: BrandUpdateInput!, $where: BrandWhereUniqueInput!) {
  • GraphQL error: Variable '$data' expected value of type 'BrandUpdateInput!' but got: {"name":"Brand001","category":{"id":"cjl7a29pw00260874qadv56z7"},"shop":{"id":"cjl7a1ppr001x0874asov67js"}}. Reason: 'shop.id' Field 'id' is not defined in the input type 'ShopUpdateOneInput'. (line 1, column 22):
    mutation updateBrand($data: BrandUpdateInput!, $where: BrandWhereUniqueInput!) {

[Question] Use prisma-client

I've been tinkering with the new prisma-client and think there's huge potential to simplify this project.

The following is a rough (but I think mostly working) implementation of how this would be done. Updates/creates still need to be converted to use {connect: {}, disconnect: {}, set: {}} etc.

I would love to hear your thoughts!

import {
    GET_LIST,
    GET_ONE,
    GET_MANY,
    GET_MANY_REFERENCE,
    CREATE,
    UPDATE,
    UPDATE_MANY,
    DELETE,
    DELETE_MANY,
} from 'react-admin';
import { makePrismaLink } from 'prisma-binding';
import { introspectSchema } from 'graphql-tools';
import { printSchema, DocumentNode } from 'graphql';
import camelcase from 'camelcase';
import pluralize from 'pluralize';
import { Client } from 'prisma-client-lib';

interface IParams {
    filter?: { [key: string]: string };
    pagination?: { page: number; perPage: number };
    sort?: { field: string; order: string };
    id?: string;
    data?: { [key: string]: any };
    ids?: string[];
}

interface IOptions {
    endpoint: string;
    fragments?: { [key: string]: DocumentNode };
}

export default async ({ endpoint, fragments = {} }: IOptions) => {
    const link = makePrismaLink({ endpoint });
    const schema = await introspectSchema(link);
    const typeDefs = printSchema(schema);
    const client = new Client({ endpoint, typeDefs, debug: false }) as any;
    return async (
        type: string,
        resource: string,
        params: IParams,
    ): Promise<any> => {
        const f = fragments[resource];
        switch (type) {
            case GET_LIST:
                return await client[camelcase(pluralize(resource))]({
                    where: params.filter,
                    orderBy: `${params.sort!.field}_${params.sort!.order}`,
                    first: params.pagination!.perPage,
                    skip:
                        (params.pagination!.page - 1) *
                        params.pagination!.perPage,
                }).$fragment(f);

            case GET_ONE:
                return client[camelcase(resource)](params).$fragment(f);

            case CREATE:
                return client[camelcase(`create${resource}`)](params.data); // TODO connect/set relations

            case DELETE:
                return client[camelcase(`delete${resource}`)](params);

            case DELETE_MANY:
                return client[camelcase(`deleteMany${pluralize(resource)}`)]({
                    where: { id_in: params.ids },
                });
            case GET_MANY:
                return client[camelcase(pluralize(resource))]({
                    where: { id_in: params.ids },
                }).$fragment(f);

            case UPDATE_MANY:
                return client[camelcase(`updateMany${pluralize(resource)}`)]({
                    where: { id_in: params.ids },
                    data: params.data, // TODO connect/set relations
                });
        }
    };
};

It would then be used like so:

createOpenCrudDataProvider({
    endpoint: 'YOUR_ENDPOINT_URL',
    fragments: {
        User: gql`
            fragment user on User {
                id
                email
                phone
            }
        `,
    },
})

bug: Aggregated total count is not correct due to the presence of first/last/offset parameters

First of all a huge thank you for writing this data connector, really enjoying it so far! ๐Ÿ’ฏ

Our project holds a prisma instance 1.17.1 and after setting up a react-admin application with a entities List view we found that no pagination was displayed. We dug a bit through the source code and suspect that the issue is due to the fact that the e.g. productsConnection query get's called with the exact same parameters as the regular products query. This is an issue since when calling a entityConnection query of the prisma API with parameters such as first, last or offset the total amount does reflect these arameters.

E.g. when sending a query such as

{
  productsConnection(first:25) {
    aggregate {count}
  }
}

prisma will respond with

{
  "data": {
    "productsConnection": {
      "aggregate": {
        "count": 25
      }
    }
  }
}

This is not an issue on prisma's side IMO but a task that this connector should solve, e.g. sending a entityConnection query for the total amount of entities with all parameters except first, last or offset.

ReferenceInput allowEmpty attribute not working correctly

To replicate, create a reference input with the allowEmpty attribute.

  1. Set the reference input
  2. Save
  3. Edit and select empty for the reference input
  4. Save

The result is a request with the following data object:

{title: "Title", parent: {connect: {id: ""}}}

and the following error:

Warning: Missing translation for key: "GraphQL error: No Node for the model PostCategory with value for id found."

Expected result is no error and a request with data object something like this:

{title: "Title", parent: {disconnect: true}}

ReferenceArrayInput works on Edit, but not Create

This is demonstrated on the code sandbox.

To reproduce:

  1. Click the Options tab on the left,
  2. Create a new Option
  3. Add one or more Values
  4. Save

Expected behaviour: the Values are saved to the new Option.
Result: the Values are not be saved to the new Option.

  1. Click the Options tab on the left,
  2. Edit an existing Option
  3. Add one or more Values
  4. Save

Result: This works as expected.

With some guidance, I could have a crack at fixing this.

Question: How can I define nested mutation?

Suppose i have the following schema:
type User{
id: ID! @unique
age: Int
email: String! @unique
name: String!
posts: [Post!]!
}

type Post {
id: ID! @unique
title: String!
published: Boolean!
author: User!
}
How can I implemented a nested mutation like:
mutation {
createPost(data: {
title: "This is a draft"
published: false
author: {
create: {
email: "[email protected]"
name:"athina"
}
}
}) {
id
title
published
author {
name
email
}
}
}

How to use this library with prefix tables in database like am_user

I've been struggling since 5 days and not able to fixed. I'm using prisma with mysql

if tables in database were like this User, Post . it will work fine

the issue is all tables are named like this am_user , am_post

in this library they used this
${pluralize(camelCase(resource.name))};

who is the hero who can save me? i'm not able to find any workaround

Json-datatype broken

if you have a Json-graphql-scalar and your field sends an arbitrary object, you will get an error:

Cannot read property 'find' of null

There has been an attemp to fix this, but it was closed without comment: #29

i am about to try if this fix would solve the issue

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.