From f5dc0b8debe58163258c732f346a60b7dca98076 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sat, 18 Jul 2020 12:22:16 -0400 Subject: [PATCH] Add conditional stage live configuration --- .../configure_stage/conditional_stage_live.ex | 213 ++++++++++++++++++ .../conditional_stage_live.html.leex | 68 ++++++ lib/frenzy_web/live/edit_pipeline_live.ex | 33 +-- .../live/filter/filter_live.html.leex | 17 +- .../live/filter/filter_rule_live.html.leex | 58 ++--- 5 files changed, 337 insertions(+), 52 deletions(-) create mode 100644 lib/frenzy_web/live/configure_stage/conditional_stage_live.ex create mode 100644 lib/frenzy_web/live/configure_stage/conditional_stage_live.html.leex diff --git a/lib/frenzy_web/live/configure_stage/conditional_stage_live.ex b/lib/frenzy_web/live/configure_stage/conditional_stage_live.ex new file mode 100644 index 0000000..788db04 --- /dev/null +++ b/lib/frenzy_web/live/configure_stage/conditional_stage_live.ex @@ -0,0 +1,213 @@ +defmodule FrenzyWeb.ConfigureStage.ConditionalStageLive do + use FrenzyWeb, :live_component + alias Frenzy.{Keypath, JSONSchema} + + @stages FrenzyWeb.EditPipelineLive.stages() + + @impl true + def mount(socket) do + {:ok, assign(socket, stages: @stages, confirm_convert_to_rule: false)} + 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 + def handle_event("update_stage", %{"opts" => %{"stage" => ""}}, socket) do + {:noreply, socket} + end + + @impl true + def handle_event("update_stage", %{"opts" => %{"stage" => module}}, socket) do + default_options = apply(String.to_existing_atom("Elixir." <> module), :default_opts, []) + + new_opts = + socket.assigns.opts + |> Map.put("stage", module) + |> Map.put("opts", default_options) + + new_stage = Frenzy.Keypath.set(socket.assigns.stage, socket.assigns.keypath, new_opts) + + send(self(), {:update_stage, socket.assigns.index, new_stage}) + {:noreply, socket} + end + + @impl true + def handle_event("update_filter", %{"mode" => mode, "score" => score}, socket) do + new_opts = + socket.assigns.opts + |> Keypath.set(["condition", "mode"], mode) + |> Keypath.set(["condition", "score"], JSONSchema.coerce(:integer, score)) + + new_stage = Frenzy.Keypath.set(socket.assigns.stage, socket.assigns.keypath, new_opts) + + send(self(), {:update_stage, socket.assigns.index, new_stage}) + {:noreply, socket} + end + + @impl true + def handle_event("add_rule", _params, socket) do + new_rules = + socket.assigns.opts["condition"]["rules"] ++ + [%{"mode" => "text", "param" => "", "property" => "title", "weight" => 1}] + + new_stage = + Frenzy.Keypath.set( + socket.assigns.stage, + socket.assigns.keypath ++ ["condition", "rules"], + new_rules + ) + + send(self(), {:update_stage, socket.assigns.index, new_stage}) + {:noreply, socket} + end + + @impl true + def handle_event("delete_rule", %{"index" => index}, socket) do + index = String.to_integer(index) + + new_rules = + socket.assigns.opts + |> Frenzy.Keypath.get(["condition", "rules"]) + |> List.delete_at(index) + + new_stage = + Frenzy.Keypath.set( + socket.assigns.stage, + socket.assigns.keypath ++ ["condition", "rules"], + new_rules + ) + + send(self(), {:update_stage, socket.assigns.index, new_stage}) + {:noreply, socket} + end + + @impl true + def handle_event( + "update_rule", + %{ + "index" => "no_index", + "mode" => mode, + "param" => param, + "property" => property, + "weight" => weight + }, + socket + ) do + new_rule = + socket.assigns.opts["condition"] + |> Map.put("mode", mode) + |> Map.put("param", param) + |> Map.put("property", property) + |> Map.put("weight", JSONSchema.coerce(:integer, weight)) + + new_stage = + Frenzy.Keypath.set( + socket.assigns.stage, + socket.assigns.keypath ++ ["condition"], + new_rule + ) + + send(self(), {:update_stage, socket.assigns.index, new_stage}) + {:noreply, socket} + end + + @impl true + def handle_event( + "update_rule", + %{ + "index" => index, + "mode" => mode, + "param" => param, + "property" => property, + "weight" => weight + }, + socket + ) do + index = String.to_integer(index) + + new_rule = + Enum.at(socket.assigns.opts["condition"]["rules"], index) + |> Map.put("mode", mode) + |> Map.put("param", param) + |> Map.put("property", property) + |> Map.put("weight", JSONSchema.coerce(:integer, weight)) + + new_stage = + Frenzy.Keypath.set( + socket.assigns.stage, + socket.assigns.keypath ++ ["condition", "rules", index], + new_rule + ) + + send(self(), {:update_stage, socket.assigns.index, new_stage}) + {:noreply, socket} + end + + @impl true + def handle_event("convert_to_filter", _value, socket) do + rule = socket.assigns.opts["condition"] + filter = %{"mode" => "accept", "score" => 1, "rules" => [rule]} + + new_stage = + Keypath.set( + socket.assigns.stage, + socket.assigns.keypath ++ ["condition"], + filter + ) + + send(self(), {:update_stage, socket.assigns.index, new_stage}) + {:noreply, socket} + end + + @impl true + def handle_event("convert_to_rule", _value, socket) do + rules = socket.assigns.opts["condition"]["rules"] + + case length(rules) do + 0 -> + new_stage = + Keypath.set( + socket.assigns.stage, + socket.assigns.keypath ++ ["condition"], + %{"mode" => "text", "param" => "", "property" => "title", "weight" => 1} + ) + + send(self(), {:update_stage, socket.assigns.index, new_stage}) + {:noreply, socket} + + 1 -> + handle_event("confirm_convert_to_rule", nil, socket) + + _count -> + {:noreply, assign(socket, confirm_convert_to_rule: true)} + end + end + + @impl true + def handle_event("cancel_convert_to_rule", _value, socket) do + {:noreply, assign(socket, confirm_convert_to_rule: false)} + end + + @impl true + def handle_event("confirm_convert_to_rule", _value, socket) do + rule = socket.assigns.opts["condition"]["rules"] |> List.first() + + new_stage = + Keypath.set( + socket.assigns.stage, + socket.assigns.keypath ++ ["condition"], + rule + ) + + send(self(), {:update_stage, socket.assigns.index, new_stage}) + {:noreply, assign(socket, confirm_convert_to_rule: false)} + end + + def component_module(name) do + FrenzyWeb.EditPipelineLive.component_module(name) + end +end diff --git a/lib/frenzy_web/live/configure_stage/conditional_stage_live.html.leex b/lib/frenzy_web/live/configure_stage/conditional_stage_live.html.leex new file mode 100644 index 0000000..9d34c65 --- /dev/null +++ b/lib/frenzy_web/live/configure_stage/conditional_stage_live.html.leex @@ -0,0 +1,68 @@ +
+ <%= if Mix.env == :dev do %> +
<%= Jason.encode!(@opts, pretty: true) %>
+ <% end %> + + <%= f = form_for @opts, "#", [as: :opts, phx_change: :update_stage, phx_target: @myself] %> +
+ +
+ <%= select f, :stage, @stages, prompt: "Select a stage...", id: "#{@id}-stage", class: "custom-select" %> +
+
+ + +
+
+

<%= @opts["stage"] %>

+
+
+ <% component = component_module(@opts["stage"]) %> + <%= unless is_nil(component) do %> + <%= live_component(@socket, component, index: @index, id: "#{@id}-conditional", stage: @stage, keypath: @keypath ++ ["opts"]) %> + <% end %> +
+
+ + <%= if @opts["condition"]["mode"] in ["accept", "reject"] do %> +
+
+
+
+

Condition: Filter

+
+
+ +
+
+
+
+ <%= if @confirm_convert_to_rule do %> +
+

This will modify the conditional stage to only run when the first rule is met. Are you sure you want to proceed?

+ + +
+ <% end %> + + <%= live_component @socket, FrenzyWeb.FilterLive, id: "##{@id}-filter", parent_id: @id, filter: @opts["condition"] %> +
+
+ <% else %> +
+
+
+
+

Condition: Rule

+
+
+ +
+
+
+
+ <%= live_component @socket, FrenzyWeb.FilterRuleLive, id: "##{@id}-rule", parent_id: @id, rule: @opts["condition"], index: :no_index %> +
+
+ <% end %> +
diff --git a/lib/frenzy_web/live/edit_pipeline_live.ex b/lib/frenzy_web/live/edit_pipeline_live.ex index 7a3542f..e7ddcf1 100644 --- a/lib/frenzy_web/live/edit_pipeline_live.ex +++ b/lib/frenzy_web/live/edit_pipeline_live.ex @@ -84,23 +84,24 @@ defmodule FrenzyWeb.EditPipelineLive do {:noreply, assign(socket, pipeline: pipeline)} end + def component_module(module_name) do + case module_name do + "Frenzy.Pipeline.ScrapeStage" -> + FrenzyWeb.ConfigureStage.ScrapeStageLive + + "Frenzy.Pipeline.FilterStage" -> + FrenzyWeb.ConfigureStage.FilterStageLive + + "Frenzy.Pipeline.ConditionalStage" -> + FrenzyWeb.ConfigureStage.ConditionalStageLive + + _ -> + nil + end + end + def component_for(socket, %{"module_name" => module} = stage, index) do - component = - case module do - "Frenzy.Pipeline.ScrapeStage" -> - FrenzyWeb.ConfigureStage.ScrapeStageLive - - "Frenzy.Pipeline.FilterStage" -> - FrenzyWeb.ConfigureStage.FilterStageLive - - "Frenzy.Pipeline.ConditionalStage" -> - FrenzyWeb.ConfigureStage.ConditionalStageLive - - _ -> - nil - end - - case component do + case component_module(module) do nil -> nil diff --git a/lib/frenzy_web/live/filter/filter_live.html.leex b/lib/frenzy_web/live/filter/filter_live.html.leex index f6dd5d4..f78436c 100644 --- a/lib/frenzy_web/live/filter/filter_live.html.leex +++ b/lib/frenzy_web/live/filter/filter_live.html.leex @@ -14,7 +14,22 @@ <%= for {rule, index} <- Enum.with_index(@filter["rules"]) do %> - <%= live_component @socket, FrenzyWeb.FilterRuleLive, id: "#{@id}-rule-#{index}", parent_id: @parent_id, rule: rule, index: index %> +
+
+
+
+
Rule <%= index %>
+
+
+ +
+
+ +
+
+ <%= live_component @socket, FrenzyWeb.FilterRuleLive, id: "#{@id}-rule-#{index}", parent_id: @parent_id, rule: rule, index: index %> +
+
<% end %> <%= f = form_for :rule, "#", class: "mt-2", phx_submit: :add_rule, phx_target: "##{@parent_id}" %> <%= submit "Add Rule", class: "btn btn-primary" %> diff --git a/lib/frenzy_web/live/filter/filter_rule_live.html.leex b/lib/frenzy_web/live/filter/filter_rule_live.html.leex index 2f3a1f3..6827022 100644 --- a/lib/frenzy_web/live/filter/filter_rule_live.html.leex +++ b/lib/frenzy_web/live/filter/filter_rule_live.html.leex @@ -1,41 +1,29 @@ -
-
-
-
-
Rule <%= @index %>
-
-
- +
+ <%= f = form_for @rule, "#", [phx_change: :update_rule, phx_target: "##{@parent_id}"] %> + <%= hidden_input f, :index, value: @index %> +
+ +
+ <%= select f, :property, @properties, id: "#{@id}-property", class: "custom-select" %>
-
-
- <%= f = form_for @rule, "#", [phx_change: :update_rule, phx_target: "##{@parent_id}"] %> - <%= hidden_input f, :index, value: @index %> -
- -
- <%= select f, :property, @properties, id: "#{@id}-property", class: "custom-select" %> -
+
+ +
+ <%= select f, :mode, @modes, id: "#{@id}-mode", class: "custom-select" %>
-
- -
- <%= select f, :mode, @modes, id: "#{@id}-mode", class: "custom-select" %> -
+
+
+ +
+ <%= text_input f, :param, id: "#{@id}-param", placeholder: if(@rule["mode"] == "contains_string", do: "substring", else: "regex"), class: "form-control text-monospace" %>
-
- -
- <%= text_input f, :param, id: "#{@id}-param", placeholder: if(@rule["mode"] == "contains_string", do: "substring", else: "regex"), class: "form-control text-monospace" %> -
+
+
+ +
+ <%= number_input f, :weight, id: "#{@id}-weight", class: "form-control" %>
-
- -
- <%= number_input f, :weight, id: "#{@id}-weight", class: "form-control" %> -
-
- -
+
+