Skip to content
This repository has been archived by the owner on Mar 29, 2022. It is now read-only.

Gifted Stickers for incomplete users #437

Merged
merged 55 commits into from
Nov 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
b997ebb
Set up new event + state for gifted_sticker
fridaland Nov 13, 2019
f5c279a
Add presence of sticker coupon validation for gifted_sticker state
fridaland Nov 13, 2019
56f3b4f
Remove unecessary user.register call in "win" describe block
fridaland Nov 13, 2019
2573658
Add specs for #gifted_sticker event
fridaland Nov 13, 2019
b26ef90
Add gifted_sticker method to UserStateTransitionSegmentService
fridaland Nov 13, 2019
e83c023
Use gifted_sticker_coupon method and delegate gift_coupon
fridaland Nov 13, 2019
fc083b3
Add gift_coupon method to coupon service
fridaland Nov 13, 2019
d1ea8f0
Test gift_sticker context in UserStateTransitionSegmentService
fridaland Nov 13, 2019
faff518
Fix context syntax
fridaland Nov 13, 2019
fe3c7be
Fix gift_sticker_coupon indentation
fridaland Nov 13, 2019
7610b82
Clean up user_state_transition_segment_service.rb
fridaland Nov 13, 2019
232903b
Lint user model and and spec
fridaland Nov 13, 2019
ee02183
Test gift_coupon method in CouponService
fridaland Nov 13, 2019
965111e
Use event name, not state in UserTransitionSegmentService
fridaland Nov 13, 2019
7ffc4a8
Fix context syntax
fridaland Nov 13, 2019
67c12d6
Use event and pass in symbol to transition stub
fridaland Nov 13, 2019
ac40fa5
Private coupon methods for shirts and stickers, delete gift_coupon
fridaland Nov 13, 2019
a1a8a83
Change gift_sticker to gifted
fridaland Nov 13, 2019
f755f70
Use assign_coupon to follow single responsibility principle
fridaland Nov 13, 2019
a121267
Use assign_coupon method
fridaland Nov 13, 2019
ecaabc9
Update gift state + event + method names
fridaland Nov 13, 2019
2f19c8b
Use @user instance method
fridaland Nov 13, 2019
46bf99f
Correct user.gift call
fridaland Nov 13, 2019
cf4c1d7
Add initial rake task scaffolding for gifting stickers
jhaff Nov 13, 2019
ae48183
Add GiftStickersService
jhaff Nov 13, 2019
ad837dc
Lint stickers.rake
fridaland Nov 13, 2019
793487f
Fix bad logic in Coupon Service
fridaland Nov 13, 2019
4497069
Fix indentation in GiftStickerService + User model
fridaland Nov 13, 2019
15e9c04
Remove #gifted, add context to #assign_coupon
fridaland Nov 14, 2019
bc9c0b3
Delete vcr cassette
fridaland Nov 14, 2019
0edb020
Delete vcr cassette
fridaland Nov 14, 2019
765a2cf
Delete vcr cassette
fridaland Nov 14, 2019
0830596
Change inactive to incomplete
fridaland Nov 14, 2019
a8bafe9
Uncomment GiftStickerService
fridaland Nov 14, 2019
c69bb14
Fix sorting logic
fridaland Nov 14, 2019
c65d51f
Add Receipt helper
fridaland Nov 14, 2019
87a3140
Test GiftStickers Service obj
fridaland Nov 14, 2019
2472b31
Fix rspec failures; reload user before checking its sticker_coupon
jhaff Nov 14, 2019
1ae6e4c
Linting
jhaff Nov 14, 2019
8949658
Clean up GiftStickersService
jhaff Nov 14, 2019
e0b1269
Make GiftStickersService specs more specific
jhaff Nov 14, 2019
4e5048f
Reminder to allow shirt gifts next year
fridaland Nov 14, 2019
05c6fd6
Missing whitespace
fridaland Nov 14, 2019
15c617b
Prefer incompleted
fridaland Nov 14, 2019
3cd2404
comment to account for potential gifting of shirts
fridaland Nov 14, 2019
2335c6c
Rename GiftStickerService to GiftService
fridaland Nov 14, 2019
4885d7e
Use max_by, clean up logic
fridaland Nov 14, 2019
e537053
Split comments/fix line-lenght
fridaland Nov 14, 2019
086d90c
Add pending tests to be re-added for shirt giftning logic next year
fridaland Nov 14, 2019
b8e1cda
Change gifted to gift
fridaland Nov 14, 2019
da952a2
Rename rake task
fridaland Nov 14, 2019
c8b82fe
Improve variable names
fridaland Nov 18, 2019
2cb19c5
Add comments + update array var name
fridaland Nov 18, 2019
4b3af43
Add context to user spec
fridaland Nov 18, 2019
adcf1de
Improve context description
fridaland Nov 18, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ class User < ApplicationRecord
transition registered: :incompleted
end

event :gifted do
transition incompleted: :gifted_sticker,
if: ->(user) { user.sticker_coupon }
end

event :retry_complete do
transition incompleted: :completed
end
Expand All @@ -48,7 +53,7 @@ class User < ApplicationRecord
validates :shirt_coupon, absence: true
end

state all - [:won_sticker] do
state all - %i[won_sticker gifted_sticker] do
validates :sticker_coupon, absence: true
end

Expand Down Expand Up @@ -88,6 +93,15 @@ def win
in: [true], message: 'hacktoberfest has not yet ended' }
validates :sufficient_eligible_prs?, inclusion: {
in: [false], message: 'user has too many sufficient eligible prs' }

def gift
assign_coupon
gifted
end
end

state :gifted_sticker do
validates :sticker_coupon, presence: true
end

before_transition do |user, _transition|
Expand Down
20 changes: 15 additions & 5 deletions app/services/coupon_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ def initialize(user)
end

def assign_coupon
if shirt_coupon.present?
@user.association(:shirt_coupon).replace(shirt_coupon, false)
elsif sticker_coupon.present?
@user.association(:sticker_coupon).replace(sticker_coupon, false)
end
assign_sticker_coupon unless assign_shirt_coupon
end

private
Expand All @@ -22,4 +18,18 @@ def shirt_coupon
def sticker_coupon
StickerCoupon.first_available
end

def assign_shirt_coupon
# TODO: Remove this return statement to allow gifting of extra shirt
# coupons in future Hacktoberfest events.
return unless @user.completed? && shirt_coupon.present?
fridaland marked this conversation as resolved.
Show resolved Hide resolved

@user.association(:shirt_coupon).replace(shirt_coupon, false)
end

def assign_sticker_coupon
return if sticker_coupon.blank?

@user.association(:sticker_coupon).replace(sticker_coupon, false)
end
end
36 changes: 36 additions & 0 deletions app/services/gift_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

module GiftService
module_function

def call
user_dates = []

User.where(state: 'incompleted').find_in_batches do |group|
group.each do |u|
last_pr = u.receipt.max_by do |pr_obj|
Time.zone.parse(
pr_obj['github_pull_request']['graphql_hash']['createdAt']
)
end
date = last_pr['github_pull_request']['graphql_hash']['createdAt']
score = u.receipt.count
user_dates << { id: u.id, score: score, date: date }
end
end

sorted = user_dates.sort do |a, b|
a_date = Time.zone.parse(a[:date])
b_date = Time.zone.parse(b[:date])

# This comparison will prioritize first the score of the objects and then
# their dates.
[b[:score], a_date] <=> [a[:score], b_date]
johndbritton marked this conversation as resolved.
Show resolved Hide resolved
end

sorted.each do |user_date|
u = User.find(user_date[:id])
u.gift
end
end
end
6 changes: 6 additions & 0 deletions app/services/user_state_transition_segment_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def call(user, transition)
when :incomplete then incomplete(user)
when :ineligible then ineligible(user)
when :won then won(user, transition)
when :gift_sticker then gift_sticker(user)
end
end

Expand Down Expand Up @@ -61,6 +62,11 @@ def won(user, transition)
end
end

def gift_sticker(user)
segment(user).identify(state: 'gifted_sticker')
segment(user).track('user_gifted_sticker')
end

def segment(user)
SegmentService.new(user)
end
Expand Down
29 changes: 29 additions & 0 deletions lib/tasks/gifts.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

namespace :gift do
# TODO: Update this rake task to reflect giving users any remaining available
# shirt coupons when that is implemented in `CouponService`
desc 'gift remaining stickers to incompleted users'
task stickers: :environment do
sticker_coupon_count = StickerCoupon.where(user_id: nil).count

incompleted_user_count = User.where(state: 'incompleted').count

message = "There are #{sticker_coupon_count} stickers remaining.
There are #{incompleted_user_count} users who are incompleted
and eligible for gift_sticker.

Do you want to gift the remaining coupons to incompleted users? (y/n)"

Rails.logger.info message

input = STDIN.gets.strip

if input == 'y'
Rails.logger.info 'Gifting coupons...'
GiftService.call
else
Rails.logger.info 'Terminating.'
end
end
end
43 changes: 41 additions & 2 deletions spec/models/user_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -519,12 +519,51 @@
end
end

describe '#gift' do
before do
allow(UserStateTransitionSegmentService)
.to receive(:gift_sticker).and_return(true)
end

context 'the user is in the incompleted state' do
let(:user) { FactoryBot.create(:user, :incompleted) }

context 'there are shirt coupons available' do
before do
FactoryBot.create(:shirt_coupon)
end

# TODO: Edit this spec to allow ability for an incomplete user to
# receive a gifted shirt and therefore entering gifted_shirt state.
it 'does not transition user to gifted_shirt state' do
expect(user.state).to_not eq('gifted_shirt')
end
end

context 'there are sticker coupons available' do
johndbritton marked this conversation as resolved.
Show resolved Hide resolved
before do
FactoryBot.create(:sticker_coupon)
end

it 'transitions the user to gifted_sticker state' do
user.gift
expect(user.state).to eq('gifted_sticker')
end
end

context 'there are no shirt coupons or sticker coupons available' do
it 'does not transition the user' do
user.gift
expect(user.state).to eq('incompleted')
end
end
end
end

describe '#win' do
before do
allow(UserStateTransitionSegmentService)
.to receive(:won).and_return(true)

user.register
johndbritton marked this conversation as resolved.
Show resolved Hide resolved
end
context 'the user is in the completed state' do
let(:user) { FactoryBot.create(:user, :completed) }
Expand Down
1 change: 1 addition & 0 deletions spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def segment_write_key
config.include AuthenticationHelper
config.include PullRequestFilterHelper
config.include GraphqlClientHelper
config.include UserReceiptHelper

config.infer_spec_type_from_file_location!

Expand Down
155 changes: 114 additions & 41 deletions spec/services/coupon_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,67 +3,140 @@
require 'rails_helper'

RSpec.describe CouponService do
let(:user) { FactoryBot.create(:user, :completed) }
let(:coupon_service) { CouponService.new(user) }

describe '#assign_coupon' do
context 'there are only shirt coupons available' do
before do
FactoryBot.create(:shirt_coupon)
coupon_service.assign_coupon
end
context 'with a completed user' do
let(:user) { FactoryBot.create(:user, :completed) }
let(:coupon_service) { CouponService.new(user) }

it 'assigns the user a shirt coupon' do
expect(user.shirt_coupon).to be_a(ShirtCoupon)
end
context 'there are only shirt coupons available' do
fridaland marked this conversation as resolved.
Show resolved Hide resolved
before do
FactoryBot.create(:shirt_coupon)
coupon_service.assign_coupon
end

it 'does not assign a sticker coupon' do
expect(user.sticker_coupon).to eq(nil)
it 'assigns the user a shirt coupon' do
expect(user.shirt_coupon).to be_a(ShirtCoupon)
end

it 'does not assign a sticker coupon' do
expect(user.sticker_coupon).to eq(nil)
end
end
end

context 'there are only sticker coupons available' do
before do
FactoryBot.create(:sticker_coupon)
coupon_service.assign_coupon
context 'there are only sticker coupons available' do
before do
FactoryBot.create(:sticker_coupon)
coupon_service.assign_coupon
end

it 'assigns the user a sticker coupon' do
expect(user.sticker_coupon).to be_a(StickerCoupon)
end

it 'does not assign a shirt coupon' do
expect(user.shirt_coupon).to eq(nil)
end
end

it 'assigns the user a sticker coupon' do
expect(user.sticker_coupon).to be_a(StickerCoupon)
context 'there are both shirt and sticker coupons available' do
before do
FactoryBot.create(:shirt_coupon)
FactoryBot.create(:sticker_coupon)
coupon_service.assign_coupon
end

it 'assigns the user a shirt coupon' do
expect(user.shirt_coupon).to be_a(ShirtCoupon)
end

it 'does not assign a sticker coupon' do
expect(user.sticker_coupon).to eq(nil)
end
end

it 'does not assign a shirt coupon' do
expect(user.shirt_coupon).to eq(nil)
context 'there are neither shirt nor sticker coupons available' do
before do
coupon_service.assign_coupon
end

it 'does not assign a shirt coupon' do
expect(user.shirt_coupon).to eq(nil)
end

it 'does not assign a sticker coupon' do
expect(user.sticker_coupon).to eq(nil)
end
end
end

context 'there are both shirt and sticker coupons available' do
before do
FactoryBot.create(:shirt_coupon)
FactoryBot.create(:sticker_coupon)
coupon_service.assign_coupon
end
context 'with an incompleted user' do
let(:user) { FactoryBot.create(:user, :incompleted) }
let(:coupon_service) { CouponService.new(user) }

it 'assigns the user a shirt coupon' do
expect(user.shirt_coupon).to be_a(ShirtCoupon)
end
context 'there are only shirt coupons available' do
before do
FactoryBot.create(:shirt_coupon)
coupon_service.assign_coupon
end

# TODO: Add this test to allow gifting of extra shirt
# coupons in future Hacktoberfest events.
xit 'assigns the user a shirt coupon' do
expect(user.shirt_coupon).to be_a(ShirtCoupon)
end

it 'does not assign a sticker coupon' do
expect(user.sticker_coupon).to eq(nil)
it 'does not assign a sticker coupon' do
expect(user.sticker_coupon).to eq(nil)
end
end
end

context 'there are neither shirt nor sticker coupons available' do
before do
coupon_service.assign_coupon
context 'there are both shirt and sticker coupons available' do
before do
FactoryBot.create(:shirt_coupon)
FactoryBot.create(:sticker_coupon)
coupon_service.assign_coupon
end

# TODO: Add this test to allow gifting of extra shirt
# coupons in future Hacktoberfest events.
xit 'assigns the user a shirt coupon' do
expect(user.shirt_coupon).to be_a(ShirtCoupon)
end

# TODO: Add this test to allow gifting of extra shirt
# coupons in future Hacktoberfest events.
xit 'does not assign a sticker coupon' do
expect(user.sticker_coupon).to eq(nil)
end
johndbritton marked this conversation as resolved.
Show resolved Hide resolved
end

it 'does not assign a shirt coupon' do
expect(user.shirt_coupon).to eq(nil)
context 'there are only sticker coupons available' do
before do
FactoryBot.create(:sticker_coupon)
coupon_service.assign_coupon
end

it 'assigns the user a sticker coupon' do
expect(user.sticker_coupon).to be_a(StickerCoupon)
end

it 'does not assign a shirt coupon' do
expect(user.shirt_coupon).to eq(nil)
end
end

it 'does not assign a sticker coupon' do
expect(user.sticker_coupon).to eq(nil)
context 'there are no coupons available' do
before do
coupon_service.assign_coupon
end

it 'does not assign a shirt coupon' do
expect(user.shirt_coupon).to eq(nil)
end

it 'does not assign the user a sticker coupon' do
expect(user.sticker_coupon).to eq(nil)
end
end
end
end
Expand Down
Loading