Skip to content

Commit b384251

Browse files
authored
Merge pull request #683 from code-corps/refactor-segment-tracking
Refactor segment tracking
2 parents a502f4d + 258e269 commit b384251

36 files changed

+445
-396
lines changed

lib/code_corps/analytics/segment.ex

Lines changed: 0 additions & 194 deletions
This file was deleted.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
defmodule CodeCorps.Analytics.SegmentDataExtractor do
2+
@moduledoc """
3+
Extract data for use in Segment tracking
4+
"""
5+
6+
@spec get_action(Plug.Conn.t) :: atom
7+
def get_action(%Plug.Conn{private: %{phoenix_action: action}}), do: action
8+
9+
@spec get_resource(Plug.Conn.t) :: struct
10+
def get_resource(%Plug.Conn{assigns: %{data: data}}), do: data
11+
# these are used for delete actions on records that support it
12+
# we render a 404 in those cases, so data is never assigned
13+
def get_resource(%Plug.Conn{assigns: %{user_category: data}}), do: data
14+
def get_resource(%Plug.Conn{assigns: %{user_role: data}}), do: data
15+
def get_resource(%Plug.Conn{assigns: %{user_skill: data}}), do: data
16+
def get_resource(%Plug.Conn{assigns: %{token: token, user_id: user_id}}) do
17+
%{token: token, user_id: user_id}
18+
end
19+
def get_resource(_), do: nil
20+
21+
@spec get_user_id(Plug.Conn.t, CodeCorps.User.t | struct | map) :: String.t
22+
def get_user_id(%Plug.Conn{assigns: %{current_user: %CodeCorps.User{id: id}}}, _), do: id
23+
def get_user_id(_, %CodeCorps.User{id: id}), do: id
24+
def get_user_id(_, %{user_id: user_id}), do: user_id
25+
end
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
defmodule CodeCorps.Analytics.SegmentEventNameBuilder do
2+
@moduledoc """
3+
Used for building friendly event names for use in Segment tracking
4+
"""
5+
6+
@spec build(atom, struct) :: String.t
7+
def build(action, record), do: get_event_name(action, record)
8+
9+
@actions_without_properties [:updated_profile, :signed_in, :signed_out, :signed_up]
10+
11+
defp get_event_name(action, _) when action in @actions_without_properties do
12+
friendly_action_name(action)
13+
end
14+
defp get_event_name(:update, %CodeCorps.DonationGoal{}) do
15+
"Updated Donation Goal"
16+
end
17+
defp get_event_name(:create, %CodeCorps.OrganizationMembership{}) do
18+
"Requested Organization Membership"
19+
end
20+
defp get_event_name(:update, %CodeCorps.OrganizationMembership{}) do
21+
"Approved Organization Membership"
22+
end
23+
defp get_event_name(:payment_succeeded, %CodeCorps.StripeInvoice{}) do
24+
"Processed Subscription Payment"
25+
end
26+
defp get_event_name(:create, %CodeCorps.User{}), do: "Signed Up"
27+
defp get_event_name(:update, %CodeCorps.User{}), do: "Updated Profile"
28+
defp get_event_name(:create, %CodeCorps.UserCategory{}), do: "Added User Category"
29+
defp get_event_name(:create, %CodeCorps.UserSkill{}), do: "Added User Skill"
30+
defp get_event_name(:create, %CodeCorps.UserRole{}), do: "Added User Role"
31+
defp get_event_name(:create, %{token: _, user_id: _}), do: "Signed In"
32+
defp get_event_name(action, model) do
33+
[friendly_action_name(action), friendly_model_name(model)] |> Enum.join(" ")
34+
end
35+
36+
defp friendly_action_name(:create), do: "Created"
37+
defp friendly_action_name(:delete), do: "Removed"
38+
defp friendly_action_name(:update), do: "Edited"
39+
defp friendly_action_name(action) do
40+
action
41+
|> Atom.to_string
42+
|> String.split("_")
43+
|> Enum.map(&String.capitalize/1)
44+
|> Enum.join(" ")
45+
end
46+
47+
defp friendly_model_name(model) do
48+
model.__struct__
49+
|> Module.split
50+
|> List.last
51+
|> Macro.underscore
52+
|> String.split("_")
53+
|> Enum.map(&String.capitalize/1)
54+
|> Enum.join(" ")
55+
end
56+
end
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
defmodule CodeCorps.Analytics.SegmentPlugTracker do
2+
@moduledoc """
3+
Segment tracking
4+
"""
5+
6+
alias CodeCorps.Analytics.{
7+
SegmentDataExtractor,
8+
SegmentTracker,
9+
SegmentTrackingSupport
10+
}
11+
12+
@spec maybe_track(Plug.Conn.t) :: Plug.Conn.t
13+
def maybe_track(conn) do
14+
success = successful?(conn)
15+
16+
action = SegmentDataExtractor.get_action(conn)
17+
resource = SegmentDataExtractor.get_resource(conn)
18+
19+
case success && SegmentTrackingSupport.includes?(action, resource) do
20+
true ->
21+
user_id = SegmentDataExtractor.get_user_id(conn, resource)
22+
SegmentTracker.track(user_id, action, resource)
23+
mark_tracked(conn)
24+
false ->
25+
mark_untracked(conn)
26+
end
27+
end
28+
29+
defp successful?(%Plug.Conn{status: status}) when status in [200, 201, 204], do: true
30+
defp successful?(_), do: false
31+
32+
defp mark_untracked(conn), do: conn |> Plug.Conn.assign(:segment_tracked, false)
33+
defp mark_tracked(conn), do: conn |> Plug.Conn.assign(:segment_tracked, true)
34+
end
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
defmodule CodeCorps.Analytics.SegmentTracker do
2+
@moduledoc """
3+
Performs tracking of segment events
4+
"""
5+
6+
alias CodeCorps.Analytics.{
7+
SegmentEventNameBuilder,
8+
SegmentTraitsBuilder
9+
}
10+
11+
@api Application.get_env(:code_corps, :analytics)
12+
13+
@doc """
14+
Calls `identify` in the configured API module.
15+
"""
16+
@spec identify(CodeCorps.User.t) :: any
17+
def identify(%CodeCorps.User{} = user) do
18+
@api.identify(user.id, SegmentTraitsBuilder.build(user))
19+
end
20+
21+
@doc """
22+
Calls `track` in the configured API module.
23+
"""
24+
@spec track(String.t, atom, struct) :: any
25+
def track(user_id, action, data) do
26+
event = SegmentEventNameBuilder.build(action, data)
27+
traits = SegmentTraitsBuilder.build(data)
28+
@api.track(user_id, event, traits)
29+
end
30+
end
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
defmodule CodeCorps.Analytics.SegmentTrackingSupport do
2+
@moduledoc """
3+
Used to determine what Segment should and should not track
4+
"""
5+
6+
@doc """
7+
Determines if Segment should track an action/record combination.
8+
"""
9+
@spec includes?(atom, struct) :: boolean
10+
def includes?(:create, %CodeCorps.Comment{}), do: true
11+
def includes?(:update, %CodeCorps.Comment{}), do: true
12+
def includes?(:create, %CodeCorps.DonationGoal{}), do: true
13+
def includes?(:update, %CodeCorps.DonationGoal{}), do: true
14+
def includes?(:create, %CodeCorps.OrganizationMembership{}), do: true
15+
def includes?(:update, %CodeCorps.OrganizationMembership{}), do: true
16+
def includes?(:create, %CodeCorps.StripeConnectAccount{}), do: true
17+
def includes?(:create, %CodeCorps.StripeConnectCharge{}), do: true
18+
def includes?(:create, %CodeCorps.StripeConnectPlan{}), do: true
19+
def includes?(:create, %CodeCorps.StripeConnectSubscription{}), do: true
20+
def includes?(:create, %CodeCorps.StripePlatformCard{}), do: true
21+
def includes?(:create, %CodeCorps.StripePlatformCustomer{}), do: true
22+
def includes?(:create, %CodeCorps.Task{}), do: true
23+
def includes?(:update, %CodeCorps.Task{}), do: true
24+
def includes?(:create, %CodeCorps.User{}), do: true
25+
def includes?(:update, %CodeCorps.User{}), do: true
26+
def includes?(:create, %CodeCorps.UserCategory{}), do: true
27+
def includes?(:delete, %CodeCorps.UserCategory{}), do: true
28+
def includes?(:create, %CodeCorps.UserRole{}), do: true
29+
def includes?(:delete, %CodeCorps.UserRole{}), do: true
30+
def includes?(:create, %CodeCorps.UserSkill{}), do: true
31+
def includes?(:delete, %CodeCorps.UserSkill{}), do: true
32+
def includes?(:create, %{token: _, user_id: _}), do: true
33+
def includes?(_, _), do: false
34+
end

0 commit comments

Comments
 (0)