Implement UI to edit existing funnels (#4369)

* Update tailwind config

Turns out `extra` wasn't scanned properly with
previous wildcards. I don't care any more, it's not slow.

* Add interface for updating funnels

* Extend ComboBox with the ability to preselect an initial value

* Implement editing funnels UI

* Update CHANGELOG

* s/add_funnel/setup_funnel

* modal width 2/5 => 2/3

* Let's not make the list disappear on modal pop-up

* Fix the damn modal width again

* Watch extra dir

* Format

* Remove commented code

The test is implemented elsewhere

* Track funnel modified to drop default selection

* Fix screen size adoption and format large numbers

* Preserve currency so that string casting includes it

* Format

* Fix ComboBox attribute for preselected option
This commit is contained in:
hq1 2024-07-17 22:04:36 +02:00 committed by GitHub
parent 1da1da9421
commit 1488683d26
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 349 additions and 109 deletions

View File

@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.
## Unreleased
### Added
- UI to edit funnels
- Add a search functionality in all Details views, except for Goal Conversions, Countries, Regions, Cities and Google Search terms
- Icons for browsers plausible/analytics#4239
- Automatic custom property selection in the dashboard Properties report

View File

@ -6,8 +6,7 @@ module.exports = {
"./js/**/*.js",
"../lib/*_web.ex",
"../lib/*_web/**/*.*ex",
"../extra/*_web.ex",
"../extra/*_web/**/*.*ex"
"../extra/**/*.*ex",
],
safelist: [
// PlausibleWeb.StatsView.stats_container_class/1 uses this class
@ -52,9 +51,9 @@ module.exports = {
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/aspect-ratio'),
plugin(({addVariant}) => addVariant("phx-no-feedback", [".phx-no-feedback&", ".phx-no-feedback &"])),
plugin(({addVariant}) => addVariant("phx-click-loading", [".phx-click-loading&", ".phx-click-loading &"])),
plugin(({addVariant}) => addVariant("phx-submit-loading", [".phx-submit-loading&", ".phx-submit-loading &"])),
plugin(({addVariant}) => addVariant("phx-change-loading", [".phx-change-loading&", ".phx-change-loading &"])),
plugin(({ addVariant }) => addVariant("phx-no-feedback", [".phx-no-feedback&", ".phx-no-feedback &"])),
plugin(({ addVariant }) => addVariant("phx-click-loading", [".phx-click-loading&", ".phx-click-loading &"])),
plugin(({ addVariant }) => addVariant("phx-submit-loading", [".phx-submit-loading&", ".phx-submit-loading &"])),
plugin(({ addVariant }) => addVariant("phx-change-loading", [".phx-change-loading&", ".phx-change-loading &"])),
]
}

View File

@ -15,6 +15,9 @@ config :plausible, PlausibleWeb.Endpoint,
]
],
live_reload: [
dirs: [
"extra"
],
patterns: [
~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},
~r"lib/plausible_web/(controllers|live|components|templates|views|plugs)/.*(ex|heex)$"

View File

@ -46,7 +46,8 @@ defmodule Plausible.Funnel do
has_many :steps, Step,
preload_order: [
asc: :step_order
]
],
on_replace: :delete
has_many :goals, through: [:steps, :goal]
timestamps()
@ -56,21 +57,19 @@ defmodule Plausible.Funnel do
funnel
|> cast(attrs, [:name])
|> validate_required([:name])
|> cast_assoc(:steps, with: &Step.changeset/2, required: true)
|> put_steps(attrs[:steps] || attrs["steps"])
|> validate_length(:steps, min: @min_steps, max: @max_steps)
|> put_step_orders()
|> unique_constraint(:name,
name: :funnels_name_site_id_index
)
end
def put_step_orders(changeset) do
if steps = Ecto.Changeset.get_change(changeset, :steps) do
steps
|> Enum.with_index(fn step, step_order ->
Ecto.Changeset.put_change(step, :step_order, step_order + 1)
end)
|> then(&Ecto.Changeset.put_change(changeset, :steps, &1))
end
def put_steps(changeset, steps) do
steps
|> Enum.map(&Step.changeset(%Step{}, &1))
|> Enum.with_index(fn step, step_order ->
Ecto.Changeset.put_change(step, :step_order, step_order + 1)
end)
|> then(&Ecto.Changeset.put_assoc(changeset, :steps, &1))
end
end

View File

@ -35,12 +35,35 @@ defmodule Plausible.Funnels do
{:error, :invalid_funnel_size}
end
@spec update(Funnel.t(), String.t(), [map()]) ::
{:ok, Funnel.t()}
| {:error, Ecto.Changeset.t() | :invalid_funnel_size | :upgrade_required}
def update(funnel, name, steps) do
site = Plausible.Repo.preload(funnel, site: :owner).site
case Plausible.Billing.Feature.Funnels.check_availability(site.owner) do
{:error, _} = error ->
error
:ok ->
funnel
|> Funnel.changeset(%{name: name, steps: steps})
|> Repo.update()
end
end
@spec create_changeset(Plausible.Site.t(), String.t(), [map()]) ::
Ecto.Changeset.t()
def create_changeset(site, name, steps) do
Funnel.changeset(%Funnel{site_id: site.id}, %{name: name, steps: steps})
end
@spec edit_changeset(Plausible.Funnel.t(), String.t(), [map()]) ::
Ecto.Changeset.t()
def edit_changeset(funnel, name, steps) do
Funnel.changeset(funnel, %{name: name, steps: steps})
end
@spec ephemeral_definition(Plausible.Site.t(), String.t(), [map()]) :: Funnel.t()
def ephemeral_definition(site, name, steps) do
site

View File

@ -30,9 +30,10 @@ defmodule PlausibleWeb.Live.FunnelSettings do
assign(socket,
domain: domain,
displayed_funnels: socket.assigns.all_funnels,
add_funnel?: false,
setup_funnel?: false,
filter_text: "",
current_user_id: user_id
current_user_id: user_id,
funnel_id: nil
)}
end
@ -42,36 +43,37 @@ defmodule PlausibleWeb.Live.FunnelSettings do
~H"""
<div id="funnel-settings-main">
<.flash_messages flash={@flash} />
<%= if @add_funnel? do %>
<%= if @setup_funnel? do %>
<%= live_render(
@socket,
PlausibleWeb.Live.FunnelSettings.Form,
id: "funnels-form",
session: %{
"current_user_id" => @current_user_id,
"domain" => @domain
"domain" => @domain,
"funnel_id" => @funnel_id
}
) %>
<% else %>
<div :if={@goal_count >= Funnel.min_steps()}>
<.live_component
module={PlausibleWeb.Live.FunnelSettings.List}
id="funnels-list"
funnels={@displayed_funnels}
filter_text={@filter_text}
/>
</div>
<div :if={@goal_count < Funnel.min_steps()}>
<PlausibleWeb.Components.Generic.notice class="mt-4" title="Not enough goals">
You need to define at least two goals to create a funnel. Go ahead and <%= link(
"add goals",
to: PlausibleWeb.Router.Helpers.site_path(@socket, :settings_goals, @domain),
class: "text-indigo-500 w-full text-center"
) %> to proceed.
</PlausibleWeb.Components.Generic.notice>
</div>
<% end %>
<div :if={@goal_count >= Funnel.min_steps()}>
<.live_component
module={PlausibleWeb.Live.FunnelSettings.List}
id="funnels-list"
funnels={@displayed_funnels}
filter_text={@filter_text}
/>
</div>
<div :if={@goal_count < Funnel.min_steps()}>
<PlausibleWeb.Components.Generic.notice class="mt-4" title="Not enough goals">
You need to define at least two goals to create a funnel. Go ahead and <%= link(
"add goals",
to: PlausibleWeb.Router.Helpers.site_path(@socket, :settings_goals, @domain),
class: "text-indigo-500 w-full text-center"
) %> to proceed.
</PlausibleWeb.Components.Generic.notice>
</div>
</div>
"""
end
@ -92,7 +94,11 @@ defmodule PlausibleWeb.Live.FunnelSettings do
end
def handle_event("add-funnel", _value, socket) do
{:noreply, assign(socket, add_funnel?: true)}
{:noreply, assign(socket, setup_funnel?: true)}
end
def handle_event("edit-funnel", %{"funnel-id" => id}, socket) do
{:noreply, assign(socket, setup_funnel?: true, funnel_id: String.to_integer(id))}
end
def handle_event("delete-funnel", %{"funnel-id" => id}, socket) do
@ -110,18 +116,21 @@ defmodule PlausibleWeb.Live.FunnelSettings do
)}
end
def handle_info({:funnel_saved, funnel}, socket) do
def handle_info({:funnel_saved, _funnel}, socket) do
socket = put_live_flash(socket, :success, "Funnel saved successfully")
funnels = Funnels.list(socket.assigns.site)
{:noreply,
assign(socket,
add_funnel?: false,
all_funnels: [funnel | socket.assigns.all_funnels],
displayed_funnels: [funnel | socket.assigns.displayed_funnels]
setup_funnel?: false,
all_funnels: funnels,
funnel_id: nil,
displayed_funnels: funnels
)}
end
def handle_info(:cancel_add_funnel, socket) do
{:noreply, assign(socket, add_funnel?: false)}
def handle_info(:cancel_setup_funnel, socket) do
{:noreply, assign(socket, setup_funnel?: false, funnel_id: nil)}
end
end

View File

@ -9,9 +9,9 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
use Plausible.Funnel
import PlausibleWeb.Live.Components.Form
alias Plausible.{Sites, Goals}
alias Plausible.{Sites, Goals, Funnels}
def mount(_params, %{"current_user_id" => user_id, "domain" => domain}, socket) do
def mount(_params, %{"current_user_id" => user_id, "domain" => domain} = session, socket) do
site = Sites.get_for_user!(user_id, domain, [:owner, :admin, :super_admin])
# We'll have the options trimmed to only the data we care about, to keep
@ -22,19 +22,16 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
site
|> Goals.for_site()
|> Enum.map(fn goal ->
{goal.id, struct!(Plausible.Goal, Map.take(goal, [:id, :event_name, :page_path]))}
{goal.id,
struct!(Plausible.Goal, Map.take(goal, [:id, :event_name, :page_path, :currency]))}
end)
{:ok,
assign(socket,
step_ids: Enum.to_list(1..Funnel.min_steps()),
form: to_form(Plausible.Funnels.create_changeset(site, "", [])),
goals: goals,
site: site,
selections_made: Map.new(),
evaluation_result: nil,
evaluation_at: System.monotonic_time()
)}
socket =
socket
|> assign(goals: goals, site: site, evaluation_result: nil)
|> prepare_socket(site, session["funnel_id"])
{:ok, socket}
end
def render(assigns) do
@ -46,7 +43,7 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
>
</div>
<div class="fixed inset-0 flex items-center justify-center mt-16 z-50 overlofw-y-auto overflow-x-hidden">
<div class="w-2/5 h-full">
<div class="md:w-2/3 max-w-lg h-full">
<div id="funnel-form">
<.form
:let={f}
@ -58,7 +55,9 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
onkeydown="return event.key != 'Enter';"
class="bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"
>
<h2 class="text-xl font-black dark:text-gray-100 mb-6">Add Funnel</h2>
<h2 class="text-xl font-black dark:text-gray-100 mb-6">
<%= if @funnel, do: "Edit", else: "Add" %> Funnel
</h2>
<label for={f[:name].name} class="block mb-3 font-medium dark:text-gray-100">
Funnel Name
@ -81,6 +80,7 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
<div :for={step_idx <- @step_ids} class="flex mb-3 mt-3">
<div class="w-2/5 flex-1">
<.live_component
selected={find_preselected(@funnel, @funnel_modified?, step_idx)}
submit_name={"funnel[steps][#{step_idx}][goal_id]"}
module={PlausibleWeb.Live.Components.ComboBox}
suggest_fun={&PlausibleWeb.Live.Components.ComboBox.StaticSearch.suggest/2}
@ -101,7 +101,7 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
/>
</div>
<div class="w-4/12 align-middle ml-4 text-gray-500 dark:text-gray-400">
<div class="w-4/12 mt-1 ml-4 text-gray-500 dark:text-gray-400">
<.evaluation
:if={@evaluation_result}
result={@evaluation_result}
@ -137,7 +137,7 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
length(@step_ids) > map_size(@selections_made)
}
>
Add Funnel
<span><%= if @funnel, do: "Update", else: "Add" %> Funnel </span>
</PlausibleWeb.Components.Generic.button>
</div>
</div>
@ -152,7 +152,7 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
def remove_step_button(assigns) do
~H"""
<div class="inline-flex items-center ml-2 mb-4 text-red-500">
<div class="inline-flex items-center ml-2 text-red-500">
<svg
id={"remove-step-#{@step_idx}"}
class="feather feather-sm cursor-pointer"
@ -196,11 +196,11 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
class="border-dotted border-b border-gray-400 "
tooltip="Sample calculation for last month"
>
Entering Visitors: <strong><%= @result.entering_visitors %></strong>
<span class="hidden md:inline">Entering Visitors: </span><strong><%= PlausibleWeb.StatsView.large_number_format(@result.entering_visitors) %></strong>
</span>
</span>
<span :if={step && @at > 0}>
Dropoff: <strong><%= Map.get(step, :dropoff_percentage) %>%</strong>
<span class="hidden md:inline">Dropoff: </span><strong><%= Map.get(step, :dropoff_percentage) %>%</strong>
</span>
</span>
"""
@ -209,6 +209,8 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
def handle_event("add-step", _value, socket) do
step_ids = socket.assigns.step_ids
socket = assign(socket, funnel_modified?: true)
if length(step_ids) < Funnel.max_steps() do
first_free_idx = find_sequence_break(step_ids)
new_ids = step_ids ++ [first_free_idx]
@ -222,9 +224,11 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
idx = String.to_integer(idx)
step_ids = List.delete(socket.assigns.step_ids, idx)
selections_made = drop_selection(socket.assigns.selections_made, idx)
send(self(), :evaluate_funnel)
{:noreply, assign(socket, step_ids: step_ids, selections_made: selections_made)}
{:noreply,
assign(socket, step_ids: step_ids, selections_made: selections_made, funnel_modified?: true)}
end
def handle_event("validate", %{"funnel" => params}, socket) do
@ -238,7 +242,7 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
changeset =
socket.assigns.site
|> Plausible.Funnels.create_changeset(
|> Funnels.create_changeset(
params["name"],
steps_from_assigns
)
@ -247,10 +251,23 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
{:noreply, assign(socket, form: to_form(changeset))}
end
def handle_event("save", %{"funnel" => params}, %{assigns: %{site: site}} = socket) do
def handle_event(
"save",
%{"funnel" => params},
%{assigns: %{site: site, funnel: funnel}} = socket
) do
steps = Enum.map(params["steps"], fn {_idx, payload} -> payload end)
case Plausible.Funnels.create(site, params["name"], steps) do
save_fn =
case funnel do
%Plausible.Funnel{} ->
fn -> Funnels.update(funnel, params["name"], steps) end
nil ->
fn -> Funnels.create(site, params["name"], steps) end
end
case save_fn.() do
{:ok, funnel} ->
send(
socket.parent_pid,
@ -268,7 +285,7 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
end
def handle_event("cancel-add-funnel", _value, socket) do
send(socket.parent_pid, :cancel_add_funnel)
send(socket.parent_pid, :cancel_setup_funnel)
{:noreply, socket}
end
@ -296,25 +313,19 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
%{
assigns: %{
site: site,
selections_made: selections_made,
evaluation_at: evaluation_at
selections_made: selections_made
}
} = socket
) do
with true <- seconds_since_evaluation(evaluation_at) >= 1,
{:ok, {definition, query}} <- build_ephemeral_funnel(site, selections_made),
with {:ok, {definition, query}} <- build_ephemeral_funnel(site, selections_made),
{:ok, funnel} <- Plausible.Stats.funnel(site, query, definition) do
assign(socket, evaluation_result: funnel, evaluation_at: System.monotonic_time())
assign(socket, evaluation_result: funnel)
else
_ ->
socket
end
end
defp seconds_since_evaluation(evaluation_at) do
System.convert_time_unit(System.monotonic_time() - evaluation_at, :native, :second)
end
defp build_ephemeral_funnel(site, selections_made) do
steps =
selections_made
@ -331,7 +342,7 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
end)
definition =
Plausible.Funnels.ephemeral_definition(
Funnels.ephemeral_definition(
site,
"Test funnel",
steps
@ -387,4 +398,54 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
send_update(PlausibleWeb.Live.Components.ComboBox, id: combo_box, suggestions: result)
result
end
defp find_preselected(%Funnel{} = funnel, false, idx) do
if step = Enum.at(funnel.steps, idx - 1) do
{step.goal.id, to_string(step.goal)}
end
end
defp find_preselected(_, _, _), do: nil
defp prepare_socket(socket, site, funnel_id) when is_integer(funnel_id) do
funnel = Funnels.get(site.id, funnel_id)
form =
funnel
|> Funnels.edit_changeset(
funnel.name,
Enum.map(funnel.steps, &%{goal_id: &1.goal.id})
)
|> to_form()
selections_made =
Enum.reduce(Enum.with_index(funnel.steps, 1), %{}, fn {step, idx}, acc ->
Map.put(acc, "step-#{idx}", step.goal)
end)
socket =
assign(
socket,
form: form,
funnel: funnel,
funnel_modified?: false,
selections_made: selections_made,
step_ids: Enum.to_list(1..Enum.count(funnel.steps))
)
evaluate_funnel(socket)
end
defp prepare_socket(socket, site, nil) do
form = to_form(Funnels.create_changeset(site, "", []))
assign(
socket,
form: form,
funnel: nil,
funnel_modified?: false,
selections_made: Map.new(),
step_ids: Enum.to_list(1..Funnel.min_steps())
)
end
end

View File

@ -54,30 +54,35 @@ defmodule PlausibleWeb.Live.FunnelSettings.List do
<%= funnel.steps_count %>-step funnel
</span>
</span>
<button
id={"delete-funnel-#{funnel.id}"}
phx-click="delete-funnel"
phx-value-funnel-id={funnel.id}
class="text-sm text-red-600"
data-confirm={"Are you sure you want to remove funnel '#{funnel.name}'? This will just affect the UI, all of your analytics data will stay intact."}
>
<svg
class="feather feather-sm"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
<div class="flex items-center gap-x-4">
<a href="#" phx-click="edit-funnel" phx-value-funnel-id={funnel.id}>
<Heroicons.pencil_square class="feather feather-sm text-indigo-800 hover:text-indigo-500 dark:text-indigo-500 dark:hover:text-indigo-300" />
</a>
<button
id={"delete-funnel-#{funnel.id}"}
phx-click="delete-funnel"
phx-value-funnel-id={funnel.id}
class="text-sm text-red-600"
data-confirm={"Are you sure you want to remove funnel '#{funnel.name}'? This will just affect the UI, all of your analytics data will stay intact."}
>
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2">
</path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
</button>
<svg
class="feather feather-sm"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2">
</path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
</button>
</div>
</div>
<% end %>
</div>

View File

@ -42,7 +42,10 @@ defmodule PlausibleWeb.Live.Components.ComboBox do
@default_suggestions_limit 15
def update(assigns, socket) do
socket = assign(socket, assigns)
socket =
socket
|> assign(assigns)
|> select_default()
socket =
if connected?(socket) do
@ -62,6 +65,7 @@ defmodule PlausibleWeb.Live.Components.ComboBox do
attr(:submit_name, :string, required: true)
attr(:display_value, :string, default: "")
attr(:submit_value, :string, default: "")
attr(:selected, :any)
attr(:suggest_fun, :any, required: true)
attr(:suggestions_limit, :integer)
attr(:class, :string, default: "")
@ -354,6 +358,16 @@ defmodule PlausibleWeb.Live.Components.ComboBox do
end
end
defp select_default(socket) do
case {socket.assigns[:selected], socket.assigns[:submit_value]} do
{{submit_value, display_value}, nil} ->
assign(socket, submit_value: submit_value, display_value: display_value)
_ ->
socket
end
end
defp run_suggest_fun(input, options, %{id: id, suggest_fun: fun} = assigns, key_to_update) do
if assigns[:async] do
pid = self()

View File

@ -55,6 +55,28 @@ defmodule Plausible.FunnelsTest do
assert fg3.step_order == 3
end
test "update funnel", %{site: site, steps: [g1, g2, g3, g4, g5 | _]} do
{:ok, funnel1} =
Funnels.create(
site,
"Sample funnel",
[g1, g2, g3]
)
{:ok, funnel2} = Funnels.update(funnel1, "Updated funnel", [g4, g5])
assert funnel2.name == "Updated funnel"
assert [fg1, fg2] = funnel2.steps
assert fg1.goal_id == g4["goal_id"]
assert fg2.goal_id == g5["goal_id"]
assert fg1.step_order == 1
assert fg2.step_order == 2
assert funnel1.id == funnel2.id
end
test "retrieve a funnel by id and site, get steps in order", %{
site: site,
steps: [g1, g2, g3 | _]

View File

@ -24,6 +24,18 @@ defmodule PlausibleWeb.Live.Components.ComboBoxTest do
end
end
test "renders preselected default value" do
options = new_options(10)
assert doc = render_sample_component(options, selected: List.last(options))
assert element_exists?(doc, "input[type=hidden][name=test-submit-name][value=10]")
assert element_exists?(
doc,
~s|input[type=text][name=display-test-component][value="TestOption 10"]|
)
end
test "renders up to 15 suggestions by default" do
assert doc = render_sample_component(new_options(20))

View File

@ -255,7 +255,101 @@ defmodule PlausibleWeb.Live.FunnelSettingsTest do
refute element_exists?(doc, ~s/form button#save:disabled/)
end
@tag :slow
test "save button saves a new funnel", %{
conn: conn,
site: site
} do
setup_goals(site)
lv = get_liveview(conn, site)
lv |> element(~s/button[phx-click="add-funnel"]/) |> render_click()
assert lv = find_live_child(lv, "funnels-form")
lv
|> element("li#dropdown-step-1-option-1 a")
|> render_click()
lv
|> element("li#dropdown-step-2-option-1 a")
|> render_click()
lv
|> element("form")
|> render_change(%{
funnel: %{
name: "My test funnel",
steps: [
%{goal_id: 1},
%{goal_id: 2}
]
}
})
lv
|> element(~s/form/)
|> render_submit()
assert %Plausible.Funnel{steps: [_, _]} = Plausible.Funnels.get(site, "My test funnel")
end
test "editing a funnel pre-renders it", %{
conn: conn,
site: site
} do
{:ok, [f1_id, _]} = setup_funnels(site)
lv = get_liveview(conn, site)
lv
|> element(~s/a[phx-click="edit-funnel"][phx-value-funnel-id=#{f1_id}]/)
|> render_click()
assert lv = find_live_child(lv, "funnels-form")
assert lv |> element("#step-1") |> render() |> text_of_attr("value") ==
"Visit /go/to/blog/**"
assert lv |> element("#step-2") |> render() |> text_of_attr("value") == "Signup"
end
test "clicking save after editing the funnel, updates it", %{
conn: conn,
site: site
} do
{:ok, [f1_id, _]} = setup_funnels(site)
{:ok, %{id: goal_id}} = Plausible.Goals.create(site, %{"page_path" => "/"})
lv = get_liveview(conn, site)
lv
|> element(~s/a[phx-click="edit-funnel"][phx-value-funnel-id=#{f1_id}]/)
|> render_click()
assert lv = find_live_child(lv, "funnels-form")
lv
|> element("li#dropdown-step-2-option-1 a")
|> render_click()
lv
|> element("form")
|> render_change(%{
funnel: %{
name: "Updated funnel",
steps: [
%{goal_id: 1},
%{goal_id: goal_id}
]
}
})
lv
|> element(~s/form/)
|> render_submit()
assert %Plausible.Funnel{steps: [_, %Plausible.Funnel.Step{goal_id: ^goal_id}]} =
Plausible.Funnels.get(site, "Updated funnel")
end
test "funnel gets evaluated on every select, assuming a second has passed between selections",
%{
conn: conn,
@ -269,8 +363,6 @@ defmodule PlausibleWeb.Live.FunnelSettingsTest do
lv |> element("li#dropdown-step-1-option-1 a") |> render_click()
:timer.sleep(1001)
lv |> element("li#dropdown-step-2-option-1 a") |> render_click()
doc = lv |> element("#step-eval-0") |> render()