How do I filter fields with Directus - directus

Let's say I make an api call like this
const { data } = await client.getItems(`module/${module.id}`, {
fields: [
'questions.module_question_id.question_text',
'questions.module_question_id.slug',
'questions.module_question_id.type',
'questions.module_question_id.answer_options.*',
],
});
I am grabbing the fields, but I also want to filter out a certain question ala its slug, is there a way to do this at the api level? I know filters exist as a global query api, but have not found examples of them being used in conjunction with fields.

Perhaps you are looking for deep? This should allow you to filter on a deeply nested relational field.
https://docs.directus.io/reference/api/query/#deep

Related

KeystoneJS `filter` vs `Item` list access control

I am trying to understand more in depth the difference between filter and item access control.
Basically I understand that Item access control is, sort of, higher order check and will run before the GraphQL filter.
My question is, if I am doing a filter on a specific field while updating, for instance a groupID or something like this, do I need to do the same check in Item Access Control?
This will cause an extra database query that will be part of the filter.
Any thoughts on that?
The TL;DR answer...
if I am doing a filter on a specific field [..] do I need to do the same check in Item Access Control?
No, you only need to apply the restriction in one place or the other.
Generally speaking, if you can describe the restriction using filter access control (ie. as a graphQL-style filter, with the args provided) then that's the best place to do it. But, if your access control needs to behave differently based on values in the current item or the specific changes being made, item access control may be required.
Background
Access control in Keystone can be a little hard to get your head around but it's actually very powerful and the design has good reasons behind it. Let me attempt to clarify:
Filter access control is applied by adding conditions to the queries run against the database.
Imagine a content system with lists for users and posts. Users can author a post but some posts are also editable by everyone. The Post list config might have something like this:
// ..
access: {
filter: {
update: () => ({ isEditable: { equals: true } }),
}
},
// ..
What that's effectively doing is adding a condition to all update queries run for this list. So if you update a post like this:
mutation {
updatePost(where: { id: "123"}, data: { title: "Best Pizza" }) {
id name
}
}
The SQL that runs might look like this:
update "Post"
set title = 'Best Pizza'
where id = 234 and "isEditable" = true;
Note the isEditable condition that's automatically added by the update filter. This is pretty powerful in some ways but also has its limits – filter access control functions can only return GraphQL-style filters which prevents them from operating on things like virtual fields, which can't be filtered on (as they don't exist in the database). They also can't apply different filters depending on the item's current values or the specific updates being performed.
Filter access control functions can access the current session, so can do things like this:
filter: {
// If the current user is an admin don't apply the usual filter for editability
update: (session) => {
return session.isAdmin ? {} : { isEditable: { equals: true } };
},
}
But you couldn't do something like this, referencing the current item data:
filter: {
// ⚠️ this is broken; filter access control functions don't receive the current item ⚠️
// The current user can update any post they authored, regardless of the isEditable flag
update: (session, item) => {
return item.author === session.itemId ? {} : { isEditable: { equals: true } };
},
}
The benefit of filter access control is it doesn't force Keystone to read an item before an operation occurs; the filter is effectively added to the operation itself. This can makes them more efficient for the DB but does limit them somewhat. Note that things like hooks may also cause an item to be read before an operation is performed so this performance difference isn't always evident.
Item access control is applied in the application layer, by evaluating the JS function supplied against the existing item and/or the new data supplied.
This makes them a lot more powerful in some respects. You can, for example, implement the previous use case, where authors are allowed to update their own posts, like this:
item: {
// The current user can update any post they authored, regardless of the isEditable flag
update: (session, item) => {
return item.author === session.itemId || item.isEditable;
},
}
Or add further restrictions based on the specific updates being made, by referencing the inputData argument.
So item access control is arguably more powerful but they can have significant performance implications – not so much for mutations which are likely to be performed in small quantities, but definitely for read operations. In fact, Keystone won't let you define item access control for read operations. If you stop and think about this, you might see why – doing so would require reading all items in the list out of the DB and running the access control function against each one, every time a list was read. As such, the items accessible can only be restricted using filter access control.
Tip: If you think you need item access control for reads, consider putting the relevant business logic in a resolveInput hook that flattens stores the relevant values as fields, then referencing those fields using filter access control.
Hope that helps

How to limit Podio API output for complex item resources

Is it possible to limit API response from the POST /item/app/{app_id}/filter/ for a complex resources with a lot of fields set? I know there is fields param and it can be used with some predefined views, like items.view(micro), but this is not a solution for us. We need to explicitly define which fields should be returned, to have in the response only needed fields (optimize the output length as much as possible, but have all needed fields). Can we somehow achieve this by available for now API params or could Podio consider to introduce such functionality in the future API revisions?
You can specify the fields() decorator, to add desired fields to your view.
items.view(micro).fields(fields,created_on,last_edit_on)
Unfortunately I don't think it is possible to further filter the number of Podio Application's Fields returned under the REST response's fields field (ugh 😓).
I couldn't find a way to filter on the Fields defined by the user on the Application.
E.g.
[
{
"fields": [
{
"label": "My item field"
"field_id": 123456778,
"external_id": "my-item-field"
}
]
}
]
It's not possible to filter by my-item-field.
As I know, Rest Api doesn't allow you to limit (select) output fields. (As GraphQL can do)
So, while Podio is on Rest Api - you can't do that.

Merging data from different graphql resolvers in vue.js client side for simple outputting

I do query cars from an api with a single query but two resolvers (listing and listings)(hopefully resolver is the right name for it). One car I get by the id via listing and the other cars I get without filters by listings. The resolvers output the data i a little different structure on the server-side but I do get the same fields just at different „places“. I want to merge the structure in order to get a single array I can simply loop over in vue.js. For the apicalls I do use vue-apollo.
Couldn't find any information to merge data client-side inside graphqlqueries. All I found is about handling it serverside with resolvers but it's an api I do not own.
Is it possible with graphql or do I have to merge it inside my vuecomponent and if so what would be the best way to do so?
The output will be a grid of cars where I show the car of the week (requested by id) together with the newest cars of the regarding cardealer.
Full screenshot including response: https://i.imgur.com/gkCZczY.png
Stripped down example with just the id to show the problem:
query CarTeaser ($guid: String! $withVehicleDetails: Boolean!) {
search {
listing(guid: $guid){
details{
identifier{
id #for example: here I get the id under details->identifier
}
}
}
listings( metadata: { size: 2 sort:{ field: Age order: Asc}}) {
listings{
id #here it's right under listings
details{
…
}
}
}
}
}
}
Ideally you're right, it should be handled server-side, but if it's not your API the only solution is to manipulate the data on the client side, meaning in your component.
It's probably a lot simpler to leave the listings array untouched and to just merge the listing element with it, like this for instance:
// assuming 'search' holds the entire data queried from the api
const fullListing = [
// car of the week, data reformatted to have an identical structure as
// the 'other' cars
{
id: search.listing.details.identifier.id,
details: {
vehicle: search.listing.details.vehicle,
},
},
...search.listings.listings, // the 'other' cars
]

Zapier lazy load input fields choices

I'm building a Zapier app for a platform that have dynamic fields. I have an API that returns the list of fields for one of my resource (for example) :
[
{ name: "First Name", key: "first_name", type: "String" },
{ name: "Civility", key: "civility", type: "Multiple" }
]
I build my action's inputFields based on this API :
create: {
[...],
operation: {
inputFields: [
fetchFields()
],
[...]
},
}
The API returns type that are list of values (i.e : Civility), but to get these values I have to make another API call.
For now, what I have done is in my fetchFields function, each time I encounter a type: "Multiple", I do another API call to get the possible values and set it as choices in my input field. However this is expensive and the page on Zapier takes too much time to display the fields.
I tried to use the z.dehydrate feature provided by Zapier but it doesn't work for input choices.
I can't use a dynamic dropdown here as I can't pass the key of the field possible value I'm looking for. For example, to get back the possible values for Civility, I'll need to pass the civility key to my API.
What are the options in this case?
David here, from the Zapier Platform team.
Thanks for writing in! I think what you're doing is possible, but I'm also not 100% that I understand what you're asking.
You can have multiple API calls in the function (which it sounds like you are). In the end, the function should return an array of Field objects (as descried here).
The key thing you might not be aware of is that subsequent steps have access to a partially-filled bundle.inputData, so you can have a first function that gets field options and allows a user to select something, then a second function that runs and pulls in fields based on that choice.
Otherwise, I think a function that does 2 api calls (one to fetch the field types and one to turn them into Zapier field objects) is the best bet.
If this didn't answer your question, feel free to email partners#zapier.com or join the slack org (linked at the bottom of the readme) and we'll try to solve it there.

Designing of a RESTful API endpoint filter and search

I am in the process of developing some custom API endpoints (using loopback.io), on top of its existing CRUD endpoints.
In the past I've used some other Node RESTful API frameworks for prototyping, and really enjoyed the powerful filtering features they provide out of the box.
What I'd like to do is provide a similar set (or subset) of some kind of filtering for a custom endpoint. The endpoint just does a SQL query (with some JOINs) and returns an array of objects.
Is there any kind of standardized approach that I should use to design some filtering? For example, I may want to filter on fields of the base table, or filter on relations. I like the way loopback.io and sequelize allow relatively easy specification of includes to link related objects, as well as their filtering syntax.
How is this type of problem usually approached when a custom implementation is done?
As you probably noticed with CRUD endpoints, LoopBack provides querying out of the box via filter parameter. You can nicely experiment with it in API Explorer. If you want to expose querying for a custom remote method, just add filter as a parameter too.
example-model.js
module.exports = ExampleModel => {
const search = async (filter = {}) => {
return await ExampleModel.find(filter)
}
ExampleModel.remoteMethod('search', {
description: 'Returns a set of ExampleModel based on provided query.',
accepts: [
{arg: 'filter', type: 'object', required: false}
],
http: {path: '/search', verb: 'get'},
returns: {root: true}
})
ExampleModel.search = search
}