Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-10-14 00:11:11 +00:00
parent d6f2690cee
commit eb9f5ce5d9
46 changed files with 641 additions and 133 deletions

View File

@ -466,6 +466,12 @@ Migration/BatchMigrationsPostOnly:
- 'db/migrate/*.rb'
- 'db/post_migrate/*.rb'
Migration/UnfinishedDependencies:
Enabled: true
Include:
- 'db/migrate/*.rb'
- 'db/post_migrate/*.rb'
BackgroundMigration/FeatureCategory:
Enabled: true
Include:

View File

@ -11,6 +11,7 @@ class ProjectAuthorization < ApplicationRecord
validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true
validates :user, uniqueness: { scope: :project }, presence: true
scope :for_project, ->(projects) { where(project: projects) }
scope :non_guests, -> { where('access_level > ?', ::Gitlab::Access::GUEST) }
# TODO: To be removed after https://gitlab.com/gitlab-org/gitlab/-/issues/418205

View File

@ -80,6 +80,7 @@ class Todo < ApplicationRecord
scope :for_type, -> (type) { where(target_type: type) }
scope :for_target, -> (id) { where(target_id: id) }
scope :for_commit, -> (id) { where(commit_id: id) }
scope :not_in_users, -> (user_ids) { where.not('todos.user_id' => user_ids) }
scope :with_entity_associations, -> do
preload(:target, :author, :note, group: :route, project: [:route, :group, { namespace: [:route, :owner] }, :project_setting])
end

View File

@ -4,37 +4,6 @@ module Todos
module Destroy
class BaseService
def execute
return unless todos_to_remove?
::Gitlab::Database.allow_cross_joins_across_databases(url:
'https://gitlab.com/gitlab-org/gitlab/-/issues/422045') do
without_authorized(todos).delete_all
end
end
private
# rubocop: disable CodeReuse/ActiveRecord
def without_authorized(items)
items.where.not('todos.user_id' => authorized_users)
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def authorized_users
ProjectAuthorization.select(:user_id).where(project_id: project_ids)
end
# rubocop: enable CodeReuse/ActiveRecord
def todos
raise NotImplementedError
end
def project_ids
raise NotImplementedError
end
def todos_to_remove?
raise NotImplementedError
end
end

View File

@ -13,54 +13,57 @@ module Todos
attr_reader :issues
# rubocop: disable CodeReuse/ActiveRecord
def initialize(issue_id: nil, project_id: nil)
@issues =
if issue_id
Issue.where(id: issue_id)
Issue.id_in(issue_id)
elsif project_id
project_confidential_issues(project_id)
end
end
# rubocop: enable CodeReuse/ActiveRecord
def execute
return unless todos_to_remove?
::Gitlab::Database.allow_cross_joins_across_databases(
url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/422045') do
delete_todos
end
end
private
def delete_todos
authorized_users = ProjectAuthorization.select(:user_id)
.for_project(project_ids)
.non_guests
todos.not_in_users(authorized_users).delete_all
end
def project_confidential_issues(project_id)
project = Project.find(project_id)
project.issues.confidential_only
end
override :todos
# rubocop: disable CodeReuse/ActiveRecord
def todos
Todo.joins_issue_and_assignees
.where(target: issues)
.where(issues: { confidential: true })
.for_target(issues)
.merge(Issue.confidential_only)
.where('todos.user_id != issues.author_id')
.where('todos.user_id != issue_assignees.user_id')
end
# rubocop: enable CodeReuse/ActiveRecord
override :todos_to_remove?
def todos_to_remove?
issues&.any?(&:confidential?)
end
override :project_ids
def project_ids
issues&.distinct&.select(:project_id)
end
override :authorized_users
# rubocop: disable CodeReuse/ActiveRecord
def authorized_users
ProjectAuthorization.select(:user_id)
.where(project_id: project_ids)
.where('access_level >= ?', Gitlab::Access::REPORTER)
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
end

View File

@ -7,30 +7,31 @@ module Todos
attr_reader :group
# rubocop: disable CodeReuse/ActiveRecord
def initialize(group_id)
@group = Group.find_by(id: group_id)
@group = Group.find_by_id(group_id)
end
def execute
return unless todos_to_remove?
delete_todos
end
# rubocop: enable CodeReuse/ActiveRecord
private
override :todos
# rubocop: disable CodeReuse/ActiveRecord
def todos
Todo.where(group_id: group.id)
end
# rubocop: enable CodeReuse/ActiveRecord
override :authorized_users
def authorized_users
User.from_union([
def delete_todos
authorized_users = User.from_union([
group.project_users_with_descendants.select(:id),
group.members_with_parents.select(:user_id)
], remove_duplicates: false)
todos.not_in_users(authorized_users).delete_all
end
def todos
Todo.for_group(group.id)
end
override :todos_to_remove?
def todos_to_remove?
group&.private?
end

View File

@ -7,27 +7,32 @@ module Todos
attr_reader :project
# rubocop: disable CodeReuse/ActiveRecord
def initialize(project_id)
@project = Project.find_by(id: project_id)
@project = Project.find_by_id(project_id)
end
def execute
return unless todos_to_remove?
delete_todos
end
# rubocop: enable CodeReuse/ActiveRecord
private
override :todos
# rubocop: disable CodeReuse/ActiveRecord
def todos
Todo.where(project_id: project.id)
end
# rubocop: enable CodeReuse/ActiveRecord
def delete_todos
authorized_users = ProjectAuthorization.select(:user_id).for_project(project_ids)
todos.not_in_users(authorized_users).delete_all
end
def todos
Todo.for_project(project.id)
end
override :project_ids
def project_ids
project.id
end
override :todos_to_remove?
def todos_to_remove?
project&.private?
end

View File

@ -27,6 +27,14 @@ module Todos
private
def without_authorized(items)
items.not_in_users(authorized_users)
end
def authorized_users
ProjectAuthorization.select(:user_id).for_project(project_ids)
end
def related_todos
base_scope = Todo.for_project(project_id)
base_scope = base_scope.for_user(user_id) if user_id

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
class DropIndexNamespacesOnUpdatedAt < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
TABLE_NAME = :namespaces
INDEX_NAME = :index_namespaces_on_updated_at
def up
remove_concurrent_index_by_name TABLE_NAME, INDEX_NAME
end
def down
# no-op
# Since adding the same index will be time consuming,
# we have to create it asynchronously using 'prepare_async_index' helper.
end
end

View File

@ -0,0 +1 @@
0437aade771694981f1eca79c1bd8fed0857f1f10f1a616684fab3c906bd20b7

View File

@ -33199,8 +33199,6 @@ CREATE INDEX index_namespaces_on_traversal_ids_for_groups_btree ON namespaces US
CREATE INDEX index_namespaces_on_type_and_id ON namespaces USING btree (type, id);
CREATE INDEX index_namespaces_on_updated_at ON namespaces USING btree (updated_at);
CREATE INDEX index_namespaces_public_groups_name_id ON namespaces USING btree (name, id) WHERE (((type)::text = 'Group'::text) AND (visibility_level = 20));
CREATE INDEX index_namespaces_sync_events_on_namespace_id ON namespaces_sync_events USING btree (namespace_id);

View File

@ -2174,7 +2174,7 @@ Example response:
"user_id": 42,
"active": true,
"expires_at": "2020-10-15",
"token": "ggbfKkC4n-Lujy8jwCR2"
"token": "<your_new_access_token>"
}
```

View File

@ -23,10 +23,192 @@ The Vue app for rendering diffs uses many different Vue components, some of whic
with other areas of the GitLab app. The below chart shows the direction for which components
get rendered.
NOTE:
[Issue #388843](https://gitlab.com/gitlab-org/gitlab/-/issues/388843) is open to
generate a Mermaid graph of the components diagram. An image version of the
diagram is available in the issue.
This chart contains several types of items:
| Legend item | Interpretation |
| ----------- | -------------- |
| `xxx~~`, `ee-xxx~~` | A shortened directory path name. Can be found in `[ee]/app/assets/javascripts`, and omits `0..n` nested folders. |
| Rectangular nodes | Files. |
| Oval nodes | Plain language describing a deeper concept. |
| Double-rectangular nodes | Simplified code branch. |
| Diamond and circle nodes | Branches that have 2 (diamond) or 3+ (circle) options. |
| Pendant / banner nodes (left notch, right square) | A parent directory to shorten nested paths. |
| `./` | A path relative to the closest parent directory pendant node. Non-relative paths nested under parent pendant nodes are not in that directory. |
```mermaid
flowchart TB
classDef code font-family: monospace;
A["diffs~~app.vue"]
descVirtualScroller(["Virtual Scroller"])
codeForFiles[["v-for(diffFiles)"]]
B["diffs~~diff_file.vue"]
C["diffs~~diff_file_header.vue"]
D["diffs~~diff_stats.vue"]
E["diffs~~diff_content.vue"]
boolFileIsText{isTextFile}
boolOnlyWhitespace{isWhitespaceOnly}
boolNotDiffable{notDiffable}
boolNoPreview{noPreview}
descShowChanges(["Show button to &quot;Show changes&quot;"])
%% Non-text changes
dirDiffViewer>"vue_shared~~diff_viewer"]
F["./viewers/not_diffable.vue"]
G["./viewers/no_preview.vue"]
H["./diff_viewer.vue"]
I["diffs~~diff_view.vue"]
boolIsRenamed{isRenamed}
boolIsModeChanged{isModeChanged}
boolFileHasNoPath{hasNewPath}
boolIsImage{isImage}
J["./viewers/renamed.vue"]
K["./viewers/mode_changed.vue"]
descNoViewer(["No viewer is rendered"])
L["./viewers/image_diff_viewer.vue"]
M["./viewers/download.vue"]
N["vue_shared~~download_diff_viewer.vue"]
boolImageIsReplaced{isReplaced}
O["vue_shared~~image_viewer.vue"]
switchImageMode((image_diff_viewer.mode))
P["./viewers/image_diff/onion_skin_viewer.vue"]
Q["./viewers/image_diff/swipe_viewer.vue"]
R["./viewers/image_diff/two_up_viewer.vue"]
S["diffs~~image_diff_overlay.vue"]
codeForImageDiscussions[["v-for(discussions)"]]
T["vue_shared~~design_note_pin.vue"]
U["vue_shared~~user_avatar_link.vue"]
V["diffs~~diff_discussions.vue"]
W["batch_comments~~diff_file_drafts.vue"]
codeForTwoUpDiscussions[["v-for(discussions)"]]
codeForTwoUpDrafts[["v-for(drafts)"]]
X["notes~~notable_discussion.vue"]
%% Text-file changes
codeForDiffLines[["v-for(diffLines)"]]
Y["diffs~~diff_expansion_cell.vue"]
Z["diffs~~diff_row.vue"]
AA["diffs~~diff_line.vue"]
AB["batch_comments~~draft_note.vue"]
AC["diffs~~diff_comment_cell.vue"]
AD["diffs~~diff_gutter_avatars.vue"]
AE["ee-diffs~~inline_findings_flag_switcher.vue"]
AF["notes~~noteable_note.vue"]
AG["unknown~~publish_button.vue"]
AH["notes~~note_actions.vue"]
AI["notes~~note_body.vue"]
AJ["notes~~note_header.vue"]
AK["notes~~reply_button.vue"]
AL["notes~~note_awards_list.vue"]
AM["notes~~note_edited_text.vue"]
AN["notes~~note_form.vue"]
AO["vue_shared~~awards_list.vue"]
AP["emoji~~picker.vue"]
AQ["emoji~~emoji_list.vue"]
descEmojiVirtualScroll(["Virtual Scroller"])
AR["emoji~~category.vue"]
AS["emoji~emoji_category.vue"]
AT["vue_shared~~markdown_editor.vue"]
class codeForFiles,codeForImageDiscussions code;
class codeForTwoUpDiscussions,codeForTwoUpDrafts code;
class codeForDiffLines code;
%% Also apply code styling to this switch node
class switchImageMode code;
%% Also apply code styling to these boolean nodes
class boolFileIsText,boolOnlyWhitespace,boolNotDiffable,boolNoPreview code;
class boolIsRenamed,boolIsModeChanged,boolFileHasNoPath,boolIsImage code;
class boolImageIsReplaced code;
A --> descVirtualScroller
A -->|"Virtual Scroller is
disabled when
Find in page search
(Cmd/Ctrl+f) is used."|codeForFiles
descVirtualScroller --> codeForFiles
codeForFiles --> B
B --> C
C --> D
B --> E
%% File view flags cascade
E --> boolFileIsText
boolFileIsText --> |yes| I
boolFileIsText --> |no| boolOnlyWhitespace
boolOnlyWhitespace --> |yes| descShowChanges
boolOnlyWhitespace --> |no| dirDiffViewer
dirDiffViewer --> H
H --> boolNotDiffable
boolNotDiffable --> |yes| F
boolNotDiffable --> |no| boolNoPreview
boolNoPreview --> |yes| G
boolNoPreview --> |no| boolIsRenamed
boolIsRenamed --> |yes| J
boolIsRenamed --> |no| boolIsModeChanged
boolIsModeChanged --> |yes| K
boolIsModeChanged --> |no| boolFileHasNoPath
boolFileHasNoPath --> |yes| boolIsImage
boolFileHasNoPath --> |no| descNoViewer
boolIsImage --> |yes| L
boolIsImage --> |no| M
M --> N
%% Image diff viewer
L --> boolImageIsReplaced
boolImageIsReplaced --> |yes| switchImageMode
boolImageIsReplaced --> |no| O
switchImageMode -->|"'twoup' (default)"| R
switchImageMode -->|'onion'| P
switchImageMode -->|'swipe'| Q
P & Q --> S
S --> codeForImageDiscussions
S --> AN
R-->|"Rendered in
note container div"|U & W & V
R --> S
V --> codeForTwoUpDiscussions
W --> codeForTwoUpDrafts
%% This invisible link forces `noteable_discussion`
%% to render above `design_note_pin`
X ~~~ T
codeForTwoUpDrafts --> AB
codeForImageDiscussions & codeForTwoUpDiscussions & codeForTwoUpDrafts --> T
codeForTwoUpDiscussions --> X
%% Text file diff viewer
I --> codeForDiffLines
codeForDiffLines --> Z
codeForDiffLines -->|"isMatchLine?"| Y
codeForDiffLines -->|"hasCodeQuality?"| AA
codeForDiffLines -->|"hasDraftNote(s)?"| AB
Z -->|"hasCodeQuality?"| AE
Z -->|"hasDiscussions?"| AD
AA --> AC
%% Draft notes
AB --> AF
AF --> AH & AI & AJ
AH --> AK
AI --> AL & AM & AN
AL --> AO --> AP --> AQ --> descEmojiVirtualScroll --> AR --> AS
AN --> AT
```
Some of the components are rendered more than others, but the main component is `diff_row.vue`.
This component renders every diff line in a diff file. For performance reasons, this

View File

@ -62,13 +62,15 @@ To enable the Facebook OmniAuth provider, you must:
1. On your GitLab server, open the configuration file:
- For Linux package installations:
::Tabs
:::TabTitle Linux package installations
```shell
sudo editor /etc/gitlab/gitlab.rb
```
- For self-compiled installations:
:::TabTitle Self-compiled installations
```shell
cd /home/git/gitlab
@ -76,13 +78,17 @@ To enable the Facebook OmniAuth provider, you must:
sudo -u git -H editor config/gitlab.yml
```
::EndTabs
1. Configure the [common settings](omniauth.md#configure-common-settings)
to add `facebook` as a single sign-on provider. This enables Just-In-Time
account provisioning for users who do not have an existing GitLab account.
1. Add the provider configuration:
- For Linux package installations:
::Tabs
:::TabTitle Linux package installations
```ruby
gitlab_rails['omniauth_providers'] = [
@ -95,7 +101,7 @@ To enable the Facebook OmniAuth provider, you must:
]
```
- For self-compiled installations:
:::TabTitle Self-compiled installations
```yaml
- { name: 'facebook',
@ -104,6 +110,8 @@ To enable the Facebook OmniAuth provider, you must:
app_secret: 'YOUR_APP_SECRET' }
```
::EndTabs
1. In the provide configuration, paste the following values:
1. `YOUR_APP_ID`: The **App ID** you copied in the previous step.

View File

@ -21,7 +21,7 @@ with runner authentication tokens, which have replaced the deprecated registrati
method that uses registration tokens. For more information, see
[The new runner registration workflow](../../ci/runners/new_creation_workflow.md#the-new-runner-registration-workflow).
## Prerequisites
## Before you begin
- GitLab Runner must be installed on your GitLab instance.
- To create shared runners, you must be an administrator.
@ -97,7 +97,7 @@ To create a runner configuration, you can use:
### With the GitLab REST API
Prerequisites:
Before you begin, you need:
- The URL for your GitLab instance. For example, if your project is hosted on
`gitlab.example.com/yourname/yourproject`, your GitLab instance URL is

View File

@ -23,6 +23,11 @@ To set up issue boards for multiple teams:
1. [Create team issue boards](#create-team-issue-boards)
1. [Create issues for features](#create-issues-for-features)
## Before you begin
- If you're using an existing group for this tutorial, make sure you have at least the Reporter role for the group.
- If you're using an existing project for this tutorial, make sure you have at least the Reporter role for the project.
## The goal workflow
After you set up everything, the two teams will be able to hand off issues from one board to another, for example, like this:
@ -55,11 +60,6 @@ To prepare for when your project grows, start by creating a group.
You use groups to manage one or more related projects at the same time.
You add your users as members in the group, and assign them a role.
Prerequisites:
- If you're using an existing group for this tutorial, make sure you have at least the Reporter role
for the group.
To create a group:
1. On the left sidebar, at the top, select **Create new** (**{plus}**) and **New group**.
@ -75,11 +75,6 @@ The main code development work happens in projects and their repositories.
A project contains your code and pipelines, but also the issues that are used for planning your
upcoming code changes.
Prerequisites:
- If you're using an existing project for this tutorial, make sure you have at least the Reporter role
for the project.
To create a blank project:
1. In your group, on the left sidebar, at the top, select **Create new** (**{plus}**) and then select

View File

@ -18,9 +18,9 @@ In this tutorial, you:
1. Create a [new project and apply the compliance framework](#create-a-new-project-and-apply-the-compliance-framework) to it.
1. Combine [compliance pipeline configuration and regular pipeline configuration](#combine-pipeline-configurations).
Prerequisites:
## Before you begin
- Permission to create new top-level groups.
- You need permission to create new top-level groups.
## Create a new group

View File

@ -21,7 +21,7 @@ To configure GitLab Runner to use the GKE:
1. [Install and configure the Kubernetes Operator](#install-and-configure-the-kubernetes-operator).
1. Optional. [Verify that the configuration was successful](#verify-your-configuration).
## Prerequisites
## Before you begin
Before you can configure GitLab Runner to use the GKE you must:

View File

@ -25,7 +25,7 @@ configuration:
1. [Create and register a project runner](#create-and-register-a-project-runner).
1. [Trigger a pipeline to run your runner](#trigger-a-pipeline-to-run-your-runner).
## Prerequisite
## Before you begin
Before you can create, register, and run a runner, [GitLab Runner](https://docs.gitlab.com/runner/install/) must be installed on a local computer.

View File

@ -23,9 +23,9 @@ To set up dependency scanning:
- [Resolve the high severity vulnerability](#resolve-the-high-severity-vulnerability).
- [Test vulnerability detection in a merge request](#test-vulnerability-detection-in-a-merge-request).
## Prerequisites
## Before you begin
Before you begin, make sure you have GitPod enabled. GitPod is an on-demand cloud development
Make sure you have GitPod enabled. GitPod is an on-demand cloud development
environment. For details, see [GitPod](../integration/gitpod.md). Alternatively you can use your
own development setup. In this case you need to have Yarn and Node.js installed.

View File

@ -10,7 +10,7 @@ Dependency Scanning output can be exported to the CycloneDX JSON format.
This tutorial shows you how to generate a CycloneDX JSON SBOM for a pipeline, and then to upload it as a CI job artifact.
## Prerequisites
## Before you begin
Set up Dependency Scanning. For detailed instructions, follow [the Dependency Scanning tutorial](dependency_scanning.md).

View File

@ -20,7 +20,7 @@ Here's an overview of what you're going to do:
1. Build your Hugo site with a CI/CD pipeline.
1. Deploy and view your Hugo site with GitLab Pages.
## Prerequisites
## Before you begin
- An account on GitLab.com.
- Familiarity with Git.

View File

@ -17,7 +17,7 @@ To install a single node GitLab instance and configure it to be secure:
1. [Configure GitLab](#configure-gitlab)
1. [Next steps](#next-steps)
## Prerequisites
## Before you begin
- A domain name, and a correct [setup of DNS](https://docs.gitlab.com/omnibus/settings/dns.html).
- A Debian-based server with the following minimum specs:

View File

@ -24,7 +24,7 @@ To set up GitLab for issue triage in a project:
1. [Create an issue triage board](#create-an-issue-triage-board)
1. [Create issues for features](#create-issues-for-features)
## Prerequisites
## Before you begin
- If you're using an existing project for this tutorial, make sure you have at least the Reporter role
for the project.

View File

@ -12,9 +12,7 @@ committing changes to a Git repository from the command line.
When you're done, you'll have a project where you can practice using Git.
## What you need
Before you begin:
## Before you begin
- [Install Git on your local machine](../../topics/git/how_to_install_git/index.md).
- Ensure you can sign in to an instance of GitLab. If your organization doesn't

View File

@ -35,9 +35,9 @@ You're going to create:
1. A project in the organization for a specific piece of work, and add users to
that project.
Prerequisites:
## Before you begin
- You have administrator access to your self-managed GitLab instance.
- Make sure you have administrator access to your self-managed GitLab instance.
## Create the organization parent group and subgroups

View File

@ -24,7 +24,7 @@ release branches, and creates a minimal approval workflow for the project:
1. [Enforce CODEOWNER approval on branches](#enforce-codeowner-approval-on-branches)
1. [Create the release branches](#create-the-release-branches)
## Prerequisites
## Before you begin
- You must have the Maintainer or Owner role.
- You need a list of managers and their email addresses.

View File

@ -20,9 +20,9 @@ In this tutorial, you:
- [Link project B to the security policy project](#link-project-b-to-the-security-policy-project).
- [Test the scan execution policy with project B](#test-the-scan-execution-policy-with-project-b).
Prerequisite:
## Before you begin
- Permission to create new projects in an existing group.
- You need permission to create new projects in an existing group.
## Create project A

View File

@ -9,19 +9,19 @@ info: To determine the technical writer assigned to the Stage/Group associated w
This tutorial shows you how to create and configure a [scan result policy](../../user/application_security/policies/scan-result-policies.md). These policies can be set to take action based on scan results.
For example, in this tutorial, you'll set up a policy that requires approval from two specified users if a vulnerability is detected in a merge request.
Prerequisites:
The namespace used for this tutorial must:
- Contain a minimum of three users, including your own. If you don't have two other users, you must first
create them. For details, see [Creating users](../../user/profile/account/create_accounts.md).
To set up a scan result policy:
1. [Create a test project](#create-a-test-project).
1. [Add a scan result policy](#add-a-scan-result-policy).
1. [Test the scan result policy](#test-the-scan-result-policy).
## Before you begin
The namespace used for this tutorial must:
- Contain a minimum of three users, including your own. If you don't have two other users, you must first
create them. For details, see [Creating users](../../user/profile/account/create_accounts.md).
## Create a test project
1. On the left sidebar, at the top, select **Create new** (**{plus}**) and **New project/repository**.

View File

@ -27,7 +27,7 @@ To rewrite any number of commit messages:
1. [Update the commit messages](#update-the-commit-messages).
1. [Push the changes up to GitLab](#push-the-changes-up-to-gitlab).
## Prerequisites
## Before you begin
You must have:

View File

@ -15,10 +15,6 @@ If this list seems long and you're not sure where to start, then this tutorial i
Follow along to learn how to set up an example website project, collaborate with other GitLab users,
and use project-level analytics reports to evaluate the development of your project.
Prerequisite:
- You must have the Owner role for the group in which you create the project.
Here's an overview of what we're going to do:
1. Create a project from a template.
@ -28,6 +24,10 @@ Here's an overview of what we're going to do:
1. Create an Insights report.
1. View merge request and issue analytics.
## Before you begin
- You must have the Owner role for the group in which you create the project.
## Create a project from a template
First of all, you need to create a project in your group.

View File

@ -177,6 +177,24 @@ You need at least the Developer role to move a wiki page:
change the **Title** from `about` to `/about`.
1. Select **Save changes**.
## Export a wiki page
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/414691) in GitLab 16.3 [with a flag](../../../administration/feature_flags.md) named `print_wiki`. 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 `print_wiki`.
On GitLab.com, this feature is not available.
You can export a wiki page as a PDF file:
1. On the left sidebar, select **Search or go to** and find your project or group.
1. Select **Plan > Wiki**.
1. Go to the page you want to export.
1. Select the vertical ellipsis (**{ellipsis_v}**), and then select **Print as PDF**.
A PDF of the wiki page is created.
## View history of a wiki page
The changes of a wiki page over time are recorded in the wiki's Git repository.

View File

@ -17,6 +17,9 @@ class <%= migration_class_name %> < Gitlab::Database::Migration[<%= Gitlab::Data
# comments:
# disable_ddl_transaction!
# Add dependent 'batched_background_migrations.queued_migration_version' values.
# DEPENDENT_BATCHED_BACKGROUND_MIGRATIONS = []
def change
create_table :<%= table_name %> do |t|
<% attributes.each do |attribute| -%>

View File

@ -21,6 +21,9 @@ class <%= migration_class_name %> < Gitlab::Database::Migration[<%= Gitlab::Data
# Visit: https://docs.gitlab.com/ee/development/database/migrations_for_multiple_databases.html
# restrict_gitlab_migration gitlab_schema: :gitlab_main
# Add dependent 'batched_background_migrations.queued_migration_version' values.
# DEPENDENT_BATCHED_BACKGROUND_MIGRATIONS = []
<%- if migration_action == 'add' -%>
def change
<% attributes.each do |attribute| -%>

View File

@ -21,6 +21,9 @@ class <%= migration_class_name %> < Gitlab::Database::Migration[<%= Gitlab::Data
# Visit: https://docs.gitlab.com/ee/development/database/migrations_for_multiple_databases.html
# restrict_gitlab_migration gitlab_schema: :gitlab_main
# Add dependent 'batched_background_migrations.queued_migration_version' values.
# DEPENDENT_BATCHED_BACKGROUND_MIGRATIONS = []
def up
end

View File

@ -5,3 +5,6 @@ feature_category: <%= feature_category %>
introduced_by_url: # URL of the MR (or issue/commit) that introduced the migration
milestone: <%= current_milestone %>
queued_migration_version: <%= migration_number %>
# Replace with the approximate date you think it's best to ensure the completion of this BBM.
finalize_after: # yyyy-mm-dd
finalized_by: # version of the migration that ensured this bbm

View File

@ -0,0 +1,32 @@
# frozen_string_literal: true
module RuboCop
class BatchedBackgroundMigrations
DICTIONARY_BASE_DIR = 'db/docs/batched_background_migrations'
attr_reader :queued_migration_version
class << self
def dictionary_data
@dictionary_data ||= Dir.glob("*.yml", base: DICTIONARY_BASE_DIR).each_with_object({}) do |file_name, data|
dictionary = YAML.load_file(File.join(DICTIONARY_BASE_DIR, file_name))
next unless dictionary['queued_migration_version'].present?
data[dictionary['queued_migration_version'].to_s] = {
finalize_after: dictionary['finalize_after'],
finalized_by: dictionary['finalized_by'].to_s
}
end
end
end
def initialize(queued_migration_version)
@queued_migration_version = queued_migration_version
end
def finalized_by
self.class.dictionary_data.dig(queued_migration_version.to_s, :finalized_by)
end
end
end

View File

@ -0,0 +1,51 @@
# frozen_string_literal: true
require_relative '../../migration_helpers'
require_relative '../../batched_background_migrations'
module RuboCop
module Cop
module Migration
# Checks if there are any unfinished dependent batched bg migrations
class UnfinishedDependencies < RuboCop::Cop::Base
include MigrationHelpers
NOT_FINALIZED_MSG = <<-MESSAGE.delete("\n").squeeze(' ').strip
Dependent migration with queued version %{version} is not yet finalized.
Consider finalizing the dependent migration and update it's finalized_by attr in the dictionary.
MESSAGE
FINALIZED_BY_LATER_MIGRATION_MSG = <<-MESSAGE.delete("\n").squeeze(' ').strip
Dependent migration with queued version %{version} is finalized by later migration,
it has to be finalized before the current migration.
MESSAGE
def_node_matcher :dependent_migration_versions, <<~PATTERN
(casgn nil? :DEPENDENT_BATCHED_BACKGROUND_MIGRATIONS (array $...))
PATTERN
def on_casgn(node)
return unless in_migration?(node)
migration_version = version(node)
dependent_migration_versions(node)&.each do |dependent_migration_version_node|
dependent_migration_version = dependent_migration_version_node.value
finalized_by_version = fetch_finalized_by(dependent_migration_version)
next if finalized_by_version.present? && finalized_by_version.to_s < migration_version.to_s
msg = finalized_by_version.present? ? FINALIZED_BY_LATER_MIGRATION_MSG : NOT_FINALIZED_MSG
add_offense(node, message: format(msg, version: dependent_migration_version))
end
end
private
def fetch_finalized_by(queued_migration_version)
BatchedBackgroundMigrations.new(queued_migration_version).finalized_by
end
end
end
end
end

View File

@ -5,3 +5,6 @@ feature_category: database
introduced_by_url: # URL of the MR \(or issue/commit\) that introduced the migration
milestone: [0-9\.]+
queued_migration_version: [0-9]+
# Replace with the approximate date you think it's best to ensure the completion of this BBM.
finalize_after: # yyyy-mm-dd
finalized_by: # version of the migration that ensured this bbm

View File

@ -17,6 +17,9 @@ class CreateModelGeneratorTestFoos < Gitlab::Database::Migration[2.1]
# comments:
# disable_ddl_transaction!
# Add dependent 'batched_background_migrations.queued_migration_version' values.
# DEPENDENT_BATCHED_BACKGROUND_MIGRATIONS = []
def change
create_table :model_generator_test_foos do |t|

View File

@ -83,8 +83,10 @@ RSpec.describe ProjectAuthorization, feature_category: :groups_and_projects do
end
describe 'scopes' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, namespace: user.namespace) }
describe '.non_guests' do
let_it_be(:project) { create(:project) }
let_it_be(:project_original_owner_authorization) { project.owner.project_authorizations.first }
let_it_be(:project_authorization_guest) { create(:project_authorization, :guest, project: project) }
let_it_be(:project_authorization_reporter) { create(:project_authorization, :reporter, project: project) }
@ -100,6 +102,28 @@ RSpec.describe ProjectAuthorization, feature_category: :groups_and_projects do
].map(&:attributes))
end
end
describe '.for_project' do
let_it_be(:project_2) { create(:project, namespace: user.namespace) }
let_it_be(:project_3) { create(:project, namespace: user.namespace) }
let_it_be(:project_authorization_3) { project_3.project_authorizations.first }
let_it_be(:project_authorization_2) { project_2.project_authorizations.first }
let_it_be(:project_authorization) { project.project_authorizations.first }
it 'returns all records for the project' do
expect(described_class.for_project(project).map(&:attributes)).to match_array([
project_authorization
].map(&:attributes))
end
it 'returns all records for multiple projects' do
expect(described_class.for_project([project, project_3]).map(&:attributes)).to match_array([
project_authorization,
project_authorization_3
].map(&:attributes))
end
end
end
describe '.insert_all' do

View File

@ -396,6 +396,19 @@ RSpec.describe Todo do
end
end
describe '.not_in_users' do
it 'returns the expected todos' do
user1 = create(:user)
user2 = create(:user)
todo1 = create(:todo, user: user1)
todo2 = create(:todo, user: user1)
create(:todo, user: user2)
expect(described_class.not_in_users(user2)).to contain_exactly(todo1, todo2)
end
end
describe '.for_group_ids_and_descendants' do
it 'returns the todos for a group and its descendants' do
parent_group = create(:group)

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
require 'rubocop_spec_helper'
require_relative '../../rubocop/batched_background_migrations'
RSpec.describe RuboCop::BatchedBackgroundMigrations, feature_category: :database do
let(:bbm_dictionary_file_name) { "#{described_class::DICTIONARY_BASE_DIR}/test_migration.yml" }
let(:migration_version) { 20230307160250 }
let(:finalized_by_version) { 20230307160255 }
let(:bbm_dictionary_data) do
{
migration_job_name: 'TestMigration',
feature_category: :database,
introduced_by_url: 'https://test_url',
milestone: 16.5,
queued_migration_version: migration_version,
finalized_by: finalized_by_version
}
end
before do
File.open(bbm_dictionary_file_name, 'w') do |file|
file.write(bbm_dictionary_data.stringify_keys.to_yaml)
end
end
after do
FileUtils.rm(bbm_dictionary_file_name)
end
subject(:batched_background_migration) { described_class.new(migration_version) }
describe '#finalized_by' do
it 'returns the finalized_by version of the bbm with given version' do
expect(batched_background_migration.finalized_by).to eq(finalized_by_version.to_s)
end
it 'returns nothing for non-existing bbm dictionary' do
expect(described_class.new('random').finalized_by).to be_nil
end
end
end

View File

@ -1,14 +1,9 @@
# frozen_string_literal: true
require 'fast_spec_helper'
require 'stringio'
require_relative '../support/helpers/next_instance_of'
require 'rubocop_spec_helper'
require_relative '../../rubocop/check_graceful_task'
RSpec.describe RuboCop::CheckGracefulTask do
include NextInstanceOf
let(:output) { StringIO.new }
subject(:task) { described_class.new(output) }

View File

@ -0,0 +1,118 @@
# frozen_string_literal: true
require 'rubocop_spec_helper'
require_relative '../../../../rubocop/cop/migration/unfinished_dependencies'
RSpec.describe RuboCop::Cop::Migration::UnfinishedDependencies, feature_category: :database do
let(:version) { 20230307160250 }
let(:migration) do
<<~RUBY
class TestMigration < Gitlab::Database::Migration[2.1]
def perform; end
end
RUBY
end
before do
allow(cop).to receive(:in_migration?).and_return(true)
allow(cop).to receive(:version).and_return(version)
end
shared_examples 'migration with rubocop offense' do
it 'registers an offense' do
expect_offense(migration)
end
end
shared_examples 'migration without any rubocop offense' do
it 'does not register any offense' do
expect_no_offenses(migration)
end
end
context 'without any dependent batched background migrations' do
it_behaves_like 'migration without any rubocop offense'
end
context 'with dependent batched background migrations' do
let(:dependent_migration_versions) { [20230307160240] }
let(:migration) do
<<~RUBY
class TestMigration < Gitlab::Database::Migration[2.1]
DEPENDENT_BATCHED_BACKGROUND_MIGRATIONS = #{dependent_migration_versions}
def perform; end
end
RUBY
end
context 'with unfinished dependent migration' do
before do
allow(cop).to receive(:fetch_finalized_by)
.with(dependent_migration_versions.first)
.and_return(nil)
end
it_behaves_like 'migration with rubocop offense' do
let(:migration) do
<<~RUBY
class TestMigration < Gitlab::Database::Migration[2.1]
DEPENDENT_BATCHED_BACKGROUND_MIGRATIONS = #{dependent_migration_versions}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{format(described_class::NOT_FINALIZED_MSG, version: dependent_migration_versions.first)}
def perform; end
end
RUBY
end
end
end
context 'with incorrectly finalized dependent migration' do
let(:dependent_migration_versions) { [20230307160240, 20230307160230] }
before do
allow(cop).to receive(:fetch_finalized_by)
.with(dependent_migration_versions.first)
.and_return(version - 10)
allow(cop).to receive(:fetch_finalized_by)
.with(dependent_migration_versions.last)
.and_return(version + 10)
end
it_behaves_like 'migration with rubocop offense' do
let(:migration) do
<<~RUBY
class TestMigration < Gitlab::Database::Migration[2.1]
DEPENDENT_BATCHED_BACKGROUND_MIGRATIONS = #{dependent_migration_versions}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{format(described_class::FINALIZED_BY_LATER_MIGRATION_MSG, version: dependent_migration_versions.last)}
def perform; end
end
RUBY
end
end
end
context 'with properly finalized dependent background migrations' do
before do
allow_next_instance_of(RuboCop::BatchedBackgroundMigrations) do |bbms|
allow(bbms).to receive(:finalized_by).and_return(version - 5)
end
end
it_behaves_like 'migration without any rubocop offense'
end
end
context 'for non migrations' do
before do
allow(cop).to receive(:in_migration?).and_return(false)
end
it_behaves_like 'migration without any rubocop offense'
end
end

View File

@ -8,6 +8,7 @@ require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/shared_contexts/default_rspec_language_config_context'
require_relative 'support/helpers/next_instance_of'
require_relative 'rubocop/support_workaround'
RSpec.configure do |config|
@ -21,6 +22,7 @@ RSpec.configure do |config|
config.include RuboCop::RSpec::ExpectOffense, type: :rubocop
config.include RuboCop::RSpec::ExpectOffense, type: :rubocop_rspec
config.include NextInstanceOf
config.include_context 'config', type: :rubocop
config.include_context 'with default RSpec/Language config', type: :rubocop_rspec