think_feel_do_engine 3.18.0 → 3.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.