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 # Browser detection
gem 'browser', '~> 4.2' gem 'browser', '~> 4.2'
# OS detection for usage ping
gem 'ohai', '~> 16.10'
# GPG # GPG
gem 'gpgme', '~> 2.0.19' gem 'gpgme', '~> 2.0.19'

View File

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

View File

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

View File

@ -86,7 +86,11 @@ export default {
<template> <template>
<span> <span>
<span ref="issueDueDate" :class="cssClass" class="board-card-info card-number"> <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">{{ <time :class="{ 'text-danger': isPastDue }" datetime="date" class="board-card-info-text">{{
body body
}}</time> }}</time>

View File

@ -37,7 +37,7 @@ export default {
<template> <template>
<span> <span>
<span ref="issueTimeEstimate" class="board-card-info card-number"> <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> <time class="board-card-info-text">{{ timeEstimate }}</time>
</span> </span>
<gl-tooltip <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) => { removeList: ({ state, commit }, listId) => {
const listsBackup = { ...state.boardLists }; 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 RECEIVE_ADD_LIST_ERROR = 'RECEIVE_ADD_LIST_ERROR';
export const MOVE_LIST = 'MOVE_LIST'; export const MOVE_LIST = 'MOVE_LIST';
export const UPDATE_LIST_FAILURE = 'UPDATE_LIST_FAILURE'; 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 = 'REMOVE_LIST';
export const REMOVE_LIST_FAILURE = 'REMOVE_LIST_FAILURE'; export const REMOVE_LIST_FAILURE = 'REMOVE_LIST_FAILURE';
export const REQUEST_ITEMS_FOR_LIST = 'REQUEST_ITEMS_FOR_LIST'; export const REQUEST_ITEMS_FOR_LIST = 'REQUEST_ITEMS_FOR_LIST';

View File

@ -105,6 +105,10 @@ export default {
Vue.set(state, 'boardLists', backupList); Vue.set(state, 'boardLists', backupList);
}, },
[mutationTypes.TOGGLE_LIST_COLLAPSED]: (state, { listId, collapsed }) => {
Vue.set(state.boardLists[listId], 'collapsed', collapsed);
},
[mutationTypes.REMOVE_LIST]: (state, listId) => { [mutationTypes.REMOVE_LIST]: (state, listId) => {
Vue.delete(state.boardLists, 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-input id="fork-name" v-model="fork.name" data-testid="fork-name-input" required />
</gl-form-group> </gl-form-group>
<div class="gl-display-flex"> <div class="gl-md-display-flex">
<div class="gl-w-half"> <div class="gl-flex-basis-half">
<gl-form-group label="Project URL" label-for="fork-url" class="gl-pr-2"> <gl-form-group label="Project URL" label-for="fork-url" class="gl-md-mr-3">
<gl-form-input-group> <gl-form-input-group>
<template #prepend> <template #prepend>
<gl-input-group-text> <gl-input-group-text>
@ -225,8 +225,8 @@ export default {
</gl-form-input-group> </gl-form-input-group>
</gl-form-group> </gl-form-group>
</div> </div>
<div class="gl-w-half"> <div class="gl-flex-basis-half">
<gl-form-group label="Project slug" label-for="fork-slug" class="gl-pl-2"> <gl-form-group label="Project slug" label-for="fork-slug" class="gl-md-ml-3">
<gl-form-input <gl-form-input
id="fork-slug" id="fork-slug"
v-model="fork.slug" v-model="fork.slug"

View File

@ -55,7 +55,12 @@ export default {
> >
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<gl-icon :size="16" :name="option.icon" /> <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> </div>
<template #help>{{ <template #help>{{
isProjectSnippet && option.description_project isProjectSnippet && option.description_project

View File

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

View File

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

View File

@ -339,3 +339,19 @@
display: inline; 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%; flex-basis: 25%;
} }
// Will be moved to @gitlab/ui in https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1168
.gl-md-ml-3 { .gl-md-ml-3 {
@media (min-width: $breakpoint-md) { @media (min-width: $breakpoint-md) {
margin-left: $gl-spacing-scale-3; margin-left: $gl-spacing-scale-3;

View File

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

View File

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

View File

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

View File

@ -25,10 +25,6 @@ class ApplicationSetting < ApplicationRecord
alias_attribute :instance_group_id, :instance_administrators_group_id alias_attribute :instance_group_id, :instance_administrators_group_id
alias_attribute :instance_administrators_group, :instance_group 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 def self.kroki_formats_attributes
{ {
blockdiag: { blockdiag: {
@ -44,7 +40,6 @@ class ApplicationSetting < ApplicationRecord
end end
store_accessor :kroki_formats, *ApplicationSetting.kroki_formats_attributes.keys, prefix: true 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 # Include here so it can override methods from
# `add_authentication_token_field` # `add_authentication_token_field`
@ -58,8 +53,9 @@ class ApplicationSetting < ApplicationRecord
serialize :domain_allowlist, Array # rubocop:disable Cop/ActiveRecordSerialize serialize :domain_allowlist, Array # rubocop:disable Cop/ActiveRecordSerialize
serialize :domain_denylist, Array # rubocop:disable Cop/ActiveRecordSerialize serialize :domain_denylist, Array # rubocop:disable Cop/ActiveRecordSerialize
serialize :repository_storages # 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 # 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 serialize :asset_proxy_whitelist, Array # rubocop:disable Cop/ActiveRecordSerialize
cache_markdown_field :sign_in_text cache_markdown_field :sign_in_text
@ -502,6 +498,7 @@ class ApplicationSetting < ApplicationRecord
inclusion: { in: [true, false], message: _('must be a boolean value') } inclusion: { in: [true, false], message: _('must be a boolean value') }
before_validation :ensure_uuid! before_validation :ensure_uuid!
before_validation :coerce_repository_storages_weighted, if: :repository_storages_weighted_changed?
before_save :ensure_runners_registration_token before_save :ensure_runners_registration_token
before_save :ensure_health_check_access_token before_save :ensure_health_check_access_token
@ -582,12 +579,6 @@ class ApplicationSetting < ApplicationRecord
recaptcha_enabled || login_recaptcha_protection_enabled recaptcha_enabled || login_recaptcha_protection_enabled
end 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| kroki_formats_attributes.keys.each do |key|
define_method :"kroki_formats_#{key}=" do |value| define_method :"kroki_formats_#{key}=" do |value|
super(::Gitlab::Utils.to_boolean(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) self.notes_create_limit_allowlist = strings_to_array(values).map(&:downcase)
end end
def asset_proxy_allowlist=(values) def asset_proxy_whitelist=(values)
values = strings_to_array(values) if values.is_a?(String) values = strings_to_array(values) if values.is_a?(String)
# make sure we always allow the running host # make sure we always allow the running host
values << Gitlab.config.gitlab.host unless values.include?(Gitlab.config.gitlab.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 end
def repository_storages def repository_storages
Array(read_attribute(:repository_storages)) Array(read_attribute(:repository_storages))
end end
def repository_storages_weighted
read_attribute(:repository_storages_weighted)
end
def commit_email_hostname def commit_email_hostname
super.presence || self.class.default_commit_email_hostname super.presence || self.class.default_commit_email_hostname
end end
@ -328,9 +329,10 @@ module ApplicationSettingImplementation
def normalized_repository_storage_weights def normalized_repository_storage_weights
strong_memoize(:normalized_repository_storage_weights) do 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 next w if weights_total == 0
w.to_f / weights_total w.to_f / weights_total
@ -468,16 +470,20 @@ module ApplicationSettingImplementation
invalid.empty? invalid.empty?
end end
def coerce_repository_storages_weighted
repository_storages_weighted.transform_values!(&:to_i)
end
def check_repository_storages_weighted def check_repository_storages_weighted
invalid = repository_storages_weighted.keys - Gitlab.config.repositories.storages.keys 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? invalid.empty?
repository_storages_weighted.each do |key, val| repository_storages_weighted.each do |key, val|
next unless val.present? 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, _("value for '%{storage}' must be an integer") % { storage: key }) 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 between 0 and 100") % { storage: key }) unless val.between?(0, 100)
end end
end end

View File

@ -6,7 +6,7 @@ module ApplicationSettings
attr_reader :params, :application_setting 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 def execute
result = update_settings result = update_settings

View File

@ -31,6 +31,7 @@ module MergeRequests
old_mentioned_users = old_associations.fetch(:mentioned_users, []) old_mentioned_users = old_associations.fetch(:mentioned_users, [])
old_assignees = old_associations.fetch(:assignees, []) old_assignees = old_associations.fetch(:assignees, [])
old_reviewers = old_associations.fetch(:reviewers, []) old_reviewers = old_associations.fetch(:reviewers, [])
old_timelogs = old_associations.fetch(:timelogs, [])
changed_fields = merge_request.previous_changes.keys changed_fields = merge_request.previous_changes.keys
resolve_todos(merge_request, old_labels, old_assignees, old_reviewers) resolve_todos(merge_request, old_labels, old_assignees, old_reviewers)
@ -48,6 +49,7 @@ module MergeRequests
track_title_and_desc_edits(changed_fields) track_title_and_desc_edits(changed_fields)
track_discussion_lock_toggle(merge_request, 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_labels_added(merge_request, old_labels)
notify_if_mentions_added(merge_request, old_mentioned_users) notify_if_mentions_added(merge_request, old_mentioned_users)
@ -106,6 +108,11 @@ module MergeRequests
end end
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) def notify_if_labels_added(merge_request, old_labels)
added_labels = merge_request.labels - old_labels added_labels = merge_request.labels - old_labels

View File

@ -18,8 +18,9 @@
= _('Enter weights for storages for new repositories.') = _('Enter weights for storages for new repositories.')
= link_to sprite_icon('question-o'), help_page_path('administration/repository_storage_paths') = link_to sprite_icon('question-o'), help_page_path('administration/repository_storage_paths')
.form-check .form-check
- storage_weights.each do |attribute| = f.fields_for :repository_storages_weighted, storage_weights do |storage_form|
= f.text_field attribute[:name], class: 'form-text-input', value: attribute[:value] - Gitlab.config.repositories.storages.keys.each do |storage|
= f.label attribute[:label], attribute[:label], class: 'label-bold form-check-label' = storage_form.text_field storage, class: 'form-text-input'
%br = 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" = 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/import.css"
config.assets.precompile << "page_bundles/incident_management_list.css" config.assets.precompile << "page_bundles/incident_management_list.css"
config.assets.precompile << "page_bundles/issues_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.css"
config.assets.precompile << "page_bundles/jira_connect_users.css" config.assets.precompile << "page_bundles/jira_connect_users.css"
config.assets.precompile << "page_bundles/learn_gitlab.css" config.assets.precompile << "page_bundles/learn_gitlab.css"

View File

@ -1,7 +1,7 @@
--- ---
name: ci_reduce_queries_when_ticking_runner_queue name: ci_reduce_queries_when_ticking_runner_queue
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55496 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' milestone: '13.10'
type: development type: development
group: group::continuous integration 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, invisible_captcha_enabled boolean DEFAULT false NOT NULL,
enforce_ssh_key_expiration boolean DEFAULT false NOT NULL, enforce_ssh_key_expiration boolean DEFAULT false NOT NULL,
git_two_factor_session_expiry integer DEFAULT 15 NOT NULL, git_two_factor_session_expiry integer DEFAULT 15 NOT NULL,
asset_proxy_allowlist text,
keep_latest_artifact boolean DEFAULT true NOT NULL, keep_latest_artifact boolean DEFAULT true NOT NULL,
notes_create_limit integer DEFAULT 300 NOT NULL, notes_create_limit integer DEFAULT 300 NOT NULL,
notes_create_limit_allowlist text[] DEFAULT '{}'::text[] NOT NULL, notes_create_limit_allowlist text[] DEFAULT '{}'::text[] NOT NULL,
kroki_formats jsonb DEFAULT '{}'::jsonb NOT NULL, kroki_formats jsonb DEFAULT '{}'::jsonb NOT NULL,
in_product_marketing_emails_enabled boolean DEFAULT true 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_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 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)), 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` | | | `tier` | |
| `skip_validation` | true | | `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` ## `redis_hll_counters.code_review.i_code_review_user_toggled_task_item_status_monthly`
Missing description Missing description
@ -20846,6 +20926,26 @@ Is encrypted LDAP secrets configured?
| `tier` | free, premium, ultimate | | `tier` | free, premium, ultimate |
| `skip_validation` | true | | `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` ## `signup_enabled`
Whether public signup is enabled Whether public signup is enabled

View File

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

View File

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

View File

@ -77,12 +77,22 @@ module Gitlab
def add_approval!(user_id) def add_approval!(user_id)
return unless review.review_type == 'APPROVED' return unless review.review_type == 'APPROVED'
add_approval_system_note!(user_id) approval_attribues = {
merge_request_id: merge_request.id,
merge_request.approvals.create!(
user_id: user_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 end
def add_approval_system_note!(user_id) def add_approval_system_note!(user_id)

View File

@ -242,7 +242,8 @@ module Gitlab
def system_usage_data_settings def system_usage_data_settings
{ {
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 end
@ -505,6 +506,17 @@ module Gitlab
end end
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) def last_28_days_time_period(column: :created_at)
{ column => 30.days.ago..2.days.ago } { column => 30.days.ago..2.days.ago }
end end

View File

@ -44,7 +44,9 @@
'i_code_review_user_toggled_task_item_status', 'i_code_review_user_toggled_task_item_status',
'i_code_review_user_create_mr_from_issue', 'i_code_review_user_create_mr_from_issue',
'i_code_review_user_mr_discussion_locked', '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 - name: code_review_category_monthly_active_users
operator: OR operator: OR
@ -82,7 +84,9 @@
'i_code_review_user_toggled_task_item_status', 'i_code_review_user_toggled_task_item_status',
'i_code_review_user_create_mr_from_issue', 'i_code_review_user_create_mr_from_issue',
'i_code_review_user_mr_discussion_locked', '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 - name: code_review_extension_category_monthly_active_users
operator: OR operator: OR

View File

@ -174,3 +174,13 @@
category: code_review category: code_review
aggregation: weekly aggregation: weekly
feature_flag: usage_data_i_code_review_user_mr_discussion_unlocked 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_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_LOCKED_ACTION = 'i_code_review_user_mr_discussion_locked'
MR_DISCUSSION_UNLOCKED_ACTION = 'i_code_review_user_mr_discussion_unlocked' 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 class << self
def track_mr_diffs_action(merge_request:) def track_mr_diffs_action(merge_request:)
@ -163,6 +165,14 @@ module Gitlab
track_unique_action_by_user(MR_DISCUSSION_UNLOCKED_ACTION, user) track_unique_action_by_user(MR_DISCUSSION_UNLOCKED_ACTION, user)
end 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 private
def track_unique_action_by_merge_request(action, merge_request) def track_unique_action_by_merge_request(action, merge_request)

View File

@ -173,6 +173,11 @@ msgid_plural "%d days"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "%d epic"
msgid_plural "%d epics"
msgstr[0] ""
msgstr[1] ""
msgid "%d error" msgid "%d error"
msgid_plural "%d errors" msgid_plural "%d errors"
msgstr[0] "" msgstr[0] ""
@ -559,6 +564,9 @@ msgstr ""
msgid "%{issuesSize} with a limit of %{maxIssueCount}" msgid "%{issuesSize} with a limit of %{maxIssueCount}"
msgstr "" msgstr ""
msgid "%{itemsCount} issues with a limit of %{maxIssueCount}"
msgstr ""
msgid "%{labelStart}Actual response:%{labelEnd} %{headers}" msgid "%{labelStart}Actual response:%{labelEnd} %{headers}"
msgstr "" msgstr ""
@ -34912,6 +34920,9 @@ msgstr ""
msgid "can't be enabled because signed commits are required for this project" msgid "can't be enabled because signed commits are required for this project"
msgstr "" msgstr ""
msgid "can't include: %{invalid_storages}"
msgstr ""
msgid "cannot be a date in the past" msgid "cannot be a date in the past"
msgstr "" msgstr ""
@ -36318,6 +36329,12 @@ msgstr ""
msgid "v%{version} published %{timeAgo}" msgid "v%{version} published %{timeAgo}"
msgstr "" msgstr ""
msgid "value for '%{storage}' must be an integer"
msgstr ""
msgid "value for '%{storage}' must be between 0 and 100"
msgstr ""
msgid "verify ownership" msgid "verify ownership"
msgstr "" 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 # 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 element :_, "qa_selector = local_assigns.fetch(:qa_selector, '')" # rubocop:disable QA/ElementWithPattern
end end
base.view 'app/assets/javascripts/snippets/components/snippet_visibility_edit.vue' do
element :visibility_content
end
end end
def fill_title(title) def fill_title(title)
@ -44,7 +48,7 @@ module QA
end end
def set_visibility(visibility) def set_visibility(visibility)
choose visibility click_element(:visibility_content, visibility: visibility)
end end
def fill_file_name(name, file_number = nil) def fill_file_name(name, file_number = nil)

View File

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

View File

@ -144,10 +144,10 @@ RSpec.describe Admin::ApplicationSettingsController do
end end
it 'updates repository_storages_weighted setting' do 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(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 end
it 'updates kroki_formats setting' do it 'updates kroki_formats setting' do

View File

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

View File

@ -384,7 +384,20 @@ RSpec.describe 'Admin updates settings' do
click_button 'Save changes' click_button 'Save changes'
end 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
end end

View File

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

View File

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

View File

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

View File

@ -27,6 +27,22 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
expect(report_status).to eq(described_class::STATUS_SUCCESS) expect(report_status).to eq(described_class::STATUS_SUCCESS)
end end
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 end
describe '#errors_count' do describe '#errors_count' do
@ -93,6 +109,14 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
expect(resolved_count).to be_zero expect(resolved_count).to be_zero
end end
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 end
describe '#total_count' do describe '#total_count' do
@ -140,6 +164,14 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
expect(total_count).to eq(2) expect(total_count).to eq(2)
end end
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 end
describe '#existing_errors' do describe '#existing_errors' do
@ -177,6 +209,14 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
expect(existing_errors).to be_empty expect(existing_errors).to be_empty
end end
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 end
describe '#new_errors' do describe '#new_errors' do
@ -213,6 +253,14 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
expect(new_errors).to eq([degradation_1]) expect(new_errors).to eq([degradation_1])
end end
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 end
describe '#resolved_errors' do describe '#resolved_errors' do
@ -250,5 +298,13 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
expect(resolved_errors).to be_empty expect(resolved_errors).to be_empty
end end
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
end end

View File

@ -49,10 +49,6 @@ RSpec.describe Gitlab::Ci::Reports::ReportsComparer do
context 'when base_report is nil' do context 'when base_report is nil' do
let(:base_report) { nil } let(:base_report) { nil }
before do
allow(comparer).to receive(:success?).and_return(false)
end
it 'returns status not_found' do it 'returns status not_found' do
expect(status).to eq('not_found') expect(status).to eq('not_found')
end end
@ -61,10 +57,6 @@ RSpec.describe Gitlab::Ci::Reports::ReportsComparer do
context 'when head_report is nil' do context 'when head_report is nil' do
let(:head_report) { nil } let(:head_report) { nil }
before do
allow(comparer).to receive(:success?).and_return(false)
end
it 'returns status not_found' do it 'returns status not_found' do
expect(status).to eq('not_found') expect(status).to eq('not_found')
end end
@ -118,4 +110,22 @@ RSpec.describe Gitlab::Ci::Reports::ReportsComparer do
expect { total_count }.to raise_error(NotImplementedError) expect { total_count }.to raise_error(NotImplementedError)
end end
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 end

View File

@ -19,8 +19,10 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestReviewImporter, :clean
context 'when the review is "APPROVED"' do context 'when the review is "APPROVED"' do
let(:review) { create_review(type: 'APPROVED', note: '') } let(:review) { create_review(type: 'APPROVED', note: '') }
it 'creates a note for the review' do it 'creates a note for the review and approves the Merge Request' do
expect { subject.execute }.to change(Note, :count) expect { subject.execute }
.to change(Note, :count).by(1)
.and change(Approval, :count).by(1)
last_note = merge_request.notes.last last_note = merge_request.notes.last
expect(last_note.note).to eq('approved this merge request') 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.approved_by_users.reload).to include(author)
expect(merge_request.approvals.last.created_at).to eq(submitted_at) expect(merge_request.approvals.last.created_at).to eq(submitted_at)
end 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 end
context 'when the review is "COMMENTED"' do context 'when the review is "COMMENTED"' do

View File

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

View File

@ -1129,12 +1129,40 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
end end
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 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 } subject { described_class.system_usage_data_settings }
it 'gathers settings usage data', :aggregate_failures do it 'gathers settings usage data', :aggregate_failures do
expect(subject[:settings][:ldap_encrypted_secrets_enabled]).to eq(Gitlab::Auth::Ldap::Config.encrypted_secrets.active?) expect(subject[:settings][:ldap_encrypted_secrets_enabled]).to eq(Gitlab::Auth::Ldap::Config.encrypted_secrets.active?)
end end
it 'populates operating system information' do
expect(subject[:settings][:operating_system]).to eq('ubuntu-20.04')
end
end 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(false).for(:hashed_storage_enabled) }
it { is_expected.not_to allow_value(101).for(:repository_storages_weighted_default) } it { is_expected.to allow_value('default' => 0).for(:repository_storages_weighted) }
it { is_expected.to allow_value('90').for(:repository_storages_weighted_default) } it { is_expected.to allow_value('default' => 50).for(:repository_storages_weighted) }
it { is_expected.not_to allow_value(-1).for(:repository_storages_weighted_default) } it { is_expected.to allow_value('default' => 100).for(:repository_storages_weighted) }
it { is_expected.to allow_value(100).for(:repository_storages_weighted_default) } it { is_expected.to allow_value('default' => '90').for(:repository_storages_weighted) }
it { is_expected.to allow_value(0).for(:repository_storages_weighted_default) } it { is_expected.to allow_value('default' => nil).for(:repository_storages_weighted) }
it { is_expected.to allow_value(50).for(:repository_storages_weighted_default) } 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.to allow_value(nil).for(:repository_storages_weighted_default) } 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) } 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.to allow_value(400).for(:notes_create_limit) }
it { is_expected.not_to allow_value('two').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
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 describe '#asset_proxy_allowlist' do
context 'when given an Array' do context 'when given an Array' do
it 'sets the domains and adds current running host' 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' 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 describe 'kroki_format_supported?' do
it 'returns true when Excalidraw is enabled' do it 'returns true when Excalidraw is enabled' do
subject.kroki_formats_excalidraw = true subject.kroki_formats_excalidraw = true
@ -1007,11 +1027,4 @@ RSpec.describe ApplicationSetting do
expect(subject.kroki_formats_excalidraw).to eq(true) expect(subject.kroki_formats_excalidraw).to eq(true)
end end
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 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_url: 'http://test.com' }
it_behaves_like 'invalidates markdown cache', { asset_proxy_secret_key: 'another secret' } 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_allowlist: ['domain.com'] }
it_behaves_like 'invalidates markdown cache', { asset_proxy_whitelist: ['domain.com'] }
context 'when also setting the local_markdown_version' do context 'when also setting the local_markdown_version' do
let(:params) { { asset_proxy_enabled: true, local_markdown_version: 12 } } 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 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 end
context 'updating milestone' do context 'updating milestone' do

View File

@ -31,9 +31,34 @@ RSpec.describe Projects::DestroyService, :aggregate_failures do
end end
shared_examples 'deleting the project with pipeline and build' do 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!(: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' it_behaves_like 'deleting the project'
end end

View File

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

View File

@ -3,34 +3,49 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'admin/application_settings/_repository_storage.html.haml' do RSpec.describe 'admin/application_settings/_repository_storage.html.haml' do
let(:app_settings) { create(:application_setting) } let(:app_settings) { build(:application_setting, repository_storages_weighted: repository_storages_weighted) }
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
before do before do
allow(app_settings).to receive(:repository_storages_weighted).and_return(repository_storages_weighted) stub_storage_settings({ 'default': {}, 'mepmep': {}, 'foobar': {} })
allow(app_settings).to receive(:repository_storages_weighted_mepmep).and_return(100)
allow(app_settings).to receive(:repository_storages_weighted_foobar).and_return(50)
assign(:application_setting, app_settings) assign(:application_setting, app_settings)
allow(ApplicationSetting).to receive(:repository_storages_weighted_attributes).and_return(repository_storages_weighted_attributes)
end 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 it 'lists them all' do
render render
# lists storages that are saved with weights Gitlab.config.repositories.storages.keys.each do |storage_name|
repository_storages_weighted.each do |storage_name, storage_weight|
expect(rendered).to have_content(storage_name) expect(rendered).to have_content(storage_name)
end end
# lists storage not saved with weight
expect(rendered).to have_content('foobar') expect(rendered).to have_content('foobar')
end end
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 end