Skip to content

Commit

Permalink
adds hand_off_reminder slack function
Browse files Browse the repository at this point in the history
  • Loading branch information
marcdel committed Aug 18, 2024
1 parent 73814ce commit d358e07
Show file tree
Hide file tree
Showing 14 changed files with 251 additions and 35 deletions.
6 changes: 0 additions & 6 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,6 @@ config :fun_with_flags, :persistence,
repo: Pears.Repo,
ecto_table_name: "feature_flags"

config :pears, Pears.Scheduler,
jobs: [
# At 5:00 PM Pacific on every day-of-week from Monday through Friday.
{"0 12 * * 1-5", {Pears.Slack, :send_stand_down_reminders, []}}
]

# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{config_env()}.exs"
6 changes: 6 additions & 0 deletions config/prod.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,11 @@ config :swoosh, local: false
# Do not print debug messages in production
config :logger, level: :info

config :pears, Pears.Scheduler,
jobs: [
# At 5:00 PM Pacific on every day-of-week from Monday through Friday.
{"0 12 * * 1-5", {Pears, :send_hand_off_reminders, []}}
]

# Runtime production configuration, including reading
# of environment variables, is done on config/runtime.exs.
26 changes: 26 additions & 0 deletions lib/pears.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ defmodule Pears do
alias Pears.Core.Recommendator
alias Pears.Core.Team
alias Pears.Persistence
alias Pears.Slack

@topic inspect(__MODULE__)

Expand Down Expand Up @@ -457,6 +458,31 @@ defmodule Pears do
end
end

@decorate trace("pears.send_hand_off_reminders")
def send_hand_off_reminders do
teams = Persistence.find_teams_with_slack_tokens()

Enum.each(teams, &send_hand_off_reminder/1)

{:ok, nil}
end

@decorate trace("pears.send_hand_off_reminder", include: [:team])
defp send_hand_off_reminder(team_record) do
with {:ok, team} <- TeamSession.find_or_start_session(team_record.name),
{:ok, snapshot} <- Persistence.get_latest_snapshot(team.name),
true <- is_snapshot_from_today(snapshot),
matches <- Team.misaligned_tz_matches(team) do
Enum.each(matches, fn pears ->
Slack.send_hand_off_reminder(team, pears)
end)
end
end

defp is_snapshot_from_today(snapshot) do
snapshot.inserted_at >= Date.utc_today()
end

@decorate trace("pears.validate_pear_available", include: [:team, :pear_name])
defp validate_pear_available(team, pear_name) do
case Team.find_available_pear(team, pear_name) do
Expand Down
3 changes: 2 additions & 1 deletion lib/pears/boundary/team_session.ex
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ defmodule Pears.Boundary.TeamSession do
Team.add_pear(team, pear_record.name,
id: pear_record.id,
slack_name: pear_record.slack_name,
slack_id: pear_record.slack_id
slack_id: pear_record.slack_id,
timezone_offset: pear_record.timezone_offset
)
end)
end
Expand Down
44 changes: 31 additions & 13 deletions lib/pears/core/team.ex
Original file line number Diff line number Diff line change
Expand Up @@ -425,38 +425,56 @@ defmodule Pears.Core.Team do
Returns the difference in hours between two pears' timezone offsets.
"""
@spec timezone_difference(Pear.t(), Pear.t()) :: integer
@decorate with_span("team.timezone_difference")
def timezone_difference(%{timezone_offset: nil}, _), do: 0
def timezone_difference(_, %{timezone_offset: nil}), do: 0

def timezone_difference(pear1, pear2) do
difference_seconds = abs(pear1.timezone_offset - pear2.timezone_offset)
def timezone_difference(%{timezone_offset: left}, %{timezone_offset: right}) do
offset_difference_in_hours(parse_int(left), parse_int(right))
end

defp parse_int(maybe_a_string) when is_binary(maybe_a_string) do
{number, _remainder} = Integer.parse(maybe_a_string)
number
end

defp parse_int(maybe_a_string) when is_integer(maybe_a_string) do
maybe_a_string
end

@decorate with_span("team.offset_difference_in_hours")
defp offset_difference_in_hours(left, right) do
difference_seconds = abs(left - right)
difference_hours = div(difference_seconds, 3600)

O11y.set_attributes(
left: pear1.timezone_offset,
right: pear2.timezone_offset,
left: left,
right: right,
difference_hours: difference_hours,
difference_seconds: difference_seconds
)

difference_hours
end

@spec misaligned_tz_matches(Team.t()) :: [Pear.t()]
@spec misaligned_tz_matches(Team.t()) :: list(list(Pear.t()))
@decorate with_span("team.misaligned_tz_matches")
def misaligned_tz_matches(team) do
team.tracks
|> Enum.map(fn {_track_name, track} ->
|> Enum.map(fn {track_name, track} ->
pears = Map.values(track.pears)
if significant_tz_differences?(pears), do: pears, else: []

if significant_tz_differences?(pears) do
O11y.add_event("misaligned_tz_match", %{
track: track_name,
pears: Enum.map(pears, & &1.name)
})

pears
else
[]
end
end)
|> Enum.reject(&Enum.empty?/1)
|> tap(fn matches ->
Enum.map(matches, fn match ->
O11y.add_event("misaligned_tz_match", %{match: match})
end)
end)
end

def metadata(team) do
Expand Down
19 changes: 18 additions & 1 deletion lib/pears/persistence.ex
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ defmodule Pears.Persistence do
end

@decorate trace("persistence.find_teams_with_slack_tokens")
@spec find_teams_with_slack_tokens() :: [TeamRecord.t()]
def find_teams_with_slack_tokens do
Repo.all(from t in TeamRecord, where: not is_nil(t.slack_token))
end
Expand Down Expand Up @@ -80,7 +81,7 @@ defmodule Pears.Persistence do
{:tracks, :pears},
{:tracks, :anchor},
{:snapshots, :matches},
snapshots: from(s in SnapshotRecord, order_by: [desc: s.inserted_at])
snapshots: from(s in SnapshotRecord, order_by: [desc: s.id])
])

case result do
Expand Down Expand Up @@ -307,6 +308,15 @@ defmodule Pears.Persistence do
end
end

def get_latest_snapshot(team_name) do
with {:ok, team} <- get_team_by_name(team_name),
{:ok, snapshot} <- find_latest_snapshot(team) do
{:ok, snapshot}
else
error -> error
end
end

defp save_snapshot(team, snapshot) do
%SnapshotRecord{}
|> SnapshotRecord.changeset(%{
Expand All @@ -316,6 +326,13 @@ defmodule Pears.Persistence do
|> Repo.insert()
end

defp find_latest_snapshot(team) do
case Enum.at(team.snapshots, 0) do
nil -> {:error, :no_snapshots}
snapshot -> {:ok, snapshot}
end
end

defp build_matches(snapshot) do
Enum.map(snapshot, &build_match/1)
end
Expand Down
29 changes: 16 additions & 13 deletions lib/pears/slack.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ defmodule Pears.Slack do
alias Pears.Slack.Details
alias Pears.Slack.Messages.DailyPairsMessage
alias Pears.Slack.Messages.EndOfSessionQuestion
alias Pears.Slack.Messages
alias Pears.Slack.User

def slack_client do
Expand Down Expand Up @@ -78,11 +79,6 @@ defmodule Pears.Slack do
end
end

@decorate trace("slack.send_stand_down_reminders")
def send_stand_down_reminders() do
O11y.set_attributes(cron: "0 12 * * 1-5")
end

@decorate trace("slack.send_message_to_team", include: [:team_name, :message])
def send_message_to_team(team_name, message) do
case TeamSession.find_or_start_session(team_name) do
Expand Down Expand Up @@ -112,14 +108,20 @@ defmodule Pears.Slack do
end
end

@decorate trace("slack.send_hand_off_reminder")
def send_hand_off_reminder(team, pears) do
message = Messages.HandOffReminder.new()
send_message_to_pears(team, pears, message)
end

defp do_save_slack_names(team_name, users, pears, params) do
Enum.map(params, fn
{pear_name, ""} ->
{:ok, Enum.find(pears, fn pear -> pear.name == pear_name end)}

{pear_name, slack_id} ->
with {:ok, team} <- TeamSession.find_or_start_session(team_name),
user <- Enum.find(users, fn user -> user.id == slack_id end),
%User{} = user <- Enum.find(users, fn user -> user.id == slack_id end),
{:ok, pear_record} <-
Persistence.add_pear_slack_details(team.name, pear_name, %{
slack_id: user.id,
Expand All @@ -136,6 +138,7 @@ defmodule Pears.Slack do
{:ok, pear_record}
end
end)
|> Enum.reject(&(&1 == nil))
|> Enum.group_by(fn {success, _} -> success end, fn {_, result} -> result end)
|> Map.get(:ok, [])
end
Expand Down Expand Up @@ -168,19 +171,19 @@ defmodule Pears.Slack do

defp do_send_end_of_session_question(team, track) do
message = EndOfSessionQuestion.new(track)
send_message_to_pears(team, Map.values(track.pears), message)
end

case find_or_create_group_chat(team, track) do
defp send_message_to_pears(team, pears, message) do
case find_or_create_group_chat(team, pears) do
{:ok, channel_id} -> do_send_message(team, channel_id, message)
_ -> {:error, :error_creating_group_chat}
end
end

@decorate trace("slack.find_or_create_group_chat", include: [[:track, :pears], :user_ids])
defp find_or_create_group_chat(%{slack_token: token}, track) do
user_ids =
track.pears
|> Map.values()
|> Enum.map(&Map.get(&1, :slack_id))
@decorate trace("slack.find_or_create_group_chat", include: [:pears, :user_ids])
defp find_or_create_group_chat(%{slack_token: token}, pears) do
user_ids = Enum.map(pears, &Map.get(&1, :slack_id))

case slack_client().find_or_create_group_chat(user_ids, token) do
%{"ok" => true} = response ->
Expand Down
17 changes: 17 additions & 0 deletions lib/pears/slack/messages/hand_off_reminder.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
defmodule Pears.Slack.Messages.HandOffReminder do
def new do
[
%{
"type" => "section",
"text" => %{
"type" => "mrkdwn",
"text" =>
"Hey, friends! 👋\n\nLooks like you're pairing across timezones!\nThis is your friendly reminder to update your pair with any context they might need to get started tomorrow."
}
},
%{
"type" => "divider"
}
]
end
end
1 change: 1 addition & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ defmodule Pears.MixProject do
{:phoenix_live_dashboard, "~> 0.8.3"},
{:esbuild, "~> 0.8", runtime: Mix.env() == :dev},
{:tailwind, "~> 0.2", runtime: Mix.env() == :dev},
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
{:heroicons,
github: "tailwindlabs/heroicons",
tag: "v2.1.1",
Expand Down
2 changes: 2 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
"db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"},
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
"decorator": {:hex, :decorator, "1.4.0", "a57ac32c823ea7e4e67f5af56412d12b33274661bb7640ec7fc882f8d23ac419", [:mix], [], "hexpm", "0a07cedd9083da875c7418dea95b78361197cf2bf3211d743f6f7ce39656597f"},
"dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"},
"dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"},
"ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"},
"ecto_sql": {:hex, :ecto_sql, "3.11.3", "4eb7348ff8101fbc4e6bbc5a4404a24fecbe73a3372d16569526b0cf34ebc195", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e5f36e3d736b99c7fee3e631333b8394ade4bafe9d96d35669fca2d81c2be928"},
"elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"},
"erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
"esbuild": {:hex, :esbuild, "0.8.1", "0cbf919f0eccb136d2eeef0df49c4acf55336de864e63594adcea3814f3edf41", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "25fc876a67c13cb0a776e7b5d7974851556baeda2085296c14ab48555ea7560f"},
"expo": {:hex, :expo, "0.5.2", "beba786aab8e3c5431813d7a44b828e7b922bfa431d6bfbada0904535342efe2", [:mix], [], "hexpm", "8c9bfa06ca017c9cb4020fabe980bc7fdb1aaec059fd004c2ab3bff03b1c599c"},
"file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
Expand Down
22 changes: 22 additions & 0 deletions test/pears/persistence_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -195,5 +195,27 @@ defmodule Pears.PersistenceTest do
assert Enum.member?(matches, %{track_name: "track one", pear_names: ["pear1", "pear2"]})
assert Enum.member?(matches, %{track_name: "track two", pear_names: ["pear3"]})
end

test "can get the most recent snapshot" do
team = create_team("New Team")

{:error, _} = Persistence.get_latest_snapshot(team.name)

{:ok, _} =
Persistence.add_snapshot_to_team(team.name, [
{"track one", ["pear1", "pear2"]},
{"track two", ["pear3"]}
])

{:ok, second_snapshot} =
Persistence.add_snapshot_to_team(team.name, [
{"track one", ["pear1", "pear2"]},
{"track two", ["pear3"]}
])

{:ok, retrieved_snapshot} = Persistence.get_latest_snapshot(team.name)

assert retrieved_snapshot.id == second_snapshot.id
end
end
end
2 changes: 1 addition & 1 deletion test/pears/slack_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ defmodule Pears.SlackTest do
test "exchanges a code for an access token and saves it", %{team: team} do
valid_token = "xoxb-XXXXXXXX-XXXXXXXX-XXXXX"

expect(MockSlackClient, :retrieve_access_tokens, fn _code, _url ->
expect(MockSlackClient, :retrieve_access_tokens, fn "valid_code", _url ->
SlackFixtures.valid_token_response(%{access_token: valid_token})
end)

Expand Down
Loading

0 comments on commit d358e07

Please sign in to comment.