Skip to content
Snippets Groups Projects
Commit add32051 authored by 🤖 GitLab Bot 🤖's avatar 🤖 GitLab Bot 🤖
Browse files

Automatic merge of gitlab-org/gitlab master

parents b8d8bafb e20b5db9
No related branches found
No related tags found
No related merge requests found
Showing
with 264 additions and 42 deletions
include:
- project: gitlab-org/quality/pipeline-common
ref: 0.3.9
ref: 0.6.0
file:
- /ci/allure-report.yml
- /ci/knapsack-report.yml
......
......@@ -73,7 +73,7 @@ def folder
# rubocop: enable CodeReuse/ActiveRecord
def show
@deployments = environment.deployments.ordered.page(params[:page])
@deployments = deployments
end
def new
......@@ -202,6 +202,10 @@ def search
private
def deployments
environment.deployments.ordered.page(params[:page])
end
def verify_api_request!
Gitlab::Workhorse.verify_api_request!(request.headers)
end
......
......@@ -21,8 +21,6 @@ def execute(credentials)
if project.persisted?
success(project)
elsif project.errors[:import_source_disabled].present?
error(project.errors[:import_source_disabled], :forbidden)
else
log_and_return_error(project_save_error(project), :unprocessable_entity)
end
......
......@@ -25,8 +25,6 @@ def execute(access_params, provider)
if project.persisted?
success(project)
elsif project.errors[:import_source_disabled].present?
error(project.errors[:import_source_disabled], :forbidden)
else
error(project_save_error(project), :unprocessable_entity)
end
......
......@@ -4,9 +4,6 @@ module Projects
class CreateService < BaseService
include ValidatesClassificationLabel
ImportSourceDisabledError = Class.new(StandardError)
INTERNAL_IMPORT_SOURCES = %w[bare_repository gitlab_custom_project_template gitlab_project_migration].freeze
def initialize(user, params)
@current_user = user
@params = params.dup
......@@ -28,8 +25,6 @@ def execute
@project = Project.new(params)
validate_import_source_enabled!
@project.visibility_level = @project.group.visibility_level unless @project.visibility_level_allowed_by_group?
# If a project is newly created it should have shared runners settings
......@@ -82,9 +77,6 @@ def execute
rescue ActiveRecord::RecordInvalid => e
message = "Unable to save #{e.inspect}: #{e.record.errors.full_messages.join(", ")}"
fail(error: message)
rescue ImportSourceDisabledError => e
@project.errors.add(:import_source_disabled, e.message) if @project
fail(error: e.message)
rescue StandardError => e
@project.errors.add(:base, e.message) if @project
fail(error: e.message)
......@@ -246,16 +238,6 @@ def extra_attributes_for_measurement
private
def validate_import_source_enabled!
return unless @params[:import_type]
return if INTERNAL_IMPORT_SOURCES.include?(@params[:import_type])
unless ::Gitlab::CurrentSettings.import_sources&.include?(@params[:import_type])
raise ImportSourceDisabledError, "#{@params[:import_type]} import source is disabled"
end
end
def parent_namespace
@parent_namespace ||= Namespace.find_by_id(@params[:namespace_id]) || current_user.namespace
end
......
......@@ -46,3 +46,4 @@
.btn-group.table-action-buttons
= render 'projects/deployments/actions', deployment: deployment
= render 'projects/deployments/rollback', deployment: deployment
= render_if_exists 'projects/deployments/approvals', deployment: deployment
......@@ -38,6 +38,11 @@ export default {
required: true,
type: Object,
},
showText: {
required: false,
type: Boolean,
default: true,
},
},
data() {
return {
......@@ -54,6 +59,9 @@ export default {
deploymentIid: this.deploymentIid,
});
},
buttonTitle() {
return this.showText ? '' : this.$options.i18n.button;
},
upcomingDeployment() {
return this.environment?.upcomingDeployment;
},
......@@ -113,11 +121,14 @@ export default {
actOnDeployment(action) {
this.loading = true;
this.show = false;
action({
return action({
id: this.projectId,
deploymentId: this.upcomingDeployment.id,
comment: this.comment,
})
.then(() => {
this.$emit('change');
})
.catch((err) => {
if (err.response) {
createAlert({ message: err.response.data.message });
......@@ -125,7 +136,6 @@ export default {
})
.finally(() => {
this.loading = false;
this.$emit('change');
});
},
approvalText({ user }) {
......@@ -160,8 +170,18 @@ export default {
</script>
<template>
<gl-button-group v-if="needsApproval">
<gl-button :id="id" ref="button" :loading="loading" icon="thumb-up" @click="showPopover">
{{ $options.i18n.button }}
<gl-button
:id="id"
ref="button"
v-gl-tooltip
:loading="loading"
:title="buttonTitle"
icon="thumb-up"
@click="showPopover"
>
<template v-if="showText">
{{ $options.i18n.button }}
</template>
</gl-button>
<gl-popover :target="id" triggers="click blur" placement="top" :title="title" :show="show">
<p>
......
import * as Sentry from '@sentry/browser';
import Vue from 'vue';
import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
import EnvironmentApproval from './components/environment_approval.vue';
export const initDeploymentApprovals = () => {
const els = document.querySelectorAll('.js-deployment-approval');
els.forEach((el) => {
const {
name,
tier,
deployableName,
pendingApprovalCount,
iid,
id,
requiredApprovalCount,
approvals: approvalsString,
projectId,
canApproveDeployment,
} = el.dataset;
try {
const approvals = JSON.parse(approvalsString).map(convertObjectPropsToCamelCase);
const environment = {
upcomingDeployment: {
deployable: {
name: deployableName,
},
iid,
id,
pendingApprovalCount: parseInt(pendingApprovalCount, 10),
approvals,
canApproveDeployment: parseBoolean(canApproveDeployment),
},
name,
tier,
requiredApprovalCount: parseInt(requiredApprovalCount, 10),
};
// eslint-disable-next-line no-new
new Vue({
el,
provide: { projectId },
render(h) {
return h(EnvironmentApproval, {
props: { environment, showText: false },
on: {
change: () => {
window.location.reload();
},
},
});
},
});
} catch (e) {
Sentry.captureException(e);
}
});
};
import '~/pages/projects/environments/show';
import { initDeploymentApprovals } from 'ee/environments/mount_show';
initDeploymentApprovals();
......@@ -4,6 +4,7 @@ module EE
module Projects
module EnvironmentsController
extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override
prepended do
before_action :authorize_create_environment_terminal!, only: [:terminal]
......@@ -11,6 +12,11 @@ module EnvironmentsController
private
override :deployments
def deployments
super.with_approvals
end
def authorize_create_environment_terminal!
return render_404 unless can?(current_user, :create_environment_terminal, environment)
end
......
......@@ -21,5 +21,23 @@ def project_and_environment_metrics_data(project, environment)
super.merge(ee_metrics_data)
end
def deployment_approval_data(deployment)
{ pending_approval_count: deployment.pending_approval_count,
iid: deployment.iid,
id: deployment.id,
required_approval_count: deployment.environment.required_approval_count,
can_approve_deployment: can?(current_user, :update_deployment, deployment).to_s,
deployable_name: deployment.deployable&.name,
approvals: ::API::Entities::Deployments::Approval.represent(deployment.approvals).to_json,
project_id: deployment.project_id,
name: deployment.environment.name,
tier: deployment.environment.tier }
end
def show_deployment_approval?(deployment)
can?(current_user, :update_deployment, deployment) &&
deployment.environment.required_approval_count > 0
end
end
end
......@@ -16,6 +16,8 @@ module Deployment
has_many :approvals, class_name: 'Deployments::Approval'
scope :with_approvals, -> { preload(approvals: [:user])}
state_machine :status do
after_transition any => :success do |deployment|
deployment.run_after_commit do
......
- if show_deployment_approval?(deployment)
.js-deployment-approval{ data: deployment_approval_data(deployment) }
......@@ -4,7 +4,7 @@
RSpec.describe Projects::EnvironmentsController do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:environment) do
create(:environment, name: 'production', project: project)
......@@ -16,6 +16,27 @@
sign_in(user)
end
describe 'GET #show' do
before do
create(:deployment, :success, environment: environment, project: project) do |deployment|
create(:deployment_approval, deployment: deployment)
end
create(:deployment, :failed, environment: environment, project: project)
end
it 'preloads approvals their authors' do
get :show, params: environment_params
assigns(:deployments).each do |deployment|
expect(deployment.association(:approvals)).to be_loaded
deployment.approvals.each do |approval|
expect(approval.association(:user)).to be_loaded
end
end
end
end
describe '#GET terminal' do
let(:protected_environment) { create(:protected_environment, name: environment.name, project: project) }
......
import { GlButton, GlPopover } from '@gitlab/ui';
import { nextTick } from 'vue';
import { mountExtended, extendedWrapper } from 'helpers/vue_test_utils_helper';
import { trimText } from 'helpers/text_helper';
import waitForPromises from 'helpers/wait_for_promises';
......@@ -202,6 +203,7 @@ describe('ee/environments/components/environment_approval.vue', () => {
api.mockRejectedValue({ response: { data: { message: 'oops' } } });
await button.trigger('click');
await nextTick();
expect(createAlert).toHaveBeenCalledWith({ message: 'oops' });
});
......@@ -221,4 +223,21 @@ describe('ee/environments/components/environment_approval.vue', () => {
});
});
});
describe('showing text', () => {
it('should show text by default', () => {
wrapper = createWrapper();
const button = findButton();
expect(button.text()).toBe(s__('DeploymentApproval|Approval options'));
});
it('should hide the text if show text is false, and put it in the title', () => {
wrapper = createWrapper({ propsData: { showText: false } });
const button = findButton();
expect(button.text()).toBe('');
expect(button.attributes('title')).toBe(s__('DeploymentApproval|Approval options'));
});
});
});
......@@ -71,4 +71,88 @@
)
end
end
describe 'deployment_approval_data' do
let(:deployment) { create(:deployment, :blocked, project: project, environment: environment) }
subject { helper.deployment_approval_data(deployment) }
before do
stub_licensed_features(protected_environments: true)
create(:protected_environment, name: environment.name, project: project, required_approval_count: 3)
allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:can?)
.with(user, :update_deployment, deployment)
.and_return(true)
end
it 'provides data for a deployment approval' do
keys = %i(pending_approval_count
iid
id
required_approval_count
can_approve_deployment
deployable_name
approvals
project_id
name
tier)
expect(subject.keys).to match_array(keys)
end
end
describe 'show_deployment_approval?' do
let(:deployment) { create(:deployment, :blocked, project: project, environment: environment) }
subject { helper.show_deployment_approval?(deployment) }
before do
stub_licensed_features(protected_environments: true)
end
context 'with a required approval count' do
before do
create(:protected_environment, name: environment.name, project: project, required_approval_count: 3)
allow(helper).to receive(:current_user).and_return(user)
end
context 'user has access' do
before do
allow(helper).to receive(:can?)
.with(user, :update_deployment, deployment)
.and_return(true)
end
it 'returns true' do
expect(subject).to be(true)
end
end
context 'user does not have access' do
before do
allow(helper).to receive(:can?)
.with(user, :update_deployment, deployment)
.and_return(false)
end
it 'returns false' do
expect(subject).to be(false)
end
end
end
context 'without a required approval count' do
before do
allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:can?)
.with(user, :update_deployment, deployment)
.and_return(true)
end
it 'returns false' do
expect(subject).to be(false)
end
end
end
end
......@@ -190,6 +190,8 @@ def filter_attributes_using_license!(attrs)
def validate_git_import_url!(import_url)
return if import_url.blank?
yield if block_given?
result = Import::ValidateRemoteGitEndpointService.new(url: import_url).execute # network call
if result.error?
......
......@@ -5,6 +5,10 @@ class ImportBitbucketServer < ::API::Base
feature_category :importers
urgency :low
before do
forbidden! unless Gitlab::CurrentSettings.import_sources&.include?('bitbucket_server')
end
helpers do
def client
@client ||= BitbucketServer::Client.new(credentials)
......
......@@ -7,6 +7,10 @@ class ImportGithub < ::API::Base
rescue_from Octokit::Unauthorized, with: :provider_unauthorized
before do
forbidden! unless Gitlab::CurrentSettings.import_sources&.include?('github')
end
helpers do
def client
@client ||= if Feature.enabled?(:remove_legacy_github_client)
......
......@@ -90,6 +90,10 @@ def log_if_upload_exceed_max_size(user_project, file)
Gitlab::AppLogger.info({ message: "File exceeds maximum size", file_bytes: file.size, project_id: user_project.id, project_path: user_project.full_path, upload_allowed: allowed })
end
end
def check_import_by_url_is_enabled
Gitlab::CurrentSettings.import_sources&.include?('git') || forbidden!
end
end
helpers do
......@@ -198,11 +202,6 @@ def translate_params_for_compatibility(params)
params[:builds_enabled] = params.delete(:jobs_enabled) if params.key?(:jobs_enabled)
params
end
def add_import_params(params)
params[:import_type] = 'git' if params[:import_url]&.present?
params
end
end
resource :users, requirements: API::USER_REQUIREMENTS do
......@@ -272,10 +271,9 @@ def add_import_params(params)
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/issues/21139')
attrs = declared_params(include_missing: false)
attrs = translate_params_for_compatibility(attrs)
attrs = add_import_params(attrs)
filter_attributes_using_license!(attrs)
validate_git_import_url!(params[:import_url])
validate_git_import_url!(params[:import_url]) { check_import_by_url_is_enabled }
project = ::Projects::CreateService.new(current_user, attrs).execute
......@@ -288,8 +286,6 @@ def add_import_params(params)
error!(project.errors[:limit_reached], 403)
end
forbidden! if project.errors[:import_source_disabled].present?
render_validation_error!(project)
end
end
......@@ -315,7 +311,6 @@ def add_import_params(params)
attrs = declared_params(include_missing: false)
attrs = translate_params_for_compatibility(attrs)
attrs = add_import_params(attrs)
filter_attributes_using_license!(attrs)
validate_git_import_url!(params[:import_url])
......@@ -326,8 +321,6 @@ def add_import_params(params)
user_can_admin_project: can?(current_user, :admin_project, project),
current_user: current_user
else
forbidden! if project.errors[:import_source_disabled].present?
render_validation_error!(project)
end
end
......@@ -448,7 +441,6 @@ def add_import_params(params)
authorize! :change_visibility_level, user_project if user_project.visibility_attribute_present?(attrs)
attrs = translate_params_for_compatibility(attrs)
attrs = add_import_params(attrs)
filter_attributes_using_license!(attrs)
verify_update_project_attrs!(user_project, attrs)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment