Update goal settings design (#5886)

* Update goal settings design

- Replace the `Add goal` button in goal settings with a dropdown button to directly select the goal type. This way, a modal opens with the correct form for the selected goal type. The tabs in the modal have been removed.
- Add a new `pill` component to show the goal type in the table in a more distinct way. The `settings_badge` component is replaced with the `pill` component. The `pill` component that was used in `plan_box.ex` is renamed to `highlight_pill`.
- Replaced `Belongs to funnel` text with a funnel icon in the goal settings list.
- Some small tweaks like increasing the search bar width, the padding of the table cells, and adding a header to the goal settings list.

* Update tests to use the new dropdown component instead of tabs

* Replace custom `pending invitation` pill with new pill component

* Temporary: bump prima to exercise prima dropdown LV re-render fix

* Temporary: Bump prima again

* Revert "Temporary: Bump prima again"

This reverts commit 024b34a6e9.

* Revert "Temporary: bump prima to exercise prima dropdown LV re-render fix"

This reverts commit a6eabb73d0.

* Update prima

* Replace `Add goal` button with dropdown button in goal settings empty state

* Update test to check both empty and non-empty states of the add goal dropdown

* Remove pb-14 from feature gate

---------

Co-authored-by: Adam Rutkowski <hq@mtod.org>
This commit is contained in:
Sanne de Vries 2025-11-24 12:30:55 +01:00 committed by GitHub
parent f2bc96debe
commit 2c00acc89b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 326 additions and 268 deletions

View File

@ -39,7 +39,7 @@ defmodule PlausibleWeb.Components.Billing.PlanBox do
]}>
{String.capitalize(to_string(@kind))}
</h3>
<.pill :if={@highlight} text={@highlight} />
<.highlight_pill :if={@highlight} text={@highlight} />
</div>
<div>
<div class={@price_container_class}>
@ -95,7 +95,7 @@ defmodule PlausibleWeb.Components.Billing.PlanBox do
"""
end
defp pill(assigns) do
defp highlight_pill(assigns) do
~H"""
<div class="flex items-center justify-between gap-x-4">
<p

View File

@ -522,7 +522,7 @@ defmodule PlausibleWeb.Components.Generic do
current_team={@current_team}
site={@site}
>
<div class="p-6 pb-14">
<div class="p-6">
{render_slot(@inner_block)}
</div>
</PlausibleWeb.Components.Billing.feature_gate>
@ -793,7 +793,7 @@ defmodule PlausibleWeb.Components.Generic do
<td
class={[
@height,
"text-sm px-6 py-3 first:pl-0 last:pr-0 whitespace-nowrap",
"text-sm px-6 py-4 first:pl-0 last:pr-0 whitespace-nowrap",
@truncate && "truncate",
@max_width,
@actions && "flex text-right justify-end",
@ -1027,10 +1027,15 @@ defmodule PlausibleWeb.Components.Generic do
def filter_bar(assigns) do
~H"""
<div class="flex items-center justify-between" x-data>
<div :if={@filtering_enabled?} class="relative rounded-md flex">
<form id="filter-form" phx-change="filter" phx-submit="filter" class="flex items-center">
<div class="text-gray-800 inline-flex items-center">
<div class="flex items-center justify-between gap-2" x-data>
<div :if={@filtering_enabled?} class="relative rounded-md flex flex-grow-1 w-full">
<form
id="filter-form"
phx-change="filter"
phx-submit="filter"
class="flex items-center w-full"
>
<div class="text-gray-800 inline-flex items-center w-full">
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<Heroicons.magnifying_glass class="feather mr-1 dark:text-gray-300" />
</div>
@ -1038,7 +1043,7 @@ defmodule PlausibleWeb.Components.Generic do
type="text"
name="filter-text"
id="filter-text"
class="w-full max-w-64 pl-8 text-sm dark:bg-gray-750 dark:text-gray-300 focus:ring-indigo-500 focus:border-indigo-500 block border-gray-300 dark:border-gray-750 rounded-md dark:placeholder:text-gray-400 focus:outline-none focus:ring-3 focus:ring-indigo-500/20 dark:focus:ring-indigo-500/25 focus:border-indigo-500"
class="w-full max-w-80 pl-8 text-sm dark:bg-gray-750 dark:text-gray-300 focus:ring-indigo-500 focus:border-indigo-500 block border-gray-300 dark:border-gray-750 rounded-md dark:placeholder:text-gray-400 focus:outline-none focus:ring-3 focus:ring-indigo-500/20 dark:focus:ring-indigo-500/25 focus:border-indigo-500"
placeholder="Press / to search"
x-ref="filter_text"
phx-debounce={200}
@ -1140,13 +1145,41 @@ defmodule PlausibleWeb.Components.Generic do
"""
end
def settings_badge(%{type: :new} = assigns) do
attr(:class, :string, default: "")
attr(:color, :atom, default: :gray, values: [:gray, :indigo, :yellow, :green])
attr(:rest, :global)
slot(:inner_block, required: true)
def pill(assigns) do
assigns = assign(assigns, :color_classes, get_pill_color_classes(assigns.color))
~H"""
<span class="inline-block ml-2 bg-indigo-100 text-indigo-600 text-xs font-semibold py-1 px-2 rounded-md">
NEW 🔥
<span
class={[
"inline-flex items-center text-xs font-medium py-1 px-2 rounded-md",
@color_classes,
@class
]}
{@rest}
>
{render_slot(@inner_block)}
</span>
"""
end
def settings_badge(assigns), do: ~H""
defp get_pill_color_classes(:gray) do
"bg-gray-100 text-gray-800 dark:bg-gray-750 dark:text-gray-200"
end
defp get_pill_color_classes(:indigo) do
"bg-indigo-100/60 text-indigo-600 dark:bg-indigo-900/50 dark:text-indigo-300"
end
defp get_pill_color_classes(:yellow) do
"bg-yellow-100/80 text-yellow-800 dark:bg-yellow-900/40 dark:text-yellow-300"
end
defp get_pill_color_classes(:green) do
"bg-green-100/70 text-green-800 dark:bg-green-900/40 dark:text-green-300"
end
end

View File

@ -140,7 +140,9 @@ defmodule PlausibleWeb.Components.Layout do
class="size-5 mr-2"
/>
{@text}
<PlausibleWeb.Components.Generic.settings_badge type={@badge} />
<PlausibleWeb.Components.Generic.pill :if={@badge == :new} color={:indigo} class="ml-2">
NEW 🔥
</PlausibleWeb.Components.Generic.pill>
<Heroicons.chevron_down
:if={is_nil(@value)}
class="h-3 w-3 ml-2 text-gray-400 dark:text-gray-500"

View File

@ -33,7 +33,7 @@ defmodule PlausibleWeb.Components.PrimaDropdown do
<Dropdown.dropdown_item
as={@as}
disabled={@disabled}
class="group/item z-50 flex items-center gap-x-2 min-w-max rounded-md px-4 py-2 text-gray-700 text-sm dark:text-gray-300 data-focus:bg-gray-100 dark:data-focus:bg-gray-700 data-focus:text-gray-900 dark:data-focus:text-gray-100"
class="group/item z-50 flex items-center gap-x-2 min-w-max w-full rounded-md pl-3 pr-5 py-2 text-gray-700 text-sm dark:text-gray-300 data-focus:bg-gray-100 dark:data-focus:bg-gray-700 data-focus:text-gray-900 dark:data-focus:text-gray-100"
{@rest}
>
{render_slot(@inner_block)}

View File

@ -43,7 +43,8 @@ defmodule PlausibleWeb.Live.GoalSettings do
domain: domain,
displayed_goals: socket.assigns.all_goals,
filter_text: "",
form_goal: nil
form_goal: nil,
goal_type: nil
)}
end
@ -88,6 +89,7 @@ defmodule PlausibleWeb.Live.GoalSettings do
site_team={@site_team}
existing_goals={@all_goals}
goal={@form_goal}
goal_type={@goal_type}
on_save_goal={
fn goal, socket ->
send(self(), {:goal_added, goal})
@ -137,8 +139,12 @@ defmodule PlausibleWeb.Live.GoalSettings do
{:noreply, socket}
end
def handle_event("add-goal", _, socket) do
socket = socket |> assign(form_goal: nil) |> Modal.open("goals-form-modal")
def handle_event("add-goal", %{"goal-type" => goal_type}, socket) do
socket =
socket
|> assign(form_goal: nil, goal_type: goal_type)
|> Modal.open("goals-form-modal")
{:noreply, socket}
end
@ -181,7 +187,8 @@ defmodule PlausibleWeb.Live.GoalSettings do
event_name_options:
Enum.reject(socket.assigns.event_name_options, &(&1 == goal.event_name)),
displayed_goals: all_goals,
form_goal: nil
form_goal: nil,
goal_type: nil
)
|> put_live_flash(:success, "Goal saved successfully")

View File

@ -19,11 +19,15 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
|> Plausible.Goal.changeset()
|> to_form()
selected_tab =
case assigns.goal do
%{page_path: p, scroll_threshold: s} when not is_nil(p) and s > -1 -> "scroll"
%{page_path: p} when not is_nil(p) -> "pageviews"
_goal_or_nil -> "custom_events"
form_type =
if assigns.goal do
case assigns.goal do
%{page_path: p, scroll_threshold: s} when not is_nil(p) and s > -1 -> "scroll"
%{page_path: p} when not is_nil(p) -> "pageviews"
_ -> "custom_events"
end
else
assigns[:goal_type] || "custom_events"
end
socket =
@ -37,14 +41,14 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
current_user: assigns.current_user,
site_team: assigns.site_team,
domain: assigns.domain,
selected_tab: selected_tab,
tab_sequence_id: 0,
form_type: form_type,
site: site,
has_access_to_revenue_goals?: has_access_to_revenue_goals?,
existing_goals: assigns.existing_goals,
on_save_goal: assigns.on_save_goal,
on_autoconfigure: assigns.on_autoconfigure,
goal: assigns.goal
goal: assigns.goal,
goal_type: assigns[:goal_type]
)
{:ok, socket}
@ -68,7 +72,7 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
<.title>Edit goal for {@domain}</.title>
<.custom_event_fields
:if={@selected_tab == "custom_events"}
:if={@form_type == "custom_events"}
f={f}
suffix={@context_unique_id}
current_user={@current_user}
@ -80,14 +84,14 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
has_access_to_revenue_goals?={@has_access_to_revenue_goals?}
/>
<.pageview_fields
:if={@selected_tab == "pageviews"}
:if={@form_type == "pageviews"}
f={f}
goal={@goal}
suffix={@context_unique_id}
site={@site}
/>
<.scroll_fields
:if={@selected_tab == "scroll"}
:if={@form_type == "scroll"}
f={f}
goal={@goal}
suffix={@context_unique_id}
@ -103,58 +107,41 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
def create_form(assigns) do
~H"""
<.form
:let={f}
x-data="{ tabSelectionInProgress: false }"
for={@form}
phx-submit="save-goal"
phx-target={@myself}
>
<.form :let={f} for={@form} phx-submit="save-goal" phx-target={@myself}>
<.title>
Add goal for {Plausible.Sites.display_name(@site)}
</.title>
<.tabs current_user={@current_user} site={@site} selected_tab={@selected_tab} myself={@myself} />
<.custom_event_fields
:if={@selected_tab == "custom_events"}
x-show="!tabSelectionInProgress"
:if={@form_type == "custom_events"}
f={f}
suffix={suffix(@context_unique_id, @tab_sequence_id)}
suffix={@context_unique_id}
current_user={@current_user}
site_team={@site_team}
site={@site}
existing_goals={@existing_goals}
goal_options={@event_name_options}
has_access_to_revenue_goals?={@has_access_to_revenue_goals?}
x-init="tabSelectionInProgress = false"
/>
<.pageview_fields
:if={@selected_tab == "pageviews"}
x-show="!tabSelectionInProgress"
:if={@form_type == "pageviews"}
f={f}
suffix={suffix(@context_unique_id, @tab_sequence_id)}
suffix={@context_unique_id}
site={@site}
x-init="tabSelectionInProgress = false"
/>
<.scroll_fields
:if={@selected_tab == "scroll"}
x-show="!tabSelectionInProgress"
:if={@form_type == "scroll"}
f={f}
suffix={suffix(@context_unique_id, @tab_sequence_id)}
suffix={@context_unique_id}
site={@site}
x-init="tabSelectionInProgress = false"
/>
<div x-show="!tabSelectionInProgress">
<.button type="submit" class="w-full">
Add goal
</.button>
</div>
<.button type="submit" class="w-full">
Add goal
</.button>
<button
:if={@selected_tab == "custom_events" && @event_name_options_count > 0}
x-show="!tabSelectionInProgress"
:if={@form_type == "custom_events" && @event_name_options_count > 0}
class="mt-4 text-sm hover:underline text-indigo-600 dark:text-indigo-400 text-left"
phx-click="autoconfigure"
phx-target={@myself}
@ -441,73 +428,6 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
"""
end
def tabs(assigns) do
~H"""
<div class="text-sm mt-6 font-medium dark:text-gray-100">Goal trigger</div>
<div class="my-2 p-1 text-sm w-full flex gap-1 overflow-hidden rounded-lg border border-gray-300 dark:border-gray-700">
<.tab
id="event-tab"
tab_value="custom_events"
selected?={@selected_tab == "custom_events"}
myself={@myself}
>
Custom event
</.tab>
<.tab
id="pageview-tab"
tab_value="pageviews"
selected?={@selected_tab == "pageviews"}
myself={@myself}
>
Pageview
</.tab>
<.tab
id="scroll-tab"
tab_value="scroll"
selected?={@selected_tab == "scroll"}
myself={@myself}
>
Scroll depth
</.tab>
</div>
"""
end
attr(:id, :string, required: true)
attr(:tab_value, :string, required: true)
attr(:selected?, :boolean, required: true)
attr(:myself, :any, required: true)
slot(:inner_block, required: true)
defp tab(assigns) do
~H"""
<a
class={[
"flex-1 text-center py-2 px-3 rounded-md font-medium hover:bg-gray-100 dark:hover:bg-gray-750 transition-colors duration-150",
"cursor-pointer",
@selected? && "bg-gray-150 dark:bg-gray-700 text-gray-800 dark:text-white",
!@selected? && "dark:text-gray-200 text-gray-600 hover:text-gray-800 dark:hover:text-white"
]}
id={@id}
x-on:click={!@selected? && "tabSelectionInProgress = true"}
phx-click="switch-tab"
phx-value-tab={@tab_value}
phx-target={@myself}
>
{render_slot(@inner_block)}
</a>
"""
end
def handle_event("switch-tab", %{"tab" => tab}, socket) do
socket =
socket
|> assign(:selected_tab, tab)
|> update(:tab_sequence_id, &(&1 + 1))
{:noreply, socket}
end
def handle_event("save-goal", %{"goal" => goal_params}, %{assigns: %{goal: nil}} = socket) do
case Plausible.Goals.create(socket.assigns.site, goal_params) do
{:ok, goal} ->
@ -572,10 +492,6 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
|> Enum.map(fn name -> {name, name} end)
end
defp suffix(context_unique_id, tab_sequence_id) do
"#{context_unique_id}-tabseq#{tab_sequence_id}"
end
on_ee do
defp currency_option(nil), do: nil

View File

@ -4,6 +4,7 @@ defmodule PlausibleWeb.Live.GoalSettings.List do
"""
use PlausibleWeb, :live_component
alias PlausibleWeb.Live.Components.Modal
alias PlausibleWeb.Components.PrimaDropdown
attr(:goals, :list, required: true)
attr(:domain, :string, required: true)
@ -19,25 +20,52 @@ defmodule PlausibleWeb.Live.GoalSettings.List do
|> assign(:searching?, String.trim(assigns.filter_text) != "")
~H"""
<div>
<div class="flex flex-col gap-4">
<%= if @searching? or Enum.count(@goals) > 0 do %>
<.filter_bar filter_text={@filter_text} placeholder="Search Goals">
<.button
id="add-goal-button"
phx-click="add-goal"
mt?={false}
x-data
x-on:click={Modal.JS.preopen("goals-form-modal")}
>
Add goal
</.button>
<PrimaDropdown.dropdown id="add-goal-dropdown">
<PrimaDropdown.dropdown_trigger as={&button/1} mt?={false}>
Add goal <Heroicons.chevron_down mini class="size-4 mt-0.5" />
</PrimaDropdown.dropdown_trigger>
<PrimaDropdown.dropdown_menu>
<PrimaDropdown.dropdown_item
phx-click="add-goal"
phx-value-goal-type="pageviews"
x-data
x-on:click={Modal.JS.preopen("goals-form-modal")}
>
<Heroicons.plus class={PrimaDropdown.dropdown_item_icon_class()} /> Pageview
</PrimaDropdown.dropdown_item>
<PrimaDropdown.dropdown_item
phx-click="add-goal"
phx-value-goal-type="custom_events"
x-data
x-on:click={Modal.JS.preopen("goals-form-modal")}
>
<Heroicons.plus class={PrimaDropdown.dropdown_item_icon_class()} /> Custom event
</PrimaDropdown.dropdown_item>
<PrimaDropdown.dropdown_item
phx-click="add-goal"
phx-value-goal-type="scroll"
x-data
x-on:click={Modal.JS.preopen("goals-form-modal")}
>
<Heroicons.plus class={PrimaDropdown.dropdown_item_icon_class()} /> Scroll depth
</PrimaDropdown.dropdown_item>
</PrimaDropdown.dropdown_menu>
</PrimaDropdown.dropdown>
</.filter_bar>
<% end %>
<%= if Enum.count(@goals) > 0 do %>
<.table rows={@goals}>
<:thead>
<.th>Name</.th>
<.th hide_on_mobile>Type</.th>
</:thead>
<:tbody :let={goal}>
<.td max_width="max-w-40" height="h-16">
<.td max_width="max-w-64" height="h-16">
<%= if not @revenue_goals_enabled? && goal.currency do %>
<div class="truncate">{goal}</div>
<.tooltip>
@ -52,20 +80,27 @@ defmodule PlausibleWeb.Live.GoalSettings.List do
</span>
</.tooltip>
<% else %>
<div class="font-medium text-sm flex items-center gap-1.5">
<span class="truncate">{goal}</span>
<.tooltip :if={not Enum.empty?(goal.funnels)} centered?={true}>
<:tooltip_content>
Belongs to funnel
</:tooltip_content>
<Heroicons.funnel class="size-3.5 stroke-2 flex-shrink-0" />
</.tooltip>
</div>
<div class="truncate">
<.goal_description goal={goal} />
</div>
<div class="truncate">{goal}</div>
<% end %>
</.td>
<.td hide_on_mobile height="h-16">
<span :if={goal.page_path && goal.scroll_threshold > -1}>Scroll</span>
<span :if={goal.page_path && goal.scroll_threshold == -1}>Pageview</span>
<span :if={goal.event_name && !goal.currency}>Custom Event</span>
<span :if={goal.currency}>Revenue Goal ({goal.currency})</span>
<span :if={not Enum.empty?(goal.funnels)} class="text-gray-400 dark:text-gray-500">
<br />Belongs to funnel(s)
</span>
<.pill :if={goal.page_path && goal.scroll_threshold > -1} color={:green}>Scroll</.pill>
<.pill :if={goal.page_path && goal.scroll_threshold == -1} color={:gray}>
Pageview
</.pill>
<.pill :if={goal.event_name && !goal.currency} color={:yellow}>Custom Event</.pill>
<.pill :if={goal.currency} color={:indigo}>Revenue Goal ({goal.currency})</.pill>
</.td>
<.td actions height="h-16">
<.edit_button
@ -125,15 +160,38 @@ defmodule PlausibleWeb.Live.GoalSettings.List do
Learn more
</.styled_link>
</p>
<.button
id="add-goal-button"
phx-click="add-goal"
x-data
x-on:click={Modal.JS.preopen("goals-form-modal")}
class="mt-4"
>
Add goal
</.button>
<PrimaDropdown.dropdown id="add-goal-dropdown-empty" class="mt-4">
<PrimaDropdown.dropdown_trigger as={&button/1} mt?={false}>
Add goal <Heroicons.chevron_down mini class="size-4 mt-0.5" />
</PrimaDropdown.dropdown_trigger>
<PrimaDropdown.dropdown_menu>
<PrimaDropdown.dropdown_item
phx-click="add-goal"
phx-value-goal-type="pageviews"
x-data
x-on:click={Modal.JS.preopen("goals-form-modal")}
>
<Heroicons.plus class={PrimaDropdown.dropdown_item_icon_class()} /> Pageview
</PrimaDropdown.dropdown_item>
<PrimaDropdown.dropdown_item
phx-click="add-goal"
phx-value-goal-type="custom_events"
x-data
x-on:click={Modal.JS.preopen("goals-form-modal")}
>
<Heroicons.plus class={PrimaDropdown.dropdown_item_icon_class()} /> Custom event
</PrimaDropdown.dropdown_item>
<PrimaDropdown.dropdown_item
phx-click="add-goal"
phx-value-goal-type="scroll"
x-data
x-on:click={Modal.JS.preopen("goals-form-modal")}
>
<Heroicons.plus class={PrimaDropdown.dropdown_item_icon_class()} /> Scroll depth
</PrimaDropdown.dropdown_item>
</PrimaDropdown.dropdown_menu>
</PrimaDropdown.dropdown>
</div>
"""
end

View File

@ -98,52 +98,54 @@ defmodule PlausibleWeb.Live.SharedLinkSettings do
</.button>
</div>
<% else %>
<.filter_bar filtering_enabled?={false}>
<.button
id="add-shared-link-button"
phx-click="add-shared-link"
mt?={false}
x-data
x-on:click={Modal.JS.preopen("shared-links-form-modal")}
>
Add shared link
</.button>
</.filter_bar>
<div class="flex flex-col gap-4">
<.filter_bar filtering_enabled?={false}>
<.button
id="add-shared-link-button"
phx-click="add-shared-link"
mt?={false}
x-data
x-on:click={Modal.JS.preopen("shared-links-form-modal")}
>
Add shared link
</.button>
</.filter_bar>
<.table rows={@shared_links} id="shared-links-table">
<:thead>
<.th hide_on_mobile>Name</.th>
<.th>Link</.th>
<.th invisible>Actions</.th>
</:thead>
<:tbody :let={link}>
<.td truncate hide_on_mobile>
{link.name}
<Heroicons.lock_closed :if={link.password_hash} class="feather ml-2 mb-0.5" />
<Heroicons.lock_open :if={!link.password_hash} class="feather ml-2 mb-0.5" />
</.td>
<.td>
<.input_with_clipboard
name={link.slug}
id={link.slug}
value={Plausible.Sites.shared_link_url(@site, link)}
/>
</.td>
<.td actions>
<.edit_button
class="mt-1"
phx-click="edit-shared-link"
phx-value-slug={link.slug}
/>
<.delete_button
class="mt-1"
phx-click="delete-shared-link"
phx-value-slug={link.slug}
data-confirm="Are you sure you want to delete this shared link? The stats will not be accessible with this link anymore."
/>
</.td>
</:tbody>
</.table>
<.table rows={@shared_links} id="shared-links-table">
<:thead>
<.th hide_on_mobile>Name</.th>
<.th>Link</.th>
<.th invisible>Actions</.th>
</:thead>
<:tbody :let={link}>
<.td truncate hide_on_mobile>
{link.name}
<Heroicons.lock_closed :if={link.password_hash} class="feather ml-2 mb-0.5" />
<Heroicons.lock_open :if={!link.password_hash} class="feather ml-2 mb-0.5" />
</.td>
<.td>
<.input_with_clipboard
name={link.slug}
id={link.slug}
value={Plausible.Sites.shared_link_url(@site, link)}
/>
</.td>
<.td actions>
<.edit_button
class="mt-1"
phx-click="edit-shared-link"
phx-value-slug={link.slug}
/>
<.delete_button
class="mt-1"
phx-click="delete-shared-link"
phx-value-slug={link.slug}
data-confirm="Are you sure you want to delete this shared link? The stats will not be accessible with this link anymore."
/>
</.td>
</:tbody>
</.table>
</div>
<% end %>
</.tile>
</div>

View File

@ -508,9 +508,9 @@ defmodule PlausibleWeb.Live.Sites do
{@site.domain}
</h3>
</div>
<span class="inline-flex items-center -my-1 px-2 py-1 rounded-sm bg-green-100 text-green-800 text-xs font-medium leading-normal dark:bg-green-900/40 dark:text-green-400">
<.pill color={:green}>
Pending invitation
</span>
</.pill>
</div>
<.site_stats hourly_stats={@hourly_stats} />
</div>

View File

@ -131,11 +131,11 @@
"phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"},
"php_serializer": {:hex, :php_serializer, "2.0.0", "b43f31aca22ed7321f32da2b94fe2ddf9b6739a965cb51541969119e572e821d", [:mix], [], "hexpm", "61e402e99d9062c0225a3f4fcf7e43b4cba1b8654944c0e7c139c3ca9de481da"},
"plug": {:hex, :plug, "1.18.1", "5067f26f7745b7e31bc3368bc1a2b818b9779faa959b49c934c17730efc911cf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "57a57db70df2b422b564437d2d33cf8d33cd16339c1edb190cd11b1a3a546cc2"},
"plug_cowboy": {:hex, :plug_cowboy, "2.7.4", "729c752d17cf364e2b8da5bdb34fb5804f56251e88bb602aff48ae0bd8673d11", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9b85632bd7012615bae0a5d70084deb1b25d2bcbb32cab82d1e9a1e023168aa3"},
"plug_cowboy": {:hex, :plug_cowboy, "2.7.5", "261f21b67aea8162239b2d6d3b4c31efde4daa22a20d80b19c2c0f21b34b270e", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "20884bf58a90ff5a5663420f5d2c368e9e15ed1ad5e911daf0916ea3c57f77ac"},
"plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
"polymorphic_embed": {:hex, :polymorphic_embed, "5.0.3", "37444e0af941026a2c29b0539b6471bdd6737a6492a19264bf2bb0118e3ac242", [:mix], [{:attrs, "~> 0.6", [hex: :attrs, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_html_helpers, "~> 1.0", [hex: :phoenix_html_helpers, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 0.20 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}], "hexpm", "2fed44f57abf0a0fc7642e0eb0807a55b65de1562712cc0620772cbbb80e49c1"},
"postgrex": {:hex, :postgrex, "0.19.3", "a0bda6e3bc75ec07fca5b0a89bffd242ca209a4822a9533e7d3e84ee80707e19", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d31c28053655b78f47f948c85bb1cf86a9c1f8ead346ba1aa0d0df017fa05b61"},
"prima": {:hex, :prima, "0.1.8", "1f57fb7000046bb463b2a31200b138dd10c86fd78a289f1e947a970f740b68e0", [:mix], [{:esbuild, "~> 0.7", [hex: :esbuild, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.1", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "b559ee8213a8302fd40520cc55b0fcafbc884d471d198b262dd1e0a10170de17"},
"prima": {:hex, :prima, "0.1.9", "34868e3570cf2c4fac42d6254e33678e9d75a8fe3d3253a38ee437f6481a2896", [:mix], [{:esbuild, "~> 0.7", [hex: :esbuild, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.1", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "7f24efb617dd94da867bf2d362eeab846897270eff989aeaa261f04590feae3f"},
"prom_ex": {:hex, :prom_ex, "1.11.0", "1f6d67f2dead92224cb4f59beb3e4d319257c5728d9638b4a5e8ceb51a4f9c7e", [:mix], [{:absinthe, ">= 1.7.0", [hex: :absinthe, repo: "hexpm", optional: true]}, {:broadway, ">= 1.1.0", [hex: :broadway, repo: "hexpm", optional: true]}, {:ecto, ">= 3.11.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:finch, "~> 0.18", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, ">= 2.10.0", [hex: :oban, repo: "hexpm", optional: true]}, {:octo_fetch, "~> 0.4", [hex: :octo_fetch, repo: "hexpm", optional: false]}, {:peep, "~> 3.0", [hex: :peep, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.7.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_live_view, ">= 0.20.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}, {:plug, ">= 1.16.0", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 2.6.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, ">= 1.0.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.2", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}, {:telemetry_poller, "~> 1.1", [hex: :telemetry_poller, repo: "hexpm", optional: false]}], "hexpm", "76b074bc3730f0802978a7eb5c7091a65473eaaf07e99ec9e933138dcc327805"},
"public_suffix": {:git, "https://github.com/axelson/publicsuffix-elixir", "fa40c243d4b5d8598b90cff268bc4e33f3bb63f1", []},
"ranch": {:hex, :ranch, "1.8.1", "208169e65292ac5d333d6cdbad49388c1ae198136e4697ae2f474697140f201c", [:make, :rebar3], [], "hexpm", "aed58910f4e21deea992a67bf51632b6d60114895eb03bb392bb733064594dd0"},

View File

@ -12,32 +12,43 @@ defmodule PlausibleWeb.Live.GoalSettings.FormTest do
describe "integration - live rendering" do
setup [:create_user, :log_in, :create_site]
test "tabs switching", %{conn: conn, site: site} do
test "form renders with custom events when selected from dropdown", %{conn: conn, site: site} do
setup_goals(site)
lv = get_liveview(conn, site)
lv = get_liveview(conn, site) |> open_modal_with_goal_type("custom_events")
html = lv |> render()
html = render(lv)
assert html =~ "Add goal for #{site.domain}"
assert element_exists?(html, ~s/a#pageview-tab/)
assert element_exists?(html, ~s/a#event-tab/)
refute element_exists?(html, "#pageviews-form")
refute element_exists?(html, "#scroll-form")
assert element_exists?(html, "#custom-events-form")
pageview_tab = lv |> element(~s/a#pageview-tab/) |> render_click()
assert pageview_tab =~ "Page path"
event_tab = lv |> element(~s/a#event-tab/) |> render_click()
assert event_tab =~ "Event name"
assert html =~ "Event name"
end
test "can navigate to scroll tab if scroll_depth feature visible for site/user",
%{conn: conn, site: site} do
lv = get_liveview(conn, site)
lv |> element(~s/a#scroll-tab/) |> render_click()
test "form renders with pageview when selected from dropdown", %{conn: conn, site: site} do
lv = get_liveview(conn, site) |> open_modal_with_goal_type("pageviews")
html = render(lv)
input_names = html |> find("#scroll-form input") |> Enum.map(&name_of/1)
assert "goal[scroll_threshold]" in input_names
assert "goal[page_path]" in input_names
assert "goal[display_name]" in input_names
assert html =~ "Add goal for #{site.domain}"
refute element_exists?(html, "#custom-events-form")
refute element_exists?(html, "#scroll-form")
assert element_exists?(html, "#pageviews-form")
assert html =~ "Page path"
end
test "form renders with scroll when selected from dropdown", %{conn: conn, site: site} do
lv = get_liveview(conn, site) |> open_modal_with_goal_type("scroll")
html = render(lv)
assert html =~ "Add goal for #{site.domain}"
refute element_exists?(html, "#custom-events-form")
refute element_exists?(html, "#pageviews-form")
assert element_exists?(html, "#scroll-form")
assert html =~ "Scroll percentage threshold"
assert html =~ "Page path"
end
end
@ -45,8 +56,9 @@ defmodule PlausibleWeb.Live.GoalSettings.FormTest do
setup [:create_user, :log_in, :create_site]
@tag :ee_only
test "renders form fields per tab, with currency", %{conn: conn, site: site} do
lv = get_liveview(conn, site)
test "renders form fields for custom events with currency", %{conn: conn, site: site} do
lv = get_liveview(conn, site) |> open_modal_with_goal_type("custom_events")
html = render(lv)
refute element_exists?(html, "#pageviews-form")
@ -55,30 +67,35 @@ defmodule PlausibleWeb.Live.GoalSettings.FormTest do
assert input_names ==
[
"display-event_name_input_modalseq0-tabseq0",
"display-event_name_input_modalseq0",
"goal[event_name]",
"goal[display_name]",
"display-currency_input_modalseq0-tabseq0",
"display-currency_input_modalseq0",
"goal[currency]"
]
end
lv |> element(~s/a#pageview-tab/) |> render_click()
html = lv |> render()
@tag :ee_only
test "renders form fields for pageview", %{conn: conn, site: site} do
lv = get_liveview(conn, site) |> open_modal_with_goal_type("pageviews")
html = render(lv)
refute element_exists?(html, "#custom-events-form")
input_names = html |> find("#pageviews-form input") |> Enum.map(&name_of/1)
assert input_names == [
"display-page_path_input_modalseq0-tabseq1",
"display-page_path_input_modalseq0",
"goal[page_path]",
"goal[display_name]"
]
end
@tag :ce_build_only
test "renders form fields per tab (no currency)", %{conn: conn, site: site} do
lv = get_liveview(conn, site)
test "renders form fields for custom events (no currency)", %{conn: conn, site: site} do
lv = get_liveview(conn, site) |> open_modal_with_goal_type("custom_events")
html = render(lv)
refute element_exists?(html, "#pageviews-form")
@ -87,39 +104,34 @@ defmodule PlausibleWeb.Live.GoalSettings.FormTest do
assert input_names ==
[
"display-event_name_input_modalseq0-tabseq0",
"display-event_name_input_modalseq0",
"goal[event_name]",
"goal[display_name]"
]
lv |> element(~s/a#pageview-tab/) |> render_click()
html = lv |> render()
refute element_exists?(html, "#custom-events-form")
input_names = html |> find("#pageviews-form input") |> Enum.map(&name_of/1)
assert input_names == [
"display-page_path_input_modalseq0-tabseq1",
"goal[page_path]",
"goal[display_name]"
]
end
test "renders error on empty submission", %{conn: conn, site: site} do
lv = get_liveview(conn, site)
lv = get_liveview(conn, site) |> open_modal_with_goal_type("custom_events")
lv |> element("#goals-form-modalseq0 form") |> render_submit()
html = render(lv)
assert html =~ "this field is required and cannot be blank"
end
pageview_tab = lv |> element(~s/a#pageview-tab/) |> render_click()
assert pageview_tab =~ "this field is required and must start with a /"
test "renders error on empty pageview submission", %{conn: conn, site: site} do
lv = get_liveview(conn, site) |> open_modal_with_goal_type("pageviews")
lv |> element("#goals-form-modalseq0 form") |> render_submit()
html = render(lv)
assert html =~ "this field is required and must start with a /"
end
test "creates a custom event", %{conn: conn, site: site} do
lv = get_liveview(conn, site)
refute render(lv) =~ "SampleCustomEvent"
lv = open_modal_with_goal_type(lv, "custom_events")
lv
|> element("#goals-form-modalseq0 form")
|> render_submit(%{goal: %{event_name: "SampleCustomEvent"}})
@ -319,15 +331,15 @@ defmodule PlausibleWeb.Live.GoalSettings.FormTest do
@tag :ee_only
test "currency combo works", %{conn: conn, site: site} do
lv = get_liveview(conn, site)
lv = get_liveview(conn, site) |> open_modal_with_goal_type("custom_events")
type_into_combo(lv, "currency_input_modalseq0-tabseq0", "Polish")
type_into_combo(lv, "currency_input_modalseq0", "Polish")
html = render(lv)
assert element_exists?(html, ~s/a[phx-value-display-value="PLN - Polish Zloty"]/)
refute element_exists?(html, ~s/a[phx-value-display-value="EUR - Euro"]/)
type_into_combo(lv, "currency_input_modalseq0-tabseq0", "Euro")
type_into_combo(lv, "currency_input_modalseq0", "Euro")
html = render(lv)
refute element_exists?(html, ~s/a[phx-value-display-value="PLN - Polish Zloty"]/)
@ -335,10 +347,9 @@ defmodule PlausibleWeb.Live.GoalSettings.FormTest do
end
test "pageview combo works", %{conn: conn, site: site} do
lv = get_liveview(conn, site)
lv |> element(~s/a#pageview-tab/) |> render_click()
lv = get_liveview(conn, site) |> open_modal_with_goal_type("pageviews")
html = type_into_combo(lv, "page_path_input_modalseq0-tabseq1", "/hello")
html = type_into_combo(lv, "page_path_input_modalseq0", "/hello")
assert html =~ "Create &quot;/hello&quot;"
end
@ -349,17 +360,16 @@ defmodule PlausibleWeb.Live.GoalSettings.FormTest do
build(:pageview, pathname: "/go/home")
])
lv = get_liveview(conn, site)
lv |> element(~s/a#pageview-tab/) |> render_click()
lv = get_liveview(conn, site) |> open_modal_with_goal_type("pageviews")
type_into_combo(lv, "page_path_input_modalseq0-tabseq1", "/go/to/p")
type_into_combo(lv, "page_path_input_modalseq0", "/go/to/p")
html = render(lv)
assert html =~ "Create &quot;/go/to/p&quot;"
assert html =~ "/go/to/page/1"
refute html =~ "/go/home"
type_into_combo(lv, "page_path_input_modalseq0-tabseq1", "/go/h")
type_into_combo(lv, "page_path_input_modalseq0", "/go/h")
html = render(lv)
assert html =~ "/go/home"
refute html =~ "/go/to/page/1"
@ -373,17 +383,16 @@ defmodule PlausibleWeb.Live.GoalSettings.FormTest do
build(:imported_pages, page: "/go/home", pageviews: 1)
])
lv = get_liveview(conn, site)
lv |> element(~s/a#pageview-tab/) |> render_click()
lv = get_liveview(conn, site) |> open_modal_with_goal_type("pageviews")
type_into_combo(lv, "page_path_input_modalseq0-tabseq1", "/go/to/p")
type_into_combo(lv, "page_path_input_modalseq0", "/go/to/p")
html = render(lv)
assert html =~ "Create &quot;/go/to/p&quot;"
assert html =~ "/go/to/page/1"
refute html =~ "/go/home"
type_into_combo(lv, "page_path_input_modalseq0-tabseq1", "/go/h")
type_into_combo(lv, "page_path_input_modalseq0", "/go/h")
html = render(lv)
assert html =~ "/go/home"
refute html =~ "/go/to/page/1"
@ -396,9 +405,9 @@ defmodule PlausibleWeb.Live.GoalSettings.FormTest do
build(:event, name: "EventThree")
])
lv = get_liveview(conn, site)
lv = get_liveview(conn, site) |> open_modal_with_goal_type("custom_events")
type_into_combo(lv, "event_name_input_modalseq0-tabseq0", "One")
type_into_combo(lv, "event_name_input_modalseq0", "One")
html = render(lv)
assert text_of_element(html, "#goals-form-modalseq0") =~ "EventOne"
@ -451,4 +460,12 @@ defmodule PlausibleWeb.Live.GoalSettings.FormTest do
lv
end
defp open_modal_with_goal_type(lv, goal_type) do
lv
|> element(~s/[phx-click="add-goal"][phx-value-goal-type="#{goal_type}"]/)
|> render_click()
lv
end
end

View File

@ -89,10 +89,33 @@ defmodule PlausibleWeb.Live.GoalSettingsTest do
refute resp =~ "Create your first goal"
end
test "add goal button is rendered", %{conn: conn, site: site} do
test "add goal dropdown is rendered in empty state", %{conn: conn, site: site} do
conn = get(conn, "/#{site.domain}/settings/goals")
resp = html_response(conn, 200)
assert element_exists?(resp, ~s/button#add-goal-button[phx-click="add-goal"]/)
assert element_exists?(resp, ~s/[id="add-goal-dropdown-empty"]/)
assert element_exists?(
resp,
~s/[phx-click="add-goal"][phx-value-goal-type="custom_events"]/
)
assert element_exists?(resp, ~s/[phx-click="add-goal"][phx-value-goal-type="pageviews"]/)
assert element_exists?(resp, ~s/[phx-click="add-goal"][phx-value-goal-type="scroll"]/)
end
test "add goal dropdown is rendered in non-empty state", %{conn: conn, site: site} do
{:ok, _goals} = setup_goals(site)
conn = get(conn, "/#{site.domain}/settings/goals")
resp = html_response(conn, 200)
assert element_exists?(resp, ~s/[id="add-goal-dropdown"]/)
assert element_exists?(
resp,
~s/[phx-click="add-goal"][phx-value-goal-type="custom_events"]/
)
assert element_exists?(resp, ~s/[phx-click="add-goal"][phx-value-goal-type="pageviews"]/)
assert element_exists?(resp, ~s/[phx-click="add-goal"][phx-value-goal-type="scroll"]/)
end
test "search goals input is rendered", %{conn: conn, site: site} do