think_feel_do_engine 3.18.0 → 3.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/think_feel_do_engine/feel/draw_graphs.js +6 -0
  3. data/app/controllers/think_feel_do_engine/application_controller.rb +14 -1
  4. data/app/controllers/think_feel_do_engine/coach/site_messages_controller.rb +7 -2
  5. data/app/controllers/think_feel_do_engine/manage/tasks_controller.rb +5 -3
  6. data/app/helpers/think_feel_do_engine/coach/group_dashboard_helper.rb +2 -2
  7. data/app/models/arm.rb +1 -1
  8. data/app/models/content_providers/index_past_feel_provider.rb +6 -2
  9. data/app/models/delivered_message.rb +1 -1
  10. data/app/models/group.rb +1 -102
  11. data/app/models/group_metrics/weekly_activities_count.rb +20 -0
  12. data/app/models/group_metrics/weekly_comments_count.rb +12 -0
  13. data/app/models/group_metrics/weekly_count.rb +35 -0
  14. data/app/models/group_metrics/weekly_goals_count.rb +12 -0
  15. data/app/models/group_metrics/weekly_likes_count.rb +12 -0
  16. data/app/models/group_metrics/weekly_logins_count.rb +6 -0
  17. data/app/models/group_metrics/weekly_on_the_mind_statements_count.rb +12 -0
  18. data/app/models/group_metrics/weekly_thoughts_count.rb +6 -0
  19. data/app/models/membership.rb +2 -2
  20. data/app/models/participant.rb +2 -58
  21. data/app/models/participant_metrics/weekly_count.rb +39 -0
  22. data/app/models/participant_metrics/weekly_logins_count.rb +6 -0
  23. data/app/models/task.rb +1 -1
  24. data/app/models/task_status.rb +2 -2
  25. data/app/models/think_feel_do_engine/reports/user_agent.rb +1 -1
  26. data/app/support/mood_and_emotion_visualization_service.rb +66 -0
  27. data/app/views/think_feel_do_engine/coach/group_dashboard/_activities_future.html.erb +1 -1
  28. data/app/views/think_feel_do_engine/coach/group_dashboard/_activities_past.html.erb +1 -1
  29. data/app/views/think_feel_do_engine/coach/group_dashboard/_lessons.html.erb +3 -2
  30. data/app/views/think_feel_do_engine/coach/group_dashboard/_logins.html.erb +3 -2
  31. data/app/views/think_feel_do_engine/coach/group_dashboard/_summary.html.erb +5 -13
  32. data/app/views/think_feel_do_engine/coach/group_dashboard/_summary_social.html.erb +5 -13
  33. data/app/views/think_feel_do_engine/coach/group_dashboard/_thoughts.html.erb +1 -1
  34. data/app/views/think_feel_do_engine/coach/group_dashboard/_weekly_counts.html.erb +4 -0
  35. data/app/views/think_feel_do_engine/coach/patient_dashboards/_vizs.html.erb +2 -2
  36. data/config/initializers/event_capture.rb +53 -36
  37. data/config/routes.rb +3 -5
  38. data/lib/tasks/lesson_notifications.rake +1 -1
  39. data/lib/think_feel_do_engine/version.rb +1 -1
  40. metadata +15 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 248328db1d28d096496e611d71c7d38ca0973c7c
4
- data.tar.gz: bab9c20b14380eb6a3fdf3ac120ba369b591025e
3
+ metadata.gz: de032421673efdc9d72b69a4d3d521507a88247c
4
+ data.tar.gz: f40b0c0509e23bb16a22a3d39bc9190f88140caa
5
5
  SHA512:
6
- metadata.gz: 09d990df0b6015ef3e3c5d7061eff9c0d901f99d43bdd210975fcd4d480f342aa43ac9e9b828faf38894070334f8956f12b392882eec1a0ec9823531d6ca1b28
7
- data.tar.gz: 1c3c09c80590ccf1966a11f4ab71b2894fcb1922176bf8bbc797c2d08de6da410608546984e2cea89ca7247fd0c6d8827afa511680db1b0c984b32506863b80d
6
+ metadata.gz: 2da3064e7e903cfbb7e3c5ab36470877b7fd187f5dc3bba00a1467da76d048a3afc1576bcc7bbd4252becf2b3e39a35e1c39925aa9c74f1deaba3deb023ea407
7
+ data.tar.gz: b7492b5eeda1ed6fb273813d86252a0949215229a7688b2499318180bbe03464132fb3a1f8f29304ee5c8b6bd7145ed656dbb41bca5cf620047d8bc838ea65a8
@@ -25,6 +25,9 @@
25
25
  .call(columnChart(graphParameters.startDate, graphParameters.endDate, Y_MIN, Y_MAX, GRAPH_TITLE, YLABEL)
26
26
  .width(graphParameters.graphWidth)
27
27
  .height(PIXEL_HEIGHT)
28
+ .x(function(date, i) {
29
+ return moment(date.day, 'YYYY-MM-DD').startOf('day').toDate();
30
+ })
28
31
  .drawLegend(LEGEND_DIV_ID, LEGEND, ['*Click each day for more information.']));
29
32
  };
30
33
 
@@ -44,6 +47,9 @@
44
47
  .call(columnChart(graphParameters.startDate, graphParameters.endDate, Y_MIN, Y_MAX, GRAPH_TITLE, YLABEL)
45
48
  .width(graphParameters.graphWidth)
46
49
  .height(PIXEL_HEIGHT)
50
+ .x(function(date, i) {
51
+ return moment(date.day, 'YYYY-MM-DD').startOf('day').toDate();
52
+ })
47
53
  .y(function(date, i) {
48
54
  return date.intensity;
49
55
  })
@@ -13,13 +13,15 @@ module ThinkFeelDoEngine
13
13
  INACTIVE_MESSAGE = "We're sorry, but you can't sign in yet "\
14
14
  "because you are not assigned to an active group."
15
15
 
16
- protect_from_forgery with: :exception
16
+ protect_from_forgery with: :exception, except: :raise_not_found!
17
17
 
18
18
  before_action :detect_browser, :verify_active_membership
19
19
  after_action :set_csrf_cookie_for_ng
20
20
 
21
21
  layout "application"
22
22
 
23
+ rescue_from ActionController::RoutingError, with: :render_not_found
24
+
23
25
  def after_sign_in_path_for(resource)
24
26
  if resource.class == User
25
27
  if defined?(think_feel_do_dashboard)
@@ -57,6 +59,17 @@ module ThinkFeelDoEngine
57
59
  end
58
60
  end
59
61
 
62
+ # Called by last route matching unmatched routes. Raises an exception that
63
+ # is expected to be rescued.
64
+ def raise_not_found!
65
+ fail ActionController::RoutingError,
66
+ "No route matches #{params[:unmatched_route]}"
67
+ end
68
+
69
+ def render_not_found(_)
70
+ render file: "#{Rails.root}/public/404.html", status: 404
71
+ end
72
+
60
73
  protected
61
74
 
62
75
  def verified_request?
@@ -19,7 +19,7 @@ module ThinkFeelDoEngine
19
19
  end
20
20
 
21
21
  def new
22
- @participants = current_user.participants_for_group(@group)
22
+ set_participants
23
23
  redirect_to(coach_group_site_messages_path(@group),
24
24
  alert: "A group must have participants in order "\
25
25
  " to send site message.") if @participants.empty?
@@ -27,11 +27,12 @@ module ThinkFeelDoEngine
27
27
 
28
28
  def create
29
29
  if @site_message.save
30
- SiteMessageMailer.general(@site_message).deliver
30
+ SiteMessageMailer.general(@site_message).deliver_now
31
31
 
32
32
  redirect_to coach_group_site_message_path(@group, @site_message),
33
33
  notice: "Site message was successfully created."
34
34
  else
35
+ set_participants
35
36
  render :new
36
37
  end
37
38
  end
@@ -49,6 +50,10 @@ module ThinkFeelDoEngine
49
50
  def set_group
50
51
  @group = Group.find(params[:group_id])
51
52
  end
53
+
54
+ def set_participants
55
+ @participants = current_user.participants_for_group(@group)
56
+ end
52
57
  end
53
58
  end
54
59
  end
@@ -29,14 +29,16 @@ module ThinkFeelDoEngine
29
29
  def destroy
30
30
  authorize! :destroy, @task
31
31
  group = @task.group
32
- if @task.destroy
32
+ if @task.complete_participant_list.present?
33
+ flash.now[:alert] = "Unable to delete task from group: "\
34
+ "at least one related task status is complete."
35
+ elsif @task.destroy
33
36
  flash.now[:success] = "Task unassigned from group."
34
- redirect_to arm_manage_tasks_group_path(group.arm, group)
35
37
  else
36
38
  errors = @task.errors.full_messages.join(", ")
37
39
  flash[:error] = "Unable to delete task from group: #{ errors }"
38
- redirect_to arm_manage_tasks_group_path(group.arm, group)
39
40
  end
41
+ redirect_to arm_manage_tasks_group_path(group.arm, group)
40
42
  end
41
43
 
42
44
  private
@@ -145,10 +145,10 @@ module ThinkFeelDoEngine
145
145
  private
146
146
 
147
147
  def week_of_task(task)
148
- if (task.task_statuses.first.start_day / 7.0).ceil == 0
148
+ if (task.release_day / 7.0).ceil == 0
149
149
  1
150
150
  else
151
- (task.task_statuses.first.start_day / 7.0).ceil
151
+ (task.release_day / 7.0).ceil
152
152
  end
153
153
  end
154
154
  end
data/app/models/arm.rb CHANGED
@@ -38,7 +38,7 @@ class Arm < ActiveRecord::Base
38
38
  end
39
39
 
40
40
  def non_home_tools
41
- tools = Arel::Table.new(:bit_core_tools)
41
+ tools = BitCore::Tool.arel_table
42
42
  bit_core_tools
43
43
  .where(tools[:type].eq(nil)
44
44
  .or(tools[:type].not_eq("Tools::Home"))
@@ -7,12 +7,16 @@ module ContentProviders
7
7
 
8
8
  def render_current(options)
9
9
  participant = options.view_context.current_participant
10
+ visualization_service = MoodAndEmotionVisualizationService
11
+ .new(participant)
10
12
 
11
13
  options.view_context.render(
12
14
  template: "think_feel_do_engine/emotions/index",
13
15
  locals: {
14
- emotional_ratings: participant.emotional_rating_daily_averages,
15
- mood_ratings: participant.mood_rating_daily_averages
16
+ emotional_ratings: visualization_service
17
+ .emotional_rating_daily_averages,
18
+ mood_ratings: visualization_service
19
+ .mood_rating_daily_averages
16
20
  }
17
21
  )
18
22
  end
@@ -15,7 +15,7 @@ class DeliveredMessage < ActiveRecord::Base
15
15
  scope :sent_from, lambda { |sender_id|
16
16
  joins(:message)
17
17
  .where(
18
- Arel::Table.new(:messages)[:sender_id]
18
+ Message.arel_table[:sender_id]
19
19
  .eq(sender_id)
20
20
  )
21
21
  }
data/app/models/group.rb CHANGED
@@ -23,112 +23,11 @@ class Group < ActiveRecord::Base
23
23
  tasks
24
24
  .joins(:bit_core_content_module)
25
25
  .where(
26
- Arel::Table.new(:bit_core_content_modules)[:type]
26
+ BitCore::ContentModule.arel_table[:type]
27
27
  .eq("ContentModules::LessonModule")
28
28
  )
29
29
  end
30
30
 
31
- def logins_by_week(week_number)
32
- non_moderator_memberships.map do |membership|
33
- membership
34
- .logins_by_week(week_number)
35
- end.inject(:+)
36
- end
37
-
38
- def thoughts_by_week(week_number)
39
- thoughts = Arel::Table.new(:thoughts)
40
-
41
- non_moderator_memberships.map do |membership|
42
- membership
43
- .participant
44
- .thoughts
45
- .where(thoughts[:created_at]
46
- .gteq(membership.week_start_day(week_number)))
47
- .where(thoughts[:created_at]
48
- .lt(membership.week_end_day(week_number)))
49
- end.map(&:count).sum
50
- end
51
-
52
- def activities_past_by_week(week_number)
53
- activities = Arel::Table.new(:activities)
54
-
55
- non_moderator_memberships.map do |membership|
56
- membership
57
- .participant
58
- .activities
59
- .in_the_past
60
- .where(activities[:start_time]
61
- .gteq(membership.week_start_day(week_number)))
62
- end.map(&:count).sum
63
- end
64
-
65
- def activities_future_by_week(week_number)
66
- activities = Arel::Table.new(:activities)
67
-
68
- non_moderator_memberships.map do |membership|
69
- membership
70
- .participant
71
- .activities
72
- .unscheduled_or_in_the_future
73
- .where(activities[:start_time]
74
- .lt(membership.week_end_day(week_number)))
75
- end.map(&:count).sum
76
- end
77
-
78
- def goals_by_week(week_number)
79
- social_networking_goals = Arel::Table.new(:social_networking_goals)
80
-
81
- non_moderator_memberships.map do |membership|
82
- SocialNetworking::Goal
83
- .where(participant: membership.participant)
84
- .where(social_networking_goals[:created_at]
85
- .gteq(membership.week_start_day(week_number)))
86
- .where(social_networking_goals[:created_at]
87
- .lt(membership.week_end_day(week_number)))
88
- end.map(&:count).sum
89
- end
90
-
91
- def comments_by_week(week_number)
92
- social_networking_comments = Arel::Table.new(:social_networking_comments)
93
-
94
- non_moderator_memberships.map do |membership|
95
- SocialNetworking::Comment
96
- .where(participant: membership.participant)
97
- .where(social_networking_comments[:created_at]
98
- .gteq(membership.week_start_day(week_number)))
99
- .where(social_networking_comments[:created_at]
100
- .lt(membership.week_end_day(week_number)))
101
- end.map(&:count).sum
102
- end
103
-
104
- def on_the_mind_statements_by_week(week_number)
105
- social_networking_on_the_mind_statements =
106
- Arel::Table.new(:social_networking_on_the_mind_statements)
107
-
108
- non_moderator_memberships.map do |membership|
109
- SocialNetworking::OnTheMindStatement
110
- .where(participant: membership.participant)
111
- .where(social_networking_on_the_mind_statements[:created_at]
112
- .gteq(membership.week_start_day(week_number)))
113
- .where(social_networking_on_the_mind_statements[:created_at]
114
- .lt(membership.week_end_day(week_number)))
115
- end.map(&:count).sum
116
- end
117
-
118
- def likes_by_week(week_number)
119
- social_networking_likes =
120
- Arel::Table.new(:social_networking_likes)
121
-
122
- non_moderator_memberships.map do |membership|
123
- SocialNetworking::Like
124
- .where(participant: membership.participant)
125
- .where(social_networking_likes[:created_at]
126
- .gteq(membership.week_start_day(week_number)))
127
- .where(social_networking_likes[:created_at]
128
- .lt(membership.week_end_day(week_number)))
129
- end.map(&:count).sum
130
- end
131
-
132
31
  def non_moderator_memberships
133
32
  memberships.joins(:participant)
134
33
  .merge(Participant.not_moderator)
@@ -0,0 +1,20 @@
1
+ module GroupMetrics
2
+ # Tabulate count of Activities (within a study week) for a Group.
3
+ class WeeklyActivitiesCount < WeeklyCount
4
+ self.table_name = "activities"
5
+
6
+ scope :in_the_past, lambda {
7
+ where arel_table[:end_time].lt(Time.zone.now)
8
+ }
9
+
10
+ # To Do: fix naming and/or what is going on here
11
+ # change to start_time and is_scheduled
12
+ # and updated tests
13
+ scope :unscheduled_or_in_the_future, lambda {
14
+ where(
15
+ arel_table[:start_time].eq(nil)
16
+ .or(arel_table[:end_time].gt(Time.zone.now))
17
+ )
18
+ }
19
+ end
20
+ end
@@ -0,0 +1,12 @@
1
+ module GroupMetrics
2
+ # Tabulate count of Comments within a study week for a Group.
3
+ class WeeklyCommentsCount < WeeklyCount
4
+ self.table_name = "social_networking_comments"
5
+
6
+ def self.fetch(group_id)
7
+ return {} unless ActiveRecord::Base.connection.table_exists?(table_name)
8
+
9
+ super
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,35 @@
1
+ module GroupMetrics
2
+ # Tabulate count of records (within a study week) for a Group.
3
+ class WeeklyCount < ActiveRecord::Base
4
+ self.abstract_class = true
5
+
6
+ has_many :memberships,
7
+ foreign_key: :participant_id,
8
+ primary_key: :participant_id
9
+
10
+ def self.fetch(group_id)
11
+ pg_timezone = ActiveSupport::TimeZone[Time.zone.name].tzinfo.name
12
+
13
+ joins(memberships: :participant)
14
+ .select(<<-SQL
15
+ ( FLOOR ( EXTRACT ( EPOCH FROM (
16
+ ( #{connection.quote_table_name(table_name)}.created_at
17
+ AT TIME ZONE 'UTC' ) AT TIME ZONE #{connection.quote(pg_timezone)}
18
+ - memberships.start_date ) ) / 604800 ) + 1
19
+ )::int AS week, COUNT(1)
20
+ SQL
21
+ )
22
+ .merge(Participant.not_moderator)
23
+ .merge(Membership.where(group_id: group_id))
24
+ .group(:week)
25
+ end
26
+
27
+ def week
28
+ attributes["week"]
29
+ end
30
+
31
+ def count
32
+ attributes["count"]
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,12 @@
1
+ module GroupMetrics
2
+ # Tabulate count of Goals within a study week for a Group.
3
+ class WeeklyGoalsCount < WeeklyCount
4
+ self.table_name = "social_networking_goals"
5
+
6
+ def self.fetch(group_id)
7
+ return {} unless ActiveRecord::Base.connection.table_exists?(table_name)
8
+
9
+ super
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module GroupMetrics
2
+ # Tabulate count of Likes within a study week for a Group.
3
+ class WeeklyLikesCount < WeeklyCount
4
+ self.table_name = "social_networking_likes"
5
+
6
+ def self.fetch(group_id)
7
+ return {} unless ActiveRecord::Base.connection.table_exists?(table_name)
8
+
9
+ super
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,6 @@
1
+ module GroupMetrics
2
+ # Tabulate count of Logins (within a study week) for a Group.
3
+ class WeeklyLoginsCount < WeeklyCount
4
+ self.table_name = "participant_login_events"
5
+ end
6
+ end
@@ -0,0 +1,12 @@
1
+ module GroupMetrics
2
+ # Tabulate count of OnTheMindStatements within a study week for a Group.
3
+ class WeeklyOnTheMindStatementsCount < WeeklyCount
4
+ self.table_name = "social_networking_on_the_mind_statements"
5
+
6
+ def self.fetch(group_id)
7
+ return {} unless ActiveRecord::Base.connection.table_exists?(table_name)
8
+
9
+ super
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,6 @@
1
+ module GroupMetrics
2
+ # Tabulate count of Thoughts (within a study week) for a Group.
3
+ class WeeklyThoughtsCount < WeeklyCount
4
+ self.table_name = "thoughts"
5
+ end
6
+ end
@@ -107,7 +107,7 @@ class Membership < ActiveRecord::Base
107
107
  end
108
108
 
109
109
  def logins_by_week(week_number)
110
- participant_login_events = Arel::Table.new(:participant_login_events)
110
+ participant_login_events = ParticipantLoginEvent.arel_table
111
111
  participant
112
112
  .participant_login_events
113
113
  .where(participant_login_events[:created_at]
@@ -118,7 +118,7 @@ class Membership < ActiveRecord::Base
118
118
  end
119
119
 
120
120
  def logins_today
121
- participant_login_events = Arel::Table.new(:participant_login_events)
121
+ participant_login_events = ParticipantLoginEvent.arel_table
122
122
  participant
123
123
  .participant_login_events
124
124
  .where(participant_login_events[:created_at]
@@ -77,7 +77,7 @@ class Participant < ActiveRecord::Base
77
77
  scope :stepped, lambda {
78
78
  joins(:memberships)
79
79
  .where(
80
- Arel::Table.new(:memberships)[:stepped_on]
80
+ Membership.arel_table[:stepped_on]
81
81
  .not_eq(nil)
82
82
  )
83
83
  }
@@ -85,7 +85,7 @@ class Participant < ActiveRecord::Base
85
85
  scope :not_stepped, lambda {
86
86
  joins(:memberships)
87
87
  .where(
88
- Arel::Table.new(:memberships)[:stepped_on]
88
+ Membership.arel_table[:stepped_on]
89
89
  .eq(nil)
90
90
  )
91
91
  }
@@ -253,10 +253,6 @@ class Participant < ActiveRecord::Base
253
253
  "sms" == contact_preference || "phone" == contact_preference
254
254
  end
255
255
 
256
- def average_rating(array)
257
- array.reduce(0) { |a, e| a.to_f + e[0] } / array.size
258
- end
259
-
260
256
  def positive_emotions(emotion_array)
261
257
  emotions = emotion_array.collect do |emotion|
262
258
  if emotion.is_positive
@@ -279,37 +275,6 @@ class Participant < ActiveRecord::Base
279
275
  memberships.order(end_date: :desc).first
280
276
  end
281
277
 
282
- def emotional_rating_daily_averages
283
- averaged_ratings = []
284
-
285
- daily_ratings = emotional_ratings.group_by(&:created_at)
286
- # rubocop:disable all
287
- daily_ratings.each do |day, emotion_array|
288
- # rubocop:enable all
289
- positive_ratings = positive_emotions(emotion_array)
290
- if positive_ratings.size > 0
291
- daily_positive = { day: day,
292
- intensity: average_rating(positive_ratings),
293
- is_positive: true,
294
- drill_down: positive_ratings,
295
- data_type: "Emotion"
296
- }
297
- averaged_ratings << daily_positive
298
- end
299
- negative_ratings = negative_emotions(emotion_array)
300
- if negative_ratings.size > 0
301
- daily_negative = { day: day,
302
- intensity: average_rating(negative_ratings),
303
- is_positive: false,
304
- drill_down: negative_ratings,
305
- data_type: "Emotion"
306
- }
307
- averaged_ratings << daily_negative
308
- end
309
- end
310
- averaged_ratings
311
- end
312
-
313
278
  def in_study?
314
279
  if active_membership.start_date <= Date.today &&
315
280
  active_membership.end_date >= Date.today
@@ -319,27 +284,6 @@ class Participant < ActiveRecord::Base
319
284
  end
320
285
  end
321
286
 
322
- def mood_rating_daily_averages
323
- averaged_ratings = []
324
- daily_ratings = moods.group_by(&:created_at)
325
- # rubocop:disable all
326
- daily_ratings.each do |day, moods_array|
327
- # rubocop:enable all
328
- ratings = moods_array.collect do |mood|
329
- [mood.rating, mood.created_at].compact
330
- end
331
- if ratings.size > 0
332
- averaged_ratings << { day: day,
333
- intensity: average_rating(ratings),
334
- is_positive: true,
335
- drill_down: ratings,
336
- data_type: "Mood"
337
- }
338
- end
339
- end
340
- averaged_ratings
341
- end
342
-
343
287
  private
344
288
 
345
289
  def recent_awake_period
@@ -0,0 +1,39 @@
1
+ module ParticipantMetrics
2
+ # Calculate weekly (by enrollment week) of records.
3
+ class WeeklyCount < ActiveRecord::Base
4
+ self.abstract_class = true
5
+
6
+ has_many :memberships,
7
+ foreign_key: :participant_id,
8
+ primary_key: :participant_id
9
+
10
+ def self.fetch(group_id)
11
+ pg_timezone = ActiveSupport::TimeZone[Time.zone.name].tzinfo.name
12
+
13
+ joins(memberships: :participant)
14
+ .select(<<-SQL
15
+ ( FLOOR ( EXTRACT ( EPOCH FROM (
16
+ ( #{connection.quote_table_name(table_name)}.created_at
17
+ AT TIME ZONE 'UTC' ) AT TIME ZONE #{connection.quote(pg_timezone)}
18
+ - memberships.start_date ) ) / 604800 ) + 1
19
+ )::int AS week, memberships.participant_id, COUNT(1)
20
+ SQL
21
+ )
22
+ .merge(Participant.not_moderator)
23
+ .merge(Membership.where(group_id: group_id))
24
+ .group("week, memberships.participant_id")
25
+ end
26
+
27
+ def week
28
+ attributes["week"]
29
+ end
30
+
31
+ def participant_id
32
+ attributes["participant_id"]
33
+ end
34
+
35
+ def count
36
+ attributes["count"]
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,6 @@
1
+ module ParticipantMetrics
2
+ # Tabulate count of Logins within a study week for a Participant.
3
+ class WeeklyLoginsCount < WeeklyCount
4
+ self.table_name = "participant_login_events"
5
+ end
6
+ end
data/app/models/task.rb CHANGED
@@ -23,7 +23,7 @@ class Task < ActiveRecord::Base
23
23
  scope :learning, lambda {
24
24
  joins(:bit_core_content_module)
25
25
  .where(
26
- Arel::Table.new(:bit_core_content_modules)[:type]
26
+ BitCore::ContentModule.arel_table[:type]
27
27
  .eq("ContentModules::LessonModule")
28
28
  )
29
29
  }
@@ -37,7 +37,7 @@ class TaskStatus < ActiveRecord::Base
37
37
  scope :for_learning, lambda {
38
38
  joins(:task, task: :bit_core_content_module)
39
39
  .where(
40
- Arel::Table.new(:bit_core_content_modules)[:type]
40
+ BitCore::ContentModule.arel_table[:type]
41
41
  .eq(LESSON_MODULE_TYPE))
42
42
  }
43
43
 
@@ -56,7 +56,7 @@ class TaskStatus < ActiveRecord::Base
56
56
  }
57
57
 
58
58
  scope :not_terminated_by_day, lambda { |day_in_study|
59
- tasks = Arel::Table.new(:tasks)
59
+ tasks = Task.arel_table
60
60
  joins(:task)
61
61
  .where(
62
62
  tasks[:termination_day].eq(nil)
@@ -12,7 +12,7 @@ module ThinkFeelDoEngine
12
12
  Participant.not_moderator.select(:id, :study_id).map do |participant|
13
13
  user_agents = EventCapture::Event
14
14
  .where(participant_id: participant.id)
15
- .map { |event| event.payload[:ua] }
15
+ .map { |event| event.payload["ua"] }
16
16
  .uniq
17
17
 
18
18
  user_agents.map do |agent|
@@ -0,0 +1,66 @@
1
+ # Grouping logic for the visualization of participant mood and emotion data.
2
+ class MoodAndEmotionVisualizationService
3
+ def initialize(participant)
4
+ @participant = participant
5
+ end
6
+
7
+ def mood_rating_daily_averages
8
+ averaged_ratings = []
9
+ daily_ratings = @participant
10
+ .moods
11
+ .group_by { |x| x.created_at.strftime("%Y-%m-%d") }
12
+ # rubocop:disable all
13
+ daily_ratings.each do |day, moods_array|
14
+ # rubocop:enable all
15
+ ratings = moods_array.collect do |mood|
16
+ [mood.rating, mood.created_at].compact
17
+ end
18
+ if ratings.size > 0
19
+ averaged_ratings << { day: day,
20
+ intensity: average_rating(ratings),
21
+ is_positive: true,
22
+ drill_down: ratings,
23
+ data_type: "Mood"
24
+ }
25
+ end
26
+ end
27
+ averaged_ratings
28
+ end
29
+
30
+ def emotional_rating_daily_averages
31
+ averaged_ratings = []
32
+
33
+ daily_ratings = @participant.emotional_ratings
34
+ .group_by { |x| x.created_at.strftime("%Y-%m-%d") }
35
+ # rubocop:disable all
36
+ daily_ratings.each do |day, emotion_array|
37
+ # rubocop:enable all
38
+ positive_ratings = @participant.positive_emotions(emotion_array)
39
+ if positive_ratings.size > 0
40
+ daily_positive = { day: day,
41
+ intensity: average_rating(positive_ratings),
42
+ is_positive: true,
43
+ drill_down: positive_ratings,
44
+ data_type: "Emotion"
45
+ }
46
+ averaged_ratings << daily_positive
47
+ end
48
+ negative_ratings = @participant.negative_emotions(emotion_array)
49
+ if negative_ratings.size > 0
50
+ daily_negative = { day: day,
51
+ intensity: average_rating(negative_ratings),
52
+ is_positive: false,
53
+ drill_down: negative_ratings, data_type: "Emotion"
54
+ }
55
+ averaged_ratings << daily_negative
56
+ end
57
+ end
58
+ averaged_ratings
59
+ end
60
+
61
+ private
62
+
63
+ def average_rating(array)
64
+ array.reduce(0) { |a, e| a.to_f + e[0] } / array.size
65
+ end
66
+ end
@@ -26,7 +26,7 @@
26
26
  </thead>
27
27
  <tbody>
28
28
 
29
- <% group.non_moderator_memberships.each do |membership| %>
29
+ <% group.non_moderator_memberships.includes(participant: :activities).each do |membership| %>
30
30
  <% membership.participant.activities.unscheduled_or_in_the_future.each do |activity| %>
31
31
  <tr>
32
32
  <td class="not-displayed">
@@ -29,7 +29,7 @@
29
29
  </thead>
30
30
  <tbody>
31
31
 
32
- <% group.non_moderator_memberships.each do |membership| %>
32
+ <% group.non_moderator_memberships.includes(participant: :activities).each do |membership| %>
33
33
  <% membership.participant.activities.in_the_past.each do |activity| %>
34
34
  <tr>
35
35
  <td class="not-displayed">
@@ -4,10 +4,11 @@
4
4
  </div>
5
5
 
6
6
  <div class="panel-body">
7
+ <% tasks = group.learning_tasks %>
7
8
  <% (1..study_length_in_weeks).each do |week_number| %>
8
9
  <h2>Week <%= week_number %></h2>
9
10
  <table class="table">
10
- <% group.learning_tasks.each do |task| %>
11
+ <% tasks.each do |task| %>
11
12
  <% if week_number == week_of_task(task) %>
12
13
  <%= render partial: "think_feel_do_engine/coach/group_dashboard/learning_lesson_row",
13
14
  collection: task.bit_core_content_module.content_providers,
@@ -18,4 +19,4 @@
18
19
  </table>
19
20
  <% end %>
20
21
  </div>
21
- </div>
22
+ </div>
@@ -11,14 +11,15 @@
11
11
  <th>week <%= current_week %></th>
12
12
  <% end %>
13
13
  </tr>
14
+ <% counts = ParticipantMetrics::WeeklyLoginsCount.fetch(group.id).map { |l| [[l.participant_id, l.week], l.count] }.to_h %>
14
15
  <% group.non_moderator_memberships.each do | membership | %>
15
16
  <tr>
16
17
  <td><%= membership.participant.display_name %></td>
17
18
  <% (1..study_length_in_weeks).each do |current_week| %>
18
- <td><%= membership.logins_by_week(current_week) %></td>
19
+ <td><%= counts[[membership.participant_id, current_week]] || 0 %></td>
19
20
  <% end %>
20
21
  </tr>
21
22
  <% end %>
22
23
  </table>
23
24
  </div>
24
- </div>
25
+ </div>
@@ -6,25 +6,17 @@
6
6
  </tr>
7
7
  <tr>
8
8
  <td><a href="#login_table">logins</a></td>
9
- <% (1..study_length_in_weeks).each do |current_week| %>
10
- <td><%= group.logins_by_week(current_week) %></td>
11
- <% end %>
9
+ <%= render "weekly_counts", klass: GroupMetrics::WeeklyLoginsCount, group_id: group.id, weeks: study_length_in_weeks %>
12
10
  </tr>
13
11
  <tr>
14
12
  <td><a href="#thoughts_table">thoughts</a></td>
15
- <% (1..study_length_in_weeks).each do |current_week| %>
16
- <td><%= group.thoughts_by_week(current_week) %></td>
17
- <% end %>
13
+ <%= render "weekly_counts", klass: GroupMetrics::WeeklyThoughtsCount, group_id: group.id, weeks: study_length_in_weeks %>
18
14
  </tr>
19
15
  <tr>
20
16
  <td><a href="#activities_past_table">activities past</a></td>
21
- <% (1..study_length_in_weeks).each do |current_week| %>
22
- <td><%= group.activities_past_by_week(current_week) %></td>
23
- <% end %>
17
+ <%= render "weekly_counts", klass: GroupMetrics::WeeklyActivitiesCount.in_the_past, group_id: group.id, weeks: study_length_in_weeks %>
24
18
  </tr>
25
19
  <tr>
26
20
  <td><a href="#activities_future_table">activities future</a></td>
27
- <% (1..study_length_in_weeks).each do |current_week| %>
28
- <td><%= group.activities_future_by_week(current_week) %></td>
29
- <% end %>
30
- </tr>
21
+ <%= render "weekly_counts", klass: GroupMetrics::WeeklyActivitiesCount.unscheduled_or_in_the_future, group_id: group.id, weeks: study_length_in_weeks %>
22
+ </tr>
@@ -1,24 +1,16 @@
1
1
  <tr>
2
2
  <td><a href="#on_the_mind_statements_table">on the mind statements</a></td>
3
- <% (1..study_length_in_weeks).each do |current_week| %>
4
- <td><%= group.on_the_mind_statements_by_week(current_week) %></td>
5
- <% end %>
3
+ <%= render "weekly_counts", klass: GroupMetrics::WeeklyOnTheMindStatementsCount, group_id: group.id, weeks: study_length_in_weeks %>
6
4
  </tr>
7
5
  <tr>
8
6
  <td><a href="#comments_table">comments</a></td>
9
- <% (1..study_length_in_weeks).each do |current_week| %>
10
- <td><%= group.comments_by_week(current_week) %></td>
11
- <% end %>
7
+ <%= render "weekly_counts", klass: GroupMetrics::WeeklyCommentsCount, group_id: group.id, weeks: study_length_in_weeks %>
12
8
  </tr>
13
9
  <tr>
14
10
  <td><a href="#goals_table">goals</a></td>
15
- <% (1..study_length_in_weeks).each do |current_week| %>
16
- <td><%= group.goals_by_week(current_week) %></td>
17
- <% end %>
11
+ <%= render "weekly_counts", klass: GroupMetrics::WeeklyGoalsCount, group_id: group.id, weeks: study_length_in_weeks %>
18
12
  </tr>
19
13
  <tr>
20
14
  <td><a href="#likes_table">likes</a></td>
21
- <% (1..study_length_in_weeks).each do |current_week| %>
22
- <td><%= group.likes_by_week(current_week) %></td>
23
- <% end %>
24
- </tr>
15
+ <%= render "weekly_counts", klass: GroupMetrics::WeeklyLikesCount, group_id: group.id, weeks: study_length_in_weeks %>
16
+ </tr>
@@ -25,7 +25,7 @@
25
25
  </tr>
26
26
  </thead>
27
27
  <tbody>
28
- <% group.non_moderator_memberships.each do |membership| %>
28
+ <% group.non_moderator_memberships.includes(participant: :thoughts).each do |membership| %>
29
29
  <% membership.participant.thoughts.each do |thought| %>
30
30
  <tr>
31
31
  <td class="not-displayed">
@@ -0,0 +1,4 @@
1
+ <% counts_by_week = klass.fetch(group_id).map { |c| [c.week, c.count] }.to_h %>
2
+ <% (1..study_length_in_weeks).each do |current_week| %>
3
+ <td><%= counts_by_week[current_week] || 0 %></td>
4
+ <% end %>
@@ -28,8 +28,8 @@
28
28
  // starting graph parameters assume 7 day view ending in today
29
29
  var activationDate = activation("<%= patient.created_at %>")
30
30
  var graphParameters = new Graph(
31
- <%= sanitize(patient.mood_rating_daily_averages.to_json) %>,
32
- <%= sanitize(patient.emotional_rating_daily_averages.to_json) %>,
31
+ <%= sanitize(MoodAndEmotionVisualizationService.new(patient).mood_rating_daily_averages.to_json) %>,
32
+ <%= sanitize(MoodAndEmotionVisualizationService.new(patient).emotional_rating_daily_averages.to_json) %>,
33
33
  <%= phq_features? ? sanitize(patient.phq_scores.to_json) : "null" %>,
34
34
  $("div#viz-container")
35
35
  );
@@ -1,43 +1,60 @@
1
1
  require "event_capture/events_controller"
2
-
3
- # Augment/override behavior.
4
- class EventCapture::EventsController
5
- before_action :authenticate_participant!
6
-
7
- def event_params
8
- {
9
- kind: params[:kind],
10
- payload: params[:payload].merge(remote_ip: request.remote_ip),
11
- emitted_at: params[:emittedAt],
12
- participant_id: current_participant.id
13
- }
14
- end
15
- end
16
-
17
2
  require "event_capture/event"
18
3
 
19
- class EventCapture::Event
20
- belongs_to :participant
21
-
22
- def self.next_event_for(event)
23
- where(participant_id: event.participant_id)
24
- .where.not(kind: "videoPlay")
25
- .where("emitted_at > ?", event.emitted_at)
26
- .select(:participant_id, :kind, :emitted_at)
27
- .order(:emitted_at)
28
- .limit(1)
29
- .first
30
- end
31
-
32
- def current_url
33
- payload[:currentUrl]
34
- end
35
-
36
- def button_html
37
- payload[:buttonHtml]
4
+ module EventCapture
5
+ # Augment/override behavior.
6
+ class EventsController
7
+ before_action :authenticate_participant!
8
+
9
+ def event_params
10
+ {
11
+ kind: params[:kind],
12
+ payload: payload_params.to_h.merge(remote_ip: request.remote_ip),
13
+ emitted_at: params[:emittedAt],
14
+ participant_id: current_participant.id
15
+ }
16
+ end
17
+
18
+ private
19
+
20
+ # Permit payload parameters defined in
21
+ # app/assets/javascripts/think_feel_do_engine/event_capture/init.js
22
+ def payload_params
23
+ params.require(:payload).permit(
24
+ :currentUrl,
25
+ :headers,
26
+ :ua,
27
+ :buttonHtml,
28
+ :parentHtml,
29
+ :headers
30
+ )
31
+ end
38
32
  end
39
33
 
40
- def headers
41
- payload[:headers]
34
+ # Augment behavior.
35
+ class Event
36
+ belongs_to :participant
37
+
38
+ def self.next_event_for(event)
39
+ where(participant_id: event.participant_id)
40
+ .where.not(kind: "videoPlay")
41
+ .where("emitted_at > ?", event.emitted_at)
42
+ .select(:participant_id, :kind, :emitted_at)
43
+ .order(:emitted_at)
44
+ .limit(1)
45
+ .first
46
+ end
47
+
48
+ def current_url
49
+ payload["currentUrl"]
50
+ end
51
+
52
+ def button_html
53
+ payload["buttonHtml"]
54
+ end
55
+
56
+ def headers
57
+ payload["headers"]
58
+ end
42
59
  end
43
60
  end
data/config/routes.rb CHANGED
@@ -41,8 +41,8 @@ ThinkFeelDoEngine::Engine.routes.draw do
41
41
  resources :slides do
42
42
  collection { post :sort }
43
43
  end
44
- get 'create_table_of_contents', controller: :slides
45
- get 'destroy_table_of_contents', controller: :slides
44
+ get "create_table_of_contents", controller: :slides
45
+ get "destroy_table_of_contents", controller: :slides
46
46
  resources :slideshow_anchors, only: [:create, :destroy]
47
47
  end
48
48
  end
@@ -85,7 +85,6 @@ ThinkFeelDoEngine::Engine.routes.draw do
85
85
  namespace :participants do
86
86
  resources :assessments, only: [:new, :create]
87
87
  resources :received_messages, only: :index
88
- resources :task_status, only: [:update]
89
88
  get "public_slideshows/:slideshow_id/slides/:id", to: "public_slides#show", as: "public_slideshow_slide"
90
89
  resources :thoughts, only: :create
91
90
  resources :activities, only: [:create, :update]
@@ -102,6 +101,5 @@ ThinkFeelDoEngine::Engine.routes.draw do
102
101
  end
103
102
 
104
103
  # catch all for unrecognized routes
105
- get "*unknown", to: redirect("/404")
106
- post "*unknown", to: redirect("/404")
104
+ match "/(*unmatched_route)", to: "application#raise_not_found!", via: :all
107
105
  end
@@ -24,7 +24,7 @@ namespace :lesson_notifications do
24
24
  participant_email: participant.email,
25
25
  lesson_module: content_module,
26
26
  application_name: application_name)
27
- .deliver
27
+ .deliver_now
28
28
 
29
29
  next unless defined?(Raven)
30
30
  Raven.capture_message "failed to email lesson notification",
@@ -1,4 +1,4 @@
1
1
  # nodoc
2
2
  module ThinkFeelDoEngine
3
- VERSION = "3.18.0"
3
+ VERSION = "3.19.0"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: think_feel_do_engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.18.0
4
+ version: 3.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Carty-Fickes
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-05-25 00:00:00.000000000 Z
11
+ date: 2016-06-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -500,6 +500,14 @@ files:
500
500
  - app/models/emotional_rating.rb
501
501
  - app/models/engagement.rb
502
502
  - app/models/group.rb
503
+ - app/models/group_metrics/weekly_activities_count.rb
504
+ - app/models/group_metrics/weekly_comments_count.rb
505
+ - app/models/group_metrics/weekly_count.rb
506
+ - app/models/group_metrics/weekly_goals_count.rb
507
+ - app/models/group_metrics/weekly_likes_count.rb
508
+ - app/models/group_metrics/weekly_logins_count.rb
509
+ - app/models/group_metrics/weekly_on_the_mind_statements_count.rb
510
+ - app/models/group_metrics/weekly_thoughts_count.rb
503
511
  - app/models/media_access_event.rb
504
512
  - app/models/membership.rb
505
513
  - app/models/message.rb
@@ -507,6 +515,8 @@ files:
507
515
  - app/models/mood.rb
508
516
  - app/models/participant.rb
509
517
  - app/models/participant_login_event.rb
518
+ - app/models/participant_metrics/weekly_count.rb
519
+ - app/models/participant_metrics/weekly_logins_count.rb
510
520
  - app/models/participant_token.rb
511
521
  - app/models/phq_assessment.rb
512
522
  - app/models/phq_stepping.rb
@@ -560,6 +570,7 @@ files:
560
570
  - app/presenters/think_feel_do_engine/harmful_thought_viz_presenter.rb
561
571
  - app/presenters/think_feel_do_engine/lesson_event/completion_data_presenter.rb
562
572
  - app/presenters/think_feel_do_engine/media_access_event_presenter.rb
573
+ - app/support/mood_and_emotion_visualization_service.rb
563
574
  - app/views/devise/mailer/reset_password_instructions.html.erb
564
575
  - app/views/devise/passwords/edit.html.erb
565
576
  - app/views/devise/passwords/new.html.erb
@@ -632,6 +643,7 @@ files:
632
643
  - app/views/think_feel_do_engine/coach/group_dashboard/_summary.html.erb
633
644
  - app/views/think_feel_do_engine/coach/group_dashboard/_summary_social.html.erb
634
645
  - app/views/think_feel_do_engine/coach/group_dashboard/_thoughts.html.erb
646
+ - app/views/think_feel_do_engine/coach/group_dashboard/_weekly_counts.html.erb
635
647
  - app/views/think_feel_do_engine/coach/group_dashboard/index.html.erb
636
648
  - app/views/think_feel_do_engine/coach/messages/index.html.erb
637
649
  - app/views/think_feel_do_engine/coach/messages/new.html.erb
@@ -975,7 +987,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
975
987
  version: '0'
976
988
  requirements: []
977
989
  rubyforge_project:
978
- rubygems_version: 2.5.1
990
+ rubygems_version: 2.6.4
979
991
  signing_key:
980
992
  specification_version: 4
981
993
  summary: Summary of ThinkFeelDoEngine.