Skip to content
139 changes: 125 additions & 14 deletions lib/plausible_web/live/goal_settings/form.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
|> to_form()

selected_tab =
if assigns.goal && assigns.goal.page_path do
"pageviews"
else
"custom_events"
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"
end

socket =
Expand Down Expand Up @@ -84,7 +84,13 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
f={f}
goal={@goal}
suffix={@context_unique_id}
current_user={@current_user}
site={@site}
/>
<.scroll_fields
:if={@selected_tab == "scroll"}
f={f}
goal={@goal}
suffix={@context_unique_id}
site={@site}
/>

Expand All @@ -108,7 +114,7 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do

<.title>Add Goal for {@domain}</.title>

<.tabs selected_tab={@selected_tab} myself={@myself} />
<.tabs current_user={@current_user} site={@site} selected_tab={@selected_tab} myself={@myself} />

<.custom_event_fields
:if={@selected_tab == "custom_events"}
Expand All @@ -128,7 +134,14 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
x-show="!tabSelectionInProgress"
f={f}
suffix={suffix(@context_unique_id, @tab_sequence_id)}
current_user={@current_user}
site={@site}
x-init="tabSelectionInProgress = false"
/>
<.scroll_fields
:if={@selected_tab == "scroll"}
x-show="!tabSelectionInProgress"
f={f}
suffix={suffix(@context_unique_id, @tab_sequence_id)}
site={@site}
x-init="tabSelectionInProgress = false"
/>
Expand Down Expand Up @@ -158,7 +171,6 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
end

attr(:f, Phoenix.HTML.Form)
attr(:current_user, Plausible.Auth.User)
attr(:site, Plausible.Site)
attr(:suffix, :string)
attr(:goal, Plausible.Goal, default: nil)
Expand Down Expand Up @@ -196,16 +208,91 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
x-data="{ firstFocus: true }"
x-on:focus="if (firstFocus) { $el.select(); firstFocus = false; }"
/>
</div>
"""
end

attr(:f, Phoenix.HTML.Form)
attr(:site, Plausible.Site)
attr(:suffix, :string)
attr(:goal, Plausible.Goal, default: nil)
attr(:rest, :global)

def scroll_fields(assigns) do
js =
if is_nil(assigns.goal) do
"""
{
scrollThreshold: '90',
pagePath: '',
displayName: '',
updateDisplayName() {
if (this.scrollThreshold && this.pagePath) {
this.displayName = `Scroll ${this.scrollThreshold}% on ${this.pagePath}`
}
}
}
"""
else
"""
{
scrollThreshold: '#{assigns.goal.scroll_threshold}',
pagePath: '#{assigns.goal.page_path}',
displayName: '#{assigns.goal.display_name}',
updateDisplayName() {}
}
"""
end

assigns = assign(assigns, :js, js)

~H"""
<div id="scroll-form" class="py-2" x-data={@js} {@rest}>
<.label for={"scroll_threshold_input_#{@suffix}"}>
Scroll Percentage Threshold (0-100)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like there could be a useful tooltip we could add here with context what this means.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think rather than a tooltip, an info paragraph (like in the custom events tab) would perhaps be better? Anyway, not sure if we need extra info there? (cc @metmarkosaric)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's pretty clear due to the headings but it doesn't hurt to add an info paragraph. how about:

Scroll Depth goals allow you to see how many people scroll beyond your desired scroll depth percentage threshold. Learn more in our docs.

maybe then it also makes sense to add an info paragraph to Pageview goals as well?

Pageview goals allow you to measure how many people visit a specific page or section of your site. Learn more in our docs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense @metmarkosaric. I'll do that as part of the documentation task: https://3.basecamp.com/5308029/buckets/39034214/card_tables/cards/8195964494

</.label>

<.input
:if={Plausible.Stats.ScrollDepth.feature_visible?(@site, @current_user)}
label="Scroll Depth Threshold (optional)"
id={"scroll_threshold_input_#{@suffix}"}
required
field={@f[:scroll_threshold]}
type="number"
value={if @goal && @goal.scroll_threshold > -1, do: @goal.scroll_threshold, else: nil}
min="0"
max="100"
step="1"
x-model="scrollThreshold"
x-on:change="updateDisplayName"
/>

<.label for={"scroll_page_path_input_#{@suffix}"} class="mt-3">
Page Path
</.label>

<.live_component
id={"scroll_page_path_input_#{@suffix}"}
submit_name="goal[page_path]"
class={[
"py-2"
]}
module={ComboBox}
suggest_fun={fn input, _options -> suggest_page_paths(input, @site) end}
selected={if @goal && @goal.page_path, do: @goal.page_path}
creatable
x-on-selection-change="pagePath = $event.detail.value.displayValue; updateDisplayName()"
/>

<.error :for={msg <- Enum.map(@f[:page_path].errors, &translate_error/1)}>
{msg}
</.error>

<.input
label="Display Name"
id="scroll_display_name_input"
field={@f[:display_name]}
type="text"
x-model="displayName"
x-data="{ firstFocus: true }"
x-on:focus="if (firstFocus) { $el.select(); firstFocus = false; }"
/>
</div>
"""
Expand Down Expand Up @@ -372,9 +459,14 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
def tabs(assigns) do
~H"""
<div class="text-sm mt-6 font-medium dark:text-gray-100">Goal Trigger</div>
<div class="my-2 text-sm w-full flex rounded border border-gray-300 dark:border-gray-500">
<div class="my-2 text-sm w-full flex rounded border border-gray-300 dark:border-gray-500 overflow-hidden">
<.custom_events_tab selected?={@selected_tab == "custom_events"} myself={@myself} />
<.pageviews_tab selected?={@selected_tab == "pageviews"} myself={@myself} />
<.scroll_tab
:if={Plausible.Stats.ScrollDepth.feature_visible?(@site, @current_user)}
selected?={@selected_tab == "scroll"}
myself={@myself}
/>
</div>
"""
end
Expand All @@ -383,7 +475,7 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
~H"""
<a
class={[
"w-1/2 text-center py-2.5 border-r dark:border-gray-500",
"flex-1 text-center py-2.5 border-r dark:border-gray-500",
"cursor-pointer",
@selected? && "shadow-inner font-medium bg-indigo-600 text-white",
!@selected? && "dark:text-gray-100 text-gray-800"
Expand All @@ -403,7 +495,7 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
~H"""
<a
class={[
"w-1/2 text-center py-2.5 cursor-pointer",
"flex-1 text-center py-2.5 cursor-pointer",
@selected? && "shadow-inner font-medium bg-indigo-600 text-white",
!@selected? && "dark:text-gray-100 text-gray-800"
]}
Expand All @@ -418,6 +510,25 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
"""
end

def scroll_tab(assigns) do
~H"""
<a
class={[
"flex-1 text-center py-2.5 cursor-pointer border-l dark:border-gray-500",
@selected? && "shadow-inner font-medium bg-indigo-600 text-white",
!@selected? && "dark:text-gray-100 text-gray-800"
]}
id="scroll-tab"
x-on:click={!@selected? && "tabSelectionInProgress = true"}
phx-click="switch-tab"
phx-value-tab="scroll"
phx-target={@myself}
>
Scroll
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this should say "Scroll depth" instead. I can see myself getting confused as a user.

Copy link
Contributor Author

@RobertJoonas RobertJoonas Feb 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the suggestion is to rename "Scroll" to "Scroll depth" here:

image

cc @metmarkosaric thoughts?

Copy link
Contributor

@metmarkosaric metmarkosaric Feb 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Scroll Depth" is fine. does it fit on mobile screens etc?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good!

does it fit on mobile screens etc?

Even on the smallest screens there's no overflow and it looks pretty okay (on most newer phones though, even "Custom Event" won't split onto two lines)

image

</a>
"""
end

def handle_event("switch-tab", %{"tab" => tab}, socket) do
socket =
socket
Expand Down
13 changes: 7 additions & 6 deletions lib/plausible_web/live/goal_settings/list.ex
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,13 @@ defmodule PlausibleWeb.Live.GoalSettings.List do
</div>
</.td>
<.td hide_on_mobile height="h-16">
<span :if={goal.page_path}>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-600"
><br />Belongs to funnel(s)</span>
<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-600">
<br />Belongs to funnel(s)
</span>
</.td>
<.td actions height="h-16">
<.edit_button
Expand Down
39 changes: 19 additions & 20 deletions test/plausible_web/live/goal_settings/form_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,25 @@ defmodule PlausibleWeb.Live.GoalSettings.FormTest do
event_tab = lv |> element(~s/a#event-tab/) |> render_click()
assert event_tab =~ "Event Name"
end

test "can navigate to scroll tab if scroll_depth feature visible for site/user",
%{conn: conn, site: site} do
Plausible.Sites.set_scroll_depth_visible_at(site)

lv = get_liveview(conn, site)
lv |> element(~s/a#scroll-tab/) |> render_click()
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
end

test "does not render scroll tab if scroll_depth feature not visible for site/user",
%{conn: conn, site: site} do
html = get_liveview(conn, site) |> render()
refute element_exists?(html, ~s/a#scroll-tab/)
end
end

describe "Goal submission" do
Expand Down Expand Up @@ -88,26 +107,6 @@ defmodule PlausibleWeb.Live.GoalSettings.FormTest do
]
end

test "renders scroll_threshold input in pageview goal form if scroll_depth feature visible for site/user",
%{conn: conn, site: site} do
Plausible.Sites.set_scroll_depth_visible_at(site)

lv = get_liveview(conn, site)
lv |> element(~s/a#pageview-tab/) |> render_click()
html = render(lv)
input_names = html |> find("#pageviews-form input") |> Enum.map(&name_of/1)
assert "goal[scroll_threshold]" in input_names
end

test "does not render scroll_threshold input in pageview goal form if scroll_depth feature not visible for site/user",
%{conn: conn, site: site} do
lv = get_liveview(conn, site)
lv |> element(~s/a#pageview-tab/) |> render_click()
html = render(lv)
input_names = html |> find("#pageviews-form input") |> Enum.map(&name_of/1)
refute "goal[scroll_threshold]" in input_names
end

test "renders error on empty submission", %{conn: conn, site: site} do
lv = get_liveview(conn, site)
lv |> element("#goals-form-modalseq0 form") |> render_submit()
Expand Down
Loading