Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-07-09 15:08:17 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-07-09 15:08:17 +0300
commita373ecffca1512404cb4f237399e3bf275d87f69 (patch)
tree0c131c86a4f88b427c5d644cd14c4596daa87d36
parent1613500bf7400f5692a55fd65235a4a10fc40a7d (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml8
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml2
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock9
-rw-r--r--app/assets/javascripts/blob/components/blob_header_filepath.vue2
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_bundle.js6
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue2
-rw-r--r--app/assets/javascripts/error_tracking/components/stacktrace_entry.vue2
-rw-r--r--app/assets/javascripts/issuable_context.js30
-rw-r--r--app/assets/javascripts/issuable_show/components/issuable_show_root.vue6
-rw-r--r--app/assets/javascripts/issuable_sidebar/components/issuable_sidebar_root.vue23
-rw-r--r--app/assets/javascripts/issuable_sidebar/constants.js1
-rw-r--r--app/assets/javascripts/issue_show/queries/update_issue.mutation.graphql4
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflict_resolver_app.vue2
-rw-r--r--app/assets/javascripts/merge_request.js8
-rw-r--r--app/assets/javascripts/merge_request_tabs.js11
-rw-r--r--app/assets/javascripts/security_configuration/components/constants.js2
-rw-r--r--app/assets/javascripts/sidebar/queries/updateStatus.mutation.graphql3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/file_icon.vue1
-rw-r--r--app/controllers/search_controller.rb11
-rw-r--r--app/finders/concerns/merged_at_filter.rb20
-rw-r--r--app/helpers/issuables_helper.rb9
-rw-r--r--app/helpers/search_helper.rb2
-rw-r--r--app/helpers/sorting_helper.rb8
-rw-r--r--app/helpers/sorting_titles_values_helper.rb24
-rw-r--r--app/models/issue.rb19
-rw-r--r--app/models/merge_request.rb5
-rw-r--r--app/views/search/_results.html.haml24
-rw-r--r--app/views/search/results/_timeout.html.haml10
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--app/views/shared/issuable/_sort_dropdown.html.haml2
-rw-r--r--app/workers/container_expiration_policies/cleanup_container_repository_worker.rb10
-rw-r--r--app/workers/container_expiration_policy_worker.rb21
-rw-r--r--db/migrate/20210705130919_create_container_repos_on_exp_cleanup_status_project_id_start_date_index.rb24
-rw-r--r--db/schema_migrations/202107051309191
-rw-r--r--db/structure.sql2
-rw-r--r--doc/development/code_review.md1
-rw-r--r--doc/development/pipelines.md2
-rw-r--r--doc/integration/jira/dvcs.md3
-rw-r--r--doc/user/admin_area/custom_project_templates.md29
-rw-r--r--doc/user/search/index.md6
-rw-r--r--lib/gitlab/database/load_balancing/sidekiq_client_middleware.rb26
-rw-r--r--lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb36
-rw-r--r--locale/gitlab.pot66
-rwxr-xr-xscripts/review_apps/review-apps.sh6
-rw-r--r--spec/features/merge_requests/user_lists_merge_requests_spec.rb16
-rw-r--r--spec/features/search/user_searches_for_code_spec.rb1
-rw-r--r--spec/features/search/user_searches_for_comments_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_commits_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_issues_spec.rb1
-rw-r--r--spec/features/search/user_searches_for_merge_requests_spec.rb1
-rw-r--r--spec/features/search/user_searches_for_milestones_spec.rb1
-rw-r--r--spec/features/search/user_searches_for_projects_spec.rb1
-rw-r--r--spec/features/search/user_searches_for_users_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_wiki_pages_spec.rb1
-rw-r--r--spec/frontend/blob/components/__snapshots__/blob_header_filepath_spec.js.snap2
-rw-r--r--spec/frontend/issuable_show/components/issuable_show_root_spec.js8
-rw-r--r--spec/frontend/issuable_sidebar/components/issuable_sidebar_root_spec.js214
-rw-r--r--spec/lib/gitlab/database/load_balancing/sidekiq_client_middleware_spec.rb61
-rw-r--r--spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb100
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb10
-rw-r--r--spec/models/issue_spec.rb54
-rw-r--r--spec/models/merge_request_spec.rb16
-rw-r--r--spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb21
-rw-r--r--spec/tooling/danger/feature_flag_spec.rb2
-rw-r--r--spec/workers/container_expiration_policies/cleanup_container_repository_worker_spec.rb45
-rw-r--r--spec/workers/container_expiration_policy_worker_spec.rb35
-rw-r--r--tooling/danger/feature_flag.rb3
70 files changed, 669 insertions, 426 deletions
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index 0b6675ab84f..b7a4ef45240 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -94,13 +94,13 @@ review-deploy:
before_script:
- *base-before_script
-review-stop-failed-deployment:
+review-delete-deployment:
extends:
- .review-stop-base
- - .review:rules:review-stop-failed-deployment
+ - .review:rules:review-delete-deployment
stage: prepare
script:
- - delete_failed_release
+ - delete_release
review-stop:
extends:
@@ -108,7 +108,7 @@ review-stop:
- .review:rules:review-stop
stage: post-qa
script:
- - delete_release
+ - delete_k8s_release_namespace
.review-qa-base:
extends:
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 55ebd3ba038..b9cf5a7d5bd 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -1180,7 +1180,7 @@
- <<: *if-dot-com-gitlab-org-schedule
allow_failure: true
-.review:rules:review-stop-failed-deployment:
+.review:rules:review-delete-deployment:
rules:
- <<: *if-not-ee
when: never
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index af8db279ebd..28c55f889ba 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-fef798978197809e268e5395838b4f4eeb4288e9
+49893735ed64feebc208f4efe90bebbb8bbb02ad
diff --git a/Gemfile b/Gemfile
index 5a52a7e13ad..52bbd458808 100644
--- a/Gemfile
+++ b/Gemfile
@@ -394,7 +394,7 @@ group :development, :test do
end
group :development, :test, :danger do
- gem 'gitlab-dangerfiles', '~> 2.1.2', require: false
+ gem 'gitlab-dangerfiles', '~> 2.2.1', require: false
end
group :development, :test, :coverage do
diff --git a/Gemfile.lock b/Gemfile.lock
index 9237261d7c1..44422535cc9 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -229,7 +229,7 @@ GEM
css_parser (1.7.0)
addressable
daemons (1.3.1)
- danger (8.2.3)
+ danger (8.3.1)
claide (~> 1.0)
claide-plugins (>= 0.9.2)
colored2 (~> 3.1)
@@ -468,8 +468,9 @@ GEM
terminal-table (~> 1.5, >= 1.5.1)
gitlab-chronic (0.10.5)
numerizer (~> 0.2)
- gitlab-dangerfiles (2.1.2)
- danger-gitlab
+ gitlab-dangerfiles (2.2.1)
+ danger (>= 8.3.1)
+ danger-gitlab (>= 8.0.0)
gitlab-experiment (0.6.1)
activesupport (>= 3.0)
request_store (>= 1.0)
@@ -1488,7 +1489,7 @@ DEPENDENCIES
gitaly (~> 14.1.0.pre.rc2)
github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5)
- gitlab-dangerfiles (~> 2.1.2)
+ gitlab-dangerfiles (~> 2.2.1)
gitlab-experiment (~> 0.6.1)
gitlab-fog-azure-rm (~> 1.1.1)
gitlab-labkit (~> 0.18.0)
diff --git a/app/assets/javascripts/blob/components/blob_header_filepath.vue b/app/assets/javascripts/blob/components/blob_header_filepath.vue
index 99fe3938046..cb441a7e491 100644
--- a/app/assets/javascripts/blob/components/blob_header_filepath.vue
+++ b/app/assets/javascripts/blob/components/blob_header_filepath.vue
@@ -29,7 +29,7 @@ export default {
<slot name="filepath-prepend"></slot>
<template v-if="blob.path">
- <file-icon :file-name="blob.path" :size="18" aria-hidden="true" css-classes="mr-2" />
+ <file-icon :file-name="blob.path" :size="16" aria-hidden="true" css-classes="mr-2" />
<strong
class="file-title-name mr-1 js-blob-header-filepath"
data-qa-selector="file_title_content"
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js
index 60cfb758e14..8d88b682df2 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js
+++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js
@@ -1,5 +1,4 @@
import Vue from 'vue';
-import CommitPipelinesTable from './pipelines_table.vue';
/**
* Used in:
@@ -23,12 +22,15 @@ export default () => {
if (pipelineTableViewEl.dataset.disableInitialization === undefined) {
const table = new Vue({
+ components: {
+ CommitPipelinesTable: () => import('~/commit/pipelines/pipelines_table.vue'),
+ },
provide: {
artifactsEndpoint: pipelineTableViewEl.dataset.artifactsEndpoint,
artifactsEndpointPlaceholder: pipelineTableViewEl.dataset.artifactsEndpointPlaceholder,
},
render(createElement) {
- return createElement(CommitPipelinesTable, {
+ return createElement('commit-pipelines-table', {
props: {
endpoint: pipelineTableViewEl.dataset.endpoint,
emptyStateSvgPath: pipelineTableViewEl.dataset.emptyStateSvgPath,
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index 25d35b18fae..b7fbaaecb1d 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -306,7 +306,7 @@ export default {
>
<file-icon
:file-name="filePath"
- :size="18"
+ :size="16"
aria-hidden="true"
css-classes="gl-mr-2"
:submodule="diffFile.submodule"
diff --git a/app/assets/javascripts/error_tracking/components/stacktrace_entry.vue b/app/assets/javascripts/error_tracking/components/stacktrace_entry.vue
index dd320676e98..68b4438831e 100644
--- a/app/assets/javascripts/error_tracking/components/stacktrace_entry.vue
+++ b/app/assets/javascripts/error_tracking/components/stacktrace_entry.vue
@@ -82,7 +82,7 @@ export default {
<div v-if="hasCode" class="d-inline-block cursor-pointer" @click="toggle()">
<gl-icon :name="collapseIcon" :size="16" class="gl-mr-2" />
</div>
- <file-icon :file-name="filePath" :size="18" aria-hidden="true" css-classes="gl-mr-2" />
+ <file-icon :file-name="filePath" :size="16" aria-hidden="true" css-classes="gl-mr-2" />
<strong
v-gl-tooltip
:title="filePath"
diff --git a/app/assets/javascripts/issuable_context.js b/app/assets/javascripts/issuable_context.js
index a87d4f077cc..51b5237a339 100644
--- a/app/assets/javascripts/issuable_context.js
+++ b/app/assets/javascripts/issuable_context.js
@@ -9,19 +9,23 @@ export default class IssuableContext {
this.userSelect = new UsersSelect(currentUser);
this.reviewersSelect = new UsersSelect(currentUser, '.js-reviewer-search');
- import(/* webpackChunkName: 'select2' */ 'select2/select2')
- .then(() => {
- // eslint-disable-next-line promise/no-nesting
- loadCSSFile(gon.select2_css_path)
- .then(() => {
- $('select.select2').select2({
- width: 'resolve',
- dropdownAutoWidth: true,
- });
- })
- .catch(() => {});
- })
- .catch(() => {});
+ const $select2 = $('select.select2');
+
+ if ($select2.length) {
+ import(/* webpackChunkName: 'select2' */ 'select2/select2')
+ .then(() => {
+ // eslint-disable-next-line promise/no-nesting
+ loadCSSFile(gon.select2_css_path)
+ .then(() => {
+ $select2.select2({
+ width: 'resolve',
+ dropdownAutoWidth: true,
+ });
+ })
+ .catch(() => {});
+ })
+ .catch(() => {});
+ }
$('.issuable-sidebar .inline-update').on('change', 'select', function onClickSelect() {
return $(this).submit();
diff --git a/app/assets/javascripts/issuable_show/components/issuable_show_root.vue b/app/assets/javascripts/issuable_show/components/issuable_show_root.vue
index ca057094868..011db52cbe3 100644
--- a/app/assets/javascripts/issuable_show/components/issuable_show_root.vue
+++ b/app/assets/javascripts/issuable_show/components/issuable_show_root.vue
@@ -153,9 +153,9 @@ export default {
</template>
</issuable-discussion>
- <issuable-sidebar @sidebar-toggle="$emit('sidebar-toggle', $event)">
- <template #right-sidebar-items="sidebarProps">
- <slot name="right-sidebar-items" v-bind="sidebarProps"></slot>
+ <issuable-sidebar>
+ <template #right-sidebar-items="{ sidebarExpanded, toggleSidebar }">
+ <slot name="right-sidebar-items" v-bind="{ sidebarExpanded, toggleSidebar }"></slot>
</template>
</issuable-sidebar>
</div>
diff --git a/app/assets/javascripts/issuable_sidebar/components/issuable_sidebar_root.vue b/app/assets/javascripts/issuable_sidebar/components/issuable_sidebar_root.vue
index 8a159139af0..99dcccd12ed 100644
--- a/app/assets/javascripts/issuable_sidebar/components/issuable_sidebar_root.vue
+++ b/app/assets/javascripts/issuable_sidebar/components/issuable_sidebar_root.vue
@@ -2,15 +2,15 @@
import { GlIcon } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import Cookies from 'js-cookie';
-
import { parseBoolean } from '~/lib/utils/common_utils';
+import { USER_COLLAPSED_GUTTER_COOKIE } from '../constants';
export default {
components: {
GlIcon,
},
data() {
- const userExpanded = !parseBoolean(Cookies.get('collapsed_gutter'));
+ const userExpanded = !parseBoolean(Cookies.get(USER_COLLAPSED_GUTTER_COOKIE));
// We're deliberately keeping two different props for sidebar status;
// 1. userExpanded reflects value based on cookie `collapsed_gutter`.
@@ -20,13 +20,6 @@ export default {
isExpanded: userExpanded ? bp.isDesktop() : userExpanded,
};
},
- watch: {
- isExpanded(expanded) {
- this.$emit('sidebar-toggle', {
- expanded,
- });
- },
- },
mounted() {
window.addEventListener('resize', this.handleWindowResize);
this.updatePageContainerClass();
@@ -49,11 +42,11 @@ export default {
this.updatePageContainerClass();
}
},
- handleToggleSidebarClick() {
+ toggleSidebar() {
this.isExpanded = !this.isExpanded;
this.userExpanded = this.isExpanded;
- Cookies.set('collapsed_gutter', !this.userExpanded);
+ Cookies.set(USER_COLLAPSED_GUTTER_COOKIE, !this.userExpanded);
this.updatePageContainerClass();
},
},
@@ -68,8 +61,9 @@ export default {
>
<button
class="toggle-right-sidebar-button js-toggle-right-sidebar-button w-100 gl-text-decoration-none! gl-display-flex gl-outline-0!"
+ data-testid="toggle-right-sidebar-button"
:title="__('Toggle sidebar')"
- @click="handleToggleSidebarClick"
+ @click="toggleSidebar"
>
<span v-if="isExpanded" class="collapse-text gl-flex-grow-1 gl-text-left">{{
__('Collapse sidebar')
@@ -83,7 +77,10 @@ export default {
/>
</button>
<div data-testid="sidebar-items" class="issuable-sidebar">
- <slot name="right-sidebar-items" v-bind="{ sidebarExpanded: isExpanded }"></slot>
+ <slot
+ name="right-sidebar-items"
+ v-bind="{ sidebarExpanded: isExpanded, toggleSidebar }"
+ ></slot>
</div>
</aside>
</template>
diff --git a/app/assets/javascripts/issuable_sidebar/constants.js b/app/assets/javascripts/issuable_sidebar/constants.js
new file mode 100644
index 00000000000..4f4b6341a1c
--- /dev/null
+++ b/app/assets/javascripts/issuable_sidebar/constants.js
@@ -0,0 +1 @@
+export const USER_COLLAPSED_GUTTER_COOKIE = 'collapsed_gutter';
diff --git a/app/assets/javascripts/issue_show/queries/update_issue.mutation.graphql b/app/assets/javascripts/issue_show/queries/update_issue.mutation.graphql
index 9c28fdded21..ec8d8f32d8b 100644
--- a/app/assets/javascripts/issue_show/queries/update_issue.mutation.graphql
+++ b/app/assets/javascripts/issue_show/queries/update_issue.mutation.graphql
@@ -1,5 +1,9 @@
mutation updateIssue($input: UpdateIssueInput!) {
updateIssue(input: $input) {
+ issuable: issue {
+ id
+ state
+ }
errors
}
}
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_resolver_app.vue b/app/assets/javascripts/merge_conflicts/merge_conflict_resolver_app.vue
index 3e31e2e93ae..5fcc778a714 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflict_resolver_app.vue
+++ b/app/assets/javascripts/merge_conflicts/merge_conflict_resolver_app.vue
@@ -120,7 +120,7 @@ export default {
>
<div class="js-file-title file-title file-title-flex-parent cursor-default">
<div class="file-header-content" data-testid="file-name">
- <file-icon :file-name="file.filePath" :size="18" css-classes="gl-mr-2" />
+ <file-icon :file-name="file.filePath" :size="16" css-classes="gl-mr-2" />
<strong class="file-title-name">{{ file.filePath }}</strong>
</div>
<div class="file-actions d-flex align-items-center gl-ml-auto gl-align-self-start">
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index feaf8b0d996..0ddb2c2334c 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -148,14 +148,6 @@ MergeRequest.prototype.initCommitMessageListeners = function () {
});
};
-MergeRequest.setStatusBoxToMerged = function () {
- $('.detail-page-header .status-box')
- .removeClass('status-box-open')
- .addClass('status-box-mr-merged')
- .find('span')
- .text(__('Merged'));
-};
-
MergeRequest.decreaseCounter = function (by = 1) {
const $el = $('.js-merge-counter');
const count = Math.max(parseInt($el.text().replace(/[^\d]/, ''), 10) - by, 0);
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 602d6ef611f..c3c3aacae35 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -3,9 +3,7 @@ import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import $ from 'jquery';
import Cookies from 'js-cookie';
import Vue from 'vue';
-import CommitPipelinesTable from '~/commit/pipelines/pipelines_table.vue';
import createEventHub from '~/helpers/event_hub_factory';
-import initAddContextCommitsTriggers from './add_context_commits_modal';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
import Diff from './diff';
import createFlash from './flash';
@@ -341,8 +339,10 @@ export default class MergeRequestTabs {
this.scrollToContainerElement('#commits');
this.toggleLoading(false);
- initAddContextCommitsTriggers();
+
+ return import('./add_context_commits_modal');
})
+ .then((m) => m.default())
.catch(() => {
this.toggleLoading(false);
createFlash({
@@ -356,13 +356,16 @@ export default class MergeRequestTabs {
const { mrWidgetData } = gl;
this.commitPipelinesTable = new Vue({
+ components: {
+ CommitPipelinesTable: () => import('~/commit/pipelines/pipelines_table.vue'),
+ },
provide: {
artifactsEndpoint: pipelineTableViewEl.dataset.artifactsEndpoint,
artifactsEndpointPlaceholder: pipelineTableViewEl.dataset.artifactsEndpointPlaceholder,
targetProjectFullPath: mrWidgetData?.target_project_full_path || '',
},
render(createElement) {
- return createElement(CommitPipelinesTable, {
+ return createElement('commit-pipelines-table', {
props: {
endpoint: pipelineTableViewEl.dataset.endpoint,
emptyStateSvgPath: pipelineTableViewEl.dataset.emptyStateSvgPath,
diff --git a/app/assets/javascripts/security_configuration/components/constants.js b/app/assets/javascripts/security_configuration/components/constants.js
index ea2053b3326..874d51a3577 100644
--- a/app/assets/javascripts/security_configuration/components/constants.js
+++ b/app/assets/javascripts/security_configuration/components/constants.js
@@ -77,7 +77,7 @@ export const CONTAINER_SCANNING_CONFIG_HELP_PATH = helpPagePath(
{ anchor: 'configuration' },
);
-export const CLUSTER_IMAGE_SCANNING_NAME = __('ciReport|Cluster Image Scanning');
+export const CLUSTER_IMAGE_SCANNING_NAME = s__('ciReport|Cluster Image Scanning');
export const CLUSTER_IMAGE_SCANNING_DESCRIPTION = __(
'Check your Kubernetes cluster images for known vulnerabilities.',
);
diff --git a/app/assets/javascripts/sidebar/queries/updateStatus.mutation.graphql b/app/assets/javascripts/sidebar/queries/updateStatus.mutation.graphql
index b45b6b46c8f..28a47735143 100644
--- a/app/assets/javascripts/sidebar/queries/updateStatus.mutation.graphql
+++ b/app/assets/javascripts/sidebar/queries/updateStatus.mutation.graphql
@@ -1,6 +1,7 @@
mutation($projectPath: ID!, $iid: String!, $healthStatus: HealthStatus) {
updateIssue(input: { projectPath: $projectPath, iid: $iid, healthStatus: $healthStatus }) {
- issue {
+ issuable: issue {
+ id
healthStatus
}
errors
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index 7ab8da93a57..2d0b7fe46a6 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -412,7 +412,6 @@ export default {
// If state is merged we should update the widget and stop the polling
eventHub.$emit('MRWidgetUpdateRequested');
eventHub.$emit('FetchActionsContent');
- MergeRequest.setStatusBoxToMerged();
MergeRequest.hideCloseButton();
MergeRequest.decreaseCounter();
stopPolling();
diff --git a/app/assets/javascripts/vue_shared/components/file_icon.vue b/app/assets/javascripts/vue_shared/components/file_icon.vue
index 2e7c10636a2..276fb35b51f 100644
--- a/app/assets/javascripts/vue_shared/components/file_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/file_icon.vue
@@ -95,7 +95,6 @@ export default {
:name="folderIconName"
:size="size"
class="folder-icon"
- use-deprecated-sizes
data-qa-selector="folder_icon_content"
/>
</span>
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index ac6239615b4..4160b528301 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -16,6 +16,8 @@ class SearchController < ApplicationController
search_term_present && !params[:project_id].present?
end
+ rescue_from ActiveRecord::QueryCanceled, with: :render_timeout
+
layout 'search'
feature_category :global_search
@@ -150,6 +152,15 @@ class SearchController < ApplicationController
redirect_to new_user_session_path, alert: _('You must be logged in to search across all of GitLab')
end
+
+ def render_timeout(exception)
+ raise exception unless action_name.to_sym == :show
+
+ log_exception(exception)
+
+ @timeout = true
+ render status: :request_timeout
+ end
end
SearchController.prepend_mod_with('SearchController')
diff --git a/app/finders/concerns/merged_at_filter.rb b/app/finders/concerns/merged_at_filter.rb
index e44354f36d1..581bcca3c25 100644
--- a/app/finders/concerns/merged_at_filter.rb
+++ b/app/finders/concerns/merged_at_filter.rb
@@ -10,7 +10,7 @@ module MergedAtFilter
mr_metrics_scope = mr_metrics_scope.merged_after(merged_after) if merged_after.present?
mr_metrics_scope = mr_metrics_scope.merged_before(merged_before) if merged_before.present?
- join_metrics(items, mr_metrics_scope)
+ items.join_metrics.merge(mr_metrics_scope)
end
def merged_after
@@ -20,22 +20,4 @@ module MergedAtFilter
def merged_before
params[:merged_before]
end
-
- # rubocop: disable CodeReuse/ActiveRecord
- #
- # This join optimizes merged_at queries when the finder is invoked for a project by moving
- # the target_project_id condition from merge_requests table to merge_request_metrics table.
- def join_metrics(items, mr_metrics_scope)
- scope = if project_id = items.where_values_hash["target_project_id"]
- # removing the original merge_requests.target_project_id condition
- items = items.unscope(where: :target_project_id)
- # adding the target_project_id condition to merge_request_metrics
- items.join_metrics(project_id)
- else
- items.join_metrics
- end
-
- scope.merge(mr_metrics_scope)
- end
- # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index c40feb42eea..d8ba530f3f6 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -425,6 +425,15 @@ module IssuablesHelper
}
end
+ def sidebar_status_data(issuable_sidebar, project)
+ {
+ iid: issuable_sidebar[:iid],
+ issuable_type: issuable_sidebar[:type],
+ full_path: project.full_path,
+ can_edit: issuable_sidebar.dig(:current_user, :can_edit).to_s
+ }
+ end
+
def parent
@project || @group
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 04cc6886eb1..1cbde1871d4 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -301,7 +301,7 @@ module SearchHelper
if @scope == scope
li_class = 'active'
- count = @search_results.formatted_count(scope)
+ count = @timeout ? 0 : @search_results.formatted_count(scope)
else
badge_class = 'js-search-count hidden'
badge_data = { url: search_count_path(search_params) }
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index 9a0c82ddbb5..da32dfb0b9b 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -26,6 +26,9 @@ module SortingHelper
sort_value_recently_updated => sort_title_recently_updated,
sort_value_popularity => sort_title_popularity,
sort_value_priority => sort_title_priority,
+ sort_value_merged_date => sort_title_merged_date,
+ sort_value_merged_recently => sort_title_merged_recently,
+ sort_value_merged_earlier => sort_title_merged_earlier,
sort_value_upvotes => sort_title_upvotes,
sort_value_contacted_date => sort_title_contacted_date,
sort_value_relative_position => sort_title_relative_position,
@@ -178,6 +181,7 @@ module SortingHelper
sort_value_oldest_updated => sort_value_recently_updated,
sort_value_milestone_later => sort_value_milestone,
sort_value_due_date_later => sort_value_due_date,
+ sort_value_merged_recently => sort_value_merged_date,
sort_value_least_popular => sort_value_popularity
}
end
@@ -190,6 +194,8 @@ module SortingHelper
sort_value_milestone => sort_value_milestone_later,
sort_value_due_date => sort_value_due_date_later,
sort_value_due_date_soon => sort_value_due_date_later,
+ sort_value_merged_date => sort_value_merged_recently,
+ sort_value_merged_earlier => sort_value_merged_recently,
sort_value_popularity => sort_value_least_popular,
sort_value_most_popular => sort_value_least_popular
}.merge(issuable_sort_option_overrides)
@@ -210,7 +216,7 @@ module SortingHelper
def sort_direction_icon(sort_value)
case sort_value
- when sort_value_milestone, sort_value_due_date, /_asc\z/
+ when sort_value_milestone, sort_value_due_date, sort_value_merged_date, /_asc\z/
'sort-lowest'
else
'sort-highest'
diff --git a/app/helpers/sorting_titles_values_helper.rb b/app/helpers/sorting_titles_values_helper.rb
index b1921983104..9b839f4e9bc 100644
--- a/app/helpers/sorting_titles_values_helper.rb
+++ b/app/helpers/sorting_titles_values_helper.rb
@@ -26,6 +26,18 @@ module SortingTitlesValuesHelper
s_('SortOptions|Label priority')
end
+ def sort_title_merged_date
+ s_('SortOptions|Merged date')
+ end
+
+ def sort_title_merged_recently
+ s_('SortOptions|Merged recently')
+ end
+
+ def sort_title_merged_earlier
+ s_('SortOptions|Merged earlier')
+ end
+
def sort_title_largest_group
s_('SortOptions|Largest group')
end
@@ -175,6 +187,18 @@ module SortingTitlesValuesHelper
'label_priority'
end
+ def sort_value_merged_date
+ 'merged_at'
+ end
+
+ def sort_value_merged_recently
+ 'merged_at_desc'
+ end
+
+ def sort_value_merged_earlier
+ 'merged_at_asc'
+ end
+
def sort_value_largest_group
'storage_size_desc'
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 5ed8a119035..3b236620ed6 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -426,8 +426,15 @@ class Issue < ApplicationRecord
end
def check_for_spam?
- publicly_visible? &&
- (title_changed? || description_changed? || confidential_changed?)
+ # content created via support bots is always checked for spam, EVEN if
+ # the issue is not publicly visible and/or confidential
+ return true if author.support_bot? && spammable_attribute_changed?
+
+ # Only check for spam on issues which are publicly visible (and thus indexed in search engines)
+ return false unless publicly_visible?
+
+ # Only check for spam if certain attributes have changed
+ spammable_attribute_changed?
end
def as_json(options = {})
@@ -515,6 +522,14 @@ class Issue < ApplicationRecord
private
+ def spammable_attribute_changed?
+ title_changed? ||
+ description_changed? ||
+ # NOTE: We need to check them for spam when issues are made non-confidential, because spam
+ # may have been added while they were confidential and thus not being checked for spam.
+ confidential_changed?(from: true, to: false)
+ end
+
# Ensure that the metrics association is safely created and respecting the unique constraint on issue_id
override :ensure_metrics
def ensure_metrics
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index fcbf1202e69..06512d2fdb9 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -300,6 +300,11 @@ class MergeRequest < ApplicationRecord
query = joins(:metrics)
+ if !target_project_id && self.where_values_hash["target_project_id"]
+ target_project_id = self.where_values_hash["target_project_id"]
+ query = query.unscope(where: :target_project_id)
+ end
+
project_condition = if target_project_id
MergeRequest::Metrics.arel_table[:target_project_id].eq(target_project_id)
else
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index 4ba906dd02f..d5d3cd753f3 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -1,20 +1,16 @@
- search_bar_classes = 'search-sidebar gl-display-flex gl-flex-direction-column gl-mr-4'
+= render_if_exists 'shared/promotions/promote_advanced_search'
+= render partial: 'search/results_status', locals: { search_service: @search_service } unless @search_objects.to_a.empty?
-- if @search_objects.to_a.empty?
- .gl-md-display-flex
- - if %w(issues merge_requests).include?(@scope)
- #js-search-sidebar{ class: search_bar_classes }
- .gl-w-full.gl-flex-grow-1.gl-overflow-x-hidden
+.results.gl-md-display-flex.gl-mt-3
+ - if %w(issues merge_requests).include?(@scope)
+ #js-search-sidebar{ class: search_bar_classes }
+ .gl-w-full.gl-flex-grow-1.gl-overflow-x-hidden
+ - if @timeout
+ = render partial: "search/results/timeout"
+ - elsif @search_objects.to_a.empty?
= render partial: "search/results/empty"
- = render_if_exists 'shared/promotions/promote_advanced_search'
-- else
- = render partial: 'search/results_status', locals: { search_service: @search_service }
- = render_if_exists 'shared/promotions/promote_advanced_search'
-
- .results.gl-md-display-flex.gl-mt-3
- - if %w(issues merge_requests).include?(@scope)
- #js-search-sidebar{ class: search_bar_classes }
- .gl-w-full.gl-flex-grow-1.gl-overflow-x-hidden
+ - else
- if @scope == 'commits'
%ul.content-list.commit-list
= render partial: "search/results/commit", collection: @search_objects
diff --git a/app/views/search/results/_timeout.html.haml b/app/views/search/results/_timeout.html.haml
new file mode 100644
index 00000000000..740e2bedd54
--- /dev/null
+++ b/app/views/search/results/_timeout.html.haml
@@ -0,0 +1,10 @@
+.gl-display-flex.gl-flex-direction-column.gl-align-items-center
+ %div
+ .svg-content.svg-150
+ = image_tag 'illustrations/search-timeout-md.svg'
+ %div
+ %h4.gl-text-center.gl-font-weight-bold= _('Your search timed out')
+ %p.gl-text-center= _('To resolve this, try to:')
+ %ul
+ %li= html_escape(_('Refine your search criteria (select a %{strong_open}group%{strong_close} and %{strong_open}project%{strong_close} when possible)')) % { strong_open: '<strong>'.html_safe, strong_close: '</strong>'.html_safe }
+ %li= html_escape(_('Use double quotes for multiple keywords, such as %{code_open}"your search"%{code_close}')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 5156ad96684..82f03421799 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -58,7 +58,7 @@
#js-severity
- if issuable_sidebar.dig(:features_available, :health_status)
- .js-sidebar-status-entry-point
+ .js-sidebar-status-entry-point{ data: sidebar_status_data(issuable_sidebar, @project) }
- if issuable_sidebar.has_key?(:confidential)
%script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: issuable_sidebar[:confidential], is_editable: can_edit_issuable }.to_json.html_safe
diff --git a/app/views/shared/issuable/_sort_dropdown.html.haml b/app/views/shared/issuable/_sort_dropdown.html.haml
index 9e3caf62d77..caf271e9ee9 100644
--- a/app/views/shared/issuable/_sort_dropdown.html.haml
+++ b/app/views/shared/issuable/_sort_dropdown.html.haml
@@ -1,6 +1,7 @@
- sort_value = @sort
- sort_title = issuable_sort_option_title(sort_value)
- viewing_issues = controller.controller_name == 'issues' || controller.action_name == 'issues'
+- viewing_merge_requests = controller.controller_name == 'merge_requests'
.dropdown.inline.gl-ml-3.issue-sort-dropdown
.btn-group{ role: 'group' }
@@ -17,6 +18,7 @@
= sortable_item(sort_title_due_date, page_filter_path(sort: sort_value_due_date), sort_title) if viewing_issues
= sortable_item(sort_title_popularity, page_filter_path(sort: sort_value_popularity), sort_title)
= sortable_item(sort_title_label_priority, page_filter_path(sort: sort_value_label_priority), sort_title)
+ = sortable_item(sort_title_merged_date, page_filter_path(sort: sort_value_merged_date), sort_title) if viewing_merge_requests
= sortable_item(sort_title_relative_position, page_filter_path(sort: sort_value_relative_position), sort_title) if viewing_issues
= render_if_exists('shared/ee/issuable/sort_dropdown', viewing_issues: viewing_issues, sort_title: sort_title)
= issuable_sort_direction_button(sort_value)
diff --git a/app/workers/container_expiration_policies/cleanup_container_repository_worker.rb b/app/workers/container_expiration_policies/cleanup_container_repository_worker.rb
index 3027d46b8b1..33dda6a8f0c 100644
--- a/app/workers/container_expiration_policies/cleanup_container_repository_worker.rb
+++ b/app/workers/container_expiration_policies/cleanup_container_repository_worker.rb
@@ -49,15 +49,11 @@ module ContainerExpirationPolicies
end
def remaining_work_count
- total_count = cleanup_scheduled_count + cleanup_unfinished_count
+ count = cleanup_scheduled_count
- log_info(
- cleanup_scheduled_count: cleanup_scheduled_count,
- cleanup_unfinished_count: cleanup_unfinished_count,
- cleanup_total_count: total_count
- )
+ return count if count > max_running_jobs
- total_count
+ count + cleanup_unfinished_count
end
private
diff --git a/app/workers/container_expiration_policy_worker.rb b/app/workers/container_expiration_policy_worker.rb
index 8fc139ac87c..a35ca5d184e 100644
--- a/app/workers/container_expiration_policy_worker.rb
+++ b/app/workers/container_expiration_policy_worker.rb
@@ -17,6 +17,7 @@ class ContainerExpirationPolicyWorker # rubocop:disable Scalability/IdempotentWo
process_stale_ongoing_cleanups
disable_policies_without_container_repositories
throttling_enabled? ? perform_throttled : perform_unthrottled
+ log_counts
end
private
@@ -28,6 +29,26 @@ class ContainerExpirationPolicyWorker # rubocop:disable Scalability/IdempotentWo
end
end
+ def log_counts
+ use_replica_if_available do
+ required_count = ContainerRepository.requiring_cleanup.count
+ unfinished_count = ContainerRepository.with_unfinished_cleanup.count
+
+ log_extra_metadata_on_done(:cleanup_required_count, required_count)
+ log_extra_metadata_on_done(:cleanup_unfinished_count, unfinished_count)
+ log_extra_metadata_on_done(:cleanup_total_count, required_count + unfinished_count)
+ end
+ end
+
+ # data_consistency :delayed not used as this is a cron job and those jobs are
+ # not perfomed with a delay
+ # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63635#note_603771207
+ def use_replica_if_available(&blk)
+ return yield unless ::Gitlab::Database::LoadBalancing.enable?
+
+ ::Gitlab::Database::LoadBalancing::Session.current.use_replicas_for_read_queries(&blk)
+ end
+
def process_stale_ongoing_cleanups
threshold = delete_tags_service_timeout.seconds + 30.minutes
ContainerRepository.with_stale_ongoing_cleanup(threshold.ago)
diff --git a/db/migrate/20210705130919_create_container_repos_on_exp_cleanup_status_project_id_start_date_index.rb b/db/migrate/20210705130919_create_container_repos_on_exp_cleanup_status_project_id_start_date_index.rb
new file mode 100644
index 00000000000..a6983c2d599
--- /dev/null
+++ b/db/migrate/20210705130919_create_container_repos_on_exp_cleanup_status_project_id_start_date_index.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+# See https://docs.gitlab.com/ee/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class CreateContainerReposOnExpCleanupStatusProjectIdStartDateIndex < ActiveRecord::Migration[6.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ OLD_INDEX_NAME = 'idx_container_repositories_on_exp_cleanup_status_and_start_date'
+ NEW_INDEX_NAME = 'idx_container_repos_on_exp_cleanup_status_project_id_start_date'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index(:container_repositories, [:expiration_policy_cleanup_status, :project_id, :expiration_policy_started_at], name: NEW_INDEX_NAME)
+ remove_concurrent_index(:container_repositories, [:expiration_policy_cleanup_status, :expiration_policy_started_at], name: OLD_INDEX_NAME)
+ end
+
+ def down
+ add_concurrent_index(:container_repositories, [:expiration_policy_cleanup_status, :expiration_policy_started_at], name: OLD_INDEX_NAME)
+ remove_concurrent_index(:container_repositories, [:expiration_policy_cleanup_status, :project_id, :expiration_policy_started_at], name: NEW_INDEX_NAME)
+ end
+end
diff --git a/db/schema_migrations/20210705130919 b/db/schema_migrations/20210705130919
new file mode 100644
index 00000000000..9e0b9ffe69a
--- /dev/null
+++ b/db/schema_migrations/20210705130919
@@ -0,0 +1 @@
+c33dd2c63d5a8c6e3c2f49e640b1780734b4bfca88378fac67ea5f5bd24fb2b4 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 2861a545945..21dd89ceedc 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -22536,7 +22536,7 @@ CREATE INDEX idx_container_exp_policies_on_project_id_next_run_at ON container_e
CREATE INDEX idx_container_exp_policies_on_project_id_next_run_at_enabled ON container_expiration_policies USING btree (project_id, next_run_at, enabled);
-CREATE INDEX idx_container_repositories_on_exp_cleanup_status_and_start_date ON container_repositories USING btree (expiration_policy_cleanup_status, expiration_policy_started_at);
+CREATE INDEX idx_container_repos_on_exp_cleanup_status_project_id_start_date ON container_repositories USING btree (expiration_policy_cleanup_status, project_id, expiration_policy_started_at);
CREATE INDEX idx_deployment_clusters_on_cluster_id_and_kubernetes_namespace ON deployment_clusters USING btree (cluster_id, kubernetes_namespace);
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index 000c909664a..20530d805ca 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -74,6 +74,7 @@ page, with these behaviors:
1. It doesn't pick people whose Slack or [GitLab status](../user/profile/index.md#set-your-current-status):
- contains the string 'OOO', 'PTO', 'Parental Leave', or 'Friends and Family'
- emoji is `:palm_tree:`, `:beach:`, `:beach_umbrella:`, `:beach_with_umbrella:`, `:ferris_wheel:`, `:thermometer:`, `:face_with_thermometer:`, `:red_circle:`, `:bulb:`, `:sun_with_face:`.
+ - GitLab user busy indicator is set to true
1. [Trainee maintainers](https://about.gitlab.com/handbook/engineering/workflow/code-review/#trainee-maintainer)
are three times as likely to be picked as other reviewers.
1. Team members whose Slack or [GitLab status](../user/profile/index.md#set-your-current-status) emoji
diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md
index dd51d981658..b58e644d639 100644
--- a/doc/development/pipelines.md
+++ b/doc/development/pipelines.md
@@ -92,7 +92,7 @@ graph RL;
click 1-5 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914312&udv=0"
1-6["setup-test-env (4 minutes)"];
click 1-6 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914315&udv=0"
- 1-7["review-stop-failed-deployment"];
+ 1-7["review-delete-deployment"];
1-8["dependency_scanning"];
1-9["qa:internal, qa:internal-as-if-foss"];
1-11["qa:selectors, qa:selectors-as-if-foss"];
diff --git a/doc/integration/jira/dvcs.md b/doc/integration/jira/dvcs.md
index dc23765337b..046dd125cd1 100644
--- a/doc/integration/jira/dvcs.md
+++ b/doc/integration/jira/dvcs.md
@@ -77,6 +77,7 @@ your integration.
- *For GitLab versions 13.0 and later* **and** *Jira versions 8.14 and later,* use the
generated `Redirect URL` from
[Linking GitLab accounts with Jira](https://confluence.atlassian.com/adminjiraserver/linking-gitlab-accounts-1027142272.html).
+ - *For GitLab versions 13.0 and later* **and** *Jira Cloud,* use `https://<gitlab.example.com>/login/oauth/callback`.
- *For GitLab versions 11.3 and later,* use `https://<gitlab.example.com>/login/oauth/callback`.
If you use GitLab.com, the URL is `https://gitlab.com/login/oauth/callback`.
- *For GitLab versions 11.2 and earlier,* use
@@ -89,7 +90,7 @@ your integration.
## Configure Jira for DVCS
-If you use Jira Cloud and GitLab.com, use the [GitLab for Jira app](connect-app.md)
+If you use Jira Cloud, use the [GitLab for Jira app](connect-app.md)
unless you specifically need the DVCS Connector.
Configure this connection when you want to import all GitLab commits and branches,
diff --git a/doc/user/admin_area/custom_project_templates.md b/doc/user/admin_area/custom_project_templates.md
index 6cf3c5bbd7d..12d143b3a13 100644
--- a/doc/user/admin_area/custom_project_templates.md
+++ b/doc/user/admin_area/custom_project_templates.md
@@ -9,36 +9,33 @@ type: reference
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/6860) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.2.
-GitLab administrators can configure the group where all the custom project
-templates are sourced.
+GitLab administrators can set a group to be the source of project templates that are
+selectable when a new project is created on the instance. These templates can be selected
+when you go to **New project > Create from template** and select the **Instance** tab.
-Every project directly under the group namespace will be
-available to the user if they have access to them. For example:
+Every project in the group, but not its subgroups, can be selected when a new project
+is created, based on the user's access permissions:
-- Public projects, in the group will be available to every signed-in user, if all enabled [project features](../project/settings/index.md#sharing-and-permissions)
+- Public projects can be selected by any signed-in user as a template for a new project,
+ if all enabled [project features](../project/settings/index.md#sharing-and-permissions)
except for GitLab Pages are set to **Everyone With Access**.
-- Private projects will be available only if the user is a member of the project.
+- Private projects can be selected only by users who are members of the projects.
Repository and database information that are copied over to each new project are
-identical to the data exported with the
-[GitLab Project Import/Export](../project/settings/import_export.md).
+identical to the data exported with the [GitLab Project Import/Export](../project/settings/import_export.md).
-NOTE:
-To set project templates at a group level,
-see [Custom group-level project templates](../group/custom_project_templates.md).
+To set project templates at the group level, see [Custom group-level project templates](../group/custom_project_templates.md).
-## Configuring
+## Select instance-level project template group
-GitLab administrators can configure a GitLab group that serves as template
-source for an entire GitLab instance:
+To select the group to use as the source for the project templates:
1. On the top bar, navigate to **Menu > Admin > Settings > Templates**.
1. Expand **Custom project templates**.
1. Select a group to use.
1. Select **Save changes**.
-NOTE:
-Projects below subgroups of the template group are **not** supported.
+Projects in subgroups of the template group are **not** included in the template list.
<!-- ## Troubleshooting
diff --git a/doc/user/search/index.md b/doc/user/search/index.md
index 0cdaa3150c5..babe13969ce 100644
--- a/doc/user/search/index.md
+++ b/doc/user/search/index.md
@@ -17,9 +17,9 @@ and to-do items are assigned to you:
![issues and MRs dashboard links](img/dashboard_links_v13_11.png)
-- **(issues)** **Issues**: The open issues assigned to you.
-- **(merge-request-open)** **Merge requests**: The [merge requests](../project/merge_requests/index.md) assigned to you.
-- **(todo-done)** **To-do items**: The [to-do items](../todos.md) assigned to you.
+- **{issues}** **Issues**: The open issues assigned to you.
+- **{merge-request-open}** **Merge requests**: The [merge requests](../project/merge_requests/index.md) assigned to you.
+- **{todo-done}** **To-do items**: The [to-do items](../todos.md) assigned to you.
When you click **Issues**, GitLab shows the opened issues assigned to you:
diff --git a/lib/gitlab/database/load_balancing/sidekiq_client_middleware.rb b/lib/gitlab/database/load_balancing/sidekiq_client_middleware.rb
index 524d69c00c0..0e36ebbc3ee 100644
--- a/lib/gitlab/database/load_balancing/sidekiq_client_middleware.rb
+++ b/lib/gitlab/database/load_balancing/sidekiq_client_middleware.rb
@@ -5,27 +5,29 @@ module Gitlab
module LoadBalancing
class SidekiqClientMiddleware
def call(worker_class, job, _queue, _redis_pool)
+ # Mailers can't be constantized
worker_class = worker_class.to_s.safe_constantize
- mark_data_consistency_location(worker_class, job)
+ if load_balancing_enabled?(worker_class)
+ job['worker_data_consistency'] = worker_class.get_data_consistency
+ set_data_consistency_location!(job) unless location_already_provided?(job)
+ else
+ job['worker_data_consistency'] = ::WorkerAttributes::DEFAULT_DATA_CONSISTENCY
+ end
yield
end
private
- def mark_data_consistency_location(worker_class, job)
- # Mailers can't be constantized
- return unless worker_class
- return unless worker_class.include?(::ApplicationWorker)
- return unless worker_class.get_data_consistency_feature_flag_enabled?
-
- return if location_already_provided?(job)
-
- job['worker_data_consistency'] = worker_class.get_data_consistency
-
- return unless worker_class.utilizes_load_balancing_capabilities?
+ def load_balancing_enabled?(worker_class)
+ worker_class &&
+ worker_class.include?(::ApplicationWorker) &&
+ worker_class.utilizes_load_balancing_capabilities? &&
+ worker_class.get_data_consistency_feature_flag_enabled?
+ end
+ def set_data_consistency_location!(job)
if Session.current.use_primary?
job['database_write_location'] = load_balancer.primary_write_location
else
diff --git a/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb b/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb
index 4e06a94557d..0551750568a 100644
--- a/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb
+++ b/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb
@@ -10,17 +10,14 @@ module Gitlab
worker_class = worker.class
strategy = select_load_balancing_strategy(worker_class, job)
- # This is consumed by ServerMetrics and StructuredLogger to emit metrics so we only
- # make this available when load-balancing is actually utilized.
- job['load_balancing_strategy'] = strategy.to_s if load_balancing_available?(worker_class)
+ job['load_balancing_strategy'] = strategy.to_s
- case strategy
- when :primary, :retry_primary
+ if use_primary?(strategy)
Session.current.use_primary!
- when :retry_replica
+ elsif strategy == :retry
raise JobReplicaNotUpToDate, "Sidekiq job #{worker_class} JID-#{job['jid']} couldn't use the replica."\
- " Replica was not up to date."
- when :replica
+ " Replica was not up to date."
+ else
# this means we selected an up-to-date replica, but there is nothing to do in this case.
end
@@ -36,17 +33,24 @@ module Gitlab
Session.clear_session
end
+ def use_primary?(strategy)
+ strategy.start_with?('primary')
+ end
+
def select_load_balancing_strategy(worker_class, job)
return :primary unless load_balancing_available?(worker_class)
location = job['database_write_location'] || job['database_replica_location']
- return :primary unless location
+ return :primary_no_wal unless location
if replica_caught_up?(location)
- :replica
- elsif worker_class.get_data_consistency == :delayed
- not_yet_retried?(job) ? :retry_replica : :retry_primary
+ # Happy case: we can read from a replica.
+ retried_before?(worker_class, job) ? :replica_retried : :replica
+ elsif can_retry?(worker_class, job)
+ # Optimistic case: The worker allows retries and we have retries left.
+ :retry
else
+ # Sad case: we need to fall back to the primary.
:primary
end
end
@@ -57,6 +61,14 @@ module Gitlab
worker_class.get_data_consistency_feature_flag_enabled?
end
+ def can_retry?(worker_class, job)
+ worker_class.get_data_consistency == :delayed && not_yet_retried?(job)
+ end
+
+ def retried_before?(worker_class, job)
+ worker_class.get_data_consistency == :delayed && !not_yet_retried?(job)
+ end
+
def not_yet_retried?(job)
# if `retry_count` is `nil` it indicates that this job was never retried
# the `0` indicates that this is a first retry
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index d4a9ce18a09..961b0da9f2a 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2409,6 +2409,9 @@ msgstr ""
msgid "AdminSettings|Select a CI/CD template"
msgstr ""
+msgid "AdminSettings|Select a group to use as the source for instance-level project templates."
+msgstr ""
+
msgid "AdminSettings|Service template allows you to set default values for integrations"
msgstr ""
@@ -2430,6 +2433,9 @@ msgstr ""
msgid "AdminSettings|The latest artifacts for all jobs in the most recent successful pipelines in each project are stored and do not expire."
msgstr ""
+msgid "AdminSettings|The projects in this group can be selected as templates for new projects created on the instance. %{link_start}Learn more.%{link_end} "
+msgstr ""
+
msgid "AdminSettings|The template for the required pipeline configuration can be one of the GitLab-provided templates, or a custom template added to an instance template repository. %{link_start}How do I create an instance template repository?%{link_end}"
msgstr ""
@@ -10218,6 +10224,12 @@ msgstr ""
msgid "DastProfiles|Website"
msgstr ""
+msgid "DastProfiles|You can either choose a passive scan or validate the target site in your chosen site profile. %{docsLinkStart}Learn more about site validation.%{docsLinkEnd}"
+msgstr ""
+
+msgid "DastProfiles|You cannot run an active scan against an unvalidated site."
+msgstr ""
+
msgid "DastSiteValidation|Copy HTTP header to clipboard"
msgstr ""
@@ -11838,9 +11850,6 @@ msgstr ""
msgid "Edit issues"
msgstr ""
-msgid "Edit iteration"
-msgstr ""
-
msgid "Edit public deploy key"
msgstr ""
@@ -12855,6 +12864,9 @@ msgstr ""
msgid "Error occurred when saving reviewers"
msgstr ""
+msgid "Error occurred while updating the %{issuableType} status"
+msgstr ""
+
msgid "Error occurred while updating the issue status"
msgstr ""
@@ -15900,9 +15912,6 @@ msgstr ""
msgid "GroupSettings|remove the share with group lock from %{ancestor_group_name}"
msgstr ""
-msgid "GroupTemplate|The selected group's repositories and databases are copied into the projects created in this group. %{link_start}What should I add to my template group?%{link_end} "
-msgstr ""
-
msgid "Groups"
msgstr ""
@@ -18318,9 +18327,6 @@ msgstr ""
msgid "Iteration"
msgstr ""
-msgid "Iteration cadences"
-msgstr ""
-
msgid "Iteration changed to"
msgstr ""
@@ -18363,6 +18369,9 @@ msgstr ""
msgid "Iterations|Edit cadence"
msgstr ""
+msgid "Iterations|Edit iteration"
+msgstr ""
+
msgid "Iterations|Edit iteration cadence"
msgstr ""
@@ -18372,12 +18381,18 @@ msgstr ""
msgid "Iterations|Future iterations"
msgstr ""
+msgid "Iterations|Iteration cadences"
+msgstr ""
+
msgid "Iterations|Iteration scheduling will be handled automatically"
msgstr ""
msgid "Iterations|Move incomplete issues to the next iteration"
msgstr ""
+msgid "Iterations|New iteration"
+msgstr ""
+
msgid "Iterations|New iteration cadence"
msgstr ""
@@ -21948,9 +21963,6 @@ msgstr ""
msgid "New issue title"
msgstr ""
-msgid "New iteration"
-msgstr ""
-
msgid "New iteration created"
msgstr ""
@@ -26953,6 +26965,9 @@ msgstr ""
msgid "References"
msgstr ""
+msgid "Refine your search criteria (select a %{strong_open}group%{strong_close} and %{strong_open}project%{strong_close} when possible)"
+msgstr ""
+
msgid "Refresh"
msgstr ""
@@ -29341,9 +29356,6 @@ msgstr ""
msgid "Select a shared template repository for all projects on this instance."
msgstr ""
-msgid "Select a subgroup to use as a template when creating new projects in the group."
-msgstr ""
-
msgid "Select a template repository"
msgstr ""
@@ -29380,6 +29392,9 @@ msgstr ""
msgid "Select file"
msgstr ""
+msgid "Select group"
+msgstr ""
+
msgid "Select group or project"
msgstr ""
@@ -30533,6 +30548,9 @@ msgstr ""
msgid "Something went wrong while setting %{issuableType} confidentiality."
msgstr ""
+msgid "Something went wrong while setting %{issuableType} health status."
+msgstr ""
+
msgid "Something went wrong while setting %{issuableType} notifications."
msgstr ""
@@ -30653,6 +30671,15 @@ msgstr ""
msgid "SortOptions|Manual"
msgstr ""
+msgid "SortOptions|Merged date"
+msgstr ""
+
+msgid "SortOptions|Merged earlier"
+msgstr ""
+
+msgid "SortOptions|Merged recently"
+msgstr ""
+
msgid "SortOptions|Milestone due date"
msgstr ""
@@ -34218,6 +34245,9 @@ msgstr ""
msgid "To receive alerts from manually configured Prometheus services, add the following URL and Authorization key to your Prometheus webhook config file. Learn more about %{linkStart}configuring Prometheus%{linkEnd} to send alerts to GitLab."
msgstr ""
+msgid "To resolve this, try to:"
+msgstr ""
+
msgid "To run CI/CD pipelines with JetBrains TeamCity, input the GitLab project details in the TeamCity project Version Control Settings."
msgstr ""
@@ -35474,6 +35504,9 @@ msgstr ""
msgid "Use custom color #FF0000"
msgstr ""
+msgid "Use double quotes for multiple keywords, such as %{code_open}\"your search\"%{code_close}"
+msgstr ""
+
msgid "Use hashed storage"
msgstr ""
@@ -38048,6 +38081,9 @@ msgstr ""
msgid "Your search didn't match any commits. Try a different query."
msgstr ""
+msgid "Your search timed out"
+msgstr ""
+
msgid "Your sign-in page is %{url}."
msgstr ""
diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh
index 3ffdeb31894..a799f8cd925 100755
--- a/scripts/review_apps/review-apps.sh
+++ b/scripts/review_apps/review-apps.sh
@@ -48,7 +48,9 @@ function delete_release() {
return
fi
- delete_k8s_release_namespace
+ if deploy_exists "${namespace}" "${release}"; then
+ helm uninstall --namespace="${namespace}" "${release}"
+ fi
}
function delete_failed_release() {
@@ -66,7 +68,7 @@ function delete_failed_release() {
# Cleanup and previous installs, as FAILED and PENDING_UPGRADE will cause errors with `upgrade`
if previous_deploy_failed "${namespace}" "${release}" ; then
echoinfo "Review App deployment in bad state, cleaning up namespace ${release}"
- delete_release
+ delete_k8s_release_namespace
else
echoinfo "Review App deployment in good state"
fi
diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
index 6b8dcd7dbb6..ab6242784fe 100644
--- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
@@ -23,7 +23,9 @@ RSpec.describe 'Merge requests > User lists merge requests' do
milestone: create(:milestone, project: project, due_date: '2013-12-11'),
created_at: 1.minute.ago,
updated_at: 1.minute.ago)
- create(:merge_request,
+ @fix.metrics.update_column(:merged_at, 10.seconds.ago)
+
+ @markdown = create(:merge_request,
title: 'markdown',
source_project: project,
source_branch: 'markdown',
@@ -32,12 +34,15 @@ RSpec.describe 'Merge requests > User lists merge requests' do
milestone: create(:milestone, project: project, due_date: '2013-12-12'),
created_at: 2.minutes.ago,
updated_at: 2.minutes.ago)
- create(:merge_request,
+ @markdown.metrics.update_column(:merged_at, 50.seconds.ago)
+
+ @merge_test = create(:merge_request,
title: 'merge-test',
source_project: project,
source_branch: 'merge-test',
created_at: 3.minutes.ago,
updated_at: 10.seconds.ago)
+ @merge_test.metrics.update_column(:merged_at, 10.seconds.ago)
end
context 'merge request reviewers' do
@@ -102,6 +107,13 @@ RSpec.describe 'Merge requests > User lists merge requests' do
expect(count_merge_requests).to eq(3)
end
+ it 'sorts by merged at' do
+ visit_merge_requests(project, sort: sort_value_merged_date)
+
+ expect(first_merge_request).to include('markdown')
+ expect(count_merge_requests).to eq(3)
+ end
+
it 'filters on one label and sorts by due date' do
label = create(:label, project: project)
create(:label_link, label: label, target: @fix)
diff --git a/spec/features/search/user_searches_for_code_spec.rb b/spec/features/search/user_searches_for_code_spec.rb
index 56e93e4408b..f55dccce4f0 100644
--- a/spec/features/search/user_searches_for_code_spec.rb
+++ b/spec/features/search/user_searches_for_code_spec.rb
@@ -37,6 +37,7 @@ RSpec.describe 'User searches for code' do
end
include_examples 'top right search form'
+ include_examples 'search timeouts', 'blobs'
it 'finds code' do
fill_in('dashboard_search', with: 'rspec')
diff --git a/spec/features/search/user_searches_for_comments_spec.rb b/spec/features/search/user_searches_for_comments_spec.rb
index 2a12b22b457..5185a2460dc 100644
--- a/spec/features/search/user_searches_for_comments_spec.rb
+++ b/spec/features/search/user_searches_for_comments_spec.rb
@@ -13,6 +13,8 @@ RSpec.describe 'User searches for comments' do
visit(project_path(project))
end
+ include_examples 'search timeouts', 'notes'
+
context 'when a comment is in commits' do
context 'when comment belongs to an invalid commit' do
let(:comment) { create(:note_on_commit, author: user, project: project, commit_id: 12345678, note: 'Bug here') }
diff --git a/spec/features/search/user_searches_for_commits_spec.rb b/spec/features/search/user_searches_for_commits_spec.rb
index 1a882050126..279db686aa9 100644
--- a/spec/features/search/user_searches_for_commits_spec.rb
+++ b/spec/features/search/user_searches_for_commits_spec.rb
@@ -14,6 +14,8 @@ RSpec.describe 'User searches for commits', :js do
visit(search_path(project_id: project.id))
end
+ include_examples 'search timeouts', 'commits'
+
context 'when searching by SHA' do
it 'finds a commit and redirects to its page' do
submit_search(sha)
diff --git a/spec/features/search/user_searches_for_issues_spec.rb b/spec/features/search/user_searches_for_issues_spec.rb
index 184f8ba0d36..b0902096770 100644
--- a/spec/features/search/user_searches_for_issues_spec.rb
+++ b/spec/features/search/user_searches_for_issues_spec.rb
@@ -23,6 +23,7 @@ RSpec.describe 'User searches for issues', :js do
end
include_examples 'top right search form'
+ include_examples 'search timeouts', 'issues'
it 'finds an issue' do
search_for_issue(issue1.title)
diff --git a/spec/features/search/user_searches_for_merge_requests_spec.rb b/spec/features/search/user_searches_for_merge_requests_spec.rb
index 32952a127d3..d7f490ba9bc 100644
--- a/spec/features/search/user_searches_for_merge_requests_spec.rb
+++ b/spec/features/search/user_searches_for_merge_requests_spec.rb
@@ -22,6 +22,7 @@ RSpec.describe 'User searches for merge requests', :js do
end
include_examples 'top right search form'
+ include_examples 'search timeouts', 'merge_requests'
it 'finds a merge request' do
search_for_mr(merge_request1.title)
diff --git a/spec/features/search/user_searches_for_milestones_spec.rb b/spec/features/search/user_searches_for_milestones_spec.rb
index e81abb44ba5..7a1ec16385c 100644
--- a/spec/features/search/user_searches_for_milestones_spec.rb
+++ b/spec/features/search/user_searches_for_milestones_spec.rb
@@ -16,6 +16,7 @@ RSpec.describe 'User searches for milestones', :js do
end
include_examples 'top right search form'
+ include_examples 'search timeouts', 'milestones'
it 'finds a milestone' do
fill_in('dashboard_search', with: milestone1.title)
diff --git a/spec/features/search/user_searches_for_projects_spec.rb b/spec/features/search/user_searches_for_projects_spec.rb
index e34ae031679..c38ad077cd0 100644
--- a/spec/features/search/user_searches_for_projects_spec.rb
+++ b/spec/features/search/user_searches_for_projects_spec.rb
@@ -12,6 +12,7 @@ RSpec.describe 'User searches for projects', :js do
end
include_examples 'top right search form'
+ include_examples 'search timeouts', 'projects'
it 'finds a project' do
visit(search_path)
diff --git a/spec/features/search/user_searches_for_users_spec.rb b/spec/features/search/user_searches_for_users_spec.rb
index 826ed73c9bf..a5cf12fa068 100644
--- a/spec/features/search/user_searches_for_users_spec.rb
+++ b/spec/features/search/user_searches_for_users_spec.rb
@@ -11,6 +11,8 @@ RSpec.describe 'User searches for users' do
sign_in(user1)
end
+ include_examples 'search timeouts', 'users'
+
context 'when on the dashboard' do
it 'finds the user', :js do
visit dashboard_projects_path
diff --git a/spec/features/search/user_searches_for_wiki_pages_spec.rb b/spec/features/search/user_searches_for_wiki_pages_spec.rb
index 8913f1fe9ee..06545d8640f 100644
--- a/spec/features/search/user_searches_for_wiki_pages_spec.rb
+++ b/spec/features/search/user_searches_for_wiki_pages_spec.rb
@@ -15,6 +15,7 @@ RSpec.describe 'User searches for wiki pages', :js do
end
include_examples 'top right search form'
+ include_examples 'search timeouts', 'wiki_blobs'
shared_examples 'search wiki blobs' do
it 'finds a page' do
diff --git a/spec/frontend/blob/components/__snapshots__/blob_header_filepath_spec.js.snap b/spec/frontend/blob/components/__snapshots__/blob_header_filepath_spec.js.snap
index 53815820bbe..dfa6b99080b 100644
--- a/spec/frontend/blob/components/__snapshots__/blob_header_filepath_spec.js.snap
+++ b/spec/frontend/blob/components/__snapshots__/blob_header_filepath_spec.js.snap
@@ -10,7 +10,7 @@ exports[`Blob Header Filepath rendering matches the snapshot 1`] = `
cssclasses="mr-2"
filemode=""
filename="foo/bar/dummy.md"
- size="18"
+ size="16"
/>
<strong
diff --git a/spec/frontend/issuable_show/components/issuable_show_root_spec.js b/spec/frontend/issuable_show/components/issuable_show_root_spec.js
index b4c125f4910..7ad409c3a74 100644
--- a/spec/frontend/issuable_show/components/issuable_show_root_spec.js
+++ b/spec/frontend/issuable_show/components/issuable_show_root_spec.js
@@ -133,14 +133,6 @@ describe('IssuableShowRoot', () => {
expect(wrapper.emitted('task-list-update-failure')).toBeTruthy();
});
- it('component emits `sidebar-toggle` event bubbled via issuable-sidebar', () => {
- const issuableSidebar = wrapper.find(IssuableSidebar);
-
- issuableSidebar.vm.$emit('sidebar-toggle', true);
-
- expect(wrapper.emitted('sidebar-toggle')).toBeTruthy();
- });
-
it.each(['keydown-title', 'keydown-description'])(
'component emits `%s` event with event object and issuableMeta params via issuable-body',
(eventName) => {
diff --git a/spec/frontend/issuable_sidebar/components/issuable_sidebar_root_spec.js b/spec/frontend/issuable_sidebar/components/issuable_sidebar_root_spec.js
index 62a0016d67b..c872925cca2 100644
--- a/spec/frontend/issuable_sidebar/components/issuable_sidebar_root_spec.js
+++ b/spec/frontend/issuable_sidebar/components/issuable_sidebar_root_spec.js
@@ -1,88 +1,80 @@
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
-import { shallowMount } from '@vue/test-utils';
import Cookies from 'js-cookie';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import IssuableSidebarRoot from '~/issuable_sidebar/components/issuable_sidebar_root.vue';
+import { USER_COLLAPSED_GUTTER_COOKIE } from '~/issuable_sidebar/constants';
-const createComponent = (expanded = true) =>
- shallowMount(IssuableSidebarRoot, {
- propsData: {
- expanded,
- },
+const MOCK_LAYOUT_PAGE_CLASS = 'layout-page';
+
+const createComponent = () => {
+ setFixtures(`<div class="${MOCK_LAYOUT_PAGE_CLASS}"></div>`);
+
+ return shallowMountExtended(IssuableSidebarRoot, {
slots: {
'right-sidebar-items': `
<button class="js-todo">Todo</button>
`,
},
});
+};
describe('IssuableSidebarRoot', () => {
let wrapper;
- beforeEach(() => {
- wrapper = createComponent();
- });
+ const findToggleSidebarButton = () => wrapper.findByTestId('toggle-right-sidebar-button');
+
+ const assertPageLayoutClasses = ({ isExpanded }) => {
+ const { classList } = document.querySelector(`.${MOCK_LAYOUT_PAGE_CLASS}`);
+ if (isExpanded) {
+ expect(classList).toContain('right-sidebar-expanded');
+ expect(classList).not.toContain('right-sidebar-collapsed');
+ } else {
+ expect(classList).toContain('right-sidebar-collapsed');
+ expect(classList).not.toContain('right-sidebar-expanded');
+ }
+ };
afterEach(() => {
wrapper.destroy();
});
- describe('watch', () => {
- describe('isExpanded', () => {
- it('emits `sidebar-toggle` event on component', async () => {
- wrapper.setData({
- isExpanded: false,
- });
-
- await wrapper.vm.$nextTick();
-
- expect(wrapper.emitted('sidebar-toggle')).toBeTruthy();
- expect(wrapper.emitted('sidebar-toggle')[0]).toEqual([
- {
- expanded: false,
- },
- ]);
- });
- });
- });
+ describe('when sidebar is expanded', () => {
+ beforeEach(() => {
+ jest.spyOn(Cookies, 'set').mockImplementation(jest.fn());
+ jest.spyOn(Cookies, 'get').mockReturnValue(false);
+ jest.spyOn(bp, 'isDesktop').mockReturnValue(true);
- describe('methods', () => {
- describe('updatePageContainerClass', () => {
- beforeEach(() => {
- setFixtures('<div class="layout-page"></div>');
- });
+ wrapper = createComponent();
+ });
- it.each`
- isExpanded | layoutPageClass
- ${true} | ${'right-sidebar-expanded'}
- ${false} | ${'right-sidebar-collapsed'}
- `(
- 'set class $layoutPageClass to container element when `isExpanded` prop is $isExpanded',
- async ({ isExpanded, layoutPageClass }) => {
- wrapper.setData({
- isExpanded,
- });
+ it('renders component container element with class `right-sidebar-expanded`', () => {
+ expect(wrapper.classes()).toContain('right-sidebar-expanded');
+ });
- await wrapper.vm.$nextTick();
+ it('sets layout class to reflect expanded state', () => {
+ assertPageLayoutClasses({ isExpanded: true });
+ });
- wrapper.vm.updatePageContainerClass();
+ it('renders sidebar toggle button with text and icon', () => {
+ const buttonEl = findToggleSidebarButton();
- expect(document.querySelector('.layout-page').classList.contains(layoutPageClass)).toBe(
- true,
- );
- },
- );
+ expect(buttonEl.exists()).toBe(true);
+ expect(buttonEl.attributes('title')).toBe('Toggle sidebar');
+ expect(buttonEl.find('span').text()).toBe('Collapse sidebar');
+ expect(wrapper.findByTestId('icon-collapse').isVisible()).toBe(true);
});
- describe('handleWindowResize', () => {
- beforeEach(async () => {
- wrapper.setData({
- userExpanded: true,
- });
+ describe('when collapsing the sidebar', () => {
+ it('updates "collapsed_gutter" cookie value and layout classes', async () => {
+ await findToggleSidebarButton().trigger('click');
- await wrapper.vm.$nextTick();
+ expect(Cookies.set).toHaveBeenCalledWith(USER_COLLAPSED_GUTTER_COOKIE, true);
+ assertPageLayoutClasses({ isExpanded: false });
});
+ });
+ describe('when window `resize` event is triggered', () => {
it.each`
breakpoint | isExpandedValue
${'xs'} | ${false}
@@ -91,109 +83,49 @@ describe('IssuableSidebarRoot', () => {
${'lg'} | ${true}
${'xl'} | ${true}
`(
- 'sets `isExpanded` prop to $isExpandedValue only when current screen size is `lg` or `xl`',
+ 'sets page layout classes correctly when current screen size is `$breakpoint`',
async ({ breakpoint, isExpandedValue }) => {
jest.spyOn(bp, 'isDesktop').mockReturnValue(breakpoint === 'lg' || breakpoint === 'xl');
- wrapper.vm.handleWindowResize();
+ window.dispatchEvent(new Event('resize'));
+ await wrapper.vm.$nextTick();
- expect(wrapper.vm.isExpanded).toBe(isExpandedValue);
+ assertPageLayoutClasses({ isExpanded: isExpandedValue });
},
);
-
- it('calls `updatePageContainerClass` method', () => {
- jest.spyOn(wrapper.vm, 'updatePageContainerClass');
-
- wrapper.vm.handleWindowResize();
-
- expect(wrapper.vm.updatePageContainerClass).toHaveBeenCalled();
- });
- });
-
- describe('handleToggleSidebarClick', () => {
- beforeEach(async () => {
- jest.spyOn(Cookies, 'set').mockImplementation(jest.fn());
- wrapper.setData({
- isExpanded: true,
- });
-
- await wrapper.vm.$nextTick();
- });
-
- it('flips value of `isExpanded`', () => {
- wrapper.vm.handleToggleSidebarClick();
-
- expect(wrapper.vm.isExpanded).toBe(false);
- expect(wrapper.vm.userExpanded).toBe(false);
- });
-
- it('updates "collapsed_gutter" cookie value', () => {
- wrapper.vm.handleToggleSidebarClick();
-
- expect(Cookies.set).toHaveBeenCalledWith('collapsed_gutter', true);
- });
-
- it('calls `updatePageContainerClass` method', () => {
- jest.spyOn(wrapper.vm, 'updatePageContainerClass');
-
- wrapper.vm.handleWindowResize();
-
- expect(wrapper.vm.updatePageContainerClass).toHaveBeenCalled();
- });
});
});
- describe('template', () => {
- describe('sidebar expanded', () => {
- beforeEach(async () => {
- wrapper.setData({
- isExpanded: true,
- });
+ describe('when sidebar is collapsed', () => {
+ beforeEach(() => {
+ jest.spyOn(Cookies, 'get').mockReturnValue(true);
- await wrapper.vm.$nextTick();
- });
-
- it('renders component container element with class `right-sidebar-expanded` when `isExpanded` prop is true', () => {
- expect(wrapper.classes()).toContain('right-sidebar-expanded');
- });
-
- it('renders sidebar toggle button with text and icon', () => {
- const buttonEl = wrapper.find('button');
-
- expect(buttonEl.exists()).toBe(true);
- expect(buttonEl.attributes('title')).toBe('Toggle sidebar');
- expect(buttonEl.find('span').text()).toBe('Collapse sidebar');
- expect(buttonEl.find('[data-testid="icon-collapse"]').isVisible()).toBe(true);
- });
+ wrapper = createComponent();
});
- describe('sidebar collapsed', () => {
- beforeEach(async () => {
- wrapper.setData({
- isExpanded: false,
- });
-
- await wrapper.vm.$nextTick();
- });
+ it('renders component container element with class `right-sidebar-collapsed`', () => {
+ expect(wrapper.classes()).toContain('right-sidebar-collapsed');
+ });
- it('renders component container element with class `right-sidebar-collapsed` when `isExpanded` prop is false', () => {
- expect(wrapper.classes()).toContain('right-sidebar-collapsed');
- });
+ it('sets layout class to reflect collapsed state', () => {
+ assertPageLayoutClasses({ isExpanded: false });
+ });
- it('renders sidebar toggle button with text and icon', () => {
- const buttonEl = wrapper.find('button');
+ it('renders sidebar toggle button with text and icon', () => {
+ const buttonEl = findToggleSidebarButton();
- expect(buttonEl.exists()).toBe(true);
- expect(buttonEl.attributes('title')).toBe('Toggle sidebar');
- expect(buttonEl.find('[data-testid="icon-expand"]').isVisible()).toBe(true);
- });
+ expect(buttonEl.exists()).toBe(true);
+ expect(buttonEl.attributes('title')).toBe('Toggle sidebar');
+ expect(wrapper.findByTestId('icon-expand').isVisible()).toBe(true);
});
+ });
- it('renders sidebar items', () => {
- const sidebarItemsEl = wrapper.find('[data-testid="sidebar-items"]');
+ it('renders slotted sidebar items', () => {
+ wrapper = createComponent();
- expect(sidebarItemsEl.exists()).toBe(true);
- expect(sidebarItemsEl.find('button.js-todo').exists()).toBe(true);
- });
+ const sidebarItemsEl = wrapper.findByTestId('sidebar-items');
+
+ expect(sidebarItemsEl.exists()).toBe(true);
+ expect(sidebarItemsEl.find('button.js-todo').exists()).toBe(true);
});
});
diff --git a/spec/lib/gitlab/database/load_balancing/sidekiq_client_middleware_spec.rb b/spec/lib/gitlab/database/load_balancing/sidekiq_client_middleware_spec.rb
index 90051172fca..54050a87af0 100644
--- a/spec/lib/gitlab/database/load_balancing/sidekiq_client_middleware_spec.rb
+++ b/spec/lib/gitlab/database/load_balancing/sidekiq_client_middleware_spec.rb
@@ -5,12 +5,27 @@ require 'spec_helper'
RSpec.describe Gitlab::Database::LoadBalancing::SidekiqClientMiddleware do
let(:middleware) { described_class.new }
+ let(:load_balancer) { double.as_null_object }
+ let(:worker_class) { 'TestDataConsistencyWorker' }
+ let(:job) { { "job_id" => "a180b47c-3fd6-41b8-81e9-34da61c3400e" } }
+
+ before do
+ skip_feature_flags_yaml_validation
+ skip_default_enabled_yaml_check
+ allow(::Gitlab::Database::LoadBalancing).to receive_message_chain(:proxy, :load_balancer).and_return(load_balancer)
+ end
+
after do
Gitlab::Database::LoadBalancing::Session.clear_session
end
+ def run_middleware
+ middleware.call(worker_class, job, nil, nil) {}
+ end
+
describe '#call' do
shared_context 'data consistency worker class' do |data_consistency, feature_flag|
+ let(:expected_consistency) { data_consistency }
let(:worker_class) do
Class.new do
def self.name
@@ -31,13 +46,23 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqClientMiddleware do
end
end
+ shared_examples_for 'job data consistency' do
+ it "sets job data consistency" do
+ run_middleware
+
+ expect(job['worker_data_consistency']).to eq(expected_consistency)
+ end
+ end
+
shared_examples_for 'does not pass database locations' do
it 'does not pass database locations', :aggregate_failures do
- middleware.call(worker_class, job, double(:queue), redis_pool) { 10 }
+ run_middleware
expect(job['database_replica_location']).to be_nil
expect(job['database_write_location']).to be_nil
end
+
+ include_examples 'job data consistency'
end
shared_examples_for 'mark data consistency location' do |data_consistency|
@@ -45,7 +70,9 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqClientMiddleware do
let(:location) { '0/D525E3A8' }
- context 'when feature flag load_balancing_for_sidekiq is disabled' do
+ context 'when feature flag is disabled' do
+ let(:expected_consistency) { :always }
+
before do
stub_feature_flags(load_balancing_for_test_data_consistency_worker: false)
end
@@ -59,12 +86,14 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqClientMiddleware do
end
it 'passes database_replica_location' do
- expect(middleware).to receive_message_chain(:load_balancer, :host, "database_replica_location").and_return(location)
+ expect(load_balancer).to receive_message_chain(:host, "database_replica_location").and_return(location)
- middleware.call(worker_class, job, double(:queue), redis_pool) { 10 }
+ run_middleware
expect(job['database_replica_location']).to eq(location)
end
+
+ include_examples 'job data consistency'
end
context 'when write was performed' do
@@ -73,12 +102,14 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqClientMiddleware do
end
it 'passes primary write location', :aggregate_failures do
- expect(middleware).to receive_message_chain(:load_balancer, :primary_write_location).and_return(location)
+ expect(load_balancer).to receive(:primary_write_location).and_return(location)
- middleware.call(worker_class, job, double(:queue), redis_pool) { 10 }
+ run_middleware
expect(job['database_write_location']).to eq(location)
end
+
+ include_examples 'job data consistency'
end
end
@@ -89,7 +120,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqClientMiddleware do
end
it 'does not set database locations again' do
- middleware.call(worker_class, job, double(:queue), redis_pool) { 10 }
+ run_middleware
expect(job[provided_database_location]).to eq(old_location)
expect(job[other_location]).to be_nil
@@ -101,8 +132,8 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqClientMiddleware do
let(:job) { { "job_id" => "a180b47c-3fd6-41b8-81e9-34da61c3400e", provided_database_location => old_location } }
before do
- allow(middleware).to receive_message_chain(:load_balancer, :primary_write_location).and_return(new_location)
- allow(middleware).to receive_message_chain(:load_balancer, :database_replica_location).and_return(new_location)
+ allow(load_balancer).to receive(:primary_write_location).and_return(new_location)
+ allow(load_balancer).to receive(:database_replica_location).and_return(new_location)
end
context "when write was performed" do
@@ -114,24 +145,16 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqClientMiddleware do
end
end
- let(:queue) { 'default' }
- let(:redis_pool) { Sidekiq.redis_pool }
- let(:worker_class) { 'TestDataConsistencyWorker' }
- let(:job) { { "job_id" => "a180b47c-3fd6-41b8-81e9-34da61c3400e" } }
-
- before do
- skip_feature_flags_yaml_validation
- skip_default_enabled_yaml_check
- end
-
context 'when worker cannot be constantized' do
let(:worker_class) { 'ActionMailer::MailDeliveryJob' }
+ let(:expected_consistency) { :always }
include_examples 'does not pass database locations'
end
context 'when worker class does not include ApplicationWorker' do
let(:worker_class) { ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper }
+ let(:expected_consistency) { :always }
include_examples 'does not pass database locations'
end
diff --git a/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb b/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb
index a1e503147ed..14f240cd159 100644
--- a/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb
+++ b/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb
@@ -6,11 +6,16 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware do
let(:middleware) { described_class.new }
let(:load_balancer) { double.as_null_object }
- let(:has_replication_lag) { false }
+
+ let(:worker) { worker_class.new }
+ let(:job) { { "retry" => 3, "job_id" => "a180b47c-3fd6-41b8-81e9-34da61c3400e", 'database_replica_location' => '0/D525E3A8' } }
before do
+ skip_feature_flags_yaml_validation
+ skip_default_enabled_yaml_check
allow(::Gitlab::Database::LoadBalancing).to receive_message_chain(:proxy, :load_balancer).and_return(load_balancer)
- allow(load_balancer).to receive(:select_up_to_date_host).and_return(!has_replication_lag)
+
+ replication_lag!(false)
end
after do
@@ -39,24 +44,34 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware do
end
end
- shared_examples_for 'stick to the primary' do
+ shared_examples_for 'load balancing strategy' do |strategy|
+ it "sets load balancing strategy to #{strategy}" do
+ run_middleware do
+ expect(job['load_balancing_strategy']).to eq(strategy)
+ end
+ end
+ end
+
+ shared_examples_for 'stick to the primary' do |expected_strategy|
it 'sticks to the primary' do
- middleware.call(worker, job, double(:queue)) do
+ run_middleware do
expect(Gitlab::Database::LoadBalancing::Session.current.use_primary?).to be_truthy
end
end
+
+ include_examples 'load balancing strategy', expected_strategy
end
- shared_examples_for 'replica is up to date' do |location|
+ shared_examples_for 'replica is up to date' do |location, expected_strategy|
it 'does not stick to the primary', :aggregate_failures do
expect(middleware).to receive(:replica_caught_up?).with(location).and_return(true)
- middleware.call(worker, job, double(:queue)) do
+ run_middleware do
expect(Gitlab::Database::LoadBalancing::Session.current.use_primary?).not_to be_truthy
end
-
- expect(job['load_balancing_strategy']).to eq('replica')
end
+
+ include_examples 'load balancing strategy', expected_strategy
end
shared_examples_for 'sticks based on data consistency' do |data_consistency|
@@ -67,7 +82,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware do
stub_feature_flags(load_balancing_for_test_data_consistency_worker: false)
end
- include_examples 'stick to the primary'
+ include_examples 'stick to the primary', 'primary'
end
context 'when database replica location is set' do
@@ -77,7 +92,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware do
allow(middleware).to receive(:replica_caught_up?).and_return(true)
end
- it_behaves_like 'replica is up to date', '0/D525E3A8'
+ it_behaves_like 'replica is up to date', '0/D525E3A8', 'replica'
end
context 'when database primary location is set' do
@@ -87,46 +102,35 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware do
allow(middleware).to receive(:replica_caught_up?).and_return(true)
end
- it_behaves_like 'replica is up to date', '0/D525E3A8'
+ it_behaves_like 'replica is up to date', '0/D525E3A8', 'replica'
end
context 'when database location is not set' do
let(:job) { { 'job_id' => 'a180b47c-3fd6-41b8-81e9-34da61c3400e' } }
- it_behaves_like 'stick to the primary'
+ it_behaves_like 'stick to the primary', 'primary_no_wal'
end
end
- let(:queue) { 'default' }
- let(:redis_pool) { Sidekiq.redis_pool }
- let(:worker) { worker_class.new }
- let(:job) { { "retry" => 3, "job_id" => "a180b47c-3fd6-41b8-81e9-34da61c3400e", 'database_replica_location' => '0/D525E3A8' } }
- let(:block) { 10 }
-
- before do
- skip_feature_flags_yaml_validation
- skip_default_enabled_yaml_check
- allow(middleware).to receive(:clear)
- allow(Gitlab::Database::LoadBalancing::Session.current).to receive(:performed_write?).and_return(true)
- end
-
context 'when worker class does not include ApplicationWorker' do
let(:worker) { ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper.new }
- include_examples 'stick to the primary'
+ include_examples 'stick to the primary', 'primary'
end
context 'when worker data consistency is :always' do
include_context 'data consistency worker class', :always, :load_balancing_for_test_data_consistency_worker
- include_examples 'stick to the primary'
+ include_examples 'stick to the primary', 'primary'
end
context 'when worker data consistency is :delayed' do
include_examples 'sticks based on data consistency', :delayed
context 'when replica is not up to date' do
- let(:has_replication_lag) { true }
+ before do
+ replication_lag!(true)
+ end
around do |example|
with_sidekiq_server_middleware do |chain|
@@ -136,24 +140,34 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware do
end
context 'when job is executed first' do
- it 'raise an error and retries', :aggregate_failures do
+ it 'raises an error and retries', :aggregate_failures do
expect do
process_job(job)
end.to raise_error(Sidekiq::JobRetry::Skip)
expect(job['error_class']).to eq('Gitlab::Database::LoadBalancing::SidekiqServerMiddleware::JobReplicaNotUpToDate')
- expect(job['load_balancing_strategy']).to eq('retry_replica')
end
+
+ include_examples 'load balancing strategy', 'retry'
end
context 'when job is retried' do
- it 'stick to the primary', :aggregate_failures do
+ before do
expect do
process_job(job)
end.to raise_error(Sidekiq::JobRetry::Skip)
+ end
+
+ context 'and replica still lagging behind' do
+ include_examples 'stick to the primary', 'primary'
+ end
+
+ context 'and replica is now up-to-date' do
+ before do
+ replication_lag!(false)
+ end
- process_job(job)
- expect(job['load_balancing_strategy']).to eq('retry_primary')
+ it_behaves_like 'replica is up to date', '0/D525E3A8', 'replica_retried'
end
end
end
@@ -167,20 +181,24 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware do
allow(middleware).to receive(:replica_caught_up?).and_return(false)
end
- include_examples 'stick to the primary'
-
- it 'updates job hash with primary database chosen', :aggregate_failures do
- middleware.call(worker, job, double(:queue)) do
- expect(job['load_balancing_strategy']).to eq('primary')
- end
- end
+ include_examples 'stick to the primary', 'primary'
end
end
end
def process_job(job)
- Sidekiq::JobRetry.new.local(worker_class, job, queue) do
+ Sidekiq::JobRetry.new.local(worker_class, job, 'default') do
worker_class.process_job(job)
end
end
+
+ def run_middleware
+ middleware.call(worker, job, double(:queue)) { yield }
+ rescue described_class::JobReplicaNotUpToDate
+ # we silence errors here that cause the job to retry
+ end
+
+ def replication_lag!(exists)
+ allow(load_balancer).to receive(:select_up_to_date_host).and_return(!exists)
+ end
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
index c4e0bc0df20..8a9767f9012 100644
--- a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
@@ -260,7 +260,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
context 'when worker declares data consistency' do
include_context 'worker declaring data consistency'
- it 'increments load balancing counter' do
+ it 'increments load balancing counter with defined data consistency' do
process_job
expect(load_balancing_metric).to have_received(:increment).with(
@@ -272,10 +272,14 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
end
context 'when worker does not declare data consistency' do
- it 'does not increment load balancing counter' do
+ it 'increments load balancing counter with default data consistency' do
process_job
- expect(load_balancing_metric).not_to have_received(:increment)
+ expect(load_balancing_metric).to have_received(:increment).with(
+ a_hash_including(
+ data_consistency: :always,
+ load_balancing_strategy: 'primary'
+ ), 1)
end
end
end
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index edb93ecf4b6..1aa9019d240 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -1051,23 +1051,53 @@ RSpec.describe Issue do
describe '#check_for_spam?' do
using RSpec::Parameterized::TableSyntax
-
- where(:visibility_level, :confidential, :new_attributes, :check_for_spam?) do
- Gitlab::VisibilityLevel::PUBLIC | false | { description: 'woo' } | true
- Gitlab::VisibilityLevel::PUBLIC | false | { title: 'woo' } | true
- Gitlab::VisibilityLevel::PUBLIC | true | { confidential: false } | true
- Gitlab::VisibilityLevel::PUBLIC | true | { description: 'woo' } | false
- Gitlab::VisibilityLevel::PUBLIC | false | { title: 'woo', confidential: true } | false
- Gitlab::VisibilityLevel::PUBLIC | false | { description: 'original description' } | false
- Gitlab::VisibilityLevel::INTERNAL | false | { description: 'woo' } | false
- Gitlab::VisibilityLevel::PRIVATE | false | { description: 'woo' } | false
+ let_it_be(:support_bot) { ::User.support_bot }
+
+ where(:support_bot?, :visibility_level, :confidential, :new_attributes, :check_for_spam?) do
+ ### non-support-bot cases
+ # spammable attributes changing
+ false | Gitlab::VisibilityLevel::PUBLIC | false | { description: 'new' } | true
+ false | Gitlab::VisibilityLevel::PUBLIC | false | { title: 'new' } | true
+ # confidential to non-confidential
+ false | Gitlab::VisibilityLevel::PUBLIC | true | { confidential: false } | true
+ # non-confidential to confidential
+ false | Gitlab::VisibilityLevel::PUBLIC | false | { confidential: true } | false
+ # spammable attributes changing on confidential
+ false | Gitlab::VisibilityLevel::PUBLIC | true | { description: 'new' } | false
+ # spammable attributes changing while changing to confidential
+ false | Gitlab::VisibilityLevel::PUBLIC | false | { title: 'new', confidential: true } | false
+ # spammable attribute not changing
+ false | Gitlab::VisibilityLevel::PUBLIC | false | { description: 'original description' } | false
+ # non-spammable attribute changing
+ false | Gitlab::VisibilityLevel::PUBLIC | false | { weight: 3 } | false
+ # spammable attributes changing on non-public
+ false | Gitlab::VisibilityLevel::INTERNAL | false | { description: 'new' } | false
+ false | Gitlab::VisibilityLevel::PRIVATE | false | { description: 'new' } | false
+
+ ### support-bot cases
+ # confidential to non-confidential
+ true | Gitlab::VisibilityLevel::PUBLIC | true | { confidential: false } | true
+ # non-confidential to confidential
+ true | Gitlab::VisibilityLevel::PUBLIC | false | { confidential: true } | false
+ # spammable attributes changing on confidential
+ true | Gitlab::VisibilityLevel::PUBLIC | true | { description: 'new' } | true
+ # spammable attributes changing while changing to confidential
+ true | Gitlab::VisibilityLevel::PUBLIC | false | { title: 'new', confidential: true } | true
+ # spammable attributes changing on non-public
+ true | Gitlab::VisibilityLevel::INTERNAL | false | { description: 'new' } | true
+ true | Gitlab::VisibilityLevel::PRIVATE | false | { title: 'new' } | true
+ # spammable attribute not changing
+ true | Gitlab::VisibilityLevel::PUBLIC | false | { description: 'original description' } | false
+ # non-spammable attribute changing
+ true | Gitlab::VisibilityLevel::PRIVATE | true | { weight: 3 } | false
end
with_them do
- it 'checks for spam on issues that can be seen anonymously' do
+ it 'checks for spam when necessary' do
+ author = support_bot? ? support_bot : user
project = reusable_project
project.update!(visibility_level: visibility_level)
- issue = create(:issue, project: project, confidential: confidential, description: 'original description')
+ issue = create(:issue, project: project, confidential: confidential, description: 'original description', author: author)
issue.assign_attributes(new_attributes)
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 2769a139c2a..554d2b0751d 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -441,6 +441,22 @@ RSpec.describe MergeRequest, factory_default: :keep do
end
end
+ describe '.join_metrics' do
+ let_it_be(:join_condition) { '"merge_request_metrics"."target_project_id" = 1' }
+
+ context 'when a no target_project_id is available' do
+ it 'moves target_project_id condition to the merge request metrics' do
+ expect(described_class.join_metrics(1).to_sql).to include(join_condition)
+ end
+ end
+
+ context 'when a target_project_id is present in the where conditions' do
+ it 'moves target_project_id condition to the merge request metrics' do
+ expect(described_class.where(target_project_id: 1).join_metrics.to_sql).to include(join_condition)
+ end
+ end
+ end
+
describe '.by_related_commit_sha' do
subject { described_class.by_related_commit_sha(sha) }
diff --git a/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb b/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb
new file mode 100644
index 00000000000..bb5460e2a6f
--- /dev/null
+++ b/spec/support/shared_examples/features/search/search_timeouts_shared_examples.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'search timeouts' do |scope|
+ context 'when search times out' do
+ before do
+ allow_next_instance_of(SearchService) do |service|
+ allow(service).to receive(:search_objects).and_raise(ActiveRecord::QueryCanceled)
+ end
+
+ visit(search_path(search: 'test', scope: scope))
+ end
+
+ it 'renders timeout information' do
+ expect(page).to have_content('Your search timed out')
+ end
+
+ it 'sets tab count to 0' do
+ expect(page.find('.search-filter .active')).to have_text('0')
+ end
+ end
+end
diff --git a/spec/tooling/danger/feature_flag_spec.rb b/spec/tooling/danger/feature_flag_spec.rb
index 74e19d8f535..7cae3e0a8b3 100644
--- a/spec/tooling/danger/feature_flag_spec.rb
+++ b/spec/tooling/danger/feature_flag_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe Tooling::Danger::FeatureFlag do
let(:fake_danger) { DangerSpecHelper.fake_danger.include(described_class) }
- subject(:feature_flag) { fake_danger.new(git: fake_git) }
+ subject(:feature_flag) { fake_danger.new(helper: fake_helper) }
describe '#feature_flag_files' do
let(:feature_flag_files) do
diff --git a/spec/workers/container_expiration_policies/cleanup_container_repository_worker_spec.rb b/spec/workers/container_expiration_policies/cleanup_container_repository_worker_spec.rb
index c399697cbe0..506124216af 100644
--- a/spec/workers/container_expiration_policies/cleanup_container_repository_worker_spec.rb
+++ b/spec/workers/container_expiration_policies/cleanup_container_repository_worker_spec.rb
@@ -413,20 +413,30 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
disabled_repository.project.container_expiration_policy.update_column(:enabled, false)
end
+ context 'counts and capacity' do
+ where(:scheduled_count, :unfinished_count, :capacity, :expected_count) do
+ 2 | 2 | 10 | 4
+ 2 | 0 | 10 | 2
+ 0 | 2 | 10 | 2
+ 4 | 2 | 2 | 4
+ 4 | 0 | 2 | 4
+ 0 | 4 | 2 | 4
+ end
+
+ with_them do
+ before do
+ allow(worker).to receive(:cleanup_scheduled_count).and_return(scheduled_count)
+ allow(worker).to receive(:cleanup_unfinished_count).and_return(unfinished_count)
+ end
+
+ it { is_expected.to eq(expected_count) }
+ end
+ end
+
context 'with container repositories waiting for cleanup' do
let_it_be(:unfinished_repositories) { create_list(:container_repository, 2, :cleanup_unfinished) }
it { is_expected.to eq(3) }
-
- it 'logs the work count' do
- expect_log_info(
- cleanup_scheduled_count: 1,
- cleanup_unfinished_count: 2,
- cleanup_total_count: 3
- )
-
- subject
- end
end
context 'with no container repositories waiting for cleanup' do
@@ -436,16 +446,6 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
end
it { is_expected.to eq(0) }
-
- it 'logs 0 work count' do
- expect_log_info(
- cleanup_scheduled_count: 0,
- cleanup_unfinished_count: 0,
- cleanup_total_count: 0
- )
-
- subject
- end
end
end
@@ -468,9 +468,4 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
it { is_expected.to eq(0) }
end
end
-
- def expect_log_info(structure)
- expect(worker.logger)
- .to receive(:info).with(worker.structured_payload(structure))
- end
end
diff --git a/spec/workers/container_expiration_policy_worker_spec.rb b/spec/workers/container_expiration_policy_worker_spec.rb
index 6f81d06f653..69ddbe5c0f4 100644
--- a/spec/workers/container_expiration_policy_worker_spec.rb
+++ b/spec/workers/container_expiration_policy_worker_spec.rb
@@ -113,8 +113,8 @@ RSpec.describe ContainerExpirationPolicyWorker do
context 'process stale ongoing cleanups' do
let_it_be(:stuck_cleanup) { create(:container_repository, :cleanup_ongoing, expiration_policy_started_at: 1.day.ago) }
- let_it_be(:container_repository) { create(:container_repository, :cleanup_scheduled) }
- let_it_be(:container_repository) { create(:container_repository, :cleanup_unfinished) }
+ let_it_be(:container_repository1) { create(:container_repository, :cleanup_scheduled) }
+ let_it_be(:container_repository2) { create(:container_repository, :cleanup_unfinished) }
it 'set them as unfinished' do
expect { subject }
@@ -137,5 +137,36 @@ RSpec.describe ContainerExpirationPolicyWorker do
expect(container_expiration_policy3.reload.enabled).to be false
end
end
+
+ context 'counts logging' do
+ let_it_be(:container_repository1) { create(:container_repository, :cleanup_scheduled) }
+ let_it_be(:container_repository2) { create(:container_repository, :cleanup_unfinished) }
+ let_it_be(:container_repository3) { create(:container_repository, :cleanup_unfinished) }
+
+ before do
+ ContainerExpirationPolicy.update_all(enabled: true)
+ container_repository1.project.container_expiration_policy.update_column(:next_run_at, 5.minutes.ago)
+ end
+
+ it 'logs all the counts' do
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:cleanup_required_count, 1)
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:cleanup_unfinished_count, 2)
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:cleanup_total_count, 3)
+
+ subject
+ end
+
+ context 'with load balancing enabled' do
+ before do
+ allow(Gitlab::Database::LoadBalancing).to receive(:enable?).and_return(true)
+ end
+
+ it 'reads the counts from the replica' do
+ expect(Gitlab::Database::LoadBalancing::Session.current).to receive(:use_replicas_for_read_queries).and_call_original
+
+ subject
+ end
+ end
+ end
end
end
diff --git a/tooling/danger/feature_flag.rb b/tooling/danger/feature_flag.rb
index 06c89f83e23..cef64e52af3 100644
--- a/tooling/danger/feature_flag.rb
+++ b/tooling/danger/feature_flag.rb
@@ -10,7 +10,8 @@ module Tooling
# - :modified
# - :deleted
def feature_flag_files(change_type:)
- files = git.public_send("#{change_type}_files") # rubocop:disable GitlabSecurity/PublicSend
+ files = helper.public_send("#{change_type}_files") # rubocop:disable GitlabSecurity/PublicSend
+
files.select { |path| path =~ %r{\A(ee/)?config/feature_flags/} }.map { |path| Found.new(path) }
end