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
5 changes: 4 additions & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ config :reverse_proxy_plug, :http_client, ReverseProxyPlug.HTTPClient.Adapters.H

# Analytics reverse proxy
config :bike_brigade, BikeBrigadeWeb.Router,
analytics_upstream: "http://bike-brigade-analytics.internal:8080/analytics/"
analytics_upstream: "http://bike-brigade-analytics.internal:8080/analytics/",
# Destination for dispatch.bikebrigade.ca/new-rider-guide — linked in the welcome SMS sent to new riders.
new_rider_guide_url:
"https://docs.google.com/document/d/18fb6YgjMktL1MZ89RhC6aDCt1TZXTGyMWJoSIGicnms/edit?tab=t.0"

# Esbuild
config :esbuild,
Expand Down
22 changes: 21 additions & 1 deletion lib/bike_brigade/messaging.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ defmodule BikeBrigade.Messaging do
alias BikeBrigade.Riders.Rider
alias BikeBrigade.SmsService

@initial_message "Hello! This is the Bike Brigade number. The team uses this number to send you information about deliveries you sign up for, and you can text us here if you have any questions. This number is monitored by the Bike Brigade team -- we'll often sign messages with the person sending them.\n\nWe recommend you add this number to your contacts as Bike Brigade (or update your contacts if you've added our old number).\n\nYou can text STOP to this number to opt-out of receiving text messages from us."
@initial_message "Hello! This is the Bike Brigade number. The team uses this number to send you information about deliveries you sign up for, and you can text us here if you have any questions about specific deliveries. This number is monitored by the Bike Brigade team - we’ll often sign messages with the person sending them.\n\nWe recommend you add this number to your contacts as Bike Brigade.\n\nYou can text STOP to this number to opt-out of receiving text messages from us.\n\nFor more info about signing up for your first delivery, see our new rider onboarding guide https://dispatch.bikebrigade.ca/new-rider-guide. For general questions, email info@bikebrigade.ca."

@doc """
Returns the list of sms_messages.
Expand Down Expand Up @@ -213,6 +213,23 @@ defmodule BikeBrigade.Messaging do
}
end

@doc "Send the welcome SMS to a rider and record it in the database"
def send_welcome_sms(%Rider{} = rider, opts \\ []) do
rider = Repo.get!(Rider, rider.id) |> Repo.preload(:location)
message = new_sms_message(rider, Keyword.put_new(opts, :body, @initial_message))

with {:ok, %{status: status, sid: sid}} <-
SmsService.send_sms(message, send_callback: true),
{:ok, _} <-
create_sms_message(message, %{
sent_at: DateTime.utc_now(),
twilio_status: status,
twilio_sid: sid
}) do
Riders.update_rider(rider, %{flags: %{initial_message_sent: true}})
end
end
Comment thread
coderabbitai[bot] marked this conversation as resolved.

@doc """
Appends the dispatcher's signature to a message body if the user has signatures enabled.
Returns the original body unchanged if no sent_by_user_id, user has setting disabled, or user not found.
Expand Down Expand Up @@ -312,6 +329,9 @@ defmodule BikeBrigade.Messaging do

defp maybe_send_initial_message(%Ecto.Multi{} = multi, nil), do: multi

# Sends the welcome SMS for riders who opted in to the new number via the dispatcher flow.
# Riders signing up through the portal receive the welcome SMS via Riders.create_or_update/1
# and will not reach this clause (initial_message_sent is already true by then).
defp maybe_send_initial_message(%Ecto.Multi{} = multi, %Rider{} = rider) do
case rider.flags do
%Rider.Flags{opt_in_to_new_number: true, initial_message_sent: false} ->
Expand Down
6 changes: 5 additions & 1 deletion lib/bike_brigade/tasks/mailchimp_importer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ defmodule BikeBrigade.Tasks.MailchimpImporter do
alias BikeBrigade.Locations.Location
alias BikeBrigade.Tasks.Importer
alias BikeBrigade.Riders
alias BikeBrigade.Messaging
alias BikeBrigade.Messaging.Slack
alias BikeBrigade.MailchimpApi

Expand Down Expand Up @@ -84,7 +85,10 @@ defmodule BikeBrigade.Tasks.MailchimpImporter do
Riders.update_rider(rider, rider_attrs)

nil ->
Riders.create_rider_with_user(rider_attrs)
with {:ok, rider} <- Riders.create_rider_with_user(rider_attrs),
{:ok, rider} <- Messaging.send_welcome_sms(rider) do
Comment thread
coderabbitai[bot] marked this conversation as resolved.
{:ok, rider}
end
end
end

Expand Down
13 changes: 13 additions & 0 deletions lib/bike_brigade_web/controllers/redirect_controller.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule BikeBrigadeWeb.RedirectController do
use BikeBrigadeWeb, :controller

import BikeBrigade.Utils, only: [get_env: 3]

def new_rider_guide(conn, _params) do
url =
get_env(BikeBrigadeWeb.Router, :new_rider_guide_url, nil) ||
raise "NEW_RIDER_GUIDE_URL is not configured"

redirect(conn, external: url)
end
end
6 changes: 6 additions & 0 deletions lib/bike_brigade_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ defmodule BikeBrigadeWeb.Router do
delete "/login", AuthenticationController, :cancel
end

scope "/", BikeBrigadeWeb do
pipe_through [:browser]

get "/new-rider-guide", RedirectController, :new_rider_guide
end

# this scope is for authenticated users that aren't dispatchers (ie: riders).
scope "/", BikeBrigadeWeb do
pipe_through [:browser, :require_authenticated_user, :set_honeybadger_context]
Expand Down
42 changes: 28 additions & 14 deletions test/bike_brigade/importers/mailchimp_importer_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ defmodule BikeBrigade.Tasks.MailchimpImporterTest do
alias BikeBrigade.SlackApi.FakeSlack

alias BikeBrigade.Riders
alias BikeBrigade.Messaging

@list_id "LIST_ID"

@valid_attrs %{
@member %{
id: "123",
email_address: "dispatcher@example.com",
status: "subscribed",
Expand All @@ -25,18 +26,18 @@ defmodule BikeBrigade.Tasks.MailchimpImporterTest do

describe "parse_mailchimp_attrs/1" do
test "is successful if fields from Mailchimp are valid" do
{:ok, expected_fields} = MailchimpImporter.parse_mailchimp_attrs(@valid_attrs)
{:ok, expected_fields} = MailchimpImporter.parse_mailchimp_attrs(@member)
address = expected_fields[:location][:address]
assert address == "1508 Davenport Rd"
end

test "returns an error if the phone number is not a valid Canadian number" do
invalid_attrs = put_in(@valid_attrs, [:merge_fields, :PHONEYUI_], "oops")
invalid_attrs = put_in(@member, [:merge_fields, :PHONEYUI_], "oops")
assert {:error, _} = MailchimpImporter.parse_mailchimp_attrs(invalid_attrs)
end

test "returns an error if address fails geocoding" do
invalid_attrs = put_in(@valid_attrs, [:merge_fields, :TEXTYUI_3], "oops")
invalid_attrs = put_in(@member, [:merge_fields, :TEXTYUI_3], "oops")

assert {:error, {:update_location, _}} =
MailchimpImporter.parse_mailchimp_attrs(invalid_attrs)
Expand All @@ -61,36 +62,36 @@ defmodule BikeBrigade.Tasks.MailchimpImporterTest do
end

test "is successful when all required attributes are valid" do
assert Riders.get_rider_by_email("dispatcher@example.com") == nil
FakeMailchimp.add_members(@list_id, [@valid_attrs])
assert Riders.get_rider_by_email(@member[:email_address]) == nil
FakeMailchimp.add_members(@list_id, [@member])

assert {:ok, _} = MailchimpImporter.sync_riders()

r = Riders.get_rider_by_email("dispatcher@example.com")
r = Riders.get_rider_by_email(@member[:email_address])

assert r.name == "Morty"
assert r.location.address == "1508 Davenport Rd"
end

test "handles case where phone is invalid" do
invalid_attrs = put_in(@valid_attrs, [:merge_fields, :PHONEYUI_], "oops")
invalid_attrs = put_in(@member, [:merge_fields, :PHONEYUI_], "oops")
FakeMailchimp.add_members(@list_id, [invalid_attrs])

assert {:ok, _} = MailchimpImporter.sync_riders()

assert Riders.get_rider_by_email("dispatcher@example.com") == nil
assert Riders.get_rider_by_email(@member[:email_address]) == nil

call = FakeSlack.get_last_call()
assert call[:body] =~ "An error ocurred when importing dispatcher@example.com"
end

test "handles case where address is invalid" do
invalid_attrs = put_in(@valid_attrs, [:merge_fields, :TEXTYUI_3], "oops")
invalid_attrs = put_in(@member, [:merge_fields, :TEXTYUI_3], "oops")
FakeMailchimp.add_members(@list_id, [invalid_attrs])

assert {:ok, _} = MailchimpImporter.sync_riders()

r = Riders.get_rider_by_email("dispatcher@example.com")
r = Riders.get_rider_by_email(@member[:email_address])
assert r.name == "Morty"

# The rider has a default location
Expand All @@ -104,19 +105,32 @@ defmodule BikeBrigade.Tasks.MailchimpImporterTest do
assert [%{name: "invalid_location"}] = Repo.preload(r, :tags).tags
end

test "sends a welcome SMS when a new rider is created" do
FakeMailchimp.add_members(@list_id, [@member])

assert {:ok, _} = MailchimpImporter.sync_riders()

rider = Riders.get_rider_by_email(@member[:email_address])
messages = Messaging.list_sms_messages()

welcome = Enum.find(messages, &(&1.to == rider.phone && !&1.incoming))
assert welcome != nil
assert welcome.body =~ "Hello! This is the Bike Brigade number"
end

test "updates riders" do
FakeMailchimp.add_members(@list_id, [@valid_attrs])
FakeMailchimp.add_members(@list_id, [@member])

assert {:ok, _} = MailchimpImporter.sync_riders()

FakeMailchimp.clear_members(@list_id)

FakeMailchimp.add_members(@list_id, [
put_in(@valid_attrs, [:merge_fields, :FNAME], "Mortimus")
put_in(@member, [:merge_fields, :FNAME], "Mortimus")
])

assert {:ok, _} = MailchimpImporter.sync_riders()
r = Riders.get_rider_by_email("dispatcher@example.com")
r = Riders.get_rider_by_email(@member[:email_address])
assert r.name == "Mortimus"
end
end
Expand Down
Loading