New upstream version 12.10.1

This commit is contained in:
Pirate Praveen 2020-04-25 10:58:03 +05:30
parent d85b82979f
commit 33be1af3d8
61 changed files with 782 additions and 423 deletions

View file

@ -1,5 +1,48 @@
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
## 12.10.0 (2020-04-22)
### Fixed (6 changes, 1 of them is from the community)
- Split CI minutes resets into different workers. !29017
- Fix duplicated events on Value Stream Analytics stage form. !29030
- Fix missing autocomplete results in the ElasticSearch admin UI. !29043 (mbergeron)
- Support visual reviews on private and internal projects through PAT authentication. !29336
- Nuget search: fix the prerelease filtering. !29482
- Smartcard should be counted as 2fa. !29504
### Changed (6 changes)
- Default the :tasks_by_type_chart feature flag on. !28486
- Remove Add Metric button from custom dashboards. !29036
- Improve Geo Node save error messages. !29079
- Cosmetic changes for Epic Health Status. !29234
- Improve readability of Dependency List. !29593
- Restrict prompt to check user account settings only to Gitlab.com. !29672
### Added (15 changes)
- Add LDAP user filter to group link API. !26202
- Implement Shared Runner Minute Factors. !27792
- Updates the package registry list UI which also includes adding pipeline information. !28426
- Allow Admins to preview the payload for Seat Link requests. !28582
- Add deployment frequency to Project level Value Stream Analytics summary. !28772
- Add deployment frequency to Group Value Stream Analytics summary. !28776
- Provide milestone burnup chart data for scope committed graph. !28899
- Make Status Page MVC generally available. !28966
- License Compliance - Add `order_by` filter. !28970
- Add the selected compliance frameworks label to project home and listings. !29137
- Generate smaller versions of Design Management design files. !29215
- .com has a subscription expired banner. !29422
- Allow changing item parent in epic tree via GraphQL. !29567
- Add PyPI Packages Repository. !29702
- Enable requirements for projects and basic actions (CRUD) for them.
### Other (1 change)
- Add health status counts to usage data. !28964
## 12.9.4 (2020-04-16) ## 12.9.4 (2020-04-16)
- No changes. - No changes.

View file

@ -2,6 +2,25 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 12.10.1 (2020-04-24)
### Fixed (5 changes)
- Fix bug creating project from git ssh. !29771
- Fix Web IDE handling of deleting newly added files. !29783
- Fix null dereference in /import status REST endpoint. !29886
- Fix Service Templates missing Active toggle. !29936
- Fix 500 error on accessing restricted levels. !30313
### Changed (1 change)
- Move Group Deploy Tokens to new Group-scoped Repository settings. !29290
### Other (1 change)
- Migration of dismissals to vulnerabilities. !29711
## 12.10.0 (2020-04-22) ## 12.10.0 (2020-04-22)
### Removed (3 changes) ### Removed (3 changes)

View file

@ -1 +1 @@
66fd5d1b9018ebf5427141c733234060b45bf626 12.10.1

View file

@ -1 +1 @@
12.10.0 12.10.1

View file

@ -39,7 +39,11 @@ export const diffCompareDropdownTargetVersions = (state, getters) => {
...v, ...v,
}; };
}; };
return [...state.mergeRequestDiffs.slice(1).map(formatVersion), baseVersion, headVersion];
if (gon.features?.diffCompareWithHead) {
return [...state.mergeRequestDiffs.slice(1).map(formatVersion), baseVersion, headVersion];
}
return [...state.mergeRequestDiffs.slice(1).map(formatVersion), baseVersion];
}; };
export const diffCompareDropdownSourceVersions = (state, getters) => { export const diffCompareDropdownSourceVersions = (state, getters) => {

View file

@ -216,7 +216,12 @@ export default {
if (entry.type === 'blob') { if (entry.type === 'blob') {
if (tempFile) { if (tempFile) {
// Since we only support one list of file changes, it's safe to just remove from both
// changed and staged. Otherwise, we'd need to somehow evaluate the difference between
// changed and HEAD.
// https://gitlab.com/gitlab-org/create-stage/-/issues/12669
state.changedFiles = state.changedFiles.filter(f => f.path !== path); state.changedFiles = state.changedFiles.filter(f => f.path !== path);
state.stagedFiles = state.stagedFiles.filter(f => f.path !== path);
} else { } else {
state.changedFiles = state.changedFiles.concat(entry); state.changedFiles = state.changedFiles.concat(entry);
} }

View file

@ -0,0 +1,9 @@
import IntegrationSettingsForm from '~/integrations/integration_settings_form';
import initAlertsSettings from '~/alerts_service_settings';
document.addEventListener('DOMContentLoaded', () => {
const integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
integrationSettingsForm.init();
initAlertsSettings(document.querySelector('.js-alerts-service-settings'));
});

View file

@ -1,13 +1,10 @@
import initSettingsPanels from '~/settings_panels'; import initSettingsPanels from '~/settings_panels';
import AjaxVariableList from '~/ci_variable_list/ajax_variable_list'; import AjaxVariableList from '~/ci_variable_list/ajax_variable_list';
import initVariableList from '~/ci_variable_list'; import initVariableList from '~/ci_variable_list';
import DueDateSelectors from '~/due_date_select';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels // Initialize expandable settings panels
initSettingsPanels(); initSettingsPanels();
// eslint-disable-next-line no-new
new DueDateSelectors();
if (gon.features.newVariablesUi) { if (gon.features.newVariablesUi) {
initVariableList(); initVariableList();

View file

@ -0,0 +1,9 @@
import initSettingsPanels from '~/settings_panels';
import DueDateSelectors from '~/due_date_select';
document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels
initSettingsPanels();
new DueDateSelectors(); // eslint-disable-line no-new
});

View file

@ -3,7 +3,6 @@ import SecretValues from '~/behaviors/secret_values';
import AjaxVariableList from '~/ci_variable_list/ajax_variable_list'; import AjaxVariableList from '~/ci_variable_list/ajax_variable_list';
import registrySettingsApp from '~/registry/settings/registry_settings_bundle'; import registrySettingsApp from '~/registry/settings/registry_settings_bundle';
import initVariableList from '~/ci_variable_list'; import initVariableList from '~/ci_variable_list';
import DueDateSelectors from '~/due_date_select';
import initDeployKeys from '~/deploy_keys'; import initDeployKeys from '~/deploy_keys';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
@ -41,9 +40,6 @@ document.addEventListener('DOMContentLoaded', () => {
autoDevOpsExtraSettings.classList.toggle('hidden', !target.checked); autoDevOpsExtraSettings.classList.toggle('hidden', !target.checked);
}); });
// eslint-disable-next-line no-new
new DueDateSelectors();
registrySettingsApp(); registrySettingsApp();
initDeployKeys(); initDeployKeys();
}); });

View file

@ -7,6 +7,6 @@ class Groups::DeployTokensController < Groups::ApplicationController
@token = @group.deploy_tokens.find(params[:id]) @token = @group.deploy_tokens.find(params[:id])
@token.revoke! @token.revoke!
redirect_to group_settings_ci_cd_path(@group, anchor: 'js-deploy-tokens') redirect_to group_settings_repository_path(@group, anchor: 'js-deploy-tokens')
end end
end end

View file

@ -8,9 +8,8 @@ module Groups
before_action :authorize_update_max_artifacts_size!, only: [:update] before_action :authorize_update_max_artifacts_size!, only: [:update]
before_action do before_action do
push_frontend_feature_flag(:new_variables_ui, @group, default_enabled: true) push_frontend_feature_flag(:new_variables_ui, @group, default_enabled: true)
push_frontend_feature_flag(:ajax_new_deploy_token, @group)
end end
before_action :define_variables, only: [:show, :create_deploy_token] before_action :define_variables, only: [:show]
def show def show
end end
@ -42,38 +41,10 @@ module Groups
redirect_to group_settings_ci_cd_path redirect_to group_settings_ci_cd_path
end end
def create_deploy_token
result = Groups::DeployTokens::CreateService.new(@group, current_user, deploy_token_params).execute
@new_deploy_token = result[:deploy_token]
if result[:status] == :success
respond_to do |format|
format.json do
# IMPORTANT: It's a security risk to expose the token value more than just once here!
json = API::Entities::DeployTokenWithToken.represent(@new_deploy_token).as_json
render json: json, status: result[:http_status]
end
format.html do
flash.now[:notice] = s_('DeployTokens|Your new group deploy token has been created.')
render :show
end
end
else
respond_to do |format|
format.json { render json: { message: result[:message] }, status: result[:http_status] }
format.html do
flash.now[:alert] = result[:message]
render :show
end
end
end
end
private private
def define_variables def define_variables
define_ci_variables define_ci_variables
define_deploy_token_variables
end end
def define_ci_variables def define_ci_variables
@ -83,12 +54,6 @@ module Groups
.map { |variable| variable.present(current_user: current_user) } .map { |variable| variable.present(current_user: current_user) }
end end
def define_deploy_token_variables
@deploy_tokens = @group.deploy_tokens.active
@new_deploy_token = DeployToken.new
end
def authorize_admin_group! def authorize_admin_group!
return render_404 unless can?(current_user, :admin_group, group) return render_404 unless can?(current_user, :admin_group, group)
end end
@ -112,10 +77,6 @@ module Groups
def update_group_params def update_group_params
params.require(:group).permit(:max_artifacts_size) params.require(:group).permit(:max_artifacts_size)
end end
def deploy_token_params
params.require(:deploy_token).permit(:name, :expires_at, :read_repository, :read_registry, :write_registry, :username)
end
end end
end end
end end

View file

@ -0,0 +1,53 @@
# frozen_string_literal: true
module Groups
module Settings
class RepositoryController < Groups::ApplicationController
skip_cross_project_access_check :show
before_action :authorize_admin_group!
before_action :define_deploy_token_variables
before_action do
push_frontend_feature_flag(:ajax_new_deploy_token, @group)
end
def create_deploy_token
result = Groups::DeployTokens::CreateService.new(@group, current_user, deploy_token_params).execute
@new_deploy_token = result[:deploy_token]
if result[:status] == :success
respond_to do |format|
format.json do
# IMPORTANT: It's a security risk to expose the token value more than just once here!
json = API::Entities::DeployTokenWithToken.represent(@new_deploy_token).as_json
render json: json, status: result[:http_status]
end
format.html do
flash.now[:notice] = s_('DeployTokens|Your new group deploy token has been created.')
render :show
end
end
else
respond_to do |format|
format.json { render json: { message: result[:message] }, status: result[:http_status] }
format.html do
flash.now[:alert] = result[:message]
render :show
end
end
end
end
private
def define_deploy_token_variables
@deploy_tokens = @group.deploy_tokens.active
@new_deploy_token = DeployToken.new
end
def deploy_token_params
params.require(:deploy_token).permit(:name, :expires_at, :read_repository, :read_registry, :write_registry, :username)
end
end
end
end

View file

@ -7,6 +7,6 @@ class Projects::DeployTokensController < Projects::ApplicationController
@token = @project.deploy_tokens.find(params[:id]) @token = @project.deploy_tokens.find(params[:id])
@token.revoke! @token.revoke!
redirect_to project_settings_ci_cd_path(project, anchor: 'js-deploy-tokens') redirect_to project_settings_repository_path(project, anchor: 'js-deploy-tokens')
end end
end end

View file

@ -26,6 +26,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:code_navigation, @project) push_frontend_feature_flag(:code_navigation, @project)
push_frontend_feature_flag(:widget_visibility_polling, @project, default_enabled: true) push_frontend_feature_flag(:widget_visibility_polling, @project, default_enabled: true)
push_frontend_feature_flag(:merge_ref_head_comments, @project) push_frontend_feature_flag(:merge_ref_head_comments, @project)
push_frontend_feature_flag(:diff_compare_with_head, @project)
end end
before_action do before_action do

View file

@ -48,33 +48,6 @@ module Projects
redirect_to namespace_project_settings_ci_cd_path redirect_to namespace_project_settings_ci_cd_path
end end
def create_deploy_token
result = Projects::DeployTokens::CreateService.new(@project, current_user, deploy_token_params).execute
@new_deploy_token = result[:deploy_token]
if result[:status] == :success
respond_to do |format|
format.json do
# IMPORTANT: It's a security risk to expose the token value more than just once here!
json = API::Entities::DeployTokenWithToken.represent(@new_deploy_token).as_json
render json: json, status: result[:http_status]
end
format.html do
flash.now[:notice] = s_('DeployTokens|Your new project deploy token has been created.')
render :show
end
end
else
respond_to do |format|
format.json { render json: { message: result[:message] }, status: result[:http_status] }
format.html do
flash.now[:alert] = result[:message]
render :show
end
end
end
end
private private
def update_params def update_params
@ -93,10 +66,6 @@ module Projects
end end
end end
def deploy_token_params
params.require(:deploy_token).permit(:name, :expires_at, :read_repository, :read_registry, :write_registry, :username)
end
def run_autodevops_pipeline(service) def run_autodevops_pipeline(service)
return unless service.run_auto_devops_pipeline? return unless service.run_auto_devops_pipeline?
@ -116,7 +85,6 @@ module Projects
def define_variables def define_variables
define_runners_variables define_runners_variables
define_ci_variables define_ci_variables
define_deploy_token_variables
define_triggers_variables define_triggers_variables
define_badges_variables define_badges_variables
define_auto_devops_variables define_auto_devops_variables
@ -168,12 +136,6 @@ module Projects
@auto_devops = @project.auto_devops || ProjectAutoDevops.new @auto_devops = @project.auto_devops || ProjectAutoDevops.new
end end
def define_deploy_token_variables
@deploy_tokens = @project.deploy_tokens.active
@new_deploy_token = DeployToken.new
end
def define_deploy_keys def define_deploy_keys
@deploy_keys = DeployKeysPresenter.new(@project, current_user: current_user) @deploy_keys = DeployKeysPresenter.new(@project, current_user: current_user)
end end

View file

@ -4,7 +4,10 @@ module Projects
module Settings module Settings
class RepositoryController < Projects::ApplicationController class RepositoryController < Projects::ApplicationController
before_action :authorize_admin_project! before_action :authorize_admin_project!
before_action :remote_mirror, only: [:show] before_action :define_variables, only: [:create_deploy_token]
before_action do
push_frontend_feature_flag(:ajax_new_deploy_token, @project)
end
def show def show
render_show render_show
@ -24,15 +27,47 @@ module Projects
redirect_to project_settings_repository_path(project) redirect_to project_settings_repository_path(project)
end end
def create_deploy_token
result = Projects::DeployTokens::CreateService.new(@project, current_user, deploy_token_params).execute
@new_deploy_token = result[:deploy_token]
if result[:status] == :success
respond_to do |format|
format.json do
# IMPORTANT: It's a security risk to expose the token value more than just once here!
json = API::Entities::DeployTokenWithToken.represent(@new_deploy_token).as_json
render json: json, status: result[:http_status]
end
format.html do
flash.now[:notice] = s_('DeployTokens|Your new project deploy token has been created.')
render :show
end
end
else
respond_to do |format|
format.json { render json: { message: result[:message] }, status: result[:http_status] }
format.html do
flash.now[:alert] = result[:message]
render :show
end
end
end
end
private private
def render_show def render_show
define_protected_refs define_variables
remote_mirror
render 'show' render 'show'
end end
def define_variables
define_deploy_token_variables
define_protected_refs
remote_mirror
end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def define_protected_refs def define_protected_refs
@protected_branches = @project.protected_branches.order(:name).page(params[:page]) @protected_branches = @project.protected_branches.order(:name).page(params[:page])
@ -51,6 +86,10 @@ module Projects
@remote_mirror = project.remote_mirrors.first_or_initialize @remote_mirror = project.remote_mirrors.first_or_initialize
end end
def deploy_token_params
params.require(:deploy_token).permit(:name, :expires_at, :read_repository, :read_registry, :write_registry, :username)
end
def access_levels_options def access_levels_options
{ {
create_access_levels: levels_for_dropdown, create_access_levels: levels_for_dropdown,
@ -74,6 +113,12 @@ module Projects
{ open_branches: ProtectableDropdown.new(@project, :branches).hash } { open_branches: ProtectableDropdown.new(@project, :branches).hash }
end end
def define_deploy_token_variables
@deploy_tokens = @project.deploy_tokens.active
@new_deploy_token ||= DeployToken.new
end
def load_gon_index def load_gon_index
gon.push(protectable_tags_for_dropdown) gon.push(protectable_tags_for_dropdown)
gon.push(protectable_branches_for_dropdown) gon.push(protectable_branches_for_dropdown)

View file

@ -0,0 +1,69 @@
# frozen_string_literal: true
module Analytics
module NavbarHelper
class NavbarSubItem
attr_reader :title, :path, :link, :link_to_options
def initialize(title:, path:, link:, link_to_options: {})
@title = title
@path = path
@link = link
@link_to_options = link_to_options.merge(title: title)
end
end
def project_analytics_navbar_links(project, current_user)
[
cycle_analytics_navbar_link(project, current_user),
repository_analytics_navbar_link(project, current_user),
ci_cd_analytics_navbar_link(project, current_user)
].compact
end
def group_analytics_navbar_links(group, current_user)
[]
end
private
def navbar_sub_item(args)
NavbarSubItem.new(args)
end
def cycle_analytics_navbar_link(project, current_user)
return unless project_nav_tab?(:cycle_analytics)
navbar_sub_item(
title: _('Value Stream'),
path: 'cycle_analytics#show',
link: project_cycle_analytics_path(project),
link_to_options: { class: 'shortcuts-project-cycle-analytics' }
)
end
def repository_analytics_navbar_link(project, current_user)
return if project.empty_repo?
navbar_sub_item(
title: _('Repository'),
path: 'graphs#charts',
link: charts_project_graph_path(project, current_ref),
link_to_options: { class: 'shortcuts-repository-charts' }
)
end
def ci_cd_analytics_navbar_link(project, current_user)
return unless project_nav_tab?(:pipelines)
return unless project.feature_available?(:builds, current_user) || !project.empty_repo?
navbar_sub_item(
title: _('CI / CD'),
path: 'pipelines#charts',
link: charts_project_pipelines_path(project)
)
end
end
end
Analytics::NavbarHelper.prepend_if_ee('EE::Analytics::NavbarHelper')

View file

@ -1,67 +0,0 @@
# frozen_string_literal: true
module AnalyticsNavbarHelper
class NavbarSubItem
attr_reader :title, :path, :link, :link_to_options
def initialize(title:, path:, link:, link_to_options: {})
@title = title
@path = path
@link = link
@link_to_options = link_to_options.merge(title: title)
end
end
def project_analytics_navbar_links(project, current_user)
[
cycle_analytics_navbar_link(project, current_user),
repository_analytics_navbar_link(project, current_user),
ci_cd_analytics_navbar_link(project, current_user)
].compact
end
def group_analytics_navbar_links(group, current_user)
[]
end
private
def navbar_sub_item(args)
NavbarSubItem.new(args)
end
def cycle_analytics_navbar_link(project, current_user)
return unless project_nav_tab?(:cycle_analytics)
navbar_sub_item(
title: _('Value Stream'),
path: 'cycle_analytics#show',
link: project_cycle_analytics_path(project),
link_to_options: { class: 'shortcuts-project-cycle-analytics' }
)
end
def repository_analytics_navbar_link(project, current_user)
return if project.empty_repo?
navbar_sub_item(
title: _('Repository'),
path: 'graphs#charts',
link: charts_project_graph_path(project, current_ref),
link_to_options: { class: 'shortcuts-repository-charts' }
)
end
def ci_cd_analytics_navbar_link(project, current_user)
return unless project_nav_tab?(:pipelines)
return unless project.feature_available?(:builds, current_user) || !project.empty_repo?
navbar_sub_item(
title: _('CI / CD'),
path: 'pipelines#charts',
link: charts_project_pipelines_path(project)
)
end
end
AnalyticsNavbarHelper.prepend_if_ee('EE::AnalyticsNavbarHelper')

View file

@ -7,7 +7,7 @@ module CiVariablesHelper
def create_deploy_token_path(entity, opts = {}) def create_deploy_token_path(entity, opts = {})
if entity.is_a?(Group) if entity.is_a?(Group)
create_deploy_token_group_settings_ci_cd_path(entity, opts) create_deploy_token_group_settings_repository_path(entity, opts)
else else
# TODO: change this path to 'create_deploy_token_project_settings_ci_cd_path' # TODO: change this path to 'create_deploy_token_project_settings_ci_cd_path'
# See MR comment for more detail: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27059#note_311585356 # See MR comment for more detail: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27059#note_311585356

View file

@ -52,7 +52,7 @@ module ExploreHelper
end end
def public_visibility_restricted? def public_visibility_restricted?
Gitlab::CurrentSettings.restricted_visibility_levels.include? Gitlab::VisibilityLevel::PUBLIC Gitlab::CurrentSettings.restricted_visibility_levels&.include? Gitlab::VisibilityLevel::PUBLIC
end end
private private

View file

@ -15,6 +15,7 @@ module GroupsHelper
groups#projects groups#projects
groups#edit groups#edit
badges#index badges#index
repository#show
ci_cd#show ci_cd#show
integrations#index integrations#index
integrations#edit integrations#edit

View file

@ -163,7 +163,7 @@ class MergeRequest < ApplicationRecord
state_machine :merge_status, initial: :unchecked do state_machine :merge_status, initial: :unchecked do
event :mark_as_unchecked do event :mark_as_unchecked do
transition [:can_be_merged, :checking, :unchecked] => :unchecked transition [:can_be_merged, :checking, :unchecked] => :unchecked
transition [:cannot_be_merged, :cannot_be_merged_recheck] => :cannot_be_merged_recheck transition [:cannot_be_merged, :cannot_be_merged_rechecking, :cannot_be_merged_recheck] => :cannot_be_merged_recheck
end end
event :mark_as_checking do event :mark_as_checking do
@ -200,7 +200,7 @@ class MergeRequest < ApplicationRecord
# rubocop: enable CodeReuse/ServiceClass # rubocop: enable CodeReuse/ServiceClass
def check_state?(merge_status) def check_state?(merge_status)
[:unchecked, :cannot_be_merged_recheck, :checking].include?(merge_status.to_sym) [:unchecked, :cannot_be_merged_recheck, :checking, :cannot_be_merged_rechecking].include?(merge_status.to_sym)
end end
end end

View file

@ -2402,7 +2402,7 @@ class Project < ApplicationRecord
end end
def deploy_token_create_url(opts = {}) def deploy_token_create_url(opts = {})
Gitlab::Routing.url_helpers.create_deploy_token_project_settings_ci_cd_path(self, opts) Gitlab::Routing.url_helpers.create_deploy_token_project_settings_repository_path(self, opts)
end end
def deploy_token_revoke_url_for(token) def deploy_token_revoke_url_for(token)

View file

@ -3,7 +3,6 @@
- expanded = expanded_by_default? - expanded = expanded_by_default?
- general_expanded = @group.errors.empty? ? expanded : true - general_expanded = @group.errors.empty? ? expanded : true
- deploy_token_description = s_('DeployTokens|Group deploy tokens allow read-only access to the repositories and registry images within the group.')
-# Given we only have one field in this form which is also admin-only, -# Given we only have one field in this form which is also admin-only,
-# we don't want to show an empty section to non-admin users, -# we don't want to show an empty section to non-admin users,
@ -25,8 +24,6 @@
.settings-content .settings-content
= render 'ci/variables/index', save_endpoint: group_variables_path = render 'ci/variables/index', save_endpoint: group_variables_path
= render "shared/deploy_tokens/index", group_or_project: @group, description: deploy_token_description
%section.settings#runners-settings.no-animate{ class: ('expanded' if expanded) } %section.settings#runners-settings.no-animate{ class: ('expanded' if expanded) }
.settings-header .settings-header
%h4 %h4

View file

@ -0,0 +1,6 @@
- breadcrumb_title _('Repository Settings')
- page_title _('Repository')
- deploy_token_description = s_('DeployTokens|Group deploy tokens allow read-only access to the repositories and registry images within the group.')
= render "shared/deploy_tokens/index", group_or_project: @group, description: deploy_token_description

View file

@ -155,6 +155,11 @@
%span %span
= _('Projects') = _('Projects')
= nav_link(controller: :repository) do
= link_to group_settings_repository_path(@group), title: _('Repository') do
%span
= _('Repository')
= nav_link(controller: :ci_cd) do = nav_link(controller: :ci_cd) do
= link_to group_settings_ci_cd_path(@group), title: _('CI / CD') do = link_to group_settings_ci_cd_path(@group), title: _('CI / CD') do
%span %span

View file

@ -4,7 +4,6 @@
- expanded = expanded_by_default? - expanded = expanded_by_default?
- general_expanded = @project.errors.empty? ? expanded : true - general_expanded = @project.errors.empty? ? expanded : true
- deploy_token_description = s_('DeployTokens|Deploy tokens allow access to your repository and registry images.')
%section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if general_expanded) } %section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if general_expanded) }
.settings-header .settings-header
@ -52,8 +51,6 @@
.settings-content .settings-content
= render 'ci/variables/index', save_endpoint: project_variables_path(@project) = render 'ci/variables/index', save_endpoint: project_variables_path(@project)
= render "shared/deploy_tokens/index", group_or_project: @project, description: deploy_token_description
= render @deploy_keys = render @deploy_keys
%section.settings.no-animate#js-pipeline-triggers{ class: ('expanded' if expanded) } %section.settings.no-animate#js-pipeline-triggers{ class: ('expanded' if expanded) }

View file

@ -1,6 +1,7 @@
- breadcrumb_title _("Repository Settings") - breadcrumb_title _("Repository Settings")
- page_title _("Repository") - page_title _("Repository")
- @content_class = "limit-container-width" unless fluid_layout - @content_class = "limit-container-width" unless fluid_layout
- deploy_token_description = s_('DeployTokens|Deploy tokens allow access to your repository and registry images.')
= render "projects/default_branch/show" = render "projects/default_branch/show"
= render_if_exists "projects/push_rules/index" = render_if_exists "projects/push_rules/index"
@ -11,6 +12,7 @@
-# Those are used throughout the actual views. These `shared` views are then -# Those are used throughout the actual views. These `shared` views are then
-# reused in EE. -# reused in EE.
= render "projects/settings/repository/protected_branches" = render "projects/settings/repository/protected_branches"
= render "shared/deploy_tokens/index", group_or_project: @project, description: deploy_token_description
= render "projects/cleanup/show" = render "projects/cleanup/show"
= render_if_exists 'shared/promotions/promote_repository_features' = render_if_exists 'shared/promotions/promote_repository_features'

View file

@ -32,6 +32,10 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
resource :ci_cd, only: [:show, :update], controller: 'ci_cd' do resource :ci_cd, only: [:show, :update], controller: 'ci_cd' do
put :reset_registration_token put :reset_registration_token
patch :update_auto_devops patch :update_auto_devops
post :create_deploy_token, path: 'deploy_token/create', to: 'repository#create_deploy_token'
end
resource :repository, only: [:show], controller: 'repository' do
post :create_deploy_token, path: 'deploy_token/create' post :create_deploy_token, path: 'deploy_token/create'
end end

View file

@ -73,7 +73,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resource :ci_cd, only: [:show, :update], controller: 'ci_cd' do resource :ci_cd, only: [:show, :update], controller: 'ci_cd' do
post :reset_cache post :reset_cache
put :reset_registration_token put :reset_registration_token
post :create_deploy_token, path: 'deploy_token/create' post :create_deploy_token, path: 'deploy_token/create', to: 'repository#create_deploy_token'
end end
resource :operations, only: [:show, :update] do resource :operations, only: [:show, :update] do
@ -87,7 +87,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resource :repository, only: [:show], controller: :repository do resource :repository, only: [:show], controller: :repository do
# TODO: Removed this "create_deploy_token" route after change was made in app/helpers/ci_variables_helper.rb:14 # TODO: Removed this "create_deploy_token" route after change was made in app/helpers/ci_variables_helper.rb:14
# See MR comment for more detail: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27059#note_311585356 # See MR comment for more detail: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27059#note_311585356
post :create_deploy_token, path: 'deploy_token/create', to: 'ci_cd#create_deploy_token' post :create_deploy_token, path: 'deploy_token/create'
post :cleanup post :cleanup
end end
end end

View file

@ -0,0 +1,37 @@
# frozen_string_literal: true
class MigrateVulnerabilityDismissals < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
MIGRATION = 'UpdateVulnerabilitiesToDismissed'.freeze
BATCH_SIZE = 500
DELAY_INTERVAL = 2.minutes.to_i
class Vulnerability < ActiveRecord::Base
self.table_name = 'vulnerabilities'
self.inheritance_column = :_type_disabled
include ::EachBatch
end
def up
return unless Gitlab.ee?
Vulnerability.select('project_id').group(:project_id).each_batch(of: BATCH_SIZE, column: "project_id") do |project_batch, index|
batch_delay = (index - 1) * BATCH_SIZE * DELAY_INTERVAL
project_batch.each_with_index do |project, project_batch_index|
project_delay = project_batch_index * DELAY_INTERVAL
migrate_in(batch_delay + project_delay, MIGRATION, project[:project_id])
end
end
end
def down
# nothing to do
end
end

View file

@ -13211,6 +13211,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200415161021 20200415161021
20200415161206 20200415161206
20200415192656 20200415192656
20200416111111
20200416120128 20200416120128
20200416120354 20200416120354
\. \.

View file

@ -70,6 +70,7 @@ The following table depicts the various user permission levels in a project.
| Create confidential issue | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ | | Create confidential issue | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View confidential issues | (*2*) | ✓ | ✓ | ✓ | ✓ | | View confidential issues | (*2*) | ✓ | ✓ | ✓ | ✓ |
| View [Releases](project/releases/index.md) | ✓ (*6*) | ✓ | ✓ | ✓ | ✓ | | View [Releases](project/releases/index.md) | ✓ (*6*) | ✓ | ✓ | ✓ | ✓ |
| View requirements **(ULTIMATE)** | ✓ | ✓ | ✓ | ✓ | ✓ |
| Assign issues | | ✓ | ✓ | ✓ | ✓ | | Assign issues | | ✓ | ✓ | ✓ | ✓ |
| Label issues | | ✓ | ✓ | ✓ | ✓ | | Label issues | | ✓ | ✓ | ✓ | ✓ |
| Set issue weight | | ✓ | ✓ | ✓ | ✓ | | Set issue weight | | ✓ | ✓ | ✓ | ✓ |
@ -85,8 +86,8 @@ The following table depicts the various user permission levels in a project.
| View project statistics | | ✓ | ✓ | ✓ | ✓ | | View project statistics | | ✓ | ✓ | ✓ | ✓ |
| View Error Tracking list | | ✓ | ✓ | ✓ | ✓ | | View Error Tracking list | | ✓ | ✓ | ✓ | ✓ |
| Create new merge request | | ✓ | ✓ | ✓ | ✓ | | Create new merge request | | ✓ | ✓ | ✓ | ✓ |
| View requirements **(ULTIMATE)** | | ✓ | ✓ | ✓ | ✓ |
| View metrics dashboard annotations | | ✓ | ✓ | ✓ | ✓ | | View metrics dashboard annotations | | ✓ | ✓ | ✓ | ✓ |
| Create/edit requirements **(ULTIMATE)** | | ✓ | ✓ | ✓ | ✓ |
| Pull [packages](packages/index.md) | | ✓ | ✓ | ✓ | ✓ | | Pull [packages](packages/index.md) | | ✓ | ✓ | ✓ | ✓ |
| Publish [packages](packages/index.md) | | | ✓ | ✓ | ✓ | | Publish [packages](packages/index.md) | | | ✓ | ✓ | ✓ |
| Pull from [Conan repository](packages/conan_repository/index.md), [Maven repository](packages/maven_repository/index.md), or [NPM registry](packages/npm_registry/index.md) **(PREMIUM)** | | ✓ | ✓ | ✓ | ✓ | | Pull from [Conan repository](packages/conan_repository/index.md), [Maven repository](packages/maven_repository/index.md), or [NPM registry](packages/npm_registry/index.md) **(PREMIUM)** | | ✓ | ✓ | ✓ | ✓ |
@ -122,7 +123,6 @@ The following table depicts the various user permission levels in a project.
| Create and edit wiki pages | | | ✓ | ✓ | ✓ | | Create and edit wiki pages | | | ✓ | ✓ | ✓ |
| Rewrite/remove Git tags | | | ✓ | ✓ | ✓ | | Rewrite/remove Git tags | | | ✓ | ✓ | ✓ |
| Manage Feature Flags **(PREMIUM)** | | | ✓ | ✓ | ✓ | | Manage Feature Flags **(PREMIUM)** | | | ✓ | ✓ | ✓ |
| Manage requirements **(ULTIMATE)** | | | ✓ | ✓ | ✓ |
| Create/edit/delete metrics dashboard annotations | | | ✓ | ✓ | ✓ | | Create/edit/delete metrics dashboard annotations | | | ✓ | ✓ | ✓ |
| Use environment terminals | | | | ✓ | ✓ | | Use environment terminals | | | | ✓ | ✓ |
| Run Web IDE's Interactive Web Terminals **(ULTIMATE ONLY)** | | | | ✓ | ✓ | | Run Web IDE's Interactive Web Terminals **(ULTIMATE ONLY)** | | | | ✓ | ✓ |

View file

@ -67,6 +67,26 @@ current default comparison.
![Merge request versions compare HEAD](img/versions_compare_head_v12_10.png) ![Merge request versions compare HEAD](img/versions_compare_head_v12_10.png)
### Enable or disable `HEAD` comparison mode **(CORE ONLY)**
`HEAD` comparison mode is under development and not ready for production use. It is
deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/troubleshooting/navigating_gitlab_via_rails_console.md#starting-a-rails-console-session)
can enable it for your instance. You're welcome to test it, but use it at your
own risk.
To enable it:
```ruby
Feature.enable(:diff_compare_with_head)
```
To disable it:
```ruby
Feature.disable(:diff_compare_with_head)
```
<!-- ## Troubleshooting <!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues Include any troubleshooting steps that you can foresee. If you know beforehand what issues

View file

@ -9,7 +9,7 @@ module API
end end
expose :failed_relations, using: Entities::ProjectImportFailedRelation do |project, _options| expose :failed_relations, using: Entities::ProjectImportFailedRelation do |project, _options|
project.import_state.relation_hard_failures(limit: 100) project.import_state&.relation_hard_failures(limit: 100) || []
end end
# TODO: Use `expose_nil` once we upgrade the grape-entity gem # TODO: Use `expose_nil` once we upgrade the grape-entity gem

View file

@ -43,12 +43,9 @@ module API
Gitlab::Git::HookEnv.set(gl_repository, env) if container Gitlab::Git::HookEnv.set(gl_repository, env) if container
actor.update_last_used_at! actor.update_last_used_at!
access_checker = access_checker_for(actor, params[:protocol])
check_result = begin check_result = begin
result = access_checker.check(params[:action], params[:changes]) access_check!(actor, params)
@project ||= access_checker.project
result
rescue Gitlab::GitAccess::ForbiddenError => e rescue Gitlab::GitAccess::ForbiddenError => e
# The return code needs to be 401. If we return 403 # The return code needs to be 401. If we return 403
# the custom message we return won't be shown to the user # the custom message we return won't be shown to the user
@ -92,6 +89,17 @@ module API
response_with_status(code: 500, success: false, message: UNKNOWN_CHECK_RESULT_ERROR) response_with_status(code: 500, success: false, message: UNKNOWN_CHECK_RESULT_ERROR)
end end
end end
def access_check!(actor, params)
access_checker = access_checker_for(actor, params[:protocol])
access_checker.check(params[:action], params[:changes]).tap do |result|
break result if @project || !repo_type.project?
# If we have created a project directly from a git push
# we have to assign its value to both @project and @container
@project = @container = access_checker.project
end
end
end end
namespace 'internal' do namespace 'internal' do

View file

@ -0,0 +1,13 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# rubocop: disable Style/Documentation
class UpdateVulnerabilitiesToDismissed
def perform(project_id)
end
end
end
end
Gitlab::BackgroundMigration::UpdateVulnerabilitiesToDismissed.prepend_if_ee('EE::Gitlab::BackgroundMigration::UpdateVulnerabilitiesToDismissed')

View file

@ -13,20 +13,10 @@ module QA
element :variables_settings_content element :variables_settings_content
end end
view 'app/views/shared/deploy_tokens/_index.html.haml' do
element :deploy_tokens_settings
end
view 'app/views/projects/deploy_keys/_index.html.haml' do view 'app/views/projects/deploy_keys/_index.html.haml' do
element :deploy_keys_settings element :deploy_keys_settings
end end
def expand_deploy_tokens(&block)
expand_section(:deploy_tokens_settings) do
Settings::DeployTokens.perform(&block)
end
end
def expand_deploy_keys(&block) def expand_deploy_keys(&block)
expand_section(:deploy_keys_settings) do expand_section(:deploy_keys_settings) do
Settings::DeployKeys.perform(&block) Settings::DeployKeys.perform(&block)

View file

@ -15,6 +15,16 @@ module QA
element :mirroring_repositories_settings_section element :mirroring_repositories_settings_section
end end
view 'app/views/shared/deploy_tokens/_index.html.haml' do
element :deploy_tokens_settings
end
def expand_deploy_tokens(&block)
expand_section(:deploy_tokens_settings) do
Settings::DeployTokens.perform(&block)
end
end
def expand_protected_branches(&block) def expand_protected_branches(&block)
expand_section(:protected_branches_settings) do expand_section(:protected_branches_settings) do
ProtectedBranches.perform(&block) ProtectedBranches.perform(&block)

View file

@ -6,16 +6,16 @@ module QA
attr_accessor :name, :expires_at attr_accessor :name, :expires_at
attribute :username do attribute :username do
Page::Project::Settings::CICD.perform do |cicd_page| Page::Project::Settings::Repository.perform do |repository_page|
cicd_page.expand_deploy_tokens do |token| repository_page.expand_deploy_tokens do |token|
token.token_username token.token_username
end end
end end
end end
attribute :password do attribute :password do
Page::Project::Settings::CICD.perform do |cicd_page| Page::Project::Settings::Repository.perform do |repository_page|
cicd_page.expand_deploy_tokens do |token| repository_page.expand_deploy_tokens do |token|
token.token_password token.token_password
end end
end end
@ -31,10 +31,10 @@ module QA
def fabricate! def fabricate!
project.visit! project.visit!
Page::Project::Menu.perform(&:go_to_ci_cd_settings) Page::Project::Menu.perform(&:go_to_repository_settings)
Page::Project::Settings::CICD.perform do |cicd| Page::Project::Settings::Repository.perform do |setting|
cicd.expand_deploy_tokens do |page| setting.expand_deploy_tokens do |page|
page.fill_token_name(name) page.fill_token_name(name)
page.fill_token_expires_at(expires_at) page.fill_token_expires_at(expires_at)
page.fill_scopes(read_repository: true, read_registry: false) page.fill_scopes(read_repository: true, read_registry: false)

View file

@ -216,88 +216,4 @@ describe Groups::Settings::CiCdController do
end end
end end
end end
describe 'POST create_deploy_token' do
context 'when ajax_new_deploy_token feature flag is disabled for the project' do
before do
stub_feature_flags(ajax_new_deploy_token: { enabled: false, thing: group })
entity.add_owner(user)
end
it_behaves_like 'a created deploy token' do
let(:entity) { group }
let(:create_entity_params) { { group_id: group } }
let(:deploy_token_type) { DeployToken.deploy_token_types[:group_type] }
end
end
context 'when ajax_new_deploy_token feature flag is enabled for the project' do
let(:good_deploy_token_params) do
{
name: 'name',
expires_at: 1.day.from_now.to_s,
username: 'deployer',
read_repository: '1',
deploy_token_type: DeployToken.deploy_token_types[:group_type]
}
end
let(:request_params) do
{
group_id: group.to_param,
deploy_token: deploy_token_params
}
end
before do
group.add_owner(user)
end
subject { post :create_deploy_token, params: request_params, format: :json }
context('a good request') do
let(:deploy_token_params) { good_deploy_token_params }
let(:expected_response) do
{
'id' => be_a(Integer),
'name' => deploy_token_params[:name],
'username' => deploy_token_params[:username],
'expires_at' => Time.parse(deploy_token_params[:expires_at]),
'token' => be_a(String),
'scopes' => deploy_token_params.inject([]) do |scopes, kv|
key, value = kv
key.to_s.start_with?('read_') && !value.to_i.zero? ? scopes << key.to_s : scopes
end
}
end
it 'creates the deploy token' do
subject
expect(response).to have_gitlab_http_status(:created)
expect(response).to match_response_schema('public_api/v4/deploy_token')
expect(json_response).to match(expected_response)
end
end
context('a bad request') do
let(:deploy_token_params) { good_deploy_token_params.except(:read_repository) }
let(:expected_response) { { 'message' => "Scopes can't be blank" } }
it 'does not create the deploy token' do
subject
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response).to match(expected_response)
end
end
context('an invalid request') do
let(:deploy_token_params) { good_deploy_token_params.except(:name) }
it 'raises a validation error' do
expect { subject }.to raise_error(ActiveRecord::StatementInvalid)
end
end
end
end
end end

View file

@ -0,0 +1,98 @@
# frozen_string_literal: true
require 'spec_helper'
describe Groups::Settings::RepositoryController do
include ExternalAuthorizationServiceHelpers
let(:group) { create(:group) }
let(:user) { create(:user) }
before do
sign_in(user)
end
describe 'POST create_deploy_token' do
context 'when ajax_new_deploy_token feature flag is disabled for the project' do
before do
stub_feature_flags(ajax_new_deploy_token: { enabled: false, thing: group })
entity.add_owner(user)
end
it_behaves_like 'a created deploy token' do
let(:entity) { group }
let(:create_entity_params) { { group_id: group } }
let(:deploy_token_type) { DeployToken.deploy_token_types[:group_type] }
end
end
context 'when ajax_new_deploy_token feature flag is enabled for the project' do
let(:good_deploy_token_params) do
{
name: 'name',
expires_at: 1.day.from_now.to_s,
username: 'deployer',
read_repository: '1',
deploy_token_type: DeployToken.deploy_token_types[:group_type]
}
end
let(:request_params) do
{
group_id: group.to_param,
deploy_token: deploy_token_params
}
end
before do
group.add_owner(user)
end
subject { post :create_deploy_token, params: request_params, format: :json }
context('a good request') do
let(:deploy_token_params) { good_deploy_token_params }
let(:expected_response) do
{
'id' => be_a(Integer),
'name' => deploy_token_params[:name],
'username' => deploy_token_params[:username],
'expires_at' => Time.parse(deploy_token_params[:expires_at]),
'token' => be_a(String),
'scopes' => deploy_token_params.inject([]) do |scopes, kv|
key, value = kv
key.to_s.start_with?('read_') && !value.to_i.zero? ? scopes << key.to_s : scopes
end
}
end
it 'creates the deploy token' do
subject
expect(response).to have_gitlab_http_status(:created)
expect(response).to match_response_schema('public_api/v4/deploy_token')
expect(json_response).to match(expected_response)
end
end
context('a bad request') do
let(:deploy_token_params) { good_deploy_token_params.except(:read_repository) }
let(:expected_response) { { 'message' => "Scopes can't be blank" } }
it 'does not create the deploy token' do
subject
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response).to match(expected_response)
end
end
context('an invalid request') do
let(:deploy_token_params) { good_deploy_token_params.except(:name) }
it 'raises a validation error' do
expect { subject }.to raise_error(ActiveRecord::StatementInvalid)
end
end
end
end
end

View file

@ -266,84 +266,4 @@ describe Projects::Settings::CiCdController do
end end
end end
end end
describe 'POST create_deploy_token' do
context 'when ajax_new_deploy_token feature flag is disabled for the project' do
before do
stub_feature_flags(ajax_new_deploy_token: { enabled: false, thing: project })
end
it_behaves_like 'a created deploy token' do
let(:entity) { project }
let(:create_entity_params) { { namespace_id: project.namespace, project_id: project } }
let(:deploy_token_type) { DeployToken.deploy_token_types[:project_type] }
end
end
context 'when ajax_new_deploy_token feature flag is enabled for the project' do
let(:good_deploy_token_params) do
{
name: 'name',
expires_at: 1.day.from_now.to_s,
username: 'deployer',
read_repository: '1',
deploy_token_type: DeployToken.deploy_token_types[:project_type]
}
end
let(:request_params) do
{
namespace_id: project.namespace.to_param,
project_id: project.to_param,
deploy_token: deploy_token_params
}
end
subject { post :create_deploy_token, params: request_params, format: :json }
context('a good request') do
let(:deploy_token_params) { good_deploy_token_params }
let(:expected_response) do
{
'id' => be_a(Integer),
'name' => deploy_token_params[:name],
'username' => deploy_token_params[:username],
'expires_at' => Time.parse(deploy_token_params[:expires_at]),
'token' => be_a(String),
'scopes' => deploy_token_params.inject([]) do |scopes, kv|
key, value = kv
key.to_s.start_with?('read_') && !value.to_i.zero? ? scopes << key.to_s : scopes
end
}
end
it 'creates the deploy token' do
subject
expect(response).to have_gitlab_http_status(:created)
expect(response).to match_response_schema('public_api/v4/deploy_token')
expect(json_response).to match(expected_response)
end
end
context('a bad request') do
let(:deploy_token_params) { good_deploy_token_params.except(:read_repository) }
let(:expected_response) { { 'message' => "Scopes can't be blank" } }
it 'does not create the deploy token' do
subject
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response).to match(expected_response)
end
end
context('an invalid request') do
let(:deploy_token_params) { good_deploy_token_params.except(:name) }
it 'raises a validation error' do
expect { subject }.to raise_error(ActiveRecord::StatementInvalid)
end
end
end
end
end end

View file

@ -32,4 +32,84 @@ describe Projects::Settings::RepositoryController do
expect(RepositoryCleanupWorker).to have_received(:perform_async).once expect(RepositoryCleanupWorker).to have_received(:perform_async).once
end end
end end
describe 'POST create_deploy_token' do
context 'when ajax_new_deploy_token feature flag is disabled for the project' do
before do
stub_feature_flags(ajax_new_deploy_token: { enabled: false, thing: project })
end
it_behaves_like 'a created deploy token' do
let(:entity) { project }
let(:create_entity_params) { { namespace_id: project.namespace, project_id: project } }
let(:deploy_token_type) { DeployToken.deploy_token_types[:project_type] }
end
end
context 'when ajax_new_deploy_token feature flag is enabled for the project' do
let(:good_deploy_token_params) do
{
name: 'name',
expires_at: 1.day.from_now.to_s,
username: 'deployer',
read_repository: '1',
deploy_token_type: DeployToken.deploy_token_types[:project_type]
}
end
let(:request_params) do
{
namespace_id: project.namespace.to_param,
project_id: project.to_param,
deploy_token: deploy_token_params
}
end
subject { post :create_deploy_token, params: request_params, format: :json }
context('a good request') do
let(:deploy_token_params) { good_deploy_token_params }
let(:expected_response) do
{
'id' => be_a(Integer),
'name' => deploy_token_params[:name],
'username' => deploy_token_params[:username],
'expires_at' => Time.parse(deploy_token_params[:expires_at]),
'token' => be_a(String),
'scopes' => deploy_token_params.inject([]) do |scopes, kv|
key, value = kv
key.to_s.start_with?('read_') && !value.to_i.zero? ? scopes << key.to_s : scopes
end
}
end
it 'creates the deploy token' do
subject
expect(response).to have_gitlab_http_status(:created)
expect(response).to match_response_schema('public_api/v4/deploy_token')
expect(json_response).to match(expected_response)
end
end
context('a bad request') do
let(:deploy_token_params) { good_deploy_token_params.except(:read_repository) }
let(:expected_response) { { 'message' => "Scopes can't be blank" } }
it 'does not create the deploy token' do
subject
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response).to match(expected_response)
end
end
context('an invalid request') do
let(:deploy_token_params) { good_deploy_token_params.except(:name) }
it 'raises a validation error' do
expect { subject }.to raise_error(ActiveRecord::StatementInvalid)
end
end
end
end
end end

View file

@ -37,19 +37,6 @@ describe 'Group CI/CD settings' do
end end
end end
context 'Deploy tokens' do
let!(:deploy_token) { create(:deploy_token, :group, groups: [group]) }
before do
stub_container_registry_config(enabled: true)
visit group_settings_ci_cd_path(group)
end
it_behaves_like 'a deploy token in ci/cd settings' do
let(:entity_type) { 'group' }
end
end
describe 'Auto DevOps form' do describe 'Auto DevOps form' do
before do before do
stub_application_setting(auto_devops_enabled: true) stub_application_setting(auto_devops_enabled: true)

View file

@ -0,0 +1,28 @@
# frozen_string_literal: true
require 'spec_helper'
describe 'Group Repository settings' do
include WaitForRequests
let(:user) { create(:user) }
let(:group) { create(:group) }
before do
group.add_owner(user)
sign_in(user)
end
context 'Deploy tokens' do
let!(:deploy_token) { create(:deploy_token, :group, groups: [group]) }
before do
stub_container_registry_config(enabled: true)
visit group_settings_repository_path(group)
end
it_behaves_like 'a deploy token in settings' do
let(:entity_type) { 'group' }
end
end
end

View file

@ -30,4 +30,14 @@ describe 'IDE user commits changes', :js do
expect(project.repository.blob_at('master', 'foo/bar/.gitkeep')).to be_nil expect(project.repository.blob_at('master', 'foo/bar/.gitkeep')).to be_nil
expect(project.repository.blob_at('master', 'foo/bar/lorem_ipsum.md').data).to eql(content) expect(project.repository.blob_at('master', 'foo/bar/lorem_ipsum.md').data).to eql(content)
end end
it 'user adds then deletes new file' do
ide_create_new_file('foo/bar/lorem_ipsum.md')
expect(page).to have_selector(ide_commit_tab_selector)
ide_delete_file('foo/bar/lorem_ipsum.md')
expect(page).not_to have_selector(ide_commit_tab_selector)
end
end end

View file

@ -7,22 +7,6 @@ describe 'Projects > Settings > CI / CD settings' do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:role) { :maintainer } let_it_be(:role) { :maintainer }
context 'Deploy tokens' do
let!(:deploy_token) { create(:deploy_token, projects: [project]) }
before do
project.add_role(user, role)
sign_in(user)
stub_container_registry_config(enabled: true)
stub_feature_flags(ajax_new_deploy_token: { enabled: false, thing: project })
visit project_settings_ci_cd_path(project)
end
it_behaves_like 'a deploy token in ci/cd settings' do
let(:entity_type) { 'project' }
end
end
context 'Deploy Keys', :js do context 'Deploy Keys', :js do
let_it_be(:private_deploy_key) { create(:deploy_key, title: 'private_deploy_key', public: false) } let_it_be(:private_deploy_key) { create(:deploy_key, title: 'private_deploy_key', public: false) }
let_it_be(:public_deploy_key) { create(:another_deploy_key, title: 'public_deploy_key', public: true) } let_it_be(:public_deploy_key) { create(:another_deploy_key, title: 'public_deploy_key', public: true) }

View file

@ -25,6 +25,20 @@ describe 'Projects > Settings > Repository settings' do
context 'for maintainer' do context 'for maintainer' do
let(:role) { :maintainer } let(:role) { :maintainer }
context 'Deploy tokens' do
let!(:deploy_token) { create(:deploy_token, projects: [project]) }
before do
stub_container_registry_config(enabled: true)
stub_feature_flags(ajax_new_deploy_token: { enabled: false, thing: project })
visit project_settings_repository_path(project)
end
it_behaves_like 'a deploy token in settings' do
let(:entity_type) { 'project' }
end
end
context 'remote mirror settings' do context 'remote mirror settings' do
before do before do
visit project_settings_repository_path(project) visit project_settings_repository_path(project)

View file

@ -12,7 +12,7 @@ describe 'Repository Settings > User sees revoke deploy token modal', :js do
project.add_role(user, role) project.add_role(user, role)
sign_in(user) sign_in(user)
stub_feature_flags(ajax_new_deploy_token: { enabled: false, thing: project }) stub_feature_flags(ajax_new_deploy_token: { enabled: false, thing: project })
visit(project_settings_ci_cd_path(project)) visit(project_settings_repository_path(project))
click_link('Revoke') click_link('Revoke')
end end

View file

@ -18,6 +18,7 @@ describe('Compare diff version dropdowns', () => {
}; };
localState.targetBranchName = 'baseVersion'; localState.targetBranchName = 'baseVersion';
localState.mergeRequestDiffs = diffsMockData; localState.mergeRequestDiffs = diffsMockData;
gon.features = { diffCompareWithHead: true };
}); });
describe('selectedTargetIndex', () => { describe('selectedTargetIndex', () => {
@ -128,6 +129,14 @@ describe('Compare diff version dropdowns', () => {
}); });
assertVersions(targetVersions); assertVersions(targetVersions);
}); });
it('does not list head version if feature flag is not enabled', () => {
gon.features = { diffCompareWithHead: false };
setupTest();
const targetVersions = getters.diffCompareDropdownTargetVersions(localState, getters);
expect(targetVersions.find(version => version.isHead)).toBeUndefined();
});
}); });
it('diffCompareDropdownSourceVersions', () => { it('diffCompareDropdownSourceVersions', () => {

View file

@ -273,7 +273,7 @@ describe('Multi-file store mutations', () => {
expect(localState.changedFiles).toEqual([]); expect(localState.changedFiles).toEqual([]);
}); });
it('removes tempFile from changedFiles when deleted', () => { it('removes tempFile from changedFiles and stagedFiles when deleted', () => {
localState.entries.filePath = { localState.entries.filePath = {
path: 'filePath', path: 'filePath',
deleted: false, deleted: false,
@ -282,10 +282,12 @@ describe('Multi-file store mutations', () => {
}; };
localState.changedFiles.push({ ...localState.entries.filePath }); localState.changedFiles.push({ ...localState.entries.filePath });
localState.stagedFiles.push({ ...localState.entries.filePath });
mutations.DELETE_ENTRY(localState, 'filePath'); mutations.DELETE_ENTRY(localState, 'filePath');
expect(localState.changedFiles).toEqual([]); expect(localState.changedFiles).toEqual([]);
expect(localState.stagedFiles).toEqual([]);
}); });
it('bursts unused seal', () => { it('bursts unused seal', () => {

View file

@ -17,4 +17,25 @@ describe ExploreHelper do
expect(helper.explore_nav_links).to contain_exactly(*menu_items) expect(helper.explore_nav_links).to contain_exactly(*menu_items)
end end
end end
describe '#public_visibility_restricted?' do
using RSpec::Parameterized::TableSyntax
where(:visibility_levels, :expected_status) do
nil | nil
[Gitlab::VisibilityLevel::PRIVATE] | false
[Gitlab::VisibilityLevel::PRIVATE, Gitlab::VisibilityLevel::INTERNAL] | false
[Gitlab::VisibilityLevel::PUBLIC] | true
end
with_them do
before do
stub_application_setting(restricted_visibility_levels: visibility_levels)
end
it 'returns the expected status' do
expect(helper.public_visibility_restricted?).to eq(expected_status)
end
end
end
end end

View file

@ -8,6 +8,17 @@ describe API::Entities::ProjectImportStatus do
let(:correlation_id) { 'cid' } let(:correlation_id) { 'cid' }
context 'when no import state exists' do
let(:entity) { described_class.new(build(:project)) }
it 'includes basic fields and no failures' do
expect(subject[:import_status]).to eq('none')
expect(subject[:correlation_id]).to be_nil
expect(subject[:import_error]).to be_nil
expect(subject[:failed_relations]).to eq([])
end
end
context 'when import has not finished yet' do context 'when import has not finished yet' do
let(:project) { create(:project, :import_scheduled, import_correlation_id: correlation_id) } let(:project) { create(:project, :import_scheduled, import_correlation_id: correlation_id) }
let(:entity) { described_class.new(project) } let(:entity) { described_class.new(project) }

View file

@ -3332,7 +3332,7 @@ describe MergeRequest do
describe 'check_state?' do describe 'check_state?' do
it 'indicates whether MR is still checking for mergeability' do it 'indicates whether MR is still checking for mergeability' do
state_machine = described_class.state_machines[:merge_status] state_machine = described_class.state_machines[:merge_status]
check_states = [:unchecked, :cannot_be_merged_recheck, :checking] check_states = [:unchecked, :cannot_be_merged_recheck, :cannot_be_merged_rechecking, :checking]
check_states.each do |merge_status| check_states.each do |merge_status|
expect(state_machine.check_state?(merge_status)).to be true expect(state_machine.check_state?(merge_status)).to be true

View file

@ -766,29 +766,98 @@ describe API::Internal::Base do
end end
context 'project does not exist' do context 'project does not exist' do
it 'returns a 200 response with status: false' do context 'git pull' do
project.destroy it 'returns a 200 response with status: false' do
project.destroy
pull(key, project) pull(key, project)
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
expect(json_response["status"]).to be_falsey expect(json_response["status"]).to be_falsey
end
it 'returns a 200 response when using a project path that does not exist' do
post(
api("/internal/allowed"),
params: {
key_id: key.id,
project: 'project/does-not-exist.git',
action: 'git-upload-pack',
secret_token: secret_token,
protocol: 'ssh'
}
)
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response["status"]).to be_falsey
end
end end
it 'returns a 200 response when using a project path that does not exist' do context 'git push' do
post( before do
api("/internal/allowed"), stub_const('Gitlab::QueryLimiting::Transaction::THRESHOLD', 120)
params: { end
key_id: key.id,
project: 'project/does-not-exist.git',
action: 'git-upload-pack',
secret_token: secret_token,
protocol: 'ssh'
}
)
expect(response).to have_gitlab_http_status(:not_found) subject { push_with_path(key, full_path: path, changes: '_any') }
expect(json_response["status"]).to be_falsey
context 'from a user/group namespace' do
let!(:path) { "#{user.namespace.path}/notexist.git" }
it 'creates the project' do
expect do
subject
end.to change { Project.count }.by(1)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['status']).to be_truthy
end
end
context 'from the personal snippet path' do
let!(:path) { 'snippets/notexist.git' }
it 'does not create snippet' do
expect do
subject
end.not_to change { Snippet.count }
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'from a project path' do
context 'from an non existent project path' do
let!(:path) { "#{user.namespace.path}/notexist/snippets/notexist.git" }
it 'does not create project' do
expect do
subject
end.not_to change { Project.count }
expect(response).to have_gitlab_http_status(:not_found)
end
it 'does not create snippet' do
expect do
subject
end.not_to change { Snippet.count }
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'from an existent project path' do
let!(:path) { "#{project.full_path}/notexist/snippets/notexist.git" }
it 'does not create snippet' do
expect do
subject
end.not_to change { Snippet.count }
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end end
end end
@ -1062,18 +1131,27 @@ describe API::Internal::Base do
end end
def push(key, container, protocol = 'ssh', env: nil, changes: nil) def push(key, container, protocol = 'ssh', env: nil, changes: nil)
push_with_path(key,
full_path: full_path_for(container),
gl_repository: gl_repository_for(container),
protocol: protocol,
env: env,
changes: changes)
end
def push_with_path(key, full_path:, gl_repository: nil, protocol: 'ssh', env: nil, changes: nil)
changes ||= 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master' changes ||= 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master'
params = { params = {
changes: changes, changes: changes,
key_id: key.id, key_id: key.id,
project: full_path_for(container), project: full_path,
gl_repository: gl_repository_for(container),
action: 'git-receive-pack', action: 'git-receive-pack',
secret_token: secret_token, secret_token: secret_token,
protocol: protocol, protocol: protocol,
env: env env: env
} }
params[:gl_repository] = gl_repository if gl_repository
post( post(
api("/internal/allowed"), api("/internal/allowed"),

View file

@ -800,9 +800,8 @@ describe 'project routing' do
it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/settings/repository", "/gitlab/gitlabhq/-/settings/repository" it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/settings/repository", "/gitlab/gitlabhq/-/settings/repository"
# TODO: remove this test as part of https://gitlab.com/gitlab-org/gitlab/issues/207079 (12.9) it 'to repository#create_deploy_token' do
it 'to ci_cd#create_deploy_token' do expect(post('gitlab/gitlabhq/-/settings/ci_cd/deploy_token/create')).to route_to('projects/settings/repository#create_deploy_token', namespace_id: 'gitlab', project_id: 'gitlabhq')
expect(post('gitlab/gitlabhq/-/settings/ci_cd/deploy_token/create')).to route_to('projects/settings/ci_cd#create_deploy_token', namespace_id: 'gitlab', project_id: 'gitlabhq')
end end
end end

View file

@ -32,6 +32,10 @@ module WebIdeSpecHelpers
page.find('.ide-tree-actions') page.find('.ide-tree-actions')
end end
def ide_tab_selector(mode)
".js-ide-#{mode}-mode"
end
def ide_file_row_open?(row) def ide_file_row_open?(row)
row.matches_css?('.is-open') row.matches_css?('.is-open')
end end
@ -106,14 +110,14 @@ module WebIdeSpecHelpers
evaluate_script("monaco.editor.getModel('#{uri}').getValue()") evaluate_script("monaco.editor.getModel('#{uri}').getValue()")
end end
def ide_commit def ide_commit_tab_selector
ide_switch_mode('commit') ide_tab_selector('commit')
commit_to_current_branch
end end
def ide_switch_mode(mode) def ide_commit
find(".js-ide-#{mode}-mode").click find(ide_commit_tab_selector).click
commit_to_current_branch
end end
private private

View file

@ -111,6 +111,7 @@ RSpec.shared_context 'group navbar structure' do
nav_sub_items: [ nav_sub_items: [
_('General'), _('General'),
_('Projects'), _('Projects'),
_('Repository'),
_('CI / CD'), _('CI / CD'),
_('Integrations'), _('Integrations'),
_('Webhooks'), _('Webhooks'),

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.shared_examples 'a deploy token in ci/cd settings' do RSpec.shared_examples 'a deploy token in settings' do
it 'view deploy tokens' do it 'view deploy tokens' do
within('.deploy-tokens') do within('.deploy-tokens') do
expect(page).to have_content(deploy_token.name) expect(page).to have_content(deploy_token.name)