Add conditional stage live configuration

This commit is contained in:
Shadowfacts 2020-07-18 12:22:16 -04:00
parent daa0c51db5
commit f5dc0b8deb
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
5 changed files with 337 additions and 52 deletions

View File

@ -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

View File

@ -0,0 +1,68 @@
<div id="<%= @id %>">
<%= if Mix.env == :dev do %>
<pre><%= Jason.encode!(@opts, pretty: true) %></pre>
<% end %>
<%= f = form_for @opts, "#", [as: :opts, phx_change: :update_stage, phx_target: @myself] %>
<div class="form-group row">
<label class="col-sm-2 col-form-label" for="<%= @id %>-stage">Module</label>
<div class="col-sm-10">
<%= select f, :stage, @stages, prompt: "Select a stage...", id: "#{@id}-stage", class: "custom-select" %>
</div>
</div>
</form>
<div class="card mb-2">
<div class="card-header">
<h4 class="m-0"><%= @opts["stage"] %></h4>
</div>
<div class="card-body">
<% 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 %>
</div>
</div>
<%= if @opts["condition"]["mode"] in ["accept", "reject"] do %>
<div class="card">
<div class="card-header container-fluid">
<div class="row">
<div class="col">
<h4 class="m-0">Condition: Filter</h4>
</div>
<div class="col text-right">
<button phx-click="convert_to_rule" phx-target="#<%= @id %>" class="btn btn-primary btn-sm">Convert to Rule</button>
</div>
</div>
</div>
<div class="card-body">
<%= if @confirm_convert_to_rule do %>
<div class="alert alert-danger mb-2">
<p>This will modify the conditional stage to only run when the first rule is met. Are you sure you want to proceed?</p>
<button class="btn btn-danger btn-sm" phx-click="confirm_convert_to_rule" phx-target="#<%= @id %>">Convert to Rule</button>
<button class="btn btn-secondary btn-sm" phx-click="cancel_convert_to_rule" phx-target="#<%= @id %>">Cancel</button>
</div>
<% end %>
<%= live_component @socket, FrenzyWeb.FilterLive, id: "##{@id}-filter", parent_id: @id, filter: @opts["condition"] %>
</div>
</div>
<% else %>
<div class="card">
<div class="card-header container-fluid">
<div class="row">
<div class="col">
<h4 class="m-0">Condition: Rule</h4>
</div>
<div class="col text-right">
<button phx-click="convert_to_filter" phx-target="#<%= @id %>" class="btn btn-primary btn-sm">Convert to Filter</button>
</div>
</div>
</div>
<div class="card-body">
<%= live_component @socket, FrenzyWeb.FilterRuleLive, id: "##{@id}-rule", parent_id: @id, rule: @opts["condition"], index: :no_index %>
</div>
</div>
<% end %>
</div>

View File

@ -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

View File

@ -14,7 +14,22 @@
</div>
</form>
<%= 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 %>
<div class="card mt-2">
<div class="card-header container-fluid">
<div class="row">
<div class="col">
<h5 class="m-0">Rule <%= index %></h5>
</div>
<div class="col text-right">
<button phx-click="delete_rule" phx-value-index="<%= index %>" phx-target="#<%= @id %>">Delete</button>
</div>
</div>
</div>
<div class="card-body">
<%= live_component @socket, FrenzyWeb.FilterRuleLive, id: "#{@id}-rule-#{index}", parent_id: @parent_id, rule: rule, index: index %>
</div>
</div>
<% end %>
<%= f = form_for :rule, "#", class: "mt-2", phx_submit: :add_rule, phx_target: "##{@parent_id}" %>
<%= submit "Add Rule", class: "btn btn-primary" %>

View File

@ -1,41 +1,29 @@
<div id="<%= @id %>" class="card mt-2">
<div class="card-header container-fluid">
<div class="row">
<div class="col">
<h5 class="m-0">Rule <%= @index %></h5>
</div>
<div class="col text-right">
<button phx-click="delete_rule" phx-value-index="<%= @index %>" phx-target="#<%= @parent_id %>">Delete</button>
<div id="<%= @id %>">
<%= f = form_for @rule, "#", [phx_change: :update_rule, phx_target: "##{@parent_id}"] %>
<%= hidden_input f, :index, value: @index %>
<div class="form-group row mb-2">
<label class="col-sm-2 col-form-label" for="<%= @id %>-property">Item Property</label>
<div class="col-sm-10">
<%= select f, :property, @properties, id: "#{@id}-property", class: "custom-select" %>
</div>
</div>
</div>
<div class="card-body">
<%= f = form_for @rule, "#", [phx_change: :update_rule, phx_target: "##{@parent_id}"] %>
<%= hidden_input f, :index, value: @index %>
<div class="form-group row mb-2">
<label class="col-sm-2 col-form-label" for="<%= @id %>-property">Item Property</label>
<div class="col-sm-10">
<%= select f, :property, @properties, id: "#{@id}-property", class: "custom-select" %>
</div>
<div class="form-group row mb-2">
<label class="col-sm-2 col-form-label" for="<%= @id %>-mode">Mode</label>
<div class="col-sm-10">
<%= select f, :mode, @modes, id: "#{@id}-mode", class: "custom-select" %>
</div>
<div class="form-group row mb-2">
<label class="col-sm-2 col-form-label" for="<%= @id %>-mode">Mode</label>
<div class="col-sm-10">
<%= select f, :mode, @modes, id: "#{@id}-mode", class: "custom-select" %>
</div>
</div>
<div class="form-group row mb-2">
<label class="col-sm-2 col-form-label" for="<%= @id %>-param">Value</label>
<div class="col-sm-10">
<%= text_input f, :param, id: "#{@id}-param", placeholder: if(@rule["mode"] == "contains_string", do: "substring", else: "regex"), class: "form-control text-monospace" %>
</div>
<div class="form-group row mb-2">
<label class="col-sm-2 col-form-label" for="<%= @id %>-param">Value</label>
<div class="col-sm-10">
<%= text_input f, :param, id: "#{@id}-param", placeholder: if(@rule["mode"] == "contains_string", do: "substring", else: "regex"), class: "form-control text-monospace" %>
</div>
</div>
<div class="form-group row mb-0">
<label class="col-sm-2 col-form-label" for="<%= @id %>-weight">Rule Weight</label>
<div class="col-sm-10">
<%= number_input f, :weight, id: "#{@id}-weight", class: "form-control" %>
</div>
<div class="form-group row mb-0">
<label class="col-sm-2 col-form-label" for="<%= @id %>-weight">Rule Weight</label>
<div class="col-sm-10">
<%= number_input f, :weight, id: "#{@id}-weight", class: "form-control" %>
</div>
</div>
</form>
</div>
</div>
</form>
</div>