We can now define custom queries and mutations on a list and use custom GraphQL types for the output (#1503). This lets us do things like this...
const STAFF_SALES_QUERY = `
select store, employee, sum(sale_value) as "salesLastMonth"
from sales
where store = :store
and sale_date < date_trunc('month', now())
and sale_date >= date_trunc('month', now() - interval '1 month')
group by store, employee;
`;
keystone.createList('Store', {
// ...
queries: [
{
types: [
`type StaffSalesReportItem {
store: ID
employee: ID
salesLastMonth: Number
}`,
],
schema: 'StaffSalesReport(store: ID!): StaffSalesReportItem',
resolver: async (obj, { store }) => {
const { rows } = await keystone.adapters.KnexAdapter.knex.raw(STAFF_SALES_QUERY, { store });
return rows;
},
},
],
});
Super handy but, as above, the output types often relate to existing Keystone list. Since the whole thing is resolved with custom code there's no way to drill into these fields and though they were relationship fields.
What we really want is for the type above to be closer to..
type StaffSalesReportItem {
store: Store
employee: Employee
salesLastMonth: Number
}
So we drill into the relationship and do queries like...
query {
StaffSalesReport ( store: "A8622468-9B70-41E1-97E3-620CF708CB0F" ) {
store { id name }
employee { id name email role { paygrade } startDate }
salesLastMonth
}
}
Note, we're not just resolving the related item; we might be drilling into their relationships too. We want to hand off processing that entire part branch of the query. Regardless, I think this should be possible using they existing Keystone config.
Completely made up syntax and probably wrong but imagine something like this:
keystone.createList('Store', {
// ...
queries: [
{
types: [
// Note, now referencing existing List output types..
`type StaffSalesReportItem {
store: Store
employee: Employee
salesLastMonth: Number
}`,
],
schema: 'StaffSalesReport(store: ID!): StaffSalesReportItem',
resolver: async (obj, { store }, auth, info) => {
const { rows: items } = await keystone.adapters.KnexAdapter.knex.raw(STAFF_SALES_QUERY, { store });
await keystone.lists.stores.resolveItemsForGql({ items, path: '.store', auth, info });
await keystone.lists.employees.resolveItemsForGql({ items, path: '.employee', auth, info });
return items;
},
},
],
});
Another approach to this problem is something like "virtual" or "calculated" fields (#1117) on a standard list or having read-only "Lists" on top of database views (#1133). They both have drawbacks for this use case though and solve subtly different problems.
The thing I like about this approach is it doesn't care where the IDs come from. You could use it to create custom types that bridged systems -- your DB and a remote API for example, or have an array of IDs in an environment variable that can be queries as though it's part of the larger GraphQL schema.
Hey @MadeByMike the example uses Knex but the tasks isn't actually coupled to it so I've removed the tag. This is much more about GraphQL customisation.
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. :)
Using a hypothetical GraphQL schema extension API but using the real current Keystone API for getting the items, this is how you should solve this:
extendGraphQLSchema({
typeDefs: gql`
type StaffSalesReportItem {
store: Store
employee: Employee
salesLastMonth: Float
}
extend type Query {
StaffSalesReport(store: ID!): StaffSalesReportItem
}
`,
resolvers: {
StaffSalesReport: {
store(rootVal, args, ctx) {
return keystone.lists.Store.itemQuery({ where: { id: rootVal.store } }, ctx);
},
employee(rootVal, args, ctx) {
return keystone.lists.Employee.itemQuery({ where: { id: rootVal.employee } }, ctx);
},
},
Query: {
async StaffSalesReport(_, { store }) {
const { rows } = await keystone.adapters.KnexAdapter.knex.raw(STAFF_SALES_QUERY, { store });
return rows;
},
},
},
});
So what we need to do here is:
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. :)
Most helpful comment
Using a hypothetical GraphQL schema extension API but using the real current Keystone API for getting the items, this is how you should solve this:
So what we need to do here is: