spree_cm_commissioner 2.5.0.pre.pre2 → 2.5.0.pre.pre3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/test_and_build_gem.yml +1 -1
- data/.tool-versions +1 -1
- data/Gemfile.lock +1 -1
- data/app/controllers/spree/admin/integration_mappings_controller.rb +21 -0
- data/app/controllers/spree/admin/integration_sessions_controller.rb +21 -0
- data/app/controllers/spree/admin/integrations_controller.rb +83 -0
- data/app/controllers/spree/api/v2/storefront/event_matches_controller.rb +15 -0
- data/app/controllers/spree/api/v2/storefront/popular_route_places_controller.rb +1 -7
- data/app/controllers/spree/api/v2/storefront/route_places_controller.rb +9 -9
- data/app/errors/spree_cm_commissioner/integrations/external_client_error.rb +10 -0
- data/app/errors/spree_cm_commissioner/integrations/sync_error.rb +4 -0
- data/app/finders/spree_cm_commissioner/events/find_matches.rb +15 -0
- data/app/finders/spree_cm_commissioner/places/find_with_route.rb +10 -10
- data/app/finders/spree_cm_commissioner/routes/find_popular.rb +14 -10
- data/app/helpers/spree_cm_commissioner/external_integrations_helper.rb +58 -0
- data/app/interactors/spree_cm_commissioner/stock/inventory_item_resetter.rb +1 -1
- data/app/jobs/spree_cm_commissioner/integrations/base_job.rb +39 -0
- data/app/jobs/spree_cm_commissioner/integrations/polling_job.rb +53 -0
- data/app/jobs/spree_cm_commissioner/integrations/polling_scheduler_job.rb +30 -0
- data/app/jobs/spree_cm_commissioner/stock/inventory_items_adjuster_job.rb +3 -3
- data/app/jobs/spree_cm_commissioner/telegram_alerts/integration_sync_failure_job.rb +17 -0
- data/app/models/concerns/spree_cm_commissioner/integrations/integration_mappable.rb +61 -0
- data/app/models/concerns/spree_cm_commissioner/line_item_integration.rb +36 -0
- data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +15 -3
- data/app/models/concerns/spree_cm_commissioner/order_integration.rb +33 -0
- data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +2 -0
- data/app/models/concerns/spree_cm_commissioner/store_metadata.rb +14 -2
- data/app/models/concerns/spree_cm_commissioner/tenant_preference.rb +0 -3
- data/app/models/concerns/spree_cm_commissioner/variant_options_concern.rb +1 -7
- data/app/models/spree_cm_commissioner/guest.rb +13 -0
- data/app/models/spree_cm_commissioner/integration.rb +29 -0
- data/app/models/spree_cm_commissioner/integration_mapping.rb +41 -0
- data/app/models/spree_cm_commissioner/integration_sync_session.rb +15 -0
- data/app/models/spree_cm_commissioner/integrations/stadium_x_v1.rb +37 -0
- data/app/models/spree_cm_commissioner/inventory_item.rb +5 -1
- data/app/models/spree_cm_commissioner/line_item_decorator.rb +13 -1
- data/app/models/spree_cm_commissioner/option_type_decorator.rb +8 -0
- data/app/models/spree_cm_commissioner/option_value_decorator.rb +34 -0
- data/app/models/spree_cm_commissioner/order_decorator.rb +1 -0
- data/app/models/spree_cm_commissioner/product_decorator.rb +3 -2
- data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +2 -2
- data/app/models/spree_cm_commissioner/redis_stock/inventory_updater.rb +2 -2
- data/app/models/spree_cm_commissioner/taxon_decorator.rb +1 -0
- data/app/models/spree_cm_commissioner/taxonomy_decorator.rb +13 -1
- data/app/models/spree_cm_commissioner/variant_decorator.rb +7 -4
- data/app/models/spree_cm_commissioner/vendor_decorator.rb +4 -0
- data/app/overrides/spree/admin/shared/sub_menu/_integrations/external_integrations.html.erb.deface +6 -0
- data/app/presenters/spree/variants/{visable_options_presenter.rb → visible_options_presenter.rb} +2 -4
- data/app/request_schemas/spree_cm_commissioner/route_places_request_schema.rb +0 -5
- data/app/services/spree_cm_commissioner/integrations/base/sync_manager.rb +69 -0
- data/app/services/spree_cm_commissioner/integrations/base/sync_result.rb +183 -0
- data/app/services/spree_cm_commissioner/integrations/polling.rb +70 -0
- data/app/services/spree_cm_commissioner/integrations/polling_scheduler.rb +79 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/external_client/client.rb +152 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/inventory/unstock_inventory.rb +83 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/polling/sync_matches.rb +113 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/polling/sync_zones.rb +215 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/resources/base.rb +20 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/resources/league.rb +19 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/resources/match.rb +95 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/resources/ticket.rb +81 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/resources/ticket_image.rb +19 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/resources/zone.rb +90 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_manager.rb +35 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/full_sync_strategy.rb +38 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/incremental_sync_strategy.rb +44 -0
- data/app/services/spree_cm_commissioner/integrations/stadium_x_v1/sync_strategies/webhook_sync_strategy.rb +16 -0
- data/app/services/spree_cm_commissioner/telegram_alerts/integration_sync_failure.rb +49 -0
- data/app/views/spree/admin/integration_mappings/_integration_mappings.html.erb +107 -0
- data/app/views/spree/admin/integration_mappings/index.html.erb +33 -0
- data/app/views/spree/admin/integration_sessions/_integration_sync_sessions.html.erb +116 -0
- data/app/views/spree/admin/integration_sessions/index.html.erb +42 -0
- data/app/views/spree/admin/integrations/_form.html.erb +104 -0
- data/app/views/spree/admin/integrations/_stadium_x_v1_fields.html.erb +29 -0
- data/app/views/spree/admin/integrations/edit.html.erb +45 -0
- data/app/views/spree/admin/integrations/index.html.erb +75 -0
- data/app/views/spree/admin/integrations/new.html.erb +25 -0
- data/app/views/spree/admin/tenants/_form.html.erb +0 -18
- data/app/views/spree/order_mailer/confirm_email.html.erb +1 -1
- data/app/views/spree_cm_commissioner/layouts/order_mailer.html.erb +1 -1
- data/app/views/spree_cm_commissioner/order_mailer/tenant/_footer.html.erb +6 -13
- data/app/views/spree_cm_commissioner/order_mailer/tenant/_support_contact.html.erb +24 -23
- data/config/locales/en.yml +8 -0
- data/config/locales/km.yml +8 -0
- data/config/routes.rb +8 -0
- data/db/migrate/20251017094845_create_cm_integrations.rb +22 -0
- data/db/migrate/20251017101555_create_cm_integration_sync_sessions.rb +68 -0
- data/db/migrate/20251017101605_create_cm_integration_mappings.rb +52 -0
- data/lib/cm_app_logger.rb +1 -0
- data/lib/spree_cm_commissioner/test_helper/factories/integration_factory.rb +25 -0
- data/lib/spree_cm_commissioner/test_helper/factories/integration_mapping_factory.rb +14 -0
- data/lib/spree_cm_commissioner/test_helper/factories/integration_sync_session_factory.rb +7 -0
- data/lib/spree_cm_commissioner/version.rb +1 -1
- data/lib/spree_cm_commissioner.rb +8 -7
- metadata +56 -6
- data/app/request_schemas/spree_cm_commissioner/popular_route_places_request_schema.rb +0 -12
- data/app/views/spree/admin/tenants/form/_footer.html.erb +0 -31
- data/app/views/spree/admin/tenants/form/_social.html.erb +0 -31
- data/db/migrate/20251209022924_add_contact_fields_to_cm_tenants.rb +0 -9
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
class SpreeCmCommissioner::Integrations::StadiumXV1
|
|
2
|
+
module Resources
|
|
3
|
+
class Zone < Base
|
|
4
|
+
attr_accessor :_id,
|
|
5
|
+
:title,
|
|
6
|
+
:color_brand,
|
|
7
|
+
:total_seats,
|
|
8
|
+
:online_price,
|
|
9
|
+
:walkin_price,
|
|
10
|
+
:match_id,
|
|
11
|
+
:club_id,
|
|
12
|
+
:is_active,
|
|
13
|
+
:created_by,
|
|
14
|
+
:updated_by,
|
|
15
|
+
:created_at,
|
|
16
|
+
:updated_at,
|
|
17
|
+
:end_datetime,
|
|
18
|
+
:is_earlybird,
|
|
19
|
+
:start_datetime,
|
|
20
|
+
:available_seats,
|
|
21
|
+
:ticket_image
|
|
22
|
+
|
|
23
|
+
def initialize(attributes = {})
|
|
24
|
+
super(attributes)
|
|
25
|
+
|
|
26
|
+
@total_seats = attributes['total_seats'].to_i
|
|
27
|
+
@available_seats = attributes['available_seats'].to_i
|
|
28
|
+
@online_price = attributes['online_price'].to_f
|
|
29
|
+
@walkin_price = attributes['walkin_price'].to_f
|
|
30
|
+
@is_active = attributes['is_active'] != false
|
|
31
|
+
@color_brand = valid_hex_color?(attributes['color_brand']) ? attributes['color_brand'].upcase : nil
|
|
32
|
+
|
|
33
|
+
@created_at = Time.zone.at(attributes['created_at'] / 1000) if attributes['created_at'].present?
|
|
34
|
+
@updated_at = Time.zone.at(attributes['updated_at'] / 1000) if attributes['updated_at'].present?
|
|
35
|
+
|
|
36
|
+
@start_datetime = Time.zone.parse(attributes['start_datetime']) if attributes['start_datetime'].present?
|
|
37
|
+
@end_datetime = Time.zone.parse(attributes['end_datetime']) if attributes['end_datetime'].present?
|
|
38
|
+
@ticket_image = TicketImage.new(attributes['ticket_image']) if attributes['ticket_image'].is_a?(Hash)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Manually set the match_id on the zone object.
|
|
42
|
+
# This is needed because the API does not return match_id but it's required for ticket creation.
|
|
43
|
+
# This is a temporary workaround until the API returns the match_id.
|
|
44
|
+
def set_match_id(match_id) # rubocop:disable Naming/AccessorMethodName
|
|
45
|
+
@match_id = match_id
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def to_hash
|
|
49
|
+
hash = {
|
|
50
|
+
'_id' => _id,
|
|
51
|
+
'title' => title,
|
|
52
|
+
'color_brand' => color_brand,
|
|
53
|
+
'total_seats' => total_seats,
|
|
54
|
+
'online_price' => online_price,
|
|
55
|
+
'walkin_price' => walkin_price,
|
|
56
|
+
'match_id' => match_id,
|
|
57
|
+
'club_id' => club_id,
|
|
58
|
+
'is_active' => is_active,
|
|
59
|
+
'created_by' => created_by,
|
|
60
|
+
'updated_by' => updated_by,
|
|
61
|
+
'created_at' => created_at&.iso8601,
|
|
62
|
+
'updated_at' => updated_at&.iso8601,
|
|
63
|
+
'end_datetime' => end_datetime&.iso8601,
|
|
64
|
+
'is_earlybird' => is_earlybird,
|
|
65
|
+
'start_datetime' => start_datetime&.iso8601,
|
|
66
|
+
'available_seats' => available_seats,
|
|
67
|
+
'ticket_image' => ticket_image&.to_h
|
|
68
|
+
}.compact
|
|
69
|
+
|
|
70
|
+
hash['ticket_image'] = hash['ticket_image'].transform_keys(&:to_s) if hash['ticket_image']
|
|
71
|
+
|
|
72
|
+
hash
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
alias to_h to_hash
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
# Validates if a color value is a valid hex color code.
|
|
80
|
+
# Accepts formats: #RRGGBB, #RRGGBBAA, RRGGBB, RRGGBBAA (with or without # prefix)
|
|
81
|
+
# Returns true only if the value is a valid hex color, false otherwise.
|
|
82
|
+
def valid_hex_color?(value)
|
|
83
|
+
return false if value.blank?
|
|
84
|
+
|
|
85
|
+
hex_pattern = /\A#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})\z/
|
|
86
|
+
hex_pattern.match?(value.to_s)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
class SpreeCmCommissioner::Integrations::StadiumXV1
|
|
2
|
+
class SyncManager < ::SpreeCmCommissioner::Integrations::Base::SyncManager
|
|
3
|
+
# override
|
|
4
|
+
def sync_full!
|
|
5
|
+
run_execution(:full) do
|
|
6
|
+
SyncStrategies::FullSyncStrategy.new(
|
|
7
|
+
client: @client,
|
|
8
|
+
integration: @integration
|
|
9
|
+
).call
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# override
|
|
14
|
+
def sync_incremental!
|
|
15
|
+
run_execution(:incremental) do
|
|
16
|
+
SyncStrategies::IncrementalSyncStrategy.new(
|
|
17
|
+
client: @client,
|
|
18
|
+
integration: @integration
|
|
19
|
+
).call
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# override
|
|
24
|
+
def sync_webhook!(event_type:, event_data:)
|
|
25
|
+
run_execution(:webhook) do
|
|
26
|
+
SyncStrategies::WebhookSyncStrategy.new(
|
|
27
|
+
client: @client,
|
|
28
|
+
integration: @integration,
|
|
29
|
+
event_type: event_type,
|
|
30
|
+
event_data: event_data
|
|
31
|
+
).call
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
class SpreeCmCommissioner::Integrations::StadiumXV1
|
|
2
|
+
module SyncStrategies
|
|
3
|
+
# Full sync strategy - syncs all matches from external system.
|
|
4
|
+
#
|
|
5
|
+
# Runs on a schedule (e.g., daily) to fetch and update all matches from Stadium X V1 API.
|
|
6
|
+
# After full sync, IncrementalSyncStrategy will sync zone data one match at a time.
|
|
7
|
+
#
|
|
8
|
+
# Example flow:
|
|
9
|
+
# 1. Full sync runs at 2:00 AM
|
|
10
|
+
# - Fetches all matches from Stadium X V1
|
|
11
|
+
# - Creates/updates match records
|
|
12
|
+
# - Creates integration mappings
|
|
13
|
+
# 2. Incremental sync runs every 10 seconds
|
|
14
|
+
# - Syncs zone data for one match per interval
|
|
15
|
+
# - Keeps data fresh without overwhelming the system
|
|
16
|
+
class FullSyncStrategy
|
|
17
|
+
def initialize(client:, integration:)
|
|
18
|
+
@client = client
|
|
19
|
+
@integration = integration
|
|
20
|
+
@sync_result = ::SpreeCmCommissioner::Integrations::Base::SyncResult.new
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def call
|
|
24
|
+
begin
|
|
25
|
+
Polling::SyncMatches.new(
|
|
26
|
+
client: @client,
|
|
27
|
+
integration: @integration,
|
|
28
|
+
sync_result: @sync_result
|
|
29
|
+
).call
|
|
30
|
+
rescue StandardError => e
|
|
31
|
+
@sync_result.record_error(e)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
@sync_result
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
class SpreeCmCommissioner::Integrations::StadiumXV1
|
|
2
|
+
module SyncStrategies
|
|
3
|
+
# Incremental sync strategy - syncs zones one match at a time.
|
|
4
|
+
#
|
|
5
|
+
# Runs every 10 seconds to sync zone data incrementally. Instead of syncing all matches
|
|
6
|
+
# at once (which would be slow), we sync one match per interval in round-robin order.
|
|
7
|
+
#
|
|
8
|
+
# Example with 3 matches:
|
|
9
|
+
# 10:00:00 - Sync zones for Match #1 (oldest last_synced_at)
|
|
10
|
+
# 10:00:10 - Sync zones for Match #2
|
|
11
|
+
# 10:00:20 - Sync zones for Match #3
|
|
12
|
+
# 10:00:30 - Sync zones for Match #1 again (cycle repeats)
|
|
13
|
+
#
|
|
14
|
+
# This ensures all matches get synced regularly without overwhelming the system.
|
|
15
|
+
class IncrementalSyncStrategy
|
|
16
|
+
def initialize(client:, integration:)
|
|
17
|
+
@client = client
|
|
18
|
+
@integration = integration
|
|
19
|
+
@sync_result = ::SpreeCmCommissioner::Integrations::Base::SyncResult.new
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call
|
|
23
|
+
begin
|
|
24
|
+
oldest_sync_mapping = Spree::Taxon.find_oldest_active_mapping(integration_id: @integration.id)
|
|
25
|
+
return @sync_result if oldest_sync_mapping.nil?
|
|
26
|
+
|
|
27
|
+
@sync_result.increment_metric(:match, :synced)
|
|
28
|
+
|
|
29
|
+
Polling::SyncZones.new(
|
|
30
|
+
integration: @integration,
|
|
31
|
+
client: @integration.client,
|
|
32
|
+
sync_result: @sync_result
|
|
33
|
+
).call(external_match_id: oldest_sync_mapping.external_id)
|
|
34
|
+
|
|
35
|
+
oldest_sync_mapping.update!(last_synced_at: Time.current)
|
|
36
|
+
rescue StandardError => e
|
|
37
|
+
@sync_result.record_error(e)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
@sync_result
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class SpreeCmCommissioner::Integrations::StadiumXV1
|
|
2
|
+
module SyncStrategies
|
|
3
|
+
class WebhookSyncStrategy
|
|
4
|
+
def initialize(client:, integration:, event_type:, event_data:)
|
|
5
|
+
@client = client
|
|
6
|
+
@integration = integration
|
|
7
|
+
@event_type = event_type
|
|
8
|
+
@event_data = event_data
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call
|
|
12
|
+
# TODO: Implement webhook sync logic
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module TelegramAlerts
|
|
3
|
+
class IntegrationSyncFailure
|
|
4
|
+
prepend ::Spree::ServiceModule::Base
|
|
5
|
+
|
|
6
|
+
# Send Telegram alert for integration sync failure
|
|
7
|
+
# @param error_message [String] The error message
|
|
8
|
+
# @param data [Hash] Additional context data (e.g., sync_type, match_id, event_type)
|
|
9
|
+
def call(error_message:, data: {})
|
|
10
|
+
chat_id = ENV.fetch('EXCEPTION_NOTIFIER_TELEGRAM_CHANNEL_ID', nil)
|
|
11
|
+
return failure(nil, 'Telegram chat ID not configured') if chat_id.blank?
|
|
12
|
+
|
|
13
|
+
telegram_client = ::Telegram.bots[:exception_notifier]
|
|
14
|
+
return failure(nil, 'Telegram bot not configured') if telegram_client.blank?
|
|
15
|
+
|
|
16
|
+
telegram_client.send_message(
|
|
17
|
+
chat_id: chat_id,
|
|
18
|
+
parse_mode: 'HTML',
|
|
19
|
+
text: format_message(error_message, data)
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
success(message: 'Alert sent successfully')
|
|
23
|
+
rescue StandardError => e
|
|
24
|
+
failure(nil, "Failed to send Telegram alert: #{e.message}")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def format_message(error_message, data)
|
|
30
|
+
text = []
|
|
31
|
+
|
|
32
|
+
text << '🚨 <b>Integration Sync Failed</b>'
|
|
33
|
+
|
|
34
|
+
# Render all data fields dynamically
|
|
35
|
+
data.each do |key, value|
|
|
36
|
+
next if value.blank?
|
|
37
|
+
|
|
38
|
+
formatted_key = key.to_s.humanize
|
|
39
|
+
text << "<b>#{formatted_key}:</b> <code>#{value}</code>"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
text << "<b>Error:</b> <code>#{error_message}</code>"
|
|
43
|
+
text << "<b>Time:</b> <code>#{Time.current.iso8601}</code>"
|
|
44
|
+
|
|
45
|
+
text.compact.join("\n")
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<div class="table-responsive">
|
|
2
|
+
<table class="table table-sm" id="listing_integration_mappings" data-hook>
|
|
3
|
+
<thead class="text-muted">
|
|
4
|
+
<tr>
|
|
5
|
+
<th><%= Spree.t(:id) %></th>
|
|
6
|
+
<th><%= Spree.t(:external_id) %></th>
|
|
7
|
+
<th><%= Spree.t(:internal_type) %></th>
|
|
8
|
+
<th><%= Spree.t(:internal_id) %></th>
|
|
9
|
+
<th><%= Spree.t(:status) %></th>
|
|
10
|
+
<th><%= Spree.t(:last_synced_at) %></th>
|
|
11
|
+
<th><%= Spree.t(:created_at) %></th>
|
|
12
|
+
<th><%= Spree.t(:updated_at) %></th>
|
|
13
|
+
<th></th>
|
|
14
|
+
</tr>
|
|
15
|
+
</thead>
|
|
16
|
+
|
|
17
|
+
<tbody>
|
|
18
|
+
<% mappings = defined?(@mappings) ? @mappings : integration.integration_mappings.order(updated_at: :desc).limit(25) %>
|
|
19
|
+
<% mappings.each do |mapping| %>
|
|
20
|
+
<tr id="integration_mapping_<%= mapping.id %>">
|
|
21
|
+
<td><%= mapping.id %></td>
|
|
22
|
+
<td><%= mapping.external_id %></td>
|
|
23
|
+
<td><%= mapping.internal_type %></td>
|
|
24
|
+
<td>
|
|
25
|
+
<%= integration_mapping_internal_link(mapping) %>
|
|
26
|
+
</td>
|
|
27
|
+
<td><%= mapping.status %></td>
|
|
28
|
+
<td><%= mapping.last_synced_at %></td>
|
|
29
|
+
<td><%= mapping.created_at %></td>
|
|
30
|
+
<td><%= mapping.updated_at %></td>
|
|
31
|
+
|
|
32
|
+
<td class="actions">
|
|
33
|
+
<% if mapping.external_payload.present? %>
|
|
34
|
+
<%= link_to_with_icon 'activity.svg', Spree.t("admin.external_integrations.view_result"), "#mapping_payload_modal_#{mapping.id}",
|
|
35
|
+
class: 'btn btn-light btn-sm',
|
|
36
|
+
no_text: true,
|
|
37
|
+
data: { toggle: 'modal', target: "#mapping_payload_modal_#{mapping.id}" },
|
|
38
|
+
title: Spree.t("admin.external_integrations.external_payload") %>
|
|
39
|
+
<% end %>
|
|
40
|
+
</td>
|
|
41
|
+
</tr>
|
|
42
|
+
<% end %>
|
|
43
|
+
</tbody>
|
|
44
|
+
</table>
|
|
45
|
+
|
|
46
|
+
<% if mappings.empty? %>
|
|
47
|
+
<small class="form-text text-muted"><%= Spree.t("admin.external_integrations.no_results") %></small>
|
|
48
|
+
<% end %>
|
|
49
|
+
|
|
50
|
+
<% if defined?(@mappings) && @mappings.respond_to?(:total_pages) %>
|
|
51
|
+
<div class="mt-3">
|
|
52
|
+
<%= paginate @mappings, theme: 'admin-twitter-bootstrap-4' %>
|
|
53
|
+
</div>
|
|
54
|
+
<% end %>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<% mappings.each do |mapping| %>
|
|
58
|
+
<% if mapping.external_payload.present? %>
|
|
59
|
+
<div
|
|
60
|
+
class="modal fade"
|
|
61
|
+
id="mapping_payload_modal_<%= mapping.id %>"
|
|
62
|
+
tabindex="-1"
|
|
63
|
+
role="dialog"
|
|
64
|
+
aria-labelledby="mapping_payload_modal_label_<%= mapping.id %>"
|
|
65
|
+
aria-hidden="true"
|
|
66
|
+
>
|
|
67
|
+
<div class="modal-dialog modal-lg" role="document">
|
|
68
|
+
<div class="modal-content">
|
|
69
|
+
<div class="modal-header">
|
|
70
|
+
<h5
|
|
71
|
+
class="modal-title"
|
|
72
|
+
id="mapping_payload_modal_label_<%= mapping.id %>"
|
|
73
|
+
>
|
|
74
|
+
<%= Spree.t("admin.external_integrations.external_payload") %> - Mapping #<%= mapping.id %>
|
|
75
|
+
</h5>
|
|
76
|
+
|
|
77
|
+
<button
|
|
78
|
+
type="button"
|
|
79
|
+
class="close"
|
|
80
|
+
data-dismiss="modal"
|
|
81
|
+
aria-label="Close"
|
|
82
|
+
>
|
|
83
|
+
<span aria-hidden="true">×</span>
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<div class="modal-body">
|
|
88
|
+
<pre
|
|
89
|
+
class="small mb-0 p-2 bg-light border rounded"
|
|
90
|
+
style="max-height: 500px; overflow-y: auto;"
|
|
91
|
+
><code><%= JSON.pretty_generate(mapping.external_payload) %></code></pre>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div class="modal-footer">
|
|
95
|
+
<button
|
|
96
|
+
type="button"
|
|
97
|
+
class="btn btn-secondary"
|
|
98
|
+
data-dismiss="modal"
|
|
99
|
+
>
|
|
100
|
+
<%= Spree.t(:close) %>
|
|
101
|
+
</button>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
<% end %>
|
|
107
|
+
<% end %>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<% content_for :page_title do %>
|
|
2
|
+
<%= link_to Spree.t('external_integrations'), spree.admin_integrations_url %> / <%= @integration.name %>
|
|
3
|
+
<% end %>
|
|
4
|
+
|
|
5
|
+
<% content_for :page_tabs do %>
|
|
6
|
+
<li class="nav-item">
|
|
7
|
+
<%= link_to Spree.t(:edit),
|
|
8
|
+
edit_admin_integration_path(@integration),
|
|
9
|
+
class: "nav-link" %>
|
|
10
|
+
</li>
|
|
11
|
+
|
|
12
|
+
<li class="nav-item">
|
|
13
|
+
<%= link_to Spree.t(:integration_sync_sessions),
|
|
14
|
+
admin_integration_sessions_path(@integration),
|
|
15
|
+
class: "nav-link" %>
|
|
16
|
+
</li>
|
|
17
|
+
|
|
18
|
+
<li class="nav-item">
|
|
19
|
+
<%= link_to Spree.t(:integration_mappings),
|
|
20
|
+
admin_integration_mappings_path(@integration),
|
|
21
|
+
class: "nav-link active" %>
|
|
22
|
+
</li>
|
|
23
|
+
<% end %>
|
|
24
|
+
|
|
25
|
+
<div data-hook="admin_integrations_mappings" class="card mb-3">
|
|
26
|
+
<div class="card-header">
|
|
27
|
+
<h5 class="card-title mb-0 h6"><%= Spree.t(:integration_mappings) %></h5>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div class="card-body">
|
|
31
|
+
<%= render partial: 'integration_mappings', locals: { integration: @integration } %>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<div class="table-responsive">
|
|
2
|
+
<table
|
|
3
|
+
class="table table-sm"
|
|
4
|
+
id="listing_integration_sync_sessions"
|
|
5
|
+
data-hook
|
|
6
|
+
>
|
|
7
|
+
<thead class="text-muted">
|
|
8
|
+
<tr>
|
|
9
|
+
<th><%= Spree.t(:id) %></th>
|
|
10
|
+
<th><%= Spree.t(:sync_type) %></th>
|
|
11
|
+
<th><%= Spree.t(:status) %></th>
|
|
12
|
+
<th><%= Spree.t(:started_at) %></th>
|
|
13
|
+
<th><%= Spree.t(:ended_at) %></th>
|
|
14
|
+
<th><%= Spree.t(:duration) %></th>
|
|
15
|
+
<th></th>
|
|
16
|
+
</tr>
|
|
17
|
+
</thead>
|
|
18
|
+
|
|
19
|
+
<tbody>
|
|
20
|
+
<% sync_sessions = defined?(@sync_sessions) ? @sync_sessions : integration.integration_sync_sessions.order(created_at: :desc).limit(25) %>
|
|
21
|
+
<% sync_sessions.each do |sess| %>
|
|
22
|
+
<tr id="integration_sync_session_<%= sess.id %>">
|
|
23
|
+
<td><%= sess.id %></td>
|
|
24
|
+
<td><%= sess.sync_type %></td>
|
|
25
|
+
<td><%= sess.status %></td>
|
|
26
|
+
<td><%= sess.started_at %></td>
|
|
27
|
+
<td><%= sess.ended_at %></td>
|
|
28
|
+
<td>
|
|
29
|
+
<% if sess.started_at && sess.ended_at %>
|
|
30
|
+
<% duration = (sess.ended_at - sess.started_at).to_i %>
|
|
31
|
+
|
|
32
|
+
<% if duration < 60 %>
|
|
33
|
+
<%= "#{duration} sec" %>
|
|
34
|
+
<% else %>
|
|
35
|
+
<%= "#{duration / 60} min" %>
|
|
36
|
+
<% end %>
|
|
37
|
+
<% else %>
|
|
38
|
+
N/A
|
|
39
|
+
<% end %>
|
|
40
|
+
</td>
|
|
41
|
+
<td class="actions">
|
|
42
|
+
<% if sess.sync_result.present? %>
|
|
43
|
+
<%= link_to_with_icon 'activity.svg', Spree.t("admin.external_integrations.view_result"), "#session_result_modal_#{sess.id}",
|
|
44
|
+
class: 'btn btn-light btn-sm',
|
|
45
|
+
no_text: true,
|
|
46
|
+
data: { toggle: 'modal', target: "#session_result_modal_#{sess.id}" },
|
|
47
|
+
title: Spree.t("admin.external_integrations.sync_result") %>
|
|
48
|
+
<% end %>
|
|
49
|
+
</td>
|
|
50
|
+
</tr>
|
|
51
|
+
<% end %>
|
|
52
|
+
</tbody>
|
|
53
|
+
</table>
|
|
54
|
+
|
|
55
|
+
<% if sync_sessions.empty? %>
|
|
56
|
+
<small class="form-text text-muted"><%= Spree.t("admin.external_integrations.no_results") %></small>
|
|
57
|
+
<% end %>
|
|
58
|
+
|
|
59
|
+
<% if defined?(@sync_sessions) && @sync_sessions.respond_to?(:total_pages) %>
|
|
60
|
+
<div class="mt-3">
|
|
61
|
+
<%= paginate @sync_sessions, theme: 'admin-twitter-bootstrap-4' %>
|
|
62
|
+
</div>
|
|
63
|
+
<% end %>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<% sync_sessions.each do |sess| %>
|
|
67
|
+
<% if sess.sync_result.present? %>
|
|
68
|
+
<div
|
|
69
|
+
class="modal fade"
|
|
70
|
+
id="session_result_modal_<%= sess.id %>"
|
|
71
|
+
tabindex="-1"
|
|
72
|
+
role="dialog"
|
|
73
|
+
aria-labelledby="session_result_modal_label_<%= sess.id %>"
|
|
74
|
+
aria-hidden="true"
|
|
75
|
+
>
|
|
76
|
+
<div class="modal-dialog modal-lg" role="document">
|
|
77
|
+
<div class="modal-content">
|
|
78
|
+
<div class="modal-header">
|
|
79
|
+
<h5
|
|
80
|
+
class="modal-title"
|
|
81
|
+
id="session_result_modal_label_<%= sess.id %>"
|
|
82
|
+
>
|
|
83
|
+
<%= Spree.t("admin.external_integrations.sync_result") %> - Session #<%= sess.id %>
|
|
84
|
+
</h5>
|
|
85
|
+
|
|
86
|
+
<button
|
|
87
|
+
type="button"
|
|
88
|
+
class="close"
|
|
89
|
+
data-dismiss="modal"
|
|
90
|
+
aria-label="Close"
|
|
91
|
+
>
|
|
92
|
+
<span aria-hidden="true">×</span>
|
|
93
|
+
</button>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div class="modal-body">
|
|
97
|
+
<pre
|
|
98
|
+
class="small mb-0 p-2 bg-light border rounded"
|
|
99
|
+
style="max-height: 500px; overflow-y: auto;"
|
|
100
|
+
><code><%= JSON.pretty_generate(sess.sync_result) %></code></pre>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<div class="modal-footer">
|
|
104
|
+
<button
|
|
105
|
+
type="button"
|
|
106
|
+
class="btn btn-secondary"
|
|
107
|
+
data-dismiss="modal"
|
|
108
|
+
>
|
|
109
|
+
<%= Spree.t(:close) %>
|
|
110
|
+
</button>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
<% end %>
|
|
116
|
+
<% end %>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<% content_for :page_title do %>
|
|
2
|
+
<%= link_to Spree.t('external_integrations'), spree.admin_integrations_url %> / <%= @integration.name %>
|
|
3
|
+
<% end %>
|
|
4
|
+
|
|
5
|
+
<% content_for :page_tabs do %>
|
|
6
|
+
<li class="nav-item">
|
|
7
|
+
<%= link_to Spree.t(:edit),
|
|
8
|
+
edit_admin_integration_path(@integration),
|
|
9
|
+
class: "nav-link" %>
|
|
10
|
+
</li>
|
|
11
|
+
|
|
12
|
+
<li class="nav-item">
|
|
13
|
+
<%= link_to Spree.t(:integration_sync_sessions),
|
|
14
|
+
admin_integration_sessions_path(@integration),
|
|
15
|
+
class: "nav-link active" %>
|
|
16
|
+
</li>
|
|
17
|
+
|
|
18
|
+
<li class="nav-item">
|
|
19
|
+
<%= link_to Spree.t(:integration_mappings),
|
|
20
|
+
admin_integration_mappings_path(@integration),
|
|
21
|
+
class: "nav-link" %>
|
|
22
|
+
</li>
|
|
23
|
+
<% end %>
|
|
24
|
+
|
|
25
|
+
<div data-hook="admin_integrations_sessions" class="card mb-3">
|
|
26
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
|
27
|
+
<h5 class="card-title mb-0 h6">
|
|
28
|
+
<%= Spree.t(:integration_sync_sessions) %>
|
|
29
|
+
</h5>
|
|
30
|
+
|
|
31
|
+
<div class="card-header-actions">
|
|
32
|
+
<%= button_to Spree.t(:enqueue_full_sync),
|
|
33
|
+
enqueue_polling_admin_integration_path(@integration, sync_type: :full),
|
|
34
|
+
method: :post,
|
|
35
|
+
class: 'btn btn-primary btn-sm' %>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="card-body">
|
|
40
|
+
<%= render partial: 'integration_sync_sessions', locals: { integration: @integration } %>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<div data-hook="admin_external_integrations_form_fields" class="row">
|
|
2
|
+
<div class="col-6">
|
|
3
|
+
<%= f.field_container :type do %>
|
|
4
|
+
<%= f.label :type, raw(I18n.t('spree.admin.external_integrations.integration_type', default: 'Integration type') + required_span_tag) %>
|
|
5
|
+
|
|
6
|
+
<% if f.object.persisted? %>
|
|
7
|
+
<%= f.text_field :type, value: f.object.type, class: 'form-control', disabled: true, readonly: true %>
|
|
8
|
+
<%= f.hidden_field :type, value: f.object.type %>
|
|
9
|
+
<% else %>
|
|
10
|
+
<%= f.select :type,
|
|
11
|
+
options_for_select(@integration_type_options, f.object.type),
|
|
12
|
+
{ include_blank: Spree.t(:choose_an_option) },
|
|
13
|
+
class: 'form-control select2',
|
|
14
|
+
required: true %>
|
|
15
|
+
<% end %>
|
|
16
|
+
|
|
17
|
+
<%= f.error_message_on :type %>
|
|
18
|
+
<% end %>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div class="col-6">
|
|
22
|
+
<%= f.field_container :name do %>
|
|
23
|
+
<%= f.label :name, raw(Spree.t(:name) + required_span_tag) %>
|
|
24
|
+
<%= f.text_field :name, required: true, class: 'form-control', placeholder: Spree.t(:name) %>
|
|
25
|
+
<%= f.error_message_on :name %>
|
|
26
|
+
<% end %>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div class="col-6">
|
|
30
|
+
<%= f.field_container :vendor_id do %>
|
|
31
|
+
<%= f.label :vendor_id, raw(I18n.t('spree.admin.external_integrations.vendor', default: 'Vendor') + required_span_tag) %>
|
|
32
|
+
|
|
33
|
+
<%= f.select :vendor_id,
|
|
34
|
+
options_from_collection_for_select(@vendors, :id, :name, f.object.vendor_id),
|
|
35
|
+
{ include_blank: Spree.t(:choose_an_option) },
|
|
36
|
+
class: 'form-control select2',
|
|
37
|
+
required: true %>
|
|
38
|
+
|
|
39
|
+
<%= f.error_message_on :vendor_id %>
|
|
40
|
+
<% end %>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div class="col-6">
|
|
44
|
+
<%= f.field_container :tenant_id do %>
|
|
45
|
+
<%= f.label :tenant_id, I18n.t('spree.admin.external_integrations.tenant', default: 'Tenant') %>
|
|
46
|
+
|
|
47
|
+
<%= f.select :tenant_id,
|
|
48
|
+
options_from_collection_for_select(@tenants, :id, :name, f.object.tenant_id),
|
|
49
|
+
{ include_blank: Spree.t(:choose_an_option), prompt: Spree.t(:optional) },
|
|
50
|
+
class: 'form-control select2',
|
|
51
|
+
data: { allow_clear: true, placeholder: Spree.t(:optional) } %>
|
|
52
|
+
|
|
53
|
+
<%= f.error_message_on :tenant_id %>
|
|
54
|
+
<% end %>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div class="col-6">
|
|
58
|
+
<%= f.field_container :status do %>
|
|
59
|
+
<%= f.label :status, raw(Spree.t(:status) + required_span_tag) %>
|
|
60
|
+
|
|
61
|
+
<%= f.select :status,
|
|
62
|
+
options_for_select(@status_options, f.object.status),
|
|
63
|
+
{},
|
|
64
|
+
class: 'form-control select2',
|
|
65
|
+
required: true %>
|
|
66
|
+
|
|
67
|
+
<%= f.error_message_on :status %>
|
|
68
|
+
<% end %>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div class="col-6">
|
|
72
|
+
<%= f.field_container :conflict_strategy do %>
|
|
73
|
+
<%= f.label :conflict_strategy, raw(I18n.t('spree.admin.external_integrations.conflict_strategy', default: 'Conflict strategy') + required_span_tag) %>
|
|
74
|
+
|
|
75
|
+
<%= f.select :conflict_strategy,
|
|
76
|
+
options_for_select(@conflict_strategy_options, f.object.conflict_strategy),
|
|
77
|
+
{},
|
|
78
|
+
class: 'form-control select2',
|
|
79
|
+
required: true %>
|
|
80
|
+
|
|
81
|
+
<%= f.error_message_on :conflict_strategy %>
|
|
82
|
+
<% end %>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div class="col-6">
|
|
86
|
+
<%= f.field_container :incremental_sync_interval_seconds do %>
|
|
87
|
+
<%= f.label :incremental_sync_interval_seconds, raw(I18n.t('spree.admin.external_integrations.incremental_sync_interval_seconds', default: 'Incremental sync interval (seconds)') + required_span_tag) %>
|
|
88
|
+
<%= f.number_field :incremental_sync_interval_seconds, class: 'form-control', min: 1, max: 3600, required: true %>
|
|
89
|
+
<%= f.error_message_on :incremental_sync_interval_seconds %>
|
|
90
|
+
<% end %>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div class="col-6">
|
|
94
|
+
<%= f.field_container :full_sync_interval_hours do %>
|
|
95
|
+
<%= f.label :full_sync_interval_hours, raw(I18n.t('spree.admin.external_integrations.full_sync_interval_hours', default: 'Full sync interval (hours)') + required_span_tag) %>
|
|
96
|
+
<%= f.number_field :full_sync_interval_hours, class: 'form-control', min: 1, max: 168, required: true %>
|
|
97
|
+
<%= f.error_message_on :full_sync_interval_hours %>
|
|
98
|
+
<% end %>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<% if f.object.type == 'SpreeCmCommissioner::Integrations::StadiumXV1' || (f.object.new_record? && params[:type] == 'SpreeCmCommissioner::Integrations::StadiumXV1') %>
|
|
102
|
+
<%= render partial: 'stadium_x_v1_fields', locals: { f: f } %>
|
|
103
|
+
<% end %>
|
|
104
|
+
</div>
|