Skip to content

Commit

Permalink
feat: support multiple branches per version (#495)
Browse files Browse the repository at this point in the history
* chore: add branches tables

* chore: keep track of the latest version for a branch

* chore: expose branches in version resource

* test: remove branch expectations for creating versions

* test: remove tests for updating versions with a branch

* chore: update branch head when a version is deleted

* chore: update code to find versions by branch using the matrix selector

* chore: update latest_for_branch?

* refactor: remove latest_version_for_branch code

* chore: update PactPublication.latest_for_consumer_branch

* test: update expectations

* chore: update PactPublication.latest_by_consumer_branch

* docs: update sequel annotations

* chore: update overall_latest test

* chore: use more efficient query for selecting latest for branches

* chore: display multiple branches on matrix and index pages

* feat: support adding a version to a branch

* chore: add branch-version relation to pacticipant

* chore: remove references to version.branch

* chore: update index and dashboard

* chore: remove domain object usage from migration specs

* chore: pass in branch for version creation

* refactor: remove unused param

* style: rubocop

* chore: remove unused require

* chore: add latest property to branch version decorator

* chore: update indexes

* chore: update latest by branch query

* chore: refactor

* chore: migrate version branch column to branch_version object

* chore: fix ordering of deployed/released selectors

* chore: sort environment selectors by non prod first

* test: include provider in latest_by_consumer_branch

* chore: remove unnecessary index

* chore: add index

* chore: update annotations

* chore: update logging

* chore: fix latest_for_consumer_branch

* chore: update hal docs
  • Loading branch information
bethesque authored Sep 6, 2021
1 parent 10dda8a commit acff2fc
Show file tree
Hide file tree
Showing 86 changed files with 1,529 additions and 531 deletions.
41 changes: 41 additions & 0 deletions db/migrations/20210816_create_branches_tables.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
Sequel.migration do
change do
create_table(:branches, charset: "utf8") do
primary_key :id
String :name
foreign_key :pacticipant_id, :pacticipants, null: false, on_delete: :cascade
DateTime :created_at, null: false
DateTime :updated_at, null: false
index [:pacticipant_id, :name], unique: true, name: :branches_pacticipant_id_name_index
end

create_table(:branch_versions, charset: "utf8") do
primary_key :id
foreign_key :branch_id, :branches, null: false, foreign_key_constraint_name: :branch_versions_branches_fk, on_delete: :cascade
foreign_key :version_id, :versions, null: false, foreign_key_constraint_name: :branch_versions_versions_fk, on_delete: :cascade
Integer :version_order, null: false
Integer :pacticipant_id, null: false
String :branch_name, null: false
DateTime :created_at, null: false
DateTime :updated_at, null: false
index [:branch_id, :version_id], unique: true, name: :branch_versions_branch_id_version_id_index
index [:version_id], name: :branch_versions_version_id_index
index [:branch_name], name: :branch_versions_branch_name_index
# Can probably drop this index when the "latest pact" logic changes
index [:pacticipant_id, :branch_id, :version_order], name: :branch_versions_pacticipant_id_branch_id_version_order_index
end

create_table(:branch_heads) do
primary_key :id
foreign_key :branch_id, :branches, null: false, on_delete: :cascade
foreign_key :branch_version_id, :branch_versions, null: false, on_delete: :cascade
Integer :version_id, null: false
Integer :pacticipant_id, null: false
String :branch_name, null: false
index [:branch_id], unique: true, name: :branch_heads_branch_id_index
index [:branch_name], name: :branch_heads_branch_name_index
index [:pacticipant_id], name: :branch_heads_pacticipant_id_index
index [:version_id], name: :branch_heads_version_id_index
end
end
end
1 change: 1 addition & 0 deletions lib/pact_broker/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ def self.build_api(application_context = PactBroker::ApplicationContext.default_
add ["pacticipants", :pacticipant_name, "latest-version"], Api::Resources::LatestVersion, {resource_name: "latest_pacticipant_version"}
add ["pacticipants", :pacticipant_name, "versions", :pacticipant_version_number, "tags", :tag_name], Api::Resources::Tag, {resource_name: "pacticipant_version_tag"}
add ["pacticipants", :pacticipant_name, "labels", :label_name], Api::Resources::Label, {resource_name: "pacticipant_label"}
add ["pacticipants", :pacticipant_name, "branches", :branch_name, "versions", :version_number], Api::Resources::BranchVersion, { resource_name: "branch_version" }

# Webhooks
add ["webhooks", "provider", :provider_name, "consumer", :consumer_name ], Api::Resources::PacticipantWebhooks, {resource_name: "pacticipant_webhooks"}
Expand Down
20 changes: 20 additions & 0 deletions lib/pact_broker/api/decorators/branch_version_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require "pact_broker/api/decorators/base_decorator"
require "pact_broker/api/decorators/timestamps"

module PactBroker
module Api
module Decorators
class BranchVersionDecorator < BaseDecorator

link :self do | user_options |
{
title: "Branch version",
href: branch_version_url(represented, user_options.fetch(:base_url))
}
end

include Timestamps
end
end
end
end
6 changes: 4 additions & 2 deletions lib/pact_broker/api/decorators/dashboard_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ def consumer_hash(index_item, _consumer, _consumer_version, base_url)
name: index_item.consumer_name,
version: {
number: index_item.consumer_version_number,
branch: index_item.consumer_version_branch,
branch: index_item.consumer_version_branches.last,
headBranchNames: index_item.consumer_version_branches,
_links: {
self: {
href: version_url(base_url, index_item.consumer_version)
Expand Down Expand Up @@ -87,7 +88,8 @@ def provider_hash(index_item, _provider, base_url)
if index_item.latest_verification
hash[:version] = {
number: index_item.provider_version_number,
branch: index_item.provider_version_branch
branch: index_item.provider_version_branches.last,
headBranchNames: index_item.provider_version_branches
}
end

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require_relative "base_decorator"
require_relative "timestamps"

module PactBroker
module Api
module Decorators
class EmbeddedBranchVersionDecorator < BaseDecorator
property :branch_name, as: :name
property :latest?, as: :latest

link :self do | options |
{
title: "Version branch",
name: represented.branch_name,
href: branch_version_url(represented, options[:base_url])
}
end
end
end
end
end
5 changes: 0 additions & 5 deletions lib/pact_broker/api/decorators/embedded_tag_decorator.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
require_relative "base_decorator"
require_relative "pact_pacticipant_decorator"
require_relative "timestamps"

module PactBroker

module Api

module Decorators

class EmbeddedTagDecorator < BaseDecorator

property :name

link :self do | options |
Expand Down
13 changes: 11 additions & 2 deletions lib/pact_broker/api/decorators/matrix_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
require "pact_broker/api/pact_broker_urls"
require "pact_broker/api/decorators/reason_decorator"
require "pact_broker/api/decorators/format_date_time"
require "pact_broker/api/decorators/embedded_branch_version_decorator"

module PactBroker
module Api
Expand Down Expand Up @@ -83,7 +84,8 @@ def consumer_hash(line, consumer, consumer_version, base_url)
name: line.consumer_name,
version: {
number: line.consumer_version_number,
branch: line.consumer_version_branch,
branch: line.consumer_version_branch_versions.last&.branch_name,
branches: branches(line.consumer_version_branch_versions, base_url),
_links: {
self: {
href: version_url(base_url, consumer_version)
Expand All @@ -99,6 +101,12 @@ def consumer_hash(line, consumer, consumer_version, base_url)
}
end

def branches(branch_versions, base_url)
branch_versions.collect do | branch_version |
PactBroker::Api::Decorators::EmbeddedBranchVersionDecorator.new(branch_version).to_hash(user_options: { base_url: base_url })
end
end

def tags(tags, base_url)
tags.sort_by(&:created_at).collect do | tag |
{
Expand Down Expand Up @@ -127,7 +135,8 @@ def provider_hash(line, provider, provider_version, base_url)
if !line.provider_version_number.nil?
hash[:version] = {
number: line.provider_version_number,
branch: line.provider_version_branch,
branch: line.provider_version_branch_versions.last&.branch_name,
branches: branches(line.provider_version_branch_versions, base_url),
_links: {
self: {
href: version_url(base_url, provider_version)
Expand Down
8 changes: 8 additions & 0 deletions lib/pact_broker/api/decorators/pacticipant_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ class PacticipantDecorator < BaseDecorator
}
end

link :'pb:branch-version' do | options |
{
title: "Get or add/create a version for a branch of #{represented.name}",
href: templated_branch_version_url_for_pacticipant(represented.name, options[:base_url]),
templated: true
}
end

link :'pb:label' do | options |
{
title: "Get, create or delete a label for #{represented.name}",
Expand Down
2 changes: 1 addition & 1 deletion lib/pact_broker/api/decorators/version_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module Decorators
class VersionDecorator < BaseDecorator

property :number, writeable: false
property :branch
collection :branch_versions, as: :branches, embedded: true, writeable: false, extend: PactBroker::Api::Decorators::EmbeddedBranchVersionDecorator
property :build_url, as: :buildUrl

collection :tags, embedded: true, :extend => PactBroker::Api::Decorators::EmbeddedTagDecorator, class: OpenStruct
Expand Down
8 changes: 8 additions & 0 deletions lib/pact_broker/api/pact_broker_urls.rb
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,18 @@ def tag_url base_url, tag
"#{tags_url(base_url, tag.version)}/#{url_encode(tag.name)}"
end

def branch_version_url(branch_version, base_url = "")
"#{pacticipant_url(base_url, branch_version.pacticipant)}/branches/#{url_encode(branch_version.branch_name)}/versions/#{url_encode(branch_version.version_number)}"
end

def templated_tag_url_for_pacticipant pacticipant_name, base_url = ""
pacticipant_url_from_params({ pacticipant_name: pacticipant_name }, base_url) + "/versions/{version}/tags/{tag}"
end

def templated_branch_version_url_for_pacticipant pacticipant_name, base_url = ""
pacticipant_url_from_params({ pacticipant_name: pacticipant_name }, base_url) + "/branches/{branch}/versions/{version}"
end

def templated_version_url_for_pacticipant pacticipant_name, base_url = ""
pacticipant_url_from_params({ pacticipant_name: pacticipant_name }, base_url) + "/versions/{version}"
end
Expand Down
48 changes: 48 additions & 0 deletions lib/pact_broker/api/resources/branch_version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
require "pact_broker/api/resources/base_resource"
require "pact_broker/api/decorators/branch_version_decorator"

module PactBroker
module Api
module Resources
class BranchVersion < BaseResource
def content_types_provided
[["application/hal+json", :to_json]]
end

def content_types_accepted
[["application/json", :from_json]]
end

def allowed_methods
["GET", "PUT", "OPTIONS"]
end

def resource_exists?
!!branch_version
end

def to_json
decorator_class(:branch_version_decorator).new(branch_version).to_json(decorator_options)
end

def from_json
already_existed = !!branch_version
@branch_version = branch_service.find_or_create_branch_version(identifier_from_path)
# Make it return a 201 by setting the Location header
response.headers["Location"] = branch_version_url(branch_version, base_url) unless already_existed
response.body = to_json
end

def policy_name
:'versions::branch_version'
end

private

def branch_version
@branch_version ||= branch_service.find_branch_version(identifier_from_path)
end
end
end
end
end
6 changes: 6 additions & 0 deletions lib/pact_broker/api/resources/index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ def links
title: "Get, create or delete a tag for a pacticipant version",
templated: true
},
"pb:pacticipant-branch-version" =>
{
href: base_url + "/pacticipants/{pacticipant}/branches/{branch}/versions/{version}",
title: "Get or add/create a pacticipant version for a branch",
templated: true
},
"pb:pacticipant-version" =>
{
href: base_url + "/pacticipants/{pacticipant}/versions/{version}",
Expand Down
8 changes: 0 additions & 8 deletions lib/pact_broker/api/resources/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,6 @@ def allowed_methods
["GET", "PUT", "PATCH", "DELETE", "OPTIONS"]
end

def is_conflict?
if (errors = version_service.conflict_errors(version, parsed_version, resource_url)).any?
set_json_validation_error_messages(errors)
else
false
end
end

def resource_exists?
!!version
end
Expand Down
97 changes: 97 additions & 0 deletions lib/pact_broker/db/data_migrations/create_branches.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
require "pact_broker/db/data_migrations/helpers"

module PactBroker
module DB
module DataMigrations
class CreateBranches
extend Helpers

def self.call connection
if required_columns_exist?(connection)
branch_ids = create_branch_versions(connection)
upsert_branch_heads(connection, branch_ids)
end
end

def self.required_columns_exist?(connection)
column_exists?(connection, :versions, :branch) &&
connection.table_exists?(:branches) &&
connection.table_exists?(:branch_versions) &&
connection.table_exists?(:branch_heads)
end

def self.create_branch_versions(connection)
versions_without_a_branch_version(connection).collect do | version |
create_branch_version(connection, version)
end.uniq
end

def self.upsert_branch_heads(connection, branch_ids)
branch_ids.each do | branch_id |
upsert_branch_head(connection, branch_id)
end
end

def self.versions_without_a_branch_version(connection)
branch_versions_join = {
Sequel[:versions][:id] => Sequel[:branch_versions][:version_id],
Sequel[:branch_versions][:branch_name] => Sequel[:versions][:branch]
}

connection[:versions]
.select(Sequel[:versions].*)
.exclude(branch: nil)
.left_outer_join(:branch_versions, branch_versions_join)
.where(Sequel[:branch_versions][:branch_name] => nil)
.order(:pacticipant_id, :order)
end

def self.create_branch_version(connection, version)
branch_values = {
name: version[:branch],
pacticipant_id: version[:pacticipant_id],
created_at: version[:created_at],
updated_at: version[:created_at]
}
connection[:branches].insert_ignore.insert(branch_values)
branch_id = connection[:branches].select(:id).where(pacticipant_id: version[:pacticipant_id], name: version[:branch]).single_record[:id]

branch_version_values = {
pacticipant_id: version[:pacticipant_id],
version_id: version[:id],
version_order: version[:order],
branch_id: branch_id,
branch_name: version[:branch],
created_at: version[:created_at],
updated_at: version[:created_at]
}

connection[:branch_versions].insert_ignore.insert(branch_version_values)
branch_id
end

def self.upsert_branch_head(connection, branch_id)
latest_branch_version = connection[:branch_versions].where(branch_id: branch_id).order(:version_order).last

if connection[:branch_heads].where(branch_id: branch_id).empty?
branch_head_values = {
pacticipant_id: latest_branch_version[:pacticipant_id],
branch_id: branch_id,
branch_version_id: latest_branch_version[:id],
version_id: latest_branch_version[:version_id],
branch_name: latest_branch_version[:branch_name]
}
connection[:branch_heads].insert(branch_head_values)
else
connection[:branch_heads]
.where(branch_id: branch_id)
.update(
branch_version_id: latest_branch_version[:id],
version_id: latest_branch_version[:version_id]
)
end
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/pact_broker/db/migrate_data.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def self.call database_connection, _options = {}
DataMigrations::SetWebhookUuid.call(database_connection)
DataMigrations::SetConsumerVersionOrderForPactPublications.call(database_connection)
DataMigrations::SetExtraColumnsForTags.call(database_connection)
DataMigrations::CreateBranches.call(database_connection)
end
end
end
Expand Down
Loading

0 comments on commit acff2fc

Please sign in to comment.