mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-10-10 13:38:09 +08:00
Implement emojis instead of colors (#1636)
This commit is contained in:
parent
57e1df74f3
commit
ae3a5661a8
30 changed files with 409 additions and 228 deletions
33
assets/js/hooks/emoji_picker.js
Normal file
33
assets/js/hooks/emoji_picker.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { createPicker } from "picmo";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hook for the emoji picker input.
|
||||||
|
*/
|
||||||
|
const EmojiPicker = {
|
||||||
|
mounted() {
|
||||||
|
const rootElement = this.el.querySelector("[data-emoji-container]");
|
||||||
|
const preview = this.el.querySelector("[data-emoji-preview]");
|
||||||
|
const input = this.el.querySelector("[data-emoji-input]");
|
||||||
|
const button = this.el.querySelector("[data-emoji-button]");
|
||||||
|
|
||||||
|
const pickerOptions = {
|
||||||
|
rootElement,
|
||||||
|
showSearch: false,
|
||||||
|
showPreview: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const picker = createPicker(pickerOptions);
|
||||||
|
|
||||||
|
picker.addEventListener("emoji:select", ({ emoji }) => {
|
||||||
|
preview.innerHTML = emoji;
|
||||||
|
input.value = emoji;
|
||||||
|
rootElement.classList.toggle("hidden");
|
||||||
|
});
|
||||||
|
|
||||||
|
button.addEventListener("click", (_) => {
|
||||||
|
rootElement.classList.toggle("hidden");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EmojiPicker;
|
|
@ -4,6 +4,7 @@ import CellEditor from "./cell_editor";
|
||||||
import ConfirmModal from "./confirm_modal";
|
import ConfirmModal from "./confirm_modal";
|
||||||
import Dropzone from "./dropzone";
|
import Dropzone from "./dropzone";
|
||||||
import EditorSettings from "./editor_settings";
|
import EditorSettings from "./editor_settings";
|
||||||
|
import EmojiPicker from "./emoji_picker";
|
||||||
import FocusOnUpdate from "./focus_on_update";
|
import FocusOnUpdate from "./focus_on_update";
|
||||||
import Headline from "./headline";
|
import Headline from "./headline";
|
||||||
import Highlight from "./highlight";
|
import Highlight from "./highlight";
|
||||||
|
@ -26,6 +27,7 @@ export default {
|
||||||
ConfirmModal,
|
ConfirmModal,
|
||||||
Dropzone,
|
Dropzone,
|
||||||
EditorSettings,
|
EditorSettings,
|
||||||
|
EmojiPicker,
|
||||||
FocusOnUpdate,
|
FocusOnUpdate,
|
||||||
Headline,
|
Headline,
|
||||||
Highlight,
|
Highlight,
|
||||||
|
|
34
assets/package-lock.json
generated
34
assets/package-lock.json
generated
|
@ -21,6 +21,7 @@
|
||||||
"phoenix": "file:../deps/phoenix",
|
"phoenix": "file:../deps/phoenix",
|
||||||
"phoenix_html": "file:../deps/phoenix_html",
|
"phoenix_html": "file:../deps/phoenix_html",
|
||||||
"phoenix_live_view": "file:../deps/phoenix_live_view",
|
"phoenix_live_view": "file:../deps/phoenix_live_view",
|
||||||
|
"picmo": "^5.7.2",
|
||||||
"postcss-import": "^15.0.0",
|
"postcss-import": "^15.0.0",
|
||||||
"postcss-loader": "^7.0.1",
|
"postcss-loader": "^7.0.1",
|
||||||
"rehype-katex": "^6.0.0",
|
"rehype-katex": "^6.0.0",
|
||||||
|
@ -4505,6 +4506,15 @@
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||||
},
|
},
|
||||||
|
"node_modules/emojibase": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emojibase/-/emojibase-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-1GkKJPXP6tVkYJHOBSJHoGOr/6uaDxZ9xJ6H7m6PfdGXTmQgbALHLWaVRY4Gi/qf5x/gT/NUXLPuSHYLqtLtrQ==",
|
||||||
|
"funding": {
|
||||||
|
"type": "ko-fi",
|
||||||
|
"url": "https://ko-fi.com/milesjohnson"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/emojis-list": {
|
"node_modules/emojis-list": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
|
||||||
|
@ -8544,6 +8554,17 @@
|
||||||
"resolved": "../deps/phoenix_live_view",
|
"resolved": "../deps/phoenix_live_view",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
|
"node_modules/picmo": {
|
||||||
|
"version": "5.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/picmo/-/picmo-5.7.2.tgz",
|
||||||
|
"integrity": "sha512-A7c5O8x1Xwq11KBYFY93+GIbHnw9PVz35HaWWHn/dgT08GA67M6cXKjjwzLnEAyXSdxXKrEk8/gPyTs+ibzWfQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"emojibase": "^6.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/joeattardi"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||||
|
@ -14080,6 +14101,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||||
},
|
},
|
||||||
|
"emojibase": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emojibase/-/emojibase-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-1GkKJPXP6tVkYJHOBSJHoGOr/6uaDxZ9xJ6H7m6PfdGXTmQgbALHLWaVRY4Gi/qf5x/gT/NUXLPuSHYLqtLtrQ=="
|
||||||
|
},
|
||||||
"emojis-list": {
|
"emojis-list": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
|
||||||
|
@ -16897,6 +16923,14 @@
|
||||||
"phoenix_live_view": {
|
"phoenix_live_view": {
|
||||||
"version": "file:../deps/phoenix_live_view"
|
"version": "file:../deps/phoenix_live_view"
|
||||||
},
|
},
|
||||||
|
"picmo": {
|
||||||
|
"version": "5.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/picmo/-/picmo-5.7.2.tgz",
|
||||||
|
"integrity": "sha512-A7c5O8x1Xwq11KBYFY93+GIbHnw9PVz35HaWWHn/dgT08GA67M6cXKjjwzLnEAyXSdxXKrEk8/gPyTs+ibzWfQ==",
|
||||||
|
"requires": {
|
||||||
|
"emojibase": "^6.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"picocolors": {
|
"picocolors": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
"phoenix": "file:../deps/phoenix",
|
"phoenix": "file:../deps/phoenix",
|
||||||
"phoenix_html": "file:../deps/phoenix_html",
|
"phoenix_html": "file:../deps/phoenix_html",
|
||||||
"phoenix_live_view": "file:../deps/phoenix_live_view",
|
"phoenix_live_view": "file:../deps/phoenix_live_view",
|
||||||
|
"picmo": "^5.7.2",
|
||||||
"postcss-import": "^15.0.0",
|
"postcss-import": "^15.0.0",
|
||||||
"postcss-loader": "^7.0.1",
|
"postcss-loader": "^7.0.1",
|
||||||
"rehype-katex": "^6.0.0",
|
"rehype-katex": "^6.0.0",
|
||||||
|
|
|
@ -197,13 +197,11 @@ defmodule Livebook.Application do
|
||||||
|
|
||||||
if Livebook.Config.feature_flag_enabled?(:localhost_hub) do
|
if Livebook.Config.feature_flag_enabled?(:localhost_hub) do
|
||||||
defp insert_development_hub do
|
defp insert_development_hub do
|
||||||
unless Livebook.Hubs.hub_exists?("local-host") do
|
Livebook.Hubs.save_hub(%Livebook.Hubs.Local{
|
||||||
Livebook.Hubs.save_hub(%Livebook.Hubs.Local{
|
id: "local-host",
|
||||||
id: "local-host",
|
hub_name: "Localhost",
|
||||||
hub_name: "Localhost",
|
hub_emoji: "🏠"
|
||||||
hub_color: Livebook.EctoTypes.HexColor.random()
|
})
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
defp insert_development_hub, do: :ok
|
defp insert_development_hub, do: :ok
|
||||||
|
|
|
@ -2,7 +2,7 @@ defmodule Livebook.Hubs do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
|
|
||||||
alias Livebook.Storage
|
alias Livebook.Storage
|
||||||
alias Livebook.Hubs.{Enterprise, Fly, Local, Metadata, Provider}
|
alias Livebook.Hubs.{Broadcasts, Enterprise, Fly, Local, Metadata, Provider}
|
||||||
|
|
||||||
@namespace :hubs
|
@namespace :hubs
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ defmodule Livebook.Hubs do
|
||||||
@spec get_metadatas() :: list(Metadata.t())
|
@spec get_metadatas() :: list(Metadata.t())
|
||||||
def get_metadatas do
|
def get_metadatas do
|
||||||
for hub <- get_hubs() do
|
for hub <- get_hubs() do
|
||||||
Provider.normalize(hub)
|
%{Provider.normalize(hub) | connected?: Provider.connected?(hub)}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ defmodule Livebook.Hubs do
|
||||||
attributes = struct |> Map.from_struct() |> Map.to_list()
|
attributes = struct |> Map.from_struct() |> Map.to_list()
|
||||||
:ok = Storage.insert(@namespace, struct.id, attributes)
|
:ok = Storage.insert(@namespace, struct.id, attributes)
|
||||||
:ok = connect_hub(struct)
|
:ok = connect_hub(struct)
|
||||||
:ok = broadcast_hubs_change()
|
:ok = Broadcasts.hubs_metadata_changed()
|
||||||
|
|
||||||
struct
|
struct
|
||||||
end
|
end
|
||||||
|
@ -84,7 +84,7 @@ defmodule Livebook.Hubs do
|
||||||
end
|
end
|
||||||
|
|
||||||
:ok = Storage.delete(@namespace, id)
|
:ok = Storage.delete(@namespace, id)
|
||||||
:ok = broadcast_hubs_change()
|
:ok = Broadcasts.hubs_metadata_changed()
|
||||||
end
|
end
|
||||||
|
|
||||||
:ok
|
:ok
|
||||||
|
@ -98,30 +98,50 @@ defmodule Livebook.Hubs do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Subscribes to updates in hubs information.
|
Subscribes to one or more subtopics in `"hubs"`.
|
||||||
|
|
||||||
## Messages
|
## Messages
|
||||||
|
|
||||||
* `{:hubs_metadata_changed, hubs}`
|
Topic `hubs:crud`:
|
||||||
|
|
||||||
|
* `:hubs_metadata_changed`
|
||||||
|
|
||||||
|
Topic `hubs:connection`:
|
||||||
|
|
||||||
|
* `:hub_connected`
|
||||||
|
* `:hub_disconnected`
|
||||||
|
* `{:connection_error, reason}`
|
||||||
|
* `{:disconnection_error, reason}`
|
||||||
|
|
||||||
|
Topic `hubs:secrets`:
|
||||||
|
|
||||||
|
* `{:secret_created, %Secret{}}`
|
||||||
|
* `{:secret_updated, %Secret{}}`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec subscribe() :: :ok | {:error, term()}
|
@spec subscribe(atom() | list(atom())) :: :ok | {:error, term()}
|
||||||
def subscribe do
|
def subscribe(topics) when is_list(topics) do
|
||||||
Phoenix.PubSub.subscribe(Livebook.PubSub, "hubs")
|
for topic <- topics, do: subscribe(topic)
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
def subscribe(topic) do
|
||||||
|
Phoenix.PubSub.subscribe(Livebook.PubSub, "hubs:#{topic}")
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Unsubscribes from `subscribe/0`.
|
Unsubscribes from `subscribe/0`.
|
||||||
"""
|
"""
|
||||||
@spec unsubscribe() :: :ok
|
@spec unsubscribe(atom() | list(atom())) :: :ok
|
||||||
def unsubscribe do
|
def unsubscribe(topics) when is_list(topics) do
|
||||||
Phoenix.PubSub.unsubscribe(Livebook.PubSub, "hubs")
|
for topic <- topics, do: unsubscribe(topic)
|
||||||
|
|
||||||
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
# Notifies interested processes about hubs data change.
|
def unsubscribe(topic) do
|
||||||
# Broadcasts `{:hubs_metadata_changed, hubs}` message under the `"hubs"` topic.
|
Phoenix.PubSub.unsubscribe(Livebook.PubSub, "hubs:#{topic}")
|
||||||
defp broadcast_hubs_change do
|
|
||||||
Phoenix.PubSub.broadcast(Livebook.PubSub, "hubs", {:hubs_metadata_changed, get_metadatas()})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp to_struct(%{id: "fly-" <> _} = fields) do
|
defp to_struct(%{id: "fly-" <> _} = fields) do
|
||||||
|
|
71
lib/livebook/hubs/broadcasts.ex
Normal file
71
lib/livebook/hubs/broadcasts.ex
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
defmodule Livebook.Hubs.Broadcasts do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
alias Livebook.Secrets.Secret
|
||||||
|
|
||||||
|
@type broadcast :: :ok | {:error, term()}
|
||||||
|
|
||||||
|
@crud_topic "hubs:crud"
|
||||||
|
@connection_topic "hubs:connection"
|
||||||
|
@secrets_topic "hubs:secrets"
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Broadcasts when hubs changed under `hubs:crud` topic
|
||||||
|
"""
|
||||||
|
@spec hubs_metadata_changed() :: broadcast()
|
||||||
|
def hubs_metadata_changed do
|
||||||
|
broadcast(@crud_topic, :hubs_metadata_changed)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Broadcasts when hub connected under `hubs:connection` topic
|
||||||
|
"""
|
||||||
|
@spec hub_connected() :: broadcast()
|
||||||
|
def hub_connected do
|
||||||
|
broadcast(@connection_topic, :hub_connected)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Broadcasts when hub disconnected under `hubs:connection` topic
|
||||||
|
"""
|
||||||
|
@spec hub_disconnected() :: broadcast()
|
||||||
|
def hub_disconnected do
|
||||||
|
broadcast(@connection_topic, :hub_disconnected)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Broadcasts when hub had an error when connecting under `hubs:connection` topic
|
||||||
|
"""
|
||||||
|
@spec hub_connection_failed(String.t()) :: broadcast()
|
||||||
|
def hub_connection_failed(reason) when is_binary(reason) do
|
||||||
|
broadcast(@connection_topic, {:connection_error, reason})
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Broadcasts when hub had an error when disconnecting under `hubs:connection` topic
|
||||||
|
"""
|
||||||
|
@spec hub_disconnection_failed(String.t()) :: broadcast()
|
||||||
|
def hub_disconnection_failed(reason) when is_binary(reason) do
|
||||||
|
broadcast(@connection_topic, {:disconnection_error, reason})
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Broadcasts when hub received a new secret under `hubs:secrets` topic
|
||||||
|
"""
|
||||||
|
@spec secret_created(Secret.t()) :: broadcast()
|
||||||
|
def secret_created(%Secret{} = secret) do
|
||||||
|
broadcast(@secrets_topic, {:secret_created, secret})
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Broadcasts when hub received an updated secret under `hubs:secrets` topic
|
||||||
|
"""
|
||||||
|
@spec secret_updated(Secret.t()) :: broadcast()
|
||||||
|
def secret_updated(%Secret{} = secret) do
|
||||||
|
broadcast(@secrets_topic, {:secret_updated, secret})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp broadcast(topic, message) do
|
||||||
|
Phoenix.PubSub.broadcast(Livebook.PubSub, topic, message)
|
||||||
|
end
|
||||||
|
end
|
|
@ -12,7 +12,7 @@ defmodule Livebook.Hubs.Enterprise do
|
||||||
token: String.t() | nil,
|
token: String.t() | nil,
|
||||||
external_id: String.t() | nil,
|
external_id: String.t() | nil,
|
||||||
hub_name: String.t() | nil,
|
hub_name: String.t() | nil,
|
||||||
hub_color: String.t() | nil
|
hub_emoji: String.t() | nil
|
||||||
}
|
}
|
||||||
|
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
|
@ -20,7 +20,7 @@ defmodule Livebook.Hubs.Enterprise do
|
||||||
field :token, :string
|
field :token, :string
|
||||||
field :external_id, :string
|
field :external_id, :string
|
||||||
field :hub_name, :string
|
field :hub_name, :string
|
||||||
field :hub_color, Livebook.EctoTypes.HexColor
|
field :hub_emoji, :string
|
||||||
end
|
end
|
||||||
|
|
||||||
@fields ~w(
|
@fields ~w(
|
||||||
|
@ -28,7 +28,7 @@ defmodule Livebook.Hubs.Enterprise do
|
||||||
token
|
token
|
||||||
external_id
|
external_id
|
||||||
hub_name
|
hub_name
|
||||||
hub_color
|
hub_emoji
|
||||||
)a
|
)a
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -113,7 +113,7 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Enterprise do
|
||||||
token: fields.token,
|
token: fields.token,
|
||||||
external_id: fields.external_id,
|
external_id: fields.external_id,
|
||||||
hub_name: fields.hub_name,
|
hub_name: fields.hub_name,
|
||||||
hub_color: fields.hub_color
|
hub_emoji: fields.hub_emoji
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Enterprise do
|
||||||
id: enterprise.id,
|
id: enterprise.id,
|
||||||
name: enterprise.hub_name,
|
name: enterprise.hub_name,
|
||||||
provider: enterprise,
|
provider: enterprise,
|
||||||
color: enterprise.hub_color
|
emoji: enterprise.hub_emoji
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -130,4 +130,8 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Enterprise do
|
||||||
|
|
||||||
def connect(%Livebook.Hubs.Enterprise{} = enterprise),
|
def connect(%Livebook.Hubs.Enterprise{} = enterprise),
|
||||||
do: {Livebook.Hubs.EnterpriseClient, enterprise}
|
do: {Livebook.Hubs.EnterpriseClient, enterprise}
|
||||||
|
|
||||||
|
def connected?(%Livebook.Hubs.Enterprise{id: id}) do
|
||||||
|
Livebook.Hubs.EnterpriseClient.connected?(id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,21 +2,31 @@ defmodule Livebook.Hubs.EnterpriseClient do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
use GenServer
|
use GenServer
|
||||||
|
|
||||||
|
alias Livebook.Hubs.Broadcasts
|
||||||
alias Livebook.Hubs.Enterprise
|
alias Livebook.Hubs.Enterprise
|
||||||
alias Livebook.Secrets.Secret
|
alias Livebook.Secrets.Secret
|
||||||
alias Livebook.WebSocket.Server
|
alias Livebook.WebSocket.Server
|
||||||
|
|
||||||
@pubsub_topic "enterprise"
|
|
||||||
@registry Livebook.HubsRegistry
|
@registry Livebook.HubsRegistry
|
||||||
|
|
||||||
defstruct [:server, :hub, secrets: []]
|
defstruct [:server, :hub, connected?: false, secrets: []]
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Connects the Enterprise client with WebSocket server.
|
Connects the Enterprise client with WebSocket server.
|
||||||
"""
|
"""
|
||||||
@spec start_link(Enterprise.t()) :: GenServer.on_start()
|
@spec start_link(Enterprise.t()) :: GenServer.on_start()
|
||||||
def start_link(%Enterprise{} = enterprise) do
|
def start_link(%Enterprise{} = enterprise) do
|
||||||
GenServer.start_link(__MODULE__, enterprise, name: registry_name(enterprise))
|
GenServer.start_link(__MODULE__, enterprise, name: registry_name(enterprise.id))
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Stops the WebSocket server.
|
||||||
|
"""
|
||||||
|
@spec stop(pid()) :: :ok
|
||||||
|
def stop(pid) do
|
||||||
|
pid |> GenServer.call(:get_server) |> GenServer.stop()
|
||||||
|
|
||||||
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -36,27 +46,15 @@ defmodule Livebook.Hubs.EnterpriseClient do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Subscribe to WebSocket Server events.
|
Returns if the given enterprise is connected.
|
||||||
|
|
||||||
## Messages
|
|
||||||
|
|
||||||
* `{:connect, :ok, :connected}`
|
|
||||||
* `{:connect, :error, reason}`
|
|
||||||
* `{:secret_created, %Secret{}}`
|
|
||||||
* `{:secret_updated, %Secret{}}`
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec subscribe() :: :ok | {:error, {:already_registered, pid()}}
|
@spec connected?(String.t()) :: boolean()
|
||||||
def subscribe do
|
def connected?(id) do
|
||||||
Phoenix.PubSub.subscribe(Livebook.PubSub, @pubsub_topic)
|
try do
|
||||||
end
|
GenServer.call(registry_name(id), :connected?)
|
||||||
|
catch
|
||||||
@doc """
|
:exit, _ -> false
|
||||||
Unsubscribes from `subscribe/0`.
|
end
|
||||||
"""
|
|
||||||
@spec unsubscribe() :: :ok
|
|
||||||
def unsubscribe do
|
|
||||||
Phoenix.PubSub.unsubscribe(Livebook.PubSub, @pubsub_topic)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
## GenServer callbacks
|
## GenServer callbacks
|
||||||
|
@ -78,44 +76,48 @@ defmodule Livebook.Hubs.EnterpriseClient do
|
||||||
{:reply, state.secrets, state}
|
{:reply, state.secrets, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
def handle_call(:connected?, _caller, state) do
|
||||||
def handle_info({:connect, _, _} = message, state) do
|
{:reply, state.connected?, state}
|
||||||
broadcast_message(message)
|
|
||||||
{:noreply, state}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:disconnect, :error, _} = message, state) do
|
@impl true
|
||||||
broadcast_message(message)
|
def handle_info({:connect, :ok, _}, state) do
|
||||||
{:noreply, state}
|
Broadcasts.hub_connected()
|
||||||
|
{:noreply, %{state | connected?: true}}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info({:connect, :error, reason}, state) do
|
||||||
|
Broadcasts.hub_connection_failed(reason)
|
||||||
|
{:noreply, %{state | connected?: false}}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info({:disconnect, :error, reason}, state) do
|
||||||
|
Broadcasts.hub_disconnection_failed(reason)
|
||||||
|
{:noreply, %{state | connected?: false}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:event, :secret_created, %{name: name, value: value}}, state) do
|
def handle_info({:event, :secret_created, %{name: name, value: value}}, state) do
|
||||||
secret = %Secret{name: name, value: value}
|
secret = %Secret{name: name, value: value}
|
||||||
broadcast_message({:secret_created, secret})
|
Broadcasts.secret_created(secret)
|
||||||
|
|
||||||
{:noreply, put_secret(state, secret)}
|
{:noreply, put_secret(state, secret)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:event, :secret_updated, %{name: name, value: value}}, state) do
|
def handle_info({:event, :secret_updated, %{name: name, value: value}}, state) do
|
||||||
secret = %Secret{name: name, value: value}
|
secret = %Secret{name: name, value: value}
|
||||||
broadcast_message({:secret_updated, secret})
|
Broadcasts.secret_updated(secret)
|
||||||
|
|
||||||
{:noreply, put_secret(state, secret)}
|
{:noreply, put_secret(state, secret)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:disconnect, :ok, :disconnected}, state) do
|
def handle_info({:disconnect, :ok, :disconnected}, state) do
|
||||||
|
Broadcasts.hub_disconnected()
|
||||||
{:stop, :normal, state}
|
{:stop, :normal, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Private
|
# Private
|
||||||
|
|
||||||
# Notifies interested processes about WebSocket Server messages.
|
defp registry_name(id) do
|
||||||
# Broadcasts the given message under the `"enterprise"` topic.
|
|
||||||
defp broadcast_message(message) do
|
|
||||||
Phoenix.PubSub.broadcast(Livebook.PubSub, @pubsub_topic, message)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp registry_name(%Enterprise{id: id}) do
|
|
||||||
{:via, Registry, {@registry, id}}
|
{:via, Registry, {@registry, id}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ defmodule Livebook.Hubs.Fly do
|
||||||
id: String.t() | nil,
|
id: String.t() | nil,
|
||||||
access_token: String.t() | nil,
|
access_token: String.t() | nil,
|
||||||
hub_name: String.t() | nil,
|
hub_name: String.t() | nil,
|
||||||
hub_color: String.t() | nil,
|
hub_emoji: String.t() | nil,
|
||||||
organization_id: String.t() | nil,
|
organization_id: String.t() | nil,
|
||||||
organization_type: String.t() | nil,
|
organization_type: String.t() | nil,
|
||||||
organization_name: String.t() | nil,
|
organization_name: String.t() | nil,
|
||||||
|
@ -20,7 +20,7 @@ defmodule Livebook.Hubs.Fly do
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field :access_token, :string
|
field :access_token, :string
|
||||||
field :hub_name, :string
|
field :hub_name, :string
|
||||||
field :hub_color, Livebook.EctoTypes.HexColor
|
field :hub_emoji, :string
|
||||||
field :organization_id, :string
|
field :organization_id, :string
|
||||||
field :organization_type, :string
|
field :organization_type, :string
|
||||||
field :organization_name, :string
|
field :organization_name, :string
|
||||||
|
@ -30,7 +30,7 @@ defmodule Livebook.Hubs.Fly do
|
||||||
@fields ~w(
|
@fields ~w(
|
||||||
access_token
|
access_token
|
||||||
hub_name
|
hub_name
|
||||||
hub_color
|
hub_emoji
|
||||||
organization_id
|
organization_id
|
||||||
organization_name
|
organization_name
|
||||||
organization_type
|
organization_type
|
||||||
|
@ -116,7 +116,7 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Fly do
|
||||||
| id: fields.id,
|
| id: fields.id,
|
||||||
access_token: fields.access_token,
|
access_token: fields.access_token,
|
||||||
hub_name: fields.hub_name,
|
hub_name: fields.hub_name,
|
||||||
hub_color: fields.hub_color,
|
hub_emoji: fields.hub_emoji,
|
||||||
organization_id: fields.organization_id,
|
organization_id: fields.organization_id,
|
||||||
organization_type: fields.organization_type,
|
organization_type: fields.organization_type,
|
||||||
organization_name: fields.organization_name,
|
organization_name: fields.organization_name,
|
||||||
|
@ -129,11 +129,13 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Fly do
|
||||||
id: fly.id,
|
id: fly.id,
|
||||||
name: fly.hub_name,
|
name: fly.hub_name,
|
||||||
provider: fly,
|
provider: fly,
|
||||||
color: fly.hub_color
|
emoji: fly.hub_emoji
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def type(_fly), do: "fly"
|
def type(_fly), do: "fly"
|
||||||
|
|
||||||
def connect(_fly), do: nil
|
def connect(_fly), do: nil
|
||||||
|
|
||||||
|
def connected?(_fly), do: false
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
defmodule Livebook.Hubs.Local do
|
defmodule Livebook.Hubs.Local do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
|
|
||||||
defstruct [:id, :hub_name, :hub_color]
|
defstruct [:id, :hub_name, :hub_emoji]
|
||||||
end
|
end
|
||||||
|
|
||||||
defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Local do
|
defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Local do
|
||||||
def load(%Livebook.Hubs.Local{} = local, fields) do
|
def load(%Livebook.Hubs.Local{} = local, fields) do
|
||||||
%{local | id: fields.id, hub_name: fields.hub_name, hub_color: fields.hub_color}
|
%{local | id: fields.id, hub_name: fields.hub_name, hub_emoji: fields.hub_emoji}
|
||||||
end
|
end
|
||||||
|
|
||||||
def normalize(%Livebook.Hubs.Local{} = local) do
|
def normalize(%Livebook.Hubs.Local{} = local) do
|
||||||
|
@ -14,11 +14,13 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Local do
|
||||||
id: local.id,
|
id: local.id,
|
||||||
name: local.hub_name,
|
name: local.hub_name,
|
||||||
provider: local,
|
provider: local,
|
||||||
color: local.hub_color
|
emoji: local.hub_emoji
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def type(_local), do: "local"
|
def type(_local), do: "local"
|
||||||
|
|
||||||
def connect(_local), do: nil
|
def connect(_local), do: nil
|
||||||
|
|
||||||
|
def connected?(_local), do: false
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
defmodule Livebook.Hubs.Metadata do
|
defmodule Livebook.Hubs.Metadata do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
|
|
||||||
defstruct [:id, :name, :provider, :color]
|
defstruct [:id, :name, :provider, :emoji, connected?: false]
|
||||||
|
|
||||||
@type t :: %__MODULE__{
|
@type t :: %__MODULE__{
|
||||||
id: String.t(),
|
id: String.t(),
|
||||||
name: String.t(),
|
name: String.t(),
|
||||||
provider: struct(),
|
provider: struct(),
|
||||||
color: String.t()
|
emoji: String.t(),
|
||||||
|
connected?: boolean()
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -24,4 +24,10 @@ defprotocol Livebook.Hubs.Provider do
|
||||||
"""
|
"""
|
||||||
@spec connect(struct()) :: Supervisor.child_spec() | module() | {module(), any()} | nil
|
@spec connect(struct()) :: Supervisor.child_spec() | module() | {module(), any()} | nil
|
||||||
def connect(struct)
|
def connect(struct)
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets the connection status of the given struct.
|
||||||
|
"""
|
||||||
|
@spec connected?(struct()) :: boolean()
|
||||||
|
def connected?(struct)
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,7 +2,6 @@ defmodule LivebookWeb.FormHelpers do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Conveniences for translating and building error messages.
|
Conveniences for translating and building error messages.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
use Phoenix.Component
|
use Phoenix.Component
|
||||||
|
|
||||||
import Phoenix.HTML.Form
|
import Phoenix.HTML.Form
|
||||||
|
@ -55,6 +54,38 @@ defmodule LivebookWeb.FormHelpers do
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Emoji input.
|
||||||
|
"""
|
||||||
|
def emoji_input(assigns) do
|
||||||
|
~H"""
|
||||||
|
<div id={@id} class="flex border-[1px] bg-gray-50 rounded-lg space-x-4 items-center">
|
||||||
|
<div id={"#{@id}-picker"} class="grid grid-cols-1 md:grid-cols-3 w-full" phx-hook="EmojiPicker">
|
||||||
|
<div class="place-content-start">
|
||||||
|
<div class="p-1 pl-3">
|
||||||
|
<span id={"#{@id}-preview"} data-emoji-preview><%= input_value(@form, @field) %></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div />
|
||||||
|
|
||||||
|
<div class="flex items-center place-content-end">
|
||||||
|
<button
|
||||||
|
id={"#{@id}-button"}
|
||||||
|
type="button"
|
||||||
|
data-emoji-button
|
||||||
|
class="p-1 pl-3 pr-3 rounded-tr-lg rounded-br-lg bg-gray-50 hover:bg-gray-100 active:bg-gray-200 border-l-[1px] bg-white flex justify-center items-center cursor-pointer"
|
||||||
|
>
|
||||||
|
<.remix_icon icon="emotion-line" class="text-xl" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id={"#{@id}-container"} data-emoji-container class="absolute mt-10 hidden" />
|
||||||
|
<%= hidden_input(@form, @field, class: "hidden emoji-picker-input", "data-emoji-input": true) %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Translates an error message.
|
Translates an error message.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -6,7 +6,7 @@ defmodule LivebookWeb.SidebarHook do
|
||||||
|
|
||||||
def on_mount(:default, _params, _session, socket) do
|
def on_mount(:default, _params, _session, socket) do
|
||||||
if connected?(socket) do
|
if connected?(socket) do
|
||||||
Livebook.Hubs.subscribe()
|
Livebook.Hubs.subscribe([:crud, :connection])
|
||||||
end
|
end
|
||||||
|
|
||||||
socket =
|
socket =
|
||||||
|
@ -18,8 +18,16 @@ defmodule LivebookWeb.SidebarHook do
|
||||||
{:cont, socket}
|
{:cont, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp handle_info({:hubs_metadata_changed, hubs}, socket) do
|
@connection_events ~w(hub_connected hub_disconnected hubs_metadata_changed)a
|
||||||
{:halt, assign(socket, saved_hubs: hubs)}
|
|
||||||
|
defp handle_info(event, socket) when event in @connection_events do
|
||||||
|
{:halt, assign(socket, saved_hubs: Livebook.Hubs.get_metadatas())}
|
||||||
|
end
|
||||||
|
|
||||||
|
@error_events ~w(connection_error disconnection_error)a
|
||||||
|
|
||||||
|
defp handle_info({event, _reason}, socket) when event in @error_events do
|
||||||
|
{:halt, assign(socket, saved_hubs: Livebook.Hubs.get_metadatas())}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp handle_info(_event, socket), do: {:cont, socket}
|
defp handle_info(_event, socket), do: {:cont, socket}
|
||||||
|
|
|
@ -21,7 +21,7 @@ defmodule LivebookWeb.UserHook do
|
||||||
{:user_change, %{id: id} = user},
|
{:user_change, %{id: id} = user},
|
||||||
%{assigns: %{current_user: %{id: id}}} = socket
|
%{assigns: %{current_user: %{id: id}}} = socket
|
||||||
) do
|
) do
|
||||||
{:cont, assign(socket, :current_user, user)}
|
{:halt, assign(socket, :current_user, user)}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp info(_message, socket), do: {:cont, socket}
|
defp info(_message, socket), do: {:cont, socket}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
defmodule LivebookWeb.Hub.Edit.EnterpriseComponent do
|
defmodule LivebookWeb.Hub.Edit.EnterpriseComponent do
|
||||||
use LivebookWeb, :live_component
|
use LivebookWeb, :live_component
|
||||||
|
|
||||||
alias Livebook.EctoTypes.HexColor
|
|
||||||
alias Livebook.Hubs.Enterprise
|
alias Livebook.Hubs.Enterprise
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
@ -35,13 +34,9 @@ defmodule LivebookWeb.Hub.Edit.EnterpriseComponent do
|
||||||
phx-debounce="blur"
|
phx-debounce="blur"
|
||||||
>
|
>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-1 gap-3">
|
<div class="grid grid-cols-1 md:grid-cols-1 gap-3">
|
||||||
<.input_wrapper form={f} field={:hub_color} class="flex flex-col space-y-1">
|
<.input_wrapper form={f} field={:hub_emoji} class="flex flex-col space-y-1">
|
||||||
<div class="input-label">Color</div>
|
<div class="input-label">Emoji</div>
|
||||||
<.hex_color_input
|
<.emoji_input id="enterprise-emoji-input" form={f} field={:hub_emoji} />
|
||||||
form={f}
|
|
||||||
field={:hub_color}
|
|
||||||
randomize={JS.push("randomize_color", target: @myself)}
|
|
||||||
/>
|
|
||||||
</.input_wrapper>
|
</.input_wrapper>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -58,10 +53,6 @@ defmodule LivebookWeb.Hub.Edit.EnterpriseComponent do
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("randomize_color", _, socket) do
|
|
||||||
handle_event("validate", %{"enterprise" => %{"hub_color" => HexColor.random()}}, socket)
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("save", %{"enterprise" => params}, socket) do
|
def handle_event("save", %{"enterprise" => params}, socket) do
|
||||||
case Enterprise.update_hub(socket.assigns.hub, params) do
|
case Enterprise.update_hub(socket.assigns.hub, params) do
|
||||||
{:ok, hub} ->
|
{:ok, hub} ->
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
defmodule LivebookWeb.Hub.Edit.FlyComponent do
|
defmodule LivebookWeb.Hub.Edit.FlyComponent do
|
||||||
use LivebookWeb, :live_component
|
use LivebookWeb, :live_component
|
||||||
|
|
||||||
alias Livebook.EctoTypes.HexColor
|
|
||||||
alias Livebook.Hubs.{Fly, FlyClient}
|
alias Livebook.Hubs.{Fly, FlyClient}
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
@ -76,13 +75,9 @@ defmodule LivebookWeb.Hub.Edit.FlyComponent do
|
||||||
<%= text_input(f, :hub_name, class: "input") %>
|
<%= text_input(f, :hub_name, class: "input") %>
|
||||||
</.input_wrapper>
|
</.input_wrapper>
|
||||||
|
|
||||||
<.input_wrapper form={f} field={:hub_color} class="flex flex-col space-y-1">
|
<.input_wrapper form={f} field={:hub_emoji} class="flex flex-col space-y-1">
|
||||||
<div class="input-label">Color</div>
|
<div class="input-label">Emoji</div>
|
||||||
<.hex_color_input
|
<.emoji_input id="fly-emoji-input" form={f} field={:hub_emoji} />
|
||||||
form={f}
|
|
||||||
field={:hub_color}
|
|
||||||
randomize={JS.push("randomize_color", target: @myself)}
|
|
||||||
/>
|
|
||||||
</.input_wrapper>
|
</.input_wrapper>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -134,10 +129,6 @@ defmodule LivebookWeb.Hub.Edit.FlyComponent do
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("randomize_color", _, socket) do
|
|
||||||
handle_event("validate", %{"fly" => %{"hub_color" => HexColor.random()}}, socket)
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("save", %{"fly" => params}, socket) do
|
def handle_event("save", %{"fly" => params}, socket) do
|
||||||
case Fly.update_hub(socket.assigns.hub, params) do
|
case Fly.update_hub(socket.assigns.hub, params) do
|
||||||
{:ok, hub} ->
|
{:ok, hub} ->
|
||||||
|
|
|
@ -3,13 +3,12 @@ defmodule LivebookWeb.Hub.New.EnterpriseComponent do
|
||||||
|
|
||||||
import Ecto.Changeset, only: [get_field: 2]
|
import Ecto.Changeset, only: [get_field: 2]
|
||||||
|
|
||||||
alias Livebook.EctoTypes.HexColor
|
|
||||||
alias Livebook.Hubs.{Enterprise, EnterpriseClient}
|
alias Livebook.Hubs.{Enterprise, EnterpriseClient}
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def update(assigns, socket) do
|
def update(assigns, socket) do
|
||||||
if connected?(socket) do
|
if connected?(socket) do
|
||||||
EnterpriseClient.subscribe()
|
Livebook.Hubs.subscribe(:connection)
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok,
|
{:ok,
|
||||||
|
@ -82,13 +81,9 @@ defmodule LivebookWeb.Hub.New.EnterpriseComponent do
|
||||||
<%= text_input(f, :hub_name, class: "input", readonly: true) %>
|
<%= text_input(f, :hub_name, class: "input", readonly: true) %>
|
||||||
</.input_wrapper>
|
</.input_wrapper>
|
||||||
|
|
||||||
<.input_wrapper form={f} field={:hub_color} class="flex flex-col space-y-1">
|
<.input_wrapper form={f} field={:hub_emoji} class="flex flex-col space-y-1">
|
||||||
<div class="input-label">Color</div>
|
<div class="input-label">Emoji</div>
|
||||||
<.hex_color_input
|
<.emoji_input id="enterprise-emoji-input" form={f} field={:hub_emoji} />
|
||||||
form={f}
|
|
||||||
field={:hub_color}
|
|
||||||
randomize={JS.push("randomize_color", target: @myself)}
|
|
||||||
/>
|
|
||||||
</.input_wrapper>
|
</.input_wrapper>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -109,20 +104,26 @@ defmodule LivebookWeb.Hub.New.EnterpriseComponent do
|
||||||
token = get_field(socket.assigns.changeset, :token)
|
token = get_field(socket.assigns.changeset, :token)
|
||||||
|
|
||||||
base = %Enterprise{
|
base = %Enterprise{
|
||||||
|
id: "enterprise-placeholder",
|
||||||
token: token,
|
token: token,
|
||||||
|
external_id: "placeholder",
|
||||||
url: url,
|
url: url,
|
||||||
hub_name: "Enterprise",
|
hub_name: "Enterprise",
|
||||||
hub_color: HexColor.random()
|
hub_emoji: "🏭"
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, pid} = EnterpriseClient.start_link(base)
|
{:ok, pid} = EnterpriseClient.start_link(base)
|
||||||
|
|
||||||
receive do
|
receive do
|
||||||
{:connect, :error, reason} ->
|
{:connection_error, reason} ->
|
||||||
GenServer.stop(pid)
|
EnterpriseClient.stop(pid)
|
||||||
handle_error(reason, socket)
|
|
||||||
|
|
||||||
{:connect, :ok, :connected} ->
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> put_flash(:error, "Failed to connect with Enterprise: " <> reason)
|
||||||
|
|> push_patch(to: Routes.hub_path(socket, :new))}
|
||||||
|
|
||||||
|
:hub_connected ->
|
||||||
session_request =
|
session_request =
|
||||||
LivebookProto.SessionRequest.new!(app_version: Livebook.Config.app_version())
|
LivebookProto.SessionRequest.new!(app_version: Livebook.Config.app_version())
|
||||||
|
|
||||||
|
@ -134,16 +135,16 @@ defmodule LivebookWeb.Hub.New.EnterpriseComponent do
|
||||||
{:noreply, assign(socket, pid: pid, changeset: changeset, base: base)}
|
{:noreply, assign(socket, pid: pid, changeset: changeset, base: base)}
|
||||||
|
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
GenServer.stop(pid)
|
EnterpriseClient.stop(pid)
|
||||||
handle_error(reason, socket)
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> put_flash(:error, "Failed to connect with Enterprise: " <> reason)
|
||||||
|
|> push_patch(to: Routes.hub_path(socket, :new))}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("randomize_color", _, socket) do
|
|
||||||
handle_event("validate", %{"enterprise" => %{"hub_color" => HexColor.random()}}, socket)
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("save", %{"enterprise" => params}, socket) do
|
def handle_event("save", %{"enterprise" => params}, socket) do
|
||||||
if socket.assigns.changeset.valid? do
|
if socket.assigns.changeset.valid? do
|
||||||
case Enterprise.create_hub(socket.assigns.base, params) do
|
case Enterprise.create_hub(socket.assigns.base, params) do
|
||||||
|
@ -168,23 +169,4 @@ defmodule LivebookWeb.Hub.New.EnterpriseComponent do
|
||||||
def handle_event("validate", %{"enterprise" => attrs}, socket) do
|
def handle_event("validate", %{"enterprise" => attrs}, socket) do
|
||||||
{:noreply, assign(socket, changeset: Enterprise.change_hub(socket.assigns.base, attrs))}
|
{:noreply, assign(socket, changeset: Enterprise.change_hub(socket.assigns.base, attrs))}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_error(%{reason: :econnrefused}, socket) do
|
|
||||||
show_connect_error("Failed to connect with given URL", socket)
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_error(%{reason: _}, socket) do
|
|
||||||
show_connect_error("Failed to connect with Enterprise", socket)
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_error(reason, socket) do
|
|
||||||
show_connect_error(reason, socket)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp show_connect_error(message, socket) do
|
|
||||||
{:noreply,
|
|
||||||
socket
|
|
||||||
|> put_flash(:error, message)
|
|
||||||
|> push_patch(to: Routes.hub_path(socket, :new))}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,7 +3,6 @@ defmodule LivebookWeb.Hub.New.FlyComponent do
|
||||||
|
|
||||||
import Ecto.Changeset, only: [get_field: 2, add_error: 3]
|
import Ecto.Changeset, only: [get_field: 2, add_error: 3]
|
||||||
|
|
||||||
alias Livebook.EctoTypes.HexColor
|
|
||||||
alias Livebook.Hubs.{Fly, FlyClient}
|
alias Livebook.Hubs.{Fly, FlyClient}
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
@ -60,13 +59,9 @@ defmodule LivebookWeb.Hub.New.FlyComponent do
|
||||||
<%= text_input(f, :hub_name, class: "input") %>
|
<%= text_input(f, :hub_name, class: "input") %>
|
||||||
</.input_wrapper>
|
</.input_wrapper>
|
||||||
|
|
||||||
<.input_wrapper form={f} field={:hub_color} class="flex flex-col space-y-1">
|
<.input_wrapper form={f} field={:hub_emoji} class="flex flex-col space-y-1">
|
||||||
<div class="input-label">Color</div>
|
<div class="input-label">Emoji</div>
|
||||||
<.hex_color_input
|
<.emoji_input id="fly-emoji-input" form={f} field={:hub_emoji} />
|
||||||
form={f}
|
|
||||||
field={:hub_color}
|
|
||||||
randomize={JS.push("randomize_color", target: @myself)}
|
|
||||||
/>
|
|
||||||
</.input_wrapper>
|
</.input_wrapper>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -86,7 +81,7 @@ defmodule LivebookWeb.Hub.New.FlyComponent do
|
||||||
case FlyClient.fetch_apps(token) do
|
case FlyClient.fetch_apps(token) do
|
||||||
{:ok, apps} ->
|
{:ok, apps} ->
|
||||||
opts = select_options(apps)
|
opts = select_options(apps)
|
||||||
base = %Fly{access_token: token, hub_color: HexColor.random()}
|
base = %Fly{access_token: token, hub_emoji: "🚀"}
|
||||||
changeset = Fly.change_hub(base)
|
changeset = Fly.change_hub(base)
|
||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
|
@ -103,10 +98,6 @@ defmodule LivebookWeb.Hub.New.FlyComponent do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("randomize_color", _, socket) do
|
|
||||||
handle_event("validate", %{"fly" => %{"hub_color" => HexColor.random()}}, socket)
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("save", %{"fly" => params}, socket) do
|
def handle_event("save", %{"fly" => params}, socket) do
|
||||||
if socket.assigns.changeset.valid? do
|
if socket.assigns.changeset.valid? do
|
||||||
case Fly.create_hub(socket.assigns.selected_app, params) do
|
case Fly.create_hub(socket.assigns.selected_app, params) do
|
||||||
|
|
|
@ -5,6 +5,7 @@ defmodule LivebookWeb.LayoutHelpers do
|
||||||
import LivebookWeb.UserHelpers
|
import LivebookWeb.UserHelpers
|
||||||
|
|
||||||
alias Phoenix.LiveView.JS
|
alias Phoenix.LiveView.JS
|
||||||
|
alias Livebook.Hubs.Provider
|
||||||
alias LivebookWeb.Router.Helpers, as: Routes
|
alias LivebookWeb.Router.Helpers, as: Routes
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -165,15 +166,9 @@ defmodule LivebookWeb.LayoutHelpers do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp sidebar_link(assigns) do
|
defp sidebar_link(assigns) do
|
||||||
assigns = assign_new(assigns, :icon_style, fn -> nil end)
|
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<%= live_redirect to: @to, class: "h-7 flex items-center hover:text-white #{sidebar_link_text_color(@to, @current)} border-l-4 #{sidebar_link_border_color(@to, @current)} hover:border-white" do %>
|
<%= live_redirect to: @to, class: "h-7 flex items-center hover:text-white #{sidebar_link_text_color(@to, @current)} border-l-4 #{sidebar_link_border_color(@to, @current)} hover:border-white" do %>
|
||||||
<.remix_icon
|
<.remix_icon icon={@icon} class="text-lg leading-6 w-[56px] flex justify-center" />
|
||||||
icon={@icon}
|
|
||||||
class="text-lg leading-6 w-[56px] flex justify-center"
|
|
||||||
style={@icon_style}
|
|
||||||
/>
|
|
||||||
<span class="text-sm font-medium">
|
<span class="text-sm font-medium">
|
||||||
<%= @title %>
|
<%= @title %>
|
||||||
</span>
|
</span>
|
||||||
|
@ -181,6 +176,28 @@ defmodule LivebookWeb.LayoutHelpers do
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp sidebar_hub_link(assigns) do
|
||||||
|
~H"""
|
||||||
|
<%= live_redirect to: @to, class: "h-7 flex items-center hover:text-white #{sidebar_link_text_color(@to, @current)} border-l-4 #{sidebar_link_border_color(@to, @current)} hover:border-white" do %>
|
||||||
|
<div class="text-lg leading-6 w-[56px] flex justify-center">
|
||||||
|
<span class="relative">
|
||||||
|
<%= @hub.emoji %>
|
||||||
|
|
||||||
|
<%= if Provider.connect(@hub.provider) do %>
|
||||||
|
<div class={[
|
||||||
|
"absolute w-[10px] h-[10px] border-gray-900 border-2 rounded-full right-0 bottom-0",
|
||||||
|
if(@hub.connected?, do: "bg-green-400", else: "bg-red-400")
|
||||||
|
]} />
|
||||||
|
<% end %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-sm font-medium">
|
||||||
|
<%= @hub.name %>
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
defp hub_section(assigns) do
|
defp hub_section(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<%= if Livebook.Config.feature_flag_enabled?(:hub) do %>
|
<%= if Livebook.Config.feature_flag_enabled?(:hub) do %>
|
||||||
|
@ -191,10 +208,8 @@ defmodule LivebookWeb.LayoutHelpers do
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= for hub <- @hubs do %>
|
<%= for hub <- @hubs do %>
|
||||||
<.sidebar_link
|
<.sidebar_hub_link
|
||||||
title={hub.name}
|
hub={hub}
|
||||||
icon="checkbox-blank-circle-fill"
|
|
||||||
icon_style={"color: #{hub.color}"}
|
|
||||||
to={Routes.hub_path(@socket, :edit, hub.id)}
|
to={Routes.hub_path(@socket, :edit, hub.id)}
|
||||||
current={@current_page}
|
current={@current_page}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -26,8 +26,7 @@ defmodule LivebookWeb.SessionLive do
|
||||||
|
|
||||||
Session.subscribe(session_id)
|
Session.subscribe(session_id)
|
||||||
Secrets.subscribe()
|
Secrets.subscribe()
|
||||||
# TODO: Move this to Hubs.subscribe([:secrets]) and rename all "enterprise" to "hubs"
|
Hubs.subscribe(:secrets)
|
||||||
EnterpriseClient.subscribe()
|
|
||||||
|
|
||||||
{data, client_id}
|
{data, client_id}
|
||||||
else
|
else
|
||||||
|
@ -64,7 +63,7 @@ defmodule LivebookWeb.SessionLive do
|
||||||
autofocus_cell_id: autofocus_cell_id(data.notebook),
|
autofocus_cell_id: autofocus_cell_id(data.notebook),
|
||||||
page_title: get_page_title(data.notebook.name),
|
page_title: get_page_title(data.notebook.name),
|
||||||
livebook_secrets: Secrets.fetch_secrets() |> Map.new(&{&1.name, &1.value}),
|
livebook_secrets: Secrets.fetch_secrets() |> Map.new(&{&1.name, &1.value}),
|
||||||
enterprise_secrets: fetch_enterprise_secrets(),
|
hub_secrets: get_hub_secrets(),
|
||||||
select_secret_ref: nil,
|
select_secret_ref: nil,
|
||||||
select_secret_options: nil
|
select_secret_options: nil
|
||||||
)
|
)
|
||||||
|
@ -186,7 +185,7 @@ defmodule LivebookWeb.SessionLive do
|
||||||
<.secrets_list
|
<.secrets_list
|
||||||
data_view={@data_view}
|
data_view={@data_view}
|
||||||
livebook_secrets={@livebook_secrets}
|
livebook_secrets={@livebook_secrets}
|
||||||
enterprise_secrets={@enterprise_secrets}
|
hub_secrets={@hub_secrets}
|
||||||
session={@session}
|
session={@session}
|
||||||
socket={@socket}
|
socket={@socket}
|
||||||
/>
|
/>
|
||||||
|
@ -739,13 +738,13 @@ defmodule LivebookWeb.SessionLive do
|
||||||
<%= if Livebook.Config.feature_flag_enabled?(:hub) do %>
|
<%= if Livebook.Config.feature_flag_enabled?(:hub) do %>
|
||||||
<div class="mt-16">
|
<div class="mt-16">
|
||||||
<h3 class="uppercase text-sm font-semibold text-gray-500">
|
<h3 class="uppercase text-sm font-semibold text-gray-500">
|
||||||
Enterprise secrets
|
Hub secrets
|
||||||
</h3>
|
</h3>
|
||||||
<span class="text-sm text-gray-500">Available in all sessions</span>
|
<span class="text-sm text-gray-500">Available in all sessions</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col space-y-4 mt-6">
|
<div class="flex flex-col space-y-4 mt-6">
|
||||||
<%= for {secret_name, secret_value} <- Enum.sort(@enterprise_secrets) do %>
|
<%= for {secret_name, secret_value} <- Enum.sort(@hub_secrets) do %>
|
||||||
<div
|
<div
|
||||||
class="flex flex-col text-gray-500 rounded-lg px-2 pt-1"
|
class="flex flex-col text-gray-500 rounded-lg px-2 pt-1"
|
||||||
id={"enterprise-secret-#{secret_name}-wrapper"}
|
id={"enterprise-secret-#{secret_name}-wrapper"}
|
||||||
|
@ -1450,14 +1449,14 @@ defmodule LivebookWeb.SessionLive do
|
||||||
def handle_info({:secret_created, %Secrets.Secret{}}, socket) do
|
def handle_info({:secret_created, %Secrets.Secret{}}, socket) do
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|> assign(enterprise_secrets: fetch_enterprise_secrets())
|
|> assign(hub_secrets: get_hub_secrets())
|
||||||
|> put_flash(:info, "A new secret has been created on your Livebook Enterprise")}
|
|> put_flash(:info, "A new secret has been created on your Livebook Enterprise")}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:secret_updated, %Secrets.Secret{}}, socket) do
|
def handle_info({:secret_updated, %Secrets.Secret{}}, socket) do
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|> assign(enterprise_secrets: fetch_enterprise_secrets())
|
|> assign(hub_secrets: get_hub_secrets())
|
||||||
|> put_flash(:info, "An existing secret has been updated on your Livebook Enterprise")}
|
|> put_flash(:info, "An existing secret has been updated on your Livebook Enterprise")}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -2298,7 +2297,7 @@ defmodule LivebookWeb.SessionLive do
|
||||||
secret in secrets
|
secret in secrets
|
||||||
end
|
end
|
||||||
|
|
||||||
defp fetch_enterprise_secrets do
|
defp get_hub_secrets do
|
||||||
for connected_hub <- Hubs.get_connected_hubs(),
|
for connected_hub <- Hubs.get_connected_hubs(),
|
||||||
secret <- EnterpriseClient.list_cached_secrets(connected_hub.pid),
|
secret <- EnterpriseClient.list_cached_secrets(connected_hub.pid),
|
||||||
into: %{},
|
into: %{},
|
||||||
|
|
|
@ -6,7 +6,7 @@ defmodule Livebook.Hubs.EnterpriseClientTest do
|
||||||
alias Livebook.Secrets.Secret
|
alias Livebook.Secrets.Secret
|
||||||
|
|
||||||
setup do
|
setup do
|
||||||
EnterpriseClient.subscribe()
|
Livebook.Hubs.subscribe([:connection, :secrets])
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -15,21 +15,21 @@ defmodule Livebook.Hubs.EnterpriseClientTest do
|
||||||
enterprise = build(:enterprise, url: url, token: token)
|
enterprise = build(:enterprise, url: url, token: token)
|
||||||
|
|
||||||
EnterpriseClient.start_link(enterprise)
|
EnterpriseClient.start_link(enterprise)
|
||||||
assert_receive {:connect, :ok, :connected}
|
assert_receive :hub_connected
|
||||||
end
|
end
|
||||||
|
|
||||||
test "rejects the websocket with invalid address", %{token: token} do
|
test "rejects the websocket with invalid address", %{token: token} do
|
||||||
enterprise = build(:enterprise, url: "http://localhost:9999", token: token)
|
enterprise = build(:enterprise, url: "http://localhost:9999", token: token)
|
||||||
|
|
||||||
EnterpriseClient.start_link(enterprise)
|
EnterpriseClient.start_link(enterprise)
|
||||||
assert_receive {:connect, :error, "connection refused"}
|
assert_receive {:connection_error, "connection refused"}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "rejects the web socket connection with invalid credentials", %{url: url} do
|
test "rejects the web socket connection with invalid credentials", %{url: url} do
|
||||||
enterprise = build(:enterprise, url: url, token: "foo")
|
enterprise = build(:enterprise, url: url, token: "foo")
|
||||||
|
|
||||||
EnterpriseClient.start_link(enterprise)
|
EnterpriseClient.start_link(enterprise)
|
||||||
assert_receive {:connect, :error, reason}
|
assert_receive {:connection_error, reason}
|
||||||
assert reason =~ "the given token is invalid"
|
assert reason =~ "the given token is invalid"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -38,8 +38,7 @@ defmodule Livebook.Hubs.EnterpriseClientTest do
|
||||||
setup %{url: url, token: token} do
|
setup %{url: url, token: token} do
|
||||||
enterprise = build(:enterprise, url: url, token: token)
|
enterprise = build(:enterprise, url: url, token: token)
|
||||||
EnterpriseClient.start_link(enterprise)
|
EnterpriseClient.start_link(enterprise)
|
||||||
|
assert_receive :hub_connected
|
||||||
assert_receive {:connect, :ok, :connected}
|
|
||||||
|
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,7 @@ defmodule Livebook.Hubs.ProviderTest do
|
||||||
assert Provider.normalize(fly) == %Metadata{
|
assert Provider.normalize(fly) == %Metadata{
|
||||||
id: fly.id,
|
id: fly.id,
|
||||||
name: fly.hub_name,
|
name: fly.hub_name,
|
||||||
color: fly.hub_color,
|
emoji: fly.hub_emoji,
|
||||||
provider: fly
|
provider: fly
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,7 +23,7 @@ defmodule Livebook.HubsTest do
|
||||||
assert Hubs.get_metadatas() == [
|
assert Hubs.get_metadatas() == [
|
||||||
%Hubs.Metadata{
|
%Hubs.Metadata{
|
||||||
id: "fly-livebook",
|
id: "fly-livebook",
|
||||||
color: fly.hub_color,
|
emoji: fly.hub_emoji,
|
||||||
name: fly.hub_name,
|
name: fly.hub_name,
|
||||||
provider: fly
|
provider: fly
|
||||||
}
|
}
|
||||||
|
@ -60,9 +60,9 @@ defmodule Livebook.HubsTest do
|
||||||
|
|
||||||
test "save_hub/1 updates hub" do
|
test "save_hub/1 updates hub" do
|
||||||
fly = insert_hub(:fly, id: "fly-foo2")
|
fly = insert_hub(:fly, id: "fly-foo2")
|
||||||
Hubs.save_hub(%{fly | hub_color: "#FFFFFF"})
|
Hubs.save_hub(%{fly | hub_emoji: "🐈"})
|
||||||
|
|
||||||
refute Hubs.fetch_hub!("fly-foo2") == fly
|
refute Hubs.fetch_hub!("fly-foo2") == fly
|
||||||
assert Hubs.fetch_hub!("fly-foo2").hub_color == "#FFFFFF"
|
assert Hubs.fetch_hub!("fly-foo2").hub_emoji == "🐈"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -40,7 +40,7 @@ defmodule LivebookWeb.Hub.EditLiveTest do
|
||||||
|
|
||||||
attrs = %{
|
attrs = %{
|
||||||
"hub_name" => "Personal Hub",
|
"hub_name" => "Personal Hub",
|
||||||
"hub_color" => "#FF00FF"
|
"hub_emoji" => "🐈"
|
||||||
}
|
}
|
||||||
|
|
||||||
view
|
view
|
||||||
|
@ -59,7 +59,7 @@ defmodule LivebookWeb.Hub.EditLiveTest do
|
||||||
|
|
||||||
assert render(view) =~ "Hub updated successfully"
|
assert render(view) =~ "Hub updated successfully"
|
||||||
|
|
||||||
assert_hub(view, conn, %{hub | hub_color: attrs["hub_color"], hub_name: attrs["hub_name"]})
|
assert_hub(view, conn, %{hub | hub_emoji: attrs["hub_emoji"], hub_name: attrs["hub_name"]})
|
||||||
refute Hubs.fetch_hub!(hub.id) == hub
|
refute Hubs.fetch_hub!(hub.id) == hub
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -195,7 +195,7 @@ defmodule LivebookWeb.Hub.EditLiveTest do
|
||||||
hub = insert_hub(:enterprise)
|
hub = insert_hub(:enterprise)
|
||||||
{:ok, view, _html} = live(conn, Routes.hub_path(conn, :edit, hub.id))
|
{:ok, view, _html} = live(conn, Routes.hub_path(conn, :edit, hub.id))
|
||||||
|
|
||||||
attrs = %{"hub_color" => "#FF00FF"}
|
attrs = %{"hub_emoji" => "🐈"}
|
||||||
|
|
||||||
view
|
view
|
||||||
|> element("#enterprise-form")
|
|> element("#enterprise-form")
|
||||||
|
@ -213,7 +213,7 @@ defmodule LivebookWeb.Hub.EditLiveTest do
|
||||||
|
|
||||||
assert render(view) =~ "Hub updated successfully"
|
assert render(view) =~ "Hub updated successfully"
|
||||||
|
|
||||||
assert_hub(view, conn, %{hub | hub_color: attrs["hub_color"]})
|
assert_hub(view, conn, %{hub | hub_emoji: attrs["hub_emoji"]})
|
||||||
refute Hubs.fetch_hub!(hub.id) == hub
|
refute Hubs.fetch_hub!(hub.id) == hub
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -221,7 +221,7 @@ defmodule LivebookWeb.Hub.EditLiveTest do
|
||||||
defp assert_hub(view, conn, hub) do
|
defp assert_hub(view, conn, hub) do
|
||||||
hubs_html = view |> element("#hubs") |> render()
|
hubs_html = view |> element("#hubs") |> render()
|
||||||
|
|
||||||
assert hubs_html =~ ~s/style="color: #{hub.hub_color}"/
|
assert hubs_html =~ hub.hub_emoji
|
||||||
assert hubs_html =~ Routes.hub_path(conn, :edit, hub.id)
|
assert hubs_html =~ Routes.hub_path(conn, :edit, hub.id)
|
||||||
assert hubs_html =~ hub.hub_name
|
assert hubs_html =~ hub.hub_name
|
||||||
end
|
end
|
||||||
|
|
|
@ -37,7 +37,7 @@ defmodule LivebookWeb.Hub.New.EnterpriseComponentTest do
|
||||||
"url" => url,
|
"url" => url,
|
||||||
"token" => token,
|
"token" => token,
|
||||||
"hub_name" => "Enterprise",
|
"hub_name" => "Enterprise",
|
||||||
"hub_color" => "#FF00FF"
|
"hub_emoji" => "🐈"
|
||||||
}
|
}
|
||||||
|
|
||||||
view
|
view
|
||||||
|
@ -58,7 +58,7 @@ defmodule LivebookWeb.Hub.New.EnterpriseComponentTest do
|
||||||
assert render(view) =~ "Hub added successfully"
|
assert render(view) =~ "Hub added successfully"
|
||||||
|
|
||||||
hubs_html = view |> element("#hubs") |> render()
|
hubs_html = view |> element("#hubs") |> render()
|
||||||
assert hubs_html =~ ~s/style="color: #FF00FF"/
|
assert hubs_html =~ "🐈"
|
||||||
assert hubs_html =~ "/hub/enterprise-#{id}"
|
assert hubs_html =~ "/hub/enterprise-#{id}"
|
||||||
assert hubs_html =~ "Enterprise"
|
assert hubs_html =~ "Enterprise"
|
||||||
end
|
end
|
||||||
|
@ -94,8 +94,13 @@ defmodule LivebookWeb.Hub.New.EnterpriseComponentTest do
|
||||||
stop_new_instance(name)
|
stop_new_instance(name)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "fails to create existing hub", %{conn: conn, url: url, token: token} do
|
test "fails to create existing hub", %{test: name, conn: conn} do
|
||||||
node = EnterpriseServer.get_node()
|
start_new_instance(name)
|
||||||
|
|
||||||
|
node = EnterpriseServer.get_node(name)
|
||||||
|
url = EnterpriseServer.url(name)
|
||||||
|
token = EnterpriseServer.token(name)
|
||||||
|
|
||||||
id = :erpc.call(node, Enterprise.Integration, :fetch_env!, [])
|
id = :erpc.call(node, Enterprise.Integration, :fetch_env!, [])
|
||||||
user = :erpc.call(node, Enterprise.Integration, :create_user, [])
|
user = :erpc.call(node, Enterprise.Integration, :create_user, [])
|
||||||
|
|
||||||
|
@ -135,7 +140,7 @@ defmodule LivebookWeb.Hub.New.EnterpriseComponentTest do
|
||||||
"url" => url,
|
"url" => url,
|
||||||
"token" => token,
|
"token" => token,
|
||||||
"hub_name" => "Enterprise",
|
"hub_name" => "Enterprise",
|
||||||
"hub_color" => "#FFFFFF"
|
"hub_emoji" => "🐈"
|
||||||
}
|
}
|
||||||
|
|
||||||
view
|
view
|
||||||
|
@ -151,11 +156,13 @@ defmodule LivebookWeb.Hub.New.EnterpriseComponentTest do
|
||||||
|> render_submit(%{"enterprise" => attrs}) =~ "already exists"
|
|> render_submit(%{"enterprise" => attrs}) =~ "already exists"
|
||||||
|
|
||||||
hubs_html = view |> element("#hubs") |> render()
|
hubs_html = view |> element("#hubs") |> render()
|
||||||
assert hubs_html =~ ~s/style="color: #{hub.hub_color}"/
|
assert hubs_html =~ hub.hub_emoji
|
||||||
assert hubs_html =~ Routes.hub_path(conn, :edit, hub.id)
|
assert hubs_html =~ Routes.hub_path(conn, :edit, hub.id)
|
||||||
assert hubs_html =~ hub.hub_name
|
assert hubs_html =~ hub.hub_name
|
||||||
|
|
||||||
assert Hubs.fetch_hub!(hub.id) == hub
|
assert Hubs.fetch_hub!(hub.id) == hub
|
||||||
|
after
|
||||||
|
stop_new_instance(name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ defmodule LivebookWeb.Hub.NewLiveTest do
|
||||||
"access_token" => "dummy access token",
|
"access_token" => "dummy access token",
|
||||||
"application_id" => "123456789",
|
"application_id" => "123456789",
|
||||||
"hub_name" => "My Foo Hub",
|
"hub_name" => "My Foo Hub",
|
||||||
"hub_color" => "#FF00FF"
|
"hub_emoji" => "🐈"
|
||||||
}
|
}
|
||||||
|
|
||||||
view
|
view
|
||||||
|
@ -55,17 +55,11 @@ defmodule LivebookWeb.Hub.NewLiveTest do
|
||||||
|
|
||||||
assert render(view) =~ "Hub added successfully"
|
assert render(view) =~ "Hub added successfully"
|
||||||
|
|
||||||
assert view
|
hubs_html = view |> element("#hubs") |> render()
|
||||||
|> element("#hubs")
|
|
||||||
|> render() =~ ~s/style="color: #FF00FF"/
|
|
||||||
|
|
||||||
assert view
|
assert hubs_html =~ "🐈"
|
||||||
|> element("#hubs")
|
assert hubs_html =~ "/hub/fly-123456789"
|
||||||
|> render() =~ "/hub/fly-123456789"
|
assert hubs_html =~ "My Foo Hub"
|
||||||
|
|
||||||
assert view
|
|
||||||
|> element("#hubs")
|
|
||||||
|> render() =~ "My Foo Hub"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "fails to create existing hub", %{conn: conn} do
|
test "fails to create existing hub", %{conn: conn} do
|
||||||
|
@ -87,7 +81,7 @@ defmodule LivebookWeb.Hub.NewLiveTest do
|
||||||
"access_token" => "dummy access token",
|
"access_token" => "dummy access token",
|
||||||
"application_id" => "foo",
|
"application_id" => "foo",
|
||||||
"hub_name" => "My Foo Hub",
|
"hub_name" => "My Foo Hub",
|
||||||
"hub_color" => "#FF00FF"
|
"hub_emoji" => "🐈"
|
||||||
}
|
}
|
||||||
|
|
||||||
view
|
view
|
||||||
|
@ -102,18 +96,7 @@ defmodule LivebookWeb.Hub.NewLiveTest do
|
||||||
|> element("#fly-form")
|
|> element("#fly-form")
|
||||||
|> render_submit(%{"fly" => attrs}) =~ "already exists"
|
|> render_submit(%{"fly" => attrs}) =~ "already exists"
|
||||||
|
|
||||||
assert view
|
assert_hub(view, conn, hub)
|
||||||
|> element("#hubs")
|
|
||||||
|> render() =~ ~s/style="color: #{hub.hub_color}"/
|
|
||||||
|
|
||||||
assert view
|
|
||||||
|> element("#hubs")
|
|
||||||
|> render() =~ Routes.hub_path(conn, :edit, hub.id)
|
|
||||||
|
|
||||||
assert view
|
|
||||||
|> element("#hubs")
|
|
||||||
|> render() =~ hub.hub_name
|
|
||||||
|
|
||||||
assert Hubs.fetch_hub!(hub.id) == hub
|
assert Hubs.fetch_hub!(hub.id) == hub
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -177,4 +160,12 @@ defmodule LivebookWeb.Hub.NewLiveTest do
|
||||||
|
|
||||||
%{"data" => %{"app" => app}}
|
%{"data" => %{"app" => app}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp assert_hub(view, conn, hub) do
|
||||||
|
hubs_html = view |> element("#hubs") |> render()
|
||||||
|
|
||||||
|
assert hubs_html =~ hub.hub_emoji
|
||||||
|
assert hubs_html =~ Routes.hub_path(conn, :edit, hub.id)
|
||||||
|
assert hubs_html =~ hub.hub_name
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,7 +9,8 @@ defmodule LivebookWeb.SessionLive.SecretsComponentTest do
|
||||||
|
|
||||||
describe "enterprise" do
|
describe "enterprise" do
|
||||||
setup %{url: url, token: token} do
|
setup %{url: url, token: token} do
|
||||||
id = Livebook.Utils.random_id()
|
node = EnterpriseServer.get_node()
|
||||||
|
id = :erpc.call(node, Enterprise.Integration, :fetch_env!, [])
|
||||||
Livebook.Hubs.delete_hub("enterprise-#{id}")
|
Livebook.Hubs.delete_hub("enterprise-#{id}")
|
||||||
|
|
||||||
enterprise =
|
enterprise =
|
||||||
|
@ -21,8 +22,7 @@ defmodule LivebookWeb.SessionLive.SecretsComponentTest do
|
||||||
)
|
)
|
||||||
|
|
||||||
{:ok, session} = Sessions.create_session(notebook: Livebook.Notebook.new())
|
{:ok, session} = Sessions.create_session(notebook: Livebook.Notebook.new())
|
||||||
Livebook.Hubs.EnterpriseClient.subscribe()
|
Livebook.Hubs.subscribe(:secrets)
|
||||||
Livebook.Hubs.connect_hubs()
|
|
||||||
|
|
||||||
on_exit(fn ->
|
on_exit(fn ->
|
||||||
Session.close(session.pid)
|
Session.close(session.pid)
|
||||||
|
|
|
@ -17,7 +17,7 @@ defmodule Livebook.Factory do
|
||||||
%Livebook.Hubs.Fly{
|
%Livebook.Hubs.Fly{
|
||||||
id: "fly-foo-bar-baz",
|
id: "fly-foo-bar-baz",
|
||||||
hub_name: "My Personal Hub",
|
hub_name: "My Personal Hub",
|
||||||
hub_color: "#FF00FF",
|
hub_emoji: "🚀",
|
||||||
access_token: Livebook.Utils.random_cookie(),
|
access_token: Livebook.Utils.random_cookie(),
|
||||||
organization_id: Livebook.Utils.random_id(),
|
organization_id: Livebook.Utils.random_id(),
|
||||||
organization_type: "PERSONAL",
|
organization_type: "PERSONAL",
|
||||||
|
@ -36,7 +36,7 @@ defmodule Livebook.Factory do
|
||||||
%Livebook.Hubs.Enterprise{
|
%Livebook.Hubs.Enterprise{
|
||||||
id: "enterprise-#{id}",
|
id: "enterprise-#{id}",
|
||||||
hub_name: "Enterprise",
|
hub_name: "Enterprise",
|
||||||
hub_color: "#FF0000",
|
hub_emoji: "🏭",
|
||||||
external_id: id,
|
external_id: id,
|
||||||
token: Livebook.Utils.random_cookie(),
|
token: Livebook.Utils.random_cookie(),
|
||||||
url: "http://localhost"
|
url: "http://localhost"
|
||||||
|
|
Loading…
Add table
Reference in a new issue