Pass file or directory as open command (#969)

* caputre directory from params

* set file if file parameter is a file

* set file param

* docs

* partial review changes

* apply review changes

* add tests and rename file to path

* formatting

* applied feedback + fixed test

* Apply suggestions from code review

Co-authored-by: Jonatan Kłosko <jonatanklosko@gmail.com>
This commit is contained in:
gpopides 2022-02-03 20:26:18 +01:00 committed by GitHub
parent 24cdca9c18
commit be1fce326c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 112 additions and 33 deletions

View file

@ -388,11 +388,11 @@ defmodule Livebook.Utils do
end
# TODO: On Elixir v1.14, use URI.append_query/2
defp append_query(%URI{query: query} = uri, query_to_add) when query in [nil, ""] do
def append_query(%URI{query: query} = uri, query_to_add) when query in [nil, ""] do
%{uri | query: query_to_add}
end
defp append_query(%URI{} = uri, query) do
def append_query(%URI{} = uri, query) do
%{uri | query: uri.query <> "&" <> query}
end

View file

@ -26,6 +26,12 @@ defmodule LivebookCLI.Server do
* If the open-command is a URL, the notebook at the given URL
will be imported
* If the open-command is a directory, the browser window will point
to the home page with the directory selected
* If the open-command is a notebook file, the browser window will point
to the opened notebook
The open-command runs after the server is started. If a server is
already running, the browser window will point to the server
currently running.
@ -48,7 +54,6 @@ defmodule LivebookCLI.Server do
--name Set a name for the app distributed node
--no-token Disable token authentication, enabled by default
If LIVEBOOK_PASSWORD is set, it takes precedence over token auth
--open Open browser window pointing to the application
-p, --port The port to start the web application on, defaults to 8080
--sname Set a short name for the app distributed node
@ -87,7 +92,7 @@ defmodule LivebookCLI.Server do
case check_endpoint_availability(base_url) do
:livebook_running ->
IO.puts("Livebook already running on #{base_url}")
open_from_options(base_url, opts, extra_args)
open_from_args(base_url, extra_args)
:taken ->
print_error(
@ -100,7 +105,7 @@ defmodule LivebookCLI.Server do
# so it's gonna start listening
case Application.ensure_all_started(:livebook) do
{:ok, _} ->
open_from_options(LivebookWeb.Endpoint.access_url(), opts, extra_args)
open_from_args(LivebookWeb.Endpoint.access_url(), extra_args)
Process.sleep(:infinity)
{:error, error} ->
@ -141,25 +146,42 @@ defmodule LivebookCLI.Server do
end
end
defp open_from_options(base_url, opts, []) do
if opts[:open] do
Livebook.Utils.browser_open(base_url)
end
defp open_from_args(base_url, []) do
Livebook.Utils.browser_open(base_url)
end
defp open_from_options(base_url, _opts, ["new"]) do
defp open_from_args(base_url, ["new"]) do
base_url
|> append_path("/explore/notebooks/new")
|> Livebook.Utils.browser_open()
end
defp open_from_options(base_url, _opts, [url]) do
base_url
|> Livebook.Utils.notebook_import_url(url)
|> Livebook.Utils.browser_open()
defp open_from_args(base_url, [url_or_file_or_dir]) do
url = URI.parse(url_or_file_or_dir)
cond do
url.scheme in ~w(http https file) ->
base_url
|> Livebook.Utils.notebook_import_url(url_or_file_or_dir)
|> Livebook.Utils.browser_open()
File.regular?(url_or_file_or_dir) ->
base_url
|> append_path("open")
|> update_query(%{"path" => url_or_file_or_dir})
|> Livebook.Utils.browser_open()
File.dir?(url_or_file_or_dir) ->
base_url
|> update_query(%{"path" => url_or_file_or_dir})
|> Livebook.Utils.browser_open()
true ->
Livebook.Utils.browser_open(base_url)
end
end
defp open_from_options(_base_url, _opts, _extra_args) do
defp open_from_args(_base_url, _extra_args) do
print_error(
"Too many arguments entered. Ensure only one argument is used to specify the file path and all other arguments are preceded by the relevant switch"
)
@ -171,7 +193,6 @@ defmodule LivebookCLI.Server do
default_runtime: :string,
ip: :string,
name: :string,
open: :boolean,
port: :integer,
home: :string,
sname: :string,
@ -252,6 +273,13 @@ defmodule LivebookCLI.Server do
|> URI.to_string()
end
defp update_query(url, params) do
url
|> URI.parse()
|> Livebook.Utils.append_query(URI.encode_query(params))
|> URI.to_string()
end
defp print_error(message) do
IO.ANSI.format([:red, message]) |> IO.puts()
end

View file

@ -8,7 +8,7 @@ defmodule LivebookWeb.HomeLive do
alias Livebook.{Sessions, Session, LiveMarkdown, Notebook, FileSystem}
@impl true
def mount(_params, _session, socket) do
def mount(params, _session, socket) do
if connected?(socket) do
Phoenix.PubSub.subscribe(Livebook.PubSub, "tracker_sessions")
end
@ -20,7 +20,7 @@ defmodule LivebookWeb.HomeLive do
socket
|> SidebarHelpers.shared_home_handlers()
|> assign(
file: Livebook.Config.local_filesystem_home(),
file: determine_file(params),
file_info: %{exists: true, access: :read_write},
sessions: sessions,
notebook_infos: notebook_infos,
@ -192,6 +192,18 @@ defmodule LivebookWeb.HomeLive do
end
end
def handle_params(%{"path" => path} = _params, _uri, socket)
when socket.assigns.live_action == :public_open do
file = FileSystem.File.local(path)
if file_running?(file, socket.assigns.sessions) do
session_id = session_id_by_file(file, socket.assigns.sessions)
{:noreply, push_redirect(socket, to: Routes.session_path(socket, :page, session_id))}
else
{:noreply, open_notebook(socket, FileSystem.File.local(path))}
end
end
def handle_params(_params, _url, socket), do: {:noreply, socket}
@impl true
@ -225,19 +237,7 @@ defmodule LivebookWeb.HomeLive do
def handle_event("open", %{}, socket) do
file = socket.assigns.file
socket =
case import_notebook(file) do
{:ok, {notebook, messages}} ->
socket
|> put_import_warnings(messages)
|> create_session(notebook: notebook, file: file, origin: {:file, file})
{:error, error} ->
put_flash(socket, :error, Livebook.Utils.upcase_first(error))
end
{:noreply, socket}
{:noreply, open_notebook(socket, file)}
end
def handle_event("bulk_action", %{"action" => "disconnect"} = params, socket) do
@ -320,8 +320,6 @@ defmodule LivebookWeb.HomeLive do
{:noreply, socket}
end
def handle_info(_message, socket), do: {:noreply, socket}
defp files(sessions) do
Enum.map(sessions, & &1.file)
end
@ -384,4 +382,33 @@ defmodule LivebookWeb.HomeLive do
defp selected_sessions(sessions, selected_session_ids) do
Enum.filter(sessions, &(&1.id in selected_session_ids))
end
defp determine_file(%{"path" => path} = _params) do
cond do
File.dir?(path) ->
path
|> FileSystem.Utils.ensure_dir_path()
|> FileSystem.File.local()
File.regular?(path) ->
FileSystem.File.local(path)
true ->
Livebook.Config.local_filesystem_home()
end
end
defp determine_file(_params), do: Livebook.Config.local_filesystem_home()
defp open_notebook(socket, file) do
case import_notebook(file) do
{:ok, {notebook, messages}} ->
socket
|> put_import_warnings(messages)
|> create_session(notebook: notebook, file: file, origin: {:file, file})
{:error, error} ->
put_flash(socket, :error, Livebook.Utils.upcase_first(error))
end
end
end

View file

@ -78,6 +78,7 @@ defmodule LivebookWeb.Router do
pipe_through [:browser, :auth]
live "/import", HomeLive, :public_import
live "/open", HomeLive, :public_open
end
end

View file

@ -325,6 +325,29 @@ defmodule LivebookWeb.HomeLiveTest do
end
end
describe "public open endpoint" do
test "checkouts the directory when a directory is passed", %{conn: conn} do
directory_path = Path.join(File.cwd!(), "test")
{:ok, view, _} = live(conn, "/?path=#{directory_path}")
assert render(view) =~ directory_path
end
@tag :tmp_dir
test "opens a file when livebook file is passed", %{conn: conn, tmp_dir: tmp_dir} do
notebook_path = Path.join(tmp_dir, "notebook.livemd")
:ok = File.write(notebook_path, "# Notebook OPEN section")
assert {:error, {:live_redirect, %{flash: %{}, to: to}}} =
live(conn, "/open?path=#{notebook_path}")
{:ok, view, _} = live(conn, to)
assert render(view) =~ "Notebook OPEN section"
end
end
# Helpers
defp test_notebook_path(name) do