-
Notifications
You must be signed in to change notification settings - Fork 94
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
Add subscriptions #3390
Add subscriptions #3390
Conversation
To be updated soon. At the moment I have an issue with the context info. In the Query resolvers, the context is added in the Tornado handler. However, that handler extends a class from While for subscriptions, we don't extend this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dwsutherland finished the PR for the subscription { workflows {} }
. Added some comments to the code. I think our only option would be to copy everything done for the Query, but wrapping around a while True: ... asyncio.sleep
? If you agree with that, then I will start the copy-and-paste and finish updating this PR.
cylc/flow/network/schema.py
Outdated
args['exworkflows'] = [parse_workflow_id(w_id) for w_id in | ||
args['exids']] | ||
resolvers = info.context.get('resolvers') | ||
yield await resolvers.get_workflows(args) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is pretty much the same as get_workflows
- used for a Query.
With a difference that we return the coroutine here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could move the argument parsing to a common function called by both.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, but then we would also have to do that for all other resolvers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, all if it’s worth it... but if we move argument parsing to resolvers.py it would serve the same purpose... Then the schema would be pure schema
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then the schema would be pure schema
Ah, I think I'm starting to understand it. This would simplify the schema a bit more. Would it be alright to move this to a follow-up PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it also might open other avenues, hiding the nuts and bolts 🔩 of the driving data... i.e. defining the schema in GraphQL pseudo code, like a document which is not implementation language specific (which is a thing)
(Not that we want to go that way)
cylc/flow/network/schema.py
Outdated
args['exids']] | ||
resolvers = info.context.get('resolvers') | ||
yield await resolvers.get_workflows(args) | ||
await asyncio.sleep(5.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most examples in graphql-ws
use a 1.
interval. However, doing so would send data to the UI too fast I think (?). At least for now, until we have the deltas and/or update the UI to handle updates selectively (i.e. update the workflow/graph data structure on the UI only if it's different.. though it'd be a bit hard for the tree at least)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I've set it to 5 seconds... which isn't really much different than the polling we have right now 😬 but one step each time 🚶♂️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Depends on the query I suppose, and whether it's only updates feed to the resolver.. i.e.
query { workflows { id } }
would be ok.. All this will be driven by data updates in the future (which could be sub 1sec 😬 )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we can't just send updated content then, this is another reason why views just subscribing to exactly what they need might be better (so not all data is sent every push)..
cylc/flow/network/schema.py
Outdated
description=Workflow._meta.description, | ||
ids=List(ID, default_value=[]), | ||
exids=List(ID, default_value=[]), | ||
resolver=subscribe_workflows) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is pretty much the same as the class Queries
we have already for a Query. Only difference being the resolver...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This duplication is ok..
resolver=subscribe_workflows) | ||
|
||
|
||
schema = Schema(query=Queries, subscription=Subscriptions, mutation=Mutations) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Schema untouched, with exception of a new subscription=
value 👍 then the schema "understands" queries with subcription { ... }
while True: | ||
yield await func(*args, **kwargs) | ||
await asyncio.sleep(sleep_seconds) | ||
return gen |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dwsutherland a function that converts the existing resolver methods into async-generators, expected for the websockets to work.
description=Workflow._meta.description, | ||
ids=List(ID, default_value=[]), | ||
exids=List(ID, default_value=[]), | ||
resolver=to_subscription(get_workflows)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dwsutherland now I can query
subscription { workflows }
Or
subscription { tasks }
However, querying
subscription { workflows { tasks } }
Doesn't bring me anything. Any clue what I could be missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will have a look
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, never mind. Just realized why that's happening. In the class Workflow(ObjectType)
, it has the Tasks
and the resolver is set there too. Hmmm, tricky to get this one working for subscriptions now 😞
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think someone had the same issue with the aiohttp version:
graphql-python/graphql-ws#12 (comment)
Looks as though they modified the handler to await all async nested fields..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀 gonna read it again tomorrow. That method could be a life saver. Just need to understand the logic and see if that'd work for us too. Great find @dwsutherland !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dwsutherland , quick test, and that actually worked. Will update the PR (just clean up current mess, move customizations to a separate file, etc) in a few minutes. Understood about 50% of the code, but will re-read it in the morning ☕ Thanks a ton for that. Otherwise I would still be scratching my head with no solution.
d4e2503
to
2f61a51
Compare
Works great! thanks @kinow.. Tested it also:
After the nested async issue is solved, we can probably try get this one in... We can modify & organise the code where needed afterwards... This will at least allow the UI to change to the new protocol (not sure if the proxy needs modifying?) |
Was going to add a changelog for 8.0a2, but looks like we don't have a changelog for 8.0a1, only for 8.0a0. Should we add one retroactively @cylc/core ? |
2f61a51
to
fa0a576
Compare
Just tried writing a very simple functional test. Copied Unfortunately it doesn't look like
Which is a command similar to what So I think we only need the unit test here for the schema. |
fa0a576
to
0646ffc
Compare
Coverage report not passing the threshold, but looking at the diff doesn't seem to have much else that can be tested without actually using a websocket + the ioloop to consume the async-generator 😥 |
I think it's Ok that we don't hit the 95% mark with this one.. Because adding functioning GraphQL subscriptions at the This PR is at minimum for adding UIS subscriptions (although you could wait, and construct the handler after #3389 )... Proper GraphQL subscriptions would be data-driven, not just a loop in the schema calling the query (IMO). |
We do have a section in CHANGES.md for 8.0a1 - maybe your branch was out of date? I've added one for 8.0a2 in #3402 |
Oh, I think I simply missed that, sorry. 👏 👏 will add one for the subscriptions now. Thanks! |
0646ffc
to
305de13
Compare
(change rebased onto master, no other code changes today 👍 ) |
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works for me! 🎉 ... We can make the push data driven in future PRs..
As long as tests pass, good to merge for me..
Kicked Travis (quite sure it passed before, probably because I rebased it). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍
This is a small change with no associated Issue.
Requirements check-list
CONTRIBUTING.md
and added my name as a Code Contributor.Sibling PRs: