# What's new in Livebook v0.7
```elixir
Mix.install([
{:kino, "~> 0.7.0"},
{:kino_db, "~> 0.2.0"},
{:explorer, "~> 0.3.1"},
{:req, "~> 0.3.1"},
{:postgrex, "~> 0.16.5"},
{:req_athena, "~> 0.1.1"}
])
```
## Intro
This is an accompanying notebook to the Livebook v0.7 announcement blog post.
<!-- livebook:{"branch_parent_index":0} -->
## Secrets management
#### Secrets inside Code cells
<!-- livebook:{"break_markdown":true} -->
We know we should not hardcode passwords, API tokens, and sensitive data in our code or notebook like this:
```elixir
api_username = "postman"
api_password = "password"
Req.get!("https://postman-echo.com/basic-auth", auth: {api_username, api_password})
```
To deal with sensitive data, Livebook has a feature called Secrets.
You can add a secret using the Secrets menu on the sidebar.
Once you have created a secret, you can use it using the `System.fetch_env!/1`, adding a **"LB_"** namespace to the name of the secret you created.
For example, let's say you create a secret with the name `API_USERNAME`. You can call that secret from your code like this:
<!-- livebook:{"force_markdown":true} -->
```elixir
api_username = System.fetch_env!("LB_API_USERNAME")
```
<!-- livebook:{"break_markdown":true} -->
Livebook will automatically detect when your code is trying to use a secret that was not setted up before.
For example, if you evaluate the cell below and the `API_USERNAME` or `API_PASSWORD` secret were not setted up, Livebook you ask you to create that secret.
You can create those secrets with the following value and evaluate the cell below again:
* `API_USERNAME`: postman
* `API_PASSWORD`: password
```elixir
api_username = System.fetch_env!("LB_API_USERNAME")
api_password = System.fetch_env!("LB_API_PASSWORD")
Req.get!("https://postman-echo.com/basic-auth", auth: {api_username, api_password})
```
#### Secrets inside Database Connection Smart cell
<!-- livebook:{"break_markdown":true} -->
The new Secrets feature is integrated with Database Connection Smart cells.
So, when you're creating a connection to PostgreSQL or Amazon Athena, Livebook will give you the option to use a Secret for the database password.
<!-- livebook:{"break_markdown":true} -->
To see how that works, evaluate the following cell and click in the **Password** field in the form below:
<!-- livebook:{"attrs":{"database":"","hostname":"localhost","password":"","port":5432,"type":"postgres","use_ipv6":false,"username":"postgres","variable":"conn"},"kind":"Elixir.KinoDB.ConnectionCell","livebook_object":"smart_cell"} -->
```elixir
opts = [
hostname: "localhost",
port: 5432,
username: "postgres",
password: "",
database: ""
]
{:ok, conn} = Kino.start_child({Postgrex, opts})
```
Or, evaluate the following cell and click in the **Secret Access Key** field in the form below:
<!-- livebook:{"attrs":{"access_key_id":"some_access_key","database":"some_database","output_location":"","region":"us-east-1","secret_access_key_secret":"","token":"","type":"athena","variable":"conn2","workgroup":""},"kind":"Elixir.KinoDB.ConnectionCell","livebook_object":"smart_cell"} -->
```elixir
```
<!-- livebook:{"branch_parent_index":0} -->
## Visual representations of the running system
### An example of visualizing two processes exchanging a few messages
<!-- livebook:{"break_markdown":true} -->
Let's say you want to visualize how that piece of code is exchanging messages when it runs:
<!-- livebook:{"force_markdown":true} -->
```elixir
parent = self()
child =
spawn(fn ->
receive do
:ping -> send(parent, :pong)
end
end)
send(child, :ping)
receive do
:pong -> :ponged!
end
```
All you need to do is wrap your code with `Kino.Process.render_seq_trace/2`:
```elixir
Kino.Process.render_seq_trace(fn ->
parent = self()
child =
spawn(fn ->
receive do
:ping -> send(parent, :pong)
end
end)
send(child, :ping)
receive do
:pong -> :ponged!
end
end)
```
### An example of visualizing more than two processes exchanging messages
```elixir
Kino.Process.render_seq_trace(fn ->
1..4
|> Task.async_stream(fn i ->
i
end)
|> Stream.run()
end)
```
### Visualize supervision trees
<!-- livebook:{"break_markdown":true} -->
To visualize a supervision tree, call `Kino.Process.render_sup_tree/2` with the supervisor’s PID:
```elixir
{:ok, supervisor_pid} =
Supervisor.start_link(
[
{Task, fn -> Process.sleep(:infinity) end},
{Agent, fn -> [] end}
],
strategy: :one_for_one
)
Kino.Process.render_sup_tree(supervisor_pid)
```
### Automatic detection of a supervisor through its PID
<!-- livebook:{"break_markdown":true} -->
Livebook will also automatically show you a supervision tree if the last line in your code cell is the PID of a supervisor:
```elixir
{:ok, supervisor_pid} =
Supervisor.start_link(
[
{Task, fn -> Process.sleep(:infinity) end},
{Agent, fn -> [] end}
],
strategy: :one_for_one
)
```
```elixir
supervisor_pid
```
### Automatic detection of an application tree through its atom
<!-- livebook:{"break_markdown":true} -->
Given the name of an application as an atom, Livebook will automatically render the application tree:
```elixir
:kino
```
<!-- livebook:{"branch_parent_index":0} -->
## Interactive user interface to visualize and edit Elixir pipelines
**An example of using Livebook's dgb interactive user interface with a simple pipeline**
```elixir
"Elixir is cool!"
|> String.trim_trailing("!")
|> String.split()
|> List.first()
|> dbg()
```
**An example of using Livebook's dgb interactive user interface with a pipeline that is doing some data analysis**
```elixir
alias Explorer.DataFrame
alias Explorer.Series
Explorer.Datasets.iris()
|> DataFrame.filter_with(&Series.equal(&1["species"], "Iris-virginica"))
|> DataFrame.select(["sepal_length", "sepal_width", "petal_length", "petal_width"])
|> DataFrame.arrange(desc: "sepal_width")
|> DataFrame.rename(["sepal length", "sepal width", "petal lenght", "petal width"])
|> DataFrame.to_rows()
|> dbg()
```
<!-- livebook:{"branch_parent_index":0} -->
## Organize your cell output with layouts
#### Tabs layout
```elixir
data = [
%{id: 1, name: "Elixir", website: "https://elixir-lang.org"},
%{id: 2, name: "Erlang", website: "https://www.erlang.org"}
]
Kino.Layout.tabs(
Table: Kino.DataTable.new(data),
Raw: data
)
```
#### Grid layout
```elixir
urls = [
"https://images.unsplash.com/photo-1603203040743-24aced6793b4?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=580&h=580&q=80",
"https://images.unsplash.com/photo-1578339850459-76b0ac239aa2?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=580&h=580&q=80",
"https://images.unsplash.com/photo-1633479397973-4e69efa75df2?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=580&h=580&q=80",
"https://images.unsplash.com/photo-1597838816882-4435b1977fbe?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=580&h=580&q=80",
"https://images.unsplash.com/photo-1629778712393-4f316eee143e?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=580&h=580&q=80",
"https://images.unsplash.com/photo-1638667168629-58c2516fbd22?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=580&h=580&q=80"
]
images =
for {url, i} <- Enum.with_index(urls, 1) do
image = Kino.Markdown.new("")
label = Kino.Markdown.new("**Image #{i}**")
Kino.Layout.grid([image, label], boxed: true)
end
Kino.Layout.grid(images, columns: 3)
```