Neo4j-graphql-js: Add support for filter parameter

Created on 18 Sep 2018  路  15Comments  路  Source: neo4j-graphql/neo4j-graphql-js

filter parameter can be added as part of augmentSchema, similar to functionality implemented in the neo4j-graphql database plugin. See: https://grandstack.io/docs/neo4j-graphql-plugin.html#auto-generated-query-types

Most helpful comment

We've added a generated filter argument in v2.5.0. The docs here shows some examples.

Please test it out and let us know how it works for your use cases.

All 15 comments

Hello,

But, what if I don't want to use makeAugmentedSchema?
I don't want the auto-generated resolvers, querys and mutations, but I want to filter by some fields,

It's that possible? How and where do I implement the augmentSchema?

Thanks


[Edited: Solution in next comment.]
I tried this:

const {makeExecutableSchema} = require('graphql-tools');
const {transpileSchema} = require('graphql-s2s').graphqls2s;
const glue = require('schemaglue');
const { augmentSchema } = require('neo4j-graphql-js');

const {schema, resolver} = glue('graphql');

module.exports = makeExecutableSchema({
    typeDefs: [transpileSchema(augmentSchema(schema))],         // <--this
    resolvers: resolver,
});

But got:

.../node_modules/neo4j-graphql-js/dist/augmentSchema.js:74
  var typeMap = schema.getTypeMap();
                       ^

TypeError: schema.getTypeMap is not a function
    at extractTypeMapFromSchema (.../node_modules/neo4j-graphql-js/dist/augmentSchema.js:74:24)
    at augmentSchema (.../node_modules/neo4j-graphql-js/dist/index.js:521:61)
    at Object.<anonymous> (.../graphql/index.js:12:13)
    at Module._compile (module.js:652:30)
    at Object.Module._extensions..js (module.js:663:10)
    at Module.load (module.js:565:32)
    at tryModuleLoad (module.js:505:12)
    at Function.Module._load (module.js:497:3)
    at Module.require (module.js:596:17)
    at require (internal/module.js:11:18)

Hello,

But, what if I don't want to use makeAugmentedSchema?
I don't want the auto-generated resolvers, querys and mutations, but I want to filter by some fields,

It's that possible? How and where do I implement the augmentSchema?

Thanks

I tried this:

const {makeExecutableSchema} = require('graphql-tools');
const {transpileSchema} = require('graphql-s2s').graphqls2s;
const glue = require('schemaglue');
const { augmentSchema } = require('neo4j-graphql-js');

const {schema, resolver} = glue('graphql');

module.exports = makeExecutableSchema({
    typeDefs: [transpileSchema(augmentSchema(schema))],         // <--this
    resolvers: resolver,
});

But got:

.../node_modules/neo4j-graphql-js/dist/augmentSchema.js:74
  var typeMap = schema.getTypeMap();
                       ^

TypeError: schema.getTypeMap is not a function
    at extractTypeMapFromSchema (.../node_modules/neo4j-graphql-js/dist/augmentSchema.js:74:24)
    at augmentSchema (.../node_modules/neo4j-graphql-js/dist/index.js:521:61)
    at Object.<anonymous> (.../graphql/index.js:12:13)
    at Module._compile (module.js:652:30)
    at Object.Module._extensions..js (module.js:663:10)
    at Module.load (module.js:565:32)
    at tryModuleLoad (module.js:505:12)
    at Function.Module._load (module.js:497:3)
    at Module.require (module.js:596:17)
    at require (internal/module.js:11:18)

Now I'm a step forward, this solve my previus problem:

const {makeExecutableSchema} = require('graphql-tools');
const {transpileSchema} = require('graphql-s2s').graphqls2s;
const glue = require('schemaglue');
const { augmentSchema } = require('neo4j-graphql-js');
const {schema, resolver} = glue('graphql');

const executableSchema = makeExecutableSchema({
    typeDefs: [transpileSchema(schema)],
    resolvers: resolver,
});

module.exports = augmentSchema(executableSchema);

But i still can't use the filter parameter and can't avoid the auto generated create, update, delete querys.

Query:

{
 service(first:3, filter:"a"){
    name
  }
}

Result:

{
  "errors": [
    {
      "message": "Unknown argument \"filter\" on field \"service\" of type \"Query\".",
      "locations": [
        {
          "line": 3,
          "column": 18
        }
      ]
    }
  ]
}

Schema:

type Service inherits Node {
    name: String
}
type Query {
    service():[Service]
}

Resolver:

.
.
.
exports.resolver = {
    Query: {
        service: (object, params, context, resolveInfo) => {
            return neo4jgraphql(object, params, context, resolveInfo, process.env.NODE_ENV === 'development')
        },
    },
};

PD: It's work with first and offset.


Any idea how to implements the filter?

Thanks.

Hi @rdimicheleb - in general, yes you can selectively make use of the features that augmentSchema adds to the schema by manually adding them to your schema (like first, offset, etc) and then in your resolver delegating out to theneo4jgraphql function.

We've also added the ability to configure which types are included in the schema augmentation process (or to turn off the auto-generated queries/mutations altogether): https://grandstack.io/docs/neo4j-graphql-js.html#configuring-schema-augmentation

Note however that we have not yet implemented the filter argument in neo4j-graphql.js, but hope to get to this soon!

As requested a few use cases:

  1. filter using STARTS WITH, ENDS WITH and CONTAINS matching on strings
  2. filter based on the existence of a particular relationship on a node
  3. filter based on properties present on a relationship
  4. filter based on values on a related node
  5. All the above applied to transitive relationships
  6. All the above, but negated

To give a flavour of what we're trying to model, it's all about organisational structure and how this relates to physical assets. Hugely simplified it looks a bit like this:

(Department)-[:HAS_TEAM]->(:Team)<-[:SUPPORTED_BY]-(System)-[:DEPENDS_ON]->(:System)

Examples of what we might use filters like the above are as follows. I've provided, for clarity, roughly what the cypher queries would look like, assuming that the first node in each query is given by the entry point of the graphql query. May include mistakes 馃槤

  1. Building some search functionality into the api MATCH (t:Team) WHERE d.name ENDS WITH " operations"
  2. and 6. Finding unsupported services MATCH (s:System) WHERE count((s)-[:SUPPORTED_BY]->(:Team)) = 0
  3. Distinguishing between production and development dependencies MATCH (s:System)-[rel:DEPENDS_ON]->(dep:System) WHERE rel.environment = "production"
  4. Finding all Systems supported by a team that depend on a given system MATCH (t:Team)<-[:SUPPORTED_BY]-(s:System)-[:DEPENDS_ON]->(dep:System) WHERE dep.code = "neo4j"
  5. Finding all Systems supported by a teams within a department that depend on a given system MATCH (d:Department)-[:HAS_TEAM]->(t:Team)<-[:SUPPORTED_BY]-(s:System)-[:DEPENDS_ON]->(dep:System) WHERE dep.code = "neo4j"

Thanks a bunch - this really helps give us an idea of what to support in filter.

btw - I think you can currently do

and 6. Finding unsupported services MATCH (s:System) WHERE count((s)-[:SUPPORTED_BY]->(:Team)) = 0

with a computed field @cypher directive, since it's an equality.

type System {
   name: String
   supportingTeamsCount: Int @cypher("RETURN SIZE( (this)-[:SUPPORTED_BY]->(:Team)" )
}

then the query would be:

{
  System(supportingTeamCout: 0) {
    name
  }
}

but would be good to support this in a more general way

Yeah I've written something similar. Worked as a property definition but not as a filter, but we're on version 0.x of the library. Will upgrade when I have time and let you know if it works

@johnymontana any rough timeline on this? I'd definitely jump in with both feet if filter was supported.

We've got this on the roadmap for the next epic after we finish up a few other things. So look for it to be added within the next month or so.

@johnymontana we're desperately looking forward to filter for relationships. Here is a use-case:

type Post {
  disabled: Boolean! @cypher(statement: "OPTIONAL MATCH (this)<-[d:DISABLED]-() RETURN d IS NOT NULL")
  disabledBy: User @relation(name: "DISABLED", direction: "IN")
}

Here's what I would like to do:

{
  Post(disabled: true) {
    id
    title
    disabled
    disabledBy {
      name
    }
  }
}

@johnymontana what you suggested here does not work:

Here is the generated cypher query:

MATCH (`post`:`Post` {disabled:$disabled}) RETURN `post` { .id , .title ,disabled: apoc.cypher.runFirstColumn("OPTIONAL MATCH (this)<-[d:DISABLED]-() RETURN d IS NOT NULL", {this: post}, false),disabledBy: head([(`post`)<-[:`DISABLED`]-(`post_disabledBy`:`User`) | post_disabledBy { .name }]) } AS `post` SKIP $offset

We've added a generated filter argument in v2.5.0. The docs here shows some examples.

Please test it out and let us know how it works for your use cases.

@johnymontana I tried what you pointed out to @roschaefer with v2.5.0.
I used a kind of trick to get what we want, but in a direct manner it did鈥檛 worked:

graphql.schema:

type User {
  id: ID!
  name: String
  role: UserGroupEnum
}

enum UserGroupEnum {
  admin
  moderator
  user
}

type Post {
  id: ID!
  title: String!
  content: String!

  disabled: Boolean! @cypher(statement: "OPTIONAL MATCH (this)<-[d:DISABLED]-() RETURN d IS NOT NULL")
  disabledBy: User @relation(name: "DISABLED", direction: "IN")
  userDisabledThis(filter: _UserFilter): User @relation(name: "DISABLED", direction: IN)
}

input _UserFilter {
  id: ID
  role_in: [UserGroupEnum!]
}

Query we would like, as showed here.
Doesn't work:

{
  Post(disabled: true) {
    id
    disabled
}

Because disabled as computed field @cypher directive seems not to be supported as a real parameter with a specific value in the query.

The following works, but is kind of indirectly through a filter:

{
  Post(filter: { userDisabledThis: { role_in: [admin,moderator,user] } }) {
    id
    disabled
}

We have to put all possible roles of a user into the filter to be sure we miss no User disable a Post.

The first and not working example would be not a kind of workaround, but straightforward.
Is there a possibility to get there?

@Tirokk We decided not to add @cypher directive fields to the auto-generated filter initially because of performance considerations, but I suppose we could reconsider this. Perhaps make this configurable to allow the user to include these?

That'd be great @johnymontana. I can think of lots of use cases where users might want to filter for things that do/don't have a given relationship, which we expose using a @cypher directive that converts the existence of relationships into a Boolean

It would be very helpful if you would consider the possibility to have @cypher directives that converts the existence of relationships into a Booleans, @johnymontana. Please make this configurable to include these. There are lots of use cases like @wheresrhys said.

The current filters can be used to filter for nodes that have/do not have relationship fields defined with @relation. For example, using this Codesandbox example:

Movies that have no (:Movie)<-[:ACTED_IN]-(:Actor) relationships

{
  Movie(filter: { actors: null }) {
    title
  }
}

Movies that have at least one (:Movie)<-[:ACTED_IN]-(:Actor) relationship

{
  Movie(filter: { actors_not: null }, first: 10) {
    title
  }
}

These filters can be nested as well:

What genres are connected to the movies with no actors?

{
  Genre(filter: { movies_some: { actors: null } }) {
    name
  }
}

This currently does not work with relationship types. For example, (:User)-[:RATED]->(:Movie) in this example, but that will be added soon.

Does this address your use cases @Tirokk @wheresrhys @roschaefer ?

I'm hesitant to expose filtering on @cypher fields too widely, because I think this could quickly lead to sub-optimal performance in some cases.

Was this page helpful?
0 / 5 - 0 ratings