Use keypaths for stage option editing
This commit is contained in:
parent
5f81d8dfe4
commit
046c4d47a9
|
@ -0,0 +1,73 @@
|
||||||
|
defmodule Frenzy.Keypath do
|
||||||
|
@moduledoc """
|
||||||
|
Utilities for accessing or updating values using keypaths.
|
||||||
|
A keypath is a list of map keys or list indices representing the path through nested maps/lists.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@type container() :: map() | list()
|
||||||
|
@type t() :: [Map.key() | non_neg_integer()]
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets the value in the given container at the given keypath. Raises on index out of bounds/missing map key.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> Frenzy.Keypath.get(%{foo: %{bar: "baz"}}, [:foo, :bar])
|
||||||
|
"baz"
|
||||||
|
|
||||||
|
iex> Frenzy.Keypath.get([%{"foo" => "bar"}, %{"foo" => "baz"}], [1, "foo"])
|
||||||
|
"baz"
|
||||||
|
"""
|
||||||
|
@spec get(container(), t()) :: any()
|
||||||
|
|
||||||
|
def get(value, []), do: value
|
||||||
|
|
||||||
|
def get(map, [key | rest]) when is_map(map) do
|
||||||
|
get(Map.fetch!(map, key), rest)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(list, [index | rest]) when is_list(list) and is_integer(index) and index >= 0 do
|
||||||
|
if index >= length(list) do
|
||||||
|
raise KeyError, "Index #{index} out of bounds (>= #{length(list)})"
|
||||||
|
else
|
||||||
|
get(Enum.at(list, index), rest)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Sets the vlaue in the given container at the given keypath. Raises on index out of bounds/missing map key.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> Frenzy.Keypath.set(%{foo: %{bar: "baz"}}, [:foo, :bar], "blah")
|
||||||
|
%{foo: %{bar: "blah"}}
|
||||||
|
|
||||||
|
iex> Frenzy.Keypath.set([%{"foo" => "bar"}, %{"list" => ["a", "b"]}], [1, "list", 0], "c")
|
||||||
|
[%{"foo" => "bar"}, %{"list" => ["c", "b"]}]
|
||||||
|
"""
|
||||||
|
@spec set(container(), t(), any()) :: map()
|
||||||
|
|
||||||
|
def set(map, [key], value) when is_map(map) do
|
||||||
|
Map.put(map, key, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set(list, [index], value) when is_list(list) and is_integer(index) and index >= 0 do
|
||||||
|
if index >= length(list) do
|
||||||
|
raise KeyError, "Index #{index} out of bounds (>= #{length(list)})"
|
||||||
|
else
|
||||||
|
List.replace_at(list, index, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def set(map, [key | rest], value) when is_map(map) do
|
||||||
|
Map.put(map, key, set(Map.fetch!(map, key), rest, value))
|
||||||
|
end
|
||||||
|
|
||||||
|
def set(list, [index | rest], value) when is_list(list) and is_integer(index) and index >= 0 do
|
||||||
|
if index >= length(list) do
|
||||||
|
raise KeyError, "Index #{index} out of bounds (>= #{length(list)})"
|
||||||
|
else
|
||||||
|
List.replace_at(list, index, set(Enum.at(list, index), rest, value))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,5 @@
|
||||||
defmodule FrenzyWeb.ConfigureStage.ScrapeStageLive do
|
defmodule FrenzyWeb.ConfigureStage.ScrapeStageLive do
|
||||||
use FrenzyWeb, :live_component
|
use FrenzyWeb, :live_component
|
||||||
alias Frenzy.JSONSchema
|
|
||||||
|
|
||||||
@extractors [
|
@extractors [
|
||||||
{"Builtin", "builtin"},
|
{"Builtin", "builtin"},
|
||||||
|
@ -32,6 +31,12 @@ defmodule FrenzyWeb.ConfigureStage.ScrapeStageLive do
|
||||||
{:ok, assign(socket, extractors: @extractors)}
|
{:ok, assign(socket, extractors: @extractors)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def update(assigns, socket) do
|
||||||
|
assigns = Map.put(assigns, :opts, Frenzy.Keypath.get(assigns.stage, assigns.keypath))
|
||||||
|
{:ok, assign(socket, assigns)}
|
||||||
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event(
|
def handle_event(
|
||||||
"update_stage",
|
"update_stage",
|
||||||
|
@ -45,7 +50,9 @@ defmodule FrenzyWeb.ConfigureStage.ScrapeStageLive do
|
||||||
|> Map.put("convert_to_data_uris", convert_to_data_uris)
|
|> Map.put("convert_to_data_uris", convert_to_data_uris)
|
||||||
|> Map.put("extractor", extractor)
|
|> Map.put("extractor", extractor)
|
||||||
|
|
||||||
send(self(), {:update_stage_opts, socket.assigns.index, new_opts})
|
new_stage = Frenzy.Keypath.set(socket.assigns.stage, socket.assigns.keypath, new_opts)
|
||||||
|
|
||||||
|
send(self(), {:update_stage, socket.assigns.index, new_stage})
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<div id="<%= @id %>">
|
<div id="<%= @id %>">
|
||||||
|
<%= if Mix.env == :dev do %>
|
||||||
<pre><%= Jason.encode!(@opts, pretty: true) %></pre>
|
<pre><%= Jason.encode!(@opts, pretty: true) %></pre>
|
||||||
|
<% end %>
|
||||||
<%= f = form_for @opts, "#", [as: :opts, phx_change: :update_stage, phx_target: @myself] %>
|
<%= f = form_for @opts, "#", [as: :opts, phx_change: :update_stage, phx_target: @myself] %>
|
||||||
<div class="form-group form-check">
|
<div class="form-group form-check">
|
||||||
<%= checkbox f, :convert_to_data_uris, id: "#{@id}-convert_to_data_uris", class: "form-check-input" %>
|
<%= checkbox f, :convert_to_data_uris, id: "#{@id}-convert_to_data_uris", class: "form-check-input" %>
|
||||||
|
|
|
@ -3,6 +3,12 @@ defmodule FrenzyWeb.EditPipelineLive do
|
||||||
use Phoenix.HTML
|
use Phoenix.HTML
|
||||||
alias Frenzy.{Repo, Pipeline}
|
alias Frenzy.{Repo, Pipeline}
|
||||||
|
|
||||||
|
@stages [
|
||||||
|
{"Filter Stage", "Frenzy.Pipeline.FilterStage"},
|
||||||
|
{"Scrape Stage", "Frenzy.Pipeline.ScrapeStage"},
|
||||||
|
{"Conditional Stage", "Frenzy.Pipeline.ConditionalStage"}
|
||||||
|
]
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def mount(%{"id" => pipeline_id}, _session, socket) do
|
def mount(%{"id" => pipeline_id}, _session, socket) do
|
||||||
pipeline = Repo.get(Pipeline, pipeline_id)
|
pipeline = Repo.get(Pipeline, pipeline_id)
|
||||||
|
@ -10,11 +16,7 @@ defmodule FrenzyWeb.EditPipelineLive do
|
||||||
{:ok,
|
{:ok,
|
||||||
assign(socket,
|
assign(socket,
|
||||||
pipeline: pipeline,
|
pipeline: pipeline,
|
||||||
stages: [
|
stages: @stages
|
||||||
{"Filter Stage", "Frenzy.Pipeline.FilterStage"},
|
|
||||||
{"Scrape Stage", "Frenzy.Pipeline.ScrapeStage"},
|
|
||||||
{"Conditional Stage", "Frenzy.Pipeline.ConditionalStage"}
|
|
||||||
]
|
|
||||||
)}
|
)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -72,23 +74,26 @@ defmodule FrenzyWeb.EditPipelineLive do
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_info({:update_stage_opts, index, new_opts}, socket) do
|
def handle_info({:update_stage, index, new_stage}, socket) do
|
||||||
pipeline = socket.assigns.pipeline
|
pipeline = socket.assigns.pipeline
|
||||||
stages = pipeline.stages
|
# stages = pipeline.stages
|
||||||
stage = Enum.at(stages, index)
|
# stage = Enum.at(stages, index)
|
||||||
new_stage = Map.put(stage, "options", new_opts)
|
# new_stage = Map.put(stage, "options", new_opts)
|
||||||
new_stages = List.replace_at(stages, index, new_stage)
|
new_stages = List.replace_at(pipeline.stages, index, new_stage)
|
||||||
changeset = Pipeline.changeset(pipeline, %{stages: new_stages})
|
changeset = Pipeline.changeset(pipeline, %{stages: new_stages})
|
||||||
{:ok, pipeline} = Repo.update(changeset)
|
{:ok, pipeline} = Repo.update(changeset)
|
||||||
{:noreply, assign(socket, pipeline: pipeline)}
|
{:noreply, assign(socket, pipeline: pipeline)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def component_for(socket, %{"module_name" => module, "options" => opts}, index) do
|
def component_for(socket, %{"module_name" => module} = stage, index) do
|
||||||
component =
|
component =
|
||||||
case module do
|
case module do
|
||||||
"Frenzy.Pipeline.ScrapeStage" ->
|
"Frenzy.Pipeline.ScrapeStage" ->
|
||||||
FrenzyWeb.ConfigureStage.ScrapeStageLive
|
FrenzyWeb.ConfigureStage.ScrapeStageLive
|
||||||
|
|
||||||
|
"Frenzy.Pipeline.ConditionalStage" ->
|
||||||
|
FrenzyWeb.ConfigureStage.ConditionalStageLive
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
@ -98,7 +103,12 @@ defmodule FrenzyWeb.EditPipelineLive do
|
||||||
nil
|
nil
|
||||||
|
|
||||||
component ->
|
component ->
|
||||||
live_component(socket, component, index: index, id: "stage-#{index}", opts: opts)
|
live_component(socket, component,
|
||||||
|
index: index,
|
||||||
|
id: "stage-#{index}",
|
||||||
|
stage: stage,
|
||||||
|
keypath: ["options"]
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
defmodule Frenzy.KeypathTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
doctest Frenzy.Keypath
|
||||||
|
end
|
Loading…
Reference in New Issue