From 2a5602280ec851f613ecce2e0a43b0960d171fd4 Mon Sep 17 00:00:00 2001 From: Cameron Bothner Date: Tue, 23 Jan 2018 10:45:52 -0500 Subject: [PATCH 1/2] feat: add flag on deployments that a retrospective was prompted The flag should be set when an instructor is prompted to post a teaching retrospective in CaseLog --- .rubocop.yml | 1 + Gemfile | 2 +- Gemfile.lock | 13 ++++--------- app/models/deployment.rb | 5 +++++ ...d_retrospective_prompt_sent_at_to_deployments.rb | 5 +++++ db/structure.sql | 6 ++++-- 6 files changed, 20 insertions(+), 12 deletions(-) create mode 100644 db/migrate/20180129162300_add_retrospective_prompt_sent_at_to_deployments.rb diff --git a/.rubocop.yml b/.rubocop.yml index 7e988d932..ecdff66a2 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -28,6 +28,7 @@ Layout/ClassStructure: - attr_writer - attr_accessor - alias_attribute + - time_for_a_boolean - translates associations: - has_one diff --git a/Gemfile b/Gemfile index 82f7b439c..39e0a57fe 100644 --- a/Gemfile +++ b/Gemfile @@ -16,7 +16,7 @@ gem 'redis', '~> 3.0' gem 'acts_as_list' gem 'kaminari' gem 'memoist' -gem 'time_for_a_boolean', git: 'https://github.com/calebthompson/time_for_a_boolean' +gem 'time_for_a_boolean', '~> 0.2.0' gem 'virtus' # Authentication and Authorization diff --git a/Gemfile.lock b/Gemfile.lock index 05422f7f1..fd39900a5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -14,14 +14,6 @@ GIT user_agent_parser uuidtools -GIT - remote: https://github.com/calebthompson/time_for_a_boolean - revision: 636a4c6b4645f9888402ff49b40b7f42c270d946 - specs: - time_for_a_boolean (0.1.0) - activerecord - railties - GIT remote: https://github.com/cbothner/omniauth-lti revision: 578245b0cabb755a209221ce1682667d9154ac4d @@ -395,6 +387,9 @@ GEM thor (0.19.4) thread_safe (0.3.6) tilt (2.0.8) + time_for_a_boolean (0.2.0) + activerecord + railties tzinfo (1.2.3) thread_safe (~> 0.1) uglifier (3.2.0) @@ -487,7 +482,7 @@ DEPENDENCIES spring-commands-rspec spring-watcher-listen (~> 2.0.0) table_print - time_for_a_boolean! + time_for_a_boolean (~> 0.2.0) uglifier (>= 1.3.0) virtus web-console diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 4555eaaad..d123db795 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -11,11 +11,16 @@ # - 0 means no quiz (and assumes quiz == nil) # @attr key [String] a random URL safe string used as the hard-to-guess # identifier that allows the {MagicLink} to work +# @attr retrospective_prompt_sent_at [DateTime] at most once after a professor +# has deployed a case, we want to prompt them to write up their experience +# teaching with the case as a post in CaseLog # # @see GenericDeployment GenericDeployment: this model’s null object class Deployment < ApplicationRecord include Authority::Abilities + time_for_a_boolean :retrospective_prompt_sent + belongs_to :case belongs_to :group belongs_to :quiz diff --git a/db/migrate/20180129162300_add_retrospective_prompt_sent_at_to_deployments.rb b/db/migrate/20180129162300_add_retrospective_prompt_sent_at_to_deployments.rb new file mode 100644 index 000000000..6c5a71c7e --- /dev/null +++ b/db/migrate/20180129162300_add_retrospective_prompt_sent_at_to_deployments.rb @@ -0,0 +1,5 @@ +class AddRetrospectivePromptSentAtToDeployments < ActiveRecord::Migration[5.1] + def change + add_column :deployments, :retrospective_prompt_sent_at, :timestamp + end +end diff --git a/db/structure.sql b/db/structure.sql index 2cf25af5e..2e7283741 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -423,7 +423,8 @@ CREATE TABLE deployments ( created_at timestamp without time zone NOT NULL, updated_at timestamp without time zone NOT NULL, answers_needed integer DEFAULT 1, - key character varying + key character varying, + retrospective_prompt_sent_at timestamp without time zone ); @@ -2309,6 +2310,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20171113192541'), ('20171220165301'), ('20180119170858'), -('20180129143420'); +('20180129143420'), +('20180129162300'); From d0fa4c31be61071714a1c7d72ff1b145a6e2ef1b Mon Sep 17 00:00:00 2001 From: Cameron Bothner Date: Mon, 29 Jan 2018 09:30:18 -0500 Subject: [PATCH 2/2] feat: implement method of calculating which instructors to prompt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After a professor has deployed a case in their classroom, we want to invite them to write up a retrospective of their experience as a post in the instructors-only community “CaseLog.” We look for deployments whose members were much more active in the case 14 to 7 days ago than they were in the last 7 days. This is defined as an 80% drop-off of use from wee to week, where the previous week measured over 500 events. We exclude any deployments with fewer than five readers as a sanity check. These magic numbers were chosen empirically based on recorded data at an early stage and should be subject to review. --- app/services/send_retrospective_prompts.rb | 65 ++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 app/services/send_retrospective_prompts.rb diff --git a/app/services/send_retrospective_prompts.rb b/app/services/send_retrospective_prompts.rb new file mode 100644 index 000000000..d38864922 --- /dev/null +++ b/app/services/send_retrospective_prompts.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +# After a professor has deployed a case in their classroom, we want to invite +# them to write up a retrospective of their experience as a post in the +# instructors-only community “CaseLog.” This service sends such notifications. +# +# Running once a week, on Wednesdays at noon (Eastern), we look for deployments +# whose members were much more active in the case 14 to 7 days ago than they +# were in the last 7 days. This is defined as an 80% drop-off of use from week +# to week, where the previous week measured over 500 events. This was chosen +# empirically based on recorded data at an early stage and should be subject to +# review. We exclude any deployments with fewer than five readers as a sanity +# check. When we send prompts to the instructors, we set the +# `retrospective_prompt_sent` flag so never to bug people more than once. +class SendRetrospectivePrompts + def self.call(time_basis: Time.zone.now) + new(time_basis).call + end + + def initialize(now) + @now = now + end + + def call + deployments_needing_prompts.map { |x| [x.group.name, x.case.slug] } + # deployments_needing_prompts.each do |deployment| + # deployment.update retrospective_prompt_set: true + # DeploymentMailer.retrospective_prompt(deployment).deliver + # end + end + + private + + def deployments_needing_prompts + candidate_deployments_with_enough_readers + .select { |d| much_more_use_two_weeks_ago d } + end + + def candidate_deployments_with_enough_readers + count_readers_by_group = GroupMembership.group(:group_id).count(:reader_id) + Deployment.where(retrospective_prompt_sent_at: nil) + .select { |d| count_readers_by_group[d.group_id] > 5 } + end + + def much_more_use_two_weeks_ago(deployment) + two_weeks_ago = count_interesting_events( + deployment, + (@now - 2.weeks)..(@now - 1.week) + ) + last_week = count_interesting_events( + deployment, + (@now - 1.week)..@now + ) + + two_weeks_ago > 500 && (two_weeks_ago - last_week) / two_weeks_ago > -0.8 + end + + def count_interesting_events(deployment, range) + Ahoy::Event.interesting + .where(user_id: deployment.group.readers.pluck(:id)) + .where_properties(case_slug: deployment.case.slug) + .where(time: range) + .count + end +end