Skip to content

Commit

Permalink
Merge pull request #374 from roomorama/release/0.11.0
Browse files Browse the repository at this point in the history
Release/0.11.0
  • Loading branch information
keang authored Sep 21, 2016
2 parents 12a4d7a + 160bfae commit 65fcc0f
Show file tree
Hide file tree
Showing 46 changed files with 1,020 additions and 350 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ CONCIERGE_WEB_AUTHENTICATION=admin:admin
CONCIERGE_APP=all
CONCIERGE_API_SECRET="f52bfb549b54f119272cb21"
CONCIERGE_URL=http://localhost:2300

ZENDESK_NOTIFY_URL="https://example.org"
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ This file summarises the most important changes that went live on each release
of Concierge. Please check the Wiki entry on the release process to understand
how this file is formatted and how the process works.

## [0.11.0] - 2016-09-21
### Added
- `ZendeskNotify` client with ticket creation on cancellation of bookings from Poplidays and AtLeisure

### Fixed
- Ciirus:: ignore permissions error about MC disabled clone properties
- SAW: return unavailable quotation instead of Result.error while quote price
- SAW: pass stay length = 2 days instead 1 so that API returns prices for a bit more properties
- SAW: add synchronisation.new_context for metadata worker
- SAW: renamed PropertyRate entity to UnitsPricing
- SAW: Skip invalid postal code "." #286

## [0.10.0] - 2016-09-20
### Added
- Atleisure:: calendar sync worker
Expand Down
3 changes: 2 additions & 1 deletion apps/api/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ class Application < Hanami::Application

load_paths << [
'controllers',
'views'
'views',
'support'
]

routes 'config/routes'
Expand Down
1 change: 1 addition & 0 deletions apps/api/config/environment_variables.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
- ROOMORAMA_SECRET_WAYTOSTAY
- ROOMORAMA_SECRET_CIIRUS
- ROOMORAMA_SECRET_SAW
- ZENDESK_NOTIFY_URL
14 changes: 8 additions & 6 deletions apps/api/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
post '/saw/booking', to: 's_a_w#booking'
post '/poplidays/booking', to: 'poplidays#booking'

post 'waytostay/cancel', to: 'waytostay#cancel'
post 'ciirus/cancel', to: 'ciirus#cancel'
post 'saw/cancel', to: 's_a_w#cancel'
post 'kigo/cancel', to: 'kigo#cancel'
post 'kigo/legacy/cancel', to: 'kigo/legacy#cancel'
post '/waytostay/cancel', to: 'waytostay#cancel'
post '/ciirus/cancel', to: 'ciirus#cancel'
post '/saw/cancel', to: 's_a_w#cancel'
post '/kigo/cancel', to: 'kigo#cancel'
post '/kigo/legacy/cancel', to: 'kigo/legacy#cancel'
post '/poplidays/cancel', to: 'poplidays#cancel'
post '/atleisure/cancel', to: 'at_leisure#cancel'

post 'checkout', to: 'static#checkout'
post '/checkout', to: 'static#checkout'
get '/kigo/image/:property_id/:image_id', to: 'kigo#image'
18 changes: 18 additions & 0 deletions apps/api/controllers/atleisure/cancel.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require_relative "../cancel"
require_relative "../zendesk_notify_cancellation"

module API::Controllers::AtLeisure

# API::Controllers::AtLeisure::Cancel
#
# AtLeisure does not have a cancellation API. Therefore, when a booking is
# cancelled, we notify Customer Support through Zendesk.
class Cancel
include API::Controllers::Cancel
include API::Controllers::ZendeskNotifyCancellation

def supplier_name
AtLeisure::Client::SUPPLIER_NAME
end
end
end
1 change: 1 addition & 0 deletions apps/api/controllers/params/cancel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module API::Controllers::Params
class Cancel < API::Action::Params

param :reference_number, presence: true, type: String
param :inquiry_id, presence: true, type: String

# Constructs a map of errors for the request.
#
Expand Down
18 changes: 18 additions & 0 deletions apps/api/controllers/poplidays/cancel.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require_relative "../cancel"
require_relative "../zendesk_notify_cancellation"

module API::Controllers::Poplidays

# API::Controllers::Poplidays::Cancel
#
# Poplidays does not have a cancellation API. Therefore, when a booking is
# cancelled, we notify Customer Support through Zendesk.
class Cancel
include API::Controllers::Cancel
include API::Controllers::ZendeskNotifyCancellation

def supplier_name
Poplidays::Client::SUPPLIER_NAME
end
end
end
32 changes: 32 additions & 0 deletions apps/api/controllers/zendesk_notify_cancellation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module API::Controllers

# +API::Controllers::ZendeskNotifyCancellation+
#
# This module implements the +cancel_reservation+ method expected to be present
# on cancellation controllers that include the helper +API::Controllers::Cancel+
# module.
#
# The +cancel_reservation+ expects a +supplier_name+ method to be implemented
# (a requirement already enforced by +API::Controllers::Cancel+), and sends a request
# to the +ZendeskNotify+ service to notify Customer Support about a supplier cancellation.
#
# To be used by suppliers which do not provide a cancellation API.
module ZendeskNotifyCancellation

def cancel_reservation(params)
zendesk_notify = API::Support::ZendeskNotify.new.notify("cancellation", {
supplier: supplier_name,
supplier_id: params[:reference_number],
bridge_id: params[:inquiry_id]
})

if zendesk_notify.success?
Result.new(params[:reference_number])
else
zendesk_notify
end
end

end

end
5 changes: 4 additions & 1 deletion apps/api/middlewares/roomorama_webhook.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,10 @@ def booking(data)
def cancelled(data)
payload = safe_access(data)

params = { reference_number: payload.get("inquiry.reference_number") }
params = {
reference_number: payload.get("inquiry.reference_number"),
inquiry_id: payload.get("inquiry.id")
}

env["rack.input"] = StringIO.new(json_encode(params))
true
Expand Down
91 changes: 91 additions & 0 deletions apps/api/support/zendesk_notify.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
module API::Support

# +API::Support::ZendeskNotify+
#
# This is a client class to the +ZendeskNotify+ service, a simple API to
# send tickets on Roomorama/BridgeRentals Zendesk account.
#
# Usage
#
# client = API::Support::ZendeskNotify.new
# ticket_id = "cancellation"
# attributes = {
# supplier: "Supplier X",
# supplier_id: "212",
# bridge_id: "291"
# }
#
# client.notify # => <#Result value=true>
#
# The +attributes+ passed when calling the +notify+ method are dependent on the
# type of ticket being sent.
class ZendeskNotify
include Concierge::JSON

SUPPORTED_TICKETS = %w(cancellation)

# Zendesk's API is very slow when sending tickets. Especially on the sandbox
# environment. As the cancellation webhook is sent on the background, without
# blocking the user interface, we can afford a higher timeout.
CONNECTION_TIMEOUT = 20

attr_reader :http, :endpoint

# initializes internal state. The +ZendeskNotify+ service URL must be properly
# configured on the +ZENDESK_NOTIFY_URL+ environment variable.
def initialize
uri = URI.parse(zendesk_notify_url)
host = [uri.scheme, "://", uri.host].join

@http = Concierge::HTTPClient.new(host, timeout: CONNECTION_TIMEOUT)
@endpoint = uri.request_uri
end

def notify(ticket_id, attributes)
return invalid_ticket unless SUPPORTED_TICKETS.include?(ticket_id.to_s)

params = {
ticketId: ticket_id,
attributes: attributes
}

result = http.post(endpoint, json_encode(params), { "Content-Type" => "application/json" })
return result unless result.success?

parse_response(result.value.body)
end

private

# expected response (examples)
#
# Success
# { "status": "ok", "message": "Ticket sent successfully" }
#
# Failure
# { "status": "error", "message": "Failure to deliver ticket" }
def parse_response(body)
decoded_body = json_decode(body)
return decoded_body unless decoded_body.success?

successful = (decoded_body.value["status"] == "ok")

if successful
Result.new(true)
else
error_message = decoded_body.value["message"]
Result.error(:zendesk_notify_failure, error_message)
end
end

def invalid_ticket
Result.error(:zendesk_invalid_ticket)
end

def zendesk_notify_url
ENV["ZENDESK_NOTIFY_URL"]
end

end

end
70 changes: 41 additions & 29 deletions apps/workers/suppliers/saw.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def initialize(host)
end

def perform
result = importer.fetch_countries
result = synchronisation.new_context { importer.fetch_countries }

if result.success?
countries = result.value
Expand All @@ -21,7 +21,9 @@ def perform
return
end

result = importer.fetch_available_properties_by_countries(countries)
result = synchronisation.new_context do
importer.fetch_properties_by_countries(countries)
end

if result.success?
properties = result.value
Expand All @@ -31,52 +33,62 @@ def perform
return
end

result = importer.fetch_all_unit_rates_for_properties(properties)
result = synchronisation.new_context do
importer.fetch_all_unit_rates_for_properties(properties)
end

if result.success?
all_unit_rates = result.value
else
message = "Failed to perform the `#fetch_rates_for_properties` operation"
message = "Failed to perform the `#fetch_all_unit_rates_for_properties` operation"
announce_error(message, result)
return
end

properties.each do |property|
synchronisation.start(property.internal_id) do
Concierge.context.disable!

unit_rates = find_rates(property.internal_id, all_unit_rates)
fetch_details_and_build_property(property, unit_rates)
result = synchronisation.new_context do
importer.fetch_detailed_property(property.internal_id)
end
if result.success?
detailed_property = result.value

next if skip?(detailed_property)

synchronisation.start(property.internal_id) do
unit_rates = find_rates(property.internal_id, all_unit_rates)
Result.new ::SAW::Mappers::RoomoramaProperty.build(
property,
detailed_property,
unit_rates
)
end
else
synchronisation.failed!
# potentially a more meaningful result can be passed from HTTPClient, into result.error.data
announce_error("Failed to perform the `#fetch_detailed_property` operation", result)
end
end

synchronisation.finish!
end

def fetch_details_and_build_property(property, rates)
result = importer.fetch_detailed_property(property.internal_id)

if result.success?
detailed_property = result.value

roomorama_property = ::SAW::Mappers::RoomoramaProperty.build(
property,
detailed_property,
rates
)
private

Result.new(roomorama_property)
else
message = "Failed to perform the `#fetch_detailed_property` operation"
announce_error(message, result)
result
# Check if we can skip(and not publish) property, because of the following cases
# - Postal code is "."
#
# Returns true to caller if skipped
#
def skip?(detailed_property)
if detailed_property.postal_code == "."
synchronisation.skip_property(detailed_property.internal_id, "Invalid postal_code: .")
return true
end
return false
end

private

def importer
@properties ||= ::SAW::Importer.new(credentials)
@importer ||= ::SAW::Importer.new(credentials)
end

def credentials
Expand All @@ -101,7 +113,7 @@ def announce_error(message, result)
end

def find_rates(property_id, all_unit_rates)
all_unit_rates.find { |rate| rate.id == property_id.to_s }
all_unit_rates.find { |rate| rate.property_id == property_id.to_s }
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ class PropertyPermissionsFetcher < BaseCommand
MC_PROPERTY_DISABLED_MESSAGE = 'Error (8000) The MC may have disabled the property from the feed. The MC user for '\
'this property has not enabled this property for your feed, but you have accepted this property'\
' in the SuperSites area of the CiiRUS windows application. '
IGNORABLE_ERROR_MESSAGES = [PROPERTY_DELETED_MESSAGE, MC_PROPERTY_DISABLED_MESSAGE]
MC_DISABLED_CLONE_PROPERTY_MESSAGE = 'Error (15000) The MC may have disabled the clone property from the feed. The MC '\
'user for this property has not enabled this property for your feed, but you have accepted this property in '\
'the SuperSites area of the CiiRUS windows application. '
IGNORABLE_ERROR_MESSAGES = [PROPERTY_DELETED_MESSAGE, MC_PROPERTY_DISABLED_MESSAGE, MC_DISABLED_CLONE_PROPERTY_MESSAGE]


def call(property_id)
Expand Down
3 changes: 2 additions & 1 deletion lib/concierge/suppliers/saw/commands/base_fetcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ class BaseFetcher
#
# Whitelisted SAW API errors:
# 1007 - No properties are available for the given search parameters
VALID_RESULT_ERROR_CODES = ['1007']
# 3031 - Rates are not available for this property
VALID_RESULT_ERROR_CODES = ['1007', '3031']

attr_reader :credentials

Expand Down
Loading

0 comments on commit 65fcc0f

Please sign in to comment.