Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion app/assets/stylesheets/_bootstrap-custom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ $carousel-control-width: 3rem;
@import "bootstrap/pagination";
@import "bootstrap/badge";
@import "bootstrap/alert";
// @import "bootstrap/progress";
@import "bootstrap/progress";
@import "bootstrap/list-group";
@import "bootstrap/close";
// @import "bootstrap/toasts";
Expand Down
1 change: 1 addition & 0 deletions app/controllers/admin/chapters_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def show
@sponsors = @chapter.sponsors.uniq
@groups = @chapter.groups
@subscribers = @chapter.subscriptions.last(20).reverse
@how_you_found_us = HowYouFoundUsPresenter.new(@chapter)
end

def edit
Expand Down
49 changes: 49 additions & 0 deletions app/presenters/how_you_found_us_presenter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
class HowYouFoundUsPresenter

def initialize(chapter)
@chapter = chapter
end

def by_percentage
return how_values.to_h { |how| [how, 0] } unless data_present?

# use the largest remainder algorithm so that percentages are whole
# numbers but always add up to 100
# https://stackoverflow.com/a/13483710/
entries = how_values.map do |how|
count = raw_stats.fetch(how, 0)
exact = (count / total_responses.to_f) * 100
percentage_value = exact.floor
remainder = exact - percentage_value
{ how: how, percentage_value: percentage_value, remainder: remainder }
end

allocated_so_far = entries.sum { |entry| entry[:percentage_value] }
left_to_allocate = 100 - allocated_so_far

entries
.sort_by { |entry| [-entry[:remainder], entry[:how].to_s] }
.first(left_to_allocate)
.each { |entry| entry[:percentage_value] += 1 }

entries.to_h { |entry| [entry[:how], entry[:percentage_value]] }
end

def total_responses
raw_stats.values.sum(0)
end

def data_present?
total_responses.positive?
end

private

def raw_stats
@stats ||= @chapter.members.where.not(how_you_found_us: nil).group(:how_you_found_us).count
end

def how_values
Member.how_you_found_us.keys
end
end
45 changes: 31 additions & 14 deletions app/views/admin/chapters/show.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,38 @@
%span.badge.bg-warning.text-dark Phone no. not set
- if current_user.is_admin?
= link_to 'Edit organisers', admin_chapter_organisers_path(@chapter), class: 'btn btn-primary btn-sm'
%ul.nav.flex-column.ms-0.mb-4
- @groups.each do |group|
%li.nav-item
= link_to [ :admin, group ], class: 'nav-link' do
#{group.name} (#{group.members.count})
%li.nav-item
= link_to admin_chapter_members_path(@chapter, type: group.name.downcase), class: 'nav-link' do
View #{group.name} emails
%li.nav-item
= link_to 'View all sponsors', admin_sponsors_path, class: 'nav-link'
%li.nav-item
= link_to 'View all workshops', admin_chapter_workshops_path(@chapter), class: 'nav-link'

.col-12.col-lg-7.offset-lg-1
.mb-4
.card.border-info.my-4.my-md-0.my-lg-4.ms-md-4.ms-lg-0
.card-body
%ul.nav.flex-column.ms-0.mb-0
- @groups.each do |group|
%li.nav-item
= link_to [ :admin, group ], class: 'nav-link' do
#{group.name} (#{group.members.count})
%li.nav-item
= link_to admin_chapter_members_path(@chapter, type: group.name.downcase), class: 'nav-link' do
View #{group.name} emails
%li.nav-item
= link_to 'View all sponsors', admin_sponsors_path, class: 'nav-link'
%li.nav-item
= link_to 'View all workshops', admin_chapter_workshops_path(@chapter), class: 'nav-link'

- if @how_you_found_us.data_present?
.card.border-info.my-4.my-md-0.my-lg-4.ms-md-4.ms-lg-0
.card-body
%h3 How members found this chapter
- @how_you_found_us.by_percentage.each do |(how, percent)|
- label = t("member.details.edit.how_you_found_us_options.#{how}")
.mb-3
.d-flex.justify-content-between
%div= label
%div #{percent}%
.progress
.progress-bar.bg-primary{ role: 'progressbar', style: "width: #{percent}%", "aria-valuenow": percent, "aria-valuemin": 0, "aria-valuemax": 100 }
%div.text-muted.small= "Based on #{pluralize(@how_you_found_us.total_responses, 'response')}"

.col-12.col-lg-8
.mb-4.mt-md-4.mt-lg-0
.d-md-flex.justify-content-between.align-items-center
%h3 Upcoming Workshops
= link_to 'New workshop', new_admin_workshop_path, class: 'btn btn-primary btn-sm'
Expand Down
25 changes: 25 additions & 0 deletions spec/features/admin/chapters_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,29 @@
expect(page).not_to have_content(coach_email)
end
end

context 'how you found us card' do
let(:chapter) { Fabricate(:chapter) }
let(:group) { Fabricate(:group, chapter: chapter) }

before do
login_as_admin(member)
end

scenario 'shows the card when there are responses' do
member_with_response = Fabricate(:member, how_you_found_us: :from_a_friend)
Fabricate(:subscription, member: member_with_response, group: group)

visit admin_chapter_path(chapter)

expect(page).to have_content('How members found this chapter')
expect(page).to have_content('Based on 1 response')
end

scenario 'does not show the card when there are no responses' do
visit admin_chapter_path(chapter)

expect(page).not_to have_content('How members found this chapter')
end
end
end
72 changes: 72 additions & 0 deletions spec/presenters/how_you_found_us_presenter_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
RSpec.describe HowYouFoundUsPresenter do
def add_member(group, how)
member = Fabricate(:member, how_you_found_us: how)
Fabricate(:subscription, member: member, group: group)
member
end

def add_member_without_how(group)
member = Fabricate(:member, how_you_found_us: nil)
Fabricate(:subscription, member: member, group: group)
member
end

let(:chapter) { Fabricate(:chapter_without_organisers) }
let(:group) { Fabricate(:group, chapter: chapter) }
let(:presenter) { HowYouFoundUsPresenter.new(chapter) }

describe '#by_percentage' do
it 'returns integer percentages for all enum values in enum order using largest remainder rounding' do
add_member(group, :from_a_friend)
add_member(group, :search_engine)
add_member(group, :search_engine)
add_member(group, :social_media)
add_member(group, :social_media)
add_member(group, :social_media)

expect(presenter.by_percentage).to eq(
{
'from_a_friend' => 17,
'search_engine' => 33,
'social_media' => 50,
'codebar_host_or_partner' => 0,
'other' => 0
}
)
expect(presenter.by_percentage.values.sum).to eq(100)
end

it 'returns all enum values with zeros when there is no data' do
expect(presenter.by_percentage).to eq(
{
'from_a_friend' => 0,
'search_engine' => 0,
'social_media' => 0,
'codebar_host_or_partner' => 0,
'other' => 0
}
)
end
end

describe '#total_responses' do
it 'sums the counts' do
add_member(group, :from_a_friend)
add_member(group, :search_engine)

expect(presenter.total_responses).to eq(2)
end
end

describe '#data_present?' do
it 'returns true when there are responses' do
add_member(group, :from_a_friend)

expect(presenter.data_present?).to eq(true)
end

it 'returns false when there are no responses' do
expect(presenter.data_present?).to eq(false)
end
end
end
Loading