-
Notifications
You must be signed in to change notification settings - Fork 65
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
[Feature Request] Stream of custom events #469
Comments
I think #426 would give a bit more of structure to what you want to do. But you can also achieve it today by starting a process and having the process subscribe to the events and then update the frames as appropriate. My concern with the pubsub style approach you mention is the possibility of races (if events are emitted before the animate subscribes?). |
@arathunku you could invoke all handles from the same listener, no? What am I missing? :) |
You're both, of course, right. I'm aware of that too and you're not missing anything! Unfortunetly, this isn't the reality of how I see people write notebooks at place where I work. People start with creating and documenting cells. Usually markdown -> Elixir cell -> markdown -> elixir/smart cell -> markdown, this all works fine. They typically mark cells to be evaluated automatically, this is still fine... great UX, very beginner-friendly. Then, "let me just share this as app" with option to reload data initial data/some form at the beginning... This is a moment where I typically get asked for support on how to do that because it's like drawing the rest of the owl meme and they're lost. 😄 My idea with events was that it would be easy to explain, "feel" like rest of the events(input, interval, form) Kino may handle. Maybe in reality, I'm looking for a solution in apps to reevaluate cells and have other cells in apps reevaluate automatically too. @josevalim, regarding possibility of races -> I think that's expected and this is already a case with current events from forms/inputs if |
@arathunku actually, if you mark cells as reevaluating automatically, they get reevaluate in the app also. Which Livebook version are you using? |
🤔 @jonatanklosko I'm using main branch from about 4 weeks ago so almost v0.14 before it was released. I don't think it's a problem with the updates but structure and flow of creating notebooks that may at some point get shared as app. Example notebook: <!-- livebook:{"app_settings":{"access_type":"public","auto_shutdown_ms":5000,"output_type":"rich","slug":"issue-469"}} -->
# Prototype idea
```elixir
Mix.install([{:kino, "~> 0.14.0"}])
```
## Section
<!-- livebook:{"reevaluate_automatically":true} -->
```elixir
Kino.Markdown.new(
"Testing automatic eval in app model: #{DateTime.utc_now()}. Only rich outputs"
)
```
<!-- livebook:{"reevaluate_automatically":true} -->
```elixir
# heavy db query
# how to reload this cell in app with rich outputs only that would trigger notification in other apps?
{:ok, results} = {:ok, [1, 2, 3, 4, 5]}
```
<!-- livebook:{"reevaluate_automatically":true} -->
```elixir
Kino.Markdown.new("#{Enum.join(results, ", ")} #{DateTime.utc_now()}")
``` Right now, when editing, it's very intuitve for users that when they reevaluate the cell with DB query, any cell below it with a reference to variable will get reevaluated, smart cells included. How can app trigger this behaviour? 🤔 Right now my approach and recommendation was what's in the initial post - reactive frames listening to interval/button click. |
@arathunku ok, I see what you mean. You have that baseline notebook and then want to convert to an app, and currently there is no app equivalent of reevaluating the query cell, so you need to rework it into a form. I think we can solve this with an input button. It would be similar to the button control, but it would have a value, which is the number of clicks (bump-only counter). With that we can implement reload functionality by showing the button and doing So your notebook would become: <!-- livebook:{"app_settings":{"access_type":"public","auto_shutdown_ms":5000,"output_type":"rich","slug":"issue-469"}} -->
# Prototype idea
```elixir
Mix.install([{:kino, "~> 0.14.0"}])
```
## Section
<!-- livebook:{"reevaluate_automatically":true} -->
```elixir
Kino.Markdown.new(
"Testing automatic eval in app model: #{DateTime.utc_now()}. Only rich outputs"
)
```
<!-- livebook:{"reevaluate_automatically":true} -->
```elixir
reload_button = Kino.Input.button("Reload data")
```
```elixir
_ = Kino.Input.read(reload_button)
# heavy db query
# how to reload this cell in app with rich outputs only that would trigger notification in other apps?
{:ok, results} = {:ok, [1, 2, 3, 4, 5]}
```
<!-- livebook:{"reevaluate_automatically":true} -->
```elixir
Kino.Markdown.new("#{Enum.join(results, ", ")} #{DateTime.utc_now()}")
``` You can even read the button in multiple cells, so that clicking it would evaluate all of them. This is a little bit indirect, but should do the job. What do you think? |
@jonatanklosko this would 100% solve the problem but adding "button" in input module just for this use case sounds... wrong? I'm unsure what other use cases are there. One alternative that maybe feels more intiutive is empty form with just "submit"? I believe <!-- livebook:{"reevaluate_automatically":true} -->
```elixir
# may be expanded later with additional inputs!
# this currently doesn't work, form wants at least 1 input
form = Kino.Control.form([], submit: "Reload data")
```
<!-- livebook:{"reevaluate_automatically":true} -->
```elixir
# new functionality
_form = Kino.Control.read(form)
``` |
There is a conceptual difference though. Controls are not read, only inputs are. But maybe that's something we can change as part of this work and maybe we can have a single unified module? |
Forms are fundamentally different, because each user has their own values of the inputs, so "reading a form" is not a thing. The use case for forms is processing submissions asynchronously, and possibly rendering results to the individual user (though that depends on what you are doing). On the other hand, I think this use case is a good enough justification for input button, because it effectively is a bridge for the app user to reevaluate cells. |
FWIW I would love to unify inputs and forms, but it's two fundamentally different models (shared vs not shared computation), so that's why we have both. |
Oh, #TIL! Given examples in Kino.Control.stream/1` I assumed it would be similar.
|
Technically we could deprecate Just to be clear on the distinction, I agree that |
It probably makes sense to deprecate Control.button given we could use an empty form? And we delegate it to the input one during the deprecation? |
@josevalim not even because of form, you can already do that, but rather because we support subscribing to inputs, so it would be like this: button = Kino.Control.button("Button")
Kino.listen(button, &IO.inspect/1)
#=> %{type: :click, origin: "4mb6p7xbajyendmo"} button = Kino.Input.button("Button")
Kino.listen(button, &IO.inspect/1)
#=> %{type: :change, value: 1, origin: "4mb6p7xbajyendmo"} The event is different, so we want to be graceful with the deprecation, but you should be able to replace the control with input in all places. |
The button input is essentially a stateful button control. |
👋 Hi!
I believe this would be a feature request, and I'm unclear whether issue tracker is the correct place to raise this?
Context
Let's say we've 2-3 heavy DB queries and I want to create a report that's available as app. Ideally, automatically refetch the data at some interval or when user clicks a button.
In edit mode, I can select most of the cells as "evaluate automatically" and it's works nicely, but it doesn't work for apps.
Actual solution as workaround
At this moment, I get around this by creating "UI" module that's appending data info in a single frame, but this mode of writing a notebook that also works as an app leaves a lot to be desired. This is also very difficult to explain to people that are just starting with Elixir and want to publish their notebook as an app.
Example:
Expected solution
Kino already has API for streams via Kino.Control.stream/1. I would love to simplify the code above to something like:
It's also very likely that I'm doing something wrong and this is already possible but given the validation here I don't think it's easy to do nor obvious from the documentation.
The text was updated successfully, but these errors were encountered: