From dadc046d3a5c797294330bcd26acdadac069c9cc Mon Sep 17 00:00:00 2001 From: Alessio Caiazza Date: Mon, 29 Oct 2018 11:34:41 +0000 Subject: [PATCH] post merge pipeline and environments status --- .../components/deployment.vue | 15 +- .../mr_widget_options.vue | 95 +++++++-- .../services/mr_widget_service.js | 8 +- .../stores/mr_widget_store.js | 2 + .../projects/merge_requests_controller.rb | 12 +- app/models/deployment.rb | 4 + app/models/environment_status.rb | 41 +++- app/models/merge_request.rb | 6 + app/serializers/environment_status_entity.rb | 1 + .../merge_request_widget_entity.rb | 1 + .../unreleased/ac-post-merge-pipeline.yml | 5 + locale/gitlab.pot | 15 ++ .../merge_requests_controller_spec.rb | 51 ++++- .../user_sees_deployment_widget_spec.rb | 19 +- .../user_sees_merge_widget_spec.rb | 11 +- .../entities/merge_request_widget.json | 1 + .../components/deployment_spec.js | 144 ++++++++------ .../vue_mr_widget/mr_widget_options_spec.js | 188 +++++++++++++++++- spec/models/environment_status_spec.rb | 32 ++- spec/models/merge_request_spec.rb | 20 ++ .../environment_status_entity_spec.rb | 3 +- .../merge_request_widget_entity_spec.rb | 34 ++++ 22 files changed, 589 insertions(+), 119 deletions(-) create mode 100644 changelogs/unreleased/ac-post-merge-pipeline.yml diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue index 6c87287a4c4..5e7a35e9396 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue @@ -2,6 +2,7 @@ import Icon from '~/vue_shared/components/icon.vue'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; import FilteredSearchDropdown from '~/vue_shared/components/filtered_search_dropdown.vue'; +import { __ } from '~/locale'; import timeagoMixin from '../../vue_shared/mixins/timeago'; import tooltip from '../../vue_shared/directives/tooltip'; import LoadingButton from '../../vue_shared/components/loading_button.vue'; @@ -31,6 +32,11 @@ export default { required: true, }, }, + deployedTextMap: { + running: __('Deploying to'), + success: __('Deployed to'), + failed: __('Failed to deploy to'), + }, data() { const features = window.gon.features || {}; return { @@ -54,10 +60,13 @@ export default { hasMetrics() { return !!this.deployment.metrics_url; }, + deployedText() { + return this.$options.deployedTextMap[this.deployment.status]; + }, }, methods: { stopEnvironment() { - const msg = 'Are you sure you want to stop this environment?'; + const msg = __('Are you sure you want to stop this environment?'); const isConfirmed = confirm(msg); // eslint-disable-line if (isConfirmed) { @@ -87,10 +96,10 @@ export default {
-
+
diff --git a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js index fecbfec2214..bf5b85b2ae6 100644 --- a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js +++ b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js @@ -21,8 +21,12 @@ export default class MRWidgetService { return axios.delete(this.endpoints.sourceBranchPath); } - fetchDeployments() { - return axios.get(this.endpoints.ciEnvironmentsStatusPath); + fetchDeployments(targetParam) { + return axios.get(this.endpoints.ciEnvironmentsStatusPath, { + params: { + environment_target: targetParam + } + }); } poll() { diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index e6655914700..a0c008e7314 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -32,7 +32,9 @@ export default class MergeRequestStore { this.commitsCount = data.commits_count; this.divergedCommitsCount = data.diverged_commits_count; this.pipeline = data.pipeline || {}; + this.mergePipeline = data.merge_pipeline || {}; this.deployments = this.deployments || data.deployments || []; + this.postMergeDeployments = this.postMergeDeployments || []; this.initRebase(data); if (data.issues_links) { diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 757b03d0b0e..614722fd60d 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -201,9 +201,11 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo end def ci_environments_status - environments = @merge_request.environments_for(current_user).map do |environment| - EnvironmentStatus.new(environment, @merge_request) - end + environments = if ci_environments_status_on_merge_result? + EnvironmentStatus.after_merge_request(@merge_request, current_user) + else + EnvironmentStatus.for_merge_request(@merge_request, current_user) + end render json: EnvironmentStatusSerializer.new(current_user: current_user).represent(environments) end @@ -241,6 +243,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo private + def ci_environments_status_on_merge_result? + params[:environment_target] == 'merge_commit' + end + def target_branch_missing? @merge_request.has_no_commits? && !@merge_request.target_branch_exists? end diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 62dc0f2cbeb..ee5b96e7454 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -127,6 +127,10 @@ class Deployment < ActiveRecord::Base metrics&.merge(deployment_time: created_at.to_i) || {} end + def status + 'success' + end + private def prometheus_adapter diff --git a/app/models/environment_status.rb b/app/models/environment_status.rb index 5ff3acc0e58..a84871f7253 100644 --- a/app/models/environment_status.rb +++ b/app/models/environment_status.rb @@ -3,21 +3,33 @@ class EnvironmentStatus include Gitlab::Utils::StrongMemoize - attr_reader :environment, :merge_request + attr_reader :environment, :merge_request, :sha delegate :id, to: :environment delegate :name, to: :environment delegate :project, to: :environment delegate :deployed_at, to: :deployment, allow_nil: true + delegate :status, to: :deployment - def initialize(environment, merge_request) + def self.for_merge_request(mr, user) + build_environments_status(mr, user, mr.head_pipeline) + end + + def self.after_merge_request(mr, user) + return [] unless mr.merged? + + build_environments_status(mr, user, mr.merge_pipeline) + end + + def initialize(environment, merge_request, sha) @environment = environment @merge_request = merge_request + @sha = sha end def deployment strong_memoize(:deployment) do - environment.first_deployment_for(merge_request.diff_head_sha) + environment.first_deployment_for(sha) end end @@ -26,10 +38,9 @@ class EnvironmentStatus end def changes - sha = merge_request.diff_head_sha return [] if project.route_map_for(sha).nil? - changed_files.map { |file| build_change(file, sha) }.compact + changed_files.map { |file| build_change(file) }.compact end def changed_files @@ -41,7 +52,7 @@ class EnvironmentStatus PAGE_EXTENSIONS = /\A\.(s?html?|php|asp|cgi|pl)\z/i.freeze - def build_change(file, sha) + def build_change(file) public_path = project.public_path_for_source_path(file.new_path, sha) return if public_path.nil? @@ -53,4 +64,22 @@ class EnvironmentStatus external_url: environment.external_url_for(file.new_path, sha) } end + + def self.build_environments_status(mr, user, pipeline) + return [] unless pipeline.present? + + find_environments(user, pipeline).map do |environment| + EnvironmentStatus.new(environment, mr, pipeline.sha) + end + end + private_class_method :build_environments_status + + def self.find_environments(user, pipeline) + env_ids = Deployment.where(deployable: pipeline.builds).select(:environment_id) + + Environment.available.where(id: env_ids).select do |environment| + Ability.allowed?(user, :read_environment, environment) + end + end + private_class_method :find_environments end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 6559f94a696..7eef08aa6a3 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -204,6 +204,12 @@ class MergeRequest < ActiveRecord::Base head_pipeline&.sha == diff_head_sha ? head_pipeline : nil end + def merge_pipeline + return unless merged? + + target_project.pipeline_for(target_branch, merge_commit_sha) + end + # Pattern used to extract `!123` merge request references from text # # This pattern supports cross-project references. diff --git a/app/serializers/environment_status_entity.rb b/app/serializers/environment_status_entity.rb index 3dfa4f204c9..f87cc894d2f 100644 --- a/app/serializers/environment_status_entity.rb +++ b/app/serializers/environment_status_entity.rb @@ -5,6 +5,7 @@ class EnvironmentStatusEntity < Grape::Entity expose :id expose :name + expose :status expose :url do |es| project_environment_path(es.project, es.environment) diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb index 9ec24f799ef..f33a1654d5e 100644 --- a/app/serializers/merge_request_widget_entity.rb +++ b/app/serializers/merge_request_widget_entity.rb @@ -55,6 +55,7 @@ class MergeRequestWidgetEntity < IssuableEntity expose :merge_commit_message expose :actual_head_pipeline, with: PipelineDetailsEntity, as: :pipeline + expose :merge_pipeline, with: PipelineDetailsEntity, if: ->(mr, _) { mr.merged? && can?(request.current_user, :read_pipeline, mr.target_project)} # Booleans expose :merge_ongoing?, as: :merge_ongoing diff --git a/changelogs/unreleased/ac-post-merge-pipeline.yml b/changelogs/unreleased/ac-post-merge-pipeline.yml new file mode 100644 index 00000000000..08322c9cb8a --- /dev/null +++ b/changelogs/unreleased/ac-post-merge-pipeline.yml @@ -0,0 +1,5 @@ +--- +title: Show post-merge pipeline in merge request page +merge_request: 22292 +author: +type: added diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 463a58fff7c..7f7b44800c7 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -648,6 +648,9 @@ msgstr "" msgid "Are you sure you want to reset the health check token?" msgstr "" +msgid "Are you sure you want to stop this environment?" +msgstr "" + msgid "Are you sure?" msgstr "" @@ -2324,6 +2327,12 @@ msgstr "" msgid "DeployTokens|Your new project deploy token has been created." msgstr "" +msgid "Deployed to" +msgstr "" + +msgid "Deploying to" +msgstr "" + msgid "Deprioritize label" msgstr "" @@ -2750,6 +2759,9 @@ msgstr "" msgid "Failed to check related branches." msgstr "" +msgid "Failed to deploy to" +msgstr "" + msgid "Failed to load emoji list." msgstr "" @@ -5604,6 +5616,9 @@ msgstr "" msgid "Something went wrong while fetching comments. Please try again." msgstr "" +msgid "Something went wrong while fetching the environments for this merge request. Please try again." +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "" diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index dcfd6c05200..7b0459e0325 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -749,13 +749,15 @@ describe Projects::MergeRequestsController do describe 'GET ci_environments_status' do context 'the environment is from a forked project' do - let!(:forked) { fork_project(project, user, repository: true) } - let!(:environment) { create(:environment, project: forked) } - let!(:deployment) { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') } - let(:admin) { create(:admin) } + let(:forked) { fork_project(project, user, repository: true) } + let(:sha) { forked.commit.sha } + let(:environment) { create(:environment, project: forked) } + let(:pipeline) { create(:ci_pipeline, sha: sha, project: forked) } + let(:build) { create(:ci_build, pipeline: pipeline) } + let!(:deployment) { create(:deployment, environment: environment, sha: sha, ref: 'master', deployable: build) } let(:merge_request) do - create(:merge_request, source_project: forked, target_project: project) + create(:merge_request, source_project: forked, target_project: project, target_branch: 'master', head_pipeline: pipeline) end it 'links to the environment on that project' do @@ -764,6 +766,35 @@ describe Projects::MergeRequestsController do expect(json_response.first['url']).to match /#{forked.full_path}/ end + context "when environment_target is 'merge_commit'" do + it 'returns nothing' do + get_ci_environments_status(environment_target: 'merge_commit') + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_empty + end + + context 'when is merged' do + let(:source_environment) { create(:environment, project: project) } + let(:merge_commit_sha) { project.repository.merge(user, forked.commit.id, merge_request, "merged in test") } + let(:post_merge_pipeline) { create(:ci_pipeline, sha: merge_commit_sha, project: project) } + let(:post_merge_build) { create(:ci_build, pipeline: post_merge_pipeline) } + let!(:source_deployment) { create(:deployment, environment: source_environment, sha: merge_commit_sha, ref: 'master', deployable: post_merge_build) } + + before do + merge_request.update!(merge_commit_sha: merge_commit_sha) + merge_request.mark_as_merged! + end + + it 'returns the enviroment on the source project' do + get_ci_environments_status(environment_target: 'merge_commit') + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.first['url']).to match /#{project.full_path}/ + end + end + end + # we're trying to reduce the overall number of queries for this method. # set a hard limit for now. https://gitlab.com/gitlab-org/gitlab-ce/issues/52287 it 'keeps queries in check' do @@ -772,11 +803,15 @@ describe Projects::MergeRequestsController do expect(control_count).to be <= 137 end - def get_ci_environments_status - get :ci_environments_status, + def get_ci_environments_status(extra_params = {}) + params = { namespace_id: merge_request.project.namespace.to_param, project_id: merge_request.project, - id: merge_request.iid, format: 'json' + id: merge_request.iid, + format: 'json' + } + + get :ci_environments_status, params.merge(extra_params) end end end diff --git a/spec/features/merge_request/user_sees_deployment_widget_spec.rb b/spec/features/merge_request/user_sees_deployment_widget_spec.rb index f744d7941f5..a298ead43db 100644 --- a/spec/features/merge_request/user_sees_deployment_widget_spec.rb +++ b/spec/features/merge_request/user_sees_deployment_widget_spec.rb @@ -3,15 +3,19 @@ require 'rails_helper' describe 'Merge request > User sees deployment widget', :js do describe 'when deployed to an environment' do let(:user) { create(:user) } - let(:project) { merge_request.target_project } - let(:merge_request) { create(:merge_request, :merged) } + let(:project) { create(:project, :repository) } + let(:merge_request) { create(:merge_request, :merged, source_project: project) } let(:environment) { create(:environment, project: project) } let(:role) { :developer } - let(:sha) { project.commit('master').id } - let!(:deployment) { create(:deployment, environment: environment, sha: sha) } + let(:ref) { merge_request.target_branch } + let(:sha) { project.commit(ref).id } + let(:pipeline) { create(:ci_pipeline_without_jobs, sha: sha, project: project, ref: ref) } + let(:build) { create(:ci_build, :success, pipeline: pipeline) } + let!(:deployment) { create(:deployment, environment: environment, sha: sha, ref: ref, deployable: build) } let!(:manual) { } before do + merge_request.update!(merge_commit_sha: sha) project.add_user(user, role) sign_in(user) visit project_merge_request_path(project, merge_request) @@ -26,15 +30,10 @@ describe 'Merge request > User sees deployment widget', :js do end context 'with stop action' do - let(:pipeline) { create(:ci_pipeline, project: project) } - let(:build) { create(:ci_build, pipeline: pipeline) } let(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') } - let(:deployment) do - create(:deployment, environment: environment, ref: merge_request.target_branch, - sha: sha, deployable: build, on_stop: 'close_app') - end before do + deployment.update!(on_stop: manual.name) wait_for_requests end diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb index f15129759de..4e5699f9571 100644 --- a/spec/features/merge_request/user_sees_merge_widget_spec.rb +++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb @@ -40,21 +40,26 @@ describe 'Merge request > User sees merge widget', :js do context 'view merge request' do let!(:environment) { create(:environment, project: project) } + let(:sha) { project.commit(merge_request.source_branch).sha } + let(:pipeline) { create(:ci_pipeline_without_jobs, status: 'success', sha: sha, project: project, ref: merge_request.source_branch) } + let(:build) { create(:ci_build, :success, pipeline: pipeline) } let!(:deployment) do create(:deployment, environment: environment, - ref: 'feature', - sha: merge_request.diff_head_sha) + ref: merge_request.source_branch, + deployable: build, + sha: sha) end before do + merge_request.update!(head_pipeline: pipeline) visit project_merge_request_path(project, merge_request) end it 'shows environments link' do wait_for_requests - page.within('.mr-widget-heading') do + page.within('.js-pre-merge-deploy') do expect(page).to have_content("Deployed to #{environment.name}") expect(find('.js-deploy-url')[:href]).to include(environment.formatted_external_url) end diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json index c40977bc4ee..35971d564d5 100644 --- a/spec/fixtures/api/schemas/entities/merge_request_widget.json +++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json @@ -46,6 +46,7 @@ "diff_head_commit_short_id": { "type": ["string", "null"] }, "merge_commit_message": { "type": ["string", "null"] }, "pipeline": { "type": ["object", "null"] }, + "merge_pipeline": { "type": ["object", "null"] }, "work_in_progress": { "type": "boolean" }, "source_branch_exists": { "type": "boolean" }, "mergeable_discussions_state": { "type": "boolean" }, diff --git a/spec/javascripts/vue_mr_widget/components/deployment_spec.js b/spec/javascripts/vue_mr_widget/components/deployment_spec.js index ce850bc621e..d26ffa4b4a6 100644 --- a/spec/javascripts/vue_mr_widget/components/deployment_spec.js +++ b/spec/javascripts/vue_mr_widget/components/deployment_spec.js @@ -2,54 +2,48 @@ import Vue from 'vue'; import deploymentComponent from '~/vue_merge_request_widget/components/deployment.vue'; import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service'; import { getTimeago } from '~/lib/utils/datetime_utility'; - -const deploymentMockData = { - id: 15, - name: 'review/diplo', - url: '/root/acets-review-apps/environments/15', - stop_url: '/root/acets-review-apps/environments/15/stop', - metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics', - metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics', - external_url: 'http://diplo.', - external_url_formatted: 'diplo.', - deployed_at: '2017-03-22T22:44:42.258Z', - deployed_at_formatted: 'Mar 22, 2017 10:44pm', - changes: [ - { - path: 'index.html', - external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html', - }, - { - path: 'imgs/gallery.html', - external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html', - }, - { - path: 'about/', - external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/', - }, - ], -}; -const createComponent = () => { - const Component = Vue.extend(deploymentComponent); - - return new Component({ - el: document.createElement('div'), - propsData: { deployment: { ...deploymentMockData } }, - }); -}; +import mountComponent from '../../helpers/vue_mount_component_helper'; describe('Deployment component', () => { - let vm; + const Component = Vue.extend(deploymentComponent); + const deploymentMockData = { + id: 15, + name: 'review/diplo', + url: '/root/review-apps/environments/15', + stop_url: '/root/review-apps/environments/15/stop', + metrics_url: '/root/review-apps/environments/15/deployments/1/metrics', + metrics_monitoring_url: '/root/review-apps/environments/15/metrics', + external_url: 'http://gitlab.com.', + external_url_formatted: 'gitlab', + deployed_at: '2017-03-22T22:44:42.258Z', + deployed_at_formatted: 'Mar 22, 2017 10:44pm', + changes: [ + { + path: 'index.html', + external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html', + }, + { + path: 'imgs/gallery.html', + external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html', + }, + { + path: 'about/', + external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/', + }, + ], + }; - beforeEach(() => { - vm = createComponent(); - }); + let vm; afterEach(() => { vm.$destroy(); }); - describe('computed', () => { + describe('', () => { + beforeEach(() => { + vm = mountComponent(Component, { deployment: { ...deploymentMockData } }); + }); + describe('deployTimeago', () => { it('return formatted date', () => { const readable = getTimeago().format(deploymentMockData.deployed_at); @@ -111,9 +105,7 @@ describe('Deployment component', () => { expect(vm.hasDeploymentMeta).toEqual(false); }); }); - }); - describe('methods', () => { describe('stopEnvironment', () => { const url = '/foo/bar'; const returnPromise = () => @@ -152,42 +144,33 @@ describe('Deployment component', () => { expect(MRWidgetService.stopEnvironment).not.toHaveBeenCalled(); }); }); - }); - - describe('template', () => { - let el; - - beforeEach(() => { - vm = createComponent(deploymentMockData); - el = vm.$el; - }); it('renders deployment name', () => { - expect(el.querySelector('.js-deploy-meta').getAttribute('href')).toEqual( + expect(vm.$el.querySelector('.js-deploy-meta').getAttribute('href')).toEqual( deploymentMockData.url, ); - expect(el.querySelector('.js-deploy-meta').innerText).toContain(deploymentMockData.name); + expect(vm.$el.querySelector('.js-deploy-meta').innerText).toContain(deploymentMockData.name); }); it('renders external URL', () => { - expect(el.querySelector('.js-deploy-url').getAttribute('href')).toEqual( + expect(vm.$el.querySelector('.js-deploy-url').getAttribute('href')).toEqual( deploymentMockData.external_url, ); - expect(el.querySelector('.js-deploy-url').innerText).toContain('View app'); + expect(vm.$el.querySelector('.js-deploy-url').innerText).toContain('View app'); }); it('renders stop button', () => { - expect(el.querySelector('.btn')).not.toBeNull(); + expect(vm.$el.querySelector('.btn')).not.toBeNull(); }); it('renders deployment time', () => { - expect(el.querySelector('.js-deploy-time').innerText).toContain(vm.deployTimeago); + expect(vm.$el.querySelector('.js-deploy-time').innerText).toContain(vm.deployTimeago); }); it('renders metrics component', () => { - expect(el.querySelector('.js-mr-memory-usage')).not.toBeNull(); + expect(vm.$el.querySelector('.js-mr-memory-usage')).not.toBeNull(); }); }); @@ -196,8 +179,7 @@ describe('Deployment component', () => { window.gon = window.gon || {}; window.gon.features = window.gon.features || {}; window.gon.features.ciEnvironmentsStatusChanges = true; - - vm = createComponent(deploymentMockData); + vm = mountComponent(Component, { deployment: { ...deploymentMockData } }); }); afterEach(() => { @@ -216,7 +198,7 @@ describe('Deployment component', () => { window.gon.features = window.gon.features || {}; window.gon.features.ciEnvironmentsStatusChanges = false; - vm = createComponent(deploymentMockData); + vm = mountComponent(Component, { deployment: { ...deploymentMockData } }); }); afterEach(() => { @@ -228,4 +210,44 @@ describe('Deployment component', () => { expect(vm.$el.querySelector('.js-deploy-url-feature-flag')).not.toBeNull(); }); }); + + describe('deployment status', () => { + describe('running', () => { + beforeEach(() => { + vm = mountComponent(Component, { + deployment: Object.assign({}, deploymentMockData, { status: 'running' }), + }); + }); + + it('renders information about running deployment', () => { + expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain('Deploying to'); + }); + }); + + describe('success', () => { + beforeEach(() => { + vm = mountComponent(Component, { + deployment: Object.assign({}, deploymentMockData, { status: 'success' }), + }); + }); + + it('renders information about finished deployment', () => { + expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain('Deployed to'); + }); + }); + + describe('failed', () => { + beforeEach(() => { + vm = mountComponent(Component, { + deployment: Object.assign({}, deploymentMockData, { status: 'failed' }), + }); + }); + + it('renders information about finished deployment', () => { + expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain( + 'Failed to deploy to', + ); + }); + }); + }); }); diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js index d1a064b9f4d..27b6c91e154 100644 --- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js +++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js @@ -189,7 +189,7 @@ describe('mrWidgetOptions', () => { it('should fetch deployments', done => { spyOn(vm.service, 'fetchDeployments').and.returnValue(returnPromise([{ id: 1 }])); - vm.fetchDeployments(); + vm.fetchPreMergeDeployments(); setTimeout(() => { expect(vm.service.fetchDeployments).toHaveBeenCalled(); @@ -454,6 +454,7 @@ describe('mrWidgetOptions', () => { deployed_at: '2017-03-22T22:44:42.258Z', deployed_at_formatted: 'Mar 22, 2017 10:44pm', changes, + status: 'success' }; beforeEach(done => { @@ -486,4 +487,189 @@ describe('mrWidgetOptions', () => { ).toEqual(changes.length); }); }); + + describe('pipeline for target branch after merge', () => { + describe('with information for target branch pipeline', () => { + beforeEach(done => { + vm.mr.state = 'merged'; + vm.mr.mergePipeline = { + id: 127, + user: { + id: 1, + name: 'Administrator', + username: 'root', + state: 'active', + avatar_url: null, + web_url: 'http://localhost:3000/root', + status_tooltip_html: null, + path: '/root', + }, + active: true, + coverage: null, + source: 'push', + created_at: '2018-10-22T11:41:35.186Z', + updated_at: '2018-10-22T11:41:35.433Z', + path: '/root/ci-web-terminal/pipelines/127', + flags: { + latest: true, + stuck: true, + auto_devops: false, + yaml_errors: false, + retryable: false, + cancelable: true, + failure_reason: false, + }, + details: { + status: { + icon: 'status_pending', + text: 'pending', + label: 'pending', + group: 'pending', + tooltip: 'pending', + has_details: true, + details_path: '/root/ci-web-terminal/pipelines/127', + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png', + }, + duration: null, + finished_at: null, + stages: [ + { + name: 'test', + title: 'test: pending', + status: { + icon: 'status_pending', + text: 'pending', + label: 'pending', + group: 'pending', + tooltip: 'pending', + has_details: true, + details_path: '/root/ci-web-terminal/pipelines/127#test', + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png', + }, + path: '/root/ci-web-terminal/pipelines/127#test', + dropdown_path: '/root/ci-web-terminal/pipelines/127/stage.json?stage=test', + }, + ], + artifacts: [], + manual_actions: [], + scheduled_actions: [], + }, + ref: { + name: 'master', + path: '/root/ci-web-terminal/commits/master', + tag: false, + branch: true, + }, + commit: { + id: 'aa1939133d373c94879becb79d91828a892ee319', + short_id: 'aa193913', + title: "Merge branch 'master-test' into 'master'", + created_at: '2018-10-22T11:41:33.000Z', + parent_ids: [ + '4622f4dd792468993003caf2e3be978798cbe096', + '76598df914cdfe87132d0c3c40f80db9fa9396a4', + ], + message: + "Merge branch 'master-test' into 'master'\n\nUpdate .gitlab-ci.yml\n\nSee merge request root/ci-web-terminal!1", + author_name: 'Administrator', + author_email: 'admin@example.com', + authored_date: '2018-10-22T11:41:33.000Z', + committer_name: 'Administrator', + committer_email: 'admin@example.com', + committed_date: '2018-10-22T11:41:33.000Z', + author: { + id: 1, + name: 'Administrator', + username: 'root', + state: 'active', + avatar_url: null, + web_url: 'http://localhost:3000/root', + status_tooltip_html: null, + path: '/root', + }, + author_gravatar_url: null, + commit_url: + 'http://localhost:3000/root/ci-web-terminal/commit/aa1939133d373c94879becb79d91828a892ee319', + commit_path: '/root/ci-web-terminal/commit/aa1939133d373c94879becb79d91828a892ee319', + }, + cancel_path: '/root/ci-web-terminal/pipelines/127/cancel', + }; + vm.$nextTick(done); + }); + + it('renders pipeline block', () => { + expect(vm.$el.querySelector('.js-post-merge-pipeline')).not.toBeNull(); + }); + + describe('with post merge deployments', () => { + beforeEach(done => { + vm.mr.postMergeDeployments = [{ + id: 15, + name: 'review/diplo', + url: '/root/acets-review-apps/environments/15', + stop_url: '/root/acets-review-apps/environments/15/stop', + metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics', + metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics', + external_url: 'http://diplo.', + external_url_formatted: 'diplo.', + deployed_at: '2017-03-22T22:44:42.258Z', + deployed_at_formatted: 'Mar 22, 2017 10:44pm', + changes: [ + { + path: 'index.html', + external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html', + }, + { + path: 'imgs/gallery.html', + external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html', + }, + { + path: 'about/', + external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/', + }, + ], + status: 'success' + }]; + + vm.$nextTick(done); + }); + + it('renders post deployment information', () => { + expect(vm.$el.querySelector('.js-post-deployment')).not.toBeNull(); + }); + }); + }); + + describe('without information for target branch pipeline', () => { + beforeEach(done => { + vm.mr.state = 'merged'; + + vm.$nextTick(done); + }); + + it('does not render pipeline block', () => { + expect(vm.$el.querySelector('.js-post-merge-pipeline')).toBeNull(); + }); + }); + + describe('when state is not merged', () => { + beforeEach(done => { + vm.mr.state = 'archived'; + + vm.$nextTick(done); + }); + + it('does not render pipeline block', () => { + expect(vm.$el.querySelector('.js-post-merge-pipeline')).toBeNull(); + }); + + it('does not render post deployment information', () => { + expect(vm.$el.querySelector('.js-post-deployment')).toBeNull(); + }); + }); + }); }); diff --git a/spec/models/environment_status_spec.rb b/spec/models/environment_status_spec.rb index f2eb263c98c..e7805d52d75 100644 --- a/spec/models/environment_status_spec.rb +++ b/spec/models/environment_status_spec.rb @@ -5,13 +5,15 @@ describe EnvironmentStatus do let(:environment) { deployment.environment} let(:project) { deployment.project } let(:merge_request) { create(:merge_request, :deployed_review_app, deployment: deployment) } + let(:sha) { deployment.sha } - subject(:environment_status) { described_class.new(environment, merge_request) } + subject(:environment_status) { described_class.new(environment, merge_request, sha) } it { is_expected.to delegate_method(:id).to(:environment) } it { is_expected.to delegate_method(:name).to(:environment) } it { is_expected.to delegate_method(:project).to(:environment) } it { is_expected.to delegate_method(:deployed_at).to(:deployment).as(:created_at) } + it { is_expected.to delegate_method(:status).to(:deployment) } describe '#project' do subject { environment_status.project } @@ -58,4 +60,32 @@ describe EnvironmentStatus do ) end end + + describe '.for_merge_request' do + let(:admin) { create(:admin) } + let(:pipeline) { create(:ci_pipeline, sha: sha) } + + it 'is based on merge_request.head_pipeline' do + expect(merge_request).to receive(:head_pipeline).and_return(pipeline) + expect(merge_request).not_to receive(:merge_pipeline) + + described_class.for_merge_request(merge_request, admin) + end + end + + describe '.after_merge_request' do + let(:admin) { create(:admin) } + let(:pipeline) { create(:ci_pipeline, sha: sha) } + + before do + merge_request.mark_as_merged! + end + + it 'is based on merge_request.merge_pipeline' do + expect(merge_request).to receive(:merge_pipeline).and_return(pipeline) + expect(merge_request).not_to receive(:head_pipeline) + + described_class.after_merge_request(merge_request, admin) + end + end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 666d7e69f89..c8943f2d86f 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1058,6 +1058,26 @@ describe MergeRequest do end end + describe '#merge_pipeline' do + it 'returns nil when not merged' do + expect(subject.merge_pipeline).to be_nil + end + + context 'when the MR is merged' do + let(:sha) { subject.target_project.commit.id } + let(:pipeline) { create(:ci_empty_pipeline, sha: sha, ref: subject.target_branch, project: subject.target_project) } + + before do + subject.mark_as_merged! + subject.update_attribute(:merge_commit_sha, pipeline.sha) + end + + it 'returns the post-merge pipeline' do + expect(subject.merge_pipeline).to eq(pipeline) + end + end + end + describe '#has_ci?' do let(:merge_request) { build_stubbed(:merge_request) } diff --git a/spec/serializers/environment_status_entity_spec.rb b/spec/serializers/environment_status_entity_spec.rb index 6894c65d639..1b4d8b70aa6 100644 --- a/spec/serializers/environment_status_entity_spec.rb +++ b/spec/serializers/environment_status_entity_spec.rb @@ -9,7 +9,7 @@ describe EnvironmentStatusEntity do let(:project) { deployment.project } let(:merge_request) { create(:merge_request, :deployed_review_app, deployment: deployment) } - let(:environment_status) { EnvironmentStatus.new(environment, merge_request) } + let(:environment_status) { EnvironmentStatus.new(environment, merge_request, merge_request.diff_head_sha) } let(:entity) { described_class.new(environment_status, request: request) } subject { entity.as_json } @@ -26,6 +26,7 @@ describe EnvironmentStatusEntity do it { is_expected.to include(:deployed_at) } it { is_expected.to include(:deployed_at_formatted) } it { is_expected.to include(:changes) } + it { is_expected.to include(:status) } it { is_expected.not_to include(:stop_url) } it { is_expected.not_to include(:metrics_url) } diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb index 5bf8aa7f23f..561421d5ac8 100644 --- a/spec/serializers/merge_request_widget_entity_spec.rb +++ b/spec/serializers/merge_request_widget_entity_spec.rb @@ -52,6 +52,40 @@ describe MergeRequestWidgetEntity do end end + describe 'merge_pipeline' do + it 'returns nil' do + expect(subject[:merge_pipeline]).to be_nil + end + + context 'when is merged' do + let(:resource) { create(:merged_merge_request, source_project: project, merge_commit_sha: project.commit.id) } + let(:pipeline) { create(:ci_empty_pipeline, project: project, ref: resource.target_branch, sha: resource.merge_commit_sha) } + + before do + project.add_maintainer(user) + end + + it 'returns merge_pipeline' do + pipeline.reload + pipeline_payload = PipelineDetailsEntity + .represent(pipeline, request: request) + .as_json + + expect(subject[:merge_pipeline]).to eq(pipeline_payload) + end + + context 'when user cannot read pipelines on target project' do + before do + project.add_guest(user) + end + + it 'returns nil' do + expect(subject[:merge_pipeline]).to be_nil + end + end + end + end + describe 'metrics' do context 'when metrics record exists with merged data' do before do