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>2024-01-06 00:11:24 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2024-01-06 00:11:24 +0300
commit6fd670b99b45d3374f8b429e87be51dd67d6ab39 (patch)
treee3c6ad8503548b306cacd37c7d6c5b48a1a0a6c5
parent534ce3b2d0a6ec24de9c370e5b85c9528ff63e34 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--Gemfile.checksum2
-rw-r--r--Gemfile.lock2
-rw-r--r--app/assets/javascripts/badges/components/badge_list.vue15
-rw-r--r--app/assets/javascripts/clusters/agents/components/show.vue2
-rw-r--r--app/assets/stylesheets/application.scss4
-rw-r--r--app/assets/stylesheets/components/_index.scss11
-rw-r--r--app/assets/stylesheets/page_bundles/pipeline.scss5
-rw-r--r--app/assets/stylesheets/vendors/_index.scss1
-rw-r--r--app/models/merge_request.rb2
-rw-r--r--app/views/devise/sessions/two_factor.html.haml2
-rw-r--r--app/views/profiles/accounts/_providers.html.haml41
-rw-r--r--config/feature_flags/development/arkose_labs_signup_data_exchange.yml (renamed from config/feature_flags/development/clickhouse_ci_analytics.yml)10
-rw-r--r--config/sidekiq_queues.yml2
-rw-r--r--doc/editor_extensions/index.md1
-rw-r--r--doc/integration/index.md1
-rw-r--r--doc/user/application_security/policies/scan-result-policies.md13
-rw-r--r--doc/user/markdown.md145
-rw-r--r--doc/user/project/integrations/webhooks.md1
-rw-r--r--doc/user/project/repository/code_suggestions/index.md4
-rw-r--r--lib/gitlab/database/namespace_each_batch.rb223
-rw-r--r--locale/gitlab.pot3
-rw-r--r--qa/gdk/Dockerfile.gdk2
-rw-r--r--qa/qa/support/helpers/plan.rb8
-rw-r--r--spec/frontend/clusters/agents/components/show_spec.js20
-rw-r--r--spec/lib/gitlab/database/namespace_each_batch_spec.rb174
-rw-r--r--spec/models/packages/protection/rule_spec.rb46
26 files changed, 614 insertions, 126 deletions
diff --git a/Gemfile.checksum b/Gemfile.checksum
index e5eb59ed9cd..81df2b4c75e 100644
--- a/Gemfile.checksum
+++ b/Gemfile.checksum
@@ -684,7 +684,7 @@
{"name":"uniform_notifier","version":"1.16.0","platform":"ruby","checksum":"99b39ee4a0864e3b49f375b5e5803eb26d35ed6eb1719c96407573a87bc4dbb5"},
{"name":"unleash","version":"3.2.2","platform":"ruby","checksum":"0f6e56498de920de66a01bceffb93933693ade646bb853fc70eb16bd1026b93b"},
{"name":"unparser","version":"0.6.7","platform":"ruby","checksum":"ae42e73edfa273766e66c166368fb75ca5972cd8ec50c536253e0f6299a9dec8"},
-{"name":"uri","version":"0.12.2","platform":"ruby","checksum":"be79bd8017858c9fc36c78765c69e92b6a7e049ea6a83364909476ad1b4b4439"},
+{"name":"uri","version":"0.13.0","platform":"ruby","checksum":"26553c2a9399762e1e8bebd4444b4361c4b21298cf1c864b22eeabc9c4998f24"},
{"name":"uri_template","version":"0.7.0","platform":"ruby","checksum":"312c8fe13700db86ac9d05ea997af3db03abdf50c65b1801d775bc7a695f185d"},
{"name":"valid_email","version":"0.1.3","platform":"ruby","checksum":"b81452b51b64c4beb67913f68db52c20ecb4d73d45512f5b282ab4a3f4416570"},
{"name":"validate_email","version":"0.1.6","platform":"ruby","checksum":"9dfe9016d527b17a8d3a6e95e4dc50a125400eef899d13d4cc2a254393f82ee4"},
diff --git a/Gemfile.lock b/Gemfile.lock
index 69a3a0356cf..b2e8f6c95b8 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1722,7 +1722,7 @@ GEM
unparser (0.6.7)
diff-lcs (~> 1.3)
parser (>= 3.2.0)
- uri (0.12.2)
+ uri (0.13.0)
uri_template (0.7.0)
valid_email (0.1.3)
activemodel
diff --git a/app/assets/javascripts/badges/components/badge_list.vue b/app/assets/javascripts/badges/components/badge_list.vue
index a4f88067fa9..3f6435d66ce 100644
--- a/app/assets/javascripts/badges/components/badge_list.vue
+++ b/app/assets/javascripts/badges/components/badge_list.vue
@@ -3,6 +3,7 @@ import {
GlBadge,
GlLoadingIcon,
GlTable,
+ GlTooltipDirective,
GlPagination,
GlButton,
GlModalDirective,
@@ -27,6 +28,7 @@ export default {
},
directives: {
GlModal: GlModalDirective,
+ GlTooltip: GlTooltipDirective,
},
i18n: {
emptyGroupMessage: s__('Badges|This group has no badges. Add an existing badge or create one.'),
@@ -107,19 +109,26 @@ export default {
:current-page="currentPage"
stacked="md"
show-empty
+ class="b-table-fixed"
data-testid="badge-list"
>
<template #cell(name)="{ item }">
- <label class="label-bold str-truncated mb-0">{{ item.name }}</label>
+ <label v-gl-tooltip class="label-bold str-truncated mb-0" :title="item.name">{{
+ item.name
+ }}</label>
<gl-badge size="sm">{{ badgeKindText(item) }}</gl-badge>
</template>
<template #cell(badge)="{ item }">
- <badge :image-url="item.renderedImageUrl" :link-url="item.renderedLinkUrl" />
+ <div class="overflow-hidden">
+ <badge :image-url="item.renderedImageUrl" :link-url="item.renderedLinkUrl" />
+ </div>
</template>
<template #cell(url)="{ item }">
- {{ item.linkUrl }}
+ <span v-gl-tooltip :title="item.linkUrl" class="str-truncated">
+ {{ item.linkUrl }}
+ </span>
</template>
<template #cell(actions)="{ item }">
diff --git a/app/assets/javascripts/clusters/agents/components/show.vue b/app/assets/javascripts/clusters/agents/components/show.vue
index 9d7d68ee31c..c1a6f7e0800 100644
--- a/app/assets/javascripts/clusters/agents/components/show.vue
+++ b/app/assets/javascripts/clusters/agents/components/show.vue
@@ -161,6 +161,8 @@ export default {
</div>
</div>
</gl-tab>
+
+ <slot name="ee-workspaces-tab" :agent-name="agentName" :project-path="projectPath"></slot>
</gl-tabs>
</template>
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index cd9bbf45727..1b99a27b12c 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -13,10 +13,10 @@
@import 'page_specific_files';
// Component specific styles, will be moved to gitlab-ui
-@import 'components/**/*';
+@import 'components/index';
// Vendors specific styles
-@import 'vendors/**/*';
+@import 'vendors/index';
// Styles for JS behaviors.
@import 'behaviors';
diff --git a/app/assets/stylesheets/components/_index.scss b/app/assets/stylesheets/components/_index.scss
new file mode 100644
index 00000000000..f53837b5671
--- /dev/null
+++ b/app/assets/stylesheets/components/_index.scss
@@ -0,0 +1,11 @@
+@import './avatar';
+@import './collapsible_card';
+@import './content_editor';
+@import './deployment_instance';
+@import './detail_page';
+@import './ref_selector';
+@import './related_items_list';
+@import './severity/icons';
+@import './shortcuts_help';
+@import './upload_dropzone/upload_dropzone';
+@import './whats_new';
diff --git a/app/assets/stylesheets/page_bundles/pipeline.scss b/app/assets/stylesheets/page_bundles/pipeline.scss
index 9bab5d65b59..fde997cba7b 100644
--- a/app/assets/stylesheets/page_bundles/pipeline.scss
+++ b/app/assets/stylesheets/page_bundles/pipeline.scss
@@ -130,6 +130,11 @@
.gl-pipeline-job-width {
width: 100%;
+ max-width: 400px;
+
+ .pipeline-graph-container & {
+ max-width: unset;
+ }
}
.gl-pipeline-job-width\! {
diff --git a/app/assets/stylesheets/vendors/_index.scss b/app/assets/stylesheets/vendors/_index.scss
new file mode 100644
index 00000000000..e26ba23d1b9
--- /dev/null
+++ b/app/assets/stylesheets/vendors/_index.scss
@@ -0,0 +1 @@
+@import './atwho';
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index aa8bea1bd83..b67d6d20f41 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -2189,7 +2189,7 @@ class MergeRequest < ApplicationRecord
attr_accessor :skip_fetch_ref
def merge_base_pipelines
- return ::Ci::Pipeline.none unless actual_head_pipeline
+ return ::Ci::Pipeline.none unless actual_head_pipeline&.target_sha
target_branch_pipelines_for(sha: actual_head_pipeline.target_sha)
end
diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml
index a291c2f8a9c..454b89e40f8 100644
--- a/app/views/devise/sessions/two_factor.html.haml
+++ b/app/views/devise/sessions/two_factor.html.haml
@@ -1,7 +1,7 @@
.login-box.gl-p-5
.login-body
- if @user.two_factor_enabled?
- = gitlab_ui_form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: "edit_user gl-show-field-errors js-2fa-form #{'hidden' if @user.two_factor_webauthn_enabled?}" }) do |f|
+ = gitlab_ui_form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: "gl-show-field-errors js-2fa-form #{'hidden' if @user.two_factor_webauthn_enabled?}", aria: { live: 'assertive' }}) do |f|
.form-group
= f.label :otp_attempt, _('Enter verification code')
= f.text_field :otp_attempt, class: 'form-control gl-form-input', required: true, autofocus: true, autocomplete: 'off', inputmode: 'numeric', title: _('This field is required.'), data: { testid: 'two-fa-code-field' }
diff --git a/app/views/profiles/accounts/_providers.html.haml b/app/views/profiles/accounts/_providers.html.haml
index 6f0c091dfdb..3ecdc3f63e5 100644
--- a/app/views/profiles/accounts/_providers.html.haml
+++ b/app/views/profiles/accounts/_providers.html.haml
@@ -3,26 +3,27 @@
%label.label-bold.gl-mb-0
= s_('Profiles|Connected Accounts')
%p= s_('Profiles|Select a service to sign in with.')
- - providers.each do |provider|
- - unlink_allowed = unlink_provider_allowed?(provider)
- - link_allowed = link_provider_allowed?(provider)
- - has_icon = provider_has_icon?(provider)
- - if unlink_allowed || link_allowed
- - if auth_active?(provider)
- - if unlink_allowed
- = link_to unlink_profile_account_path(provider: provider), method: :delete, class: button_class do
+ .gl-display-flex.gl-flex-wrap.gl-gap-3
+ - providers.each do |provider|
+ - unlink_allowed = unlink_provider_allowed?(provider)
+ - link_allowed = link_provider_allowed?(provider)
+ - has_icon = provider_has_icon?(provider)
+ - if unlink_allowed || link_allowed
+ - if auth_active?(provider)
+ - if unlink_allowed
+ = link_to unlink_profile_account_path(provider: provider), method: :delete, class: button_class do
+ - if has_icon
+ .social-provider-btn-image.gl-button-icon= provider_image_tag(provider)
+ .gl-button-text
+ = s_('Profiles|Disconnect %{provider}') % { provider: label_for_provider(provider) }
+ - else
+ %a{ class: button_class }
+ .gl-button-text
+ = s_('Profiles|%{provider} Active') % { provider: label_for_provider(provider) }
+ - elsif link_allowed
+ = link_to omniauth_authorize_path(:user, provider), method: :post, class: button_class do
- if has_icon
.social-provider-btn-image.gl-button-icon= provider_image_tag(provider)
.gl-button-text
- = s_('Profiles|Disconnect %{provider}') % { provider: label_for_provider(provider) }
- - else
- %a{ class: button_class }
- .gl-button-text
- = s_('Profiles|%{provider} Active') % { provider: label_for_provider(provider) }
- - elsif link_allowed
- = link_to omniauth_authorize_path(:user, provider), method: :post, class: button_class do
- - if has_icon
- .social-provider-btn-image.gl-button-icon= provider_image_tag(provider)
- .gl-button-text
- = s_('Profiles|Connect %{provider}') % { provider: label_for_provider(provider) }
- = render_if_exists 'profiles/accounts/group_saml_unlink_buttons', group_saml_identities: group_saml_identities
+ = s_('Profiles|Connect %{provider}') % { provider: label_for_provider(provider) }
+ = render_if_exists 'profiles/accounts/group_saml_unlink_buttons', group_saml_identities: group_saml_identities
diff --git a/config/feature_flags/development/clickhouse_ci_analytics.yml b/config/feature_flags/development/arkose_labs_signup_data_exchange.yml
index e56d2e19036..701a2ee33e9 100644
--- a/config/feature_flags/development/clickhouse_ci_analytics.yml
+++ b/config/feature_flags/development/arkose_labs_signup_data_exchange.yml
@@ -1,8 +1,8 @@
---
-name: clickhouse_ci_analytics
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/130211
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/424498
-milestone: '16.4'
+name: arkose_labs_signup_data_exchange
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/139070
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/435275
+milestone: '16.8'
type: development
-group: group::runner
+group: group::anti-abuse
default_enabled: false
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 23b4fb3959f..c67e69ab692 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -687,6 +687,8 @@
- 1
- - security_scan_result_policies_sync_any_merge_request_approval_rules
- 1
+- - security_scan_result_policies_sync_merge_request_approvals
+ - 1
- - security_scan_result_policies_sync_project
- 1
- - security_scans
diff --git a/doc/editor_extensions/index.md b/doc/editor_extensions/index.md
index de1e3f21ee6..052327596c9 100644
--- a/doc/editor_extensions/index.md
+++ b/doc/editor_extensions/index.md
@@ -1,6 +1,7 @@
---
stage: Create
group: Editor Extensions
+description: Visual Studio Code, JetBrains, Neovim, GitLab CLI.
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
diff --git a/doc/integration/index.md b/doc/integration/index.md
index 736f25f71d8..2a3154a739d 100644
--- a/doc/integration/index.md
+++ b/doc/integration/index.md
@@ -1,6 +1,7 @@
---
stage: Manage
group: Import and Integrate
+description: Projects, issues, authentication, security providers.
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
diff --git a/doc/user/application_security/policies/scan-result-policies.md b/doc/user/application_security/policies/scan-result-policies.md
index 33db2695732..5b2a7062cb9 100644
--- a/doc/user/application_security/policies/scan-result-policies.md
+++ b/doc/user/application_security/policies/scan-result-policies.md
@@ -300,13 +300,20 @@ actions:
## Understanding scan result policy approvals
+> The branch comparison logic for `scan_finding` was [changed](https://gitlab.com/gitlab-org/gitlab/-/issues/428518) in GitLab 16.8 [with a flag](../../../administration/feature_flags.md) named `scan_result_policy_merge_base_pipeline`. Disabled by default.
+
+FLAG:
+On self-managed GitLab, by default this feature is not available. To make it available, an administrator can [enable the feature flag](../../../administration/feature_flags.md) named `scan_result_policy_merge_base_pipeline`.
+On GitLab.com, this feature is not available.
+
### Scope of scan result policy comparison
- To determine when approval is required on a merge request, we compare completed pipelines for each supported pipeline source for the source and target branch (for example, `feature`/`main`). This ensures the most comprehensive evaluation of scan results.
- For the source branch, the comparison pipeline is its latest completed `HEAD` pipeline.
-- For the target branch, the comparison pipeline differs for `license_finding` rules:
- - For `license_finding` rules, we compare to a common ancestor's latest completed pipeline.
- - For all other rules, we compare to the target branch's latest completed `HEAD` pipeline.
+- For `license_finding` rules, we compare to a common ancestor's latest completed pipeline.
+- For `scan_finding` rules, the comparison pipeline may differ:
+ - If the `scan_result_policy_merge_base_pipeline` feature flag is enabled, we compare to a common ancestor's latest completed pipeline.
+ - Otherwise, we compare to the target branch's latest completed `HEAD` pipeline.
- Scan result policies considers all supported pipeline sources (based on the [`CI_PIPELINE_SOURCE` variable](../../../ci/variables/predefined_variables.md)) when comparing results from both the source and target branches when determining if a merge request requires approval. Pipeline sources `webide` and `parent_pipeline` are not supported.
### Accepting risk and ignoring vulnerabilities in future merge requests
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index 0ec60a9274a..aafae095fc3 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -109,6 +109,8 @@ The following features are not found in standard Markdown.
### Colors
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#colors).
+
Markdown does not support changing text color.
You can write a color code in the formats: `HEX`, `RGB`, or `HSL`.
@@ -134,9 +136,6 @@ display a color chip next to the color code. For example:
- `HSLA(540,70%,50%,0.3)`
```
-[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#colors)
-to see the color chips next to the color code:
-
- `#F00`
- `#F00A`
- `#FF0000`
@@ -159,6 +158,8 @@ In wikis, you can also add and edit diagrams created with the [diagrams.net edit
#### Mermaid
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#mermaid).
+
Visit the [official page](https://mermaidjs.github.io/) for more details. The
[Mermaid Live Editor](https://mermaid-js.github.io/mermaid-live-editor/) helps you
learn Mermaid and debug issues in your Mermaid code. Use it to identify and resolve
@@ -480,11 +481,6 @@ To include task lists in tables, [use HTML list tags or HTML tables](#task-lists
### Table of contents
-<!--
-Tags for the table of contents are presented in a code block to work around a Markdown bug.
-Do not change the code block back to single backticks.
-For more information, see https://gitlab.com/gitlab-org/gitlab/-/issues/359077.
--->
A table of contents is an unordered list that links to subheadings in the document.
You can add a table of contents to issues, merge requests, and epics, but you can't add one
to notes or comments.
@@ -492,9 +488,15 @@ to notes or comments.
Add one of these tags on their own line to the **description** field of any of the supported
content types:
+<!--
+Tags for the table of contents are presented in a code block to work around a Markdown bug.
+Do not change the code block back to single backticks.
+For more information, see https://gitlab.com/gitlab-org/gitlab/-/issues/359077.
+-->
+
```markdown
[[_TOC_]]
-
+or
[TOC]
```
@@ -767,6 +769,8 @@ If a functionality is extended, the new option is listed as a sub-section.
### Blockquotes
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#blockquotes).
+
Use a blockquote to highlight information, such as a side note. It's generated
by starting the lines of the blockquote with `>`:
@@ -788,7 +792,7 @@ Quote break.
#### Multiline blockquote
-If this section isn't rendered correctly, [view it in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#multiline-blockquote).
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#multiline-blockquote).
GitLab Flavored Markdown extends the standard Markdown by also supporting multi-line blockquotes
fenced by `>>>`, with a blank line before and after the block:
@@ -819,6 +823,8 @@ trigger this problem.
### Code spans and blocks
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#code-spans-and-blocks).
+
You can highlight anything that should be viewed as code and not standard text.
Inline code is highlighted with single backticks `` ` ``:
@@ -880,8 +886,7 @@ Tildes are OK too.
#### Colored code and syntax highlighting
-If this section isn't rendered correctly,
-[view it in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#colored-code-and-syntax-highlighting).
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#colored-code-and-syntax-highlighting).
GitLab uses the [Rouge Ruby library](https://github.com/rouge-ruby/rouge) for more colorful syntax
highlighting in code blocks. For a list of supported languages visit the
@@ -945,6 +950,8 @@ But let's throw in a <b>tag</b>.
### Emphasis
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#emphasis).
+
In Markdown, you can emphasize text in multiple ways. You can italicize, bold, strikethrough,
and combine these emphasis styles together.
Strikethrough is not part of the core Markdown standard, but is part of GitLab Flavored Markdown.
@@ -975,8 +982,7 @@ Strikethrough with double tildes. ~~Scratch this.~~
#### Multiple underscores in words and mid-word emphasis
-If this section isn't rendered correctly,
-[view it in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#multiple-underscores-in-words).
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#multiple-underscores-in-words).
Avoid italicizing a portion of a word, especially when you're
dealing with code and names that often appear with multiple underscores.
@@ -1017,6 +1023,8 @@ do*this*and*do*that*and*another thing
### Footnotes
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#footnotes).
+
Footnotes add a link to a note rendered at the end of a Markdown file.
To make a footnote, you need both a reference tag and a separate line (anywhere in the file) with
@@ -1061,9 +1069,11 @@ These are used to force the Vale ReferenceLinks check to skip these examples.
#### H4
##### H5
###### H6
+```
-Alternatively, for H1 and H2, an underline-ish style:
+Alternatively, for H1 and H2, an underline style:
+```markdown
Alt-H1
======
@@ -1073,6 +1083,8 @@ Alt-H2
#### Header IDs and links
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#header-ids-and-links).
+
GitLab Flavored Markdown extends the standard Markdown standard so that all Markdown-rendered headers automatically
get IDs, which can be linked to, except in comments.
@@ -1113,69 +1125,77 @@ emoji is converted to an image, which is then removed from the ID.
### Horizontal Rule
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#horizontal-rule).
+
Create a horizontal rule by using three or more hyphens, asterisks, or underscores:
```markdown
-Three or more hyphens,
-
---
-asterisks,
-
***
-or underscores
-
___
```
+---
+
+---
+
+---
+
### Images
-Examples:
+Embed images using inline or reference links.
+To see title text, hover over the image.
<!--
-The following codeblock uses HTML to skip the Vale ReferenceLinks test.
-Do not change it back to a markdown codeblock.
+The following examples use HTML to skip the Vale ReferenceLinks test.
+Do not change it back to a markdown codeblocks.
-->
-<!-- markdownlint-disable proper-names -->
+<!--
+DO NOT change the name of markdown_logo.png. This file is used for a test in
+spec/controllers/help_controller_spec.rb.
+-->
-<pre class="highlight"><code>Inline-style (hover to see title text):
+<!--
+The examples below use an in-line link to pass the Vale ReferenceLinks test.
+Do not change to a reference style link.
+-->
-![alt text](img/markdown_logo.png "Title Text")
+Inline-style:
-Reference-style (hover to see title text):
+<!-- markdownlint-disable proper-names -->
-![alt text1][logo]
+<pre class="highlight"><code>
+
+![alt text](img/markdown_logo.png "Title Text")
-&#91;logo]: img/markdown_logo.png "Title Text"
</code></pre>
-<!-- markdownlint-enable proper-names -->
+![alt text](img/markdown_logo.png "Title Text")
-<!--
-DO NOT change the name of markdown_logo.png. This file is used for a test in
-spec/controllers/help_controller_spec.rb.
--->
+Reference-style:
-Inline-style (hover to see title text):
+<pre class="highlight"><code>
-![alt text](img/markdown_logo.png "Title Text")
+![alt text1][logo]
-Reference-style (hover to see title text):
+&#91;logo]: img/markdown_logo.png "Title Text"
-<!--
-The example below uses an in-line link to pass the Vale ReferenceLinks test.
-Do not change to a reference style link.
--->
+</code></pre>
![alt text](img/markdown_logo.png "Title Text")
+<!-- markdownlint-enable proper-names -->
+
#### Change the image or video dimensions
> - Support for images [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/28118) in GitLab 15.7.
> - Support for videos [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/17139) in GitLab 15.9.
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#change-the-image-or-video-dimensions).
+
You can control the width and height of an image or video by following the image with
an attribute list.
The value must an integer with a unit of either `px` (default) or `%`.
@@ -1195,44 +1215,39 @@ You can also use the `img` HTML tag instead of Markdown and set its `height` and
#### Videos
-If this section isn't rendered correctly, [view it in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#videos).
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#videos).
Image tags that link to files with a video extension are automatically converted to
a video player. The valid video extensions are `.mp4`, `.m4v`, `.mov`, `.webm`, and `.ogv`:
-```markdown
-Here's a sample video:
+Here's an example video:
+```markdown
![Sample Video](img/markdown_video.mp4)
```
-Here's a sample video:
-
![Sample Video](img/markdown_video.mp4)
#### Audio
-If this section isn't rendered correctly, [view it in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#audio).
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#audio).
Similar to videos, link tags for files with an audio extension are automatically converted to
an audio player. The valid audio extensions are `.mp3`, `.oga`, `.ogg`, `.spx`, and `.wav`:
-```markdown
-Here's a sample audio clip:
+Here's an example audio clip:
+```markdown
![Sample Audio](img/markdown_audio.mp3)
```
-Here's a sample audio clip:
-
![Sample Audio](img/markdown_audio.mp3)
### Inline HTML
> Allowing `rel="license"` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/20857) in GitLab 14.6.
-To see the second example of Markdown rendered in HTML,
-[view it in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#inline-html).
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#inline-html).
You can also use raw HTML in your Markdown, and it usually works pretty well.
@@ -1297,8 +1312,7 @@ Markdown is fine in GitLab.
#### Collapsible section
-To see the second Markdown example rendered in HTML,
-[view it in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#details-and-summary).
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#details-and-summary).
Content can be collapsed using HTML's [`<details>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details)
and [`<summary>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary)
@@ -1363,6 +1377,8 @@ These details <em>remain</em> <b>hidden</b> until expanded.
### Line breaks
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#line-breaks).
+
A line break is inserted (a new paragraph starts) if the previous text is
ended with two newlines, like when you press <kbd>Enter</kbd> twice in a row. If you only
use one newline (press <kbd>Enter</kbd> once), the next sentence remains part of the
@@ -1414,6 +1430,8 @@ A new line due to the previous backslash.
### Links
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#links).
+
You can create links two ways: inline-style and reference-style. For example:
<!--
@@ -1475,6 +1493,8 @@ points the link to `wikis/style` only when the link is inside of a wiki Markdown
#### URL auto-linking
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#url-auto-linking).
+
GitLab Flavored Markdown auto-links almost any URL you put into your text:
```markdown
@@ -1496,8 +1516,11 @@ GitLab Flavored Markdown auto-links almost any URL you put into your text:
- <http://localhost:3000>
<!-- vale gitlab.Spelling = YES -->
+
### Lists
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#lists).
+
You can create ordered and unordered lists.
For an ordered list, add the number you want the list
@@ -1653,6 +1676,8 @@ CommonMark ignores the blank line and renders this as one list with paragraph sp
### Superscripts / Subscripts
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#superscripts-subscripts).
+
CommonMark and GitLab Flavored Markdown don't support the Redcarpet superscript syntax ( `x^2` ).
Use the standard HTML syntax for superscripts and subscripts:
@@ -1670,6 +1695,8 @@ while the equation for the theory of relativity is E = mc<sup>2</sup>.
### Keyboard HTML tag
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#keyboard-html-tag).
+
The `<kbd>` element is used to identify text that represents user keyboard input. Text surrounded by `<kbd>` tags is typically displayed in the browser's default monospace font.
```html
@@ -1680,6 +1707,8 @@ Press <kbd>Enter</kbd> to go to the next page.
### Tables
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#tables-1).
+
Tables are not part of the core Markdown specification, but are part of GitLab Flavored Markdown.
- The first line contains the headers, separated by "pipes" (`|`).
@@ -1716,6 +1745,8 @@ Example:
#### Alignment
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#alignment).
+
Additionally, you can choose the alignment of text in columns by adding colons (`:`)
to the sides of the "dash" lines in the second row. This affects every cell in the column:
@@ -1736,6 +1767,8 @@ the headers are always left-aligned in Chrome and Firefox, and centered in Safar
#### Cells with multiple lines
+[View this topic in GitLab](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/markdown.md#cells-with-multiple-lines).
+
You can use HTML formatting to adjust the rendering of tables. For example, you can
use `<br>` tags to force a cell to have multiple lines:
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index 33da78191c0..ccf416fc8c6 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -1,6 +1,7 @@
---
stage: Manage
group: Import and Integrate
+description: Custom HTTP callbacks, used to send events.
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
diff --git a/doc/user/project/repository/code_suggestions/index.md b/doc/user/project/repository/code_suggestions/index.md
index 2315778e0f2..ed9eeac1c2c 100644
--- a/doc/user/project/repository/code_suggestions/index.md
+++ b/doc/user/project/repository/code_suggestions/index.md
@@ -70,12 +70,12 @@ The editor supports these languages:
| Google SQL | **{dotted-circle}** No | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
| Java | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
| JavaScript | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
-| Kotlin | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
+| Kotlin | **{check-circle}** Yes (Requires third-party extension providing Kotlin support) | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
| PHP | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
| Python | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
| Ruby | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
| Rust | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
-| Scala | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
+| Scala | **{check-circle}** Yes (Requires third-party extension providing Scala support) | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
| Swift | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
| TypeScript | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
| Terraform | **{check-circle}** Yes (Requires third-party extension providing Terraform support) | **{check-circle}** Yes | **{dotted-circle}** No | **{check-circle}** Yes (Requires third-party extension providing the `terraform` file type) |
diff --git a/lib/gitlab/database/namespace_each_batch.rb b/lib/gitlab/database/namespace_each_batch.rb
new file mode 100644
index 00000000000..ffc3e16061c
--- /dev/null
+++ b/lib/gitlab/database/namespace_each_batch.rb
@@ -0,0 +1,223 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ # This class implements an iterator over the namespace hierarchy which uses a recursive
+ # depth-first algorithm.
+ # You can read more about the algorithm here:
+ # https://docs.gitlab.com/ee/development/database/poc_tree_iterator.html
+ #
+ # With the class, you can iterate over the whole hierarchy including subgroups and project namespaces
+ # or just iterate over the subgroups.
+ #
+ # Usage:
+ #
+ # # To invoke the iterator, you can take any group id.
+ # # Build the cursor object that will be used for tracking our position in the tree hierarchy.
+ # cursor = { current_id: 9970, depth: [9970] }
+ #
+ # # Instantiate the object.
+ # iterator = Gitlab::Database::NamespaceEachBatch.new(namespace_class: Namespace, cursor: cursor)
+ #
+ # iterator.each_batch(of: 100) do |ids|
+ # # return namespace ids which can be Group id or Namespaces::ProjectNamespace id
+ # puts ids
+ # end
+ #
+ # # When you need to break out of the iteration and continue later, you can yield the cursor as a second parameter:
+ # iterator.each_batch(of: 100) do |ids, new_cursor|
+ # save_cursor(new_cursor) && break if limit_reached?
+ # puts ids
+ # end
+ #
+ # You can build a new iterator later and resume the processing.
+ #
+ # # Building an iterator that only returns groups:
+ # iterator = Gitlab::Database::NamespaceEachBatch.new(namespace_class: Group, cursor: cursor)
+ #
+ class NamespaceEachBatch
+ PROJECTIONS = %w[current_id depth ids count index].freeze
+
+ def initialize(namespace_class:, cursor:)
+ @namespace_class = namespace_class
+ set_cursor!(cursor)
+ end
+
+ def each_batch(of: 500)
+ current_cursor = cursor.dup
+
+ first_iteration = true
+ loop do
+ new_cursor, ids = load_batch(cursor: current_cursor, of: of, first_iteration: first_iteration)
+ break if new_cursor.nil?
+
+ first_iteration = false
+ current_cursor = new_cursor
+
+ yield ids, new_cursor
+
+ break if new_cursor[:depth].empty?
+ end
+ end
+
+ private
+
+ attr_reader :namespace_class, :cursor, :namespace_id
+
+ def load_batch(cursor:, of:, first_iteration: false)
+ recursive_scope = build_recursive_query(cursor, of, first_iteration)
+
+ row = Namespace
+ .select(*PROJECTIONS)
+ .from(recursive_scope.arel.as(Namespace.table_name)).order(count: :desc)
+ .limit(1)
+ .first
+
+ return [] unless row
+
+ [{ current_id: row[:current_id], depth: row[:depth] }, row[:ids]]
+ end
+
+ # rubocop: disable Style/AsciiComments -- Rendering a graph
+ # The depth-first algorithm is implemented here. Consider the following group hierarchy:
+ #
+ # ┌──┐
+ # │10│
+ # ┌────┴──┴────┐
+ # │ │
+ # ┌─┴┐ ┌┴─┐
+ # │41│ │72│
+ # └─┬┘ └──┘
+ # │
+ # ┌─┴┐
+ # ┌────┤32├─────┐
+ # │ └─┬┘ │
+ # │ │ │
+ # ┌─┴┐ ┌─┴┐ ┌┴─┐
+ # │11│ │12│ │18│
+ # └──┘ └──┘ └──┘
+ #
+ # 1. Start with node 10 and look up the left-hand child nodes until reaching the leaf. (walk_down)
+ # 2. While walking down, record the depth in an array and also store them in the ids array.
+ # 3. depth: 10, 41, 32, 11 | ids: 10, 41, 32, 11
+ # 4. Start collecting the ids by looking at the nodes on the deepest level. (next_elements)
+ # 5. This gives us the rest of the nodes on the same level (parent_id = 32 AND id > 11)
+ # 6. depth: 10, 41, 32, 11 | ids: 10, 41, 32, 11, 12, 18
+ # 7. When done, move one level up and pop the last value from the depth. (up_one_level)
+ # 8. depth: 10, 41, 32 | ids: 10, 41, 32, 11, 12, 18
+ # 9. Do the same, look at the nodes on the same level: no records, 32 was already collected
+ # 10. depth: 10, 41, 32 | ids: 10, 41, 32, 11, 12, 18
+ # 11. Move one level up again and look at the nodes on the same level.
+ # 12. depth: 10, 41 | ids: 10, 41, 32, 11, 12, 18, 72
+ # 13. Move one level up again, we reached the root node, iteration is done.
+ # 14. depth: 10 | ids: 10, 41, 32, 11, 12, 18, 72
+ #
+ # By tracking the currently accessed node and the depth we can stop and restore the processing of
+ # the hierarchy at any point.
+ #
+ # rubocop: enable Style/AsciiComments
+ def build_recursive_query(cursor, of, first_iteration)
+ ids = first_iteration ? cursor[:current_id] : ''
+
+ recursive_cte = Gitlab::SQL::RecursiveCTE.new(:result,
+ union_args: {
+ remove_order: false,
+ remove_duplicates: false
+ })
+
+ recursive_cte << base_namespace_class.select(
+ Arel.sql(cursor[:current_id].to_s).as('current_id'),
+ Arel.sql("ARRAY[#{cursor[:depth].join(',')}]::int[]").as('depth'),
+ Arel.sql("ARRAY[#{ids}]::int[]").as('ids'),
+ Arel.sql('1::bigint AS count'),
+ Arel.sql('0::bigint AS index')
+ ).from('(VALUES (1)) AS initializer_row')
+ .where_exists(namespace_exists_query)
+
+ cte = Gitlab::SQL::CTE.new(:cte, base_namespace_class.select('result.*').from('result'))
+
+ union_query = base_namespace_class.with(cte.to_arel).from_union(
+ walk_down,
+ next_elements,
+ up_one_level,
+ remove_duplicates: false,
+ remove_order: false
+ ).select(*PROJECTIONS).order(base_namespace_class.arel_table[:index].asc).limit(1)
+
+ recursive_cte << union_query
+
+ base_namespace_class.with
+ .recursive(recursive_cte.to_arel)
+ .from(recursive_cte.alias_to(namespace_class.arel_table))
+ .select(*PROJECTIONS)
+ .limit(of + 1)
+ end
+
+ def namespace_exists_query
+ Namespace.where(id: cursor[:current_id])
+ end
+
+ def walk_down
+ lateral_query = namespace_class
+ .select(:id)
+ .where('parent_id = cte.current_id')
+ .order(:id)
+ .limit(1)
+
+ base_namespace_class.select(
+ base_namespace_class.arel_table[:id].as('current_id'),
+ Arel.sql("cte.depth || #{base_namespace_table}.id").as('depth'),
+ Arel.sql("cte.ids || #{base_namespace_table}.id").as('ids'),
+ Arel.sql('cte.count + 1').as('count'),
+ Arel.sql('1::bigint AS index')
+ ).from("cte, LATERAL (#{lateral_query.to_sql}) #{base_namespace_table}")
+ end
+
+ def next_elements
+ lateral_query = namespace_class
+ .select(:id)
+ .where("#{base_namespace_table}.parent_id = cte.depth[array_length(cte.depth, 1) - 1]")
+ .where("#{base_namespace_table}.id > cte.depth[array_length(cte.depth, 1)]")
+ .order(:id)
+ .limit(1)
+
+ base_namespace_class.select(
+ base_namespace_class.arel_table[:id].as('current_id'),
+ Arel.sql("cte.depth[:array_length(cte.depth, 1) - 1] || #{base_namespace_table}.id").as('depth'),
+ Arel.sql("cte.ids || #{base_namespace_table}.id").as('ids'),
+ Arel.sql('cte.count + 1').as('count'),
+ Arel.sql('2::bigint AS index')
+ ).from("cte, LATERAL (#{lateral_query.to_sql}) #{base_namespace_table}")
+ end
+
+ def up_one_level
+ Namespace.select(
+ Arel.sql('cte.current_id').as('current_id'),
+ Arel.sql('cte.depth[:array_length(cte.depth, 1) - 1]').as('depth'),
+ Arel.sql('cte.ids').as('ids'),
+ Arel.sql('cte.count + 1').as('count'),
+ Arel.sql('3::bigint AS index')
+ ).from('cte')
+ .where('cte.depth <> ARRAY[]::int[]')
+ .limit(1)
+ end
+
+ def base_namespace_class
+ Namespace
+ end
+
+ def base_namespace_table
+ Namespace.quoted_table_name
+ end
+
+ def set_cursor!(original_cursor)
+ raise ArgumentError unless original_cursor[:depth].is_a?(Array)
+
+ @cursor = {
+ current_id: Integer(original_cursor[:current_id]),
+ depth: original_cursor[:depth].map { |value| Integer(value) }
+ }
+ end
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 920fbe3ad06..3dc6dbfff30 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -40308,6 +40308,9 @@ msgstr ""
msgid "Remote object has no absolute path."
msgstr ""
+msgid "RemoteDevelopment|Workspaces"
+msgstr ""
+
msgid "Remove"
msgstr ""
diff --git a/qa/gdk/Dockerfile.gdk b/qa/gdk/Dockerfile.gdk
index 62249c1a92f..404cd67065d 100644
--- a/qa/gdk/Dockerfile.gdk
+++ b/qa/gdk/Dockerfile.gdk
@@ -5,7 +5,7 @@ ENV GITLAB_LICENSE_MODE=test \
# Clone GDK at specific sha and bootstrap packages
#
-ARG GDK_SHA=88c3231079278a49ba59d37362357e5908fb7179
+ARG GDK_SHA=600866eab57a9645e2c16f6f1756ecc6e4983c86
RUN set -eux; \
git clone --depth 1 https://gitlab.com/gitlab-org/gitlab-development-kit.git && cd gitlab-development-kit; \
git fetch --depth 1 origin ${GDK_SHA} && git -c advice.detachedHead=false checkout ${GDK_SHA}; \
diff --git a/qa/qa/support/helpers/plan.rb b/qa/qa/support/helpers/plan.rb
index c53c099b2bd..dd9124a8f04 100644
--- a/qa/qa/support/helpers/plan.rb
+++ b/qa/qa/support/helpers/plan.rb
@@ -56,14 +56,6 @@ module QA
storage: 10
}.freeze
- CODE_SUGGESTIONS = {
- # Annual plan, rate and price
- plan_id: '8a8aa0ac8874ddc4018878da1f736782',
- rate_charge_id: '8a8aa0ac8874ddc4018878da1f9a6784',
- name: 'code suggestions',
- price: 108
- }.freeze
-
LICENSE_TYPE = {
legacy_license: 'legacy license',
online_cloud: 'online license',
diff --git a/spec/frontend/clusters/agents/components/show_spec.js b/spec/frontend/clusters/agents/components/show_spec.js
index 019f789d875..8a40c528c1d 100644
--- a/spec/frontend/clusters/agents/components/show_spec.js
+++ b/spec/frontend/clusters/agents/components/show_spec.js
@@ -76,6 +76,7 @@ describe('ClusterAgentShow', () => {
const findPaginationButtons = () => wrapper.findComponent(GlKeysetPagination);
const findTokenCount = () => wrapper.findByTestId('cluster-agent-token-count').text();
const findEESecurityTabSlot = () => wrapper.findByTestId('ee-security-tab');
+ const findEEWorkspacesTabSlot = () => wrapper.findByTestId('ee-workspaces-tab');
const findActivity = () => wrapper.findComponent(ActivityEvents);
const findIntegrationStatus = () => wrapper.findComponent(IntegrationStatus);
@@ -253,4 +254,23 @@ describe('ClusterAgentShow', () => {
expect(findEESecurityTabSlot().exists()).toBe(true);
});
});
+
+ describe('ee-workspaces-tab slot', () => {
+ it('does not display when a slot is not passed in', async () => {
+ createWrapperWithoutApollo({ clusterAgent: defaultClusterAgent });
+ await nextTick();
+ expect(findEEWorkspacesTabSlot().exists()).toBe(false);
+ });
+
+ it('does display when a slot is passed in', async () => {
+ createWrapperWithoutApollo({
+ clusterAgent: defaultClusterAgent,
+ slots: {
+ 'ee-workspaces-tab': `<gl-tab data-testid="ee-workspaces-tab">Workspaces Tab!</gl-tab>`,
+ },
+ });
+ await nextTick();
+ expect(findEEWorkspacesTabSlot().exists()).toBe(true);
+ });
+ });
});
diff --git a/spec/lib/gitlab/database/namespace_each_batch_spec.rb b/spec/lib/gitlab/database/namespace_each_batch_spec.rb
new file mode 100644
index 00000000000..23de19a6683
--- /dev/null
+++ b/spec/lib/gitlab/database/namespace_each_batch_spec.rb
@@ -0,0 +1,174 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::NamespaceEachBatch, feature_category: :database do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:other_group) { create(:group) }
+ let_it_be(:user) { create(:user, :admin) }
+
+ let(:namespace_id) { group.id }
+
+ let_it_be(:subgroup1) { create(:group, parent: group) }
+ let_it_be(:subgroup2) { create(:group, parent: group) }
+
+ let_it_be(:subsubgroup1) { create(:group, parent: subgroup1) }
+ let_it_be(:subsubgroup2) { create(:group, parent: subgroup1) }
+ let_it_be(:subsubgroup3) { create(:group, parent: subgroup1) }
+
+ let_it_be(:project1) { create(:project, namespace: group) }
+ let_it_be(:project2) { create(:project, namespace: group) }
+ let_it_be(:project3) { create(:project, namespace: subsubgroup2) }
+ let_it_be(:project4) { create(:project, namespace: subsubgroup3) }
+ let_it_be(:project5) { create(:project, namespace: subsubgroup3) }
+
+ let(:namespace_class) { Namespace }
+ let(:batch_size) { 3 }
+
+ def collected_ids(cursor = { current_id: namespace_id, depth: [namespace_id] })
+ [].tap do |ids|
+ described_class.new(namespace_class: namespace_class, cursor: cursor).each_batch(of: batch_size) do |batch_ids|
+ ids.concat(batch_ids)
+ end
+ end
+ end
+
+ shared_examples 'iteration over the hierarchy' do
+ it 'returns the correct namespace ids' do
+ expect(collected_ids).to eq([
+ group.id,
+ subgroup1.id,
+ subsubgroup1.id,
+ subsubgroup2.id,
+ project3.project_namespace_id,
+ subsubgroup3.id,
+ project4.project_namespace_id,
+ project5.project_namespace_id,
+ subgroup2.id,
+ project1.project_namespace_id,
+ project2.project_namespace_id
+ ])
+ end
+ end
+
+ it_behaves_like 'iteration over the hierarchy'
+
+ context 'when batch size is larger than the hierarchy' do
+ let(:batch_size) { 100 }
+
+ it_behaves_like 'iteration over the hierarchy'
+ end
+
+ context 'when batch size is 1' do
+ let(:batch_size) { 1 }
+
+ it_behaves_like 'iteration over the hierarchy'
+ end
+
+ context 'when stopping the iteration in the middle and resuming' do
+ it 'returns the correct ids' do
+ ids = []
+ cursor = { current_id: namespace_id, depth: [namespace_id] }
+
+ iterator = described_class.new(namespace_class: namespace_class, cursor: cursor)
+ iterator.each_batch(of: 5) do |batch_ids, new_cursor|
+ ids.concat(batch_ids)
+ cursor = new_cursor
+ end
+
+ iterator = described_class.new(namespace_class: namespace_class, cursor: cursor)
+ iterator.each_batch(of: 500) do |batch_ids|
+ ids.concat(batch_ids)
+ end
+
+ expect(collected_ids).to eq([
+ group.id,
+ subgroup1.id,
+ subsubgroup1.id,
+ subsubgroup2.id,
+ project3.project_namespace_id,
+ subsubgroup3.id,
+ project4.project_namespace_id,
+ project5.project_namespace_id,
+ subgroup2.id,
+ project1.project_namespace_id,
+ project2.project_namespace_id
+ ])
+ end
+ end
+
+ context 'when querying a subgroup' do
+ let(:namespace_id) { subgroup1.id }
+
+ it 'returns the correct ids' do
+ expect(collected_ids).to eq([
+ subgroup1.id,
+ subsubgroup1.id,
+ subsubgroup2.id,
+ project3.project_namespace_id,
+ subsubgroup3.id,
+ project4.project_namespace_id,
+ project5.project_namespace_id
+ ])
+ end
+ end
+
+ context 'when querying a subgroup without descendants' do
+ let(:namespace_id) { subgroup2.id }
+
+ it 'finds only the given namespace id' do
+ expect(collected_ids).to eq([subgroup2.id])
+ end
+ end
+
+ context 'when batching over groups only' do
+ let(:namespace_class) { Group }
+
+ it 'returns the correct namespace ids' do
+ expect(collected_ids).to eq([
+ group.id,
+ subgroup1.id,
+ subsubgroup1.id,
+ subsubgroup2.id,
+ subsubgroup3.id,
+ subgroup2.id
+ ])
+ end
+ end
+
+ context 'when the cursor is invalid' do
+ context 'when non-integer current id is given' do
+ it 'raises error' do
+ cursor = { current_id: 'not int', depth: [group.id] }
+
+ expect { collected_ids(cursor) }.to raise_error(ArgumentError)
+ end
+ end
+
+ context 'when depth is not an array' do
+ it 'raises error' do
+ cursor = { current_id: group.id, depth: group.id }
+
+ expect { collected_ids(cursor) }.to raise_error(ArgumentError)
+ end
+ end
+
+ context 'when non-integer depth values are given' do
+ it 'raises error' do
+ cursor = { current_id: group.id, depth: ['not int'] }
+
+ expect { collected_ids(cursor) }.to raise_error(ArgumentError)
+ end
+ end
+
+ context 'when giving non-existing namespace id' do
+ it 'returns nothing', :enable_admin_mode do
+ cursor = { current_id: subgroup1.id, depth: [group.id, subgroup1.id] }
+
+ Groups::DestroyService.new(group, user).execute
+
+ expect(collected_ids(cursor)).to eq([])
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/protection/rule_spec.rb b/spec/models/packages/protection/rule_spec.rb
index 3f0aefa945a..03d0440f0d9 100644
--- a/spec/models/packages/protection/rule_spec.rb
+++ b/spec/models/packages/protection/rule_spec.rb
@@ -35,30 +35,32 @@ RSpec.describe Packages::Protection::Rule, type: :model, feature_category: :pack
it { is_expected.to validate_uniqueness_of(:package_name_pattern).scoped_to(:project_id, :package_type) }
it { is_expected.to validate_length_of(:package_name_pattern).is_at_most(255) }
- [
- '@my-scope/my-package',
- '@my-scope/*my-package-with-wildcard-inbetween',
- '@my-scope/*my-package-with-wildcard-start',
- '@my-scope/my-*package-*with-wildcard-multiple-*',
- '@my-scope/my-package-with_____underscore',
- '@my-scope/my-package-with-regex-characters.+',
- '@my-scope/my-package-with-wildcard-end*'
- ].each do |package_name_pattern|
- it { is_expected.to allow_value(package_name_pattern).for(:package_name_pattern) }
+ where(:package_name_pattern, :allowed) do
+ '@my-scope/my-package' | true
+ '@my-scope/*my-package-with-wildcard-inbetween' | true
+ '@my-scope/*my-package-with-wildcard-start' | true
+ '@my-scope/my-*package-*with-wildcard-multiple-*' | true
+ '@my-scope/my-package-with_____underscore' | true
+ '@my-scope/my-package-with-regex-characters.+' | true
+ '@my-scope/my-package-with-wildcard-end*' | true
+
+ '@my-scope/my-package-with-percent-sign-%' | false
+ '*@my-scope/my-package-with-wildcard-start' | false
+ '@my-scope/my-package-with-backslash-\*' | false
end
- [
- '@my-scope/my-package-with-percent-sign-%',
- '*@my-scope/my-package-with-wildcard-start',
- '@my-scope/my-package-with-backslash-\*'
- ].each do |package_name_pattern|
- it {
- is_expected.not_to(
- allow_value(package_name_pattern)
- .for(:package_name_pattern)
- .with_message(_('should be a valid NPM package name with optional wildcard characters.'))
- )
- }
+ with_them do
+ if params[:allowed]
+ it { is_expected.to allow_value(package_name_pattern).for(:package_name_pattern) }
+ else
+ it {
+ is_expected.not_to(
+ allow_value(package_name_pattern)
+ .for(:package_name_pattern)
+ .with_message(_('should be a valid NPM package name with optional wildcard characters.'))
+ )
+ }
+ end
end
end