Skip to content

Commit

Permalink
Implement UI to edit existing funnels (plausible#4369)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
aerosol authored Jul 17, 2024
1 parent 1da1da9 commit 1488683
Show file tree
Hide file tree
Showing 12 changed files with 349 additions and 109 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 5 additions & 6 deletions assets/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 &"])),
]
}
3 changes: 3 additions & 0 deletions config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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)$"
Expand Down
21 changes: 10 additions & 11 deletions extra/lib/plausible/funnel.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
23 changes: 23 additions & 0 deletions extra/lib/plausible/funnels.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
69 changes: 39 additions & 30 deletions extra/lib/plausible_web/live/funnel_settings.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Loading

0 comments on commit 1488683

Please sign in to comment.