mirror of
https://github.com/livebook-dev/livebook.git
synced 2024-09-20 10:05:57 +08:00
Initial revamp of notebooks
Deployment and introduction to Kino still need to be rewritten.
This commit is contained in:
parent
26af7c33ce
commit
764d775ed3
|
@ -54,21 +54,21 @@ defmodule Livebook.Notebook.Learn do
|
|||
details: %{
|
||||
description:
|
||||
"A fast-paced introduction to Elixir by building distributed data-transfer portals.",
|
||||
cover_url: "/images/elixir-portal.jpeg"
|
||||
}
|
||||
},
|
||||
%{
|
||||
path: Path.join(__DIR__, "learn/elixir_and_livebook.livemd"),
|
||||
details: %{
|
||||
description: "Learn how to use some of their unique features together.",
|
||||
cover_url: "/images/elixir.png"
|
||||
}
|
||||
},
|
||||
%{
|
||||
path: Path.join(__DIR__, "learn/intro_to_kino.livemd"),
|
||||
path: Path.join(__DIR__, "learn/deploy_apps.livemd"),
|
||||
details: %{
|
||||
description: "Make your notebooks interactive with inputs, controls, and more.",
|
||||
cover_url: "/images/kino.png"
|
||||
description: "Write and deploy a chat app with Kino control and frames.",
|
||||
cover_url: "/images/learn-deploy.svg"
|
||||
}
|
||||
},
|
||||
%{
|
||||
path: Path.join(__DIR__, "learn/intro_to_explorer.livemd"),
|
||||
details: %{
|
||||
description: "Intuitive data visualizations and data pipelines on the fly.",
|
||||
cover_url: "/images/explorer.png"
|
||||
}
|
||||
},
|
||||
%{
|
||||
|
@ -86,20 +86,13 @@ defmodule Livebook.Notebook.Learn do
|
|||
}
|
||||
},
|
||||
%{
|
||||
path: Path.join(__DIR__, "learn/intro_to_explorer.livemd"),
|
||||
details: %{
|
||||
description: "Intuitive data visualizations and data pipelines on the fly.",
|
||||
cover_url: "/images/explorer.png"
|
||||
}
|
||||
ref: :kino_reference,
|
||||
path: Path.join(__DIR__, "learn/kino/reference.livemd")
|
||||
},
|
||||
%{
|
||||
ref: :kino_vm_introspection,
|
||||
path: Path.join(__DIR__, "learn/kino/vm_introspection.livemd")
|
||||
},
|
||||
%{
|
||||
ref: :kino_chat_app,
|
||||
path: Path.join(__DIR__, "learn/kino/chat_app.livemd")
|
||||
},
|
||||
%{
|
||||
ref: :kino_pong,
|
||||
path: Path.join(__DIR__, "learn/kino/pong.livemd")
|
||||
|
@ -207,25 +200,17 @@ defmodule Livebook.Notebook.Learn do
|
|||
|
||||
@group_configs [
|
||||
%{
|
||||
title: "Advanced Kino",
|
||||
title: "Deep dive into Kino",
|
||||
description:
|
||||
"Advanced guides for learning more about the Kino package, including the creation of custom UI components.",
|
||||
"Learn more about the Kino package, including the creation of custom UI components.",
|
||||
cover_url: "/images/kino.png",
|
||||
notebook_refs: [
|
||||
:kino_reference,
|
||||
:kino_vm_introspection,
|
||||
:kino_custom_kinos,
|
||||
:kino_pong,
|
||||
:kino_smart_cells
|
||||
]
|
||||
},
|
||||
%{
|
||||
title: "Building and deploying apps",
|
||||
description:
|
||||
"Advanced guides for learning more about the deploying experience and teaching Kino",
|
||||
cover_url: "/images/kino.png",
|
||||
notebook_refs: [
|
||||
:kino_chat_app
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Building a chat app with Kino.Control
|
||||
# Deploy a chat app with Kino
|
||||
|
||||
```elixir
|
||||
Mix.install([
|
|
@ -1,218 +0,0 @@
|
|||
# Elixir and Livebook
|
||||
|
||||
## Introduction
|
||||
|
||||
In this notebook, we will explore some unique features when
|
||||
using Elixir and Livebook together, such as inputs, autocompletion,
|
||||
and more.
|
||||
|
||||
If you are not familiar with Elixir, there is a fast paced
|
||||
introduction to the language in the [Distributed portals with
|
||||
Elixir](/learn/notebooks/distributed-portals-with-elixir)
|
||||
notebook. For a more structured introduction to the language,
|
||||
see [Elixir's Getting Started guide](https://elixir-lang.org/getting-started/introduction.html)
|
||||
and [the many learning resources available](https://elixir-lang.org/learning.html).
|
||||
|
||||
Let's move forward.
|
||||
|
||||
## Autocompletion
|
||||
|
||||
Elixir code cells also support autocompletion by
|
||||
pressing <kbd>ctrl</kbd> + <kbd>␣</kbd>. The runtime must
|
||||
have started for autocompletion to work. A simple way to
|
||||
do so is by executing any code, such as the cell below:
|
||||
|
||||
```elixir
|
||||
"Hello world"
|
||||
```
|
||||
|
||||
Now try autocompleting the code below to `System.version()`.
|
||||
First put the cursor after the `.` below and
|
||||
press <kbd>ctrl</kbd> + <kbd>␣</kbd>:
|
||||
|
||||
```elixir
|
||||
System.
|
||||
```
|
||||
|
||||
You should have seen the editor listing many different options,
|
||||
which you can use to find `version`. Executing the code will
|
||||
return the Elixir version.
|
||||
|
||||
Note you can also press <kbd>tab</kbd> to cycle across the completion
|
||||
alternatives.
|
||||
|
||||
## Getting the current directory
|
||||
|
||||
You can access the location of the current `.livemd` file and
|
||||
its current directory using `__ENV__` and `__DIR__` variables:
|
||||
|
||||
```elixir
|
||||
IO.puts(__ENV__.file)
|
||||
IO.puts(__DIR__)
|
||||
```
|
||||
|
||||
## Mix projects
|
||||
|
||||
Sometimes you may want to run a notebook within the context of an existing
|
||||
Mix project. This is possible from Elixir v1.14 with the help of `Mix.install/2`.
|
||||
|
||||
As an example, imagine you have created a notebook inside your current project,
|
||||
at `notebooks/example.livemd`. In order to run within the root Mix project, using
|
||||
the same configuration and dependencies versions, you can change your notebook
|
||||
setup cell to invoke `Mix.install/2` with the following arguments:
|
||||
|
||||
<!-- livebook:{"force_markdown":true} -->
|
||||
|
||||
```elixir
|
||||
my_app_root = Path.join(__DIR__, "..")
|
||||
|
||||
Mix.install(
|
||||
[
|
||||
{:my_app, path: my_app_root, env: :dev}
|
||||
],
|
||||
config_path: Path.join(my_app_root, "config/config.exs"),
|
||||
lockfile: Path.join(my_app_root, "mix.lock")
|
||||
)
|
||||
```
|
||||
|
||||
## Runtimes
|
||||
|
||||
Livebook has a concept of **runtime**, which in practice is an Elixir node responsible
|
||||
for evaluating your code. You can choose the runtime by clicking the "Runtime" icon
|
||||
(<i class="ri-livebook-runtime"></i>) on the sidebar (or by using the <kbd>s</kbd> <kbd>r</kbd>
|
||||
keyboard shortcut).
|
||||
|
||||
By default, a new Elixir node is started (similarly to starting `iex`). You can click
|
||||
reconnect whenever you want to discard the current node and start a new one.
|
||||
|
||||
You can also manually *attach* to an existing distributed node by picking the
|
||||
"Attached Node" runtime. To do so, you will need the Erlang Name of the external node
|
||||
and its Erlang Cookie. For example, you can start a Phoenix application as follows:
|
||||
|
||||
```shell
|
||||
$ iex --sname phoenix-app --cookie secret -S mix phx.server
|
||||
```
|
||||
|
||||
Now open up a new notebook and click the "Runtime" icon on the sidebar.
|
||||
Click to "Configure" the runtime and choose "Attached node". Input the
|
||||
name and cookie as above and you should be ready to connect to it.
|
||||
|
||||
Note, however, that you can't install new dependencies on a connected runtime.
|
||||
If you want to install dependencies, you have two options:
|
||||
|
||||
1. Use the Mix project approach outlined in the previous section;
|
||||
|
||||
2. Use a regular notebook and use `Node.connect/1`](https://hexdocs.pm/elixir/Node.html#connect/1)
|
||||
to connect to your application. Use [the `:erpc` module](https://www.erlang.org/doc/man/erpc.html)
|
||||
to fetch data from the remote node and execute code.
|
||||
|
||||
## More on branches #1
|
||||
|
||||
We already mentioned branching sections in
|
||||
[Welcome to Livebook](/learn/notebooks/intro-to-livebook),
|
||||
but in Elixir terms each branching section:
|
||||
|
||||
* runs in a separate process from the main flow
|
||||
* copies relevant bindings, imports and aliases from the parent
|
||||
* updates its process dictionary to mirror the parent
|
||||
|
||||
Let's see this in practice:
|
||||
|
||||
```elixir
|
||||
parent = self()
|
||||
```
|
||||
|
||||
```elixir
|
||||
Process.put(:info, "deal carefully with process dictionaries")
|
||||
```
|
||||
|
||||
<!-- livebook:{"branch_parent_index":4} -->
|
||||
|
||||
## More on branches #2
|
||||
|
||||
```elixir
|
||||
parent
|
||||
```
|
||||
|
||||
```elixir
|
||||
self()
|
||||
```
|
||||
|
||||
```elixir
|
||||
Process.get(:info)
|
||||
```
|
||||
|
||||
Since this branch is a separate process, a crash has limited scope:
|
||||
|
||||
```elixir
|
||||
Process.exit(self(), :kill)
|
||||
```
|
||||
|
||||
## Evaluation vs compilation
|
||||
|
||||
Livebook automatically shows the execution time of each Code
|
||||
cell on the bottom-right of the cell. After evaluation, the total
|
||||
time can be seen by hovering the green dot.
|
||||
|
||||
However, it is important to remember that all code outside of
|
||||
a module in Elixir is *evaluated*, and therefore executes much
|
||||
slower than code defined inside modules, which are *compiled*.
|
||||
|
||||
Let's see an example. Run the cell below:
|
||||
|
||||
```elixir
|
||||
Enum.reduce(1..1_000_000, 0, fn x, acc -> x + acc end)
|
||||
```
|
||||
|
||||
We are adding all of the elements in a range by iterating them
|
||||
one by one. However, executing it likely takes some reasonable
|
||||
amount of time, as the invocation of the `Enum.reduce/3` as well
|
||||
as the anonymous function argument are evaluated.
|
||||
|
||||
However, what if we move the above to inside a function? Let's do
|
||||
that:
|
||||
|
||||
```elixir
|
||||
defmodule Bench do
|
||||
def sum do
|
||||
Enum.reduce(1..1_000_000, 0, fn x, acc -> x + acc end)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Now let's try running it:
|
||||
|
||||
```elixir
|
||||
Bench.sum()
|
||||
```
|
||||
|
||||
The latest cell should execute orders of magnitude faster than
|
||||
the previous `Enum.reduce/3` call. While the call `Bench.sum()`
|
||||
itself is evaluated, the one million iterations of `Enum.reduce/3`
|
||||
happen inside a module, which is compiled.
|
||||
|
||||
If a notebook is performing slower than expected, consider moving
|
||||
the bulk of the execution to inside modules.
|
||||
|
||||
## Running tests
|
||||
|
||||
It is also possible to run tests directly from your notebooks.
|
||||
The key is to disable `ExUnit`'s autorun feature and then explicitly
|
||||
run the test suite after all test cases have been defined:
|
||||
|
||||
```elixir
|
||||
ExUnit.start(autorun: false)
|
||||
|
||||
defmodule MyTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test "it works" do
|
||||
assert true
|
||||
end
|
||||
end
|
||||
|
||||
ExUnit.run()
|
||||
```
|
||||
|
||||
This helps you follow best practices and ensure the code you write
|
||||
behaves as expected!
|
|
@ -24,9 +24,12 @@ Subsequent cells have access to the bindings you've defined:
|
|||
String.replace(message, "🍵", "☕")
|
||||
```
|
||||
|
||||
Note however that bindings are not global, so each cell *sees* only stuff that goes
|
||||
above itself. This approach helps to keep the notebook clean and predictable
|
||||
as you keep working on it!
|
||||
Note however that bindings are not global, so each cell *sees* only stuff
|
||||
that goes above itself. This approach helps to keep the notebook clean and
|
||||
predictable as you keep working on it. Furthermore, Livebook tracks which
|
||||
variables are used by each cell and in order to detect which cells become
|
||||
stale. For example, try changing the `message` variable and you will see
|
||||
the status indicator on the bottom right of the second cell become yellow.
|
||||
|
||||
## Sections
|
||||
|
||||
|
@ -72,7 +75,9 @@ Process.sleep(300_000)
|
|||
```
|
||||
|
||||
Having this cell running, feel free to insert another Code cell
|
||||
in the section below and see it evaluates immediately.
|
||||
in the section below and see it evaluates immediately. Crashes
|
||||
in branched sections also have limited scope and will affect only
|
||||
the branched section.
|
||||
|
||||
## Saving notebooks
|
||||
|
||||
|
@ -81,12 +86,20 @@ interactive hacking, but oftentimes you will want to save your work for later.
|
|||
Such can be done by clicking on the "Disk" icon (<i class="ri-livebook-save"></i>)
|
||||
in the bottom-right corner and selecting the file location.
|
||||
|
||||
Once saved, you can access the location of the current `.livemd` file and
|
||||
its current directory using `__ENV__` and `__DIR__` variables:
|
||||
|
||||
```elixir
|
||||
IO.puts(__ENV__.file)
|
||||
IO.puts(__DIR__)
|
||||
```
|
||||
|
||||
Notebooks are stored in **live markdown** format, which is the Markdown you know,
|
||||
with just a few assumptions on how particular elements are represented. Thanks to this
|
||||
approach you can easily keep notebooks under version control and get readable diffs.
|
||||
You can also easily preview those files and even edit them in a text editor.
|
||||
|
||||
## Stepping up your workflow
|
||||
## Keyboard shortcuts
|
||||
|
||||
Once you start using notebooks more, it's gonna be beneficial
|
||||
to optimise how you move around. Livebook leverages the concept of
|
||||
|
@ -95,6 +108,64 @@ Make sure to check out the shortcuts by clicking the "Keyboard" icon
|
|||
(<i class="ri-livebook-shortcuts"></i>) in the sidebar or
|
||||
by pressing <kbd>?</kbd>.
|
||||
|
||||
<!-- livebook:{"branch_parent_index":1} -->
|
||||
|
||||
## Autocompletion
|
||||
|
||||
Elixir code cells also support autocompletion. Autocompletion
|
||||
happens automatically as you type but you can explicitly trigger
|
||||
it with <kbd>ctrl</kbd> + <kbd>␣</kbd>. You must have started
|
||||
executed a cell at least once for the autocompletion engine to
|
||||
load.
|
||||
|
||||
Let's try autocompleting the code below to `System.version()`.
|
||||
First put the cursor after the `System` below and type `.`:
|
||||
|
||||
```elixir
|
||||
System
|
||||
```
|
||||
|
||||
You should have seen the editor listing many different options,
|
||||
which you can use to find `version`. Executing the code will
|
||||
return the Elixir version.
|
||||
|
||||
Note you can also press <kbd>tab</kbd> to cycle across the completion
|
||||
alternatives.
|
||||
|
||||
<!-- livebook:{"branch_parent_index":1} -->
|
||||
|
||||
## Storing secrets in Hubs
|
||||
|
||||
Livebook is capable of managing all secrets that belong to a notebook.
|
||||
Secrets can be read by Elixir Code cells and also by Smart cells -
|
||||
a feature we will explore in future sections.
|
||||
|
||||
Secrets are environment variables starting with the `LB_` prefix. For
|
||||
example, let's try reading an environment variable:
|
||||
|
||||
```elixir
|
||||
System.fetch_env!("LB_MY_SECRETZ")
|
||||
```
|
||||
|
||||
Execute the cell above. If the secret is not available, Livebook will
|
||||
automatically detect it and prompt you to add the missing secret. You
|
||||
can save a secret in two different locations:
|
||||
|
||||
* in your notebook session: once you restart the session or your
|
||||
Livebook, the secret will be gone and you must input it again
|
||||
|
||||
* in a Hub: a Hub is a location where you can safely store secrets,
|
||||
deploy notebooks, and more. Every Livebook application has a personal
|
||||
Hub that can store secrets and stamp notebooks
|
||||
|
||||
Once the secret is saved, execute the cell again and it will evaluate
|
||||
successfully.
|
||||
|
||||
If you save secrets in your Hub, you will only need to input the secret
|
||||
once in notebooks authored by you. You can manage all of your secrets
|
||||
by clicking the lock icon (<i class="ri-lock-password-line"></i>) on the
|
||||
sidebar.
|
||||
|
||||
## Markdown extensions
|
||||
|
||||
Livebook also include supports for links, mathematical expressions, and Mermaid diagrams.
|
||||
|
@ -107,6 +178,8 @@ to a Livebook named `chapter_2.livemd` in the same directory as the current
|
|||
notebook. Once clicked, Livebook will automatically open up a new session
|
||||
to execute the linked notebook.
|
||||
|
||||
<!-- livebook:{"break_markdown":true} -->
|
||||
|
||||
### Math expressions
|
||||
|
||||
Livebook uses $\TeX$ syntax for math inside your Markdown cells.
|
||||
|
@ -124,6 +197,8 @@ how they are written.
|
|||
|
||||
You can explore all supported expressions in the [KaTeX documentation](https://katex.org/docs/supported.html).
|
||||
|
||||
<!-- livebook:{"break_markdown":true} -->
|
||||
|
||||
### Mermaid diagrams
|
||||
|
||||
[Mermaid](https://mermaid-js.github.io/) is a library for creating diagrams
|
||||
|
@ -138,6 +213,142 @@ graph TD;
|
|||
C-->D;
|
||||
```
|
||||
|
||||
## Elixir integration
|
||||
|
||||
Here are some tips on how to better integrate Livebook with Elixir.
|
||||
|
||||
### Mix projects
|
||||
|
||||
Sometimes you may want to run a notebook within the context of an existing
|
||||
Mix project. This is possible from Elixir v1.14 with the help of `Mix.install/2`.
|
||||
|
||||
As an example, imagine you have created a notebook inside your current project,
|
||||
at `notebooks/example.livemd`. In order to run within the root Mix project, using
|
||||
the same configuration and dependencies versions, you can change your notebook
|
||||
setup cell to invoke `Mix.install/2` with the following arguments:
|
||||
|
||||
<!-- livebook:{"force_markdown":true} -->
|
||||
|
||||
```elixir
|
||||
my_app_root = Path.join(__DIR__, "..")
|
||||
|
||||
Mix.install(
|
||||
[
|
||||
{:my_app, path: my_app_root, env: :dev}
|
||||
],
|
||||
config_path: Path.join(my_app_root, "config/config.exs"),
|
||||
lockfile: Path.join(my_app_root, "mix.lock")
|
||||
)
|
||||
```
|
||||
|
||||
<!-- livebook:{"break_markdown":true} -->
|
||||
|
||||
### Runtimes
|
||||
|
||||
Livebook has a concept of **runtime**, which in practice is an Elixir node responsible
|
||||
for evaluating your code. You can choose the runtime by clicking the "Runtime" icon
|
||||
(<i class="ri-livebook-runtime"></i>) on the sidebar (or by using the <kbd>s</kbd> <kbd>r</kbd>
|
||||
keyboard shortcut).
|
||||
|
||||
By default, a new Elixir node is started (similarly to starting `iex`). You can click
|
||||
reconnect whenever you want to discard the current node and start a new one.
|
||||
|
||||
You can also manually *attach* to an existing distributed node by picking the
|
||||
"Attached Node" runtime. To do so, you will need the Erlang Name of the external node
|
||||
and its Erlang Cookie. For example, you can start a Phoenix application as follows:
|
||||
|
||||
```shell
|
||||
$ iex --sname phoenix-app --cookie secret -S mix phx.server
|
||||
```
|
||||
|
||||
Now open up a new notebook and click the "Runtime" icon on the sidebar.
|
||||
Click to "Configure" the runtime and choose "Attached node". Input the
|
||||
name and cookie as above and you should be ready to connect to it.
|
||||
|
||||
Note, however, that you can't install new dependencies on a connected runtime.
|
||||
If you want to install dependencies, you have two options:
|
||||
|
||||
1. Use the Mix project approach outlined in the previous section;
|
||||
|
||||
2. Use a regular notebook and use
|
||||
[`Node.connect/1`](https://hexdocs.pm/elixir/Node.html#connect/1)
|
||||
to connect to your application.
|
||||
Use [the `:erpc` module](https://www.erlang.org/doc/man/erpc.html)
|
||||
to fetch data from the remote node and execute code.
|
||||
|
||||
<!-- livebook:{"break_markdown":true} -->
|
||||
|
||||
### Evaluation vs compilation
|
||||
|
||||
Livebook automatically shows the execution time of each Code
|
||||
cell on the bottom-right of the cell. After evaluation, the total
|
||||
time can be seen by hovering the green dot.
|
||||
|
||||
However, it is important to remember that all code outside of
|
||||
a module in Elixir is *evaluated*, and therefore executes much
|
||||
slower than code defined inside modules, which are *compiled*.
|
||||
|
||||
Let's see an example. Run the cell below:
|
||||
|
||||
```elixir
|
||||
Enum.reduce(1..1_000_000, 0, fn x, acc -> x + acc end)
|
||||
```
|
||||
|
||||
We are adding all of the elements in a range by iterating them
|
||||
one by one. However, executing it likely takes some reasonable
|
||||
amount of time, as the invocation of the `Enum.reduce/3` as well
|
||||
as the anonymous function argument are evaluated.
|
||||
|
||||
However, what if we move the above to inside a function? Let's do
|
||||
that:
|
||||
|
||||
```elixir
|
||||
defmodule Bench do
|
||||
def sum do
|
||||
Enum.reduce(1..1_000_000, 0, fn x, acc -> x + acc end)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Now let's try running it:
|
||||
|
||||
```elixir
|
||||
Bench.sum()
|
||||
```
|
||||
|
||||
The latest cell should execute a least an order of magnitude faster
|
||||
than the previous `Enum.reduce/3` call. While the call `Bench.sum()`
|
||||
itself is evaluated, the one million iterations of `Enum.reduce/3`
|
||||
happen inside a module, which is compiled.
|
||||
|
||||
If a notebook is performing slower than expected, consider moving
|
||||
the bulk of the execution to inside modules.
|
||||
|
||||
<!-- livebook:{"break_markdown":true} -->
|
||||
|
||||
### Running tests
|
||||
|
||||
It is also possible to run tests directly from your notebooks.
|
||||
The key is to disable `ExUnit`'s autorun feature and then explicitly
|
||||
run the test suite after all test cases have been defined:
|
||||
|
||||
```elixir
|
||||
ExUnit.start(autorun: false)
|
||||
|
||||
defmodule MyTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test "it works" do
|
||||
assert true
|
||||
end
|
||||
end
|
||||
|
||||
ExUnit.run()
|
||||
```
|
||||
|
||||
This helps you follow best practices and ensure the code you write
|
||||
behaves as expected!
|
||||
|
||||
## Next steps
|
||||
|
||||
That's our quick intro to Livebook! Where to go next?
|
||||
|
@ -147,8 +358,8 @@ That's our quick intro to Livebook! Where to go next?
|
|||
with Elixir](/learn/notebooks/distributed-portals-with-elixir)
|
||||
notebook;
|
||||
|
||||
* Learn how Elixir integrates with Livebook in the
|
||||
[Elixir and Livebook](/learn/notebooks/elixir-and-livebook) notebook;
|
||||
* Go back [to the Learn page](/learn) and see how to use Livebook to
|
||||
deploy apps, explore data, plot graphs, and much more;
|
||||
|
||||
* Finally, remember Livebook is an open source project, so feel free to
|
||||
look into [the repository](https://github.com/livebook-dev/livebook)
|
||||
|
|
1
static/images/learn-deploy.svg
Normal file
1
static/images/learn-deploy.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 8.5 KiB |
|
@ -25,7 +25,11 @@ defmodule Livebook.StorageTest do
|
|||
}} = Storage.fetch(:insert, "replace")
|
||||
|
||||
assert :ok =
|
||||
Storage.insert(:insert, "replace", key1: "updated_val1", key2: "val2", key3: "val3")
|
||||
Storage.insert(:insert, "replace",
|
||||
key1: "updated_val1",
|
||||
key2: "val2",
|
||||
key3: "val3"
|
||||
)
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
|
|
Loading…
Reference in a new issue