Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-03-03 15:10:53 +00:00
parent b5820a6bcd
commit 7fcda12793
74 changed files with 911 additions and 230 deletions

View File

@ -74,6 +74,9 @@ gem 'acme-client', '~> 2.0', '>= 2.0.6'
# Browser detection
gem 'browser', '~> 4.2'
# OS detection for usage ping
gem 'ohai', '~> 16.10'
# GPG
gem 'gpgme', '~> 2.0.19'

View File

@ -172,6 +172,14 @@ GEM
cbor (0.5.9.6)
character_set (1.4.0)
charlock_holmes (0.7.7)
chef-config (16.10.17)
addressable
chef-utils (= 16.10.17)
fuzzyurl
mixlib-config (>= 2.2.12, < 4.0)
mixlib-shellout (>= 2.0, < 4.0)
tomlrb (~> 1.2)
chef-utils (16.10.17)
childprocess (3.0.0)
chunky_png (1.3.5)
citrus (3.0.2)
@ -348,6 +356,8 @@ GEM
ffi-compiler (1.0.1)
ffi (>= 1.0.0)
rake
ffi-yajl (2.3.4)
libyajl2 (~> 1.2)
flipper (0.17.1)
flipper-active_record (0.17.1)
activerecord (>= 4.2, < 7)
@ -403,6 +413,7 @@ GEM
fuubar (2.2.0)
rspec-core (~> 3.0)
ruby-progressbar (~> 1.4)
fuzzyurl (0.9.0)
gemoji (3.0.1)
gemojione (3.3.0)
json
@ -668,6 +679,7 @@ GEM
actionmailer (>= 3.2)
letter_opener (~> 1.0)
railties (>= 3.2)
libyajl2 (1.2.0)
license_finder (6.0.0)
bundler
rubyzip (>= 1, < 3)
@ -717,6 +729,12 @@ GEM
mini_mime (1.0.2)
mini_portile2 (2.5.0)
minitest (5.11.3)
mixlib-cli (2.1.8)
mixlib-config (3.0.9)
tomlrb
mixlib-log (3.0.9)
mixlib-shellout (3.2.5)
chef-utils
ms_rest (0.7.6)
concurrent-ruby (~> 1.0)
faraday (>= 0.9, < 2.0.0)
@ -741,6 +759,8 @@ GEM
connection_pool (~> 2.2)
net-ldap (0.16.3)
net-ntp (2.1.3)
net-scp (3.0.0)
net-ssh (>= 2.6.5, < 7.0.0)
net-ssh (6.0.0)
netrc (0.11.0)
nio4r (2.5.4)
@ -764,6 +784,19 @@ GEM
octokit (4.20.0)
faraday (>= 0.9)
sawyer (~> 0.8.0, >= 0.5.3)
ohai (16.10.6)
chef-config (>= 12.8, < 17)
chef-utils (>= 16.0, < 17)
ffi (~> 1.9)
ffi-yajl (~> 2.2)
ipaddress
mixlib-cli (>= 1.7.0)
mixlib-config (>= 2.0, < 4.0)
mixlib-log (>= 2.0.1, < 4.0)
mixlib-shellout (>= 2.0, < 4.0)
plist (~> 3.1)
train-core
wmi-lite (~> 1.0)
oj (3.10.6)
omniauth (1.9.0)
hashie (>= 3.4.6, < 3.7.0)
@ -857,6 +890,7 @@ GEM
railties (>= 4.0.0)
pg (1.2.3)
pg_query (1.3.0)
plist (3.6.0)
png_quantizator (0.2.1)
po_to_json (1.0.1)
json (>= 1.6.0)
@ -1196,9 +1230,17 @@ GEM
parslet (~> 1.8.0)
toml-rb (1.0.0)
citrus (~> 3.0, > 3.0)
tomlrb (1.3.0)
tpm-key_attestation (0.9.0)
bindata (~> 2.4)
openssl-signature_algorithm (~> 0.4.0)
train-core (3.4.9)
addressable (~> 2.5)
ffi (!= 1.13.0)
json (>= 1.8, < 3.0)
mixlib-shellout (>= 2.0, < 4.0)
net-scp (>= 1.2, < 4.0)
net-ssh (>= 2.9, < 7.0)
truncato (0.7.11)
htmlentities (~> 4.3.1)
nokogiri (>= 1.7.0, <= 2.0)
@ -1271,6 +1313,7 @@ GEM
expression_parser
rinku
with_env (1.1.0)
wmi-lite (1.0.5)
xml-simple (1.1.5)
xpath (3.2.0)
nokogiri (~> 1.8)
@ -1443,6 +1486,7 @@ DEPENDENCIES
nokogiri (~> 1.11.1)
oauth2 (~> 1.4)
octokit (~> 4.15)
ohai (~> 16.10)
oj (~> 3.10.6)
omniauth (~> 1.8)
omniauth-atlassian-oauth2 (~> 0.2.0)

View File

@ -10,7 +10,7 @@ import {
} from '@gitlab/ui';
import { mapActions, mapState } from 'vuex';
import { isListDraggable } from '~/boards/boards_util';
import { isScopedLabel } from '~/lib/utils/common_utils';
import { isScopedLabel, parseBoolean } from '~/lib/utils/common_utils';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import { n__, s__, __ } from '~/locale';
import sidebarEventHub from '~/sidebar/event_hub';
@ -69,7 +69,7 @@ export default {
},
},
computed: {
...mapState(['activeId']),
...mapState(['activeId', 'isEpicBoard']),
isLoggedIn() {
return Boolean(this.currentUserId);
},
@ -97,11 +97,14 @@ export default {
showListDetails() {
return !this.list.collapsed || !this.isSwimlanesHeader;
},
issuesCount() {
itemsCount() {
return this.list.issuesCount;
},
issuesTooltipLabel() {
return n__(`%d issue`, `%d issues`, this.issuesCount);
countIcon() {
return 'issues';
},
itemsTooltipLabel() {
return n__(`%d issue`, `%d issues`, this.itemsCount);
},
chevronTooltip() {
return this.list.collapsed ? this.$options.i18n.expand : this.$options.i18n.collapse;
@ -110,7 +113,7 @@ export default {
return this.list.collapsed ? 'chevron-down' : 'chevron-right';
},
isNewIssueShown() {
return this.listType === ListType.backlog || this.showListHeaderButton;
return (this.listType === ListType.backlog || this.showListHeaderButton) && !this.isEpicBoard;
},
isSettingsShown() {
return (
@ -131,8 +134,14 @@ export default {
return !this.disabled && isListDraggable(this.list);
},
},
created() {
const localCollapsed = parseBoolean(localStorage.getItem(`${this.uniqueKey}.collapsed`));
if ((!this.isLoggedIn || this.isEpicBoard) && localCollapsed) {
this.toggleListCollapsed({ listId: this.list.id, collapsed: true });
}
},
methods: {
...mapActions(['updateList', 'setActiveId']),
...mapActions(['updateList', 'setActiveId', 'toggleListCollapsed']),
openSidebarSettings() {
if (this.activeId === inactiveId) {
sidebarEventHub.$emit('sidebar.closeAll');
@ -148,10 +157,10 @@ export default {
eventHub.$emit(`toggle-issue-form-${this.list.id}`);
},
toggleExpanded() {
// eslint-disable-next-line vue/no-mutating-props
this.list.collapsed = !this.list.collapsed;
const collapsed = !this.list.collapsed;
this.toggleListCollapsed({ listId: this.list.id, collapsed });
if (!this.isLoggedIn) {
if (!this.isLoggedIn || this.isEpicBoard) {
this.addToLocalStorage();
} else {
this.updateListFunction();
@ -163,7 +172,7 @@ export default {
},
addToLocalStorage() {
if (AccessorUtilities.isLocalStorageAccessSafe()) {
localStorage.setItem(`${this.uniqueKey}.expanded`, !this.list.collapsed);
localStorage.setItem(`${this.uniqueKey}.collapsed`, this.list.collapsed);
}
},
updateListFunction() {
@ -203,6 +212,7 @@ export default {
class="board-title-caret no-drag gl-cursor-pointer"
category="tertiary"
size="small"
data-testid="board-title-caret"
@click="toggleExpanded"
/>
<!-- EE start -->
@ -301,11 +311,11 @@ export default {
<div v-if="list.maxIssueCount !== 0">
<gl-sprintf :message="__('%{issuesSize} with a limit of %{maxIssueCount}')">
<template #issuesSize>{{ issuesTooltipLabel }}</template>
<template #issuesSize>{{ itemsTooltipLabel }}</template>
<template #maxIssueCount>{{ list.maxIssueCount }}</template>
</gl-sprintf>
</div>
<div v-else> {{ issuesTooltipLabel }}</div>
<div v-else> {{ itemsTooltipLabel }}</div>
<div v-if="weightFeatureAvailable">
<gl-sprintf :message="__('%{totalWeight} total weight')">
@ -323,13 +333,13 @@ export default {
}"
>
<span class="gl-display-inline-flex">
<gl-tooltip :target="() => $refs.issueCount" :title="issuesTooltipLabel" />
<span ref="issueCount" class="issue-count-badge-count">
<gl-icon class="gl-mr-2" name="issues" />
<issue-count :issues-size="issuesCount" :max-issue-count="list.maxIssueCount" />
<gl-tooltip :target="() => $refs.itemCount" :title="itemsTooltipLabel" />
<span ref="itemCount" class="issue-count-badge-count">
<gl-icon class="gl-mr-2" :name="countIcon" />
<issue-count :issues-size="itemsCount" :max-issue-count="list.maxIssueCount" />
</span>
<!-- EE start -->
<template v-if="weightFeatureAvailable">
<template v-if="weightFeatureAvailable && !isEpicBoard">
<gl-tooltip :target="() => $refs.weightTooltip" :title="weightCountToolTip" />
<span ref="weightTooltip" class="gl-display-inline-flex gl-ml-3">
<gl-icon class="gl-mr-2" name="weight" />

View File

@ -86,7 +86,11 @@ export default {
<template>
<span>
<span ref="issueDueDate" :class="cssClass" class="board-card-info card-number">
<gl-icon :class="{ 'text-danger': isPastDue }" class="board-card-info-icon" name="calendar" />
<gl-icon
:class="{ 'text-danger': isPastDue }"
class="board-card-info-icon gl-mr-2"
name="calendar"
/>
<time :class="{ 'text-danger': isPastDue }" datetime="date" class="board-card-info-text">{{
body
}}</time>

View File

@ -37,7 +37,7 @@ export default {
<template>
<span>
<span ref="issueTimeEstimate" class="board-card-info card-number">
<gl-icon name="hourglass" class="board-card-info-icon" />
<gl-icon name="hourglass" class="board-card-info-icon gl-mr-2" />
<time class="board-card-info-text">{{ timeEstimate }}</time>
</span>
<gl-tooltip

View File

@ -256,6 +256,10 @@ export default {
});
},
toggleListCollapsed: ({ commit }, { listId, collapsed }) => {
commit(types.TOGGLE_LIST_COLLAPSED, { listId, collapsed });
},
removeList: ({ state, commit }, listId) => {
const listsBackup = { ...state.boardLists };

View File

@ -14,6 +14,7 @@ export const RECEIVE_ADD_LIST_SUCCESS = 'RECEIVE_ADD_LIST_SUCCESS';
export const RECEIVE_ADD_LIST_ERROR = 'RECEIVE_ADD_LIST_ERROR';
export const MOVE_LIST = 'MOVE_LIST';
export const UPDATE_LIST_FAILURE = 'UPDATE_LIST_FAILURE';
export const TOGGLE_LIST_COLLAPSED = 'TOGGLE_LIST_COLLAPSED';
export const REMOVE_LIST = 'REMOVE_LIST';
export const REMOVE_LIST_FAILURE = 'REMOVE_LIST_FAILURE';
export const REQUEST_ITEMS_FOR_LIST = 'REQUEST_ITEMS_FOR_LIST';

View File

@ -105,6 +105,10 @@ export default {
Vue.set(state, 'boardLists', backupList);
},
[mutationTypes.TOGGLE_LIST_COLLAPSED]: (state, { listId, collapsed }) => {
Vue.set(state.boardLists[listId], 'collapsed', collapsed);
},
[mutationTypes.REMOVE_LIST]: (state, listId) => {
Vue.delete(state.boardLists, listId);
},

View File

@ -200,9 +200,9 @@ export default {
<gl-form-input id="fork-name" v-model="fork.name" data-testid="fork-name-input" required />
</gl-form-group>
<div class="gl-display-flex">
<div class="gl-w-half">
<gl-form-group label="Project URL" label-for="fork-url" class="gl-pr-2">
<div class="gl-md-display-flex">
<div class="gl-flex-basis-half">
<gl-form-group label="Project URL" label-for="fork-url" class="gl-md-mr-3">
<gl-form-input-group>
<template #prepend>
<gl-input-group-text>
@ -225,8 +225,8 @@ export default {
</gl-form-input-group>
</gl-form-group>
</div>
<div class="gl-w-half">
<gl-form-group label="Project slug" label-for="fork-slug" class="gl-pl-2">
<div class="gl-flex-basis-half">
<gl-form-group label="Project slug" label-for="fork-slug" class="gl-md-ml-3">
<gl-form-input
id="fork-slug"
v-model="fork.slug"

View File

@ -55,7 +55,12 @@ export default {
>
<div class="d-flex align-items-center">
<gl-icon :size="16" :name="option.icon" />
<span class="font-weight-bold ml-1 js-visibility-option">{{ option.label }}</span>
<span
class="font-weight-bold ml-1 js-visibility-option"
data-qa-selector="visibility_content"
:data-qa-visibility="option.label"
>{{ option.label }}</span
>
</div>
<template #help>{{
isProjectSnippet && option.description_project

View File

@ -35,5 +35,4 @@
@import './pages/sherlock';
@import './pages/storage_quota';
@import './pages/tree';
@import './pages/trials';
@import './pages/users';

View File

@ -830,8 +830,8 @@ $ci-variable-remove-button-width: calc(1em + #{2 * $gl-padding});
/*
GitLab Plans
*/
$gl-gold-plan: #d4af37;
$gl-silver-plan: #91a1ab;
$gl-ultimate-plan: #d4af37;
$gl-premium-plan: #91a1ab;
$gl-bronze-plan: #cd7f32;
/*

View File

@ -339,3 +339,19 @@
display: inline;
}
}
.gl-select2-html5-required-fix {
.select2-container {
+ .select2 {
@include gl-opacity-0;
@include gl-border-0;
@include gl-bg-none;
@include gl-bg-transparent;
display: block !important;
width: 1px;
height: 1px;
z-index: -1;
margin: -3px auto 0;
}
}
}

View File

@ -1,15 +0,0 @@
/*
* A CSS cross-browser fix for Select2 failire to display HTML5 required warnings
* MR link https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22716
*/
.gl-select2-html5-required-fix div.select2-container+select.select2 {
@include gl-opacity-0;
@include gl-border-0;
@include gl-bg-none;
@include gl-bg-transparent;
display: block !important;
width: 1px;
height: 1px;
z-index: -1;
margin: -3px auto 0;
}

View File

@ -117,6 +117,7 @@
flex-basis: 25%;
}
// Will be moved to @gitlab/ui in https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1168
.gl-md-ml-3 {
@media (min-width: $breakpoint-md) {
margin-left: $gl-spacing-scale-3;

View File

@ -237,7 +237,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
[
*::ApplicationSettingsHelper.visible_attributes,
*::ApplicationSettingsHelper.external_authorization_service_attributes,
*ApplicationSetting.repository_storages_weighted_attributes,
*ApplicationSetting.kroki_formats_attributes.keys.map { |key| "kroki_formats_#{key}".to_sym },
:lets_encrypt_notification_email,
:lets_encrypt_terms_of_service_accepted,
@ -248,8 +247,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:default_branch_name,
disabled_oauth_sign_in_sources: [],
import_sources: [],
repository_storages: [],
restricted_visibility_levels: []
restricted_visibility_levels: [],
repository_storages_weighted: {}
]
end

View File

@ -5,35 +5,38 @@ module Mutations
module Issues
class IssueMoveList < Mutations::Issues::Base
graphql_name 'IssueMoveList'
BoardGID = ::Types::GlobalIDType[::Board]
ListID = ::GraphQL::ID_TYPE
IssueID = ::GraphQL::ID_TYPE
argument :board_id, GraphQL::ID_TYPE,
required: true,
loads: Types::BoardType,
description: 'Global ID of the board that the issue is in.'
argument :board_id, BoardGID,
required: true,
loads: Types::BoardType,
description: 'Global ID of the board that the issue is in.'
argument :project_path, GraphQL::ID_TYPE,
required: true,
description: 'Project the issue to mutate is in.'
required: true,
description: 'Project the issue to mutate is in.'
argument :iid, GraphQL::STRING_TYPE,
required: true,
description: 'IID of the issue to mutate.'
required: true,
description: 'IID of the issue to mutate.'
argument :from_list_id, GraphQL::ID_TYPE,
required: false,
description: 'ID of the board list that the issue will be moved from.'
argument :from_list_id, ListID,
required: false,
description: 'ID of the board list that the issue will be moved from.'
argument :to_list_id, GraphQL::ID_TYPE,
required: false,
description: 'ID of the board list that the issue will be moved to.'
argument :to_list_id, ListID,
required: false,
description: 'ID of the board list that the issue will be moved to.'
argument :move_before_id, GraphQL::ID_TYPE,
required: false,
description: 'ID of issue that should be placed before the current issue.'
argument :move_before_id, IssueID,
required: false,
description: 'ID of issue that should be placed before the current issue.'
argument :move_after_id, GraphQL::ID_TYPE,
required: false,
description: 'ID of issue that should be placed after the current issue.'
argument :move_after_id, IssueID,
required: false,
description: 'ID of issue that should be placed after the current issue.'
def ready?(**args)
if move_arguments(args).blank?

View File

@ -37,13 +37,8 @@ module ApplicationSettingsHelper
end
def storage_weights
ApplicationSetting.repository_storages_weighted_attributes.map do |attribute|
storage = attribute.to_s.delete_prefix('repository_storages_weighted_')
{
name: attribute,
label: storage,
value: @application_setting.repository_storages_weighted[storage] || 0
}
Gitlab.config.repositories.storages.keys.each_with_object(OpenStruct.new) do |storage, weights|
weights[storage.to_sym] = @application_setting.repository_storages_weighted[storage] || 0
end
end

View File

@ -25,10 +25,6 @@ class ApplicationSetting < ApplicationRecord
alias_attribute :instance_group_id, :instance_administrators_group_id
alias_attribute :instance_administrators_group, :instance_group
def self.repository_storages_weighted_attributes
@repository_storages_weighted_atributes ||= Gitlab.config.repositories.storages.keys.map { |k| "repository_storages_weighted_#{k}".to_sym }.freeze
end
def self.kroki_formats_attributes
{
blockdiag: {
@ -44,7 +40,6 @@ class ApplicationSetting < ApplicationRecord
end
store_accessor :kroki_formats, *ApplicationSetting.kroki_formats_attributes.keys, prefix: true
store_accessor :repository_storages_weighted, *Gitlab.config.repositories.storages.keys, prefix: true
# Include here so it can override methods from
# `add_authentication_token_field`
@ -58,8 +53,9 @@ class ApplicationSetting < ApplicationRecord
serialize :domain_allowlist, Array # rubocop:disable Cop/ActiveRecordSerialize
serialize :domain_denylist, Array # rubocop:disable Cop/ActiveRecordSerialize
serialize :repository_storages # rubocop:disable Cop/ActiveRecordSerialize
serialize :asset_proxy_allowlist, Array # rubocop:disable Cop/ActiveRecordSerialize
# See https://gitlab.com/gitlab-org/gitlab/-/issues/300916
serialize :asset_proxy_allowlist, Array # rubocop:disable Cop/ActiveRecordSerialize
serialize :asset_proxy_whitelist, Array # rubocop:disable Cop/ActiveRecordSerialize
cache_markdown_field :sign_in_text
@ -502,6 +498,7 @@ class ApplicationSetting < ApplicationRecord
inclusion: { in: [true, false], message: _('must be a boolean value') }
before_validation :ensure_uuid!
before_validation :coerce_repository_storages_weighted, if: :repository_storages_weighted_changed?
before_save :ensure_runners_registration_token
before_save :ensure_health_check_access_token
@ -582,12 +579,6 @@ class ApplicationSetting < ApplicationRecord
recaptcha_enabled || login_recaptcha_protection_enabled
end
repository_storages_weighted_attributes.each do |attribute|
define_method :"#{attribute}=" do |value|
super(value.to_i)
end
end
kroki_formats_attributes.keys.each do |key|
define_method :"kroki_formats_#{key}=" do |value|
super(::Gitlab::Utils.to_boolean(value))

View File

@ -280,23 +280,24 @@ module ApplicationSettingImplementation
self.notes_create_limit_allowlist = strings_to_array(values).map(&:downcase)
end
def asset_proxy_allowlist=(values)
def asset_proxy_whitelist=(values)
values = strings_to_array(values) if values.is_a?(String)
# make sure we always allow the running host
values << Gitlab.config.gitlab.host unless values.include?(Gitlab.config.gitlab.host)
self[:asset_proxy_allowlist] = values
self[:asset_proxy_whitelist] = values
end
alias_method :asset_proxy_allowlist=, :asset_proxy_whitelist=
def asset_proxy_allowlist
read_attribute(:asset_proxy_whitelist)
end
def repository_storages
Array(read_attribute(:repository_storages))
end
def repository_storages_weighted
read_attribute(:repository_storages_weighted)
end
def commit_email_hostname
super.presence || self.class.default_commit_email_hostname
end
@ -328,9 +329,10 @@ module ApplicationSettingImplementation
def normalized_repository_storage_weights
strong_memoize(:normalized_repository_storage_weights) do
weights_total = repository_storages_weighted.values.reduce(:+)
repository_storages_weights = repository_storages_weighted.slice(*Gitlab.config.repositories.storages.keys)
weights_total = repository_storages_weights.values.reduce(:+)
repository_storages_weighted.transform_values do |w|
repository_storages_weights.transform_values do |w|
next w if weights_total == 0
w.to_f / weights_total
@ -468,16 +470,20 @@ module ApplicationSettingImplementation
invalid.empty?
end
def coerce_repository_storages_weighted
repository_storages_weighted.transform_values!(&:to_i)
end
def check_repository_storages_weighted
invalid = repository_storages_weighted.keys - Gitlab.config.repositories.storages.keys
errors.add(:repository_storages_weighted, "can't include: %{invalid_storages}" % { invalid_storages: invalid.join(", ") }) unless
errors.add(:repository_storages_weighted, _("can't include: %{invalid_storages}") % { invalid_storages: invalid.join(", ") }) unless
invalid.empty?
repository_storages_weighted.each do |key, val|
next unless val.present?
errors.add(:"repository_storages_weighted_#{key}", "value must be an integer") unless val.is_a?(Integer)
errors.add(:"repository_storages_weighted_#{key}", "value must be between 0 and 100") unless val.between?(0, 100)
errors.add(:repository_storages_weighted, _("value for '%{storage}' must be an integer") % { storage: key }) unless val.is_a?(Integer)
errors.add(:repository_storages_weighted, _("value for '%{storage}' must be between 0 and 100") % { storage: key }) unless val.between?(0, 100)
end
end

View File

@ -6,7 +6,7 @@ module ApplicationSettings
attr_reader :params, :application_setting
MARKDOWN_CACHE_INVALIDATING_PARAMS = %w(asset_proxy_enabled asset_proxy_url asset_proxy_secret_key asset_proxy_allowlist).freeze
MARKDOWN_CACHE_INVALIDATING_PARAMS = %w(asset_proxy_enabled asset_proxy_url asset_proxy_secret_key asset_proxy_whitelist).freeze
def execute
result = update_settings

View File

@ -31,6 +31,7 @@ module MergeRequests
old_mentioned_users = old_associations.fetch(:mentioned_users, [])
old_assignees = old_associations.fetch(:assignees, [])
old_reviewers = old_associations.fetch(:reviewers, [])
old_timelogs = old_associations.fetch(:timelogs, [])
changed_fields = merge_request.previous_changes.keys
resolve_todos(merge_request, old_labels, old_assignees, old_reviewers)
@ -48,6 +49,7 @@ module MergeRequests
track_title_and_desc_edits(changed_fields)
track_discussion_lock_toggle(merge_request, changed_fields)
track_time_estimate_and_spend_edits(merge_request, old_timelogs, changed_fields)
notify_if_labels_added(merge_request, old_labels)
notify_if_mentions_added(merge_request, old_mentioned_users)
@ -106,6 +108,11 @@ module MergeRequests
end
end
def track_time_estimate_and_spend_edits(merge_request, old_timelogs, changed_fields)
merge_request_activity_counter.track_time_estimate_changed_action(user: current_user) if changed_fields.include?('time_estimate')
merge_request_activity_counter.track_time_spent_changed_action(user: current_user) if old_timelogs != merge_request.timelogs
end
def notify_if_labels_added(merge_request, old_labels)
added_labels = merge_request.labels - old_labels

View File

@ -18,8 +18,9 @@
= _('Enter weights for storages for new repositories.')
= link_to sprite_icon('question-o'), help_page_path('administration/repository_storage_paths')
.form-check
- storage_weights.each do |attribute|
= f.text_field attribute[:name], class: 'form-text-input', value: attribute[:value]
= f.label attribute[:label], attribute[:label], class: 'label-bold form-check-label'
%br
= f.fields_for :repository_storages_weighted, storage_weights do |storage_form|
- Gitlab.config.repositories.storages.keys.each do |storage|
= storage_form.text_field storage, class: 'form-text-input'
= storage_form.label storage, storage, class: 'label-bold form-check-label'
%br
= f.submit _('Save changes'), class: "gl-button btn btn-success qa-save-changes-button"

View File

@ -0,0 +1,5 @@
---
title: Add space next to icons in epic issue list
merge_request: 54138
author: Yogi (@yo)
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Add tracking to merge request time estimate/spent changes
merge_request: 55046
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Add Operating System details to usage ping
merge_request: 54778
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Rename asset_proxy_allowlist column
merge_request: 55419
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Allow saving repository weights after a storage has been removed
merge_request: 53803
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: 'GithubImporter: Add Merge request approval only if it does not exists yet'
merge_request: 55376
author:
type: fixed

View File

@ -193,7 +193,6 @@ module Gitlab
config.assets.precompile << "page_bundles/import.css"
config.assets.precompile << "page_bundles/incident_management_list.css"
config.assets.precompile << "page_bundles/issues_list.css"
config.assets.precompile << "page_bundles/iterations.css"
config.assets.precompile << "page_bundles/jira_connect.css"
config.assets.precompile << "page_bundles/jira_connect_users.css"
config.assets.precompile << "page_bundles/learn_gitlab.css"

View File

@ -1,7 +1,7 @@
---
name: ci_reduce_queries_when_ticking_runner_queue
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55496
rollout_issue_url:
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/323328
milestone: '13.10'
type: development
group: group::continuous integration

View File

@ -0,0 +1,8 @@
---
name: usage_data_i_code_review_user_time_estimate_changed
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55046
rollout_issue_url:
milestone: '13.10'
type: development
group: group::code review
default_enabled: true

View File

@ -0,0 +1,8 @@
---
name: usage_data_i_code_review_user_time_spent_changed
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55046
rollout_issue_url:
milestone: '13.10'
type: development
group: group::code review
default_enabled: true

View File

@ -0,0 +1,20 @@
---
key_path: redis_hll_counters.code_review.i_code_review_user_time_estimate_changed_monthly
description: Count of unique users per month who changed time estimate of a MR
product_section: dev
product_stage: create
product_group: group::code review
product_category: code_review
value_type: number
status: implemented
milestone: "13.10"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55046
time_frame: 28d
data_source: redis_hll
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -0,0 +1,20 @@
---
key_path: redis_hll_counters.code_review.i_code_review_user_time_spent_changed_monthly
description: Count of unique users per month who changed time spent on a MR
product_section: dev
product_stage: create
product_group: group::code review
product_category: code_review
value_type: number
status: implemented
milestone: "13.10"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55046
time_frame: 28d
data_source: redis_hll
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -0,0 +1,20 @@
---
key_path: redis_hll_counters.code_review.i_code_review_user_time_estimate_changed_weekly
description: Count of unique users per week who changed time estimate of a MR
product_section: dev
product_stage: create
product_group: group::code review
product_category: code_review
value_type: number
status: implemented
milestone: "13.10"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55046
time_frame: 7d
data_source: redis_hll
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -0,0 +1,20 @@
---
key_path: redis_hll_counters.code_review.i_code_review_user_time_spent_changed_weekly
description: Count of unique users per week who changed time spent on a MR
product_section: dev
product_stage: create
product_group: group::code review
product_category: code_review
value_type: number
status: implemented
milestone: "13.10"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55046
time_frame: 7d
data_source: redis_hll
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -0,0 +1,20 @@
---
key_path: settings.operating_system
description: Information about the operating system running GitLab
product_section: enablement
product_stage: enablement
product_group: group::distribution
product_category: collection
value_type: string
status: implemented
milestone: "13.10"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54778
time_frame: none
data_source: ruby
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class RenameAssetProxyAllowlistOnApplicationSettings < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers::V2
DOWNTIME = false
disable_ddl_transaction!
def up
rename_column_concurrently :application_settings,
:asset_proxy_allowlist,
:asset_proxy_whitelist
end
def down
undo_rename_column_concurrently :application_settings,
:asset_proxy_allowlist,
:asset_proxy_whitelist
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class CleanUpAssetProxyAllowlistRenameOnApplicationSettings < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers::V2
DOWNTIME = false
disable_ddl_transaction!
def up
cleanup_concurrent_column_rename :application_settings,
:asset_proxy_allowlist,
:asset_proxy_whitelist
end
def down
undo_cleanup_concurrent_column_rename :application_settings,
:asset_proxy_allowlist,
:asset_proxy_whitelist
end
end

View File

@ -0,0 +1 @@
21ae7ea7cbf1d34c7b9dc300a641eaf975ed1e33f5bc519494cd37c4a661bec8

View File

@ -0,0 +1 @@
28b90c9b7c2e4f2e2b12088f5aee85c16dfb567f89ed6a8e771f2c5d91c818d9

View File

@ -9394,12 +9394,12 @@ CREATE TABLE application_settings (
invisible_captcha_enabled boolean DEFAULT false NOT NULL,
enforce_ssh_key_expiration boolean DEFAULT false NOT NULL,
git_two_factor_session_expiry integer DEFAULT 15 NOT NULL,
asset_proxy_allowlist text,
keep_latest_artifact boolean DEFAULT true NOT NULL,
notes_create_limit integer DEFAULT 300 NOT NULL,
notes_create_limit_allowlist text[] DEFAULT '{}'::text[] NOT NULL,
kroki_formats jsonb DEFAULT '{}'::jsonb NOT NULL,
in_product_marketing_emails_enabled boolean DEFAULT true NOT NULL,
asset_proxy_whitelist text,
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
CONSTRAINT app_settings_registry_exp_policies_worker_capacity_positive CHECK ((container_registry_expiration_policies_worker_capacity >= 0)),
CONSTRAINT check_17d9558205 CHECK ((char_length(kroki_url) <= 1024)),

View File

@ -13730,6 +13730,86 @@ Count of unique users per week|month with diffs viewed file by file
| `tier` | |
| `skip_validation` | true |
## `redis_hll_counters.code_review.i_code_review_user_time_estimate_changed_monthly`
Count of unique users per month who changed time estimate of a MR
| field | value |
| --- | --- |
| `key_path` | **`redis_hll_counters.code_review.i_code_review_user_time_estimate_changed_monthly`** |
| `product_section` | dev |
| `product_stage` | create |
| `product_group` | `group::code review` |
| `product_category` | `code_review` |
| `value_type` | number |
| `status` | implemented |
| `milestone` | 13.10 |
| `introduced_by_url` | [Introduced by](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55046) |
| `time_frame` | 28d |
| `data_source` | Redis_hll |
| `distribution` | ce, ee |
| `tier` | free, premium, ultimate |
## `redis_hll_counters.code_review.i_code_review_user_time_estimate_changed_weekly`
Count of unique users per week who changed time estimate of a MR
| field | value |
| --- | --- |
| `key_path` | **`redis_hll_counters.code_review.i_code_review_user_time_estimate_changed_weekly`** |
| `product_section` | dev |
| `product_stage` | create |
| `product_group` | `group::code review` |
| `product_category` | `code_review` |
| `value_type` | number |
| `status` | implemented |
| `milestone` | 13.10 |
| `introduced_by_url` | [Introduced by](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55046) |
| `time_frame` | 7d |
| `data_source` | Redis_hll |
| `distribution` | ce, ee |
| `tier` | free, premium, ultimate |
## `redis_hll_counters.code_review.i_code_review_user_time_spent_changed_monthly`
Count of unique users per month who changed time spent on a MR
| field | value |
| --- | --- |
| `key_path` | **`redis_hll_counters.code_review.i_code_review_user_time_spent_changed_monthly`** |
| `product_section` | dev |
| `product_stage` | create |
| `product_group` | `group::code review` |
| `product_category` | `code_review` |
| `value_type` | number |
| `status` | implemented |
| `milestone` | 13.10 |
| `introduced_by_url` | [Introduced by](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55046) |
| `time_frame` | 28d |
| `data_source` | Redis_hll |
| `distribution` | ce, ee |
| `tier` | free, premium, ultimate |
## `redis_hll_counters.code_review.i_code_review_user_time_spent_changed_weekly`
Count of unique users per week who changed time spent on a MR
| field | value |
| --- | --- |
| `key_path` | **`redis_hll_counters.code_review.i_code_review_user_time_spent_changed_weekly`** |
| `product_section` | dev |
| `product_stage` | create |
| `product_group` | `group::code review` |
| `product_category` | `code_review` |
| `value_type` | number |
| `status` | implemented |
| `milestone` | 13.10 |
| `introduced_by_url` | [Introduced by](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55046) |
| `time_frame` | 7d |
| `data_source` | Redis_hll |
| `distribution` | ce, ee |
| `tier` | free, premium, ultimate |
## `redis_hll_counters.code_review.i_code_review_user_toggled_task_item_status_monthly`
Missing description
@ -20846,6 +20926,26 @@ Is encrypted LDAP secrets configured?
| `tier` | free, premium, ultimate |
| `skip_validation` | true |
## `settings.operating_system`
Information about the operating system running GitLab
| field | value |
| --- | --- |
| `key_path` | **`settings.operating_system`** |
| `product_section` | enablement |
| `product_stage` | enablement |
| `product_group` | `group::distribution` |
| `product_category` | `collection` |
| `value_type` | string |
| `status` | implemented |
| `milestone` | 13.10 |
| `introduced_by_url` | [Introduced by](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54778) |
| `time_frame` | none |
| `data_source` | Ruby |
| `distribution` | ce, ee |
| `tier` | free, premium, ultimate |
## `signup_enabled`
Whether public signup is enabled

View File

@ -5,7 +5,7 @@ module Gitlab
module Reports
class CodequalityReportsComparer < ReportsComparer
def initialize(base_report, head_report)
@base_report = base_report || CodequalityReports.new
@base_report = base_report
@head_report = head_report
end
@ -15,12 +15,16 @@ module Gitlab
def existing_errors
strong_memoize(:existing_errors) do
next [] if not_found?
base_report.all_degradations & head_report.all_degradations
end
end
def new_errors
strong_memoize(:new_errors) do
next [] if not_found?
fingerprints = head_report.degradations.keys - base_report.degradations.keys
head_report.degradations.fetch_values(*fingerprints)
end
@ -28,6 +32,8 @@ module Gitlab
def resolved_errors
strong_memoize(:resolved_errors) do
next [] if not_found?
fingerprints = base_report.degradations.keys - head_report.degradations.keys
base_report.degradations.fetch_values(*fingerprints)
end

View File

@ -18,10 +18,10 @@ module Gitlab
end
def status
if success?
STATUS_SUCCESS
elsif base_report.nil? || head_report.nil?
if base_report.nil? || head_report.nil?
STATUS_NOT_FOUND
elsif success?
STATUS_SUCCESS
else
STATUS_FAILED
end
@ -54,6 +54,10 @@ module Gitlab
def total_count
existing_errors.size + new_errors.size
end
def not_found?
status == STATUS_NOT_FOUND
end
end
end
end

View File

@ -77,12 +77,22 @@ module Gitlab
def add_approval!(user_id)
return unless review.review_type == 'APPROVED'
add_approval_system_note!(user_id)
merge_request.approvals.create!(
approval_attribues = {
merge_request_id: merge_request.id,
user_id: user_id,
created_at: review.submitted_at
created_at: review.submitted_at,
updated_at: review.submitted_at
}
result = ::Approval.insert(
approval_attribues,
returning: [:id],
unique_by: [:user_id, :merge_request_id]
)
if result.rows.present?
add_approval_system_note!(user_id)
end
end
def add_approval_system_note!(user_id)

View File

@ -242,7 +242,8 @@ module Gitlab
def system_usage_data_settings
{
settings: {
ldap_encrypted_secrets_enabled: alt_usage_data(fallback: nil) { Gitlab::Auth::Ldap::Config.encrypted_secrets.active? }
ldap_encrypted_secrets_enabled: alt_usage_data(fallback: nil) { Gitlab::Auth::Ldap::Config.encrypted_secrets.active? },
operating_system: alt_usage_data(fallback: nil) { operating_system }
}
}
end
@ -505,6 +506,17 @@ module Gitlab
end
end
def operating_system
ohai_data = Ohai::System.new.tap do |oh|
oh.all_plugins(['platform'])
end.data
platform = ohai_data['platform']
platform = 'raspbian' if ohai_data['platform'] == 'debian' && /armv/.match?(ohai_data['kernel']['machine'])
"#{platform}-#{ohai_data['platform_version']}"
end
def last_28_days_time_period(column: :created_at)
{ column => 30.days.ago..2.days.ago }
end

View File

@ -44,7 +44,9 @@
'i_code_review_user_toggled_task_item_status',
'i_code_review_user_create_mr_from_issue',
'i_code_review_user_mr_discussion_locked',
'i_code_review_user_mr_discussion_unlocked'
'i_code_review_user_mr_discussion_unlocked',
'i_code_review_user_time_estimate_changed',
'i_code_review_user_time_spent_changed'
]
- name: code_review_category_monthly_active_users
operator: OR
@ -82,7 +84,9 @@
'i_code_review_user_toggled_task_item_status',
'i_code_review_user_create_mr_from_issue',
'i_code_review_user_mr_discussion_locked',
'i_code_review_user_mr_discussion_unlocked'
'i_code_review_user_mr_discussion_unlocked',
'i_code_review_user_time_estimate_changed',
'i_code_review_user_time_spent_changed'
]
- name: code_review_extension_category_monthly_active_users
operator: OR

View File

@ -174,3 +174,13 @@
category: code_review
aggregation: weekly
feature_flag: usage_data_i_code_review_user_mr_discussion_unlocked
- name: i_code_review_user_time_estimate_changed
redis_slot: code_review
category: code_review
aggregation: weekly
feature_flag: usage_data_i_code_review_user_time_estimate_changed
- name: i_code_review_user_time_spent_changed
redis_slot: code_review
category: code_review
aggregation: weekly
feature_flag: usage_data_i_code_review_user_time_spent_changed

View File

@ -37,6 +37,8 @@ module Gitlab
MR_CREATE_FROM_ISSUE_ACTION = 'i_code_review_user_create_mr_from_issue'
MR_DISCUSSION_LOCKED_ACTION = 'i_code_review_user_mr_discussion_locked'
MR_DISCUSSION_UNLOCKED_ACTION = 'i_code_review_user_mr_discussion_unlocked'
MR_TIME_ESTIMATE_CHANGED_ACTION = 'i_code_review_user_time_estimate_changed'
MR_TIME_SPENT_CHANGED_ACTION = 'i_code_review_user_time_spent_changed'
class << self
def track_mr_diffs_action(merge_request:)
@ -163,6 +165,14 @@ module Gitlab
track_unique_action_by_user(MR_DISCUSSION_UNLOCKED_ACTION, user)
end
def track_time_estimate_changed_action(user:)
track_unique_action_by_user(MR_TIME_ESTIMATE_CHANGED_ACTION, user)
end
def track_time_spent_changed_action(user:)
track_unique_action_by_user(MR_TIME_SPENT_CHANGED_ACTION, user)
end
private
def track_unique_action_by_merge_request(action, merge_request)

View File

@ -173,6 +173,11 @@ msgid_plural "%d days"
msgstr[0] ""
msgstr[1] ""
msgid "%d epic"
msgid_plural "%d epics"
msgstr[0] ""
msgstr[1] ""
msgid "%d error"
msgid_plural "%d errors"
msgstr[0] ""
@ -559,6 +564,9 @@ msgstr ""
msgid "%{issuesSize} with a limit of %{maxIssueCount}"
msgstr ""
msgid "%{itemsCount} issues with a limit of %{maxIssueCount}"
msgstr ""
msgid "%{labelStart}Actual response:%{labelEnd} %{headers}"
msgstr ""
@ -34912,6 +34920,9 @@ msgstr ""
msgid "can't be enabled because signed commits are required for this project"
msgstr ""
msgid "can't include: %{invalid_storages}"
msgstr ""
msgid "cannot be a date in the past"
msgstr ""
@ -36318,6 +36329,12 @@ msgstr ""
msgid "v%{version} published %{timeAgo}"
msgstr ""
msgid "value for '%{storage}' must be an integer"
msgstr ""
msgid "value for '%{storage}' must be between 0 and 100"
msgstr ""
msgid "verify ownership"
msgstr ""

View File

@ -32,6 +32,10 @@ module QA
# This 'element' is here only to ensure the changes in the view source aren't mistakenly changed
element :_, "qa_selector = local_assigns.fetch(:qa_selector, '')" # rubocop:disable QA/ElementWithPattern
end
base.view 'app/assets/javascripts/snippets/components/snippet_visibility_edit.vue' do
element :visibility_content
end
end
def fill_title(title)
@ -44,7 +48,7 @@ module QA
end
def set_visibility(visibility)
choose visibility
click_element(:visibility_content, visibility: visibility)
end
def fill_file_name(name, file_number = nil)

View File

@ -30,6 +30,7 @@ UsageData/LargeTable:
- :Settings
- :CE_MEMOIZED_VALUES
- :EE_MEMOIZED_VALUES
- :Ohai::System
CountMethods:
- :count
- :distinct_count

View File

@ -144,10 +144,10 @@ RSpec.describe Admin::ApplicationSettingsController do
end
it 'updates repository_storages_weighted setting' do
put :update, params: { application_setting: { repository_storages_weighted_default: 75 } }
put :update, params: { application_setting: { repository_storages_weighted: { default: 75 } } }
expect(response).to redirect_to(general_admin_application_settings_path)
expect(ApplicationSetting.current.repository_storages_weighted_default).to eq(75)
expect(ApplicationSetting.current.repository_storages_weighted).to eq('default' => 75)
end
it 'updates kroki_formats setting' do

View File

@ -3,7 +3,7 @@
FactoryBot.define do
factory :ci_build_trace_chunk, class: 'Ci::BuildTraceChunk' do
build factory: :ci_build
chunk_index { 0 }
chunk_index { generate(:iid) }
data_store { :redis }
trait :redis_with_data do

View File

@ -384,7 +384,20 @@ RSpec.describe 'Admin updates settings' do
click_button 'Save changes'
end
expect(current_settings.repository_storages_weighted_default).to be 50
expect(current_settings.repository_storages_weighted).to eq('default' => 50)
end
it 'still saves when settings are outdated' do
current_settings.update_attribute :repository_storages_weighted, { 'default' => 100, 'outdated' => 100 }
visit repository_admin_application_settings_path
page.within('.as-repository-storage') do
fill_in 'application_setting_repository_storages_weighted_default', with: 50
click_button 'Save changes'
end
expect(current_settings.repository_storages_weighted).to eq('default' => 50)
end
end

View File

@ -1,5 +1,6 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { mockLabelList } from 'jest/boards/mock_data';
import BoardListHeader from '~/boards/components/board_list_header.vue';
@ -14,6 +15,7 @@ describe('Board List Header Component', () => {
let store;
const updateListSpy = jest.fn();
const toggleListCollapsedSpy = jest.fn();
afterEach(() => {
wrapper.destroy();
@ -43,38 +45,39 @@ describe('Board List Header Component', () => {
if (withLocalStorage) {
localStorage.setItem(
`boards.${boardId}.${listMock.listType}.${listMock.id}.expanded`,
(!collapsed).toString(),
`boards.${boardId}.${listMock.listType}.${listMock.id}.collapsed`,
collapsed.toString(),
);
}
store = new Vuex.Store({
state: {},
actions: { updateList: updateListSpy },
actions: { updateList: updateListSpy, toggleListCollapsed: toggleListCollapsedSpy },
getters: {},
});
wrapper = shallowMount(BoardListHeader, {
store,
localVue,
propsData: {
disabled: false,
list: listMock,
},
provide: {
boardId,
weightFeatureAvailable: false,
currentUserId,
},
});
wrapper = extendedWrapper(
shallowMount(BoardListHeader, {
store,
localVue,
propsData: {
disabled: false,
list: listMock,
},
provide: {
boardId,
weightFeatureAvailable: false,
currentUserId,
},
}),
);
};
const isCollapsed = () => wrapper.vm.list.collapsed;
const isExpanded = () => !isCollapsed;
const findAddIssueButton = () => wrapper.find({ ref: 'newIssueBtn' });
const findTitle = () => wrapper.find('.board-title');
const findCaret = () => wrapper.find('.board-title-caret');
const findCaret = () => wrapper.findByTestId('board-title-caret');
describe('Add issue button', () => {
const hasNoAddButton = [ListType.closed];
@ -114,40 +117,29 @@ describe('Board List Header Component', () => {
});
describe('expanding / collapsing the column', () => {
it('does not collapse when clicking the header', async () => {
it('should display collapse icon when column is expanded', async () => {
createComponent();
expect(isCollapsed()).toBe(false);
const icon = findCaret();
wrapper.find('[data-testid="board-list-header"]').trigger('click');
await wrapper.vm.$nextTick();
expect(isCollapsed()).toBe(false);
expect(icon.props('icon')).toBe('chevron-right');
});
it('collapses expanded Column when clicking the collapse icon', async () => {
createComponent();
expect(isCollapsed()).toBe(false);
findCaret().vm.$emit('click');
await wrapper.vm.$nextTick();
expect(isCollapsed()).toBe(true);
});
it('expands collapsed Column when clicking the expand icon', async () => {
it('should display expand icon when column is collapsed', async () => {
createComponent({ collapsed: true });
expect(isCollapsed()).toBe(true);
const icon = findCaret();
expect(icon.props('icon')).toBe('chevron-down');
});
it('should dispatch toggleListCollapse when clicking the collapse icon', async () => {
createComponent();
findCaret().vm.$emit('click');
await wrapper.vm.$nextTick();
expect(isCollapsed()).toBe(false);
expect(toggleListCollapsedSpy).toHaveBeenCalledTimes(1);
});
it("when logged in it calls list update and doesn't set localStorage", async () => {
@ -157,7 +149,7 @@ describe('Board List Header Component', () => {
await wrapper.vm.$nextTick();
expect(updateListSpy).toHaveBeenCalledTimes(1);
expect(localStorage.getItem(`${wrapper.vm.uniqueKey}.expanded`)).toBe(null);
expect(localStorage.getItem(`${wrapper.vm.uniqueKey}.collapsed`)).toBe(null);
});
it("when logged out it doesn't call list update and sets localStorage", async () => {
@ -167,7 +159,7 @@ describe('Board List Header Component', () => {
await wrapper.vm.$nextTick();
expect(updateListSpy).not.toHaveBeenCalled();
expect(localStorage.getItem(`${wrapper.vm.uniqueKey}.expanded`)).toBe(String(isExpanded()));
expect(localStorage.getItem(`${wrapper.vm.uniqueKey}.collapsed`)).toBe(String(isCollapsed()));
});
});

View File

@ -452,6 +452,22 @@ describe('updateList', () => {
});
});
describe('toggleListCollapsed', () => {
it('should commit TOGGLE_LIST_COLLAPSED mutation', async () => {
const payload = { listId: 'gid://gitlab/List/1', collapsed: true };
await testAction({
action: actions.toggleListCollapsed,
payload,
expectedMutations: [
{
type: types.TOGGLE_LIST_COLLAPSED,
payload,
},
],
});
});
});
describe('removeList', () => {
let state;
const list = mockLists[0];

View File

@ -202,6 +202,24 @@ describe('Board Store Mutations', () => {
});
});
describe('TOGGLE_LIST_COLLAPSED', () => {
it('updates collapsed attribute of list in boardLists state', () => {
const listId = 'gid://gitlab/List/1';
state = {
...state,
boardLists: {
[listId]: mockLists[0],
},
};
expect(state.boardLists[listId].collapsed).toEqual(false);
mutations.TOGGLE_LIST_COLLAPSED(state, { listId, collapsed: true });
expect(state.boardLists[listId].collapsed).toEqual(true);
});
});
describe('REMOVE_LIST', () => {
it('removes list from boardLists', () => {
const [list, secondList] = mockLists;

View File

@ -46,6 +46,8 @@ exports[`Snippet Visibility Edit component rendering matches the snapshot 1`] =
<span
class="font-weight-bold ml-1 js-visibility-option"
data-qa-selector="visibility_content"
data-qa-visibility="Private"
>
Private
</span>
@ -65,6 +67,8 @@ exports[`Snippet Visibility Edit component rendering matches the snapshot 1`] =
<span
class="font-weight-bold ml-1 js-visibility-option"
data-qa-selector="visibility_content"
data-qa-visibility="Internal"
>
Internal
</span>
@ -84,6 +88,8 @@ exports[`Snippet Visibility Edit component rendering matches the snapshot 1`] =
<span
class="font-weight-bold ml-1 js-visibility-option"
data-qa-selector="visibility_content"
data-qa-visibility="Public"
>
Public
</span>

View File

@ -130,20 +130,15 @@ RSpec.describe ApplicationSettingsHelper do
before do
helper.instance_variable_set(:@application_setting, application_setting)
stub_storage_settings({ 'default': {}, 'storage_1': {}, 'storage_2': {} })
allow(ApplicationSetting).to receive(:repository_storages_weighted_attributes).and_return(
[:repository_storages_weighted_default,
:repository_storages_weighted_storage_1,
:repository_storages_weighted_storage_2])
stub_application_setting(repository_storages_weighted: { 'default' => 100, 'storage_1' => 50, 'storage_2' => nil })
end
it 'returns storages correctly' do
expect(helper.storage_weights).to eq([
{ name: :repository_storages_weighted_default, label: 'default', value: 100 },
{ name: :repository_storages_weighted_storage_1, label: 'storage_1', value: 50 },
{ name: :repository_storages_weighted_storage_2, label: 'storage_2', value: 0 }
])
expect(helper.storage_weights).to eq(OpenStruct.new(
default: 100,
storage_1: 50,
storage_2: 0
))
end
end

View File

@ -27,6 +27,22 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
expect(report_status).to eq(described_class::STATUS_SUCCESS)
end
end
context 'when head report does not exist' do
let(:head_report) { nil }
it 'returns status not found' do
expect(report_status).to eq(described_class::STATUS_NOT_FOUND)
end
end
context 'when base report does not exist' do
let(:base_report) { nil }
it 'returns status success' do
expect(report_status).to eq(described_class::STATUS_NOT_FOUND)
end
end
end
describe '#errors_count' do
@ -93,6 +109,14 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
expect(resolved_count).to be_zero
end
end
context 'when base report is nil' do
let(:base_report) { nil }
it 'returns zero' do
expect(resolved_count).to be_zero
end
end
end
describe '#total_count' do
@ -140,6 +164,14 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
expect(total_count).to eq(2)
end
end
context 'when base report is nil' do
let(:base_report) { nil }
it 'returns zero' do
expect(total_count).to be_zero
end
end
end
describe '#existing_errors' do
@ -177,6 +209,14 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
expect(existing_errors).to be_empty
end
end
context 'when base report is nil' do
let(:base_report) { nil }
it 'returns an empty array' do
expect(existing_errors).to be_empty
end
end
end
describe '#new_errors' do
@ -213,6 +253,14 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
expect(new_errors).to eq([degradation_1])
end
end
context 'when base report is nil' do
let(:base_report) { nil }
it 'returns an empty array' do
expect(new_errors).to be_empty
end
end
end
describe '#resolved_errors' do
@ -250,5 +298,13 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
expect(resolved_errors).to be_empty
end
end
context 'when base report is nil' do
let(:base_report) { nil }
it 'returns an empty array' do
expect(resolved_errors).to be_empty
end
end
end
end

View File

@ -49,10 +49,6 @@ RSpec.describe Gitlab::Ci::Reports::ReportsComparer do
context 'when base_report is nil' do
let(:base_report) { nil }
before do
allow(comparer).to receive(:success?).and_return(false)
end
it 'returns status not_found' do
expect(status).to eq('not_found')
end
@ -61,10 +57,6 @@ RSpec.describe Gitlab::Ci::Reports::ReportsComparer do
context 'when head_report is nil' do
let(:head_report) { nil }
before do
allow(comparer).to receive(:success?).and_return(false)
end
it 'returns status not_found' do
expect(status).to eq('not_found')
end
@ -118,4 +110,22 @@ RSpec.describe Gitlab::Ci::Reports::ReportsComparer do
expect { total_count }.to raise_error(NotImplementedError)
end
end
describe '#not_found?' do
subject(:not_found) { comparer.not_found? }
context 'when base report is nil' do
let(:base_report) { nil }
it { is_expected.to be_truthy }
end
context 'when base report exists' do
before do
allow(comparer).to receive(:success?).and_return(true)
end
it { is_expected.to be_falsey }
end
end
end

View File

@ -19,8 +19,10 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestReviewImporter, :clean
context 'when the review is "APPROVED"' do
let(:review) { create_review(type: 'APPROVED', note: '') }
it 'creates a note for the review' do
expect { subject.execute }.to change(Note, :count)
it 'creates a note for the review and approves the Merge Request' do
expect { subject.execute }
.to change(Note, :count).by(1)
.and change(Approval, :count).by(1)
last_note = merge_request.notes.last
expect(last_note.note).to eq('approved this merge request')
@ -31,6 +33,14 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestReviewImporter, :clean
expect(merge_request.approved_by_users.reload).to include(author)
expect(merge_request.approvals.last.created_at).to eq(submitted_at)
end
it 'does nothing if the user already approved the merge request' do
create(:approval, merge_request: merge_request, user: author)
expect { subject.execute }
.to change(Note, :count).by(0)
.and change(Approval, :count).by(0)
end
end
context 'when the review is "COMMENTED"' do

View File

@ -4,8 +4,8 @@ require 'spec_helper'
RSpec.describe Gitlab::ImportExport do
describe 'export filename' do
let(:group) { create(:group, :nested) }
let(:project) { create(:project, :public, path: 'project-path', namespace: group) }
let(:group) { build(:group, path: 'child', parent: build(:group, path: 'parent')) }
let(:project) { build(:project, :public, path: 'project-path', namespace: group) }
it 'contains the project path' do
expect(described_class.export_filename(exportable: project)).to include(project.path)

View File

@ -300,4 +300,20 @@ RSpec.describe Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter, :cl
let(:action) { described_class::MR_DISCUSSION_UNLOCKED_ACTION }
end
end
describe '.track_time_estimate_changed_action' do
subject { described_class.track_time_estimate_changed_action(user: user) }
it_behaves_like 'a tracked merge request unique event' do
let(:action) { described_class::MR_TIME_ESTIMATE_CHANGED_ACTION }
end
end
describe '.track_time_spent_changed_action' do
subject { described_class.track_time_spent_changed_action(user: user) }
it_behaves_like 'a tracked merge request unique event' do
let(:action) { described_class::MR_TIME_SPENT_CHANGED_ACTION }
end
end
end

View File

@ -1129,12 +1129,40 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
end
end
describe ".operating_system" do
let(:ohai_data) { { "platform" => "ubuntu", "platform_version" => "20.04" } }
before do
allow_next_instance_of(Ohai::System) do |ohai|
allow(ohai).to receive(:data).and_return(ohai_data)
end
end
subject { described_class.operating_system }
it { is_expected.to eq("ubuntu-20.04") }
context 'when on Debian with armv architecture' do
let(:ohai_data) { { "platform" => "debian", "platform_version" => "10", 'kernel' => { 'machine' => 'armv' } } }
it { is_expected.to eq("raspbian-10") }
end
end
describe ".system_usage_data_settings" do
before do
allow(described_class).to receive(:operating_system).and_return('ubuntu-20.04')
end
subject { described_class.system_usage_data_settings }
it 'gathers settings usage data', :aggregate_failures do
expect(subject[:settings][:ldap_encrypted_secrets_enabled]).to eq(Gitlab::Auth::Ldap::Config.encrypted_secrets.active?)
end
it 'populates operating system information' do
expect(subject[:settings][:operating_system]).to eq('ubuntu-20.04')
end
end
end

View File

@ -105,14 +105,14 @@ RSpec.describe ApplicationSetting do
it { is_expected.not_to allow_value(false).for(:hashed_storage_enabled) }
it { is_expected.not_to allow_value(101).for(:repository_storages_weighted_default) }
it { is_expected.to allow_value('90').for(:repository_storages_weighted_default) }
it { is_expected.not_to allow_value(-1).for(:repository_storages_weighted_default) }
it { is_expected.to allow_value(100).for(:repository_storages_weighted_default) }
it { is_expected.to allow_value(0).for(:repository_storages_weighted_default) }
it { is_expected.to allow_value(50).for(:repository_storages_weighted_default) }
it { is_expected.to allow_value(nil).for(:repository_storages_weighted_default) }
it { is_expected.not_to allow_value({ default: 100, shouldntexist: 50 }).for(:repository_storages_weighted) }
it { is_expected.to allow_value('default' => 0).for(:repository_storages_weighted) }
it { is_expected.to allow_value('default' => 50).for(:repository_storages_weighted) }
it { is_expected.to allow_value('default' => 100).for(:repository_storages_weighted) }
it { is_expected.to allow_value('default' => '90').for(:repository_storages_weighted) }
it { is_expected.to allow_value('default' => nil).for(:repository_storages_weighted) }
it { is_expected.not_to allow_value('default' => -1).for(:repository_storages_weighted).with_message("value for 'default' must be between 0 and 100") }
it { is_expected.not_to allow_value('default' => 101).for(:repository_storages_weighted).with_message("value for 'default' must be between 0 and 100") }
it { is_expected.not_to allow_value('default' => 100, shouldntexist: 50).for(:repository_storages_weighted).with_message("can't include: shouldntexist") }
it { is_expected.to allow_value(400).for(:notes_create_limit) }
it { is_expected.not_to allow_value('two').for(:notes_create_limit) }
@ -650,6 +650,32 @@ RSpec.describe ApplicationSetting do
end
end
describe '#asset_proxy_whitelist' do
context 'when given an Array' do
it 'sets the domains and adds current running host' do
setting.asset_proxy_whitelist = ['example.com', 'assets.example.com']
expect(setting.asset_proxy_whitelist).to eq(['example.com', 'assets.example.com', 'localhost'])
end
end
context 'when given a String' do
it 'sets multiple domains with spaces' do
setting.asset_proxy_whitelist = 'example.com *.example.com'
expect(setting.asset_proxy_whitelist).to eq(['example.com', '*.example.com', 'localhost'])
end
it 'sets multiple domains with newlines and a space' do
setting.asset_proxy_whitelist = "example.com\n *.example.com"
expect(setting.asset_proxy_whitelist).to eq(['example.com', '*.example.com', 'localhost'])
end
it 'sets multiple domains with commas' do
setting.asset_proxy_whitelist = "example.com, *.example.com"
expect(setting.asset_proxy_whitelist).to eq(['example.com', '*.example.com', 'localhost'])
end
end
end
describe '#asset_proxy_allowlist' do
context 'when given an Array' do
it 'sets the domains and adds current running host' do
@ -958,12 +984,6 @@ RSpec.describe ApplicationSetting do
it_behaves_like 'application settings examples'
describe 'repository_storages_weighted_attributes' do
it 'returns the keys for repository_storages_weighted' do
expect(subject.class.repository_storages_weighted_attributes).to eq([:repository_storages_weighted_default])
end
end
describe 'kroki_format_supported?' do
it 'returns true when Excalidraw is enabled' do
subject.kroki_formats_excalidraw = true
@ -1007,11 +1027,4 @@ RSpec.describe ApplicationSetting do
expect(subject.kroki_formats_excalidraw).to eq(true)
end
end
it 'does not allow to set weight for non existing storage' do
setting.repository_storages_weighted = { invalid_storage: 100 }
expect(setting).not_to be_valid
expect(setting.errors.messages[:repository_storages_weighted]).to match_array(["can't include: invalid_storage"])
end
end

View File

@ -123,6 +123,7 @@ RSpec.describe ApplicationSettings::UpdateService do
it_behaves_like 'invalidates markdown cache', { asset_proxy_url: 'http://test.com' }
it_behaves_like 'invalidates markdown cache', { asset_proxy_secret_key: 'another secret' }
it_behaves_like 'invalidates markdown cache', { asset_proxy_allowlist: ['domain.com'] }
it_behaves_like 'invalidates markdown cache', { asset_proxy_whitelist: ['domain.com'] }
context 'when also setting the local_markdown_version' do
let(:params) { { asset_proxy_enabled: true, local_markdown_version: 12 } }

View File

@ -169,6 +169,23 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
end
end
end
it 'tracks time estimate and spend time changes' do
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
.to receive(:track_time_estimate_changed_action).once.with(user: user)
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
.to receive(:track_time_spent_changed_action).once.with(user: user)
opts[:time_estimate] = 86400
opts[:spend_time] = {
duration: 3600,
user_id: user.id,
spent_at: Date.parse('2021-02-24')
}
MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
end
end
context 'updating milestone' do

View File

@ -31,9 +31,34 @@ RSpec.describe Projects::DestroyService, :aggregate_failures do
end
shared_examples 'deleting the project with pipeline and build' do
context 'with pipeline and build', :sidekiq_inline do # which has optimistic locking
context 'with pipeline and build related records', :sidekiq_inline do # which has optimistic locking
let!(:pipeline) { create(:ci_pipeline, project: project) }
let!(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
let!(:build) { create(:ci_build, :artifacts, :with_runner_session, pipeline: pipeline) }
let!(:trace_chunks) { create(:ci_build_trace_chunk, build: build) }
let!(:job_variables) { create(:ci_job_variable, job: build) }
let!(:report_result) { create(:ci_build_report_result, build: build) }
let!(:pending_state) { create(:ci_build_pending_state, build: build) }
it 'deletes build related records' do
expect { destroy_project(project, user, {}) }.to change { Ci::Build.count }.by(-1)
.and change { Ci::BuildTraceChunk.count }.by(-1)
.and change { Ci::JobArtifact.count }.by(-2)
.and change { Ci::JobVariable.count }.by(-1)
.and change { Ci::BuildPendingState.count }.by(-1)
.and change { Ci::BuildReportResult.count }.by(-1)
.and change { Ci::BuildRunnerSession.count }.by(-1)
end
it 'avoids N+1 queries', skip: 'skipped until fixed in https://gitlab.com/gitlab-org/gitlab/-/issues/24644' do
recorder = ActiveRecord::QueryRecorder.new { destroy_project(project, user, {}) }
project = create(:project, :repository, namespace: user.namespace)
pipeline = create(:ci_pipeline, project: project)
builds = create_list(:ci_build, 3, :artifacts, pipeline: pipeline)
create_list(:ci_build_trace_chunk, 3, build: builds[0])
expect { destroy_project(project, project.owner, {}) }.not_to exceed_query_limit(recorder)
end
it_behaves_like 'deleting the project'
end

View File

@ -289,6 +289,7 @@ RSpec.shared_examples 'application settings examples' do
describe '#pick_repository_storage' do
before do
allow(Gitlab.config.repositories.storages).to receive(:keys).and_return(%w(default backup))
allow(setting).to receive(:repository_storages_weighted).and_return({ 'default' => 20, 'backup' => 80 })
end
@ -304,15 +305,19 @@ RSpec.shared_examples 'application settings examples' do
describe '#normalized_repository_storage_weights' do
using RSpec::Parameterized::TableSyntax
where(:storages, :normalized) do
{ 'default' => 0, 'backup' => 100 } | { 'default' => 0.0, 'backup' => 1.0 }
{ 'default' => 100, 'backup' => 100 } | { 'default' => 0.5, 'backup' => 0.5 }
{ 'default' => 20, 'backup' => 80 } | { 'default' => 0.2, 'backup' => 0.8 }
{ 'default' => 0, 'backup' => 0 } | { 'default' => 0.0, 'backup' => 0.0 }
where(:config_storages, :storages, :normalized) do
%w(default backup) | { 'default' => 0, 'backup' => 100 } | { 'default' => 0.0, 'backup' => 1.0 }
%w(default backup) | { 'default' => 100, 'backup' => 100 } | { 'default' => 0.5, 'backup' => 0.5 }
%w(default backup) | { 'default' => 20, 'backup' => 80 } | { 'default' => 0.2, 'backup' => 0.8 }
%w(default backup) | { 'default' => 0, 'backup' => 0 } | { 'default' => 0.0, 'backup' => 0.0 }
%w(default) | { 'default' => 0, 'backup' => 100 } | { 'default' => 0.0 }
%w(default) | { 'default' => 100, 'backup' => 100 } | { 'default' => 1.0 }
%w(default) | { 'default' => 20, 'backup' => 80 } | { 'default' => 1.0 }
end
with_them do
before do
allow(Gitlab.config.repositories.storages).to receive(:keys).and_return(config_storages)
allow(setting).to receive(:repository_storages_weighted).and_return(storages)
end

View File

@ -3,34 +3,49 @@
require 'spec_helper'
RSpec.describe 'admin/application_settings/_repository_storage.html.haml' do
let(:app_settings) { create(:application_setting) }
let(:repository_storages_weighted_attributes) { [:repository_storages_weighted_default, :repository_storages_weighted_mepmep, :repository_storages_weighted_foobar]}
let(:repository_storages_weighted) do
{
"default" => 100,
"mepmep" => 50
}
end
let(:app_settings) { build(:application_setting, repository_storages_weighted: repository_storages_weighted) }
before do
allow(app_settings).to receive(:repository_storages_weighted).and_return(repository_storages_weighted)
allow(app_settings).to receive(:repository_storages_weighted_mepmep).and_return(100)
allow(app_settings).to receive(:repository_storages_weighted_foobar).and_return(50)
stub_storage_settings({ 'default': {}, 'mepmep': {}, 'foobar': {} })
assign(:application_setting, app_settings)
allow(ApplicationSetting).to receive(:repository_storages_weighted_attributes).and_return(repository_storages_weighted_attributes)
end
context 'when multiple storages are available' do
context 'additional storage config' do
let(:repository_storages_weighted) do
{
'default' => 100,
'mepmep' => 50
}
end
it 'lists them all' do
render
# lists storages that are saved with weights
repository_storages_weighted.each do |storage_name, storage_weight|
Gitlab.config.repositories.storages.keys.each do |storage_name|
expect(rendered).to have_content(storage_name)
end
# lists storage not saved with weight
expect(rendered).to have_content('foobar')
end
end
context 'fewer storage configs' do
let(:repository_storages_weighted) do
{
'default' => 100,
'mepmep' => 50,
'something_old' => 100
}
end
it 'lists only configured storages' do
render
Gitlab.config.repositories.storages.keys.each do |storage_name|
expect(rendered).to have_content(storage_name)
end
expect(rendered).not_to have_content('something_old')
end
end
end