Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic channel #69

Merged
merged 39 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
3897d0d
Include linux arch
tuxagon Sep 27, 2023
35c71a3
Add matchmaking_groups table
tuxagon Sep 27, 2023
292946e
Add docker for dev to alleviate postgres frustrations
tuxagon Sep 27, 2023
30ee4ce
Add CollectGroups
alimi Oct 12, 2023
feb2141
Implement CollectsGroups
tuxagon Oct 25, 2023
dae313b
Use CollectsGroups in RunsMatchmaking
tuxagon Oct 25, 2023
98543d5
Add MatchmakingGroups index
alimi Nov 8, 2023
e699d0b
fixup! Add MatchmakingGroups index
alimi Nov 8, 2023
d650edf
Set up UI workflow for matchmaking groups
tuxagon Dec 6, 2023
0e87825
Merge remote-tracking branch 'origin/main' into dynamic-channel
tuxagon Jan 17, 2024
3e5da6e
Add timecop gem
tuxagon Jan 17, 2024
61e403a
Refactor to support matchmaking changes
tuxagon Jan 17, 2024
9c0f936
Fix typo in MatchmakingGroupsController
alimi Jan 17, 2024
cc31e30
Style MatchmakingGroups UI
alimi Jan 17, 2024
1b89b0f
Fix update bug
alimi Jan 17, 2024
720bdf2
Add unique constraint to matchmaking_groups name col
tuxagon Jan 31, 2024
fff378c
Introduce flash for matchmatching groups
tuxagon Jan 31, 2024
15cb176
Refactor CollectsGroups
tuxagon Feb 14, 2024
b8eef35
s/matchmaking_config/build_config in test helper
tuxagon Feb 14, 2024
d4bbea3
Refactor ChooseStrategy
tuxagon Feb 14, 2024
162ff88
Add group_with test helper
tuxagon Feb 14, 2024
acd7367
Refactor CollectScoredParticipants
tuxagon Feb 14, 2024
0a48299
Refactor MatchParticipants
tuxagon Feb 14, 2024
62eff6d
Refactor EstablishMatchesForGroup
tuxagon Feb 14, 2024
1aff0c5
Refactor RunsMatchmaking
tuxagon Feb 14, 2024
e77cbd1
s/Runs/Run in service name
tuxagon Feb 14, 2024
29feaa7
Remove unused test helper
tuxagon Feb 14, 2024
80887b2
fixup! s/Runs/Run in service name
tuxagon Feb 14, 2024
be60b49
s/Sends/Send in service name
tuxagon Feb 14, 2024
8cb8e94
Refactor SendPendingNotifications
tuxagon Feb 15, 2024
775c4cd
Add validations and extra fields to MatchmakingGroup
tuxagon Feb 15, 2024
facb8df
Refactor how config is handled in tests
tuxagon Feb 15, 2024
a3cae0d
Refactor to fix UI for matchmaking groups
tuxagon Feb 15, 2024
a7bf6ee
Add integration test for matchmaking groups controller
tuxagon Feb 15, 2024
3e39295
HACK prevent segfault in test & development with pg
tuxagon Feb 15, 2024
e35552f
Slightly larger refactor to get notifications working again
tuxagon Feb 15, 2024
60325f6
fixup! Slightly larger refactor to get notifications working again
tuxagon Feb 15, 2024
6ffd408
CI
tuxagon Feb 15, 2024
0db6418
Strip whitespace from group_name
tuxagon Feb 23, 2024
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
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ group :test do
gem "mocktail"
gem "rspec"
gem "rspec-rails"
gem "timecop"
end
7 changes: 7 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ GEM
net-smtp (0.3.3)
net-protocol
nio4r (2.5.8)
nokogiri (1.13.9-aarch64-linux)
racc (~> 1.4)
nokogiri (1.13.9-arm64-darwin)
racc (~> 1.4)
nokogiri (1.13.9-x86_64-darwin)
Expand Down Expand Up @@ -270,13 +272,16 @@ GEM
rubocop-performance (~> 1.19.1)
stimulus-rails (1.2.1)
railties (>= 6.0.0)
tailwindcss-rails (2.0.21-aarch64-linux)
railties (>= 6.0.0)
tailwindcss-rails (2.0.21-arm64-darwin)
railties (>= 6.0.0)
tailwindcss-rails (2.0.21-x86_64-darwin)
railties (>= 6.0.0)
tailwindcss-rails (2.0.21-x86_64-linux)
railties (>= 6.0.0)
thor (1.2.1)
timecop (0.9.8)
timeout (0.3.0)
todo_or_die (0.1.1)
turbo-rails (1.3.2)
Expand All @@ -292,6 +297,7 @@ GEM
zeitwerk (2.6.6)

PLATFORMS
aarch64-linux
arm64-darwin-20
arm64-darwin-21
arm64-darwin-22
Expand Down Expand Up @@ -322,6 +328,7 @@ DEPENDENCIES
sprockets-rails
standard
tailwindcss-rails
timecop
todo_or_die

RUBY VERSION
Expand Down
10 changes: 10 additions & 0 deletions app/assets/stylesheets/application.tailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@
@tailwind components;
@tailwind utilities;

td,th {
@apply p-2;
}

.actions {
a {
@apply px-1 mx-1 border border-black;
}
}

/*

@layer components {
Expand Down
55 changes: 55 additions & 0 deletions app/controllers/matchmaking_groups_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
class MatchmakingGroupsController < ApplicationController
def index
@groups = CollectGroups.new.call.sort_by { |group| [group.readonly? ? 0 : 1, group.name] }
end

def new
@group = MatchmakingGroup.new
end

def create
if MatchmakingGroup.name_exists?(group_params[:name])
flash[:error] = "Group already exists"
else
MatchmakingGroup.create(group_params.merge(slack_user_id: @current_user.slack_user_id))
flash[:notice] = "Group created"
end

redirect_to matchmaking_groups_path
end

def edit
@group = MatchmakingGroup.find_by(id: params[:id])
redirect_to matchmaking_groups_path if @group.nil?
end

def update
@group = MatchmakingGroup.find_by(id: params[:id])

if @group.name != group_params[:name] && MatchmakingGroup.name_exists?(group_params[:name])
flash[:error] = "Other group already exists with that name"
else
@group = MatchmakingGroup.find_by(id: params[:id])
@group.update(group_params)

flash[:notice] = "Group updated"
end
redirect_to matchmaking_groups_path
end

def destroy
@group = MatchmakingGroup.find_by(id: params[:id])
@group.destroy if @group.present?
redirect_to matchmaking_groups_path
end

private

def group_params
params.require(:matchmaking_group)
.permit(:name, :slack_channel_name, :schedule, :target_size, :is_active)
.tap do |hash|
hash[:name] = hash[:name].strip
end
end
end
46 changes: 0 additions & 46 deletions app/jobs/establish_matches_for_grouping_job.rb

This file was deleted.

44 changes: 44 additions & 0 deletions app/models/matchmaking_group.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
class MatchmakingGroup < ApplicationRecord
validate :name_not_in_config
validates :name, uniqueness: true

def self.name_exists?(name)
Rails.application.config.x.matchmaking.to_h.transform_keys(&:to_s).key?(name) || exists?(name: name)
end

def active?
is_active
end

def active
is_active
end

def active=(value)
self.is_active = value
end

def channel
slack_channel_name
end

def channel=(value)
self.slack_channel_name = value
end

def size
target_size
end

def size=(value)
self.target_size = value
end

private

def name_not_in_config
if Rails.application.config.x.matchmaking.to_h.key?(name.intern)
errors.add(:name, "cannot be the same as a key in the matchmaking config")
end
end
end
27 changes: 27 additions & 0 deletions app/services/collect_groups.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class CollectGroups
def initialize
@config = Rails.application.config.x.matchmaking
end

def call
extra_groups = MatchmakingGroup.all
readonly_groups + extra_groups
end

private

def readonly_groups
@config.to_h.map do |name, group_config|
normalized = group_config.to_h.transform_keys do |key|
next :target_size if key.intern == :size
next :is_active if key.intern == :active
next :slack_channel_name if key.intern == :channel
key
end

group = MatchmakingGroup.new(normalized.merge(name: name))
group.define_singleton_method(:readonly?) { true }
group
end
end
end
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Mailer
class BuildsGroupingMailerMessage
class BuildGroupMailerMessage
def render(recipient:, channel:, grouping:, other_members:)
GroupingMailer.encourage_match(
recipient: recipient,
Expand Down
13 changes: 4 additions & 9 deletions app/services/matchmaking/choose_strategy.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
module Matchmaking
class ChooseStrategy
def initialize(config: nil)
@config = config || Rails.application.config.x.matchmaking
end

def call(grouping)
group_config = @config.send(grouping.intern)
return nil unless group_config&.active
def call(group)
return nil unless group&.active?

return Strategies::PairByFewestEncounters.new if group_config.size == 2
return Strategies::PairByFewestEncounters.new if group.target_size == 2

Strategies::ArrangeGroupsGenetically.new(target_group_size: group_config.size)
Strategies::ArrangeGroupsGenetically.new(target_group_size: group.target_size)
end
end
end
4 changes: 2 additions & 2 deletions app/services/matchmaking/collect_scored_participants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ def initialize
@assign_score_to_candidates = AssignScoreToCandidates.new
end

def call(participants, grouping)
def call(participants, group)
participants.reduce({}) do |memo, participant|
recent_matches = HistoricalMatch.scoreable.with_member(participant).in_grouping(grouping)
recent_matches = HistoricalMatch.scoreable.with_member(participant).in_grouping(group.name)
candidates = participants.difference([participant])

scored_candidates = @assign_score_to_candidates.call(candidates, recent_matches)
Expand Down
9 changes: 9 additions & 0 deletions app/services/matchmaking/errors/channel_not_found.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module Matchmaking
module Errors
class ChannelNotFound < StandardError
def initialize(group_name, channel_name)
super("No channel found with name '#{channel_name}' for grouping '#{group_name}'")
end
end
end
end
9 changes: 9 additions & 0 deletions app/services/matchmaking/errors/no_configured_channel.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module Matchmaking
module Errors
class NoConfiguredChannel < StandardError
def initialize(group_name)
super("No configured channel for grouping '#{group_name}'")
end
end
end
end
49 changes: 49 additions & 0 deletions app/services/matchmaking/establish_matches_for_group.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
module Matchmaking
class EstablishMatchesForGroup
def initialize
@loads_slack_channels = Slack::LoadsSlackChannels.new
@loads_slack_channel_members = Slack::LoadsSlackChannelMembers.new
@match_participants = Matchmaking::MatchParticipants.new
end

def call(group)
ensure_channel_configured(group)

channel = fetch_slack_channel(group.slack_channel_name)
ensure_channel_found(channel, group)

participants = @loads_slack_channel_members.call(channel: channel.id)

matches = @match_participants.call(participants, group)
matches.each do |match|
HistoricalMatch.create(
members: match,
grouping: group.name,
matched_on: Date.today,
pending_notifications: [
PendingNotification.create(strategy: "email"),
PendingNotification.create(strategy: "slack")
]
)
end
rescue => e
ReportsError.report(e)
end

private

def ensure_channel_configured(group)
raise Errors::NoConfiguredChannel.new(group.name) unless group.slack_channel_name
end

def ensure_channel_found(channel, group)
raise Errors::ChannelNotFound.new(group.slack_channel_name, group.name) unless channel
end

def fetch_slack_channel(channel_name)
@loads_slack_channels.call(types: "public_channel").find { |channel|
channel.name_normalized == channel_name
}
end
end
end
11 changes: 5 additions & 6 deletions app/services/matchmaking/match_participants.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
module Matchmaking
class MatchParticipants
def initialize(config: nil)
def initialize
@collect_scored_participants = CollectScoredParticipants.new
@config = config || Rails.application.config.x.matchmaking
@choose_strategy = ChooseStrategy.new(config: @config)
@choose_strategy = ChooseStrategy.new
end

def call(participants, grouping)
def call(participants, group)
# We don't want to consider a group of 1 a match
return [] if participants.size < 2

scored_participants = @collect_scored_participants.call(participants, grouping)
scored_participants = @collect_scored_participants.call(participants, group)

strategy = @choose_strategy.call(grouping)
strategy = @choose_strategy.call(group)
return [] unless strategy

strategy.call(scored_participants)
Expand Down
Loading
Loading