post merge pipeline and environments status
This commit is contained in:
parent
289651e26f
commit
dadc046d3a
22 changed files with 589 additions and 119 deletions
|
@ -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 {
|
|||
<div class="ci-widget media">
|
||||
<div class="media-body">
|
||||
<div class="deploy-body">
|
||||
<div class="deployment-info">
|
||||
<div class="js-deployment-info deployment-info">
|
||||
<template v-if="hasDeploymentMeta">
|
||||
<span>
|
||||
Deployed to
|
||||
{{ deployedText }}
|
||||
</span>
|
||||
<tooltip-on-truncate
|
||||
:title="deployment.name"
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
<script>
|
||||
import _ from 'underscore';
|
||||
import { __ } from '~/locale';
|
||||
import Project from '~/pages/projects/project';
|
||||
import SmartInterval from '~/smart_interval';
|
||||
import createFlash from '../flash';
|
||||
|
@ -80,6 +82,7 @@ export default {
|
|||
const service = this.createService(store);
|
||||
return {
|
||||
mr: store,
|
||||
state: store.state,
|
||||
service,
|
||||
};
|
||||
},
|
||||
|
@ -103,6 +106,17 @@ export default {
|
|||
(!this.mr.isNothingToMergeState && !this.mr.isMergedState)
|
||||
);
|
||||
},
|
||||
shouldRenderMergedPipeline() {
|
||||
return this.mr.state === 'merged' && !_.isEmpty(this.mr.mergePipeline);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
state(newVal, oldVal) {
|
||||
if (newVal !== oldVal && this.shouldRenderMergedPipeline) {
|
||||
// init polling
|
||||
this.initPostMergeDeploymentsPolling();
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initPolling();
|
||||
|
@ -112,11 +126,19 @@ export default {
|
|||
mounted() {
|
||||
this.setFaviconHelper();
|
||||
this.initDeploymentsPolling();
|
||||
|
||||
if (this.shouldRenderMergedPipeline) {
|
||||
this.initPostMergeDeploymentsPolling();
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
eventHub.$off('mr.discussion.updated', this.checkStatus);
|
||||
this.pollingInterval.destroy();
|
||||
this.deploymentsInterval.destroy();
|
||||
|
||||
if (this.postMergeDeploymentsInterval) {
|
||||
this.postMergeDeploymentsInterval.destroy();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
createService(store) {
|
||||
|
@ -146,7 +168,13 @@ export default {
|
|||
cb.call(null, data);
|
||||
}
|
||||
})
|
||||
.catch(() => createFlash('Something went wrong. Please try again.'));
|
||||
.catch(() => createFlash(__('Something went wrong. Please try again.')));
|
||||
},
|
||||
setFaviconHelper() {
|
||||
if (this.mr.ciStatusFaviconPath) {
|
||||
return setFaviconOverlay(this.mr.ciStatusFaviconPath);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
initPolling() {
|
||||
this.pollingInterval = new SmartInterval({
|
||||
|
@ -158,8 +186,14 @@ export default {
|
|||
});
|
||||
},
|
||||
initDeploymentsPolling() {
|
||||
this.deploymentsInterval = new SmartInterval({
|
||||
callback: this.fetchDeployments,
|
||||
this.deploymentsInterval = this.deploymentsPoll(this.fetchPreMergeDeployments);
|
||||
},
|
||||
initPostMergeDeploymentsPolling() {
|
||||
this.postMergeDeploymentsInterval = this.deploymentsPoll(this.fetchPostMergeDeployments);
|
||||
},
|
||||
deploymentsPoll(callback) {
|
||||
return new SmartInterval({
|
||||
callback,
|
||||
startingInterval: 30000,
|
||||
maxInterval: 120000,
|
||||
hiddenInterval: 240000,
|
||||
|
@ -167,26 +201,29 @@ export default {
|
|||
immediateExecution: true,
|
||||
});
|
||||
},
|
||||
setFaviconHelper() {
|
||||
if (this.mr.ciStatusFaviconPath) {
|
||||
return setFaviconOverlay(this.mr.ciStatusFaviconPath);
|
||||
}
|
||||
return Promise.resolve();
|
||||
fetchDeployments(target) {
|
||||
return this.service.fetchDeployments(target);
|
||||
},
|
||||
fetchDeployments() {
|
||||
return this.service
|
||||
.fetchDeployments()
|
||||
.then(res => res.data)
|
||||
.then(data => {
|
||||
fetchPreMergeDeployments() {
|
||||
return this.fetchDeployments()
|
||||
.then(({ data }) => {
|
||||
if (data.length) {
|
||||
this.mr.deployments = data;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
createFlash(
|
||||
'Something went wrong while fetching the environments for this merge request. Please try again.',
|
||||
);
|
||||
});
|
||||
.catch(() => this.throwDeploymentsError());
|
||||
},
|
||||
fetchPostMergeDeployments(){
|
||||
return this.fetchDeployments('merge_commit')
|
||||
.then(({ data }) => {
|
||||
if (data.length) {
|
||||
this.mr.postMergeDeployments = data;
|
||||
}
|
||||
})
|
||||
.catch(() => this.throwDeploymentsError());
|
||||
},
|
||||
throwDeploymentsError() {
|
||||
createFlash(__('Something went wrong while fetching the environments for this merge request. Please try again.'));
|
||||
},
|
||||
fetchActionsContent() {
|
||||
this.service
|
||||
|
@ -199,7 +236,7 @@ export default {
|
|||
Project.initRefSwitcher();
|
||||
}
|
||||
})
|
||||
.catch(() => createFlash('Something went wrong. Please try again.'));
|
||||
.catch(() => createFlash(__('Something went wrong. Please try again.')));
|
||||
},
|
||||
handleNotification(data) {
|
||||
if (data.ci_status === this.mr.ciStatus) return;
|
||||
|
@ -267,7 +304,8 @@ export default {
|
|||
/>
|
||||
<deployment
|
||||
v-for="deployment in mr.deployments"
|
||||
:key="deployment.id"
|
||||
:key="`pre-merge-deploy-${deployment.id}`"
|
||||
class="js-pre-merge-deploy"
|
||||
:deployment="deployment"
|
||||
/>
|
||||
<div class="mr-section-container">
|
||||
|
@ -308,5 +346,22 @@ export default {
|
|||
<mr-widget-merge-help />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="shouldRenderMergedPipeline">
|
||||
<mr-widget-pipeline
|
||||
class="js-post-merge-pipeline prepend-top-default"
|
||||
:pipeline="mr.mergePipeline"
|
||||
:ci-status="mr.ciStatus"
|
||||
:has-ci="mr.hasCI"
|
||||
:source-branch="mr.targetBranch"
|
||||
:source-branch-link="mr.targetBranch"
|
||||
/>
|
||||
<deployment
|
||||
v-for="postMergeDeployment in mr.postMergeDeployments"
|
||||
:key="`post-merge-deploy-${postMergeDeployment.id}`"
|
||||
:deployment="postMergeDeployment"
|
||||
class="js-post-deployment"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
5
changelogs/unreleased/ac-post-merge-pipeline.yml
Normal file
5
changelogs/unreleased/ac-post-merge-pipeline.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Show post-merge pipeline in merge request page
|
||||
merge_request: 22292
|
||||
author:
|
||||
type: added
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -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',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) }
|
||||
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue