spree_cm_commissioner 2.5.0.pre.pre8 → 2.5.0.pre.pre10
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/.bundle/config +3 -2
- data/.github/workflows/test_and_build_gem.yml +123 -57
- data/.tool-versions +1 -1
- data/Gemfile.lock +1 -1
- data/Rakefile +55 -29
- data/app/controllers/spree/admin/base_controller_decorator.rb +3 -3
- data/app/controllers/spree/admin/base_import_orders_controller.rb +6 -1
- data/app/controllers/spree/admin/classifications_controller.rb +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/admin/notification_sender_controller.rb +1 -1
- data/app/controllers/spree/api/v2/storefront/event_matches_controller.rb +15 -0
- data/app/controllers/spree/api/v2/storefront/queue_cart/line_items_controller.rb +6 -6
- data/app/controllers/spree/api/v2/storefront/trips_controller.rb +11 -0
- 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/helpers/spree_cm_commissioner/external_integrations_helper.rb +58 -0
- data/app/interactors/spree_cm_commissioner/create_event.rb +1 -1
- data/app/interactors/spree_cm_commissioner/customer_notification_cron_executor.rb +1 -1
- data/app/interactors/spree_cm_commissioner/stock/inventory_item_resetter.rb +1 -1
- data/app/interactors/spree_cm_commissioner/stock/stock_movement_creator.rb +3 -2
- data/app/jobs/spree_cm_commissioner/conversion_pre_calculator_job.rb +2 -2
- data/app/jobs/spree_cm_commissioner/customer_notification_sender_job.rb +3 -3
- data/app/jobs/spree_cm_commissioner/enqueue_cart/add_item_job.rb +7 -7
- data/app/jobs/spree_cm_commissioner/ensure_event_for_product_line_item_guests_job.rb +1 -1
- data/app/jobs/spree_cm_commissioner/event_line_items_date_syncer_job.rb +2 -2
- data/app/jobs/spree_cm_commissioner/export_csv_job.rb +2 -2
- data/app/jobs/spree_cm_commissioner/import_order_job.rb +5 -5
- 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/invalidate_cache_request_job.rb +2 -2
- data/app/jobs/spree_cm_commissioner/inventory_item_syncer_job.rb +8 -2
- data/app/jobs/spree_cm_commissioner/option_type_variants_public_metadata_updater_job.rb +7 -3
- data/app/jobs/spree_cm_commissioner/option_value_variants_public_metadata_updater_job.rb +6 -2
- data/app/jobs/spree_cm_commissioner/order_complete_telegram_sender_job.rb +2 -2
- data/app/jobs/spree_cm_commissioner/product_event_id_to_children_syncer_job.rb +2 -2
- data/app/jobs/spree_cm_commissioner/reports_assigner_job.rb +2 -2
- data/app/jobs/spree_cm_commissioner/sms_pin_code_job.rb +1 -1
- data/app/jobs/spree_cm_commissioner/state_job.rb +2 -2
- data/app/jobs/spree_cm_commissioner/stock/inventory_items_adjuster_job.rb +6 -3
- data/app/jobs/spree_cm_commissioner/stock/inventory_items_generator_job.rb +2 -2
- data/app/jobs/spree_cm_commissioner/telegram_alerts/integration_sync_failure_job.rb +17 -0
- data/app/jobs/spree_cm_commissioner/transit/route_fulfilled_order_count_incrementer_job.rb +2 -2
- data/app/jobs/spree_cm_commissioner/transit/route_order_count_incrementer_job.rb +2 -2
- data/app/jobs/spree_cm_commissioner/transit/route_previous_trip_count_decrementer_job.rb +2 -2
- data/app/jobs/spree_cm_commissioner/transit/route_trip_count_decrementer_job.rb +2 -2
- data/app/jobs/spree_cm_commissioner/transit/route_trip_count_incrementer_job.rb +2 -2
- data/app/jobs/spree_cm_commissioner/vendor_creation_telegram_alert_sender_job.rb +2 -2
- data/app/jobs/spree_cm_commissioner/vendor_job.rb +2 -2
- data/app/jobs/spree_cm_commissioner/waiting_room_session_firebase_logger_job.rb +1 -1
- 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 +16 -4
- data/app/models/concerns/spree_cm_commissioner/option_value_attr_type.rb +1 -1
- data/app/models/concerns/spree_cm_commissioner/order_integration.rb +33 -0
- data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +4 -2
- data/app/models/concerns/spree_cm_commissioner/store_metadata.rb +14 -2
- data/app/models/concerns/spree_cm_commissioner/variant_options_concern.rb +1 -7
- data/app/models/spree_cm_commissioner/export.rb +1 -1
- 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 +4 -3
- 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 +6 -3
- data/app/models/spree_cm_commissioner/stock_item_decorator.rb +4 -4
- data/app/models/spree_cm_commissioner/taxon_decorator.rb +2 -1
- 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 +6 -2
- 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/serializers/spree_cm_commissioner/v2/storefront/trip_serializer.rb +1 -1
- 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/bin/run_spec_group +101 -0
- 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 +36 -4
- 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/test_helper/factories/vendor_factory.rb +1 -0
- data/lib/spree_cm_commissioner/test_helper/factories/vendor_place_factory.rb +3 -2
- data/lib/spree_cm_commissioner/version.rb +1 -1
- data/lib/spree_cm_commissioner.rb +8 -7
- metadata +58 -3
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<!-- Stadium X V1 API Credentials -->
|
|
2
|
+
<div class="col-12">
|
|
3
|
+
<hr class="my-4">
|
|
4
|
+
|
|
5
|
+
<h6 class="text-muted">
|
|
6
|
+
<%= I18n.t('spree.admin.external_integrations.stadium_x_credentials', default: 'Stadium X API Credentials') %>
|
|
7
|
+
</h6>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div class="col-6">
|
|
11
|
+
<%= f.field_container :public_key do %>
|
|
12
|
+
<%= f.label :public_key, raw(I18n.t('spree.admin.external_integrations.public_key', default: 'Public Key') + required_span_tag) %>
|
|
13
|
+
<%= text_field_tag 'spree_cm_commissioner_integration[private_metadata][public_key]', f.object.public_key, class: 'form-control', required: true %>
|
|
14
|
+
<% end %>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div class="col-6">
|
|
18
|
+
<%= f.field_container :private_key do %>
|
|
19
|
+
<%= f.label :private_key, raw(I18n.t('spree.admin.external_integrations.private_key', default: 'Private Key') + required_span_tag) %>
|
|
20
|
+
<%= text_field_tag 'spree_cm_commissioner_integration[private_metadata][private_key]', f.object.private_key, class: 'form-control', required: true %>
|
|
21
|
+
<% end %>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div class="col-6">
|
|
25
|
+
<%= f.field_container :base_url do %>
|
|
26
|
+
<%= f.label :base_url, raw(I18n.t('spree.admin.external_integrations.base_url', default: 'Base URL') + required_span_tag) %>
|
|
27
|
+
<%= text_field_tag 'spree_cm_commissioner_integration[private_metadata][base_url]', f.object.base_url, class: 'form-control', placeholder: 'https://api.stadiumx.com', required: true %>
|
|
28
|
+
<% end %>
|
|
29
|
+
</div>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<% content_for :page_title do %>
|
|
2
|
+
<%= link_to Spree.t('external_integrations'), spree.admin_integrations_url %> / <%= @object.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(@object),
|
|
9
|
+
class: "nav-link active" %>
|
|
10
|
+
</li>
|
|
11
|
+
|
|
12
|
+
<li class="nav-item">
|
|
13
|
+
<%= link_to Spree.t(:integration_sync_sessions),
|
|
14
|
+
admin_integration_sessions_path(@object),
|
|
15
|
+
class: "nav-link" %>
|
|
16
|
+
</li>
|
|
17
|
+
|
|
18
|
+
<li class="nav-item">
|
|
19
|
+
<%= link_to Spree.t(:integration_mappings),
|
|
20
|
+
admin_integration_mappings_path(@object),
|
|
21
|
+
class: "nav-link" %>
|
|
22
|
+
</li>
|
|
23
|
+
<% end %>
|
|
24
|
+
|
|
25
|
+
<div data-hook="admin_integrations" class="card mb-3">
|
|
26
|
+
<div class="card-header">
|
|
27
|
+
<h5 class="card-title mb-0 h6"><%= Spree.t('external_integrations') %></h5>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div class="card-body">
|
|
31
|
+
<div data-hook="admin_integrations_edit_form_header">
|
|
32
|
+
<%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div data-hook="admin_integrations_edit_form">
|
|
36
|
+
<%= form_with model: @object, url: admin_integration_path(@object), method: :patch, scope: :spree_cm_commissioner_integration do |f| %>
|
|
37
|
+
<%= render partial: 'form', locals: { f: f } %>
|
|
38
|
+
|
|
39
|
+
<div data-hook="admin_integrations_edit_form_buttons">
|
|
40
|
+
<%= render partial: 'spree/admin/shared/edit_resource_links', locals: { collection_url: spree.admin_integrations_path } %>
|
|
41
|
+
</div>
|
|
42
|
+
<% end %>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<% content_for :page_title do %>
|
|
2
|
+
<%= Spree.t('external_integrations') %>
|
|
3
|
+
<% end %>
|
|
4
|
+
|
|
5
|
+
<% content_for :page_actions do %>
|
|
6
|
+
<%= button_link_to Spree.t(:new_external_integration), new_admin_integration_path, class: "btn-success", icon: 'add.svg' %>
|
|
7
|
+
<% end if can? :create, SpreeCmCommissioner::Integration %>
|
|
8
|
+
|
|
9
|
+
<% if @integrations.any? %>
|
|
10
|
+
<div class="table-responsive border rounded bg-white mb-3">
|
|
11
|
+
<table class="table" id="listing_integrations" data-hook>
|
|
12
|
+
<thead class="text-muted">
|
|
13
|
+
<tr data-hook="admin_integrations_index_headers">
|
|
14
|
+
<th><%= Spree.t(:name) %></th>
|
|
15
|
+
<th><%= Spree.t(:base_url) %></th>
|
|
16
|
+
<th><%= Spree.t(:conflict_strategy) %></th>
|
|
17
|
+
<th><%= Spree.t(:incremental_sync_interval_seconds) %></th>
|
|
18
|
+
<th><%= Spree.t(:full_sync_interval_hours) %></th>
|
|
19
|
+
<th><%= Spree.t(:vendor) %></th>
|
|
20
|
+
<th><%= Spree.t(:status) %></th>
|
|
21
|
+
<th><%= Spree.t(:updated_at) %></th>
|
|
22
|
+
</tr>
|
|
23
|
+
</thead>
|
|
24
|
+
|
|
25
|
+
<tbody>
|
|
26
|
+
<% @integrations.each do |integration| %>
|
|
27
|
+
<tr
|
|
28
|
+
id="<%= spree_dom_id integration %>"
|
|
29
|
+
data-hook="admin_integrations_index_rows"
|
|
30
|
+
>
|
|
31
|
+
<td><%= integration.name %></td>
|
|
32
|
+
|
|
33
|
+
<td>
|
|
34
|
+
<%= integration_base_url_link(integration.base_url) %>
|
|
35
|
+
</td>
|
|
36
|
+
|
|
37
|
+
<td>
|
|
38
|
+
<%= integration_conflict_strategy_badge(integration.conflict_strategy) %>
|
|
39
|
+
</td>
|
|
40
|
+
|
|
41
|
+
<td>
|
|
42
|
+
<%= integration_interval_badge(integration.incremental_sync_interval_seconds, 's') %>
|
|
43
|
+
</td>
|
|
44
|
+
|
|
45
|
+
<td>
|
|
46
|
+
<%= integration_interval_badge(integration.full_sync_interval_hours, 'h') %>
|
|
47
|
+
</td>
|
|
48
|
+
|
|
49
|
+
<td><%= integration.vendor.name %></td>
|
|
50
|
+
|
|
51
|
+
<td>
|
|
52
|
+
<%= integration_status_badge(integration.status) %>
|
|
53
|
+
</td>
|
|
54
|
+
|
|
55
|
+
<td><%= integration.updated_at %></td>
|
|
56
|
+
|
|
57
|
+
<td
|
|
58
|
+
data-hook="admin_integrations_index_row_actions"
|
|
59
|
+
class="actions"
|
|
60
|
+
>
|
|
61
|
+
<span class="d-flex justify-content-end">
|
|
62
|
+
<%= link_to_edit integration, url: edit_admin_integration_path(integration), no_text: true if can?(:edit, integration) %>
|
|
63
|
+
<%= link_to_delete integration, url: admin_integration_path(integration), no_text: true if can?(:delete, integration) %>
|
|
64
|
+
</span>
|
|
65
|
+
</td>
|
|
66
|
+
</tr>
|
|
67
|
+
<% end %>
|
|
68
|
+
</tbody>
|
|
69
|
+
</table>
|
|
70
|
+
</div>
|
|
71
|
+
<% else %>
|
|
72
|
+
<small class="form-text text-muted">
|
|
73
|
+
<%= raw I18n.t('external_integrations.empty_info') %>
|
|
74
|
+
</small>
|
|
75
|
+
<% end %>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<% content_for :page_title do %>
|
|
2
|
+
<%= link_to Spree.t('external_integrations'), spree.admin_integrations_url %> / <%= Spree.t(:new) %>
|
|
3
|
+
<% end %>
|
|
4
|
+
|
|
5
|
+
<div data-hook="admin_integrations" class="card mb-3">
|
|
6
|
+
<div class="card-header">
|
|
7
|
+
<h5 class="card-title mb-0 h6"><%= Spree.t('external_integrations') %></h5>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div class="card-body">
|
|
11
|
+
<div data-hook="admin_integrations_new_form_header">
|
|
12
|
+
<%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div data-hook="admin_integrations_new_form">
|
|
16
|
+
<%= form_with model: @object, url: { action: 'create' }, scope: :spree_cm_commissioner_integration do |f| %>
|
|
17
|
+
<%= render partial: 'form', locals: { f: f } %>
|
|
18
|
+
|
|
19
|
+
<div data-hook="admin_integrations_new_form_buttons">
|
|
20
|
+
<%= render partial: 'spree/admin/shared/new_resource_links' %>
|
|
21
|
+
</div>
|
|
22
|
+
<% end %>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
data/bin/run_spec_group
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# Spec Group Runner - Parallel Test Execution
|
|
5
|
+
# ============================================================================
|
|
6
|
+
#
|
|
7
|
+
# PURPOSE:
|
|
8
|
+
# Split all spec files into N equal groups and run a specific group.
|
|
9
|
+
# Used for parallel test execution in CI (GitHub Actions) and locally.
|
|
10
|
+
#
|
|
11
|
+
# HOW IT WORKS:
|
|
12
|
+
# 1. Finds all *_spec.rb files in spec/ directory
|
|
13
|
+
# 2. Sorts them alphabetically for consistent ordering
|
|
14
|
+
# 3. Divides them into N equal groups (e.g., 10 groups)
|
|
15
|
+
# 4. Runs RSpec on the files in the specified group
|
|
16
|
+
#
|
|
17
|
+
# USAGE:
|
|
18
|
+
# bin/run_spec_group <group_number> <total_groups>
|
|
19
|
+
#
|
|
20
|
+
# EXAMPLES:
|
|
21
|
+
# bin/run_spec_group 1 10 # Run group 1 out of 10 (files 1-15 if 150 total)
|
|
22
|
+
# bin/run_spec_group 5 10 # Run group 5 out of 10 (files 61-75 if 150 total)
|
|
23
|
+
# bin/run_spec_group 1 3 # Run first third of all specs
|
|
24
|
+
#
|
|
25
|
+
# CI USAGE (GitHub Actions):
|
|
26
|
+
# - Matrix strategy runs 10-12 parallel jobs (groups 1-12)
|
|
27
|
+
# - Each job runs ~1/10th or ~1/12th of total specs
|
|
28
|
+
# - Total test time = slowest group time (not sum of all)
|
|
29
|
+
# - Example: 150 specs × 2s each = 300s sequential
|
|
30
|
+
# 150 specs ÷ 10 groups = 15 specs × 2s = 30s parallel
|
|
31
|
+
# 150 specs ÷ 12 groups = 13 specs × 2s = 26s parallel
|
|
32
|
+
#
|
|
33
|
+
# LOCAL USAGE:
|
|
34
|
+
# - Run specific group to debug failing tests
|
|
35
|
+
# - Run multiple groups in parallel terminals
|
|
36
|
+
# - Faster iteration than running all specs
|
|
37
|
+
#
|
|
38
|
+
# OUTPUT:
|
|
39
|
+
# - Shows total spec count
|
|
40
|
+
# - Shows files per group
|
|
41
|
+
# - Lists all files in current group
|
|
42
|
+
# - Runs RSpec with those files
|
|
43
|
+
#
|
|
44
|
+
# WHERE USED:
|
|
45
|
+
# - .github/workflows/test_and_build_gem.yml (test job, matrix strategy)
|
|
46
|
+
# - Can be run locally for debugging
|
|
47
|
+
#
|
|
48
|
+
# ============================================================================
|
|
49
|
+
|
|
50
|
+
group_num = ARGV[0]&.to_i
|
|
51
|
+
total_groups = ARGV[1]&.to_i
|
|
52
|
+
|
|
53
|
+
unless group_num && group_num > 0 && group_num <= total_groups
|
|
54
|
+
puts "Usage: bin/run_spec_group <group_number> [total_groups]"
|
|
55
|
+
puts "Example: bin/run_spec_group 1 10"
|
|
56
|
+
puts " Runs group 1 out of 10 total groups"
|
|
57
|
+
exit 1
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
spec_dir = File.expand_path('../spec', __dir__)
|
|
61
|
+
|
|
62
|
+
# Find all spec files and sort for consistent ordering
|
|
63
|
+
all_spec_files = Dir.glob("#{spec_dir}/**/*_spec.rb").sort
|
|
64
|
+
|
|
65
|
+
if all_spec_files.empty?
|
|
66
|
+
puts "❌ No spec files found in #{spec_dir}"
|
|
67
|
+
exit 1
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Calculate files per group
|
|
71
|
+
files_per_group = (all_spec_files.length.to_f / total_groups).ceil
|
|
72
|
+
|
|
73
|
+
# Get the files for this specific group
|
|
74
|
+
start_idx = (group_num - 1) * files_per_group
|
|
75
|
+
end_idx = [start_idx + files_per_group - 1, all_spec_files.length - 1].min
|
|
76
|
+
|
|
77
|
+
if start_idx >= all_spec_files.length
|
|
78
|
+
puts "⚠️ Group #{group_num} has no spec files (total: #{all_spec_files.length} files across #{total_groups} groups)"
|
|
79
|
+
exit 0
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
group_files = all_spec_files[start_idx..end_idx]
|
|
83
|
+
|
|
84
|
+
puts "=" * 80
|
|
85
|
+
puts "Running Group #{group_num}/#{total_groups}"
|
|
86
|
+
puts "Total spec files: #{all_spec_files.length}"
|
|
87
|
+
puts "Files per group: ~#{files_per_group}"
|
|
88
|
+
puts "This group: #{group_files.length} files (#{start_idx + 1}-#{end_idx + 1})"
|
|
89
|
+
puts "=" * 80
|
|
90
|
+
puts
|
|
91
|
+
puts "📋 Files in this group:"
|
|
92
|
+
group_files.each_with_index do |file, idx|
|
|
93
|
+
relative_path = file.sub("#{spec_dir}/", '')
|
|
94
|
+
puts " #{start_idx + idx + 1}. #{relative_path}"
|
|
95
|
+
end
|
|
96
|
+
puts
|
|
97
|
+
puts "=" * 80
|
|
98
|
+
puts
|
|
99
|
+
|
|
100
|
+
# Run rspec with the specific files
|
|
101
|
+
exec("bundle", "exec", "rspec", *group_files)
|
data/config/locales/en.yml
CHANGED
|
@@ -454,6 +454,14 @@ en:
|
|
|
454
454
|
admin:
|
|
455
455
|
display_on:
|
|
456
456
|
frontend_for_early_adopter: "Storefront for Early Adopter"
|
|
457
|
+
external_integrations_title: "External Integrations"
|
|
458
|
+
integration_sync_enqueued: "Integration sync has been enqueued"
|
|
459
|
+
integration_inactive: "Integration is inactive"
|
|
460
|
+
external_integrations:
|
|
461
|
+
view_result: "View Result"
|
|
462
|
+
sync_result: "Sync Result"
|
|
463
|
+
external_payload: "External Payload"
|
|
464
|
+
no_results: "No results found"
|
|
457
465
|
event:
|
|
458
466
|
check_in:
|
|
459
467
|
success: "Guest check-in in successfully"
|
data/config/locales/km.yml
CHANGED
|
@@ -341,6 +341,14 @@ km:
|
|
|
341
341
|
admin:
|
|
342
342
|
display_on:
|
|
343
343
|
frontend_for_early_adopter: "Storefront for Early Adopter"
|
|
344
|
+
external_integrations_title: "ការភ្ជាប់ខាងក្រៅ"
|
|
345
|
+
integration_sync_enqueued: "ការធ្វើសមកាលកម្មបានដាក់ក្នុងជួរ"
|
|
346
|
+
integration_inactive: "ការភ្ជាប់មិនដំណើរការ"
|
|
347
|
+
external_integrations:
|
|
348
|
+
view_result: "មើលលទ្ធផល"
|
|
349
|
+
sync_result: "លទ្ធផលធ្វើសមកាលកម្ម"
|
|
350
|
+
external_payload: "ទិន្នន័យខាងក្រៅ"
|
|
351
|
+
no_results: "មិនមានលទ្ធផលទេ"
|
|
344
352
|
event:
|
|
345
353
|
check_in:
|
|
346
354
|
success: "Guest check-in in successfully"
|
data/config/routes.rb
CHANGED
|
@@ -283,6 +283,13 @@ Spree::Core::Engine.add_routes do
|
|
|
283
283
|
post :set_webhook
|
|
284
284
|
end
|
|
285
285
|
end
|
|
286
|
+
resources :integrations do
|
|
287
|
+
member do
|
|
288
|
+
post :enqueue_polling
|
|
289
|
+
end
|
|
290
|
+
resources :sessions, only: [:index], controller: 'integration_sessions'
|
|
291
|
+
resources :mappings, only: [:index], controller: 'integration_mappings'
|
|
292
|
+
end
|
|
286
293
|
|
|
287
294
|
resources :webhooks_subscribers do
|
|
288
295
|
resources :rules, controller: :webhooks_subscriber_rules, except: %i[index show]
|
|
@@ -695,6 +702,7 @@ Spree::Core::Engine.add_routes do
|
|
|
695
702
|
|
|
696
703
|
resources :seat_layouts, only: %i[show index]
|
|
697
704
|
resources :inventory_items, only: %i[index]
|
|
705
|
+
resources :event_matches, only: %i[index]
|
|
698
706
|
namespace :transit do
|
|
699
707
|
resources :draft_orders, only: %i[create]
|
|
700
708
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class CreateCmIntegrations < ActiveRecord::Migration[7.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :cm_integrations, if_not_exists: true do |t|
|
|
4
|
+
t.string :type, null: false
|
|
5
|
+
t.string :name, null: false
|
|
6
|
+
|
|
7
|
+
t.integer :status, null: false, default: 0
|
|
8
|
+
t.integer :conflict_strategy, null: false, default: 0
|
|
9
|
+
|
|
10
|
+
t.integer :incremental_sync_interval_seconds, null: false, default: 10
|
|
11
|
+
t.integer :full_sync_interval_hours, null: false, default: 24
|
|
12
|
+
|
|
13
|
+
t.references :tenant, foreign_key: { to_table: :cm_tenants }, null: true
|
|
14
|
+
t.references :vendor, foreign_key: { to_table: :spree_vendors }, null: false
|
|
15
|
+
|
|
16
|
+
t.jsonb :public_metadata, default: {}
|
|
17
|
+
t.jsonb :private_metadata, default: {}
|
|
18
|
+
|
|
19
|
+
t.timestamps
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
class CreateCmIntegrationSyncSessions < ActiveRecord::Migration[7.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :cm_integration_sync_sessions, if_not_exists: true do |t|
|
|
4
|
+
t.integer :status, null: false
|
|
5
|
+
t.integer :sync_type, null: false
|
|
6
|
+
|
|
7
|
+
t.datetime :started_at
|
|
8
|
+
t.datetime :ended_at
|
|
9
|
+
|
|
10
|
+
t.references :integration, foreign_key: { to_table: :cm_integrations }, null: false
|
|
11
|
+
t.references :tenant, foreign_key: { to_table: :cm_tenants }, null: true
|
|
12
|
+
|
|
13
|
+
# Stores the serialized sync result (SyncResult#to_h) for this session.
|
|
14
|
+
# This field is used to persist the outcome of the sync operation,
|
|
15
|
+
# allowing for detailed tracking of results, whether the sync was successful or not.
|
|
16
|
+
#
|
|
17
|
+
# Base class: app/services/spree_cm_commissioner/integrations/base/sync_result.rb
|
|
18
|
+
#
|
|
19
|
+
# Example implementations include:
|
|
20
|
+
# - Full sync results: integrations/stadium_x_v1/sync_results/full_sync_result.rb
|
|
21
|
+
# - Incremental sync results: integrations/stadium_x_v1/sync_results/incremental_sync_result.rb
|
|
22
|
+
#
|
|
23
|
+
# The result is saved upon completion of the session in the SyncManager#run_execution method:
|
|
24
|
+
# - app/services/spree_cm_commissioner/integrations/base/sync_manager.rb
|
|
25
|
+
t.jsonb :sync_result, default: {}
|
|
26
|
+
|
|
27
|
+
t.timestamps
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Indexes for PollingScheduler queries (optimized for performance)
|
|
31
|
+
# Initially used in polling_scheduler.rb
|
|
32
|
+
#
|
|
33
|
+
# Index 1: Find in_progress/pending syncs created within last hour
|
|
34
|
+
# Used by: should_run_full_sync? and should_run_incremental_sync?
|
|
35
|
+
# Query: .where(sync_type: [...], status: [...]).where('created_at > ?', 1.hour.ago).exists?
|
|
36
|
+
# Why: Composite index on all filter columns for fast lookup of active syncs
|
|
37
|
+
unless index_exists?(:cm_integration_sync_sessions, [:integration_id, :sync_type, :status, :created_at], name: 'idx_sync_sessions_integration_type_status_created_at')
|
|
38
|
+
add_index :cm_integration_sync_sessions, [:integration_id, :sync_type, :status, :created_at],
|
|
39
|
+
name: 'idx_sync_sessions_integration_type_status_created_at'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Index 2: Find latest completed full sync (partial index - only completed rows)
|
|
43
|
+
# Used by: should_run_full_sync?
|
|
44
|
+
# Query: .where(sync_type: :full, status: :completed).order(created_at: :desc).first
|
|
45
|
+
# Why: Partial index reduces size by ~60% (excludes in_progress/pending/failed rows)
|
|
46
|
+
# Descending order on created_at allows DB to return latest without sorting
|
|
47
|
+
# Only 2 columns needed since sync_type and status are filtered by WHERE clause
|
|
48
|
+
unless index_exists?(:cm_integration_sync_sessions, [:integration_id, :created_at], name: 'idx_sync_sessions_full_completed')
|
|
49
|
+
add_index :cm_integration_sync_sessions, [:integration_id, :created_at],
|
|
50
|
+
name: 'idx_sync_sessions_full_completed',
|
|
51
|
+
where: "sync_type = 0 AND status = 2", # full=0, completed=2
|
|
52
|
+
order: { created_at: :desc }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Index 3: Find latest completed incremental sync (partial index - only completed rows)
|
|
56
|
+
# Used by: should_run_incremental_sync?
|
|
57
|
+
# Query: .where(sync_type: :incremental, status: :completed).order(created_at: :desc).first
|
|
58
|
+
# Why: Partial index reduces size by ~60% (excludes in_progress/pending/failed rows)
|
|
59
|
+
# Descending order on created_at allows DB to return latest without sorting
|
|
60
|
+
# Only 2 columns needed since sync_type and status are filtered by WHERE clause
|
|
61
|
+
unless index_exists?(:cm_integration_sync_sessions, [:integration_id, :created_at], name: 'idx_sync_sessions_incremental_completed')
|
|
62
|
+
add_index :cm_integration_sync_sessions, [:integration_id, :created_at],
|
|
63
|
+
name: 'idx_sync_sessions_incremental_completed',
|
|
64
|
+
where: "sync_type = 1 AND status = 2", # incremental=1, completed=2
|
|
65
|
+
order: { created_at: :desc }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
class CreateCmIntegrationMappings < ActiveRecord::Migration[7.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :cm_integration_mappings, if_not_exists: true do |t|
|
|
4
|
+
t.string :external_id, null: false
|
|
5
|
+
t.string :internal_type, null: false
|
|
6
|
+
t.bigint :internal_id, null: false
|
|
7
|
+
t.jsonb :external_payload, default: nil
|
|
8
|
+
|
|
9
|
+
# optional for record that can have different mappings on different dates
|
|
10
|
+
t.date :date, null: true
|
|
11
|
+
t.integer :status, null: false, default: 0
|
|
12
|
+
t.references :integration, null: false, foreign_key: { to_table: :cm_integrations }
|
|
13
|
+
|
|
14
|
+
t.jsonb :public_metadata, default: {}
|
|
15
|
+
t.jsonb :private_metadata, default: {}
|
|
16
|
+
|
|
17
|
+
t.datetime :last_synced_at
|
|
18
|
+
t.timestamps
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Index 1: Unique constraint for external_id mappings
|
|
22
|
+
# Used by: IntegrationMapping#validates :external_id
|
|
23
|
+
# Query: validates :external_id, uniqueness: { scope: %i[integration_id internal_type internal_id date] }
|
|
24
|
+
# Why: Ensures a single external_id maps to only one internal record per integration/date combination
|
|
25
|
+
# Prevents duplicate mappings that could cause sync conflicts
|
|
26
|
+
# Unique index enforces this at database level for data integrity
|
|
27
|
+
unless index_exists?(:cm_integration_mappings, [:integration_id, :external_id, :internal_type, :internal_id, :date], name: 'idx_external_internal_mapping')
|
|
28
|
+
add_index :cm_integration_mappings, [:integration_id, :external_id, :internal_type, :internal_id, :date], unique: true, name: 'idx_external_internal_mapping'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Index 2: Efficient lookup by integration_id and external_id
|
|
32
|
+
# Used by: IntegrationMappable#find_by_integration, IntegrationMappable#find_or_initialize_by_integration,
|
|
33
|
+
# IntegrationMappable#find_or_initialize_by_integration_with_mapping
|
|
34
|
+
#
|
|
35
|
+
# Query: .find_by(integration_id: integration_id, external_id: external_id)
|
|
36
|
+
# Why: Composite index allows efficient lookup by both integration_id and external_id
|
|
37
|
+
# These methods are called frequently during sync operations
|
|
38
|
+
unless index_exists?(:cm_integration_mappings, [:integration_id, :internal_type, :external_id], name: 'idx_integration_external_id')
|
|
39
|
+
add_index :cm_integration_mappings, [:integration_id, :internal_type, :external_id], name: 'idx_integration_external_id'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Index 3: Incremental sync query optimization
|
|
43
|
+
# Used by: IntegrationMappable#find_oldest_active_mapping (called by IncrementalSyncStrategy every 10 seconds)
|
|
44
|
+
# Query: .where(integration_id: integration_id, internal_type: model_name).active.order(:last_synced_at).first
|
|
45
|
+
# Why: Composite index filters by integration_id, internal_type, status efficiently
|
|
46
|
+
# Includes last_synced_at for ORDER BY to avoid expensive sort operation
|
|
47
|
+
# Runs every 10s so performance is critical - even small optimizations matter at this frequency
|
|
48
|
+
unless index_exists?(:cm_integration_mappings, [:integration_id, :internal_type, :status, :last_synced_at], name: 'idx_incremental_sync')
|
|
49
|
+
add_index :cm_integration_mappings, [:integration_id, :internal_type, :status, :last_synced_at], name: 'idx_incremental_sync'
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/cm_app_logger.rb
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
# lib/cm_app_logger.rb
|
|
2
2
|
module CmAppLogger
|
|
3
3
|
def self.log(label:, data: nil)
|
|
4
|
-
message = { label: label, data: data }
|
|
4
|
+
message = { label: label, data: safe_serialize(data) }
|
|
5
5
|
start_time = Time.current
|
|
6
6
|
Rails.logger.info(message.to_json)
|
|
7
7
|
|
|
8
8
|
return unless block_given?
|
|
9
9
|
|
|
10
|
-
# Capture the block
|
|
10
|
+
# Capture the block's return value and return it to preserve existing behavior for callers expecting that value.
|
|
11
11
|
block_result = yield
|
|
12
12
|
|
|
13
|
-
message[:start_time] = start_time
|
|
13
|
+
message[:start_time] = start_time.iso8601(3)
|
|
14
14
|
message[:duration_ms] = (Time.current - start_time) * 1000
|
|
15
|
+
message[:result] = safe_serialize(block_result)
|
|
15
16
|
Rails.logger.info(message.to_json)
|
|
16
17
|
block_result
|
|
17
18
|
end
|
|
@@ -19,9 +20,40 @@ module CmAppLogger
|
|
|
19
20
|
def self.error(label:, data: nil)
|
|
20
21
|
message = {
|
|
21
22
|
label: label,
|
|
22
|
-
data: data
|
|
23
|
+
data: safe_serialize(data)
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
Rails.logger.error(message.to_json)
|
|
26
27
|
end
|
|
28
|
+
|
|
29
|
+
# Safely serializes objects for JSON logging.
|
|
30
|
+
#
|
|
31
|
+
# @param obj [Object] The object to serialize
|
|
32
|
+
# @param depth [Integer] Internal parameter tracking recursion depth (max: 50)
|
|
33
|
+
# @return [Object] A JSON-safe representation of the input object
|
|
34
|
+
def self.safe_serialize(obj, depth: 0) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
35
|
+
return '[Max Depth Exceeded]' if depth > 50
|
|
36
|
+
|
|
37
|
+
if obj.is_a?(Hash)
|
|
38
|
+
obj.each_with_object({}) do |(k, v), memo|
|
|
39
|
+
memo[safe_serialize(k, depth: depth + 1)] = safe_serialize(v, depth: depth + 1)
|
|
40
|
+
end
|
|
41
|
+
elsif obj.is_a?(Array)
|
|
42
|
+
obj.map { |item| safe_serialize(item, depth: depth + 1) }
|
|
43
|
+
elsif obj.is_a?(Date)
|
|
44
|
+
obj.iso8601
|
|
45
|
+
elsif obj.is_a?(Time) || obj.is_a?(DateTime) || obj.is_a?(ActiveSupport::TimeWithZone)
|
|
46
|
+
obj.iso8601(3)
|
|
47
|
+
elsif obj.is_a?(ActiveJob::Base)
|
|
48
|
+
{
|
|
49
|
+
job_class: obj.class.name,
|
|
50
|
+
job_id: obj.job_id,
|
|
51
|
+
arguments: safe_serialize(obj.arguments, depth: depth + 1)
|
|
52
|
+
}
|
|
53
|
+
elsif obj.respond_to?(:id)
|
|
54
|
+
{ class: obj.class.name, id: obj.id }
|
|
55
|
+
else
|
|
56
|
+
obj.to_s
|
|
57
|
+
end
|
|
58
|
+
end
|
|
27
59
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
FactoryBot.define do
|
|
2
|
+
factory :cm_integration, class: SpreeCmCommissioner::Integration do
|
|
3
|
+
type { 'SpreeCmCommissioner::Integrations::StadiumXV1' }
|
|
4
|
+
name { FFaker::Company.name }
|
|
5
|
+
status { :active }
|
|
6
|
+
conflict_strategy { :newest_wins }
|
|
7
|
+
incremental_sync_interval_seconds { 300 }
|
|
8
|
+
full_sync_interval_hours { 24 }
|
|
9
|
+
association :vendor, factory: :cm_vendor
|
|
10
|
+
|
|
11
|
+
trait :active do
|
|
12
|
+
status { :active }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
trait :inactive do
|
|
16
|
+
status { :inactive }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
factory :cm_stadium_x_v1_integration, parent: :cm_integration, class: SpreeCmCommissioner::Integrations::StadiumXV1 do
|
|
21
|
+
public_key { FFaker::Lorem.characters(20) }
|
|
22
|
+
private_key { FFaker::Lorem.characters(40) }
|
|
23
|
+
base_url { 'https://api.stadiumx.com' }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
FactoryBot.define do
|
|
2
|
+
factory :cm_integration_mapping, class: SpreeCmCommissioner::IntegrationMapping do
|
|
3
|
+
external_id { SecureRandom.alphanumeric(10) }
|
|
4
|
+
association :integration, factory: :cm_integration
|
|
5
|
+
status { :active }
|
|
6
|
+
|
|
7
|
+
transient do
|
|
8
|
+
internal { create(:cm_product) }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
internal_id { internal.id }
|
|
12
|
+
internal_type { internal.class.name }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
FactoryBot.define do
|
|
2
2
|
factory :cm_location_vendor_place, class: SpreeCmCommissioner::VendorPlace do
|
|
3
|
-
association :vendor, factory: :
|
|
3
|
+
association :vendor, factory: :cm_vendor
|
|
4
4
|
association :place, factory: :cm_place
|
|
5
5
|
|
|
6
6
|
distance { FFaker::Number.decimal }
|
|
@@ -10,7 +10,7 @@ FactoryBot.define do
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
factory :cm_vendor_place, class: SpreeCmCommissioner::VendorPlace do
|
|
13
|
-
association :vendor, factory: :
|
|
13
|
+
association :vendor, factory: :cm_vendor
|
|
14
14
|
association :place, factory: :cm_place
|
|
15
15
|
association :location, factory: :cm_location_vendor_place
|
|
16
16
|
|
|
@@ -28,6 +28,7 @@ FactoryBot.define do
|
|
|
28
28
|
|
|
29
29
|
trait :location do
|
|
30
30
|
place_type { :location }
|
|
31
|
+
location { nil }
|
|
31
32
|
end
|
|
32
33
|
end
|
|
33
34
|
end
|
|
@@ -55,16 +55,17 @@ require 'byebug' if Rails.env.development? || Rails.env.test?
|
|
|
55
55
|
|
|
56
56
|
module SpreeCmCommissioner
|
|
57
57
|
class << self
|
|
58
|
-
# Allows overriding the default Redis connection
|
|
59
|
-
attr_writer :
|
|
58
|
+
# Allows overriding the default Redis connection pools with custom ones
|
|
59
|
+
attr_writer :inventory_redis_pool
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
# Inventory Redis pool for inventory management
|
|
62
|
+
def inventory_redis_pool
|
|
63
|
+
@inventory_redis_pool ||= default_redis_pool
|
|
63
64
|
end
|
|
64
65
|
|
|
65
|
-
# Resets
|
|
66
|
-
def
|
|
67
|
-
@
|
|
66
|
+
# Resets all Redis pools, useful for testing or reinitialization
|
|
67
|
+
def reset_redis_pools
|
|
68
|
+
@inventory_redis_pool = nil
|
|
68
69
|
end
|
|
69
70
|
|
|
70
71
|
private
|