pact_broker 2.43.0 → 2.44.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -1
  3. data/DEVELOPER_DOCUMENTATION.md +10 -6
  4. data/README.md +1 -1
  5. data/lib/pact_broker/api.rb +2 -2
  6. data/lib/pact_broker/api/decorators/version_decorator.rb +8 -0
  7. data/lib/pact_broker/db/models.rb +1 -1
  8. data/lib/pact_broker/doc/views/matrix.markdown +92 -2
  9. data/lib/pact_broker/matrix/deployment_status_summary.rb +11 -2
  10. data/lib/pact_broker/matrix/every_row.rb +43 -0
  11. data/lib/pact_broker/matrix/query_builder.rb +5 -5
  12. data/lib/pact_broker/matrix/quick_row.rb +96 -28
  13. data/lib/pact_broker/matrix/reason.rb +3 -0
  14. data/lib/pact_broker/matrix/repository.rb +33 -35
  15. data/lib/pact_broker/pacticipants/repository.rb +9 -1
  16. data/lib/pact_broker/pacts/{latest_pact_publication_id_by_consumer_version.rb → latest_pact_publication_id_for_consumer_version.rb} +0 -0
  17. data/lib/pact_broker/pacts/repository.rb +1 -1
  18. data/lib/pact_broker/repositories/helpers.rb +12 -5
  19. data/lib/pact_broker/version.rb +1 -1
  20. data/pact_broker.gemspec +1 -1
  21. data/spec/lib/pact_broker/integrations/service_spec.rb +1 -0
  22. data/spec/lib/pact_broker/matrix/deployment_status_summary_spec.rb +38 -33
  23. data/spec/lib/pact_broker/matrix/every_row_spec.rb +136 -0
  24. data/spec/lib/pact_broker/matrix/integration_spec.rb +149 -30
  25. data/spec/lib/pact_broker/matrix/repository_dependency_spec.rb +3 -3
  26. data/spec/lib/pact_broker/matrix/repository_spec.rb +5 -4
  27. data/spec/lib/pact_broker/pacticipants/repository_spec.rb +37 -3
  28. data/spec/service_consumers/provider_states_for_pact_ruby.rb +12 -0
  29. data/spec/support/shared_examples_for_responses.rb +7 -0
  30. metadata +14 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 452acf4f3b4042c53c9e91a793f558f39ad6677e
4
- data.tar.gz: ffcc62ddc9991aed25c87cbdea9925e9471bc4f8
3
+ metadata.gz: 90700346c7cb45045249cf0006851d517a154879
4
+ data.tar.gz: 492ebdd3a03cf7d6f617cc820a1bbf0316031d66
5
5
  SHA512:
6
- metadata.gz: e843bb458d91149a64f50092fcc1d17e63ed8b57468c7c2293033947648233cc015a010d421f7386531e89e90eb7d9499bc8da177da81c5518ea1bb937305ef8
7
- data.tar.gz: c4df7ee48ef81fdb78beb668267ad13fcf848916b047c6f848a65bcc4979e7fac851617e93fba857b2a63bd77545d08a614e46257ede2e39e821d937ac3e79ef
6
+ metadata.gz: f8236c0d6c9ba4a6f81e776599047e24cea2ea6433454ba3072565e02eb1aca73bb72184fd9b01913a869bdb3ffbed12f06d791af3f853038f16dc981005512c
7
+ data.tar.gz: 29bdec1d5c5ed4be6d8751faa517012337f7e04f587d83f2d83455656104764f001bb8425706a590ae42ce32ac66c87fcc5112bad2083748e08e27af82d7f81b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,27 @@
1
+ <a name="v2.44.0"></a>
2
+ ### v2.44.0 (2020-01-22)
3
+
4
+
5
+ #### Features
6
+
7
+ * **matrix**
8
+ * optimise query to determine integrations, again ([44e78ad2](/../../commit/44e78ad2))
9
+ * add indexes to optimise queries ([cdc9aad7](/../../commit/cdc9aad7))
10
+ * optimise query to determine integrations ([9e874f3b](/../../commit/9e874f3b))
11
+
12
+ * add hal relation for creating a tag on the pacticipant version resource ([dca0ad4f](/../../commit/dca0ad4f))
13
+
14
+
15
+ #### Bug Fixes
16
+
17
+ * update pact-support to fix bug caused by missing require ([416ecdf5](/../../commit/416ecdf5))
18
+ * correct logic for finding pacticipants by name when the name contains an underscore ([6d975ebe](/../../commit/6d975ebe))
19
+ * correct logic for finding pacticipants by name when the name contains an underscore ([2db59797](/../../commit/2db59797))
20
+
21
+ * **matrix**
22
+ * correctly infer selectors when multiple selectors have been specified ([4288c282](/../../commit/4288c282))
23
+
24
+
1
25
  <a name="v2.43.0"></a>
2
26
  ### v2.43.0 (2020-01-06)
3
27
 
@@ -10,7 +34,6 @@
10
34
 
11
35
  * **matrix**
12
36
  * optimise the query that determines the integrations ([704944b6](/../../commit/704944b6))
13
- * attempt to optimise the query that determines the integrations ([afde01e1](/../../commit/afde01e1))
14
37
 
15
38
 
16
39
  #### Bug Fixes
@@ -7,7 +7,7 @@
7
7
  * API - [lib/pact_broker/api](lib/pact_broker/api)
8
8
  * Routes - [lib/pact_broker/api.rb](lib/pact_broker/api.rb)
9
9
  * HTTP Resources - [lib/pact_broker/api/resources](lib/pact_broker/api/resources) These handle the HTTP requests.
10
- * Decorators - [lib/pact_broker/api/decorators](lib/pact_broker/api/decorators) These render the response bodies.
10
+ * Decorators - [lib/pact_broker/api/decorators](lib/pact_broker/api/decorators) These parse the request bodies and render the response bodies.
11
11
  * Contracts - [lib/pact_broker/api/contracts](lib/pact_broker/api/contracts) These validate incoming API requests.
12
12
  * UI - [lib/pact_broker/ui](lib/pact_broker/ui)
13
13
  * Routes - [lib/pact_broker/ui/app.rb](lib/pact_broker/ui/app.rb)
@@ -35,10 +35,13 @@ Domain classes are found in `lib/pact_broker/domain`. Many of these classes are
35
35
 
36
36
  * `pacticipant` - an application that participates in a pact. A very bad pun which I deeply regret.
37
37
  * `pact` - this term is confusing and overloaded. It generally means a `pact publication` in the code.
38
- * `pact publication` - the resource that gets created when a PUT request is sent to the Pact Broker to /pacts/provider/PROVIDER/consumer/CONSUMER/version/VERSION.
38
+ * `pact publication` - the resource that gets created when a PUT request is sent to the Pact Broker to `/pacts/provider/PROVIDER/consumer/CONSUMER/version/VERSION`.
39
39
  * `pact version` - the JSON contents of the pact publication. One pact version may belong to many pact publications. That is, if a pact publication with exactly the same contents is published twice, then a new
40
- pact publication resource will be created, but it will reuse the existing pact version.
40
+ pact publication resource will be created with an incremented revision number, but it will reuse the existing pact version.
41
+ * `pacticipant version` - a resource that represents a version of the application
42
+ * `integration` - the relationship between a consumer and a provider
41
43
  * `pseudo branch` - A time ordered list of pacts that are related to a particular tag. The most recent pact for each pseudo branch is a "head" pact.
44
+ * `matrix` - the table that shows the cartesian join of pact versions/verifications, and hence shows which consumer versions and provider versions have been tested together.
42
45
 
43
46
  ### Tables
44
47
  * `pact_versions` - the JSON content of each UNIQUE pact document is stored in this table. The same content is likely to be published over and over again by the CI builds, so deduplicating the content saves us a lot of disk space. Once created, a row is never modified. Uniqueness is just done on string equality - no special pact logic. This means that pacts with randomly generated values or orders (most of pact-jvm pacts!) will get a new version record every time they publish.
@@ -80,11 +83,11 @@ pact publication resource will be created, but it will reuse the existing pact v
80
83
  * `consumer version number` and `consumer version order`
81
84
  * `revision_number`
82
85
 
83
- * `latest_pact_publications_by_consumer_versions` - This view has the same columns as `all_pact_publications`, but it only contains the latest revision of the pact for each provider/consumer/version. It maps to what a user would consider the "pact" resource ie. `/pacts/provider/Provider/consumer/Consumer/version/1.2.3`. Previous revisions are not currently exposed via the API.
86
+ * `latest_pact_publications_by_consumer_versions` - This view has the same columns as `all_pact_publications`, but it only contains the latest revision of the pact for each provider/consumer/version. It maps to what a user would consider the "pact" resource ie. `/pacts/provider/PROVIDER/consumer/CONSUMER/version/VERSION`. Previous revisions are not currently exposed via the API.
84
87
 
85
88
  The `AllPactPublications` Sequel model in the code is what is used when querying data for displaying in a response, rather than the normalised separate PactPublication and PactVersion models.
86
89
 
87
- * `latest_pact_publications` - This view has the same columns as `all_pact_publications`, but it only contains the latest revision of the pact for the latest consumer version for each consumer/provider pair. It is what a user would consider the "latest pact", and maps to the resource at `/pacts/provider/Provider/consumer/Consumer/latest`
90
+ * `latest_pact_publications` - This view has the same columns as `all_pact_publications`, but it only contains the latest revision of the pact for the latest consumer version for each consumer/provider pair. It is what a user would consider the "latest pact", and maps to the resource at `/pacts/provider/PROVIDER/consumer/CONSUMER/latest`
88
91
 
89
92
  * `latest_tagged_pact_publications` - This view has the same columns as `all_pact_publications`, plus a `tag_name` column. It is used to return the pact for the latest tagged version of a consumer.
90
93
 
@@ -116,8 +119,9 @@ pact publication resource will be created, but it will reuse the existing pact v
116
119
 
117
120
  ```
118
121
 
122
+ ### Database modeling approach
119
123
 
120
-
124
+ In the beginning, I made a lot of Sequel models based on views that pulled in the different tables of data together (eg denormalising consumer, provider, pact publication and pact version in to `all_pact_publications`). This made the Ruby code quite simple, but it was not very performant. As time has progressed, I have moved more and more of the "data joining" code into the Ruby to optimise the queries. That's why there are a lot of "aggregated data" views that are not being used by the code any more.
121
125
 
122
126
  ### Useful to know stuff
123
127
 
data/README.md CHANGED
@@ -166,7 +166,7 @@ Please read the [UPGRADING.md](UPGRADING.md) documentation before upgrading your
166
166
  [pact]: https://github.com/pact-foundation/pact-ruby
167
167
  [nerf]: https://github.com/pact-foundation/pact_broker/wiki/pact-broker-ci-nerf-gun
168
168
  [different-teams]: https://github.com/pact-foundation/pact-ruby/wiki/Using-pact-where-the-consumer-team-is-different-from-the-provider-team
169
- [docker]: https://hub.docker.com/r/dius/pact-broker
169
+ [docker]: https://github.com/pact-foundation/pact-broker-docker
170
170
  [terraform]: https://github.com/nadnerb/terraform-pact-broker
171
171
  [pactflow]: https:/pactflow.io/?utm_source=github&utm_campaign=pact_broker_usage
172
172
  [wiki]: https://github.com/pact-foundation/pact_broker/wiki
@@ -53,7 +53,7 @@ module PactBroker
53
53
  add ['pacts', 'provider', :provider_name, 'for-verification'], Api::Resources::ProviderPactsForVerification, {resource_name: "pacts_for_verification"}
54
54
 
55
55
  # Deprecated pact
56
- add ['pact', 'provider', :provider_name, 'consumer', :consumer_name, 'version', :consumer_version_number], Api::Resources::Pact, {resource_name: "pact_publications", deprecated: "true"} # Deprecate, singular /pact
56
+ add ['pact', 'provider', :provider_name, 'consumer', :consumer_name, 'version', :consumer_version_number], Api::Resources::Pact, {resource_name: "pact_publication", deprecated: "true"} # Deprecate, singular /pact
57
57
  add ['pact', 'provider', :provider_name, 'consumer', :consumer_name, 'latest'], Api::Resources::LatestPact, {resource_name: "latest_pact_publications", deprecated: "true"}
58
58
 
59
59
  # Pacticipants
@@ -88,7 +88,7 @@ module PactBroker
88
88
  add ['matrix', 'provider', :provider_name, 'consumer', :consumer_name], Api::Resources::MatrixForConsumerAndProvider, {resource_name: "matrix_consumer_provider"}
89
89
  add ['matrix', 'provider', :provider_name, 'latest', :provider_tag, 'consumer', :consumer_name, 'latest', :tag, 'badge'], Api::Resources::MatrixBadge, {resource_name: "matrix_tag_badge"}
90
90
  add ['matrix'], Api::Resources::Matrix, {resource_name: "matrix"}
91
- add ['can-i-deploy'], Api::Resources::CanIDeploy, {resource_name: "can-i-deploy"}
91
+ add ['can-i-deploy'], Api::Resources::CanIDeploy, {resource_name: "can_i_deploy"}
92
92
 
93
93
  add ['dashboard'], Api::Resources::Dashboard, {resource_name: "dashboard"}
94
94
  add ['dashboard', 'provider', :provider_name, 'consumer', :consumer_name ], Api::Resources::Dashboard, {resource_name: "integration_dashboard"}
@@ -26,6 +26,14 @@ module PactBroker
26
26
  }
27
27
  end
28
28
 
29
+ link :'pb:tag' do | options |
30
+ {
31
+ href: pacticipant_url(options.fetch(:base_url), represented.pacticipant) + '/tags/{tag}',
32
+ title: "Get, create or delete a tag for this pacticipant version",
33
+ templated: true
34
+ }
35
+ end
36
+
29
37
  link :'pb:latest-verification-results-where-pacticipant-is-consumer' do | options |
30
38
  {
31
39
  title: "Latest verification results for consumer version",
@@ -1,7 +1,7 @@
1
1
  require 'pact_broker/webhooks/execution'
2
2
  require 'pact_broker/webhooks/triggered_webhook'
3
3
  require 'pact_broker/webhooks/webhook'
4
- require 'pact_broker/pacts/latest_pact_publication_id_by_consumer_version'
4
+ require 'pact_broker/pacts/latest_pact_publication_id_for_consumer_version'
5
5
  require 'pact_broker/verifications/latest_verification_id_for_pact_version_and_provider_version'
6
6
  require 'pact_broker/pacts/pact_publication'
7
7
  require 'pact_broker/pacts/pact_version'
@@ -2,6 +2,96 @@
2
2
 
3
3
  Allowed methods: `GET`
4
4
 
5
- This resource returns the "cartesian join" of every pact publication and every verification results publication, and is used to determine whether or not an application is safe to deploy.
5
+ This resource returns the "cartesian join" of every pact publication and every verification results publication, and is used to determine whether or not a set of integrated application versions are safe to deploy together.
6
6
 
7
- If you need to use this API, consider calling the `/can-i-deploy` resource instead.
7
+ If you need to use this API, consider calling the `/can-i-deploy` resource instead, as it provides an interface that is easier to understand.
8
+
9
+
10
+ ## Matrix selectors and options
11
+
12
+ Selectors are the way we specify which pacticipants and versions we want to view the matrix for. The best way to understand them is to imagine that we start with a Matrix table that contains the pacts/verification results for every consumer and provider in the Pact Broker.
13
+
14
+ | Consumer | Consumer version | Provider | Provider version | Success |
15
+ |----------|------------------|----------|------------------|---------|
16
+ | Foo | 1 | Oink | 6 | true |
17
+ | Foo | 1 | Bar | 2 | true |
18
+ | Foo | 1 | Bar | 3 | false |
19
+
20
+ To specify that we wanted to see all the rows between Foo and Bar, our selectors would be:
21
+
22
+ `{"pacticipant": "Foo"}` and `{"pacticipant": "Bar"}`.
23
+
24
+ This would return:
25
+
26
+ | Consumer | Consumer version | Provider | Provider version | Success |
27
+ |----------|------------------|----------|------------------|---------|
28
+ | Foo | 1 | Bar | 2 | true |
29
+ | Foo | 1 | Bar | 3 | false |
30
+
31
+ To specify that we wanted to see the results for Foo v1 and Bar v3, our selectors would be:
32
+
33
+ `{"pacticipant": "Foo", "version": "1"}` and `{"pacticipant": "Bar", "version": "3"}`.
34
+
35
+ This would return:
36
+
37
+ | Consumer | Consumer version | Provider | Provider version | Success |
38
+ |----------|------------------|----------|------------------|---------|
39
+ | Foo | 1 | Bar | 3 | false |
40
+
41
+
42
+ Best "Pact Broker" practice specifies that once a pact is published for a particular consumer version, it should not be overwritten, however, there is nothing built in to the broker to stop multiple pacts being published with the same consumer version. If the provider verifies each revision, we would end up with a table that looks like this:
43
+
44
+ | Consumer | Consumer version | Provider | Provider version | Success |
45
+ |----------|------------------|----------|------------------|---------|
46
+ | Foo | 1 (revision 1) | Bar | 3 | false |
47
+ | Foo | 1 (revision 2) | Bar | 3 | true |
48
+ | Foo | 1 (revision 3) | Bar | 3 | false |
49
+
50
+ The overwritten revisions are not useful for determining whether or not we are safe to deploy, so to remove these lines from the dataset, we can specify the option `{latestby: "cvpv"}` to return the latest row when grouped and ordered by "consumer version and provider version".
51
+
52
+ Putting the selectors and the options together, to specify that we wanted to see the *latest* results for Foo v1 and Bar v3, our selectors would be:
53
+
54
+ `{"pacticipant": "Foo", "version": "1"}` and `{"pacticipant": "Bar", "version": "3"}` and our options would be `{"latestby": "cvpv"}`.
55
+
56
+ This would return:
57
+
58
+ | Consumer | Consumer version | Provider | Provider version | Success |
59
+ |----------|------------------|----------|------------------|---------|
60
+ | Foo | 1 (revision 3) | Bar | 3 | false |
61
+
62
+
63
+ Instead of specifying the version using the version number, you can also specify it by indicating the tag name.
64
+
65
+ | Consumer | Consumer version | Provider | Provider version | Success |
66
+ |----------|------------------|----------|------------------|---------|
67
+ | Foo | 1 (prod) | Bar | 2 | true |
68
+ | Foo | 1 (prod) | Bar | 3 (prod) | true |
69
+ | Foo | 2 | Bar | 4 (prod) | true |
70
+ | Foo | 2 | Bar | 5 | true |
71
+
72
+ Version 1 of Foo has been tagged prod, while versions 3 and 4 of Bar have been tagged prod.
73
+
74
+ To determine if Foo v2 can be deployed with the latest prod version of Bar, our selectors would be:
75
+
76
+ `{"pacticipant": "Foo", "version": "2"}` and `{"pacticipant": "Bar", tag: "prod", latest: true}` and our options would be `{"latestby": "cvpv"}`.
77
+
78
+ Using the dataset above, this query would return:
79
+
80
+ | Consumer | Consumer version | Provider | Provider version | Success |
81
+ |----------|------------------|----------|------------------|---------|
82
+ | Foo | 2 | Bar | 4 (prod) | true |
83
+
84
+
85
+ Imagine that Foo added another provider (and may add more in the future). It would be brittle to specify each of its integration partners by name. The Pact Broker already knows which applications Foo integrates with, so let's allow it to work out the dependencies by itself.
86
+
87
+ To determine if Foo v2 can be deployed with the latest prod versions of all its integration partners, our selectors would be:
88
+
89
+ `{"pacticipant": "Foo", "version": "2"}` and our options would be `{ "tag": "prod", latest: true, latestby: "cvp"}`. (Note the change in `latestby` from "cvpv" to "cvp". The reasons why this is the case are beyond the scope of this document.)
90
+
91
+
92
+ If Foo was a mobile client, and Bar was its provider, we might want to know if a particular version of Bar was compatible with _all_ the production versions of Foo. To do this, we drop the `"latest": true` from Foo's selector, like so: `{"pacticipant": "Foo", "tag": "prod"}` and `{"pacticipant": "Bar", version: "3"}`. Using the dataset from above, this query would return the following rows:
93
+
94
+ | Consumer | Consumer version | Provider | Provider version | Success |
95
+ |----------|------------------|----------|------------------|---------|
96
+ | Foo | 1 (prod) | Bar | 2 | true |
97
+ | Foo | 1 (prod) | Bar | 3 (prod) | true |
@@ -99,12 +99,21 @@ module PactBroker
99
99
  # the Foo -> Bar integration, and therefore cannot deploy Foo.
100
100
  # However, if we were to try and deploy the provider, Bar, that would be ok
101
101
  # as Bar does not rely on Foo, so this method would not return that integration.
102
+ # UPDATE:
103
+ # The matrix query now returns a row with blank provider version/verification fields
104
+ # so the above comment is now redundant.
105
+ # I'm not sure if this piece of code can ever return a list with anything in it any more.
106
+ # Will log it for a while and see.
102
107
  def required_integrations_without_a_row
103
108
  @required_integrations_without_a_row ||= begin
104
109
  integrations.select(&:required?).select do | integration |
105
110
  !row_exists_for_integration(integration)
106
111
  end
107
- end
112
+ end.tap { |it| log_required_integrations_without_a_row_occurred(it) if it.any? }
113
+ end
114
+
115
+ def log_required_integrations_without_a_row_occurred integrations
116
+ logger.info("required_integrations_without_a_row returned non empty", payload: { integrations: integrations, rows: rows })
108
117
  end
109
118
 
110
119
  def row_exists_for_integration(integration)
@@ -148,7 +157,7 @@ module PactBroker
148
157
  [selector_for(row.consumer_name), selector_for(row.provider_name)]
149
158
  end
150
159
 
151
- # When the user has not specified a version of the provider (eg no 'latest' and/or 'tag')
160
+ # When the user has not specified a version of the provider (eg no 'latest' and/or 'tag', which means 'all versions')
152
161
  # so the "add inferred selectors" code in the Matrix::Repository has not run,
153
162
  # we may end up with rows for which we do not have a selector.
154
163
  # To solve this, create dummy selectors from the row and integration data.
@@ -0,0 +1,43 @@
1
+ require 'pact_broker/matrix/quick_row'
2
+
3
+ module PactBroker
4
+ module Matrix
5
+ class EveryRow < PactBroker::Matrix::QuickRow
6
+ set_dataset(Sequel.as(:pact_publications, :p))
7
+
8
+ P_V_JOIN = { Sequel[:p][:pact_version_id] => Sequel[:v][:pact_version_id] }
9
+
10
+ PACT_COLUMNS = [
11
+ Sequel[:p][:id].as(:pact_publication_id),
12
+ Sequel[:p][:pact_version_id],
13
+ Sequel[:p][:revision_number].as(:pact_revision_number)
14
+ ]
15
+ VERIFICATION_COLUMNS = [
16
+ Sequel[:v][:id].as(:verification_id)
17
+ ]
18
+
19
+ ALL_COLUMNS = CONSUMER_COLUMNS + CONSUMER_VERSION_COLUMNS + PACT_COLUMNS +
20
+ PROVIDER_COLUMNS + PROVIDER_VERSION_COLUMNS + VERIFICATION_COLUMNS
21
+
22
+ SELECT_ALL_COLUMN_ARGS = [:select_all_columns] + ALL_COLUMNS
23
+ dataset_module do
24
+ select *SELECT_ALL_COLUMN_ARGS
25
+
26
+ def join_verifications
27
+ left_outer_join(:verifications, P_V_JOIN, { table_alias: :v } )
28
+ end
29
+
30
+ def verifications_for(query_ids)
31
+ db[:verifications]
32
+ .select(:id, :pact_version_id, :provider_id, :provider_version_id)
33
+ .where {
34
+ Sequel.&(
35
+ QueryBuilder.consumer_in_pacticipant_ids(query_ids),
36
+ QueryBuilder.provider_or_provider_version_matches(query_ids)
37
+ )
38
+ }
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -9,7 +9,7 @@ module PactBroker
9
9
  ors = provider_or_provider_version_criteria(query_ids, qualifier)
10
10
 
11
11
  ors << {
12
- qualify(:lp, :provider_id) => query_ids.all_pacticipant_ids,
12
+ qualify(:p, :provider_id) => query_ids.all_pacticipant_ids,
13
13
  qualify(qualifier, :provider_version_id) => nil
14
14
  }
15
15
  Sequel.|(*ors)
@@ -53,13 +53,13 @@ module PactBroker
53
53
  def self.consumer_or_consumer_version_or_provider_or_provider_or_provider_version_match(query_ids)
54
54
  ors = if query_ids.pacticipant_version_id
55
55
  [
56
- { Sequel[:lp][:consumer_version_id] => query_ids.pacticipant_version_id },
57
- { Sequel[:lv][:provider_version_id] => query_ids.pacticipant_version_id }
56
+ { Sequel[:p][:consumer_version_id] => query_ids.pacticipant_version_id },
57
+ { Sequel[:v][:provider_version_id] => query_ids.pacticipant_version_id }
58
58
  ]
59
59
  else
60
60
  [
61
- { Sequel[:lp][:consumer_id] => query_ids.pacticipant_id },
62
- { Sequel[:lp][:provider_id] => query_ids.pacticipant_id }
61
+ { Sequel[:p][:consumer_id] => query_ids.pacticipant_id },
62
+ { Sequel[:p][:provider_id] => query_ids.pacticipant_id }
63
63
  ]
64
64
  end
65
65
 
@@ -20,54 +20,52 @@ require 'pact_broker/matrix/query_ids'
20
20
 
21
21
  module PactBroker
22
22
  module Matrix
23
- class QuickRow < Sequel::Model(Sequel.as(:latest_pact_publication_ids_for_consumer_versions, :lp))
23
+ class QuickRow < Sequel::Model(Sequel.as(:latest_pact_publication_ids_for_consumer_versions, :p))
24
24
 
25
25
  # Tables
26
26
  LV = :latest_verification_id_for_pact_version_and_provider_version
27
27
  LP = :latest_pact_publication_ids_for_consumer_versions
28
28
 
29
29
  # Joins
30
- LP_LV_JOIN = { Sequel[:lp][:pact_version_id] => Sequel[:lv][:pact_version_id] }
31
- CONSUMER_JOIN = { Sequel[:lp][:consumer_id] => Sequel[:consumers][:id] }
32
- PROVIDER_JOIN = { Sequel[:lp][:provider_id] => Sequel[:providers][:id] }
33
- CONSUMER_VERSION_JOIN = { Sequel[:lp][:consumer_version_id] => Sequel[:cv][:id] }
34
- PROVIDER_VERSION_JOIN = { Sequel[:lv][:provider_version_id] => Sequel[:pv][:id] }
30
+ LP_LV_JOIN = { Sequel[:p][:pact_version_id] => Sequel[:v][:pact_version_id] }
31
+ CONSUMER_VERSION_JOIN = { Sequel[:p][:consumer_version_id] => Sequel[:cv][:id] }
32
+ PROVIDER_VERSION_JOIN = { Sequel[:v][:provider_version_id] => Sequel[:pv][:id] }
35
33
 
36
34
  # Not sure why we're eager loading some things and including others in the base query :shrug:
37
35
 
38
36
  # Columns
39
37
  CONSUMER_COLUMNS = [
40
- Sequel[:lp][:consumer_id],
38
+ Sequel[:p][:consumer_id],
41
39
  Sequel[:consumers][:name].as(:consumer_name)
42
40
  ]
43
41
  PROVIDER_COLUMNS = [
44
- Sequel[:lp][:provider_id],
42
+ Sequel[:p][:provider_id],
45
43
  Sequel[:providers][:name].as(:provider_name)
46
44
  ]
47
45
  CONSUMER_VERSION_COLUMNS = [
48
- Sequel[:lp][:consumer_version_id],
46
+ Sequel[:p][:consumer_version_id],
49
47
  Sequel[:cv][:number].as(:consumer_version_number),
50
48
  Sequel[:cv][:order].as(:consumer_version_order)
51
49
  ]
52
50
  PROVIDER_VERSION_COLUMNS = [
53
- Sequel[:lv][:provider_version_id],
51
+ Sequel[:v][:provider_version_id],
54
52
  Sequel[:pv][:number].as(:provider_version_number),
55
53
  Sequel[:pv][:order].as(:provider_version_order)
56
54
  ]
57
55
  PACT_COLUMNS = [
58
- Sequel[:lp][:pact_publication_id],
59
- Sequel[:lp][:pact_version_id]
56
+ Sequel[:p][:pact_publication_id],
57
+ Sequel[:p][:pact_version_id]
60
58
  ]
61
59
  VERIFICATION_COLUMNS = [
62
- Sequel[:lv][:verification_id]
60
+ Sequel[:v][:verification_id]
63
61
  ]
64
62
  ALL_COLUMNS = CONSUMER_COLUMNS + CONSUMER_VERSION_COLUMNS + PACT_COLUMNS +
65
63
  PROVIDER_COLUMNS + PROVIDER_VERSION_COLUMNS + VERIFICATION_COLUMNS
66
- PACTICIPANT_NAMES_AND_IDS = CONSUMER_COLUMNS + PROVIDER_COLUMNS
64
+
67
65
 
68
66
  # cachable select arguments
69
67
  SELECT_ALL_COLUMN_ARGS = [:select_all_columns] + ALL_COLUMNS
70
- SELECT_PACTICIPANT_NAMES_AND_IDS_ARGS = [:select_pacticipant_names_and_ids] + PACTICIPANT_NAMES_AND_IDS
68
+ SELECT_PACTICIPANT_IDS_ARGS = [:select_pacticipant_ids, Sequel[:p][:consumer_id], Sequel[:p][:provider_id]]
71
69
 
72
70
  associate(:many_to_one, :pact_publication, :class => "PactBroker::Pacts::PactPublication", :key => :pact_publication_id, :primary_key => :id)
73
71
  associate(:many_to_one, :provider, :class => "PactBroker::Domain::Pacticipant", :key => :provider_id, :primary_key => :id)
@@ -83,12 +81,26 @@ module PactBroker
83
81
  include PactBroker::Repositories::Helpers
84
82
 
85
83
  select *SELECT_ALL_COLUMN_ARGS
86
- select *SELECT_PACTICIPANT_NAMES_AND_IDS_ARGS
84
+ select *SELECT_PACTICIPANT_IDS_ARGS
87
85
 
88
86
  def distinct_integrations selectors
89
- select_pacticipant_names_and_ids
90
- .distinct
91
- .matching_selectors(selectors)
87
+ query = if selectors.size == 1
88
+ pacticipant_ids_matching_one_selector_optimised(selectors)
89
+ else
90
+ select_pacticipant_ids
91
+ .distinct
92
+ .matching_any_of_multiple_selectors(selectors)
93
+ end
94
+
95
+ query.from_self(alias: :pacticipant_ids)
96
+ .select(
97
+ :consumer_id,
98
+ Sequel[:c][:name].as(:consumer_name),
99
+ :provider_id,
100
+ Sequel[:p][:name].as(:provider_name)
101
+ )
102
+ .join_consumers(:pacticipant_ids, :c)
103
+ .join_providers(:pacticipant_ids, :p)
92
104
  end
93
105
 
94
106
  def matching_selectors selectors
@@ -117,6 +129,8 @@ module PactBroker
117
129
  .eager(:verification)
118
130
  .eager(:pact_publication)
119
131
  .eager(:pact_version)
132
+ .eager(:consumer_version_tags)
133
+ .eager(:provider_version_tags)
120
134
  end
121
135
 
122
136
  def default_scope
@@ -135,6 +149,29 @@ module PactBroker
135
149
  }
136
150
  end
137
151
 
152
+ def pacticipant_ids_matching_one_selector_optimised(selectors)
153
+ query_ids = QueryIds.from_selectors(selectors)
154
+ distinct_pacticipant_ids_where_consumer_or_consumer_version_matches(query_ids)
155
+ .union(distinct_pacticipant_ids_where_provider_or_provider_version_matches(query_ids), all: true)
156
+ end
157
+
158
+ def distinct_pacticipant_ids_where_consumer_or_consumer_version_matches(query_ids)
159
+ select_pacticipant_ids
160
+ .distinct
161
+ .where {
162
+ QueryBuilder.consumer_or_consumer_version_matches(query_ids, :p)
163
+ }
164
+ end
165
+
166
+ def distinct_pacticipant_ids_where_provider_or_provider_version_matches(query_ids)
167
+ select_pacticipant_ids
168
+ .distinct
169
+ .inner_join_verifications
170
+ .where {
171
+ QueryBuilder.provider_or_provider_version_matches(query_ids, :v)
172
+ }
173
+ end
174
+
138
175
  # When the user has specified multiple selectors, we only want to join the verifications for
139
176
  # the specified selectors. This is because of the behaviour of the left outer join.
140
177
  # Imagine a pact has been verified by a provider version that was NOT specified in the selectors.
@@ -150,15 +187,30 @@ module PactBroker
150
187
  .join_pacticipants_and_pacticipant_versions
151
188
  .where {
152
189
  Sequel.&(
153
- QueryBuilder.consumer_or_consumer_version_matches(query_ids, :lp),
154
- QueryBuilder.provider_or_provider_version_matches_or_pact_unverified(query_ids, :lv),
155
- QueryBuilder.either_consumer_or_provider_was_specified_in_query(query_ids, :lp)
190
+ QueryBuilder.consumer_or_consumer_version_matches(query_ids, :p),
191
+ QueryBuilder.provider_or_provider_version_matches_or_pact_unverified(query_ids, :v),
192
+ QueryBuilder.either_consumer_or_provider_was_specified_in_query(query_ids, :p)
193
+ )
194
+ }
195
+ end
196
+
197
+ def matching_any_of_multiple_selectors(selectors)
198
+ query_ids = QueryIds.from_selectors(selectors)
199
+ join_verifications_for(query_ids)
200
+ .join_pacticipants_and_pacticipant_versions
201
+ .where {
202
+ Sequel.&(
203
+ Sequel.|(
204
+ QueryBuilder.consumer_or_consumer_version_matches(query_ids, :p),
205
+ QueryBuilder.provider_or_provider_version_matches_or_pact_unverified(query_ids, :v),
206
+ ),
207
+ QueryBuilder.either_consumer_or_provider_was_specified_in_query(query_ids, :p)
156
208
  )
157
209
  }
158
210
  end
159
211
 
160
212
  def join_verifications_for(query_ids)
161
- left_outer_join(verifications_for(query_ids), LP_LV_JOIN, { table_alias: :lv } )
213
+ left_outer_join(verifications_for(query_ids), LP_LV_JOIN, { table_alias: :v } )
162
214
  end
163
215
 
164
216
  def verifications_for(query_ids)
@@ -179,12 +231,20 @@ module PactBroker
179
231
  .join_provider_versions
180
232
  end
181
233
 
182
- def join_consumers
183
- join(:pacticipants, CONSUMER_JOIN, { table_alias: :consumers })
234
+ def join_consumers qualifier = :p, table_alias = :consumers
235
+ join(
236
+ :pacticipants,
237
+ { Sequel[qualifier][:consumer_id] => Sequel[table_alias][:id] },
238
+ { table_alias: table_alias }
239
+ )
184
240
  end
185
241
 
186
- def join_providers
187
- join(:pacticipants, PROVIDER_JOIN, { table_alias: :providers })
242
+ def join_providers qualifier = :p, table_alias = :providers
243
+ join(
244
+ :pacticipants,
245
+ { Sequel[qualifier][:provider_id] => Sequel[table_alias][:id] },
246
+ { table_alias: table_alias }
247
+ )
188
248
  end
189
249
 
190
250
  def join_consumer_versions
@@ -196,7 +256,11 @@ module PactBroker
196
256
  end
197
257
 
198
258
  def join_verifications
199
- left_outer_join(LV, LP_LV_JOIN, { table_alias: :lv } )
259
+ left_outer_join(LV, LP_LV_JOIN, { table_alias: :v } )
260
+ end
261
+
262
+ def inner_join_verifications
263
+ join(LV, LP_LV_JOIN, { table_alias: :v } )
200
264
  end
201
265
  end # end dataset_module
202
266
 
@@ -306,6 +370,10 @@ module PactBroker
306
370
  return_or_raise_if_not_set(:provider_version_order)
307
371
  end
308
372
 
373
+ def has_verification?
374
+ !!verification_id
375
+ end
376
+
309
377
  # This model needs the verifications and pacticipants joined to it
310
378
  # before it can be used, as it's not a "real" model.
311
379
  def return_or_raise_if_not_set(key)