analytics/test/plausible_web/controllers/api/stats_controller/funnels_test.exs

315 lines
11 KiB
Elixir
Raw Normal View History

Implement Funnels view on dashboard (#3066) * Add Funnel react component assets/js/dashboard/stats/behaviours/funnel.js - restored from: 98a76cbd Remove console.info calls d94db99d Convert Funnel class component into a functional one 028036ad Review comments 3067a940 Stop doing maths in react 73407cc3 Fix error handling when local storage gets corrupted e8c6fc52 Format numbers on funnel labels c815709f Reorganize component responsibility 7a88fe44 Outline basic error handling 94caed7c Chart styling updates 4514608a Add percentages to funnel d622c32d Add funnel picker Co-authored-by: Uku Taht <uku.taht@gmail.com> * Pass funnels list to react via data-funnels * Implement Funnels react API lib/plausible_web/controllers/api/stats_controller.ex - restored from: f36ad234 Adjust to Plausible.Stats interface 9b532273 Test funnel stats controller 028036ad Review comments bea3725f Remove IO.inspect 7a88fe44 Outline basic error handling c8ae3eaf Move Funnels to StatsController and use base query 667cf222 Put private functions at the bottom * Tweak funnel presentation * Handle errors at the top * Do not register DataLabels plugin globally or else all the existing charts are affected * Calculate drop-off percentage evaluating funnels * Tweak dark mode + implement nicer tooltips * Make currently selected funnel bold in the picker * Count user_ids not session_ids when evaluating funnels So if a visitor goes: 1. Start session 2. Complete funnel step 1 3. Inactive for 30 minutes 4. Complete funnel step 2 We would not be able to track this funnel completion because of the session timeout. We like to o measure this as funnel completion even though the session expired in the middle. cc @ukutaht * Add extra properties to the funnels API cc @ukutaht * Improve tooltips so that step to data is rendered * Change tooltip number formatting * Remove debugging remnants * Quick & dirty mobile view * Fix mobile view: tweak dark mode & funnel switching * Ignore DOMException: the operation was aborted Otherwise this sometimes flashes the space shuttle screen when navigating quickly via a keyboard. * Format percentages on the main chart * Close missing tag 🙈 * Revert "Close missing tag 🙈" This reverts commit 9c2f970e22fd7e2980503242b414f42ce8bce1d2. * Use jsx to render funnel tooltip To get markup validated via lsp mostly... * Fixup: s/class/className * Fix className interpolation * Add a ruler to the tooltip * Tweak funnel chart style * Fix font distortion issue on chart/canvas labels * s/class/className * Put "Set up funnels" link behind a feature flag * Refactor internal selection storage Getting ready for live funnel evaluation * Don't try to connect LV socket if there's no CRSF token set up This is perfectly okay for some of the templates/layouts. * Fix up funnel creation typespecs Unfortunately we can't define a type with literal string keys, hence this must suffice. * Use uniq over count/distinct * Revert JSX in tooltips Ref: https://github.com/plausible/analytics/pull/3066#discussion_r1241891155 * Remove the extra query for counting all visitors cc @ukutaht * Add premium notice --------- Co-authored-by: Uku Taht <uku.taht@gmail.com>
2023-06-27 11:04:35 +03:00
defmodule PlausibleWeb.Api.StatsController.FunnelsTest do
use PlausibleWeb.ConnCase, async: true
use Plausible
@moduletag :full_build_only
on_full_build do
@user_id 123
@other_user_id 456
@build_funnel_with [
{"page_path", "/blog/announcement"},
{"event_name", "Signup"},
{"page_path", "/cart/add/product"},
{"event_name", "Purchase"}
]
describe "GET /api/stats/funnel - default" do
setup [:create_user, :log_in, :create_new_site]
test "computes funnel for a day", %{conn: conn, site: site} do
{:ok, funnel} = setup_funnel(site, @build_funnel_with)
populate_stats(site, [
build(:pageview, pathname: "/some/irrelevant", user_id: 9_999_999),
build(:pageview, pathname: "/blog/announcement", user_id: @user_id),
build(:pageview, pathname: "/blog/announcement", user_id: @other_user_id),
build(:event, name: "Signup", user_id: @user_id),
build(:event, name: "Signup", user_id: @other_user_id),
build(:pageview, pathname: "/cart/add/product", user_id: @user_id),
build(:event, name: "Purchase", user_id: @user_id)
])
resp =
conn
|> get("/api/stats/#{site.domain}/funnels/#{funnel.id}/?period=day")
|> json_response(200)
assert %{
"name" => "Test funnel",
"all_visitors" => 3,
"entering_visitors" => 2,
"entering_visitors_percentage" => "66.67",
"never_entering_visitors" => 1,
"never_entering_visitors_percentage" => "33.33",
"steps" => [
%{
"conversion_rate" => "100",
"conversion_rate_step" => "0",
"dropoff" => 0,
"dropoff_percentage" => "0",
"label" => "Visit /blog/announcement",
"visitors" => 2
},
%{
"conversion_rate" => "100",
"conversion_rate_step" => "100",
"dropoff" => 0,
"dropoff_percentage" => "0",
"label" => "Signup",
"visitors" => 2
},
%{
"conversion_rate" => "50",
"conversion_rate_step" => "50",
"dropoff" => 1,
"dropoff_percentage" => "50",
"label" => "Visit /cart/add/product",
"visitors" => 1
},
%{
"conversion_rate" => "50",
"conversion_rate_step" => "100",
"dropoff" => 0,
"dropoff_percentage" => "0",
"label" => "Purchase",
"visitors" => 1
}
]
} = resp
end
test "404 for unknown funnel", %{site: site, conn: conn} do
resp =
conn
|> get("/api/stats/#{site.domain}/funnels/122873/?period=day")
|> json_response(404)
assert resp == %{"error" => "Funnel not found"}
end
test "400 for bad funnel ID", %{site: site, conn: conn} do
resp =
conn
|> get("/api/stats/#{site.domain}/funnels/foobar/?period=day")
|> json_response(400)
assert resp == %{"error" => "There was an error with your request"}
end
test "computes all-time funnel with filters", %{conn: conn, user: user} do
site = insert(:site, stats_start_date: ~D[2020-01-01], members: [user])
{:ok, funnel} = setup_funnel(site, @build_funnel_with)
populate_stats(site, [
build(:pageview, pathname: "/blog/announcement", user_id: @user_id),
build(:pageview,
pathname: "/blog/announcement",
user_id: @other_user_id,
utm_medium: "social",
timestamp: ~N[2021-01-01 12:00:00]
),
build(:event, name: "Signup", user_id: @user_id),
build(:event,
name: "Signup",
user_id: @other_user_id,
utm_medium: "social",
timestamp: ~N[2021-01-01 12:01:00]
),
build(:pageview, pathname: "/cart/add/product", user_id: @user_id),
build(:event, name: "Purchase", user_id: @user_id)
])
filters = Jason.encode!(%{utm_medium: "social"})
resp =
conn
|> get("/api/stats/#{site.domain}/funnels/#{funnel.id}/?period=all&filters=#{filters}")
|> json_response(200)
assert %{
"name" => "Test funnel",
"all_visitors" => 1,
"entering_visitors" => 1,
"entering_visitors_percentage" => "100",
"never_entering_visitors" => 0,
"never_entering_visitors_percentage" => "0",
"steps" => [
%{
"conversion_rate" => "100",
"conversion_rate_step" => "0",
"dropoff" => 0,
"dropoff_percentage" => "0",
"label" => "Visit /blog/announcement",
"visitors" => 1
},
%{
"conversion_rate" => "100",
"conversion_rate_step" => "100",
"dropoff" => 0,
"dropoff_percentage" => "0",
"label" => "Signup",
"visitors" => 1
},
%{
"conversion_rate" => "0",
"conversion_rate_step" => "0",
"dropoff" => 1,
"dropoff_percentage" => "100",
"label" => "Visit /cart/add/product",
"visitors" => 0
},
%{
"conversion_rate" => "0",
"conversion_rate_step" => "0",
"dropoff" => 0,
"dropoff_percentage" => "0",
"label" => "Purchase",
"visitors" => 0
}
]
} = resp
end
test "computes an empty funnel", %{conn: conn, site: site} do
{:ok, funnel} = setup_funnel(site, @build_funnel_with)
resp =
conn
|> get("/api/stats/#{site.domain}/funnels/#{funnel.id}/?period=day")
|> json_response(200)
assert %{
"name" => "Test funnel",
"all_visitors" => 0,
"entering_visitors" => 0,
"entering_visitors_percentage" => "0",
"never_entering_visitors" => 0,
"never_entering_visitors_percentage" => "0",
"steps" => [
%{
"conversion_rate" => "0",
"conversion_rate_step" => "0",
"dropoff" => 0,
"dropoff_percentage" => "0",
"label" => "Visit /blog/announcement",
"visitors" => 0
},
%{
"conversion_rate" => "0",
"conversion_rate_step" => "0",
"dropoff" => 0,
"dropoff_percentage" => "0",
"label" => "Signup",
"visitors" => 0
},
%{
"conversion_rate" => "0",
"conversion_rate_step" => "0",
"dropoff" => 0,
"dropoff_percentage" => "0",
"label" => "Visit /cart/add/product",
"visitors" => 0
},
%{
"conversion_rate" => "0",
"conversion_rate_step" => "0",
"dropoff" => 0,
"dropoff_percentage" => "0",
"label" => "Purchase",
"visitors" => 0
}
]
} = resp
end
test "returns HTTP 402 when site owner is on a growth plan", %{
conn: conn,
user: user,
site: site
} do
insert(:growth_subscription, user: user)
{:ok, funnel} = setup_funnel(site, @build_funnel_with)
resp =
conn
|> get("/api/stats/#{site.domain}/funnels/#{funnel.id}/?period=day")
|> json_response(402)
assert %{
"error" =>
"Funnels is part of the Plausible Business plan. To get access to this feature, please upgrade your account."
} == resp
end
Implement Funnels view on dashboard (#3066) * Add Funnel react component assets/js/dashboard/stats/behaviours/funnel.js - restored from: 98a76cbd Remove console.info calls d94db99d Convert Funnel class component into a functional one 028036ad Review comments 3067a940 Stop doing maths in react 73407cc3 Fix error handling when local storage gets corrupted e8c6fc52 Format numbers on funnel labels c815709f Reorganize component responsibility 7a88fe44 Outline basic error handling 94caed7c Chart styling updates 4514608a Add percentages to funnel d622c32d Add funnel picker Co-authored-by: Uku Taht <uku.taht@gmail.com> * Pass funnels list to react via data-funnels * Implement Funnels react API lib/plausible_web/controllers/api/stats_controller.ex - restored from: f36ad234 Adjust to Plausible.Stats interface 9b532273 Test funnel stats controller 028036ad Review comments bea3725f Remove IO.inspect 7a88fe44 Outline basic error handling c8ae3eaf Move Funnels to StatsController and use base query 667cf222 Put private functions at the bottom * Tweak funnel presentation * Handle errors at the top * Do not register DataLabels plugin globally or else all the existing charts are affected * Calculate drop-off percentage evaluating funnels * Tweak dark mode + implement nicer tooltips * Make currently selected funnel bold in the picker * Count user_ids not session_ids when evaluating funnels So if a visitor goes: 1. Start session 2. Complete funnel step 1 3. Inactive for 30 minutes 4. Complete funnel step 2 We would not be able to track this funnel completion because of the session timeout. We like to o measure this as funnel completion even though the session expired in the middle. cc @ukutaht * Add extra properties to the funnels API cc @ukutaht * Improve tooltips so that step to data is rendered * Change tooltip number formatting * Remove debugging remnants * Quick & dirty mobile view * Fix mobile view: tweak dark mode & funnel switching * Ignore DOMException: the operation was aborted Otherwise this sometimes flashes the space shuttle screen when navigating quickly via a keyboard. * Format percentages on the main chart * Close missing tag 🙈 * Revert "Close missing tag 🙈" This reverts commit 9c2f970e22fd7e2980503242b414f42ce8bce1d2. * Use jsx to render funnel tooltip To get markup validated via lsp mostly... * Fixup: s/class/className * Fix className interpolation * Add a ruler to the tooltip * Tweak funnel chart style * Fix font distortion issue on chart/canvas labels * s/class/className * Put "Set up funnels" link behind a feature flag * Refactor internal selection storage Getting ready for live funnel evaluation * Don't try to connect LV socket if there's no CRSF token set up This is perfectly okay for some of the templates/layouts. * Fix up funnel creation typespecs Unfortunately we can't define a type with literal string keys, hence this must suffice. * Use uniq over count/distinct * Revert JSX in tooltips Ref: https://github.com/plausible/analytics/pull/3066#discussion_r1241891155 * Remove the extra query for counting all visitors cc @ukutaht * Add premium notice --------- Co-authored-by: Uku Taht <uku.taht@gmail.com>
2023-06-27 11:04:35 +03:00
end
describe "GET /api/stats/funnel - disallowed filters" do
setup [:create_user, :log_in, :create_new_site]
test "event:page", %{conn: conn, site: site} do
{:ok, funnel} = setup_funnel(site, @build_funnel_with)
filters = Jason.encode!(%{page: "/pageA"})
resp =
conn
|> get("/api/stats/#{site.domain}/funnels/#{funnel.id}/?period=day&filters=#{filters}")
|> json_response(400)
assert resp == %{
"error" =>
"We are unable to show funnels when the dashboard is filtered by pages",
"level" => "normal"
}
end
test "event:goal", %{conn: conn, site: site} do
{:ok, funnel} = setup_funnel(site, @build_funnel_with)
filters = Jason.encode!(%{goal: "Signup", page: "/pageA"})
resp =
conn
|> get("/api/stats/#{site.domain}/funnels/#{funnel.id}/?period=day&filters=#{filters}")
|> json_response(400)
assert resp == %{
"error" =>
"We are unable to show funnels when the dashboard is filtered by goals",
"level" => "normal"
}
end
test "period: realtime", %{conn: conn, site: site} do
{:ok, funnel} = setup_funnel(site, @build_funnel_with)
resp =
conn
|> get("/api/stats/#{site.domain}/funnels/#{funnel.id}/?period=realtime")
|> json_response(400)
assert resp == %{
"error" =>
"We are unable to show funnels when the dashboard is filtered by realtime period",
"level" => "normal"
}
end
Implement Funnels view on dashboard (#3066) * Add Funnel react component assets/js/dashboard/stats/behaviours/funnel.js - restored from: 98a76cbd Remove console.info calls d94db99d Convert Funnel class component into a functional one 028036ad Review comments 3067a940 Stop doing maths in react 73407cc3 Fix error handling when local storage gets corrupted e8c6fc52 Format numbers on funnel labels c815709f Reorganize component responsibility 7a88fe44 Outline basic error handling 94caed7c Chart styling updates 4514608a Add percentages to funnel d622c32d Add funnel picker Co-authored-by: Uku Taht <uku.taht@gmail.com> * Pass funnels list to react via data-funnels * Implement Funnels react API lib/plausible_web/controllers/api/stats_controller.ex - restored from: f36ad234 Adjust to Plausible.Stats interface 9b532273 Test funnel stats controller 028036ad Review comments bea3725f Remove IO.inspect 7a88fe44 Outline basic error handling c8ae3eaf Move Funnels to StatsController and use base query 667cf222 Put private functions at the bottom * Tweak funnel presentation * Handle errors at the top * Do not register DataLabels plugin globally or else all the existing charts are affected * Calculate drop-off percentage evaluating funnels * Tweak dark mode + implement nicer tooltips * Make currently selected funnel bold in the picker * Count user_ids not session_ids when evaluating funnels So if a visitor goes: 1. Start session 2. Complete funnel step 1 3. Inactive for 30 minutes 4. Complete funnel step 2 We would not be able to track this funnel completion because of the session timeout. We like to o measure this as funnel completion even though the session expired in the middle. cc @ukutaht * Add extra properties to the funnels API cc @ukutaht * Improve tooltips so that step to data is rendered * Change tooltip number formatting * Remove debugging remnants * Quick & dirty mobile view * Fix mobile view: tweak dark mode & funnel switching * Ignore DOMException: the operation was aborted Otherwise this sometimes flashes the space shuttle screen when navigating quickly via a keyboard. * Format percentages on the main chart * Close missing tag 🙈 * Revert "Close missing tag 🙈" This reverts commit 9c2f970e22fd7e2980503242b414f42ce8bce1d2. * Use jsx to render funnel tooltip To get markup validated via lsp mostly... * Fixup: s/class/className * Fix className interpolation * Add a ruler to the tooltip * Tweak funnel chart style * Fix font distortion issue on chart/canvas labels * s/class/className * Put "Set up funnels" link behind a feature flag * Refactor internal selection storage Getting ready for live funnel evaluation * Don't try to connect LV socket if there's no CRSF token set up This is perfectly okay for some of the templates/layouts. * Fix up funnel creation typespecs Unfortunately we can't define a type with literal string keys, hence this must suffice. * Use uniq over count/distinct * Revert JSX in tooltips Ref: https://github.com/plausible/analytics/pull/3066#discussion_r1241891155 * Remove the extra query for counting all visitors cc @ukutaht * Add premium notice --------- Co-authored-by: Uku Taht <uku.taht@gmail.com>
2023-06-27 11:04:35 +03:00
end
defp setup_goals(site, goals) when is_list(goals) do
goals =
Enum.map(goals, fn {type, value} ->
{:ok, g} = Plausible.Goals.create(site, %{type => value})
g
end)
{:ok, goals}
end
Implement Funnels view on dashboard (#3066) * Add Funnel react component assets/js/dashboard/stats/behaviours/funnel.js - restored from: 98a76cbd Remove console.info calls d94db99d Convert Funnel class component into a functional one 028036ad Review comments 3067a940 Stop doing maths in react 73407cc3 Fix error handling when local storage gets corrupted e8c6fc52 Format numbers on funnel labels c815709f Reorganize component responsibility 7a88fe44 Outline basic error handling 94caed7c Chart styling updates 4514608a Add percentages to funnel d622c32d Add funnel picker Co-authored-by: Uku Taht <uku.taht@gmail.com> * Pass funnels list to react via data-funnels * Implement Funnels react API lib/plausible_web/controllers/api/stats_controller.ex - restored from: f36ad234 Adjust to Plausible.Stats interface 9b532273 Test funnel stats controller 028036ad Review comments bea3725f Remove IO.inspect 7a88fe44 Outline basic error handling c8ae3eaf Move Funnels to StatsController and use base query 667cf222 Put private functions at the bottom * Tweak funnel presentation * Handle errors at the top * Do not register DataLabels plugin globally or else all the existing charts are affected * Calculate drop-off percentage evaluating funnels * Tweak dark mode + implement nicer tooltips * Make currently selected funnel bold in the picker * Count user_ids not session_ids when evaluating funnels So if a visitor goes: 1. Start session 2. Complete funnel step 1 3. Inactive for 30 minutes 4. Complete funnel step 2 We would not be able to track this funnel completion because of the session timeout. We like to o measure this as funnel completion even though the session expired in the middle. cc @ukutaht * Add extra properties to the funnels API cc @ukutaht * Improve tooltips so that step to data is rendered * Change tooltip number formatting * Remove debugging remnants * Quick & dirty mobile view * Fix mobile view: tweak dark mode & funnel switching * Ignore DOMException: the operation was aborted Otherwise this sometimes flashes the space shuttle screen when navigating quickly via a keyboard. * Format percentages on the main chart * Close missing tag 🙈 * Revert "Close missing tag 🙈" This reverts commit 9c2f970e22fd7e2980503242b414f42ce8bce1d2. * Use jsx to render funnel tooltip To get markup validated via lsp mostly... * Fixup: s/class/className * Fix className interpolation * Add a ruler to the tooltip * Tweak funnel chart style * Fix font distortion issue on chart/canvas labels * s/class/className * Put "Set up funnels" link behind a feature flag * Refactor internal selection storage Getting ready for live funnel evaluation * Don't try to connect LV socket if there's no CRSF token set up This is perfectly okay for some of the templates/layouts. * Fix up funnel creation typespecs Unfortunately we can't define a type with literal string keys, hence this must suffice. * Use uniq over count/distinct * Revert JSX in tooltips Ref: https://github.com/plausible/analytics/pull/3066#discussion_r1241891155 * Remove the extra query for counting all visitors cc @ukutaht * Add premium notice --------- Co-authored-by: Uku Taht <uku.taht@gmail.com>
2023-06-27 11:04:35 +03:00
defp setup_funnel(site, goal_names) do
{:ok, goals} = setup_goals(site, goal_names)
Plausible.Funnels.create(site, "Test funnel", Enum.map(goals, &%{"goal_id" => &1.id}))
Implement Funnels view on dashboard (#3066) * Add Funnel react component assets/js/dashboard/stats/behaviours/funnel.js - restored from: 98a76cbd Remove console.info calls d94db99d Convert Funnel class component into a functional one 028036ad Review comments 3067a940 Stop doing maths in react 73407cc3 Fix error handling when local storage gets corrupted e8c6fc52 Format numbers on funnel labels c815709f Reorganize component responsibility 7a88fe44 Outline basic error handling 94caed7c Chart styling updates 4514608a Add percentages to funnel d622c32d Add funnel picker Co-authored-by: Uku Taht <uku.taht@gmail.com> * Pass funnels list to react via data-funnels * Implement Funnels react API lib/plausible_web/controllers/api/stats_controller.ex - restored from: f36ad234 Adjust to Plausible.Stats interface 9b532273 Test funnel stats controller 028036ad Review comments bea3725f Remove IO.inspect 7a88fe44 Outline basic error handling c8ae3eaf Move Funnels to StatsController and use base query 667cf222 Put private functions at the bottom * Tweak funnel presentation * Handle errors at the top * Do not register DataLabels plugin globally or else all the existing charts are affected * Calculate drop-off percentage evaluating funnels * Tweak dark mode + implement nicer tooltips * Make currently selected funnel bold in the picker * Count user_ids not session_ids when evaluating funnels So if a visitor goes: 1. Start session 2. Complete funnel step 1 3. Inactive for 30 minutes 4. Complete funnel step 2 We would not be able to track this funnel completion because of the session timeout. We like to o measure this as funnel completion even though the session expired in the middle. cc @ukutaht * Add extra properties to the funnels API cc @ukutaht * Improve tooltips so that step to data is rendered * Change tooltip number formatting * Remove debugging remnants * Quick & dirty mobile view * Fix mobile view: tweak dark mode & funnel switching * Ignore DOMException: the operation was aborted Otherwise this sometimes flashes the space shuttle screen when navigating quickly via a keyboard. * Format percentages on the main chart * Close missing tag 🙈 * Revert "Close missing tag 🙈" This reverts commit 9c2f970e22fd7e2980503242b414f42ce8bce1d2. * Use jsx to render funnel tooltip To get markup validated via lsp mostly... * Fixup: s/class/className * Fix className interpolation * Add a ruler to the tooltip * Tweak funnel chart style * Fix font distortion issue on chart/canvas labels * s/class/className * Put "Set up funnels" link behind a feature flag * Refactor internal selection storage Getting ready for live funnel evaluation * Don't try to connect LV socket if there's no CRSF token set up This is perfectly okay for some of the templates/layouts. * Fix up funnel creation typespecs Unfortunately we can't define a type with literal string keys, hence this must suffice. * Use uniq over count/distinct * Revert JSX in tooltips Ref: https://github.com/plausible/analytics/pull/3066#discussion_r1241891155 * Remove the extra query for counting all visitors cc @ukutaht * Add premium notice --------- Co-authored-by: Uku Taht <uku.taht@gmail.com>
2023-06-27 11:04:35 +03:00
end
end
end