Add support for importing notebook from URL query parameter (#598)

* Add support for importing notebook from URL query

* Update desc

* Update URL and redirect to import form on error

* Update tests

* Remove URL scope
This commit is contained in:
Jonatan Kłosko 2021-10-16 12:23:08 +02:00 committed by GitHub
parent 06822b6e7e
commit 3ae67ede33
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 89 additions and 10 deletions

View file

@ -139,7 +139,8 @@ defmodule LivebookWeb.HomeLive do
id: "import",
modal_class: "w-full max-w-xl",
return_to: Routes.home_path(@socket, :page),
tab: @tab %>
tab: @tab,
import_opts: @import_opts %>
<% end %>
"""
end
@ -220,8 +221,23 @@ defmodule LivebookWeb.HomeLive do
{:noreply, assign(socket, session: session)}
end
def handle_params(%{"tab" => tab}, _url, socket) do
{:noreply, assign(socket, tab: tab)}
def handle_params(%{"tab" => tab} = params, _url, %{assigns: %{live_action: :import}} = socket) do
import_opts = [url: params["url"]]
{:noreply, assign(socket, tab: tab, import_opts: import_opts)}
end
def handle_params(%{"url" => url}, _url, %{assigns: %{live_action: :public_import}} = socket) do
url
|> Livebook.ContentLoader.rewrite_url()
|> Livebook.ContentLoader.fetch_content()
|> case do
{:ok, content} ->
socket = import_content(socket, content, origin: {:url, url})
{:noreply, socket}
{:error, _message} ->
{:noreply, push_patch(socket, to: Routes.home_path(socket, :import, "url", url: url))}
end
end
def handle_params(_params, _url, socket), do: {:noreply, socket}
@ -331,10 +347,8 @@ defmodule LivebookWeb.HomeLive do
end
def handle_info({:import_content, content, session_opts}, socket) do
{notebook, messages} = Livebook.LiveMarkdown.Import.notebook_from_markdown(content)
socket = put_import_flash_messages(socket, messages)
session_opts = Keyword.merge(session_opts, notebook: notebook)
{:noreply, create_session(socket, session_opts)}
socket = import_content(socket, content, session_opts)
{:noreply, socket}
end
def handle_info(
@ -391,4 +405,11 @@ defmodule LivebookWeb.HomeLive do
time_words = created_at |> DateTime.to_naive() |> Livebook.Utils.Time.time_ago_in_words()
time_words <> " ago"
end
defp import_content(socket, content, session_opts) do
{notebook, messages} = Livebook.LiveMarkdown.Import.notebook_from_markdown(content)
socket = put_import_flash_messages(socket, messages)
session_opts = Keyword.merge(session_opts, notebook: notebook)
create_session(socket, session_opts)
end
end

View file

@ -27,7 +27,7 @@ defmodule LivebookWeb.HomeLive.ImportComponent do
</div>
</div>
<div>
<%= live_component component_for_tab(@tab), id: "import-#{@tab}" %>
<%= live_component component_for_tab(@tab), [{:id, "import-#{@tab}"} | @import_opts] %>
</div>
</div>
"""

View file

@ -8,6 +8,15 @@ defmodule LivebookWeb.HomeLive.ImportUrlComponent do
{:ok, assign(socket, url: "", error_message: nil)}
end
@impl true
def update(assigns, socket) do
if url = assigns[:url] do
{:ok, do_import(socket, url)}
else
{:ok, socket}
end
end
@impl true
def render(assigns) do
~H"""
@ -45,16 +54,20 @@ defmodule LivebookWeb.HomeLive.ImportUrlComponent do
end
def handle_event("import", %{"data" => %{"url" => url}}, socket) do
{:noreply, do_import(socket, url)}
end
defp do_import(socket, url) do
url
|> ContentLoader.rewrite_url()
|> ContentLoader.fetch_content()
|> case do
{:ok, content} ->
send(self(), {:import_content, content, [origin: {:url, url}]})
{:noreply, socket}
socket
{:error, message} ->
{:noreply, assign(socket, error_message: Utils.upcase_first(message))}
assign(socket, url: url, error_message: Utils.upcase_first(message))
end
end
end

View file

@ -52,6 +52,13 @@ defmodule LivebookWeb.Router do
home_app: {"Livebook", :livebook}
end
# Public URLs that people may be directed to
scope "/", LivebookWeb do
pipe_through [:browser, :auth]
live "/import", HomeLive, :public_import
end
scope "/", LivebookWeb do
pipe_through :browser

View file

@ -208,6 +208,44 @@ defmodule LivebookWeb.HomeLiveTest do
end
end
describe "public import endpoint" do
test "imports notebook from the given url and redirects to the new session", %{conn: conn} do
bypass = Bypass.open()
Bypass.expect_once(bypass, "GET", "/notebook", fn conn ->
conn
|> Plug.Conn.put_resp_content_type("text/plain")
|> Plug.Conn.resp(200, "# My notebook")
end)
notebook_url = "http://localhost:#{bypass.port}/notebook"
assert {:error, {:live_redirect, %{to: to}}} =
live(conn, "/import?url=#{URI.encode_www_form(notebook_url)}")
{:ok, view, _} = live(conn, to)
assert render(view) =~ "My notebook"
end
test "redirects to the import form on error", %{conn: conn} do
bypass = Bypass.open()
Bypass.expect(bypass, "GET", "/notebook", fn conn ->
Plug.Conn.resp(conn, 500, "Error")
end)
notebook_url = "http://localhost:#{bypass.port}/notebook"
assert {:error, {:live_redirect, %{to: to}}} =
live(conn, "/import?url=#{URI.encode_www_form(notebook_url)}")
assert to == "/home/import/url?url=#{URI.encode_www_form(notebook_url)}"
{:ok, view, _} = live(conn, to)
assert render(view) =~ notebook_url
end
end
# Helpers
defp test_notebook_path(name) do