Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 89 additions & 2 deletions lib/livebook/hubs/team_client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ defmodule Livebook.Hubs.TeamClient do
deployment_groups: [],
app_deployments: [],
agents: [],
app_folders: [],
app_deployment_statuses: nil
]

Expand Down Expand Up @@ -172,6 +173,14 @@ defmodule Livebook.Hubs.TeamClient do
GenServer.call(registry_name(id), {:user_can_deploy?, user_id, deployment_group_id})
end

@doc """
Returns a list of cached app folders.
"""
@spec get_app_folders(String.t()) :: list(Teams.AppFolder.t())
def get_app_folders(id) do
GenServer.call(registry_name(id), :get_app_folders)
end

@doc """
Returns if the Team client is connected.
"""
Expand Down Expand Up @@ -370,6 +379,10 @@ defmodule Livebook.Hubs.TeamClient do
end
end

def handle_call(:get_app_folders, _caller, state) do
{:reply, state.app_folders, state}
end

@impl true
def handle_info(:connected, state) do
Hubs.Broadcasts.hub_connected(state.hub.id)
Expand Down Expand Up @@ -611,7 +624,8 @@ defmodule Livebook.Hubs.TeamClient do
file: nil,
deployed_by: app_deployment.deployed_by,
deployed_at: DateTime.from_gregorian_seconds(app_deployment.deployed_at),
authorization_groups: authorization_groups
authorization_groups: authorization_groups,
app_folder_id: nullify(app_deployment.app_folder_id)
}
end

Expand All @@ -630,7 +644,8 @@ defmodule Livebook.Hubs.TeamClient do
for authorization_group <- authorization_groups do
%Teams.AuthorizationGroup{
provider_id: authorization_group.provider_id,
group_name: authorization_group.group_name
group_name: authorization_group.group_name,
app_folder_id: nullify(authorization_group.app_folder_id)
}
end
end
Expand Down Expand Up @@ -664,6 +679,24 @@ defmodule Livebook.Hubs.TeamClient do
}
end

defp put_app_folder(state, app_folder) do
state = remove_app_folder(state, app_folder)

%{state | app_folders: [app_folder | state.app_folders]}
end

defp remove_app_folder(state, app_folder) do
%{state | app_folders: Enum.reject(state.app_folders, &(&1.id == app_folder.id))}
end

defp build_app_folder(state, %LivebookProto.AppFolder{} = app_folder) do
%Teams.AppFolder{
id: app_folder.id,
name: app_folder.name,
hub_id: state.hub.id
}
end

defp handle_event(:secret_created, %Secrets.Secret{} = secret, state) do
Hubs.Broadcasts.secret_created(secret)

Expand Down Expand Up @@ -787,6 +820,7 @@ defmodule Livebook.Hubs.TeamClient do
|> dispatch_deployment_groups(user_connected)
|> dispatch_app_deployments(user_connected)
|> dispatch_agents(user_connected)
|> dispatch_app_folders(user_connected)
|> dispatch_connection()
end

Expand All @@ -798,6 +832,7 @@ defmodule Livebook.Hubs.TeamClient do
|> dispatch_deployment_groups(agent_connected)
|> dispatch_app_deployments(agent_connected)
|> dispatch_agents(agent_connected)
|> dispatch_app_folders(agent_connected)
|> dispatch_connection()
end

Expand Down Expand Up @@ -873,6 +908,43 @@ defmodule Livebook.Hubs.TeamClient do
update_hub(state, org_updated)
end

defp handle_event(:app_folder_created, %Teams.AppFolder{} = app_folder, state) do
Teams.Broadcasts.app_folder_created(app_folder)
put_app_folder(state, app_folder)
end

defp handle_event(:app_folder_created, app_folder_created, state) do
handle_event(
:app_folder_created,
build_app_folder(state, app_folder_created.app_folder),
state
)
end

defp handle_event(:app_folder_updated, %Teams.AppFolder{} = app_folder, state) do
Teams.Broadcasts.app_folder_updated(app_folder)
put_app_folder(state, app_folder)
end

defp handle_event(:app_folder_updated, app_folder_updated, state) do
handle_event(
:app_folder_updated,
build_app_folder(state, app_folder_updated.app_folder),
state
)
end

defp handle_event(:app_folder_deleted, %Teams.AppFolder{} = app_folder, state) do
Teams.Broadcasts.app_folder_deleted(app_folder)
remove_app_folder(state, app_folder)
end

defp handle_event(:app_folder_deleted, %{id: id}, state) do
with {:ok, app_folder} <- fetch_app_folder(id, state) do
handle_event(:app_folder_deleted, app_folder, state)
end
end

defp dispatch_secrets(state, %{secrets: secrets}) do
decrypted_secrets = Enum.map(secrets, &build_secret(state, &1))

Expand Down Expand Up @@ -936,6 +1008,19 @@ defmodule Livebook.Hubs.TeamClient do
dispatch_events(state, agent_joined: joined, agent_left: left)
end

defp dispatch_app_folders(state, %{app_folders: app_folders}) do
app_folders = Enum.map(app_folders, &build_app_folder(state, &1))

{created, deleted, updated} =
diff(state.app_folders, app_folders, &(&1.id == &2.id))

dispatch_events(state,
app_folder_deleted: deleted,
app_folder_created: created,
app_folder_updated: updated
)
end

defp dispatch_connection(%{hub: %{id: id}} = state) do
Teams.Broadcasts.client_connected(id)
state
Expand Down Expand Up @@ -1064,6 +1149,8 @@ defmodule Livebook.Hubs.TeamClient do
defp fetch_app_deployment_from_slug(slug, state),
do: fetch_entry(state.app_deployments, &(&1.slug == slug), state)

defp fetch_app_folder(id, state), do: fetch_entry(state.app_folders, &(&1.id == id), state)

defp fetch_entry(entries, fun, state) do
if entry = Enum.find(entries, fun) do
{:ok, entry}
Expand Down
3 changes: 2 additions & 1 deletion lib/livebook/live_markdown/export.ex
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ defmodule Livebook.LiveMarkdown.Export do
:auto_shutdown_ms,
:access_type,
:show_source,
:output_type
:output_type,
:app_folder_id
]

put_unless_default(
Expand Down
3 changes: 3 additions & 0 deletions lib/livebook/live_markdown/import.ex
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,9 @@ defmodule Livebook.LiveMarkdown.Import do
{"show_source", show_source}, attrs ->
Map.put(attrs, :show_source, show_source)

{"app_folder_id", app_folder_id}, attrs ->
Map.put(attrs, :app_folder_id, app_folder_id)

{"output_type", output_type}, attrs when output_type in ["all", "rich"] ->
Map.put(attrs, :output_type, String.to_atom(output_type))

Expand Down
10 changes: 7 additions & 3 deletions lib/livebook/notebook/app_settings.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ defmodule Livebook.Notebook.AppSettings do
access_type: access_type(),
password: String.t() | nil,
show_source: boolean(),
output_type: output_type()
output_type: output_type(),
app_folder_id: String.t() | nil
}

@type access_type :: :public | :protected
Expand All @@ -33,6 +34,7 @@ defmodule Livebook.Notebook.AppSettings do
field :password, :string
field :show_source, :boolean
field :output_type, Ecto.Enum, values: [:all, :rich]
field :app_folder_id, :string
end

@doc """
Expand All @@ -49,7 +51,8 @@ defmodule Livebook.Notebook.AppSettings do
access_type: :protected,
password: generate_password(),
show_source: false,
output_type: :all
output_type: :all,
app_folder_id: nil
}
end

Expand Down Expand Up @@ -82,7 +85,8 @@ defmodule Livebook.Notebook.AppSettings do
:auto_shutdown_ms,
:access_type,
:show_source,
:output_type
:output_type,
:app_folder_id
])
|> validate_required([
:slug,
Expand Down
8 changes: 8 additions & 0 deletions lib/livebook/session.ex
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,7 @@ defmodule Livebook.Session do
def init({caller_pid, opts}) do
Livebook.Settings.subscribe()
Livebook.Hubs.Broadcasts.subscribe([:crud, :secrets, :file_systems])
Livebook.Teams.Broadcasts.subscribe(:app_folders)

id = Keyword.fetch!(opts, :id)

Expand Down Expand Up @@ -2028,6 +2029,13 @@ defmodule Livebook.Session do
{:noreply, handle_operation(state, operation)}
end

def handle_info({event, app_folder}, state)
when event in [:app_folder_created, :app_folder_updated, :app_folder_deleted] and
app_folder.hub_id == state.data.notebook.hub_id do
operation = {:sync_hub_app_folders, @client_id}
{:noreply, handle_operation(state, operation)}
end

def handle_info({:hub_deleted, id}, %{data: %{notebook: %{hub_id: id}}} = state) do
# Since the hub got deleted, we close all sessions using that hub.
# This way we clean up all secrets and other in-memory state that
Expand Down
46 changes: 40 additions & 6 deletions lib/livebook/session/data.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ defmodule Livebook.Session.Data do
:secrets,
:hub_secrets,
:hub_file_systems,
:hub_app_folders,
:mode,
:deployed_app_slug,
:app_data
Expand Down Expand Up @@ -247,6 +248,7 @@ defmodule Livebook.Session.Data do
| {:set_notebook_hub, client_id(), String.t()}
| {:sync_hub_secrets, client_id()}
| {:sync_hub_file_systems, client_id()}
| {:sync_hub_app_folders, client_id()}
| {:add_file_entries, client_id(), list(Notebook.file_entry())}
| {:rename_file_entry, client_id(), name :: String.t(), new_name :: String.t()}
| {:delete_file_entry, client_id(), String.t()}
Expand Down Expand Up @@ -306,6 +308,13 @@ defmodule Livebook.Session.Data do
hub_secrets = Livebook.Hubs.get_secrets(hub)
hub_file_systems = Livebook.Hubs.get_file_systems(hub)

hub_app_folders =
if is_struct(hub, Livebook.Hubs.Team) do
Livebook.Teams.get_app_folders(hub)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this a contract of Livebook.Hubs, so we avoid is_struct(hub, Livebook.Hubs.Team) checks. And we make it so all other hubs return an empty list.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we can avoid the checks in the other places in this module.

else
[]
end

startup_secrets =
for secret <- Livebook.Secrets.get_startup_secrets(),
do: {secret.name, secret},
Expand Down Expand Up @@ -338,6 +347,7 @@ defmodule Livebook.Session.Data do
secrets: secrets,
hub_secrets: hub_secrets,
hub_file_systems: hub_file_systems,
hub_app_folders: hub_app_folders,
mode: opts[:mode],
deployed_app_slug: nil,
app_data: app_data
Expand Down Expand Up @@ -1074,6 +1084,14 @@ defmodule Livebook.Session.Data do
|> wrap_ok()
end

def apply_operation(data, {:sync_hub_app_folders, _client_id}) do
data
|> with_actions()
|> sync_hub_app_folders()
|> set_dirty()
|> wrap_ok()
end

def apply_operation(data, {:add_file_entries, _client_id, file_entries}) do
data
|> with_actions()
Expand Down Expand Up @@ -1957,15 +1975,21 @@ defmodule Livebook.Session.Data do
end

defp set_notebook_hub({data, _} = data_actions, hub) do
teams_enabled = is_struct(hub, Livebook.Hubs.Team)

app_folders =
if teams_enabled do
Livebook.Teams.get_app_folders(hub)
else
[]
end

data_actions
|> set!(
notebook: %{
data.notebook
| hub_id: hub.id,
teams_enabled: is_struct(hub, Livebook.Hubs.Team)
},
notebook: %{data.notebook | hub_id: hub.id, teams_enabled: teams_enabled},
hub_secrets: Livebook.Hubs.get_secrets(hub),
hub_file_systems: Livebook.Hubs.get_file_systems(hub)
hub_file_systems: Livebook.Hubs.get_file_systems(hub),
hub_app_folders: app_folders
)
end

Expand All @@ -1985,6 +2009,16 @@ defmodule Livebook.Session.Data do
set!(data_actions, hub_file_systems: file_systems)
end

defp sync_hub_app_folders({data, _} = data_actions) do
if data.notebook.teams_enabled do
hub = Livebook.Hubs.fetch_hub!(data.notebook.hub_id)
app_folders = Livebook.Teams.get_app_folders(hub)
set!(data_actions, hub_app_folders: app_folders)
else
data_actions
end
end

defp update_notebook_hub_secret_names({data, _} = data_actions) do
hub_secret_names =
for {_name, secret} <- data.secrets, secret.hub_id == data.notebook.hub_id, do: secret.name
Expand Down
10 changes: 10 additions & 0 deletions lib/livebook/teams.ex
Original file line number Diff line number Diff line change
Expand Up @@ -305,4 +305,14 @@ defmodule Livebook.Teams do
def user_can_deploy?(%Team{} = team, %Teams.DeploymentGroup{} = deployment_group) do
TeamClient.user_can_deploy?(team.id, team.user_id, deployment_group.id)
end

@doc """
Gets a list of app folders for a given Hub.
"""
@spec get_app_folders(Team.t()) :: list(Teams.AppFolder.t())
def get_app_folders(team) do
team.id
|> TeamClient.get_app_folders()
|> Enum.sort_by(& &1.name)
end
end
3 changes: 3 additions & 0 deletions lib/livebook/teams/app_deployment.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ defmodule Livebook.Teams.AppDeployment do
access_type: Livebook.Notebook.AppSettings.access_type(),
hub_id: String.t() | nil,
deployment_group_id: String.t() | nil,
app_folder_id: String.t() | nil,
file: binary() | nil,
deployed_by: String.t() | nil,
deployed_at: DateTime.t() | nil,
Expand All @@ -32,6 +33,7 @@ defmodule Livebook.Teams.AppDeployment do
field :access_type, Ecto.Enum, values: @access_types
field :hub_id, :string
field :deployment_group_id, :string
field :app_folder_id, :string
field :file, :string
field :deployed_by, :string
field :deployed_at, :utc_datetime
Expand Down Expand Up @@ -75,6 +77,7 @@ defmodule Livebook.Teams.AppDeployment do
title: notebook.name,
multi_session: notebook.app_settings.multi_session,
access_type: notebook.app_settings.access_type,
app_folder_id: notebook.app_settings.app_folder_id,
hub_id: notebook.hub_id,
deployment_group_id: notebook.deployment_group_id,
file: zip_content
Expand Down
Loading