-
-
Notifications
You must be signed in to change notification settings - Fork 39
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
Use a standardized query language to replace filter
and pass
props.
#342
Comments
hmm this is interesting @jgaehring. to clarify, you're thinking of trying to use GraphQL as an internal API?
I think the larger question would be how to implement GraphQL in this "internal" way.. I agree it does seem like it's possible. Vue Apollo has a cache system that is designed to replace
Unfortunately, I'm not sure GraphQL would quite work in the ways you might be thinking It would be possible to construct a GraphQL query like this:
This is all possible, you'd just have to implement the logic in a custom GraphQL resolver for this
A GraphQL Enum type for things like If the farmOS server had a GraphQL endpoint with resolvers that matched all of the needs of Heck, if we defined some custom REST API endpoints on the farmOS server then, in a similar way, it would be possible for Field Modules to define query params and pass them straight through to the server.. |
Just doing more research into this... seems like other GraphQL toolkits like Prisma might add on some additional GraphQL filter features. This blog post from Prisma is interesting: https://www.prisma.io/blog/designing-powerful-apis-with-graphql-query-parameters-8c44a04658a9 I saw something similar with Gatsby. I hadn't seen those additions before. It's quite cool, but not a GraphQL standard. Not sure if anything like that would work out of the box with farmOS, either.. |
I concur with Paul. GraphQL is a one way API for quickly returning fields
of information from the server. It looks somewhat promising, I think more
for remote dashboards or something of that nature.
<https://www.avast.com/sig-email?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=webmail&utm_term=icon>
Virus-free.
www.avast.com
<https://www.avast.com/sig-email?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=webmail&utm_term=link>
<#DAB4FAD8-2DD7-40BB-A1B8-4E2AA1F9FDF2>
…On Mon, Apr 20, 2020 at 5:12 PM Paul Weidner ***@***.***> wrote:
Just doing more research into this... seems like other GraphQL toolkits
like Prisma might add on some additional GraphQL filter features. This blog
post from Prisma is interesting:
https://www.prisma.io/blog/designing-powerful-apis-with-graphql-query-parameters-8c44a04658a9
I saw something similar with Gatsby. I hadn't seen those additions before.
It's quite cool, but not a GraphQL standard. Not sure if anything like that
would work out of the box with farmOS, either..
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#342 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/APERD7HW5XTZKII3RQLEEVLRNTCDJANCNFSM4ML3655A>
.
|
Correct. I know that's a little weird, but I think there is some precedent, specifically how Gatsby and other SSG's use GraphQL in a Node process to structure queries of the file system and other local resources. My only significant use of GQL in the past has been with Gatsby, in fact, so that's definitely driving a lot of my line of thinking. And looking at their docs now, it does appear that Gatsby uses something similar to Prisma for
This is very similar to Prisma. So, to get to the heart of the matter, what I'm looking for is a standard language for defining a subset of data from a larger set. I think any query language fulfills that basic requirement (hell, even SQL could be used), but obviously there's a lot of unnecessary overhead that comes with a lot of those languages. Right now, with the As I mentioned in #339, I'm looking to refactor some of the IndexedDB utilities in FK to use indexes and key ranges, and it could be a good time to rework the So yea, dunno where that leaves us, but it's something to think about. |
I'm working right now on the Spraying Field Module and trying to figure out how best to structure a https://test.farmos.net/log.json?log_category=33 Crucially, the That all taken into consideration, I've made a first pass at how that might work if we allowed a function to be passed as one of the filters: filters: {
log: {
log_category(cats) {
const { tid } = cats.find(cat => cat.name === 'Spray');
return cats
.filter(cat => (
cat.name === 'Spray' || cat.parents_all.some(parent => parent.id === tid)
))
.map(cat => cat.tid);
},
},
}, I haven't tried implementing this at all, want to hammer out the API first, but I think it should be pretty simple, something like: let log_category;
if (typeof filters.log.log_category === 'function') {
log_category = filters.log.log_category(categories);
} Since the function evaluates to an array of Still, I wonder if there might be a better way of expressing this as some sort of query statement or even just plain JSON data, rather than a function. Looking at GraphQL/Prisma/Sift, I think we'd still have the problem of getting the correct So I guess GraphQL wouldn't provide any easier solutions for us, but again, I'm hopeful that it, or some type of query language, could provide a more declarative, generalized and, most importantly, standardized API for expressing the query, rather than us inventing our own standard, or forcing the consumer to write the procedure out by hand. |
@jgaehring This is a great example of the problem!I think this hits it on the nail:
Yeah, there will have to be the logic to convert a category
The function is an interesting way of doing this, but does seem a little complicated. The resolver will still need logic to figure out that it needs to supply this custom Because the resolver needs additional logic either way, documenting a JSON filter object seems to make sense... although allowing functions does seem to be more versatile... I'm torn 😕
THIS is interesting: RSQL (Rest Query Language) From the docs:
It will still require logic to parse the RSQL query...... but do so in a standardized way? Other relevant things: Summary of the problem in the GraphQL context: graphql/graphql-js#640 (comment) Older issue discussing adding these features to GraphQL: graphql/graphql-spec#271 |
Thanks, @paul121, terrific feedback!
Correct. But I'm not so worried about that, because that won't face the Field Module developer. I'm more concerned about providing a clean, stable API that doesn't leak too many of the implementation details to the API consumer.
Correct again. I think we want to do this for any fields that contain an {
"farm_activity": {
"label": "Activity",
"label_plural": "Activities",
"fields": {
"area": {
"label": "Areas",
"type": "taxonomy_term_reference",
"required": 0,
"default_value": []
},
"asset": {
"label": "Assets",
"type": "entityreference",
"required": 0,
"default_value": []
},
"files": {
"label": "Files",
"type": "file",
"required": 0,
"default_value": []
},
"geofield": {
"label": "Geometry",
"type": "geofield",
"required": 0,
"default_value": []
},
"images": {
"label": "Photos",
"type": "image",
"required": 0,
"default_value": []
},
"notes": {
"label": "Notes",
"type": "text_long",
"required": 0,
"default_value": null
},
"movement": {
"label": "Movement",
"type": "field_collection",
"required": 0,
"default_value": null
},
"log_category": {
"label": "Log category",
"type": "taxonomy_term_reference",
"required": 0,
"default_value": null
},
"log_owner": {
"label": "Assigned to",
"type": "entityreference",
"required": 0,
"default_value": null
},
"inventory": {
"label": "Inventory adjustments",
"type": "field_collection",
"required": 0,
"default_value": null
},
"membership": {
"label": "Group membership",
"type": "field_collection",
"required": 0,
"default_value": null
},
"quantity": {
"label": "Quantity",
"type": "field_collection",
"required": 0,
"default_value": null
},
"flags": {
"label": "Flags",
"type": "list_text",
"required": 0,
"default_value": null
},
"data": {
"label": "Data",
"type": "text_long",
"required": 0,
"default_value": null
},
"equipment": {
"label": "Equipment used",
"type": "entityreference",
"required": 0
}
}
}
} For the You and I spoke a little bit in Riot about this, but I think this would be aided by a more thorough means of defining log types, such as JSON Schema, as outlined in farmOS/farmOS#243. I may follow up in that issue with more details, but for now I think we can just hardcode references in the resolver.
Oh that is very cool! I think I'm a little less inclined to adopt that though, partly because it doesn't seem as readable as a GraphQL query or JSON, and also because I feel it could be misleading if it gives module developers that this would reflect the actual farmOS REST API. You have prompted me to look at other standards though, and I recalled the Sift docs were based primarily on MongoDB's query syntax. There's a whole long list of operators, such as MongoDB seems like a nice alternative, because it's basically plain JavaScript syntax, with the firm backing of a widely-adopted standard, ala MongoDB. Plus it might be possible to employ the Sift library in implementation, making my job a lot easier. I'll play around with the syntax and try to come up with an equivalent |
Just want to chime in quick on this, with something that came up as a "maybe going to do" requirement for Our Sci that could help here: thinking about adding a small bit of abstraction to the farmOS server API that will remove the need to worry about "term IDs" ( Internally, farmOS has a PHP function If we could leverage that via that API, we could potentially remove the need for the second lookup of Would that obviate the need for further effort in the short term? |
@mstenta yes, that would be helpful. I came across a similar issue in the CSV Importer. I drafted a reply for this thread but saved because I thought it might come up soon... See this: https://gist.github.com/paul121/05cace7f09e13e7d2185897c226ecef7 I wound up caching the taxonomy vocabulary If not on the farmOS server, I'm curious if some of these things could be convenience features provided by farmOS API clients |
Thumbs up - I talked with @pcambra about it and he is going to take a look at specifically enabling the ability to filter using the term name, instead of ID.
Yea that's also a possibility, but if it's possible on the server that would be better IMO. |
This would simplify a lot of FK code immensely! However, I don't think it will totally resolve this particular issue. The critical thing would be whether I could use URL params with the taxonomy On that note, however, I'm pretty optimistic about the prospect of adopting MongoDB-style queries and the Sift library. First of all, by adopting a pre-existing standard, it relieves me of the necessary decisions about how to structure every aspect of the API. Secondly, I spent a little while reading through the Sift docs and source and am very impressed; I think it will help tremendously with the implementation! What's really nice is that calling |
That's exactly what I was thinking! Sorry if that was unclear. :-)
Cool! I'm curious what that means. I'm wary of over-engineering early on, especially as things may change in farmOS 2.0.x. So might be better to accept that some of the API surface for field modules will change as these things solidify. I don't think we can expect to get it right the first time. And accepting that makes things a lot easier. I always like to "tread lightly" until we have a better sense of needs. :-) |
Oh great! Now I'm going to up the ante: how can I structure URL params to get any logs in the category "Spray" AND any logs in categories with a parent category of "Spray"?
Totally! That's actually one of my main concerns, creating a stable API surface that won't be so closely coupled to farmOS 1.0, while also outsourcing as much of the API decision-making as I can (hence, the search for a pre-existing standard we can adopt). My first naive implementation of the filters: {
log: {
done: false,
type: ['farm_activity', 'farm_observation']
},
}, becomes this: https://test.farmos.net/log.json?done=false&type[0]=farm_activity&type[1]=farm_observation That was pretty simple at the start, but it's already becoming much too complicated to implement, with even just a few edge cases to accomodate, like the And that also bleeds additional implementation details into the local database queries: So yea, don't want to over-engineer, but I'm also really wary of that implementation swelling up further and having to maintain it. I also feel like I'm going to reinvent the wheel if I start adding logical and comparative operators to the JavaScript object (eg, |
filters
in module.config.js files.filters
in module.config.js files.
Three new feature requests for farmOS core:
|
Give a mouse a cookie! ;-) That could be possible, but won't be covered by the links above. I'm curious what the use-case is, though. It seems risky to base filtering on the hierarchy of terms - since that could be changed by the user on the server. |
Sorry if I haven't followed all the details in this thread closely, but I'm not sure I understand how you can avoid hard-coding the same knowledge about the server's expectations by adopting a higher-level syntax. It seems like you would still need all the same translation logic to map from MongoDB syntax to the HTTP requests anyway... AND you're then setting expectations higher by saying "we use MongoDB syntax" (which someone might interpret as "oh great then it can do everything i want"). I might just need to be hand-held through the thought process. :-) Are you thinking about moving forward with these things right now? Or still just thinking about possibilities? I guess I'm also nervous about making big decisions before we have really reviewed what the API changes will be in D8/9. |
So I made a rough attempt at what a https://codesandbox.io/s/stoic-golick-0jk84?file=/src/index.js In the filters: {
log: {
log_category: {
$or: [
{ name: "Spray" },
{ parents_all: { $elemMatch: { name: "Spray" } } },
],
},
},
}, This query syntax is more or less equivalent to the filter function I posted above: filters: {
log: {
log_category(cats) {
const { tid } = cats.find(cat => cat.name === 'Spray');
return cats
.filter(cat => (
cat.name === 'Spray' || cat.parents_all.some(parent => parent.id === tid)
))
.map(cat => cat.tid);
},
},
}, Of course, as you'll see in the Codesandbox, I'm taking some liberties by including the category's What's interesting, I think, is that the predicate function parameter passed to Well, almost equivalent. The function parameter in the farm.log.get({
log_category: rootState.farm.categories.filter(sift({
$or: [
{ name: 'Spray' },
{ parents_all: { $elemMatch: { name: 'Spray' } } },
],
})).map(cat => cat.tid),
}); Which roughly evaluates to something like this: farm.log.get({
log_category: [33, 35],
}); From which, in turn, farmOS.js generates URL params more or less like this: https://test.farmos.net/log.json?log_category[0]=33&log_category[1]=35 (NB: It appears these URL params don't work, seemingly because the restws module doesn't support multiple Which is all to say, I think we can get a lot of use from the Sift library! |
The user already has several spraying-related categories, such as "Pre-Emerge Spray", "Post-Emerge Spray" and "Y-Drop". It's not ideal, but the apart from adding a dedicated log type, we need a way to identify all of these under one category. My hope is to reduce the chance of error by nesting the different types of spray under one, general "Spray" category (worth noting, that "Y-Drop" is already a child of the "Fertilizer" category, but we can allow for multiple parents, hence the use of Again, not ideal, but seems like the most flexible option given our constraints. |
I'm trying to avoid that hard-coded knowledge getting into the API. It's acceptable to me if that info is hardcoded into the implementation. The most naive API we could present would just be to require that each Field Module supply its own URL parameters, as a string, but obviously that has the danger of breaking all modules if there's any change to the REST API. Our current You must also remember that I could keep extending this ad hoc API to suit our needs as they come, but for each iteration that requires a lot of careful considerations that are not obvious at first (eg, how to handle
I don't see how that expectation is being set automatically. For one thing, we don't need to state in our API that we support the full MongoDB syntax. We can claim to support a subset of the syntax if we want (like the logical and comparative operators only, as I suggested above), or we can just document the supported operators piecemeal, in our own documentation. And even if we did just adopt their query syntax wholesale, it seems like a stretch to interpret that as "oh great then it can do everything I want". There are still conditions on what data is available and how it can be manipulated; we're just providing a way of describing it. And if a developer did take that leap, that any arbitrary MongoDB operation was made possible by that syntax, they would quickly find out it didn't work. The query would fail, and they'd have to figure something else out before moving on with development. I'd much prefer prompt failures in development to possible breakage in production because what they thought worked was later broken by an upstream change, which seems more likely with an ad hoc API that's more closely coupled to the REST API.
Yea, so, all my strongly stated opinions aside, I am still in the exploratory phase. I am very optimistic about what can be achieved with minimal effort by employing the Sift library, but I also recognize that, to its credit, Sift is ultimately just a way of converting MongoDB queries into predicate functions (see #342 (comment)), and it is those functions which are ultimately required in the implementation, not the queries themselves. So I'm considering the possibility of just supporting functions in the let query;
if (typeof log.filters.log_category === 'function') {
query = log.filters.log_category;
} else {
query = sift(log.filters.log_category);
} That seems like a reasonable first step. What I do want to avoid right now is extending the API to support our own ad hoc query syntax, which might have to be changed later on. For instance, if we were to try adding logical operators to the |
@mstenta, does this mean the taxonomy's |
Yes: https://www.drupal.org/project/farm/issues/3134065 It would apply to any taxonomy term reference field. It would not apply to asset, area, or other entity references. Maybe we could do the same with those, but that's outside the scope of the feature request linked. |
Ahh, ok, good to know. This would be helpful, because there are lots of places where I'm looping inside of loops to check whether the |
I'll be honest I'm a bit worried about how much complexity this all will add, and potential issues it could create in the future. But it's just a gut feeling - and I'm not fully grokking what's involved, and what the implications are. In general, I agree that it's a good thing to make the "API" of field modules abstracted, to avoid changes to the server breaking field modules. But in practice I have a feeling that we may not be able to avoid some of those issues. And that is specifically why farmOS added the The very fact that Field Modules will be loaded dynamically into an unknown version of farmOS and an unknown version of Field Kit makes it even more necessary for them to declare their supported API version, I think. What happens if they declare their filters using MongoDB syntax, but the filters they describe are not supported on the particular farmOS/Field Kit versions they are being run on? The same "breakage" can occur. So it makes me wonder if the extra complexity is worth it, or if we should just accept that field modules will need to declare their supported API versions regardless. That would keep things a whole lot simpler right now, even if it means needing to make changes in the future. Those changes are probably unavoidable. And then we're right back where we stared: if they are already pinning to a specific API version, then it's not really an issue if they make specific assumptions about the API. I guess the main feeling I have is: we don't know what we need yet. And maybe that is what 1.x vs 2.x API versions are useful for. It might be better to avoid trying to predict too much in 1.x, accept that some things will be hard-coded, and then add more capabilities via progressive-enhancement when we do have a better understanding. All that said, I want to emphasize that this is just my "gut" feeling about these things, and I admit that I haven't taken the time to understand everything in great detail. So take it for what it's worth... which might not be much. Just thoughts I felt the need to put out there. :-) If you are well into it already, don't let this slow you down! |
Updates... It's now possible to filter by term name instead of ID! https://www.drupal.org/project/farm/issues/3134066#comment-13607701 And term names are included in the results! https://www.drupal.org/project/farm/issues/3134065#comment-13607744 Thanks @pcambra! |
Awesome! Thanks @pcambra & @mstenta! So how are we incorporating this into the API versioning scheme? Will it require a bump? Can I expect this feature to be available in all Farmier instances? It's not urgent. I think I'm going to leave my new implementation of |
Good question. Yes, I think we should bump the API version to 1.3. I've already started some updates to the API docs on farmOS.org - but these won't be published until the next tagged release of farmOS (which I am getting closer to!)
Yes, it is already deployed to Farmier instances. |
filters
in module.config.js files.filter
and pass
props.
I think we've made good progress on some of the issues raised here, with the |
I'm bumping this up to the 2.0.0-beta1 milestone. I've got a first pass on using MongoDB-style queries in farmOS/farmOS.js@527d4f4 (see also #430 (comment)). It should be a simple matter at this point to enable the |
I believe the last piece of this should be resolved by jgaehring/field-kit@ff1784a, which will handle filters for local caching, since farmOS.js has nothing to do with that. Just need to test it thoroughly to be sure. |
Resolved by 59c3f30. |
I keep coming back to this idea that it might be possible to replace our current
filters
option in themodule.config.js
file that Field Modules provide with a GraphQL query. I'm really not satisfied with thatfilters
option; it just feels a bit hacky and only seems to cover a narrow range of use cases. Here's an example of what it looks like now:https://github.com/farmOS/farmOS-client/blob/cf6a1d00c3b2bcda74e92e3522a1d4a743c181d0/src/field-modules/my-logs/module.config.js#L7-L12
In place of this, Field Modules could simply provide a GraphQL query string to define precisely what data they need from the server, as well as from the local cache.
I could put a lot of time and thought into improving the
filters
options' API, but it would never be quite as robust as an established standard query language like GraphQL. Plus I don't have to worry about documenting our own API if I can just lean on the GraphQL docs instead. And it seems like a natural fit, especially if we end up adopting GraphQL on the server when we move to D8.It'd be especially cool to work out some sort of integration with GraphQL and IndexedDB for querying the local cache. There appears to be a library which does that: https://github.com/genie-team/graphql-genie
My biggest concern would be how to handle values like the
'SELF'
constant above. Not at all sure how that would work with GraphQL; perhaps it's something special we could implement with Apollo or one of the client libraries.@paul121, I know you've worked a little with GraphQL on both the client and server side of things; curious what you think of this and whether it would work.
The text was updated successfully, but these errors were encountered: