11defmodule CodeCorps.Analytics.Segment do
2+ @ moduledoc """
3+ Provides analytics tracking for Segment.com with an interface for making [`identify`](https://github.com/stueccles/analytics-elixir#identify) and [`track`](https://github.com/stueccles/analytics-elixir#track) calls via the [`analytics-elixir` package](https://github.com/stueccles/analytics-elixir).
4+
5+ You can read more about [`identify`](https://segment.com/docs/spec/identify/) and [`track`](https://segment.com/docs/spec/track/) in [Segment's documentation](https://segment.com/docs/).
6+
7+ By default, in `dev` and `test` envrionments, this module will use `CodeCorps.Analytics.InMemoryAPI` which does not make a request to Segment's REST API.
8+
9+ In `prod` and `staging` environments, the module will use `CodeCorps.Analytics.SegmentAPI` which _will_ make requests to Segment's REST API.
10+
11+ In your `config/prod.exs` you might set this like so:
12+
13+ ```elixir
14+ config :code_corps, :analytics, CodeCorps.Analytics.SegmentAPI
15+ ```
16+ """
17+
218 alias CodeCorps.Comment
319 alias CodeCorps.OrganizationMembership
420 alias CodeCorps.Task
521 alias CodeCorps.User
622 alias CodeCorps.UserCategory
723 alias CodeCorps.UserRole
824 alias CodeCorps.UserSkill
25+ alias Ecto.Changeset
926
10- def identify ( user = % User { } ) do
11- Segment.Analytics . identify ( user . id , traits ( user ) )
12- end
27+ @ api Application . get_env ( :code_corps , :analytics )
1328
14- def track ( conn , :added , user_category = % UserCategory { } ) do
15- conn |> do_track ( "Added User Category" , properties ( user_category ) )
16- end
17- def track ( conn , :added , user_role = % UserRole { } ) do
18- conn |> do_track ( "Added User Role" , properties ( user_role ) )
19- end
20- def track ( conn , :added , user_skill = % UserSkill { } ) do
21- conn |> do_track ( "Added User Skill" , properties ( user_skill ) )
22- end
23- def track ( conn , :created , comment = % Comment { } ) do
24- conn |> do_track ( "Created Comment" , properties ( comment ) )
25- end
26- def track ( conn , :created , organization_membership = % OrganizationMembership { role: "pending" } ) do
27- conn |> do_track ( "Requested Organization Membership" , properties ( organization_membership ) )
28- end
29- def track ( conn , :created , organization_membership = % OrganizationMembership { } ) do
30- conn |> do_track ( "Created Organization Membership" , properties ( organization_membership ) )
31- end
32- def track ( conn , :created , task = % Task { } ) do
33- conn |> do_track ( "Created Task" , properties ( task ) )
34- end
35- def track ( conn , :edited , comment = % Comment { } ) do
36- conn |> do_track ( "Edited Comment" , properties ( comment ) )
37- end
38- def track ( conn , :edited , task = % Task { } ) do
39- conn |> do_track ( "Edited Task" , properties ( task ) )
40- end
41- def track ( conn , :removed , user_category = % UserCategory { } ) do
42- conn |> do_track ( "Removed User Category" , properties ( user_category ) )
43- end
44- def track ( conn , :removed , user_role = % UserRole { } ) do
45- conn |> do_track ( "Removed User Role" , properties ( user_role ) )
46- end
47- def track ( conn , :removed , user_skill = % UserSkill { } ) do
48- conn |> do_track ( "Removed User Skill" , properties ( user_skill ) )
49- end
50- def track ( conn , _event , _struct ) do
51- conn # return conn without event to track
29+ @ actions_without_properties [ :updated_profile , :signed_in , :signed_out , :signed_up ]
30+
31+ @ doc """
32+ Uses the action on the record to determine the event name that should be passed in for the `track` call.
33+ """
34+ @ spec get_event_name ( atom , struct ) :: String . t
35+ def get_event_name ( action , _ ) when action in @ actions_without_properties do
36+ friendly_action_name ( action )
37+ end
38+ def get_event_name ( :created , % OrganizationMembership { } ) , do: "Requested Organization Membership"
39+ def get_event_name ( :edited , % OrganizationMembership { } ) , do: "Approved Organization Membership"
40+ def get_event_name ( :created , % UserCategory { } ) , do: "Added User Category"
41+ def get_event_name ( :created , % UserSkill { } ) , do: "Added User Skill"
42+ def get_event_name ( :created , % UserRole { } ) , do: "Added User Role"
43+ def get_event_name ( action , model ) do
44+ [ friendly_action_name ( action ) , friendly_model_name ( model ) ] |> Enum . join ( " " )
5245 end
5346
54- def track ( conn , :updated_profile ) do
55- conn |> do_track ( "Updated Profile" )
47+ @ doc """
48+ Calls `identify` in the configured API module.
49+ """
50+ @ spec identify ( User . t ) :: any
51+ def identify ( user = % User { } ) do
52+ @ api . identify ( user . id , traits ( user ) )
5653 end
57- def track ( conn , :signed_in ) do
58- conn |> do_track ( "Signed In" )
54+
55+ @ doc """
56+ Calls `track` in the configured API module.
57+
58+ Receives either an `:ok` or `:error` tuple from an attempted `Ecto.Repo` operation.
59+ """
60+ @ spec track ( { :ok , Ecto.Schema . t } | { :error , Ecto.Changeset . t } , atom , Plug.Conn . t ) :: any
61+ def track ( { :ok , record } , action , % Plug.Conn { } = conn ) when action in @ actions_without_properties do
62+ action_name = get_event_name ( action , record )
63+ do_track ( conn , action_name )
64+
65+ { :ok , record }
5966 end
60- def track ( conn , :signed_out ) do
61- conn |> do_track ( "Signed Out" )
67+ def track ( { :ok , record } , action , % Plug.Conn { } = conn ) do
68+ action_name = get_event_name ( action , record )
69+ do_track ( conn , action_name , properties ( record ) )
70+
71+ { :ok , record }
6272 end
63- def track ( conn , :signed_up ) do
64- conn |> do_track ( "Signed Up" )
73+ def track ( { :error , % Changeset { } = changeset } , _action , _conn ) , do: { :error , changeset }
74+ def track ( { :error , errors } , :deleted , _conn ) , do: { :error , errors }
75+
76+ @ doc """
77+ Calls `track` with the "Signed In" event in the configured API module.
78+ """
79+ @ spec track_sign_in ( Plug.Conn . t ) :: any
80+ def track_sign_in ( conn ) , do: conn |> do_track ( "Signed In" )
81+
82+ defp friendly_action_name ( :deleted ) , do: "Removed"
83+ defp friendly_action_name ( action ) do
84+ action
85+ |> Atom . to_string
86+ |> String . split ( "_" )
87+ |> Enum . map ( & String . capitalize / 1 )
88+ |> Enum . join ( " " )
6589 end
66- def track ( conn , _event ) do
67- conn # return conn without event to track
90+
91+ defp friendly_model_name ( model ) do
92+ model . __struct__
93+ |> Module . split
94+ |> List . last
95+ |> Macro . underscore
96+ |> String . split ( "_" )
97+ |> Enum . map ( & String . capitalize / 1 )
98+ |> Enum . join ( " " )
6899 end
69100
70101 defp do_track ( conn , event_name , properties ) do
71- Segment.Analytics . track ( conn . assigns [ :current_user ] . id , event_name , properties )
102+ @ api . track ( conn . assigns [ :current_user ] . id , event_name , properties )
72103 conn
73104 end
105+
74106 defp do_track ( conn , event_name ) do
75- Segment.Analytics . track ( conn . assigns [ :current_user ] . id , event_name , % { } )
107+ @ api . track ( conn . assigns [ :current_user ] . id , event_name , % { } )
76108 conn
77109 end
78110
79111 defp properties ( comment = % Comment { } ) do
112+ comment = comment |> CodeCorps.Repo . preload ( :task )
80113 % {
81114 comment_id: comment . id ,
82115 task: comment . task . title ,
@@ -86,6 +119,7 @@ defmodule CodeCorps.Analytics.Segment do
86119 }
87120 end
88121 defp properties ( organization_membership = % OrganizationMembership { } ) do
122+ organization_membership = organization_membership |> CodeCorps.Repo . preload ( :organization )
89123 % {
90124 organization: organization_membership . organization . name ,
91125 organization_id: organization_membership . organization . id
@@ -100,18 +134,21 @@ defmodule CodeCorps.Analytics.Segment do
100134 }
101135 end
102136 defp properties ( user_category = % UserCategory { } ) do
137+ user_category = user_category |> CodeCorps.Repo . preload ( :category )
103138 % {
104139 category: user_category . category . name ,
105140 category_id: user_category . category . id
106141 }
107142 end
108143 defp properties ( user_role = % UserRole { } ) do
144+ user_role = user_role |> CodeCorps.Repo . preload ( :role )
109145 % {
110146 role: user_role . role . name ,
111147 role_id: user_role . role . id
112148 }
113149 end
114150 defp properties ( user_skill = % UserSkill { } ) do
151+ user_skill = user_skill |> CodeCorps.Repo . preload ( :skill )
115152 % {
116153 skill: user_skill . skill . title ,
117154 skill_id: user_skill . skill . id
0 commit comments