Skip to content

Commit

Permalink
feat(survey): [PPT-358] add visitor triggers (#275)
Browse files Browse the repository at this point in the history
* feat(survey): add visitor triggers

* docs(openapi): doc gen

* docker(compose): remove duplicate health check

* test(survey_triggers): test all triggers
  • Loading branch information
chillfox authored Apr 24, 2023
1 parent b2cafe2 commit ffb30ae
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 13 deletions.
2 changes: 1 addition & 1 deletion OPENAPI_DOC.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10156,7 +10156,7 @@ components:
trigger:
type: object
description: 'Triggers on booking states: RESERVED, CHECKEDIN, CHECKEDOUT,
REJECTED, CANCELLED'
REJECTED, CANCELLED, VISITOR_CHECKEDIN, VISITOR_CHECKEDOUT'
nullable: true
zone_id:
type: string
Expand Down
3 changes: 0 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ services:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: staff_api_test
healthcheck:
test: /usr/bin/pg_isready
interval: 5s
ports:
- 5432:5432

Expand Down
11 changes: 11 additions & 0 deletions spec/controllers/helpers/booking_helper.cr
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ module BookingsHelper
booking.save!
end

def create_booking(tenant_id : Int64, user_email : String, zones : Array(String), booking_start : Int64, booking_end : Int64)
booking = create_booking(
tenant_id: tenant_id,
user_email: user_email,
zones: zones,
)
booking.booking_start = booking_start
booking.booking_end = booking_end
booking.save!
end

def create_booking(tenant_id : Int64, booking_start : Int64, booking_end : Int64, asset_id : String)
booking = create_booking(tenant_id)
booking.booking_start = booking_start
Expand Down
187 changes: 180 additions & 7 deletions spec/controllers/surveys/triggers_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,24 @@ describe "Survey Triggers", tags: ["survey"] do
client = AC::SpecHelper.client
headers = Mock::Headers.office365_guest

pending "should create an invitation on booking state change" do
before_each do
# Booking.query.each(&.delete)
WebMock.stub(:post, "#{ENV["PLACE_URI"]}/auth/oauth/token")
.to_return(body: File.read("./spec/fixtures/tokens/placeos_token.json"))
WebMock.stub(:post, "#{ENV["PLACE_URI"]}/api/engine/v2/signal?channel=staff/booking/changed")
.to_return(body: "")

Timecop.scale(600) # 1 second == 10 minutes
end

after_all do
WebMock.reset
Timecop.scale(1)
end

it "should create an invitation on RESERVED trigger" do
survey = SurveyHelper.create_survey(
zone_id: "zone-3",
zone_id: "zone-2",
building_id: "zone-1",
trigger: "RESERVED",
)
Expand All @@ -22,10 +37,168 @@ describe "Survey Triggers", tags: ["survey"] do
)

invitations = Survey::Invitation.query.select("id").map(&.id)
# pp "########################################"
# pp! booking.current_state
# pp! booking.history.map(&.state)
# pp! invitations
# pp "########################################"
invitations.size.should eq(1)
end

it "should create an invitation on CHECKEDIN trigger" do
survey = SurveyHelper.create_survey(
zone_id: "zone-2",
building_id: "zone-1",
trigger: "CHECKEDIN",
)

tenant = Tenant.query.find! { domain == "toby.staff-api.dev" }
booking = BookingsHelper.create_booking(
tenant_id: tenant.id,
user_email: "user@example.com",
zones: ["zone-1", "zone-2"],
booking_start: 1.minutes.from_now.to_unix,
booking_end: 9.minutes.from_now.to_unix,
)

client.post("#{BOOKINGS_BASE}/#{booking.id}/check_in?state=true", headers: headers)

invitations = Survey::Invitation.query.select("id").map(&.id)
invitations.size.should eq(1)
end

it "should create an invitation on CHECKEDOUT trigger" do
survey = SurveyHelper.create_survey(
zone_id: "zone-2",
building_id: "zone-1",
trigger: "CHECKEDOUT",
)

tenant = Tenant.query.find! { domain == "toby.staff-api.dev" }
booking = BookingsHelper.create_booking(
tenant_id: tenant.id,
user_email: "user@example.com",
zones: ["zone-1", "zone-2"],
booking_start: 1.minutes.from_now.to_unix,
booking_end: 9.minutes.from_now.to_unix,
)

client.post("#{BOOKINGS_BASE}/#{booking.id}/check_in?state=false", headers: headers)

invitations = Survey::Invitation.query.select("id").map(&.id)
invitations.size.should eq(1)
end

it "should create an invitation on REJECTED trigger" do
survey = SurveyHelper.create_survey(
zone_id: "zone-2",
building_id: "zone-1",
trigger: "REJECTED",
)

tenant = Tenant.query.find! { domain == "toby.staff-api.dev" }
booking = BookingsHelper.create_booking(
tenant_id: tenant.id,
user_email: "user@example.com",
zones: ["zone-1", "zone-2"],
booking_start: 1.minutes.from_now.to_unix,
booking_end: 9.minutes.from_now.to_unix,
)

client.post("#{BOOKINGS_BASE}/#{booking.id}/reject", headers: headers)

invitations = Survey::Invitation.query.select("id").map(&.id)
invitations.size.should eq(1)
end

it "should create an invitation on CANCELLED trigger" do
survey = SurveyHelper.create_survey(
zone_id: "zone-2",
building_id: "zone-1",
trigger: "CANCELLED",
)

tenant = Tenant.query.find! { domain == "toby.staff-api.dev" }
booking = BookingsHelper.create_booking(
tenant_id: tenant.id,
user_email: "user@example.com",
zones: ["zone-1", "zone-2"],
booking_start: 1.minutes.from_now.to_unix,
booking_end: 9.minutes.from_now.to_unix,
)

client.delete("#{BOOKINGS_BASE}/#{booking.id}", headers: headers)

invitations = Survey::Invitation.query.select("id").map(&.id)
invitations.size.should eq(1)
end

it "should create an invitation on VISITOR_CHECKEDIN trigger" do
survey = SurveyHelper.create_survey(
zone_id: "zone-2",
building_id: "zone-1",
trigger: "VISITOR_CHECKEDIN",
)

tenant = Tenant.query.find! { domain == "toby.staff-api.dev" }
booking = BookingsHelper.create_booking(
tenant_id: tenant.id,
user_email: "user@example.com",
zones: ["zone-1", "zone-2"],
booking_start: 1.minutes.from_now.to_unix,
booking_end: 9.minutes.from_now.to_unix,
)
guest = Guest.create!({
name: Faker::Name.name,
email: "visitor@example.com",
tenant_id: tenant.id,
banned: false,
dangerous: false,
})
visitor = Attendee.create!({
tenant_id: guest.tenant_id,
booking_id: booking.id,
guest_id: guest.id,
checked_in: false,
visit_expected: true,
})

visitor.checked_in = true
visitor.save!

invitations = Survey::Invitation.query.select("id").map(&.id)
invitations.size.should eq(1)
end

pending "should create an invitation on VISITOR_CHECKEDOUT trigger" do
survey = SurveyHelper.create_survey(
zone_id: "zone-2",
building_id: "zone-1",
trigger: "VISITOR_CHECKEDOUT",
)

tenant = Tenant.query.find! { domain == "toby.staff-api.dev" }
booking = BookingsHelper.create_booking(
tenant_id: tenant.id,
user_email: "user@example.com",
zones: ["zone-1", "zone-2"],
booking_start: 1.minutes.from_now.to_unix,
booking_end: 9.minutes.from_now.to_unix,
)
guest = Guest.create!({
name: Faker::Name.name,
email: "visitor@example.com",
tenant_id: tenant.id,
banned: false,
dangerous: false,
})
_visitor = Attendee.create!({
tenant_id: guest.tenant_id,
booking_id: booking.id,
guest_id: guest.id,
checked_in: true,
visit_expected: true,
})

visitor.checked_in = false
visitor.save!

invitations = Survey::Invitation.query.select("id").map(&.id)
invitations.size.should eq(1)
end
end
12 changes: 12 additions & 0 deletions src/migrations/0030_alter_enum_trigger_type.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class AlterEnumTriggerType
include Clear::Migration

def change(dir)
dir.up do
execute("ALTER TYPE survey_trigger_type ADD VALUE 'VISITOR_CHECKEDIN'")
execute("ALTER TYPE survey_trigger_type ADD VALUE 'VISITOR_CHECKEDOUT'")
end

# No down migration, as enum does not support removal of values
end
end
27 changes: 27 additions & 0 deletions src/models/attendee.cr
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,33 @@ class Attendee

delegate email, name, preferred_name, phone, organisation, notes, photo, to: guest

before(:save) do |m|
attendee_model = m.as(Attendee)
attendee_model.survey_trigger
end

def survey_trigger
return unless checked_in_column.changed?
state = checked_in ? "VISITOR_CHECKEDIN" : "VISITOR_CHECKEDOUT"

query = Survey.query.select("id").where(trigger: state)

if (b = booking) && (zones = b.zones) && !zones.empty?
query = query.where { var("zone_id").in?(zones) & var("building_id").in?(zones) }
end

email = guest.email
unless email.empty?
surveys = query.to_a
surveys.each do |survey|
Survey::Invitation.create!(
survey_id: survey.id,
email: email,
)
end
end
end

struct AttendeeResponse
include JSON::Serializable
include AutoInitialize
Expand Down
5 changes: 3 additions & 2 deletions src/models/survey.cr
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require "./survey/*"

Clear.enum TriggerType, "NONE", "RESERVED", "CHECKEDIN", "CHECKEDOUT", "NOSHOW", "REJECTED", "CANCELLED", "ENDED"
# No trigger actually fires on NOSHOW or ENDED, but Postgres doesn't support removing enum values
Clear.enum TriggerType, "NONE", "RESERVED", "CHECKEDIN", "CHECKEDOUT", "NOSHOW", "REJECTED", "CANCELLED", "ENDED", "VISITOR_CHECKEDIN", "VISITOR_CHECKEDOUT"

class Survey
include Clear::Model
Expand All @@ -24,7 +25,7 @@ class Survey
getter id : Int64?
getter title : String? = nil
getter description : String? = nil
@[JSON::Field(description: "Triggers on booking states: RESERVED, CHECKEDIN, CHECKEDOUT, REJECTED, CANCELLED")]
@[JSON::Field(description: "Triggers on booking states: RESERVED, CHECKEDIN, CHECKEDOUT, REJECTED, CANCELLED, VISITOR_CHECKEDIN, VISITOR_CHECKEDOUT")]
getter trigger : TriggerType? = nil
getter zone_id : String? = nil
getter building_id : String? = nil
Expand Down

0 comments on commit ffb30ae

Please sign in to comment.