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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/think_feel_do_engine/feel/draw_graphs.js +6 -0
- data/app/controllers/think_feel_do_engine/application_controller.rb +14 -1
- data/app/controllers/think_feel_do_engine/coach/site_messages_controller.rb +7 -2
- data/app/controllers/think_feel_do_engine/manage/tasks_controller.rb +5 -3
- data/app/helpers/think_feel_do_engine/coach/group_dashboard_helper.rb +2 -2
- data/app/models/arm.rb +1 -1
- data/app/models/content_providers/index_past_feel_provider.rb +6 -2
- data/app/models/delivered_message.rb +1 -1
- data/app/models/group.rb +1 -102
- data/app/models/group_metrics/weekly_activities_count.rb +20 -0
- data/app/models/group_metrics/weekly_comments_count.rb +12 -0
- data/app/models/group_metrics/weekly_count.rb +35 -0
- data/app/models/group_metrics/weekly_goals_count.rb +12 -0
- data/app/models/group_metrics/weekly_likes_count.rb +12 -0
- data/app/models/group_metrics/weekly_logins_count.rb +6 -0
- data/app/models/group_metrics/weekly_on_the_mind_statements_count.rb +12 -0
- data/app/models/group_metrics/weekly_thoughts_count.rb +6 -0
- data/app/models/membership.rb +2 -2
- data/app/models/participant.rb +2 -58
- data/app/models/participant_metrics/weekly_count.rb +39 -0
- data/app/models/participant_metrics/weekly_logins_count.rb +6 -0
- data/app/models/task.rb +1 -1
- data/app/models/task_status.rb +2 -2
- data/app/models/think_feel_do_engine/reports/user_agent.rb +1 -1
- data/app/support/mood_and_emotion_visualization_service.rb +66 -0
- data/app/views/think_feel_do_engine/coach/group_dashboard/_activities_future.html.erb +1 -1
- data/app/views/think_feel_do_engine/coach/group_dashboard/_activities_past.html.erb +1 -1
- data/app/views/think_feel_do_engine/coach/group_dashboard/_lessons.html.erb +3 -2
- data/app/views/think_feel_do_engine/coach/group_dashboard/_logins.html.erb +3 -2
- data/app/views/think_feel_do_engine/coach/group_dashboard/_summary.html.erb +5 -13
- data/app/views/think_feel_do_engine/coach/group_dashboard/_summary_social.html.erb +5 -13
- data/app/views/think_feel_do_engine/coach/group_dashboard/_thoughts.html.erb +1 -1
- data/app/views/think_feel_do_engine/coach/group_dashboard/_weekly_counts.html.erb +4 -0
- data/app/views/think_feel_do_engine/coach/patient_dashboards/_vizs.html.erb +2 -2
- data/config/initializers/event_capture.rb +53 -36
- data/config/routes.rb +3 -5
- data/lib/tasks/lesson_notifications.rake +1 -1
- data/lib/think_feel_do_engine/version.rb +1 -1
- metadata +15 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de032421673efdc9d72b69a4d3d521507a88247c
|
4
|
+
data.tar.gz: f40b0c0509e23bb16a22a3d39bc9190f88140caa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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).
|
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.
|
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.
|
148
|
+
if (task.release_day / 7.0).ceil == 0
|
149
149
|
1
|
150
150
|
else
|
151
|
-
(task.
|
151
|
+
(task.release_day / 7.0).ceil
|
152
152
|
end
|
153
153
|
end
|
154
154
|
end
|
data/app/models/arm.rb
CHANGED
@@ -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:
|
15
|
-
|
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
|
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
|
-
|
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,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
|
data/app/models/membership.rb
CHANGED
@@ -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 =
|
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 =
|
121
|
+
participant_login_events = ParticipantLoginEvent.arel_table
|
122
122
|
participant
|
123
123
|
.participant_login_events
|
124
124
|
.where(participant_login_events[:created_at]
|
data/app/models/participant.rb
CHANGED
@@ -77,7 +77,7 @@ class Participant < ActiveRecord::Base
|
|
77
77
|
scope :stepped, lambda {
|
78
78
|
joins(:memberships)
|
79
79
|
.where(
|
80
|
-
|
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
|
-
|
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
|
data/app/models/task.rb
CHANGED
data/app/models/task_status.rb
CHANGED
@@ -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
|
-
|
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 =
|
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[
|
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
|
-
<%
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
28
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
22
|
-
|
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">
|
@@ -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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
41
|
-
|
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
|
45
|
-
get
|
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
|
-
|
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
|
-
.
|
27
|
+
.deliver_now
|
28
28
|
|
29
29
|
next unless defined?(Raven)
|
30
30
|
Raven.capture_message "failed to email lesson notification",
|
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.
|
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-
|
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.
|
990
|
+
rubygems_version: 2.6.4
|
979
991
|
signing_key:
|
980
992
|
specification_version: 4
|
981
993
|
summary: Summary of ThinkFeelDoEngine.
|