Skip to content

Commit cf50ce2

Browse files
authored
Merge pull request #332 from code-corps/331-add-ja_resource
Add ja_resource
2 parents 58c0f5a + f6775e0 commit cf50ce2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+433
-724
lines changed

circle.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ dependencies:
1414
test:
1515
override:
1616
- |
17-
if [ ${CIRCLE_PR_USERNAME} ]; then MIX_ENV=test mix test --cover; else MIX_ENV=test mix test --include requires_env --cover; fi
17+
if [ ${CIRCLE_PR_USERNAME} ]; then MIX_ENV=test mix coveralls.circle; else MIX_ENV=test mix coveralls.circle --include requires_env; fi
1818
1919
post:
2020
- mix inch.report

config/config.exs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,6 @@ config :arc,
5252
bucket: System.get_env("S3_BUCKET"),
5353
asset_host: System.get_env("CLOUDFRONT_DOMAIN")
5454

55-
# Configures Segment for analytics
56-
config :code_corps, :analytics, CodeCorps.Analytics.Segment
57-
5855
config :segment,
5956
write_key: System.get_env("SEGMENT_WRITE_KEY")
6057

@@ -72,6 +69,9 @@ config :sentry,
7269
included_environments: ~w(prod staging)a,
7370
use_error_logger: true
7471

72+
config :ja_resource,
73+
repo: CodeCorps.Repo
74+
7575
# Import environment specific config. This must remain at the bottom
7676
# of this file so it overrides the configuration defined above.
7777
import_config "#{Mix.env}.exs"

config/dev.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ config :code_corps, allowed_origins: ["http://localhost:4200"]
4646
config :guardian, Guardian,
4747
secret_key: "e62fb6e2746f6b1bf8b5b735ba816c2eae1d5d76e64f18f3fc647e308b0c159e"
4848

49-
config :code_corps, :analytics, CodeCorps.Analytics.InMemory
49+
config :code_corps, :analytics, CodeCorps.Analytics.InMemoryAPI
5050

5151
config :sentry,
5252
environment_name: Mix.env || :dev

config/prod.exs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ config :guardian, Guardian,
3838
# Do not print debug messages in production
3939
config :logger, level: :info
4040

41+
# Configures Segment for analytics
42+
config :code_corps, :analytics, CodeCorps.Analytics.SegmentAPI
43+
4144
config :sentry,
4245
environment_name: Mix.env || :prod
4346

config/staging.exs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ config :guardian, Guardian,
3737
# Do not print debug messages in production
3838
config :logger, level: :info
3939

40+
# Configures Segment for analytics
41+
config :code_corps, :analytics, CodeCorps.Analytics.SegmentAPI
42+
4043
config :sentry,
4144
environment_name: Mix.env || :staging
4245

config/test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ config :code_corps, allowed_origins: ["http://localhost:4200"]
2828
config :guardian, Guardian,
2929
secret_key: "e62fb6e2746f6b1bf8b5b735ba816c2eae1d5d76e64f18f3fc647e308b0c159e"
3030

31-
config :code_corps, :analytics, CodeCorps.Analytics.InMemory
31+
config :code_corps, :analytics, CodeCorps.Analytics.InMemoryAPI
3232

3333
config :code_corps, :icon_color_generator, CodeCorps.RandomIconColor.TestGenerator
3434

lib/code_corps/analytics/in_memory.ex

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
defmodule CodeCorps.Analytics.InMemoryAPI do
2+
@moduledoc """
3+
In-memory interface to simulate calling out to the Segment API.
4+
5+
Each function should have the same signature as `CodeCorps.Analytics.SegmentAPI` and simply return `nil`.
6+
"""
7+
8+
def identify(_user_id, _traits), do: nil
9+
def track(_user_id, _event_name, _properties), do: nil
10+
end

lib/code_corps/analytics/segment.ex

Lines changed: 90 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,115 @@
11
defmodule 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
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
defmodule CodeCorps.Analytics.SegmentAPI do
2+
@moduledoc """
3+
Interface to the Segment API through the [`analytics-elixir` package](https://github.com/stueccles/analytics-elixir).
4+
"""
5+
6+
def identify(user_id, traits) do
7+
Segment.Analytics.identify(user_id, traits)
8+
end
9+
10+
def track(user_id, event_name, properties) do
11+
Segment.Analytics.track(user_id, event_name, properties)
12+
end
13+
end

0 commit comments

Comments
 (0)