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.
- 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.
|