Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return meta data for Select field from GraphQL API #2679

Closed
wants to merge 4 commits into from

Conversation

jesstelford
Copy link
Contributor

@jesstelford jesstelford commented Apr 7, 2020

Following on from @Vultraz' PR #2673, and referencing #1280 & #354.

NOTE: See "how to read this PR" below 👇

Examples

Given a list:

keystone.createList('User', {
  fields: {
    company: {
      type: Select,
      dataType: 'enum',
      options: [
        { label: 'Thinkmill', value: 'thinkmill' },
        { label: 'Atlassian', value: 'atlassian' },
        { label: 'Thomas Walker Gelato', value: 'gelato' },
        { label: 'Cete, or Seat, or Attend ¯\\_(ツ)_/¯', value: 'cete' },
      ],
    },
});

Query for Select "dataType"

query {
  _UserMeta {
    schema {
      fields {
        name
        type
        ...on _SelectMeta {
          dataType
        }
      }
    }
  }
}

Returns

{
  _UserMeta: {
    schema: {
      fields: [
        {
          name: 'company',
          type: 'Select',
          dataType: 'ENUM',
        },
      ]
    }
  }
}

Query for Select "options"

NOTE: This query may appear a bit more complicated, but the result just returns an array of { label, value }-ish objects. (The added complexity is due to the recently added enum vs string vs int dataType option of the Select field from #2678)

query {
  _UserMeta {
    schema {
      fields {
        name
        type
        ...on _SelectMeta {
          dataType
          options {
            label
            ...on _SelectMetaTypeEnum {
              enumValue
            }
            ...on _SelectMetaTypeString {
              stringValue
            }
            ...on _SelectMetaTypeInteger {
              integerValue
            }
          }
        }
      }
    }
  }
}

Returns:

{
  _UserMeta: {
    schema: {
      fields: [
        {
          name: 'company',
          type: 'Select',
          dataType: 'ENUM',
          options: [
            { label: 'Thinkmill', enumValue: 'thinkmill' },
            { label: 'Atlassian', enumValue: 'atlassian' },
            { label: 'Thomas Walker Gelato', enumValue: 'gelato' },
            { label: 'Cete, or Seat, or Attend ¯\\_(ツ)_/¯', enumValue: 'cete' },
          ],
        },
      ]
    }
  }
}

Going Forward

With this pattern in place, it should be possible for all other fields to implement any specific meta data they want to provide.

Then, we could setup the Admin UI to do a query such as:

query {
  _UserMeta {
    schema {
      fields {
        name
        type
        ...on _DecimalMeta {
          symbol
        }
        ...on _PasswordMeta {
          minLength
        }
        ...on _SelectMeta {
          dataType
          options {
            label
            ...on _SelectMetaTypeEnum {
              enumValue
            }
            ...on _SelectMetaTypeString {
              stringValue
            }
            ...on _SelectMetaTypeInteger {
              integerValue
            }
          }
        }
      }
    }
  }
}

Which would actually be auto-generated based on all the known possible field types the Admin UI was built with:

const query = `
query {
  _UserMeta {
    schema {
      fields {
        __typename
        name
        type
        ${fieldTypes.map(fieldType => `
          ...on _${fieldType.type}Meta {
            ${fieldType.getMetaQueryFragment}
          }
        `)}
      }
    }
  }
}
`;

Notice the __typename field being queried. That will let us know for every field what type it is (_DecimalMeta / _SelectMeta / etc), which could perhaps then be passed into a function which does a dynamic import of the right component type, and passes the returned data to the render.

How to read this PR

Go commit-by-commit:

@changeset-bot
Copy link

changeset-bot bot commented Apr 7, 2020

🦋 Changeset is good to go

Latest commit: efcb742

We got this.

This PR includes changesets to release 2 packages
Name Type
@keystonejs/api-tests Minor
@keystonejs/keystone Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@JedWatson
Copy link
Member

This looks great @jesstelford and is technically correct. I also like the way you're threading in existing patterns (having fields define their own getMetaQueryFragment etc)

I'm a bit skeptical of the real-world value of the added complexity though, compared to a generic "JSON" type that fields can put whatever they want into. I'd like to consider the value of a generic query that can be executed without being dynamically generated based on pre-existing knowledge of all the field types the Admin UI (or List) is built with.

e.g

query {
  _UserMeta {
    fields {
      name
      type
      config
    }
  }
}

returns

_UserMeta: {
  fields: [
    {
      name: 'company',
      type: 'Select',
      config: {
        dataType: 'enum',
        options: [
          { label: 'Thinkmill', value: 'thinkmill' },
          { label: 'Atlassian', value: 'atlassian' },
          { label: 'Thomas Walker Gelato', value: 'gelato' },
          { label: 'Cete, or Seat, or Attend ¯\\_(ツ)_/¯', value: 'cete' },
        ],
      },
    },
  ]
}

I'd like to revisit how this can be aligned with internal structure of standardised field meta (label, type, access, etc) and field-specific meta (in Select case, dataType and options, for Relationships refListKey and many, etc)

Feels like something that should be consistent whether you're working in the server-side Implementation, client-side Controller or UI Views for a field type; also should be clear whether something is private config (like the source path for a file field) or public config (like the select options). Next.js draws this line well in my experience, forcing similar clarity in fields seems like it would help - especially when trying to make it easier/clearer for people to create their own custom fields.

@jesstelford
Copy link
Contributor Author

should be clear whether something is private config (like the source path for a file field) or public config (like the select options).

Given this meta data is exposed via GraphQL, I'd assume it's all public. We can also wrap it in access control checks (there's already some basic checks there; you can't read if there is a read: false on the list)

I'm a bit skeptical of the real-world value of the added complexity though, compared to a generic "JSON" type that fields can put whatever they want into. I'd like to consider the value of a generic query that can be executed without being dynamically generated based on pre-existing knowledge of all the field types the Admin UI (or List) is built with.

Whether the query is generic or structured, the client would need to know what to do with the data returned. To know that, it must know of the structure of the data. By making the query structured, it enforces the contract so the client knows exactly what to get, the fields are inspectable in the GraphQL Playground, and it'll throw useful errors when a field is requested which doesn't exist etc.

For the Admin UI's case; I'd imagine the knowledge of the query to make (field.getMetaQueryFragment() in the example above) would live right next to the knowledge of what to do with that query (ie; it would all live within the src/fields/types/Select/* files).

For a generic client case, that knowledge needs to be built up, but there's no reason it can't act in the same way as described above; there at some point needs to be specific logic for whatever the type is.

All of this is a round-about way of saying; this is one of the rare cases where being strongly typed is useful! (sshhhh, don't tell anyone I said that 😆)

I'd like to revisit how this can be aligned with internal structure of standardised field meta (label, type, access, etc) and field-specific meta (in Select case, dataType and options, for Relationships refListKey and many, etc)

Agreed. I based the fields in this PR off of looking at the getAdminMeta call!

Perhaps in the future the Admin UI will be completely generated from the Meta queries, and so getAdminMeta will go away making the queries the source of truth?

@stale
Copy link

stale bot commented Sep 23, 2020

It looks like there hasn't been any activity here in over 6 months. Sorry about that! We've flagged this issue for special attention. It wil be manually reviewed by maintainers, not automatically closed. If you have any additional information please leave us a comment. It really helps! Thank you for you contribution. :)

@stale stale bot added the needs-review label Sep 23, 2020
@jesstelford
Copy link
Contributor Author

This will be handled differently in Keystone Next.

@jesstelford jesstelford closed this Mar 1, 2021
@timleslie timleslie deleted the field-meta-queries branch May 5, 2021 04:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants