From 339e2fda3fd5b8e43b24b36e2f4b1f508bbd4cab Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 20 May 2019 14:01:52 +0700 Subject: [PATCH] Expose merge train information Exposed data will be rendered in merge request widgets --- ee/app/models/merge_train.rb | 6 ++ .../ee/merge_request_widget_entity.rb | 10 ++- ee/app/serializers/merge_train_entity.rb | 14 ++++ .../merge_trains_summary_entity.rb | 13 ++++ .../unreleased/expose-merge-train-info.yml | 5 ++ ee/lib/api/merge_trains.rb | 66 +++++++++++++++++++ ee/lib/ee/api/endpoints.rb | 1 + ee/lib/ee/api/entities.rb | 21 ++++++ ee/spec/models/merge_train_spec.rb | 16 +++++ .../merge_request_widget_entity_spec.rb | 39 ++++++++++- .../serializers/merge_train_entity_spec.rb | 24 +++++++ .../merge_trains_summary_entity_spec.rb | 16 +++++ 12 files changed, 227 insertions(+), 4 deletions(-) create mode 100644 ee/app/serializers/merge_train_entity.rb create mode 100644 ee/app/serializers/merge_trains_summary_entity.rb create mode 100644 ee/changelogs/unreleased/expose-merge-train-info.yml create mode 100644 ee/lib/api/merge_trains.rb create mode 100644 ee/spec/serializers/merge_train_entity_spec.rb create mode 100644 ee/spec/serializers/merge_trains_summary_entity_spec.rb diff --git a/ee/app/models/merge_train.rb b/ee/app/models/merge_train.rb index dfa6105f3a7e74..9891ff51fb0a08 100644 --- a/ee/app/models/merge_train.rb +++ b/ee/app/models/merge_train.rb @@ -5,6 +5,8 @@ class MergeTrain < ApplicationRecord belongs_to :user belongs_to :pipeline, class_name: 'Ci::Pipeline' + delegate :project, to: :merge_request + class << self def all_in_train(merge_request) joined_merge_requests(merge_request).order('merge_trains.id ASC') @@ -25,6 +27,10 @@ def all_next self.class.all_in_train(merge_request).where('merge_trains.id > ?', id) end + def index + self.class.all_in_train(merge_request).where('merge_trains.id < ?', id).count + end + def first_in_train? !follower_in_train? end diff --git a/ee/app/serializers/ee/merge_request_widget_entity.rb b/ee/app/serializers/ee/merge_request_widget_entity.rb index b872aa7b9d3013..e595ad178762b3 100644 --- a/ee/app/serializers/ee/merge_request_widget_entity.rb +++ b/ee/app/serializers/ee/merge_request_widget_entity.rb @@ -135,10 +135,12 @@ module MergeRequestWidgetEntity merge_request.target_project.merge_pipelines_enabled? end - expose :merge_trains_enabled?, as: :merge_trains_enabled do |merge_request| - merge_request.target_project.merge_trains_enabled? + expose :merge_trains_summary, using: ::MergeTrainsSummaryEntity, if: -> (*) { merge_trains_enabled? } do |merge_request| + merge_request end + expose :merge_train, if: -> (merge_request) { merge_request.on_train? }, using: ::MergeTrainEntity + expose :can_push_to_source_branch do |merge_request| presenter(merge_request).can_push_to_source_branch? end @@ -176,5 +178,9 @@ def base_pipeline_downloadable_path_for_report_type(file_type) object.base_pipeline&.present(current_user: current_user) &.downloadable_path_for_report_type(file_type) end + + def merge_trains_enabled? + object.target_project.merge_trains_enabled? + end end end diff --git a/ee/app/serializers/merge_train_entity.rb b/ee/app/serializers/merge_train_entity.rb new file mode 100644 index 00000000000000..b6f9d82d82a52b --- /dev/null +++ b/ee/app/serializers/merge_train_entity.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class MergeTrainEntity < Grape::Entity + include RequestAwareEntity + + expose :index + expose :user, using: UserEntity + expose :pipeline, using: PipelineEntity + expose :created_at + + expose :cancel_path do |merge_train| + cancel_train_project_merge_request_path(merge_train.project, merge_train.merge_request) + end +end diff --git a/ee/app/serializers/merge_trains_summary_entity.rb b/ee/app/serializers/merge_trains_summary_entity.rb new file mode 100644 index 00000000000000..28e4015ab786c1 --- /dev/null +++ b/ee/app/serializers/merge_trains_summary_entity.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class MergeTrainsSummaryEntity < Grape::Entity + include RequestAwareEntity + + expose :total_count do |merge_request| + MergeTrain.all_in_train(merge_request).count + end + + expose :create_path do |merge_request| + create_train_project_merge_request_path(merge_request.project, merge_request) + end +end diff --git a/ee/changelogs/unreleased/expose-merge-train-info.yml b/ee/changelogs/unreleased/expose-merge-train-info.yml new file mode 100644 index 00000000000000..ca508ce280169d --- /dev/null +++ b/ee/changelogs/unreleased/expose-merge-train-info.yml @@ -0,0 +1,5 @@ +--- +title: Expose merge train information +merge_request: 12966 +author: +type: added diff --git a/ee/lib/api/merge_trains.rb b/ee/lib/api/merge_trains.rb new file mode 100644 index 00000000000000..02a159bad12936 --- /dev/null +++ b/ee/lib/api/merge_trains.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +module API + class MergeTrains < ::Grape::API + before { authenticate! } + + helpers do + def merge_request + @merge_request ||= find_merge_request_with_access(params[:merge_request_iid]) + end + end + + params do + requires :id, type: String, desc: 'The ID of a project' + requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request' + end + resource :projects, requirements: ::API::API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + segment ':id/merge_requests/:merge_request_iid/trains' do + desc 'Get all entries on the merge train' do + success ::EE::API::Entities::MergeTrain + end + get do + merge_request = find_merge_request_with_access(params[:merge_request_iid]) + + present MergeTrain.all_in_train(merge_request), with: ::EE::API::Entities::MergeTrain + end + + desc 'Enqueue a merge request to a train' do + success ::EE::API::Entities::MergeTrain + end + post 'enqueue' do + begin + merge_request.get_on_train!(current_user) + + present merge_request.merge_train, with: ::EE::API::Entities::MergeTrain, status: :ok + rescue => e + present { message: e.full_messages }, status: :bad_request + end + end + + segment ':id/merge_requests/:merge_request_iid/train' do + desc 'Get the detail of the merge train' do + success ::EE::API::Entities::MergeTrain + end + get do + merge_request = find_merge_request_with_access(params[:merge_request_iid]) + + present merge_request.merge_train, with: ::EE::API::Entities::MergeTrain, status: :ok + end + + desc 'Dequeue a merge request from a train' do + success ::EE::API::Entities::MergeTrain + end + post 'dequeue' do + begin + merge_request.get_off_train! + + present merge_request.merge_train, with: ::EE::API::Entities::MergeTrain, status: :ok + rescue => e + present { message: e.full_messages }, status: :bad_request + end + end + end + end + end +end diff --git a/ee/lib/ee/api/endpoints.rb b/ee/lib/ee/api/endpoints.rb index f5efdabd89bcfc..e81bd9c7948768 100644 --- a/ee/lib/ee/api/endpoints.rb +++ b/ee/lib/ee/api/endpoints.rb @@ -30,6 +30,7 @@ module Endpoints mount ::API::ManagedLicenses mount ::API::ProjectApprovals mount ::API::Vulnerabilities + mount ::API::MergeTrains version 'v3', using: :path do # Although the following endpoints are kept behind V3 namespace, diff --git a/ee/lib/ee/api/entities.rb b/ee/lib/ee/api/entities.rb index 3273f8fbc04107..532c89e4447594 100644 --- a/ee/lib/ee/api/entities.rb +++ b/ee/lib/ee/api/entities.rb @@ -445,6 +445,27 @@ class ApprovalState < Grape::Entity end end + class MergeTrainsSummary < Grape::Entity + expose :total_count do |merge_request| + MergeTrain.all_in_train(merge_request).count + end + + expose :create_path do |merge_request| + create_train_project_merge_request_path(merge_request.project, merge_request) + end + end + + class MergeTrain < Grape::Entity + expose :index + expose :user, using: UserEntity + expose :pipeline, using: PipelineEntity + expose :created_at + + expose :cancel_path do |merge_train| + cancel_train_project_merge_request_path(merge_train.project, merge_train.merge_request) + end + end + class LdapGroup < Grape::Entity expose :cn end diff --git a/ee/spec/models/merge_train_spec.rb b/ee/spec/models/merge_train_spec.rb index 7f2fc3451af3ff..66b1aecda2a2e4 100644 --- a/ee/spec/models/merge_train_spec.rb +++ b/ee/spec/models/merge_train_spec.rb @@ -114,6 +114,22 @@ end end + describe '#index' do + subject { merge_train.index } + + let(:merge_train) { merge_request.merge_train } + let!(:merge_request) { create_merge_request_on_train } + + it { is_expected.to eq(0) } + + context 'when the merge train is at the second queue' do + let(:merge_train) { merge_request_2.merge_train } + let!(:merge_request_2) { create_merge_request_on_train(source_branch: 'improve/awesome') } + + it { is_expected.to eq(1) } + end + end + def create_merge_request_on_train(target_project: project, target_branch: 'master', source_project: project, source_branch: 'feature') create(:merge_request, :on_train, diff --git a/ee/spec/serializers/merge_request_widget_entity_spec.rb b/ee/spec/serializers/merge_request_widget_entity_spec.rb index aa2c2da1536c8f..24354c956dd5f7 100644 --- a/ee/spec/serializers/merge_request_widget_entity_spec.rb +++ b/ee/spec/serializers/merge_request_widget_entity_spec.rb @@ -195,7 +195,42 @@ expect(subject.as_json).to include(:pipeline_id) end - it 'has merge trains flag' do - expect(subject.as_json).to include(:merge_trains_enabled) + describe 'Merge Trains' do + let!(:merge_train) { create(:merge_train, merge_request: merge_request) } + + before do + stub_licensed_features(merge_pipelines: true, merge_trains: true) + project.update!(merge_pipelines_enabled: true) + project.update!(merge_trains_enabled: true) + end + + it 'has merge train entity' do + expect(subject.as_json[:merge_train]).to include(:index) + expect(subject.as_json[:merge_train]).to include(:user) + expect(subject.as_json[:merge_train]).to include(:pipeline) + expect(subject.as_json[:merge_train]).to include(:created_at) + end + + it 'has merge train summary entity' do + expect(subject.as_json[:merge_trains_summary]).to include(:total_count) + end + + context 'when the merge request is not on a merge train' do + let!(:merge_train) { } + + it 'does not have merge_train entity' do + expect(subject.as_json).not_to include(:merge_train) + end + end + + context 'when the merge train feature is disabled' do + before do + project.update!(merge_trains_enabled: false) + end + + it 'does not have merge train summary entity' do + expect(subject.as_json).not_to include(:merge_trains_summary) + end + end end end diff --git a/ee/spec/serializers/merge_train_entity_spec.rb b/ee/spec/serializers/merge_train_entity_spec.rb new file mode 100644 index 00000000000000..03052849ef9069 --- /dev/null +++ b/ee/spec/serializers/merge_train_entity_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe MergeTrainEntity do + let(:merge_train) { create(:merge_train) } + let(:entity) { described_class.new(merge_train, request: request) } + let(:request) { double('request') } + + before do + allow(request).to receive(:current_user).and_return(merge_train.user) + end + + describe '#as_json' do + subject { entity.as_json } + + it 'includes attributes' do + expect(subject[:index]).to eq(merge_train.index) + expect(subject[:user][:name]).to eq(merge_train.user.name) + expect(subject[:pipeline][:id]).to eq(merge_train.pipeline.id) + expect(subject[:created_at]).to eq(merge_train.created_at) + end + end +end diff --git a/ee/spec/serializers/merge_trains_summary_entity_spec.rb b/ee/spec/serializers/merge_trains_summary_entity_spec.rb new file mode 100644 index 00000000000000..f5c93f65309159 --- /dev/null +++ b/ee/spec/serializers/merge_trains_summary_entity_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe MergeTrainsSummaryEntity do + let(:merge_train) { create(:merge_train) } + let(:entity) { described_class.new(merge_train.merge_request) } + + describe '#as_json' do + subject { entity.as_json } + + it 'includes attributes' do + expect(subject[:total_count]).to eq(1) + end + end +end -- GitLab