Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2019-10-14 15:06:07 +00:00
parent e464f195ff
commit 429d1abad2
38 changed files with 451 additions and 108 deletions

View File

@ -113,13 +113,21 @@ schedule:review-build-cng:
- install_api_client_dependencies_with_apk - install_api_client_dependencies_with_apk
- source scripts/review_apps/review-apps.sh - source scripts/review_apps/review-apps.sh
script: script:
- date
- check_kube_domain - check_kube_domain
- date
- ensure_namespace - ensure_namespace
- date
- install_tiller - install_tiller
- date
- install_external_dns - install_external_dns
- date
- download_chart - download_chart
- date
- deploy || (display_deployment_debug && exit 1) - deploy || (display_deployment_debug && exit 1)
- date
- add_license - add_license
- date
artifacts: artifacts:
paths: [review_app_url.txt] paths: [review_app_url.txt]
expire_in: 2 days expire_in: 2 days

View File

@ -95,7 +95,7 @@ GEM
babosa (1.0.2) babosa (1.0.2)
base32 (0.3.2) base32 (0.3.2)
batch-loader (1.4.0) batch-loader (1.4.0)
bcrypt (3.1.12) bcrypt (3.1.13)
bcrypt_pbkdf (1.0.0) bcrypt_pbkdf (1.0.0)
benchmark-ips (2.3.0) benchmark-ips (2.3.0)
benchmark-memory (0.1.2) benchmark-memory (0.1.2)
@ -209,10 +209,10 @@ GEM
descendants_tracker (0.0.4) descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
device_detector (1.0.0) device_detector (1.0.0)
devise (4.6.2) devise (4.7.1)
bcrypt (~> 3.0) bcrypt (~> 3.0)
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
railties (>= 4.1.0, < 6.0) railties (>= 4.1.0)
responders responders
warden (~> 1.2.3) warden (~> 1.2.3)
devise-two-factor (3.0.0) devise-two-factor (3.0.0)
@ -488,7 +488,7 @@ GEM
mime-types (~> 3.0) mime-types (~> 3.0)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
httpclient (2.8.3) httpclient (2.8.3)
i18n (1.6.0) i18n (1.7.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
i18n_data (0.8.0) i18n_data (0.8.0)
icalendar (2.4.1) icalendar (2.4.1)
@ -770,8 +770,8 @@ GEM
rails-dom-testing (2.0.3) rails-dom-testing (2.0.3)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
nokogiri (>= 1.6) nokogiri (>= 1.6)
rails-html-sanitizer (1.2.0) rails-html-sanitizer (1.3.0)
loofah (~> 2.2, >= 2.2.2) loofah (~> 2.3)
rails-i18n (5.1.1) rails-i18n (5.1.1)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
railties (>= 5.0, < 6) railties (>= 5.0, < 6)
@ -824,9 +824,9 @@ GEM
declarative-option (< 0.2.0) declarative-option (< 0.2.0)
uber (< 0.2.0) uber (< 0.2.0)
request_store (1.3.1) request_store (1.3.1)
responders (2.4.0) responders (2.4.1)
actionpack (>= 4.2.0, < 5.3) actionpack (>= 4.2.0, < 6.0)
railties (>= 4.2.0, < 5.3) railties (>= 4.2.0, < 6.0)
rest-client (2.0.2) rest-client (2.0.2)
http-cookie (>= 1.0.2, < 2.0) http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 4.0) mime-types (>= 1.16, < 4.0)
@ -1058,8 +1058,8 @@ GEM
descendants_tracker (~> 0.0, >= 0.0.3) descendants_tracker (~> 0.0, >= 0.0.3)
equalizer (~> 0.0, >= 0.0.9) equalizer (~> 0.0, >= 0.0.9)
vmstat (2.3.0) vmstat (2.3.0)
warden (1.2.7) warden (1.2.8)
rack (>= 1.0) rack (>= 2.0.6)
webfinger (1.1.0) webfinger (1.1.0)
activesupport activesupport
httpclient (>= 2.4) httpclient (>= 2.4)

View File

@ -7,10 +7,10 @@ import { __ } from '~/locale';
export default { export default {
fields: [ fields: [
{ key: 'error', label: __('Open errors') }, { key: 'error', label: __('Open errors'), thClass: 'w-70p' },
{ key: 'events', label: __('Events') }, { key: 'events', label: __('Events') },
{ key: 'users', label: __('Users') }, { key: 'users', label: __('Users') },
{ key: 'lastSeen', label: __('Last seen') }, { key: 'lastSeen', label: __('Last seen'), thClass: 'w-15p' },
], ],
components: { components: {
GlEmptyState, GlEmptyState,
@ -67,40 +67,39 @@ export default {
<div class="d-flex justify-content-end"> <div class="d-flex justify-content-end">
<gl-button class="my-3 ml-auto" variant="primary" :href="externalUrl" target="_blank"> <gl-button class="my-3 ml-auto" variant="primary" :href="externalUrl" target="_blank">
{{ __('View in Sentry') }} {{ __('View in Sentry') }}
<icon name="external-link" /> <icon name="external-link" class="flex-shrink-0" />
</gl-button> </gl-button>
</div> </div>
<gl-table :items="errors" :fields="$options.fields" :show-empty="true">
<gl-table :items="errors" :fields="$options.fields" :show-empty="true" fixed stacked="sm">
<template slot="HEAD_events" slot-scope="data"> <template slot="HEAD_events" slot-scope="data">
<div class="text-right">{{ data.label }}</div> <div class="text-md-right">{{ data.label }}</div>
</template> </template>
<template slot="HEAD_users" slot-scope="data"> <template slot="HEAD_users" slot-scope="data">
<div class="text-right">{{ data.label }}</div> <div class="text-md-right">{{ data.label }}</div>
</template> </template>
<template slot="error" slot-scope="errors"> <template slot="error" slot-scope="errors">
<div class="d-flex flex-column"> <div class="d-flex flex-column">
<div class="d-flex"> <gl-link :href="errors.item.externalUrl" class="d-flex text-dark" target="_blank">
<gl-link :href="errors.item.externalUrl" class="d-flex text-dark" target="_blank"> <strong class="text-truncate">{{ errors.item.title.trim() }}</strong>
<strong>{{ errors.item.title.trim() }}</strong> <icon name="external-link" class="ml-1 flex-shrink-0" />
<icon name="external-link" class="ml-1" /> </gl-link>
</gl-link> <span class="text-secondary text-truncate">
<span class="text-secondary ml-2">{{ errors.item.culprit }}</span> {{ errors.item.culprit }}
</div> </span>
{{ errors.item.message || __('No details available') }}
</div> </div>
</template> </template>
<template slot="events" slot-scope="errors"> <template slot="events" slot-scope="errors">
<div class="text-right">{{ errors.item.count }}</div> <div class="text-md-right">{{ errors.item.count }}</div>
</template> </template>
<template slot="users" slot-scope="errors"> <template slot="users" slot-scope="errors">
<div class="text-right">{{ errors.item.userCount }}</div> <div class="text-md-right">{{ errors.item.userCount }}</div>
</template> </template>
<template slot="lastSeen" slot-scope="errors"> <template slot="lastSeen" slot-scope="errors">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<icon name="calendar" class="text-secondary mr-1" />
<time-ago :time="errors.item.lastSeen" class="text-secondary" /> <time-ago :time="errors.item.lastSeen" class="text-secondary" />
</div> </div>
</template> </template>

View File

@ -32,6 +32,7 @@ export default class LabelsSelect {
$selectbox, $selectbox,
$sidebarCollapsedValue, $sidebarCollapsedValue,
$value, $value,
$dropdownMenu,
abilityName, abilityName,
defaultLabel, defaultLabel,
issueUpdateURL, issueUpdateURL,
@ -67,6 +68,7 @@ export default class LabelsSelect {
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span'); $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span');
$sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip'); $sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip');
$value = $block.find('.value'); $value = $block.find('.value');
$dropdownMenu = $dropdown.parent().find('.dropdown-menu');
$loading = $block.find('.block-loading').fadeOut(); $loading = $block.find('.block-loading').fadeOut();
fieldName = $dropdown.data('fieldName'); fieldName = $dropdown.data('fieldName');
initialSelected = $selectbox initialSelected = $selectbox
@ -454,9 +456,21 @@ export default class LabelsSelect {
} }
$loading.fadeIn(); $loading.fadeIn();
const oldLabels = boardsStore.detail.issue.labels;
boardsStore.detail.issue boardsStore.detail.issue
.update($dropdown.attr('data-issue-update')) .update($dropdown.attr('data-issue-update'))
.then(() => {
if (isScopedLabel(label)) {
const prevIds = oldLabels.map(label => label.id);
const newIds = boardsStore.detail.issue.labels.map(label => label.id);
const differentIds = _.difference(prevIds, newIds);
$dropdown.data('marked', newIds);
$dropdownMenu
.find(differentIds.map(id => `[data-label-id="${id}"]`).join(','))
.removeClass('is-active');
}
})
.then(fadeOutLoader) .then(fadeOutLoader)
.catch(fadeOutLoader); .catch(fadeOutLoader);
} else if (handleClick) { } else if (handleClick) {

View File

@ -1,25 +1,30 @@
import { __ } from '~/locale'; import { __ } from '~/locale';
export default () => { export default () => {
const { protocol, host, pathname } = window.location;
const shareBtn = document.querySelector('.js-share-btn'); const shareBtn = document.querySelector('.js-share-btn');
const embedBtn = document.querySelector('.js-embed-btn');
const snippetUrlArea = document.querySelector('.js-snippet-url-area');
const embedAction = document.querySelector('.js-embed-action');
const url = `${protocol}//${host + pathname}`;
shareBtn.addEventListener('click', () => { if (shareBtn) {
shareBtn.classList.add('is-active'); const { protocol, host, pathname } = window.location;
embedBtn.classList.remove('is-active');
snippetUrlArea.value = url;
embedAction.innerText = __('Share');
});
embedBtn.addEventListener('click', () => { const embedBtn = document.querySelector('.js-embed-btn');
embedBtn.classList.add('is-active');
shareBtn.classList.remove('is-active'); const snippetUrlArea = document.querySelector('.js-snippet-url-area');
const scriptTag = `<script src="${url}.js"></script>`; const embedAction = document.querySelector('.js-embed-action');
snippetUrlArea.value = scriptTag; const url = `${protocol}//${host + pathname}`;
embedAction.innerText = __('Embed');
}); shareBtn.addEventListener('click', () => {
shareBtn.classList.add('is-active');
embedBtn.classList.remove('is-active');
snippetUrlArea.value = url;
embedAction.innerText = __('Share');
});
embedBtn.addEventListener('click', () => {
embedBtn.classList.add('is-active');
shareBtn.classList.remove('is-active');
const scriptTag = `<script src="${url}.js"></script>`;
snippetUrlArea.value = scriptTag;
embedAction.innerText = __('Embed');
});
}
}; };

View File

@ -452,6 +452,8 @@ img.emoji {
.w-0 { width: 0; } .w-0 { width: 0; }
.w-8em { width: 8em; } .w-8em { width: 8em; }
.w-3rem { width: 3rem; } .w-3rem { width: 3rem; }
.w-15p { width: 15%; }
.w-70p { width: 70%; }
.h-12em { height: 12em; } .h-12em { height: 12em; }
.h-32-px { height: 32px;} .h-32-px { height: 32px;}

View File

@ -29,13 +29,15 @@ module UploadsActions
def show def show
return render_404 unless uploader&.exists? return render_404 unless uploader&.exists?
if cache_publicly? # We need to reset caching from the applications controller to get rid of the no-store value
# We need to reset caching from the applications controller to get rid of the no-store value headers['Cache-Control'] = ''
headers['Cache-Control'] = '' headers['Pragma'] = ''
expires_in 5.minutes, public: true, must_revalidate: false
else ttl, directives = *cache_settings
expires_in 0.seconds, must_revalidate: true, private: true ttl ||= 6.months
end directives ||= { private: true, must_revalidate: true }
expires_in ttl, directives
disposition = uploader.embeddable? ? 'inline' : 'attachment' disposition = uploader.embeddable? ? 'inline' : 'attachment'
@ -120,8 +122,8 @@ module UploadsActions
nil nil
end end
def cache_publicly? def cache_settings
false []
end end
def model def model

View File

@ -5,11 +5,22 @@ module Groups
class CiCdController < Groups::ApplicationController class CiCdController < Groups::ApplicationController
skip_cross_project_access_check :show skip_cross_project_access_check :show
before_action :authorize_admin_group! before_action :authorize_admin_group!
before_action :authorize_update_max_artifacts_size!, only: [:update]
def show def show
define_ci_variables define_ci_variables
end end
def update
if update_group_service.execute
flash[:notice] = s_('GroupSettings|Pipeline settings was updated for the group')
else
flash[:alert] = s_("GroupSettings|There was a problem updating the pipeline settings: %{error_messages}." % { error_messages: group.errors.full_messages })
end
redirect_to group_settings_ci_cd_path
end
def reset_registration_token def reset_registration_token
@group.reset_runners_token! @group.reset_runners_token!
@ -40,6 +51,10 @@ module Groups
return render_404 unless can?(current_user, :admin_group, group) return render_404 unless can?(current_user, :admin_group, group)
end end
def authorize_update_max_artifacts_size!
return render_404 unless can?(current_user, :update_max_artifacts_size, group)
end
def auto_devops_params def auto_devops_params
params.require(:group).permit(:auto_devops_enabled) params.require(:group).permit(:auto_devops_enabled)
end end
@ -47,6 +62,14 @@ module Groups
def auto_devops_service def auto_devops_service
Groups::AutoDevopsService.new(group, current_user, auto_devops_params) Groups::AutoDevopsService.new(group, current_user, auto_devops_params)
end end
def update_group_service
Groups::UpdateService.new(group, current_user, update_group_params)
end
def update_group_params
params.require(:group).permit(:max_artifacts_size)
end
end end
end end
end end

View File

@ -46,13 +46,19 @@ module Projects
private private
def update_params def update_params
params.require(:project).permit( params.require(:project).permit(*permitted_project_params)
end
def permitted_project_params
[
:runners_token, :builds_enabled, :build_allow_git_fetch, :runners_token, :builds_enabled, :build_allow_git_fetch,
:build_timeout_human_readable, :build_coverage_regex, :public_builds, :build_timeout_human_readable, :build_coverage_regex, :public_builds,
:auto_cancel_pending_pipelines, :ci_config_path, :auto_cancel_pending_pipelines, :ci_config_path,
auto_devops_attributes: [:id, :domain, :enabled, :deploy_strategy], auto_devops_attributes: [:id, :domain, :enabled, :deploy_strategy],
ci_cd_settings_attributes: [:default_git_depth] ci_cd_settings_attributes: [:default_git_depth]
) ].tap do |list|
list << :max_artifacts_size if can?(current_user, :update_max_artifacts_size, project)
end
end end
def run_autodevops_pipeline(service) def run_autodevops_pipeline(service)

View File

@ -81,8 +81,13 @@ class UploadsController < ApplicationController
end end
end end
def cache_publicly? def cache_settings
User === model || Appearance === model case model
when User, Appearance
[5.minutes, { public: true, must_revalidate: false }]
when Project, Group
[5.minutes, { private: true, must_revalidate: true }]
end
end end
def secret? def secret?

View File

@ -53,7 +53,10 @@ class GroupPolicy < BasePolicy
enable :upload_file enable :upload_file
end end
rule { admin }.enable :read_group rule { admin }.policy do
enable :read_group
enable :update_max_artifacts_size
end
rule { has_projects }.policy do rule { has_projects }.policy do
enable :read_group enable :read_group

View File

@ -137,6 +137,8 @@ class ProjectPolicy < BasePolicy
# not. # not.
rule { guest | admin }.enable :read_project_for_iids rule { guest | admin }.enable :read_project_for_iids
rule { admin }.enable :update_max_artifacts_size
rule { guest }.enable :guest_access rule { guest }.enable :guest_access
rule { reporter }.enable :reporter_access rule { reporter }.enable :reporter_access
rule { developer }.enable :developer_access rule { developer }.enable :developer_access

View File

@ -34,7 +34,7 @@
= f.number_field :max_artifacts_size, class: 'form-control' = f.number_field :max_artifacts_size, class: 'form-control'
.form-text.text-muted .form-text.text-muted
= _("Set the maximum file size for each job's artifacts") = _("Set the maximum file size for each job's artifacts")
= link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'maximum-artifacts-size') = link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'maximum-artifacts-size-core-only')
.form-group .form-group
= f.label :default_artifacts_expire_in, _('Default artifacts expiration'), class: 'label-bold' = f.label :default_artifacts_expire_in, _('Default artifacts expiration'), class: 'label-bold'
= f.text_field :default_artifacts_expire_in, class: 'form-control' = f.text_field :default_artifacts_expire_in, class: 'form-control'

View File

@ -30,6 +30,6 @@
= f.check_box :lets_encrypt_terms_of_service_accepted, class: 'form-check-input' = f.check_box :lets_encrypt_terms_of_service_accepted, class: 'form-check-input'
= f.label :lets_encrypt_terms_of_service_accepted, class: 'form-check-label' do = f.label :lets_encrypt_terms_of_service_accepted, class: 'form-check-label' do
- terms_of_service_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: lets_encrypt_terms_of_service_admin_application_settings_path } - terms_of_service_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: lets_encrypt_terms_of_service_admin_application_settings_path }
= _("I have read and agree to the Let's Encrypt %{link_start}Terms of Service%{link_end}").html_safe % { link_start: terms_of_service_link_start, link_end: '</a>'.html_safe } = _("I have read and agree to the Let's Encrypt %{link_start}Terms of Service%{link_end} (PDF)").html_safe % { link_start: terms_of_service_link_start, link_end: '</a>'.html_safe }
= f.submit _('Save changes'), class: "btn btn-success" = f.submit _('Save changes'), class: "btn btn-success"

View File

@ -0,0 +1,13 @@
.row.prepend-top-default
.col-lg-12
= form_for group, url: group_settings_ci_cd_path(group, anchor: 'js-general-pipeline-settings') do |f|
= form_errors(group)
%fieldset.builds-feature
.form-group
= f.label :max_artifacts_size, _('Maximum artifacts size (MB)'), class: 'label-bold'
= f.number_field :max_artifacts_size, class: 'form-control'
%p.form-text.text-muted
= _("Set the maximum file size for each job's artifacts")
= link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'maximum-artifacts-size-core-only'), target: '_blank'
= f.submit _('Save changes'), class: "btn btn-success"

View File

@ -2,6 +2,21 @@
- page_title "CI / CD" - page_title "CI / CD"
- expanded = expanded_by_default? - expanded = expanded_by_default?
- general_expanded = @group.errors.empty? ? expanded : true
-# 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,
- if can?(current_user, :update_max_artifacts_size, @group)
%section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if general_expanded) }
.settings-header
%h4
= _("General pipelines")
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _("Customize your pipeline configuration.")
.settings-content
= render 'groups/settings/ci_cd/form', group: @group
%section.settings#ci-variables.no-animate{ class: ('expanded' if expanded) } %section.settings#ci-variables.no-animate{ class: ('expanded' if expanded) }
.settings-header .settings-header

View File

@ -40,6 +40,15 @@
= _('If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like "1 hour". Values without specification represent seconds.') = _('If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like "1 hour". Values without specification represent seconds.')
= link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'timeout'), target: '_blank' = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'timeout'), target: '_blank'
- if can?(current_user, :update_max_artifacts_size, @project)
%hr
.form-group
= f.label :max_artifacts_size, _('Maximum artifacts size (MB)'), class: 'label-bold'
= f.number_field :max_artifacts_size, class: 'form-control'
%p.form-text.text-muted
= _("Set the maximum file size for each job's artifacts")
= link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'maximum-artifacts-size-core-only'), target: '_blank'
%hr %hr
.form-group .form-group
= f.label :ci_config_path, _('Custom CI config path'), class: 'label-bold' = f.label :ci_config_path, _('Custom CI config path'), class: 'label-bold'

View File

@ -1,4 +1,4 @@
# frozen_string_literal: false # frozen_string_literal: true
class MigrateExternalDiffsWorker class MigrateExternalDiffsWorker
include ApplicationWorker include ApplicationWorker

View File

@ -1,4 +1,4 @@
# frozen_string_literal: false # frozen_string_literal: true
class ScheduleMigrateExternalDiffsWorker class ScheduleMigrateExternalDiffsWorker
include ApplicationWorker include ApplicationWorker

View File

@ -1,4 +1,3 @@
# frozen_string_literal: true # frozen_string_literal: true
# Worker for updating project statistics. # Worker for updating project statistics.

View File

@ -0,0 +1,5 @@
---
title: Add max_artifacts_size fields under project and group settings.
merge_request: 18286
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Fix error tracking table layout on small screens
merge_request: 18325
author:
type: fixed

View File

@ -30,7 +30,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
as: :group, as: :group,
constraints: { group_id: Gitlab::PathRegex.full_namespace_route_regex }) do constraints: { group_id: Gitlab::PathRegex.full_namespace_route_regex }) do
namespace :settings do namespace :settings do
resource :ci_cd, only: [:show], 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
end end

View File

@ -68,7 +68,7 @@ Omnibus:
gitaly['enable'] = false gitaly['enable'] = false
redis['bind'] = '0.0.0.0' redis['bind'] = '0.0.0.0'
redis['port'] = '6379' redis['port'] = 6379
redis['password'] = 'SECRET_PASSWORD_HERE' redis['password'] = 'SECRET_PASSWORD_HERE'
gitlab_rails['auto_migrate'] = false gitlab_rails['auto_migrate'] = false

View File

@ -99,6 +99,7 @@ description: 'Learn how to contribute to GitLab.'
- [Post deployment migrations](post_deployment_migrations.md) - [Post deployment migrations](post_deployment_migrations.md)
- [Background migrations](background_migrations.md) - [Background migrations](background_migrations.md)
- [Swapping tables](swapping_tables.md) - [Swapping tables](swapping_tables.md)
- [Deleting exiting migrations](deleting_migrations.md)
### Best practices ### Best practices
@ -118,7 +119,7 @@ description: 'Learn how to contribute to GitLab.'
- [Database helper modules](database_helpers.md) - [Database helper modules](database_helpers.md)
- [Code comments](code_comments.md) - [Code comments](code_comments.md)
## Case studies ### Case studies
- [Database case study: Filtering by label](filtering_by_label.md) - [Database case study: Filtering by label](filtering_by_label.md)
- [Database case study: Namespaces storage statistics](namespaces_storage_statistics.md) - [Database case study: Namespaces storage statistics](namespaces_storage_statistics.md)

View File

@ -0,0 +1,33 @@
# Delete existing migrations
When removing existing migrations from the GitLab project, you have to take into account
the possibility of the migration already been included in past releases or in the current release, and thus already executed on GitLab.com and/or in self-hosted instances.
Because of it, it's not possible to delete existing migrations, as that could lead to:
- Schema inconsistency, as changes introduced into the database were not rollbacked properly.
- Leaving a record on the `schema_versions` table, that points out to migration that no longer exists on the codebase.
Instead of deleting we can opt for disabling the migration.
## Pre-requisites to disable a migration
Migrations can be disabled if:
- They caused a timeout or general issue on GitLab.com.
- They are obsoleted, e.g. changes are not necessary due to a feature change.
- Migration is a data migration only, i.e. the migration does not change the database schema.
## How to disable a data migration?
In order to disable a migration, the following steps apply to all types of migrations:
1. Turn the migration into a noop by removing the code inside `#up`, `#down`
or `#perform` methods, and adding `#no-op` comment instead.
1. Add a comment explaining why the code is gone.
Disabling migrations requires explicit approval of Database Maintainer.
## Examples
- [Disable scheduling of productivity analytics](https://gitlab.com/gitlab-org/gitlab/merge_requests/17253)

View File

@ -29,15 +29,27 @@ If you want to disable it for a specific project, you can do so in
## Maximum artifacts size **(CORE ONLY)** ## Maximum artifacts size **(CORE ONLY)**
The maximum size of the [job artifacts](../../../administration/job_artifacts.md) The maximum size of the [job artifacts](../../../administration/job_artifacts.md)
can be set in the Admin area of your GitLab instance. The value is in *MB* and can be set at the project level, group level, and at the instance level. The value is in *MB* and
the default is 100MB per job; on GitLab.com it's [set to 1G](../../gitlab_com/index.md#gitlab-cicd). the default is 100MB per job; on GitLab.com it's [set to 1G](../../gitlab_com/index.md#gitlab-cicd).
To change it: To change it at the instance level:
1. Go to **Admin area > Settings > Continuous Integration and Deployment**. 1. Go to **Admin area > Settings > Continuous Integration and Deployment**.
1. Change the value of maximum artifacts size (in MB). 1. Change the value of maximum artifacts size (in MB).
1. Hit **Save changes** for the changes to take effect. 1. Hit **Save changes** for the changes to take effect.
at the group level (this will override the instance setting):
1. Go to **Group > Settings > CI / CD > General Pipelines**.
1. Change the value of maximum artifacts size (in MB).
1. Hit **Save changes** for the changes to take effect.
at the project level (this will override the instance and group settings):
1. Go to **Project > Settings > CI / CD > General Pipelines**.
1. Change the value of maximum artifacts size (in MB).
1. Hit **Save changes** for the changes to take effect.
## Default artifacts expiration **(CORE ONLY)** ## Default artifacts expiration **(CORE ONLY)**
The default expiration time of the [job artifacts](../../../administration/job_artifacts.md) The default expiration time of the [job artifacts](../../../administration/job_artifacts.md)

View File

@ -4026,6 +4026,9 @@ msgstr ""
msgid "Code owners" msgid "Code owners"
msgstr "" msgstr ""
msgid "CodeAnalytics|Max files"
msgstr ""
msgid "CodeOwner|Pattern" msgid "CodeOwner|Pattern"
msgstr "" msgstr ""
@ -4840,6 +4843,9 @@ msgstr ""
msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr "" msgstr ""
msgid "Customize your pipeline configuration."
msgstr ""
msgid "Cycle Analytics" msgid "Cycle Analytics"
msgstr "" msgstr ""
@ -8222,6 +8228,9 @@ msgstr ""
msgid "GroupSettings|New runners registration token has been generated!" msgid "GroupSettings|New runners registration token has been generated!"
msgstr "" msgstr ""
msgid "GroupSettings|Pipeline settings was updated for the group"
msgstr ""
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr "" msgstr ""
@ -8234,6 +8243,9 @@ msgstr ""
msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}." msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
msgstr "" msgstr ""
msgid "GroupSettings|There was a problem updating the pipeline settings: %{error_messages}."
msgstr ""
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup." msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr "" msgstr ""
@ -8479,7 +8491,7 @@ msgstr ""
msgid "I forgot my password" msgid "I forgot my password"
msgstr "" msgstr ""
msgid "I have read and agree to the Let's Encrypt %{link_start}Terms of Service%{link_end}" msgid "I have read and agree to the Let's Encrypt %{link_start}Terms of Service%{link_end} (PDF)"
msgstr "" msgstr ""
msgid "I'd like to receive updates via email about GitLab" msgid "I'd like to receive updates via email about GitLab"
@ -10758,9 +10770,6 @@ msgstr ""
msgid "No deployments found" msgid "No deployments found"
msgstr "" msgstr ""
msgid "No details available"
msgstr ""
msgid "No due date" msgid "No due date"
msgstr "" msgstr ""

View File

@ -156,4 +156,58 @@ describe Groups::Settings::CiCdController do
end end
end end
end end
describe 'PATCH #update' do
subject do
patch :update, params: {
group_id: group,
group: { max_artifacts_size: 10 }
}
end
context 'when user is not an admin' do
before do
group.add_owner(user)
end
it { is_expected.to have_gitlab_http_status(404) }
end
context 'when user is an admin' do
let(:user) { create(:admin) }
before do
group.add_owner(user)
end
it { is_expected.to redirect_to(group_settings_ci_cd_path) }
context 'when service execution went wrong' do
let(:update_service) { double }
before do
allow(Groups::UpdateService).to receive(:new).and_return(update_service)
allow(update_service).to receive(:execute).and_return(false)
allow_any_instance_of(Group).to receive_message_chain(:errors, :full_messages)
.and_return(['Error 1'])
subject
end
it 'returns a flash alert' do
expect(response).to set_flash[:alert]
.to eq("There was a problem updating the pipeline settings: [\"Error 1\"].")
end
end
context 'when service execution was successful' do
it 'returns a flash notice' do
subject
expect(response).to set_flash[:notice]
.to eq('Pipeline settings was updated for the group')
end
end
end
end
end end

View File

@ -215,6 +215,30 @@ describe Projects::Settings::CiCdController do
expect(project.ci_default_git_depth).to eq(10) expect(project.ci_default_git_depth).to eq(10)
end end
end end
context 'when max_artifacts_size is specified' do
let(:params) { { max_artifacts_size: 10 } }
context 'and user is not an admin' do
it 'does not set max_artifacts_size' do
subject
project.reload
expect(project.max_artifacts_size).to be_nil
end
end
context 'and user is an admin' do
let(:user) { create(:admin) }
it 'sets max_artifacts_size' do
subject
project.reload
expect(project.max_artifacts_size).to eq(10)
end
end
end
end end
end end
end end

View File

@ -1,16 +1,15 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
shared_examples 'content not cached without revalidation' do shared_examples 'content 5 min private cached with revalidation' do
it 'ensures content will not be cached without revalidation' do it 'ensures content will not be cached without revalidation' do
expect(subject['Cache-Control']).to eq('max-age=0, private, must-revalidate') expect(subject['Cache-Control']).to eq('max-age=300, private, must-revalidate')
end end
end end
shared_examples 'content not cached without revalidation and no-store' do shared_examples 'content long term private cached with revalidation' do
it 'ensures content will not be cached without revalidation' do it 'ensures content will not be cached without revalidation' do
# Fixed in newer versions of ActivePack, it will only output a single `private`. expect(subject['Cache-Control']).to eq('max-age=15778476, private, must-revalidate')
expect(subject['Cache-Control']).to eq('max-age=0, private, must-revalidate, no-store')
end end
end end
@ -285,7 +284,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
it_behaves_like 'content not cached without revalidation' do it_behaves_like 'content 5 min private cached with revalidation' do
subject do subject do
get :show, params: { model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png' } get :show, params: { model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png' }
@ -305,7 +304,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
it_behaves_like 'content not cached without revalidation and no-store' do it_behaves_like 'content 5 min private cached with revalidation' do
subject do subject do
get :show, params: { model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png' } get :show, params: { model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png' }
@ -358,7 +357,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
it_behaves_like 'content not cached without revalidation and no-store' do it_behaves_like 'content 5 min private cached with revalidation' do
subject do subject do
get :show, params: { model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png' } get :show, params: { model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png' }
@ -390,7 +389,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
it_behaves_like 'content not cached without revalidation' do it_behaves_like 'content 5 min private cached with revalidation' do
subject do subject do
get :show, params: { model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png' } get :show, params: { model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png' }
@ -410,7 +409,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
it_behaves_like 'content not cached without revalidation and no-store' do it_behaves_like 'content 5 min private cached with revalidation' do
subject do subject do
get :show, params: { model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png' } get :show, params: { model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png' }
@ -454,7 +453,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
it_behaves_like 'content not cached without revalidation and no-store' do it_behaves_like 'content 5 min private cached with revalidation' do
subject do subject do
get :show, params: { model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png' } get :show, params: { model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png' }
@ -491,7 +490,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
it_behaves_like 'content not cached without revalidation' do it_behaves_like 'content long term private cached with revalidation' do
subject do subject do
get :show, params: { model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png' } get :show, params: { model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png' }
@ -511,7 +510,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
it_behaves_like 'content not cached without revalidation and no-store' do it_behaves_like 'content long term private cached with revalidation' do
subject do subject do
get :show, params: { model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png' } get :show, params: { model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png' }
@ -564,7 +563,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
it_behaves_like 'content not cached without revalidation and no-store' do it_behaves_like 'content long term private cached with revalidation' do
subject do subject do
get :show, params: { model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png' } get :show, params: { model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png' }

View File

@ -14,6 +14,8 @@ describe 'Issue Boards', :js do
let!(:bug) { create(:label, project: project, name: 'Bug') } let!(:bug) { create(:label, project: project, name: 'Bug') }
let!(:regression) { create(:label, project: project, name: 'Regression') } let!(:regression) { create(:label, project: project, name: 'Regression') }
let!(:stretch) { create(:label, project: project, name: 'Stretch') } let!(:stretch) { create(:label, project: project, name: 'Stretch') }
let!(:scoped_label_1) { create(:label, project: project, name: 'Scoped::Label1') }
let!(:scoped_label_2) { create(:label, project: project, name: 'Scoped::Label2') }
let!(:issue1) { create(:labeled_issue, project: project, assignees: [user], milestone: milestone, labels: [development], relative_position: 2) } let!(:issue1) { create(:labeled_issue, project: project, assignees: [user], milestone: milestone, labels: [development], relative_position: 2) }
let!(:issue2) { create(:labeled_issue, project: project, labels: [development, stretch], relative_position: 1) } let!(:issue2) { create(:labeled_issue, project: project, labels: [development, stretch], relative_position: 1) }
let(:board) { create(:board, project: project) } let(:board) { create(:board, project: project) }
@ -27,6 +29,8 @@ describe 'Issue Boards', :js do
end end
before do before do
stub_licensed_features(scoped_labels: true)
project.add_maintainer(user) project.add_maintainer(user)
sign_in(user) sign_in(user)
@ -309,6 +313,33 @@ describe 'Issue Boards', :js do
expect(card).to have_content(bug.title) expect(card).to have_content(bug.title)
end end
it 'removes existing scoped label' do
click_card(card)
page.within('.labels') do
click_link 'Edit'
wait_for_requests
click_link scoped_label_1.title
click_link scoped_label_2.title
wait_for_requests
find('.dropdown-menu-close-icon').click
page.within('.value') do
expect(page).to have_selector('.badge', count: 3)
expect(page).not_to have_content(scoped_label_1.title)
expect(page).to have_content(scoped_label_2.title)
end
end
expect(card).to have_selector('.badge', count: 3)
expect(card).not_to have_content(scoped_label_1.title)
expect(card).to have_content(scoped_label_2.title)
end
it 'adds a multiple labels' do it 'adds a multiple labels' do
click_card(card) click_card(card)

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
require 'spec_helper'
describe 'Private Snippets', :js do
let(:user) { create(:user) }
before do
sign_in(user)
end
it 'Private Snippet renders for creator' do
private_snippet = create(:personal_snippet, :private, author: user)
visit snippet_path(private_snippet)
wait_for_requests
expect(page).to have_content(private_snippet.content)
expect(page).not_to have_css('.js-embed-btn')
expect(page).not_to have_css('.js-share-btn')
end
end

View File

@ -10,6 +10,8 @@ describe 'Public Snippets', :js do
wait_for_requests wait_for_requests
expect(page).to have_content(public_snippet.content) expect(page).to have_content(public_snippet.content)
expect(page).to have_css('.js-embed-btn', visible: false)
expect(page).to have_css('.js-share-btn', visible: false)
end end
it 'Unauthenticated user should see raw public snippets' do it 'Unauthenticated user should see raw public snippets' do

View File

@ -45,7 +45,9 @@ describe 'User creates snippet', :js do
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
expect(link).to match(%r{/uploads/-/system/user/#{user.id}/\h{32}/banana_sample\.gif\z}) expect(link).to match(%r{/uploads/-/system/user/#{user.id}/\h{32}/banana_sample\.gif\z})
reqs = inspect_requests { visit(link) } # Adds a cache buster for checking if the image exists as Selenium is now handling the cached regquests
# not anymore as requests when they come straight from memory cache.
reqs = inspect_requests { visit("#{link}?ran=#{SecureRandom.base64(20)}") }
expect(reqs.first.status_code).to eq(200) expect(reqs.first.status_code).to eq(200)
end end
end end
@ -63,7 +65,7 @@ describe 'User creates snippet', :js do
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
expect(link).to match(%r{/uploads/-/system/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z}) expect(link).to match(%r{/uploads/-/system/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z})
reqs = inspect_requests { visit(link) } reqs = inspect_requests { visit("#{link}?ran=#{SecureRandom.base64(20)}") }
expect(reqs.first.status_code).to eq(200) expect(reqs.first.status_code).to eq(200)
end end
@ -88,7 +90,7 @@ describe 'User creates snippet', :js do
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
expect(link).to match(%r{/uploads/-/system/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z}) expect(link).to match(%r{/uploads/-/system/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z})
reqs = inspect_requests { visit(link) } reqs = inspect_requests { visit("#{link}?ran=#{SecureRandom.base64(20)}") }
expect(reqs.first.status_code).to eq(200) expect(reqs.first.status_code).to eq(200)
end end

View File

@ -547,4 +547,28 @@ describe GroupPolicy do
groups: [clusterable]) groups: [clusterable])
end end
end end
describe 'update_max_artifacts_size' do
let(:group) { create(:group, :public) }
context 'when no user' do
let(:current_user) { nil }
it { expect_disallowed(:update_max_artifacts_size) }
end
context 'admin' do
let(:current_user) { admin }
it { expect_allowed(:update_max_artifacts_size) }
end
%w(guest reporter developer maintainer owner).each do |role|
context role do
let(:current_user) { send(role) }
it { expect_disallowed(:update_max_artifacts_size) }
end
end
end
end end

View File

@ -478,4 +478,28 @@ describe ProjectPolicy do
end end
end end
end end
describe 'update_max_artifacts_size' do
subject { described_class.new(current_user, project) }
context 'when no user' do
let(:current_user) { nil }
it { expect_disallowed(:update_max_artifacts_size) }
end
context 'admin' do
let(:current_user) { admin }
it { expect_allowed(:update_max_artifacts_size) }
end
%w(guest reporter developer maintainer owner).each do |role|
context role do
let(:current_user) { send(role) }
it { expect_disallowed(:update_max_artifacts_size) }
end
end
end
end end

View File

@ -148,34 +148,25 @@ describe 'OpenID Connect requests' do
end end
end end
# These 2 calls shouldn't actually throw, they should be handled as an
# unauthorized request, so we should be able to check the response.
#
# This was not possible due to an issue with Warden:
# https://github.com/hassox/warden/pull/162
#
# When the patch gets merged and we update Warden, these specs will need to
# updated to check the response instead of a raised exception.
# https://gitlab.com/gitlab-org/gitlab-foss/issues/40218
context 'when user is blocked' do context 'when user is blocked' do
it 'returns authentication error' do it 'redirects to login page' do
access_grant access_grant
user.block! user.block!
expect do request_access_token!
request_access_token!
end.to raise_error UncaughtThrowError expect(response).to redirect_to('/users/sign_in')
end end
end end
context 'when user is ldap_blocked' do context 'when user is ldap_blocked' do
it 'returns authentication error' do it 'redirects to login page' do
access_grant access_grant
user.ldap_block! user.ldap_block!
expect do request_access_token!
request_access_token!
end.to raise_error UncaughtThrowError expect(response).to redirect_to('/users/sign_in')
end end
end end
end end