pact_broker 2.107.1 → 2.108.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -0
  3. data/Gemfile +5 -4
  4. data/README.md +1 -0
  5. data/db/migrations/20230615_add_integrations_contract_data_updated_at.rb +13 -0
  6. data/db/migrations/20230616_set_integrations_contract_data_updated_at.rb +11 -0
  7. data/db/migrations/20231002_add_version_id_index_to_released_version.rb +21 -0
  8. data/db/migrations/20231003_add_version_id_index_to_deployed_version.rb +21 -0
  9. data/docs/CONFIGURATION.md +10 -0
  10. data/lib/pact_broker/api/contracts/base_contract.rb +2 -1
  11. data/lib/pact_broker/api/contracts/dry_validation_errors_formatter.rb +2 -0
  12. data/lib/pact_broker/api/contracts/pagination_query_params_schema.rb +19 -0
  13. data/lib/pact_broker/api/contracts/publish_contracts_contract_contract.rb +29 -18
  14. data/lib/pact_broker/api/decorators/base_decorator.rb +40 -1
  15. data/lib/pact_broker/api/decorators/branch_decorator.rb +35 -0
  16. data/lib/pact_broker/api/decorators/branch_version_decorator.rb +16 -0
  17. data/lib/pact_broker/api/decorators/configuration.rb +19 -0
  18. data/lib/pact_broker/api/decorators/custom_error_problem_json_decorator.rb +1 -1
  19. data/lib/pact_broker/api/decorators/decorator_context_creator.rb +47 -2
  20. data/lib/pact_broker/api/decorators/deployed_version_decorator.rb +2 -1
  21. data/lib/pact_broker/api/decorators/deployed_versions_decorator.rb +2 -2
  22. data/lib/pact_broker/api/decorators/dry_validation_errors_decorator.rb +32 -0
  23. data/lib/pact_broker/api/decorators/dry_validation_errors_problem_json_decorator.rb +24 -0
  24. data/lib/pact_broker/api/decorators/embedded_branch_version_decorator.rb +2 -2
  25. data/lib/pact_broker/api/decorators/embedded_deployed_version_decorator.rb +30 -0
  26. data/lib/pact_broker/api/decorators/embedded_error_problem_json_decorator.rb +84 -0
  27. data/lib/pact_broker/api/decorators/embedded_released_version_decorator.rb +27 -0
  28. data/lib/pact_broker/api/decorators/embedded_version_decorator.rb +0 -3
  29. data/lib/pact_broker/api/decorators/error_decorator.rb +30 -0
  30. data/lib/pact_broker/api/decorators/extended_pact_decorator.rb +6 -1
  31. data/lib/pact_broker/api/decorators/integration_decorator.rb +3 -2
  32. data/lib/pact_broker/api/decorators/integrations_decorator.rb +3 -0
  33. data/lib/pact_broker/api/decorators/notices_decorator.rb +11 -0
  34. data/lib/pact_broker/api/decorators/pact_pacticipant_decorator.rb +1 -5
  35. data/lib/pact_broker/api/decorators/pact_versions_decorator.rb +0 -1
  36. data/lib/pact_broker/api/decorators/pact_webhooks_status_decorator.rb +0 -1
  37. data/lib/pact_broker/api/decorators/pacticipant_branches_decorator.rb +32 -0
  38. data/lib/pact_broker/api/decorators/pacticipant_decorator.rb +11 -1
  39. data/lib/pact_broker/api/decorators/{pacticipant_collection_decorator.rb → pacticipants_decorator.rb} +8 -4
  40. data/lib/pact_broker/api/decorators/pagination_links.rb +2 -2
  41. data/lib/pact_broker/api/decorators/released_versions_decorator.rb +2 -2
  42. data/lib/pact_broker/api/decorators/runtime_error_problem_json_decorator.rb +2 -2
  43. data/lib/pact_broker/api/decorators/validation_errors_decorator.rb +30 -0
  44. data/lib/pact_broker/api/decorators/validation_errors_problem_json_decorator.rb +4 -6
  45. data/lib/pact_broker/api/decorators/version_decorator.rb +5 -3
  46. data/lib/pact_broker/api/decorators/versions_decorator.rb +24 -14
  47. data/lib/pact_broker/api/decorators/webhook_execution_result_decorator.rb +2 -0
  48. data/lib/pact_broker/api/middleware/configuration.rb +2 -0
  49. data/lib/pact_broker/api/pact_broker_urls.rb +18 -2
  50. data/lib/pact_broker/api/resources/after_reply.rb +15 -0
  51. data/lib/pact_broker/api/resources/all_webhooks.rb +3 -3
  52. data/lib/pact_broker/api/resources/badge_methods.rb +2 -1
  53. data/lib/pact_broker/api/resources/base_resource.rb +6 -4
  54. data/lib/pact_broker/api/resources/branch.rb +40 -0
  55. data/lib/pact_broker/api/resources/branch_versions.rb +59 -0
  56. data/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_branch_to_environment_badge.rb +1 -1
  57. data/lib/pact_broker/api/resources/currently_deployed_versions_for_environment.rb +1 -1
  58. data/lib/pact_broker/api/resources/currently_supported_versions_for_environment.rb +1 -1
  59. data/lib/pact_broker/api/resources/dashboard.rb +10 -0
  60. data/lib/pact_broker/api/resources/deployed_versions_for_version_and_environment.rb +1 -1
  61. data/lib/pact_broker/api/resources/environments.rb +1 -1
  62. data/lib/pact_broker/api/resources/error_handler.rb +18 -52
  63. data/lib/pact_broker/api/resources/error_handling_methods.rb +40 -14
  64. data/lib/pact_broker/api/resources/error_response_generator.rb +23 -6
  65. data/lib/pact_broker/api/resources/event_methods.rb +15 -0
  66. data/lib/pact_broker/api/resources/filter_methods.rb +15 -0
  67. data/lib/pact_broker/api/resources/group.rb +11 -2
  68. data/lib/pact_broker/api/resources/index.rb +6 -0
  69. data/lib/pact_broker/api/resources/integrations.rb +18 -4
  70. data/lib/pact_broker/api/resources/latest_version.rb +2 -0
  71. data/lib/pact_broker/api/resources/pact.rb +11 -6
  72. data/lib/pact_broker/api/resources/pacticipant_branches.rb +67 -0
  73. data/lib/pact_broker/api/resources/pacticipants.rb +26 -7
  74. data/lib/pact_broker/api/resources/pacticipants_for_label.rb +2 -2
  75. data/lib/pact_broker/api/resources/pagination_methods.rb +11 -1
  76. data/lib/pact_broker/api/resources/verifications.rb +9 -4
  77. data/lib/pact_broker/api/resources/versions.rb +12 -0
  78. data/lib/pact_broker/api.rb +5 -0
  79. data/lib/pact_broker/app.rb +10 -4
  80. data/lib/pact_broker/application_context.rb +29 -25
  81. data/lib/pact_broker/async/after_reply.rb +30 -0
  82. data/lib/pact_broker/config/runtime_configuration.rb +9 -21
  83. data/lib/pact_broker/config/runtime_configuration_coercion_methods.rb +32 -2
  84. data/lib/pact_broker/config/runtime_configuration_database_methods.rb +1 -1
  85. data/lib/pact_broker/config/runtime_configuration_logging_methods.rb +15 -5
  86. data/lib/pact_broker/configuration.rb +29 -12
  87. data/lib/pact_broker/contracts/contracts_to_publish.rb +8 -0
  88. data/lib/pact_broker/contracts/service.rb +7 -1
  89. data/lib/pact_broker/dataset/page.rb +22 -0
  90. data/lib/pact_broker/dataset.rb +122 -0
  91. data/lib/pact_broker/db/data_migrations/set_contract_data_updated_at_for_integrations.rb +47 -0
  92. data/lib/pact_broker/db/migrate_data.rb +1 -0
  93. data/lib/pact_broker/db/models.rb +1 -1
  94. data/lib/pact_broker/deployments/currently_deployed_version_id.rb +2 -5
  95. data/lib/pact_broker/deployments/deployed_version.rb +3 -3
  96. data/lib/pact_broker/deployments/environment.rb +0 -2
  97. data/lib/pact_broker/deployments/released_version.rb +4 -5
  98. data/lib/pact_broker/doc/views/index/pacticipant-branch.markdown +25 -0
  99. data/lib/pact_broker/domain/label.rb +6 -3
  100. data/lib/pact_broker/domain/pact.rb +10 -5
  101. data/lib/pact_broker/domain/pacticipant.rb +4 -34
  102. data/lib/pact_broker/domain/tag.rb +4 -5
  103. data/lib/pact_broker/domain/verification.rb +2 -4
  104. data/lib/pact_broker/domain/version.rb +12 -19
  105. data/lib/pact_broker/errors/error_reporter.rb +30 -0
  106. data/lib/pact_broker/errors.rb +2 -15
  107. data/lib/pact_broker/events/subscriber.rb +12 -4
  108. data/lib/pact_broker/feature_toggle.rb +1 -1
  109. data/lib/pact_broker/groups/service.rb +38 -5
  110. data/lib/pact_broker/index/service.rb +1 -2
  111. data/lib/pact_broker/integrations/event_listener.rb +23 -0
  112. data/lib/pact_broker/integrations/integration.rb +24 -2
  113. data/lib/pact_broker/integrations/repository.rb +34 -1
  114. data/lib/pact_broker/integrations/service.rb +17 -18
  115. data/lib/pact_broker/labels/repository.rb +4 -8
  116. data/lib/pact_broker/locale/en.yml +5 -0
  117. data/lib/pact_broker/matrix/every_row.rb +58 -40
  118. data/lib/pact_broker/matrix/integration_row.rb +95 -0
  119. data/lib/pact_broker/matrix/integrations_repository.rb +133 -0
  120. data/lib/pact_broker/matrix/matrix_row.rb +88 -0
  121. data/lib/pact_broker/matrix/matrix_row_dataset_module.rb +185 -0
  122. data/lib/pact_broker/matrix/matrix_row_instance_methods.rb +150 -0
  123. data/lib/pact_broker/matrix/matrix_row_verification_dataset_module.rb +83 -0
  124. data/lib/pact_broker/matrix/parse_query.rb +1 -0
  125. data/lib/pact_broker/matrix/repository.rb +62 -285
  126. data/lib/pact_broker/matrix/resolved_selector.rb +13 -4
  127. data/lib/pact_broker/matrix/resolved_selector_builder.rb +84 -0
  128. data/lib/pact_broker/matrix/resolved_selectors_builder.rb +39 -0
  129. data/lib/pact_broker/matrix/row_ignorer.rb +36 -0
  130. data/lib/pact_broker/matrix/selector_ignorer.rb +59 -0
  131. data/lib/pact_broker/matrix/selector_resolver.rb +130 -0
  132. data/lib/pact_broker/metrics/service.rb +4 -9
  133. data/lib/pact_broker/pacticipants/latest_version_for_pacticipant_eager_loader.rb +33 -0
  134. data/lib/pact_broker/pacticipants/repository.rb +7 -9
  135. data/lib/pact_broker/pacticipants/service.rb +2 -2
  136. data/lib/pact_broker/pacts/latest_pact_publication_id_for_consumer_version.rb +2 -4
  137. data/lib/pact_broker/pacts/metadata.rb +3 -1
  138. data/lib/pact_broker/pacts/pact_publication.rb +23 -5
  139. data/lib/pact_broker/pacts/pact_publication_dataset_module.rb +5 -1
  140. data/lib/pact_broker/pacts/pact_version.rb +2 -3
  141. data/lib/pact_broker/pacts/pacts_for_verification_repository.rb +2 -5
  142. data/lib/pact_broker/pacts/placeholder_pact.rb +3 -1
  143. data/lib/pact_broker/pacts/repository.rb +12 -12
  144. data/lib/pact_broker/pacts/service.rb +1 -1
  145. data/lib/pact_broker/repositories.rb +9 -1
  146. data/lib/pact_broker/string_refinements.rb +4 -0
  147. data/lib/pact_broker/tags/head_pact_tags.rb +2 -5
  148. data/lib/pact_broker/tags/repository.rb +3 -7
  149. data/lib/pact_broker/test/test_data_builder.rb +50 -1
  150. data/lib/pact_broker/ui/controllers/groups.rb +2 -1
  151. data/lib/pact_broker/ui/view_models/matrix_line.rb +4 -4
  152. data/lib/pact_broker/ui/views/groups/show.html.erb +2 -1
  153. data/lib/pact_broker/verifications/latest_verification_for_consumer_and_provider.rb +0 -3
  154. data/lib/pact_broker/verifications/latest_verification_id_for_pact_version_and_provider_version.rb +2 -4
  155. data/lib/pact_broker/verifications/repository.rb +2 -5
  156. data/lib/pact_broker/verifications/sequence.rb +1 -4
  157. data/lib/pact_broker/verifications/service.rb +4 -0
  158. data/lib/pact_broker/version.rb +1 -1
  159. data/lib/pact_broker/versions/branch.rb +2 -5
  160. data/lib/pact_broker/versions/branch_head.rb +0 -3
  161. data/lib/pact_broker/versions/branch_repository.rb +76 -0
  162. data/lib/pact_broker/versions/branch_service.rb +13 -25
  163. data/lib/pact_broker/versions/branch_version.rb +0 -3
  164. data/lib/pact_broker/versions/branch_version_repository.rb +17 -0
  165. data/lib/pact_broker/versions/repository.rb +19 -2
  166. data/lib/pact_broker/versions/sequence.rb +1 -3
  167. data/lib/pact_broker/versions/service.rb +4 -0
  168. data/lib/pact_broker/webhooks/execution.rb +3 -7
  169. data/lib/pact_broker/webhooks/job.rb +16 -7
  170. data/lib/pact_broker/webhooks/pact_and_verification_parameters.rb +2 -2
  171. data/lib/pact_broker/webhooks/repository.rb +0 -1
  172. data/lib/pact_broker/webhooks/trigger_service.rb +11 -12
  173. data/lib/pact_broker/webhooks/triggered_webhook.rb +3 -4
  174. data/lib/pact_broker/webhooks/webhook.rb +2 -2
  175. data/lib/pact_broker/webhooks/webhook_event.rb +2 -5
  176. data/lib/rack/pact_broker/add_cache_header.rb +14 -0
  177. data/lib/rack/pact_broker/application_context.rb +16 -0
  178. data/lib/rack/pact_broker/configurable_make_it_later.rb +1 -1
  179. data/lib/rack/pact_broker/invalid_uri_protection.rb +19 -3
  180. data/lib/webmachine/describe_routes.rb +55 -39
  181. data/lib/webmachine/render_error_monkey_patch.rb +13 -4
  182. data/pact_broker.gemspec +4 -4
  183. metadata +52 -29
  184. data/lib/pact_broker/api/decorators/decorator_context.rb +0 -22
  185. data/lib/pact_broker/matrix/query_builder.rb +0 -90
  186. data/lib/pact_broker/matrix/query_ids.rb +0 -40
  187. data/lib/pact_broker/matrix/quick_row.rb +0 -458
  188. data/lib/pact_broker/relationships/groupify.rb +0 -45
  189. data/lib/pact_broker/repositories/helpers.rb +0 -96
  190. data/lib/pact_broker/repositories/page.rb +0 -24
  191. data/lib/pact_broker/tags/tag_with_latest_flag.rb +0 -28
@@ -0,0 +1,83 @@
1
+ #
2
+ # The dataset methods to be included in the MatrixRow::Verification dataset module
3
+ # and the EveryRow::Verification dataset module.
4
+ # Expects the method `select_verification_columns_with_aliases` to be defined on the class
5
+ #
6
+ module PactBroker
7
+ module Matrix
8
+ module MatrixRowVerificationDatasetModule
9
+ # Verifications for the provider versions matching the given selectors, where the consumers match the pacticipants in the given selectors.
10
+ # @public
11
+ # @param [Array<PactBroker::Matrix::ResolvedSelector>] selectors
12
+ # @return [Sequel::Dataset<MatrixRow>]
13
+ def matching_only_selectors_as_provider(resolved_selectors)
14
+ [
15
+ matching_only_selectors_as_provider_where_only_pacticipant_name_in_selector(resolved_selectors),
16
+ matching_only_selectors_as_provider_where_not_only_pacticipant_name_in_selector(resolved_selectors)
17
+ ].compact.reduce(&:union)
18
+ end
19
+
20
+ # @public
21
+ # @return [Sequel::Dataset<Verification>, nil]
22
+ def matching_selectors_as_provider_for_any_consumer(resolved_selectors)
23
+ select_verification_columns_with_aliases
24
+ .inner_join_versions_for_selectors_as_provider(resolved_selectors)
25
+ end
26
+
27
+ # Return verifications where the provider is described by any of the resolved_selectors *that only specify the pacticipant NAME*,
28
+ # AND the consumer is described by any of the resolved selectors.
29
+ # If the original selector only specified the pacticipant name, we don't need to join to the versions table to identify the required verifications.
30
+ # Return nil if there are no resolved selectors where only the pacticipant name is specified.
31
+ # @private
32
+ # @param [Array<PactBroker::Matrix::ResolvedSelector>] resolved_selectors
33
+ # @return [Sequel::Dataset<Verification>, nil]
34
+ def matching_only_selectors_as_provider_where_only_pacticipant_name_in_selector(resolved_selectors)
35
+ all_pacticipant_ids = resolved_selectors.collect(&:pacticipant_id).uniq
36
+ pacticipant_ids_for_pacticipant_only_selectors = resolved_selectors.select(&:only_pacticipant_name_specified?).collect(&:pacticipant_id).uniq
37
+
38
+ if pacticipant_ids_for_pacticipant_only_selectors.any?
39
+ select_verification_columns_with_aliases
40
+ .where(provider_id: pacticipant_ids_for_pacticipant_only_selectors)
41
+ .where(consumer_id: all_pacticipant_ids)
42
+ end
43
+ end
44
+
45
+ # Return verifications where the provider *version* is described by any of the resolved_selectors
46
+ # *that specify more than just the pacticipant name*,
47
+ # AND the consumer is described by any of the resolved selectors.
48
+ # If the selector uses any of the tag/branch/environment/latest attributes, we need to join to the versions table to identify the required verifications.
49
+ # Return nil if there are no resolved selectors where anything other than the pacticipant name is specified.
50
+ # @private
51
+ # @param [Array<PactBroker::Matrix::ResolvedSelector>] resolved_selectors
52
+ # @return [Sequel::Dataset<Verification>, nil]
53
+ def matching_only_selectors_as_provider_where_not_only_pacticipant_name_in_selector(resolved_selectors)
54
+ # get the UnresolvedSelector objects back out of the resolved_selectors because the Version.for_selector() method uses the UnresolvedSelector
55
+ all_pacticipant_ids = resolved_selectors.collect(&:pacticipant_id).uniq
56
+ resolved_selectors_with_versions_specified = resolved_selectors.reject(&:only_pacticipant_name_specified?)
57
+
58
+ if resolved_selectors_with_versions_specified.any?
59
+ select_verification_columns_with_aliases
60
+ .inner_join_versions_for_selectors_as_provider(resolved_selectors_with_versions_specified)
61
+ .where(consumer_id: all_pacticipant_ids)
62
+ end
63
+ end
64
+
65
+ # Don't think it's worth splitting this into 2 different queries for selectors with only pacticipant name/selectors with version properties,
66
+ # as it's unlikely for there ever to be a query through the UI or CLI that results in 1 selector which only has a pacticipant name in it.
67
+ # @private
68
+ # @param [Array<PactBroker::Matrix::ResolvedSelector>] resolved_selectors
69
+ # @return [Sequel::Dataset<Verification>]
70
+ def inner_join_versions_for_selectors_as_provider(resolved_selectors)
71
+ # get the UnresolvedSelector objects back out of the resolved_selectors because the Version.for_selector() method uses the UnresolvedSelector
72
+ unresolved_selectors = resolved_selectors.collect(&:original_selector).uniq
73
+ versions = PactBroker::Domain::Version.ids_for_selectors(unresolved_selectors)
74
+ join_versions_dataset(versions)
75
+ end
76
+
77
+ # @private
78
+ def join_versions_dataset(versions_dataset)
79
+ join(versions_dataset, { Sequel[self.model.table_name][:provider_version_id] => Sequel[:versions][:id] }, table_alias: :versions)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -26,6 +26,7 @@ module PactBroker
26
26
  options[:latestby] = params["latestby"]
27
27
  end
28
28
 
29
+ # Don't think this is used anywhere...
29
30
  if params.key?("days") && params["days"] != ""
30
31
  options[:days] = params["days"].to_i
31
32
  end
@@ -1,71 +1,50 @@
1
- require "pact_broker/logging"
2
- require "pact_broker/repositories/helpers"
3
- require "pact_broker/matrix/quick_row"
1
+ require "pact_broker/matrix/matrix_row"
4
2
  require "pact_broker/matrix/every_row"
5
- require "pact_broker/error"
6
3
  require "pact_broker/matrix/query_results"
7
4
  require "pact_broker/matrix/integration"
8
5
  require "pact_broker/matrix/query_results_with_deployment_status_summary"
9
- require "pact_broker/matrix/resolved_selector"
10
6
  require "pact_broker/matrix/unresolved_selector"
11
7
  require "pact_broker/verifications/latest_verification_id_for_pact_version_and_provider_version"
8
+ require "pact_broker/matrix/integrations_repository"
9
+ require "pact_broker/matrix/resolved_selectors_builder"
10
+ require "pact_broker/matrix/row_ignorer"
11
+ require "pact_broker/matrix/integrations_repository"
12
12
 
13
13
  module PactBroker
14
14
  module Matrix
15
-
16
- class Error < PactBroker::Error; end
17
-
18
15
  class Repository
19
- include PactBroker::Repositories::Helpers
20
16
  include PactBroker::Repositories
21
- include PactBroker::Logging
22
-
23
- # TODO move latest verification logic in to database
24
17
 
25
18
  # Used when using table_print to output query results
26
19
  TP_COLS = [ :consumer_version_number, :pact_revision_number, :provider_version_number, :verification_number]
27
20
 
28
- GROUP_BY_PROVIDER_VERSION_NUMBER = [:consumer_name, :consumer_version_number, :provider_name, :provider_version_number]
29
- GROUP_BY_PROVIDER = [:consumer_name, :consumer_version_number, :provider_name]
30
- GROUP_BY_PACT = [:consumer_name, :provider_name]
31
-
32
- # rubocop: disable Metrics/CyclomaticComplexity
33
- def find_ids_for_pacticipant_names params
34
- criteria = {}
35
-
36
- if params[:consumer_name] || params[:provider_name]
37
- if params[:consumer_name]
38
- pacticipant = PactBroker::Domain::Pacticipant.where(name_like(:name, params[:consumer_name])).single_record
39
- criteria[:consumer_id] = pacticipant.id if pacticipant
40
- end
41
-
42
- if params[:provider_name]
43
- pacticipant = PactBroker::Domain::Pacticipant.where(name_like(:name, params[:provider_name])).single_record
44
- criteria[:provider_id] = pacticipant.id if pacticipant
45
- end
46
- end
47
-
48
- if params[:pacticipant_name]
49
- pacticipant = PactBroker::Domain::Pacticipant.where(name_like(:name, params[:pacticipant_name])).single_record
50
- criteria[:pacticipant_id] = pacticipant.id if pacticipant
51
- end
52
-
53
- criteria[:tag_name] = params[:tag_name] if params[:tag_name].is_a?(String) # Could be a sym from resource parameters in api.rb
54
- criteria
55
- end
56
- # rubocop: enable Metrics/CyclomaticComplexity
57
-
58
- # Return the latest matrix row (pact/verification) for each consumer_version_number/provider_version_number
59
- def find specified_selectors, options = {}
60
- resolved_ignore_selectors = resolve_ignore_selectors(options)
61
- resolved_specified_selectors = resolve_versions_and_add_ids(specified_selectors, :specified, resolved_ignore_selectors)
62
- integrations = find_integrations_for_specified_selectors(resolved_specified_selectors, options)
63
- resolved_selectors = add_any_inferred_selectors(resolved_specified_selectors, resolved_ignore_selectors, integrations, options)
64
- considered_rows, ignored_rows = find_considered_and_ignored_rows(resolved_selectors, resolved_ignore_selectors, options)
65
- QueryResults.new(considered_rows.sort, ignored_rows.sort, specified_selectors, options, resolved_selectors, resolved_ignore_selectors, integrations)
66
- end
67
-
68
- def find_for_consumer_and_provider pacticipant_1_name, pacticipant_2_name
21
+ GROUP_BY_CONSUMER_VERSION_AND_PROVIDER_VERSION = [:consumer_name, :consumer_version_number, :provider_name, :provider_version_number]
22
+ GROUP_BY_CONSUMER_VERSION_AND_PROVIDER = [:consumer_name, :consumer_version_number, :provider_name]
23
+ GROUP_BY_CONSUMER_AND_PROVIDER = [:consumer_name, :provider_name]
24
+
25
+ # THE METHOD for querying the Matrix
26
+ # @param [Array<PactBroker::Matrix::UnresolvedSelector>] unresolved_specified_selectors
27
+ # @param [Hash] options
28
+ def find(unresolved_specified_selectors, options = {})
29
+ infer_selectors = infer_selectors_for_integrations?(options)
30
+ resolved_selectors_builder = ResolvedSelectorsBuilder.new
31
+ resolved_selectors_builder.resolve_selectors(unresolved_specified_selectors, options.fetch(:ignore_selectors, []))
32
+ integrations = matrix_integration_repository.find_integrations_for_specified_selectors(resolved_selectors_builder.specified_selectors, infer_selectors)
33
+ resolved_selectors_builder.resolve_inferred_selectors(integrations, options) if infer_selectors
34
+
35
+ considered_rows, ignored_rows = find_considered_and_ignored_rows(resolved_selectors_builder.all_selectors, resolved_selectors_builder.ignore_selectors, options)
36
+ QueryResults.new(
37
+ considered_rows.sort,
38
+ ignored_rows.sort,
39
+ unresolved_specified_selectors,
40
+ options,
41
+ resolved_selectors_builder.all_selectors,
42
+ resolved_selectors_builder.ignore_selectors,
43
+ integrations
44
+ )
45
+ end
46
+
47
+ def find_for_consumer_and_provider(pacticipant_1_name, pacticipant_2_name)
69
48
  selectors = [ UnresolvedSelector.new(pacticipant_name: pacticipant_1_name), UnresolvedSelector.new(pacticipant_name: pacticipant_2_name)]
70
49
  options = { latestby: "cvpv" }
71
50
  find(selectors, options)
@@ -73,88 +52,26 @@ module PactBroker
73
52
 
74
53
  private
75
54
 
76
- def find_considered_and_ignored_rows(resolved_selectors, resolved_ignore_selectors, options)
77
- rows = query_matrix(resolved_selectors, options)
78
- rows = apply_latestby(options, rows)
79
- rows = apply_success_filter(rows, options)
80
- considered_rows, ignored_rows = split_rows_into_considered_and_ignored(rows, resolved_ignore_selectors)
81
- return considered_rows, ignored_rows
55
+ def matrix_integration_repository
56
+ PactBroker::Matrix::IntegrationsRepository.new
82
57
  end
83
58
 
84
- def find_integrations_for_specified_selectors(resolved_specified_selectors, options)
85
- if infer_selectors_for_integrations?(options)
86
- find_integrations_for_specified_selectors_with_inferred_integrations(resolved_specified_selectors)
87
- else
88
- find_integrations_for_specified_selectors_only(resolved_specified_selectors)
89
- end
90
- end
91
-
92
- def find_integrations_for_specified_selectors_only(resolved_specified_selectors)
93
- specified_pacticipant_names = resolved_specified_selectors.collect(&:pacticipant_name)
94
- base_model_for_integrations
95
- .distinct_integrations(resolved_specified_selectors, false)
96
- .collect(&:to_hash)
97
- .collect do | integration_hash |
98
- required = is_a_row_for_this_integration_required?(specified_pacticipant_names, integration_hash[:consumer_name])
99
- Integration.from_hash(integration_hash.merge(required: required))
100
- end
101
- end
102
-
103
- def find_integrations_for_specified_selectors_with_inferred_integrations(resolved_specified_selectors)
104
- integrations = integrations_where_specified_selector_is_consumer(resolved_specified_selectors) +
105
- integrations_where_specified_selector_is_provider(resolved_specified_selectors)
106
- deduplicate_integrations(integrations)
107
- end
108
-
109
- def integrations_where_specified_selector_is_consumer(resolved_specified_selectors)
110
- resolved_specified_selectors.flat_map do | selector |
111
- base_model_for_integrations
112
- .distinct_integrations_for_selector_as_consumer(selector)
113
- .all
114
- .collect do | integration |
115
- Integration.from_hash(
116
- consumer_id: integration[:consumer_id],
117
- consumer_name: integration[:consumer_name],
118
- provider_id: integration[:provider_id],
119
- provider_name: integration[:provider_name],
120
- required: true # synchronous consumer requires the provider to be present
121
- )
122
- end
123
- end
124
- end
125
-
126
- # Why does the consumer equivalent of this method use the QuickRow distinct_integrations_for_selector_as_consumer
127
- # while this method uses the Integration?
128
- def integrations_where_specified_selector_is_provider(resolved_specified_selectors)
129
- integrations_involving_specified_providers = PactBroker::Integrations::Integration
130
- .where(provider_id: resolved_specified_selectors.collect(&:pacticipant_id))
131
- .eager(:consumer, :provider)
132
- .all
133
-
134
- integrations_involving_specified_providers.collect do | integration |
135
- Integration.from_hash(
136
- consumer_id: integration.consumer.id,
137
- consumer_name: integration.consumer.name,
138
- provider_id: integration.provider.id,
139
- provider_name: integration.provider.name,
140
- required: false # synchronous provider does not require the consumer to be present
141
- )
142
- end
143
- end
144
-
145
- def deduplicate_integrations(integrations)
146
- integrations
147
- .group_by{ | integration| [integration.consumer_id, integration.provider_id] }
148
- .values
149
- .collect { | duplicate_integrations | duplicate_integrations.find(&:required?) || duplicate_integrations.first }
59
+ # If the user has specified --to TAG or --to-environment ENVIRONMENT in the CLI
60
+ # (or nothing, which to defaults to latest=true - "with the latest version of the other integrated applications")
61
+ # we need to work out what the integrations are between the specified selectors and the other pacticipant versions
62
+ # in the target environment/branches/tags.
63
+ # @param [Hash] options the matrix options
64
+ # @return [Boolean]
65
+ def infer_selectors_for_integrations?(options)
66
+ options[:latest] || !!options[:tag] || !!options[:branch] || !!options[:environment_name] || options[:main_branch]
150
67
  end
151
68
 
152
- def add_any_inferred_selectors(resolved_specified_selectors, resolved_ignore_selectors, integrations, options)
153
- if infer_selectors_for_integrations?(options)
154
- resolved_specified_selectors + inferred_selectors(resolved_specified_selectors, resolved_ignore_selectors, integrations, options)
155
- else
156
- resolved_specified_selectors
157
- end
69
+ def find_considered_and_ignored_rows(all_resolved_selectors, resolved_ignore_selectors, options)
70
+ rows = query_matrix(all_resolved_selectors, options)
71
+ rows = apply_latestby(options, rows)
72
+ rows = apply_success_filter(rows, options)
73
+ considered_rows, ignored_rows = RowIgnorer.split_rows_into_considered_and_ignored(rows, resolved_ignore_selectors)
74
+ return considered_rows, ignored_rows
158
75
  end
159
76
 
160
77
  def apply_success_filter(rows_with_latest_by_applied, options)
@@ -167,36 +84,32 @@ module PactBroker
167
84
  end
168
85
  end
169
86
 
170
- # If a specified pacticipant is a consumer, then its provider is required to be deployed
171
- # to the same environment before the consumer can be deployed.
172
- # If a specified pacticipant is a provider only, then it may be deployed
173
- # without the consumer being present.
174
- def is_a_row_for_this_integration_required?(specified_pacticipant_names, consumer_name)
175
- specified_pacticipant_names.include?(consumer_name)
176
- end
177
-
178
87
  # rubocop: disable Metrics/CyclomaticComplexity
179
88
  # It would be nicer to do this in the SQL, but it requires time that I don't have at the moment
180
- def apply_latestby options, lines
89
+ def apply_latestby(options, lines)
181
90
  return lines unless options[:latestby]
182
91
  group_by_columns = case options[:latestby]
183
- when "cvpv" then GROUP_BY_PROVIDER_VERSION_NUMBER
184
- when "cvp" then GROUP_BY_PROVIDER
185
- when "cp" then GROUP_BY_PACT
92
+ when "cvpv" then GROUP_BY_CONSUMER_VERSION_AND_PROVIDER_VERSION
93
+ when "cvp" then GROUP_BY_CONSUMER_VERSION_AND_PROVIDER
94
+ when "cp" then GROUP_BY_CONSUMER_AND_PROVIDER
186
95
  end
187
96
 
188
97
  # The group with the nil provider_version_numbers will be the results of the left outer join
189
98
  # that don't have verifications, so we need to include them all.
190
- lines.group_by{|line| group_by_columns.collect{|key| line.send(key) }}
99
+
100
+ lines
101
+ .group_by{ |line| group_by_columns.collect{ |key| line.send(key) } }
191
102
  .values
192
- .collect{ | line | line.first.provider_version_number.nil? ? line : line.sort_by(&:provider_version_order).last }
103
+ .collect { | grouped_lines |
104
+ grouped_lines.first.provider_version_number.nil? ? grouped_lines.first : grouped_lines.sort_by(&:provider_version_order).last
105
+ }
193
106
  .flatten
194
107
  end
195
108
  # rubocop: enable Metrics/CyclomaticComplexity
196
109
 
197
- def query_matrix selectors, options
198
- query = base_model(options).select_all_columns
199
- .matching_selectors(selectors)
110
+ def query_matrix(all_resolved_selectors, options)
111
+ query = base_model(options)
112
+ .matching_selectors(all_resolved_selectors, limit: options[:limit])
200
113
  .order_by_last_action_date
201
114
 
202
115
  query = query.limit(options[:limit]) if options[:limit]
@@ -204,143 +117,7 @@ module PactBroker
204
117
  end
205
118
 
206
119
  def base_model(options = {})
207
- options[:latestby] ? QuickRow : EveryRow
208
- end
209
-
210
- def resolve_ignore_selectors(options)
211
- resolve_versions_and_add_ids(options.fetch(:ignore_selectors, []), :ignored)
212
- end
213
-
214
- # To make it easy for pf to override
215
- def base_model_for_integrations
216
- QuickRow
217
- end
218
-
219
- # Find the version number for selectors with the latest and/or tag specified
220
- def resolve_versions_and_add_ids(unresolved_selectors, selector_type, resolved_ignore_selectors = [])
221
- names = unresolved_selectors.collect(&:pacticipant_name)
222
- pacticipants = PactBroker::Domain::Pacticipant.where(name: names).to_a.group_by(&:name).transform_values(&:first)
223
- unresolved_selectors.collect do | unresolved_selector |
224
- pacticipant = pacticipants[unresolved_selector.pacticipant_name]
225
- if pacticipant
226
- versions = find_versions_for_selector(unresolved_selector)
227
- build_resolved_selectors(pacticipant, versions, unresolved_selector, selector_type, resolved_ignore_selectors)
228
- else
229
- selector_for_non_existing_pacticipant(unresolved_selector, selector_type)
230
- end
231
- end.flatten
232
- end
233
-
234
- def find_versions_for_selector(selector)
235
- # For selectors that just set the pacticipant name, there's no need to resolve the version -
236
- # only the pacticipant ID will be used in the query
237
- return nil if selector.all_for_pacticipant?
238
- versions = version_repository.find_versions_for_selector(selector)
239
-
240
- if selector.latest
241
- [versions.first]
242
- else
243
- versions.empty? ? [nil] : versions
244
- end
245
- end
246
-
247
- # When a single selector specifies multiple versions (eg. "all prod pacts"), this expands
248
- # the single selector into one selector for each version.
249
- def build_resolved_selectors(pacticipant, versions, unresolved_selector, selector_type, resolved_ignore_selectors)
250
- if versions
251
- one_of_many = versions.compact.size > 1
252
- versions.collect do | version |
253
- if version
254
- selector_for_found_version(pacticipant, version, unresolved_selector, selector_type, one_of_many, resolved_ignore_selectors)
255
- else
256
- selector_for_non_existing_version(pacticipant, unresolved_selector, selector_type, resolved_ignore_selectors)
257
- end
258
- end
259
- else
260
- selector_for_all_versions_of_a_pacticipant(pacticipant, unresolved_selector, selector_type, resolved_ignore_selectors)
261
- end
262
- end
263
-
264
- # The user has specified --to TAG or --to-environment ENVIRONMENT in the CLI
265
- # (or nothing, which to defaults to latest=true - "with the latest version of the other integrated applications")
266
- def infer_selectors_for_integrations?(options)
267
- options[:latest] || options[:tag] || options[:branch] || options[:environment_name] || options[:main_branch]
268
- end
269
-
270
- # When only one selector is specified, (eg. checking to see if Foo version 2 can be deployed to prod),
271
- # need to look up all integrated pacticipants, and determine their relevant (eg. latest prod) versions
272
- def inferred_selectors(resolved_specified_selectors, resolved_ignore_selectors, integrations, options)
273
- all_pacticipant_names = integrations.collect(&:pacticipant_names).flatten.uniq
274
- specified_names = resolved_specified_selectors.collect{ |s| s[:pacticipant_name] }
275
- inferred_pacticipant_names = all_pacticipant_names - specified_names
276
- build_inferred_selectors(inferred_pacticipant_names, resolved_ignore_selectors, options)
277
- end
278
-
279
- def build_inferred_selectors(inferred_pacticipant_names, resolved_ignore_selectors, options)
280
- selectors = inferred_pacticipant_names.collect do | pacticipant_name |
281
- selector = UnresolvedSelector.new(pacticipant_name: pacticipant_name)
282
- selector.tag = options[:tag] if options[:tag]
283
- selector.branch = options[:branch] if options[:branch]
284
- selector.main_branch = options[:main_branch] if options[:main_branch]
285
- selector.latest = options[:latest] if options[:latest]
286
- selector.environment_name = options[:environment_name] if options[:environment_name]
287
- selector
288
- end
289
- resolve_versions_and_add_ids(selectors, :inferred, resolved_ignore_selectors)
290
- end
291
-
292
- def selector_for_non_existing_version(pacticipant, unresolved_selector, selector_type, resolved_ignore_selectors)
293
- ignore = resolved_ignore_selectors.any? do | s |
294
- s.pacticipant_id == pacticipant.id && s.only_pacticipant_name_specified?
295
- end
296
- ResolvedSelector.for_pacticipant_and_non_existing_version(pacticipant, unresolved_selector, selector_type, ignore)
297
- end
298
-
299
- # rubocop: disable Metrics/ParameterLists
300
- def selector_for_found_version(pacticipant, version, unresolved_selector, selector_type, one_of_many, resolved_ignore_selectors)
301
- ignore = resolved_ignore_selectors.any? do | s |
302
- s.pacticipant_id == pacticipant.id && (s.only_pacticipant_name_specified? || s.pacticipant_version_id == version.id)
303
- end
304
- ResolvedSelector.for_pacticipant_and_version(pacticipant, version, unresolved_selector, selector_type, ignore, one_of_many)
305
- end
306
- # rubocop: enable Metrics/ParameterLists
307
-
308
- def selector_for_all_versions_of_a_pacticipant(pacticipant, unresolved_selector, selector_type, resolved_ignore_selectors)
309
- # Doesn't make sense to ignore this, as you can't have a can-i-deploy query
310
- # for "all versions of a pacticipant". But whatever.
311
- ignore = resolved_ignore_selectors.any? do | s |
312
- s.pacticipant_id == pacticipant.id && s.only_pacticipant_name_specified?
313
- end
314
- ResolvedSelector.for_pacticipant(pacticipant, unresolved_selector, selector_type, ignore)
315
- end
316
-
317
- # only relevant for ignore selectors, validation stops this happening for the normal
318
- # selectors
319
- def selector_for_non_existing_pacticipant(unresolved_selector, selector_type)
320
- ResolvedSelector.for_non_existing_pacticipant(unresolved_selector, selector_type, false)
321
- end
322
-
323
- def split_rows_into_considered_and_ignored(rows, resolved_ignore_selectors)
324
- if resolved_ignore_selectors.any?
325
- considered, ignored = [], []
326
- rows.each do | row |
327
- if ignore_row?(resolved_ignore_selectors, row)
328
- ignored << row
329
- else
330
- considered << row
331
- end
332
- end
333
- return considered, ignored
334
- else
335
- return rows, []
336
- end
337
- end
338
-
339
- def ignore_row?(resolved_ignore_selectors, row)
340
- resolved_ignore_selectors.any? do | s |
341
- s.pacticipant_id == row.consumer_id && (s.only_pacticipant_name_specified? || s.pacticipant_version_id == row.consumer_version_id) ||
342
- s.pacticipant_id == row.provider_id && (s.only_pacticipant_name_specified? || s.pacticipant_version_id == row.provider_version_id)
343
- end
120
+ options[:latestby] ? MatrixRow : EveryRow
344
121
  end
345
122
  end
346
123
  end
@@ -4,7 +4,9 @@ require "pact_broker/hash_refinements"
4
4
  # This is created from either specified or inferred data, based on the user's query
5
5
  # eg.
6
6
  # can-i-deploy --pacticipant Foo --version 1 (this is a specified selector)
7
- # --to prod (this is used to create inferred selectors)
7
+ # --to prod (this is used to create inferred selectors, one for each integrated pacticipant in that environment)
8
+ # When an UnresolvedSelector specifies multiple application versions (eg. { tag: "prod" }) then a ResolvedSelector
9
+ # is created for every Version object found for the original selector.
8
10
 
9
11
  module PactBroker
10
12
  module Matrix
@@ -31,6 +33,9 @@ module PactBroker
31
33
  )
32
34
  end
33
35
 
36
+ # This is not possible for specified selectors, as there is validation at the HTTP query level to
37
+ # ensure that all pacticipants in the specified selectors exist.
38
+ # It is possible for the ignore selectors however.
34
39
  def self.for_non_existing_pacticipant(original_selector, type, ignore)
35
40
  ResolvedSelector.new(
36
41
  pacticipant_id: NULL_PACTICIPANT_ID,
@@ -187,16 +192,20 @@ module PactBroker
187
192
  !ignore?
188
193
  end
189
194
 
195
+ def original_selector
196
+ self[:original_selector]
197
+ end
198
+
190
199
  # rubocop: disable Metrics/CyclomaticComplexity, Metrics/MethodLength
191
200
  def description
192
201
  if latest_tagged? && pacticipant_version_number
193
202
  "the latest version of #{pacticipant_name} with tag #{tag} (#{pacticipant_version_number})"
194
203
  elsif latest_tagged?
195
204
  "the latest version of #{pacticipant_name} with tag #{tag} (no such version exists)"
196
- elsif main_branch? && pacticipant_version_number.nil?
197
- "a version of #{pacticipant_name} from the main branch (no such version exists)"
198
205
  elsif latest_from_main_branch? && pacticipant_version_number.nil?
199
- "the latest version of #{pacticipant_name} from the main branch (no such verison exists)"
206
+ "the latest version of #{pacticipant_name} from the main branch (no versions exist for this branch)"
207
+ elsif main_branch? && pacticipant_version_number.nil?
208
+ "any version of #{pacticipant_name} from the main branch (no versions exist for this branch)"
200
209
  elsif latest_from_branch? && pacticipant_version_number
201
210
  "the latest version of #{pacticipant_name} from branch #{branch} (#{pacticipant_version_number})"
202
211
  elsif latest_from_branch?
@@ -0,0 +1,84 @@
1
+ # Builds a PactBroker::Matrix::UnresolvedSelector based on the given
2
+ # UnresolvedSelector, selector type, Pacticipant and Version objects,
3
+ # using the selector_ignorer to work out if the built ResolvedSelector
4
+ # should be marked as ignored or not.
5
+
6
+ module PactBroker
7
+ module Matrix
8
+ class ResolvedSelectorBuilder
9
+
10
+ attr_accessor :pacticipant, :versions
11
+
12
+ # @param [PactBroker::Matrix::UnresolvedSelector] unresolved_selector
13
+ # @param [Symbol] selector_type :specified or :inferred
14
+ # @param [PactBroker::Matrix::Ignorer] selector_ignorer
15
+ def initialize(unresolved_selector, selector_type, selector_ignorer)
16
+ @unresolved_selector = unresolved_selector
17
+ @selector_type = selector_type
18
+ @selector_ignorer = selector_ignorer
19
+ end
20
+
21
+ def build
22
+ if pacticipant && versions
23
+ build_resolved_selectors_for_versions(pacticipant, versions, unresolved_selector, selector_type)
24
+ elsif pacticipant
25
+ selector_for_all_versions_of_a_pacticipant(pacticipant, unresolved_selector, selector_type)
26
+ else
27
+ build_selector_for_non_existing_pacticipant(unresolved_selector, selector_type)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :unresolved_selector, :selector_type, :selector_ignorer
34
+
35
+ # When a single selector specifies multiple versions (eg. "all prod pacts"), this expands
36
+ # the single selector into one selector for each version.
37
+ # When a pacticipant is found, but there are no versions matching the selector,
38
+ # the versions array will be have a single item which is nil (`[nil]`).
39
+ # See PactBroker::Matrix::SelectorResolver#find_versions_for_selector
40
+ # There may be a better way to pass in this information.
41
+ def build_resolved_selectors_for_versions(pacticipant, versions, unresolved_selector, selector_type)
42
+ one_of_many = versions.compact.size > 1
43
+ versions.collect do | version |
44
+ if version
45
+ selector_for_found_version(pacticipant, version, unresolved_selector, selector_type, one_of_many)
46
+ else
47
+ selector_for_non_existing_version(pacticipant, unresolved_selector, selector_type)
48
+ end
49
+ end
50
+ end
51
+
52
+ def selector_for_non_existing_version(pacticipant, unresolved_selector, selector_type)
53
+ ignore = selector_ignorer.ignore_pacticipant?(pacticipant)
54
+ ResolvedSelector.for_pacticipant_and_non_existing_version(pacticipant, unresolved_selector, selector_type, ignore)
55
+ end
56
+
57
+ def selector_for_found_version(pacticipant, version, unresolved_selector, selector_type, one_of_many)
58
+ ResolvedSelector.for_pacticipant_and_version(
59
+ pacticipant,
60
+ version,
61
+ unresolved_selector,
62
+ selector_type,
63
+ selector_ignorer.ignore_pacticipant_version?(pacticipant, version),
64
+ one_of_many
65
+ )
66
+ end
67
+
68
+ def selector_for_all_versions_of_a_pacticipant(pacticipant, unresolved_selector, selector_type)
69
+ ResolvedSelector.for_pacticipant(
70
+ pacticipant,
71
+ unresolved_selector,
72
+ selector_type,
73
+ selector_ignorer.ignore_pacticipant?(pacticipant)
74
+ )
75
+ end
76
+
77
+ # only relevant for ignore selectors, validation stops this happening for the normal
78
+ # selectors
79
+ def build_selector_for_non_existing_pacticipant(unresolved_selector, selector_type)
80
+ ResolvedSelector.for_non_existing_pacticipant(unresolved_selector, selector_type, false)
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,39 @@
1
+ require "pact_broker/matrix/selector_resolver"
2
+
3
+ # Builds the array of ResolvedSelector objects using the
4
+ # ignore selectors, the specified selectors, and the inferred integrations.
5
+
6
+ module PactBroker
7
+ module Matrix
8
+ class ResolvedSelectorsBuilder
9
+ attr_reader :ignore_selectors, :specified_selectors, :inferred_selectors
10
+
11
+ def initialize
12
+ @inferred_selectors = []
13
+ end
14
+
15
+ # @param [Array<PactBroker::Matrix::UnresolvedSelector>]
16
+ # @param [Hash] options
17
+ def resolve_selectors(unresolved_specified_selectors, unresolved_ignore_selectors)
18
+ # must do this first because we need the ignore selectors to resolve the specified selectors
19
+ @ignore_selectors = SelectorResolver.resolved_ignore_selectors(unresolved_ignore_selectors)
20
+ @specified_selectors = SelectorResolver.resolve_specified_selectors(unresolved_specified_selectors, ignore_selectors)
21
+ end
22
+
23
+ # Use the given Integrations to work out what the selectors are for the versions that the versions for the specified
24
+ # selectors should be deployed with.
25
+ # eg. For `can-i-deploy --pacticipant Foo --version adfjkwejr --to-environment prod`, work out the selectors for the integrated application
26
+ # versions in the prod environment.
27
+ # @param [Array<PactBroker::Matrix::Integration>] integrations
28
+ def resolve_inferred_selectors(integrations, options)
29
+ @inferred_selectors = SelectorResolver.resolve_inferred_selectors(specified_selectors, ignore_selectors, integrations, options)
30
+ end
31
+
32
+ # All the resolved selectors to be used in the matrix query, specified and inferred (if any)
33
+ # @return [Array<PactBroker::Matrix::ResolvedSelector>]
34
+ def all_selectors
35
+ specified_selectors + inferred_selectors
36
+ end
37
+ end
38
+ end
39
+ end