From 5dfcb60475fc73da2305fa5811c5505b153664e6 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 14 Apr 2021 13:28:52 +0100 Subject: [PATCH 1/7] feat(desk_webhook): initial commit --- drivers/place/desk_booking_webhook.cr | 29 +++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/drivers/place/desk_booking_webhook.cr b/drivers/place/desk_booking_webhook.cr index c2cb29eff29..0fbab7851ba 100644 --- a/drivers/place/desk_booking_webhook.cr +++ b/drivers/place/desk_booking_webhook.cr @@ -11,7 +11,7 @@ class Place::DeskBookingWebhook < PlaceOS::Driver default_settings({ post_uri: "https://remote-server/path", - building: "zone-id", + zone_ids: ["zone-id"], # list of zone_ids to monitor custom_headers: { "API_KEY" => "123456", @@ -22,34 +22,51 @@ class Place::DeskBookingWebhook < PlaceOS::Driver booking_category: "desk", + # Note: only use metadata_key and mapped_id_key if we need to map interally used resource_id to another value + metadata_key: "desks", # e.g. metadata_key would be "desks" for below example + mapped_id_key: nil, # e.g. mapped_id_key would be "other_id" for below example + # e.g. metadata response for /api/engine/v2/metadata/zone-123 + # { + # desks: { # metadata_key + # description: "blah", + # details: { + # {id: "desk-1", name: "Desk 1", bookable: true, other_id: "d-1"}, + # {id: "desk-1", name: "Desk 2", bookable: true, other_id: "d-2"} + # } + # }, + # other_metadata_key: {} + # } + debug: false, }) def on_load monitor("staff/booking/changed") do |_subscription, payload| logger.debug { "received booking changed event #{payload}" } - fetch_and_post + process_update(payload) end - schedule.every(24.hours) { fetch_and_post } on_update end @time_period : Time::Span = 14.days @booking_category : String = "desk" @custom_headers = {} of String => String - @building = "" + @zone_ids = [] of Array(String) @post_uri = "" @debug : Bool = false def on_update @post_uri = setting(String, :post_uri) - @building = setting(String, :building) + @zone_ids = setting(Array(String), :zone_ids) @custom_headers = setting(Hash(String, String), :custom_headers) @time_period = setting(Int32, :days_from_now).days @booking_category = setting(String, :booking_category) @debug = setting(Bool, :debug) + end - fetch_and_post + private def process_update(update) + # Only do something if the update is for a zone specified in settings(:zone_ids) + return unless (@zone_ids && update[:zones]).present? end def fetch_and_post From 4e278f46a4771c1312fecd67c09bc721acd2457c Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Wed, 14 Apr 2021 14:52:15 +0100 Subject: [PATCH 2/7] fix(desk_webhook): make compilable --- drivers/place/desk_booking_webhook.cr | 55 ++++++++++++++++++--------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/drivers/place/desk_booking_webhook.cr b/drivers/place/desk_booking_webhook.cr index 0fbab7851ba..99bc5079dd0 100644 --- a/drivers/place/desk_booking_webhook.cr +++ b/drivers/place/desk_booking_webhook.cr @@ -1,11 +1,9 @@ -module Place; end - require "http/client" class Place::DeskBookingWebhook < PlaceOS::Driver descriptive_name "Desk Booking Webhook" generic_name :DeskBookingWebhook - description %(sends a webhook with booking information as it changes) + description "sends a webhook with booking information as it changes" accessor staff_api : StaffAPI_1 @@ -22,7 +20,7 @@ class Place::DeskBookingWebhook < PlaceOS::Driver booking_category: "desk", - # Note: only use metadata_key and mapped_id_key if we need to map interally used resource_id to another value + # Note: only use metadata_key and mapped_id_key if we need to map BookingUpdate.resource_id to another value metadata_key: "desks", # e.g. metadata_key would be "desks" for below example mapped_id_key: nil, # e.g. mapped_id_key would be "other_id" for below example # e.g. metadata response for /api/engine/v2/metadata/zone-123 @@ -34,27 +32,27 @@ class Place::DeskBookingWebhook < PlaceOS::Driver # {id: "desk-1", name: "Desk 2", bookable: true, other_id: "d-2"} # } # }, - # other_metadata_key: {} + # other_metadata_key: {description: "blah2", details: {}} # } - debug: false, + debug: false }) - def on_load - monitor("staff/booking/changed") do |_subscription, payload| - logger.debug { "received booking changed event #{payload}" } - process_update(payload) - end - on_update - end - @time_period : Time::Span = 14.days @booking_category : String = "desk" @custom_headers = {} of String => String - @zone_ids = [] of Array(String) + @zone_ids = [] of String @post_uri = "" @debug : Bool = false + def on_load + monitor("staff/booking/changed") do |_subscription, booking_update| + logger.debug { "received booking changed event #{booking_update}" } + process_update(booking_update) + end + on_update + end + def on_update @post_uri = setting(String, :post_uri) @zone_ids = setting(Array(String), :zone_ids) @@ -64,16 +62,35 @@ class Place::DeskBookingWebhook < PlaceOS::Driver @debug = setting(Bool, :debug) end - private def process_update(update) + struct BookingUpdate + include JSON::Serializable + + property action : String + property id : Int64 + property booking_type : String + property booking_start : Int64 + property booking_end : Int64 + property timezone : String? + property resource_id : String + property user_id : String + property user_email : String + property user_name : String + property zones : Array(String) + property title : String + property checked_in : Bool? + property description : String + end + + private def process_update(json) + update = BookingUpdate.from_json(json) # Only do something if the update is for a zone specified in settings(:zone_ids) - return unless (@zone_ids && update[:zones]).present? + return unless (@zone_ids & update.zones) end def fetch_and_post period_start = Time.utc.to_unix period_end = @time_period.from_now.to_unix - zones = [@building] - payload = staff_api.query_bookings(@booking_category, period_start, period_end, zones).get.to_json + payload = staff_api.query_bookings(@booking_category, period_start, period_end, @zone_ids).get.to_json headers = HTTP::Headers.new @custom_headers.each { |key, value| headers[key] = value } From 98c9759a289cae0fd6ef6744a1f9f70f49fec959 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 15 Apr 2021 10:58:56 +0100 Subject: [PATCH 3/7] feat(desk_webhook): add struct for booking updates --- drivers/place/desk_booking_webhook.cr | 49 ++++++++++++++------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/drivers/place/desk_booking_webhook.cr b/drivers/place/desk_booking_webhook.cr index 99bc5079dd0..f8904e4e48b 100644 --- a/drivers/place/desk_booking_webhook.cr +++ b/drivers/place/desk_booking_webhook.cr @@ -5,21 +5,15 @@ class Place::DeskBookingWebhook < PlaceOS::Driver generic_name :DeskBookingWebhook description "sends a webhook with booking information as it changes" - accessor staff_api : StaffAPI_1 - default_settings({ - post_uri: "https://remote-server/path", + booking_category: "desk", zone_ids: ["zone-id"], # list of zone_ids to monitor + post_uri: "https://remote-server/path", custom_headers: { "API_KEY" => "123456", }, - # how many days from now do we want to send - days_from_now: 14, - - booking_category: "desk", - # Note: only use metadata_key and mapped_id_key if we need to map BookingUpdate.resource_id to another value metadata_key: "desks", # e.g. metadata_key would be "desks" for below example mapped_id_key: nil, # e.g. mapped_id_key would be "other_id" for below example @@ -38,11 +32,12 @@ class Place::DeskBookingWebhook < PlaceOS::Driver debug: false }) - @time_period : Time::Span = 14.days @booking_category : String = "desk" - @custom_headers = {} of String => String @zone_ids = [] of String @post_uri = "" + @custom_headers = {} of String => String + @metadata_key : String = "desks" + @mapped_id_key : String? = nil @debug : Bool = false def on_load @@ -54,11 +49,13 @@ class Place::DeskBookingWebhook < PlaceOS::Driver end def on_update - @post_uri = setting(String, :post_uri) + @booking_category = setting(String, :booking_category) @zone_ids = setting(Array(String), :zone_ids) + @post_uri = setting(String, :post_uri) + @custom_headers = setting(Hash(String, String), :custom_headers) + @metadata_key = setting(String, :metadata_key) + @mapped_id_key = setting?(String, :mapped_id_key) @custom_headers = setting(Hash(String, String), :custom_headers) - @time_period = setting(Int32, :days_from_now).days - @booking_category = setting(String, :booking_category) @debug = setting(Bool, :debug) end @@ -76,29 +73,35 @@ class Place::DeskBookingWebhook < PlaceOS::Driver property user_email : String property user_name : String property zones : Array(String) + property process_state : String? + property last_changed : Int64? + property approver_name : String? + property approver_email : String? property title : String property checked_in : Bool? property description : String + property ext_data : JSON::Any? + property booked_by_email : String + property booked_by_name : String end - private def process_update(json) - update = BookingUpdate.from_json(json) - # Only do something if the update is for a zone specified in settings(:zone_ids) - return unless (@zone_ids & update.zones) - end - - def fetch_and_post - period_start = Time.utc.to_unix - period_end = @time_period.from_now.to_unix - payload = staff_api.query_bookings(@booking_category, period_start, period_end, @zone_ids).get.to_json + def process_update(update_json : String) + update = BookingUpdate.from_json(update_json) + # Only do something if the update is for the booking_type and zones specified in the settings + return unless update.booking_type == @booking_category && (@zone_ids & update.zones) headers = HTTP::Headers.new @custom_headers.each { |key, value| headers[key] = value } headers["Content-Type"] = "application/json; charset=UTF-8" + payload = @mapped_id_key ? map_resource_id(update).to_json : update_json logger.debug { "Posting: #{payload} \n with Headers: #{headers}" } if @debug response = HTTP::Client.post @post_uri, headers, body: payload raise "Request failed with #{response.status_code}: #{response.body}" unless response.status_code < 300 "#{response.status_code}: #{response.body}" end + + private def map_resource_id(update : BookingUpdate) + update + end end From f0cef7682e581c90d17e4bcf4cc17e77aac87407 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 15 Apr 2021 11:07:15 +0100 Subject: [PATCH 4/7] fix(desk_webhook): check matching zones correctly --- drivers/place/desk_booking_webhook.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/place/desk_booking_webhook.cr b/drivers/place/desk_booking_webhook.cr index f8904e4e48b..6b73a17816e 100644 --- a/drivers/place/desk_booking_webhook.cr +++ b/drivers/place/desk_booking_webhook.cr @@ -88,7 +88,7 @@ class Place::DeskBookingWebhook < PlaceOS::Driver def process_update(update_json : String) update = BookingUpdate.from_json(update_json) # Only do something if the update is for the booking_type and zones specified in the settings - return unless update.booking_type == @booking_category && (@zone_ids & update.zones) + return if update.booking_type != @booking_category || !(@zone_ids & update.zones).empty? headers = HTTP::Headers.new @custom_headers.each { |key, value| headers[key] = value } From ee67b87f9b468b4584fe90609415054275b44329 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Thu, 15 Apr 2021 11:58:06 +0100 Subject: [PATCH 5/7] feat(desk_webhook): add resource id mapping --- drivers/place/desk_booking_webhook.cr | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/place/desk_booking_webhook.cr b/drivers/place/desk_booking_webhook.cr index 6b73a17816e..d2fe247ef36 100644 --- a/drivers/place/desk_booking_webhook.cr +++ b/drivers/place/desk_booking_webhook.cr @@ -1,10 +1,13 @@ require "http/client" +require "placeos" class Place::DeskBookingWebhook < PlaceOS::Driver descriptive_name "Desk Booking Webhook" generic_name :DeskBookingWebhook description "sends a webhook with booking information as it changes" + accessor staff_api : StaffAPI_1 + default_settings({ booking_category: "desk", zone_ids: ["zone-id"], # list of zone_ids to monitor @@ -93,6 +96,8 @@ class Place::DeskBookingWebhook < PlaceOS::Driver headers = HTTP::Headers.new @custom_headers.each { |key, value| headers[key] = value } headers["Content-Type"] = "application/json; charset=UTF-8" + # If @mapped_id_key is present, then we need to map resource ids before sending the payload + # Otherwise, just use update_json unmodified as the payload payload = @mapped_id_key ? map_resource_id(update).to_json : update_json logger.debug { "Posting: #{payload} \n with Headers: #{headers}" } if @debug @@ -101,7 +106,16 @@ class Place::DeskBookingWebhook < PlaceOS::Driver "#{response.status_code}: #{response.body}" end + alias Metadata = PlaceOS::Client::API::Models::Metadata + private def map_resource_id(update : BookingUpdate) + metadata = Metadata.from_json(staff_api.metadata(update.zones.first, @metadata_key).get.to_json) + matching_resource = metadata.details.as_a.find(&.["id"].==(update.resource_id)).not_nil! + # If there is a mapped id value, use that for update.resource_id + # Otherwise, just use the current update.resource_id + if mapped_id_value = matching_resource[@mapped_id_key.not_nil!]? + update.resource_id = mapped_id_value.as_s + end update end end From c722f5213b4212287a5ae7c95608bbbb5c502c72 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Fri, 16 Apr 2021 12:17:20 +0100 Subject: [PATCH 6/7] fix(desk_webhook): update filtering logic --- drivers/place/desk_booking_webhook.cr | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/place/desk_booking_webhook.cr b/drivers/place/desk_booking_webhook.cr index d2fe247ef36..075cd2109e2 100644 --- a/drivers/place/desk_booking_webhook.cr +++ b/drivers/place/desk_booking_webhook.cr @@ -45,7 +45,6 @@ class Place::DeskBookingWebhook < PlaceOS::Driver def on_load monitor("staff/booking/changed") do |_subscription, booking_update| - logger.debug { "received booking changed event #{booking_update}" } process_update(booking_update) end on_update @@ -91,7 +90,10 @@ class Place::DeskBookingWebhook < PlaceOS::Driver def process_update(update_json : String) update = BookingUpdate.from_json(update_json) # Only do something if the update is for the booking_type and zones specified in the settings - return if update.booking_type != @booking_category || !(@zone_ids & update.zones).empty? + + return if update.booking_type != @booking_category || (@zone_ids & update.zones).empty? + + logger.debug { "received update #{update}" } if @debug headers = HTTP::Headers.new @custom_headers.each { |key, value| headers[key] = value } @@ -102,8 +104,10 @@ class Place::DeskBookingWebhook < PlaceOS::Driver logger.debug { "Posting: #{payload} \n with Headers: #{headers}" } if @debug response = HTTP::Client.post @post_uri, headers, body: payload - raise "Request failed with #{response.status_code}: #{response.body}" unless response.status_code < 300 - "#{response.status_code}: #{response.body}" + summary = "#{response.status_code}: #{response.body}" + raise "Request failed with #{summary}" unless response.status_code < 300 + logger.debug { summary } if @debug + summary end alias Metadata = PlaceOS::Client::API::Models::Metadata From f0ca40c4d8f7e973d5a68d6f9741a4873143f7c0 Mon Sep 17 00:00:00 2001 From: Philip Kheav Date: Fri, 16 Apr 2021 16:01:03 +0100 Subject: [PATCH 7/7] fix(desk_webhook): parse metadata json properly --- drivers/place/desk_booking_webhook.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/place/desk_booking_webhook.cr b/drivers/place/desk_booking_webhook.cr index 075cd2109e2..89f33368c3e 100644 --- a/drivers/place/desk_booking_webhook.cr +++ b/drivers/place/desk_booking_webhook.cr @@ -110,10 +110,10 @@ class Place::DeskBookingWebhook < PlaceOS::Driver summary end - alias Metadata = PlaceOS::Client::API::Models::Metadata + alias Metadata = Hash(String, PlaceOS::Client::API::Models::Metadata) private def map_resource_id(update : BookingUpdate) - metadata = Metadata.from_json(staff_api.metadata(update.zones.first, @metadata_key).get.to_json) + metadata = Metadata.from_json(staff_api.metadata(update.zones[0], @metadata_key).get.to_json)[@metadata_key] matching_resource = metadata.details.as_a.find(&.["id"].==(update.resource_id)).not_nil! # If there is a mapped id value, use that for update.resource_id # Otherwise, just use the current update.resource_id