Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-04-21 06:09:28 +00:00
parent b37b86966c
commit 430aebe8af
73 changed files with 288 additions and 292 deletions

View File

@ -118,7 +118,6 @@ linters:
- Layout/TrailingEmptyLines
- Lint/LiteralInInterpolation
- Lint/ParenthesesAsGroupedExpression
- Lint/RedundantWithIndex
- Lint/SafeNavigationConsistency
- Metrics/BlockNesting
- Naming/VariableName

View File

@ -603,26 +603,6 @@ RSpec/EmptyLineAfterFinalLetItBe:
- spec/features/merge_request/user_posts_notes_spec.rb
- spec/features/operations_sidebar_link_spec.rb
- spec/features/participants_autocomplete_spec.rb
- spec/features/projects/badges/pipeline_badge_spec.rb
- spec/features/projects/branches/user_deletes_branch_spec.rb
- spec/features/projects/commit/cherry_pick_spec.rb
- spec/features/projects/commit/user_comments_on_commit_spec.rb
- spec/features/projects/commit/user_reverts_commit_spec.rb
- spec/features/projects/commit/user_views_user_status_on_commit_spec.rb
- spec/features/projects/confluence/user_views_confluence_page_spec.rb
- spec/features/projects/files/gitlab_ci_syntax_yml_dropdown_spec.rb
- spec/features/projects/issues/design_management/user_views_design_images_spec.rb
- spec/features/projects/labels/user_sees_links_to_issuables_spec.rb
- spec/features/projects/labels/user_views_labels_spec.rb
- spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
- spec/features/projects/merge_request_button_spec.rb
- spec/features/projects/pages/user_adds_domain_spec.rb
- spec/features/projects/pipelines/pipeline_spec.rb
- spec/features/projects/product_analytics/events_spec.rb
- spec/features/projects/settings/project_settings_spec.rb
- spec/features/projects/settings/repository_settings_spec.rb
- spec/features/projects/snippets/user_views_snippets_spec.rb
- spec/features/projects/user_sees_user_popover_spec.rb
- spec/features/snippets/embedded_snippet_spec.rb
- spec/finders/alert_management/alerts_finder_spec.rb
- spec/finders/ci/commit_statuses_finder_spec.rb
@ -699,12 +679,6 @@ RSpec/EmptyLineAfterFinalLetItBe:
- spec/lib/gitlab/data_builder/wiki_page_spec.rb
- spec/lib/gitlab/deploy_key_access_spec.rb
- spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
- spec/lib/gitlab/git/lfs_changes_spec.rb
- spec/lib/gitlab/git/merge_base_spec.rb
- spec/lib/gitlab/git/push_spec.rb
- spec/lib/gitlab/git_access_design_spec.rb
- spec/lib/gitlab/git_access_project_spec.rb
- spec/lib/gitlab/git_access_wiki_spec.rb
- spec/lib/gitlab/gitaly_client/operation_service_spec.rb
- spec/lib/gitlab/gl_repository/repo_type_spec.rb
- spec/lib/gitlab/group_search_results_spec.rb

View File

@ -52,7 +52,9 @@ module Namespaces
end
def use_traversal_ids?
Feature.enabled?(:use_traversal_ids, root_ancestor, default_enabled: :yaml)
return false unless Feature.enabled?(:use_traversal_ids, root_ancestor, default_enabled: :yaml)
traversal_ids.present?
end
def self_and_descendants
@ -86,25 +88,7 @@ module Namespaces
raise UnboundedSearch.new('Must bound search by a top') unless top
without_sti_condition
.traversal_ids_contains(latest_traversal_ids(top))
end
# traversal_ids are a cached value.
#
# The traversal_ids value in a loaded object can become stale when compared
# to the database value. For example, if you load a hierarchy and then move
# a group, any previously loaded descendant objects will have out of date
# traversal_ids.
#
# To solve this problem, we never depend on the object's traversal_ids
# value. We always query the database first with a sub-select for the
# latest traversal_ids.
#
# Note that ActiveRecord will cache query results. You can avoid this by
# using `Model.uncached { ... }`
def latest_traversal_ids(namespace = self)
without_sti_condition.where('id = (?)', namespace)
.select('traversal_ids as latest_traversal_ids')
.traversal_ids_contains("{#{top.id}}")
end
end
end

View File

@ -1,7 +1,7 @@
.js-projects-list-holder
- if @projects.any?
%ul.projects-list.content-list.admin-projects
- @projects.each_with_index do |project|
- @projects.each do |project|
%li.project-row{ class: ('no-description' if project.description.blank?) }
.controls
= link_to _('Edit'), edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn gl-button btn-default"

View File

@ -0,0 +1,5 @@
---
title: Fix EmptyLineAfterFinalLetItBe offenses in spec/lib/gitlab/git
merge_request: 58254
author: Huzaifa Iftikhar @huzaifaiftikhar
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix EmptyLineAfterFinalLetItBe Rubocop offenses for projects module
merge_request: 58187
author: Huzaifa Iftikhar @huzaifaiftikhar
type: fixed

View File

@ -395,18 +395,6 @@ module.exports = {
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
Popper: ['popper.js', 'default'],
Alert: 'exports-loader?Alert!bootstrap/js/dist/alert',
Button: 'exports-loader?Button!bootstrap/js/dist/button',
Carousel: 'exports-loader?Carousel!bootstrap/js/dist/carousel',
Collapse: 'exports-loader?Collapse!bootstrap/js/dist/collapse',
Dropdown: 'exports-loader?Dropdown!bootstrap/js/dist/dropdown',
Modal: 'exports-loader?Modal!bootstrap/js/dist/modal',
Popover: 'exports-loader?Popover!bootstrap/js/dist/popover',
Scrollspy: 'exports-loader?Scrollspy!bootstrap/js/dist/scrollspy',
Tab: 'exports-loader?Tab!bootstrap/js/dist/tab',
Tooltip: 'exports-loader?Tooltip!bootstrap/js/dist/tooltip',
Util: 'exports-loader?Util!bootstrap/js/dist/util',
}),
// if DLLs are enabled, detect whether the DLL exists and create it automatically if necessary

View File

@ -300,7 +300,7 @@ variables:
DAST_SUBMIT_FIELD: login # the `id` or `name` of the element that when clicked will submit the login form or the password form of a multi-page login process
DAST_FIRST_SUBMIT_FIELD: next # the `id` or `name` of the element that when clicked will submit the username form of a multi-page login process
DAST_EXCLUDE_URLS: http://example.com/sign-out,http://example.com/sign-out-2 # optional, URLs to skip during the authenticated scan; comma-separated, no spaces in between
DAST_AUTH_VALIDATION_URL: http://example.com/loggedin_page # optional, a URL only accessible to logged in users that DAST can use to confirm successful authentication
DAST_AUTH_VERIFICATION_URL: http://example.com/loggedin_page # optional, a URL only accessible to logged in users that DAST can use to confirm successful authentication
```
The results are saved as a
@ -645,7 +645,7 @@ DAST can be [configured](#customizing-the-dast-settings) using CI/CD variables.
| `DAST_API_SPECIFICATION` | URL or string | The API specification to import. The specification can be hosted at a URL, or the name of a file present in the `/zap/wrk` directory. `DAST_WEBSITE` must be specified if this is omitted. |
| `DAST_SPIDER_START_AT_HOST` | boolean | Set to `false` to prevent DAST from resetting the target to its host before scanning. When `true`, non-host targets `http://test.site/some_path` is reset to `http://test.site` before scan. Default: `true`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/258805) in GitLab 13.6. |
| `DAST_AUTH_URL` | URL | The URL of the page containing the sign-in HTML form on the target website. `DAST_USERNAME` and `DAST_PASSWORD` are submitted with the login form to create an authenticated scan. Not supported for API scans. |
| `DAST_AUTH_VALIDATION_URL` | URL | A URL only accessible to logged in users that DAST can use to confirm successful authentication. If provided, DAST will exit if it cannot access the URL. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/207335) in GitLab 13.8.
| `DAST_AUTH_VERIFICATION_URL` | URL | A URL only accessible to logged in users that DAST can use to confirm successful authentication. If provided, DAST will exit if it cannot access the URL. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/207335) in GitLab 13.8.
| `DAST_USERNAME` | string | The username to authenticate to in the website. |
| `DAST_PASSWORD` | string | The password to authenticate to in the website. |
| `DAST_USERNAME_FIELD` | string | The name of username field at the sign-in HTML form. |

View File

@ -247,8 +247,8 @@ You can customize the default scanning rules provided by our SAST analyzers.
Ruleset customization supports two capabilities:
1. Disabling predefined rules
1. Modifying the default behavior of a given analyzer
1. Disabling predefined rules (available for all analyzers).
1. Modifying the default behavior of a given analyzer (only available for `nodejs-scan` and `gosec`).
These capabilities can be used simultaneously.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View File

@ -15,7 +15,7 @@ can do it by bulk editing them, that is, editing them together.
Only the items visible on the current page are selected for bulk editing (up to 20).
![Bulk editing](img/bulk-editing_v13_2.png)
![Bulk editing](img/bulk_editing_v13_11.png)
## Bulk edit issues at the group level

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -16,7 +16,7 @@ by bulk editing them, that is, editing them together.
Only the items visible on the current page are selected for bulk editing (up to 20).
![Bulk editing](img/bulk-editing_v13_2.png)
![Bulk editing](img/bulk_editing_v13_11.png)
## Bulk edit issues at the project level

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@ -7,22 +7,22 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Issue weight **(PREMIUM)**
> - Moved to GitLab Premium in 13.9.
> Moved to GitLab Premium in 13.9.
When you have a lot of issues, it can be hard to get an overview.
By adding a weight to each issue, you can get a better idea of how much time,
value or complexity a given issue has or costs.
With weighted issues, you can get a better idea of how much time,
value, or complexity a given issue has or costs.
You can set the weight of an issue during its creation, by changing the
value in the dropdown menu. You can set it to a non-negative integer
value from 0, 1, 2, and so on. (The database stores a 4-byte value, so the
upper bound is essentially limitless).
upper bound is essentially limitless.)
You can remove weight from an issue
as well.
This value appears on the right sidebar of an individual issue, as well as
in the issues page next to a distinctive balance scale icon.
in the issues page next to a weight icon (**{weight}**).
As an added bonus, you can see the total sum of all issues on the milestone page.
![issue page](img/issue_weight.png)
![issue page](img/issue_weight_v13_11.png)

View File

@ -67,23 +67,25 @@ When you're creating a new issue, these are the fields you can fill in:
- Milestone
- Labels
### New issue from the group-level Issue Tracker
### New issue from the group-level issue tracker
Go to the Group dashboard and click **Issues** in the sidebar to visit the Issue Tracker
for all projects in your Group. Select the project you'd like to add an issue for
using the dropdown button at the top-right of the page.
To visit the issue tracker for all projects in your group:
![Select project to create issue](img/select_project_from_group_level_issue_tracker.png)
1. Go to the group dashboard.
1. In the left sidebar, select **Issues**.
1. In the top-right, select the **Select project to create issue** button.
1. Select the project you'd like to create an issue for. The button now appears as **New issue in <selected project>**.
1. Select **New issue in <selected project>**.
![Select project to create issue](img/select_project_from_group_level_issue_tracker_v13_11.png)
The project you selected most recently becomes the default for your next visit.
This should save you a lot of time and clicks, if you mostly create issues for the same project.
![Create issue from group-level issue tracker](img/create_issue_from_group_level_issue_tracker.png)
### New issue via Service Desk
Enable [Service Desk](../service_desk.md) for your project and offer email support.
By doing so, when your customer sends a new email, a new issue can be created in
Now, when your customer sends a new email, a new issue can be created in
the appropriate project and followed up from there.
### New issue via email
@ -207,7 +209,7 @@ description, issues `#4` and `#6` are closed automatically when the MR is merged
Using `Related to` flags `#5` as a [related issue](related_issues.md),
but is not closed automatically.
![merge request closing issue when merged](img/merge_request_closes_issue.png)
![merge request closing issue when merged](img/merge_request_closes_issue_v13_11.png)
If the issue is in a different repository than the MR, add the full URL for the issue(s):
@ -278,12 +280,10 @@ of your installation.
## Deleting issues
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/2982) in GitLab 8.6.
Users with [project owner permission](../../permissions.md) can delete an issue by
editing it and clicking on the delete button.
editing it and selecting **Delete issue**.
![delete issue - button](img/delete_issue.png)
![delete issue - button](img/delete_issue_v13_11.png)
## Promote an issue to an epic **(PREMIUM)**

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -38,7 +38,7 @@ You can assign **group milestones** to any issue or merge request of any project
To view the group milestone list, in a group, go to **{issues}** **Issues > Milestones**.
You can also view all milestones you have access to in the dashboard milestones list.
To view both project milestones and group milestones you have access to, click **More > Milestones**
To view both project milestones and group milestones you have access to, select **More > Milestones**
on the top navigation bar.
For information about project and group milestones API, see:
@ -47,9 +47,9 @@ For information about project and group milestones API, see:
- [Group Milestones API](../../../api/group_milestones.md)
NOTE:
If you're in a group and click **Issues > Milestones**, GitLab displays group milestones
If you're in a group and select **Issues > Milestones**, GitLab displays group milestones
and the milestones of projects in this group.
If you're in a project and click **Issues > Milestones**, GitLab displays only this project's milestones.
If you're in a project and select **Issues > Milestones**, GitLab displays only this project's milestones.
## Creating milestones
@ -58,23 +58,23 @@ A permission level of [Developer or higher](../../permissions.md) is required to
### New project milestone
To create a **project milestone**:
To create a project milestone:
1. In a project, go to **{issues}** **Issues > Milestones**.
1. Click **New milestone**.
1. Select **New milestone**.
1. Enter the title, an optional description, an optional start date, and an optional due date.
1. Click **New milestone**.
1. Select **New milestone**.
![New project milestone](img/milestones_new_project_milestone.png)
### New group milestone
To create a **group milestone**:
To create a group milestone:
1. In a group, go to **{issues}** **Issues > Milestones**.
1. Click **New milestone**.
1. Select **New milestone**.
1. Enter the title, an optional description, an optional start date, and an optional due date.
1. Click **New milestone**.
1. Select **New milestone**.
![New group milestone](img/milestones_new_group_milestone_v13_5.png)
@ -86,10 +86,10 @@ A permission level of [Developer or higher](../../permissions.md) is required to
To edit a milestone:
1. In a project or group, go to **{issues}** **Issues > Milestones**.
1. Click a milestone's title.
1. Click **Edit**.
1. Select a milestone's title.
1. Select **Edit**.
You can delete a milestone by clicking the **Delete** button.
You can delete a milestone by selecting the **Delete** button.
### Promoting project milestones to group milestones
@ -182,7 +182,7 @@ The milestone sidebar on the milestone view shows the following:
- The total time spent on all issues and merge requests assigned to the milestone.
- The total issue weight of all issues assigned to the milestone.
![Project milestone page](img/milestones_project_milestone_page.png)
![Project milestone page](img/milestones_project_milestone_page_sidebar_v13_11.png)
<!-- ## Troubleshooting

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -20,11 +20,12 @@ Your *To-Do List* offers a chronological list of items waiting for your input
The To-Do List supports tracking [actions](#what-triggers-a-to-do-item) related to
the following:
- Issues
- Merge Requests
- Epics **(ULTIMATE)**
- [Issues](project/issues/index.md)
- [Merge requests](project/merge_requests/index.md)
- [Epics](group/epics/index.md)
- [Designs](project/issues/design_management.md)
![to-do screenshot showing a list of items to check on](img/todos_index.png)
![to-do list with items to check on](img/todos_index_v13_11.png)
You can access your To-Do List by clicking the To-Do List icon (**{task-done}**)
next to the search bar in the top navigation. If the to-do item count is:
@ -33,15 +34,13 @@ next to the search bar in the top navigation. If the to-do item count is:
- *100 or more*, the number displays as 99+. The exact number displays in the
To-Do List.
![To Do icon](img/todos_icon.png)
## What triggers a to-do item
A to-do item appears on your To-Do List when:
- An issue or merge request is assigned to you.
- You're `@mentioned` in the description or comment of an issue or merge request
(or epic **(ULTIMATE)**).
(or epic).
- You are `@mentioned` in a comment on a:
- Commit
- Design
@ -54,7 +53,7 @@ A to-do item appears on your To-Do List when:
- [In GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/12136) and later, a
merge request is removed from a
[merge train](../ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md),
and you're the user that added it. **(PREMIUM)**
and you're the user that added it.
When several trigger actions occur for the same user on the same object (for
example, an issue), GitLab displays only the first action as a single to do
@ -93,18 +92,18 @@ for filtering purposes; otherwise, they appear as normal.
### Manually creating a to-do item
You can also add the following to your To-Do List by clicking the **Add a to do** button on an:
You can also add the following to your To-Do List by clicking the **Add a to do** button on:
- [Issue](project/issues/index.md)
- [Merge Request](project/merge_requests/index.md)
- [Epic](group/epics/index.md) **(ULTIMATE)**
- [Design](project/issues/design_management.md)
- [Issues](project/issues/index.md)
- [Merge requests](project/merge_requests/index.md)
- [Epics](group/epics/index.md)
- [Designs](project/issues/design_management.md)
![Adding a to-do item from the issuable sidebar](img/todos_add_todo_sidebar.png)
## Marking a to-do item as done
Any action to an issue or merge request (or epic **(PREMIUM)**) marks its
Any action to an issue, merge request, or epic marks its
corresponding to-do item as done.
Actions that dismiss to-do items include:
@ -119,8 +118,8 @@ action. If you close the issue or merge request, your to-do item is marked as
done.
To prevent other users from closing issues without you being notified, if
someone else closes, merges, or takes action on an issue or merge request (or
epic **(ULTIMATE)**), your to-do item remains pending.
someone else closes, merges, or takes action on an issue, merge request, or
epic, your to-do item remains pending.
There's just one to-do item for each of these, so mentioning a user many times
in an issue only triggers one to-do item.
@ -132,7 +131,7 @@ your To-Do List.
![A to do in the To-Do List](img/todos_todo_list_item.png)
You can also mark a to-do item as done by clicking the **Mark as done** button
in the sidebar of an issue or merge request (or epic **(ULTIMATE)**).
in the sidebar of an issue, merge request, or epic.
![Mark as done from the issuable sidebar](img/todos_mark_done_sidebar.png)
@ -148,7 +147,7 @@ You can use the following types of filters with your To-Do List:
| Project | Filter by project. |
| Group | Filter by group. |
| Author | Filter by the author that triggered the to do. |
| Type | Filter by issue, merge request, design, or epic. **(ULTIMATE)** |
| Type | Filter by issue, merge request, design, or epic. |
| Action | Filter by the action that triggered the to do. |
You can also filter by more than one of these at the same time. The previously

View File

@ -94,7 +94,6 @@
"editorconfig": "^0.15.3",
"emoji-regex": "^7.0.3",
"emoji-unicode-version": "^0.2.1",
"exports-loader": "^0.7.0",
"fast-mersenne-twister": "1.0.2",
"file-loader": "^6.2.0",
"fuzzaldrin-plus": "^0.6.0",

View File

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe 'Pipeline Badge' do
let_it_be(:project) { create(:project, :repository, :public) }
let(:ref) { project.default_branch }
context 'when the project has a pipeline' do

View File

@ -4,6 +4,7 @@ require "spec_helper"
RSpec.describe "User deletes branch", :js do
let_it_be(:user) { create(:user) }
let(:project) { create(:project, :repository) }
before do

View File

@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe 'Cherry-pick Commits', :js do
let_it_be(:user) { create(:user) }
let_it_be(:sha) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' }
let!(:project) { create_default(:project, :repository, namespace: user.namespace) }
let(:master_pickable_commit) { project.commit(sha) }

View File

@ -8,6 +8,7 @@ RSpec.describe "User comments on commit", :js do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:comment_text) { "XML attached" }
before_all do

View File

@ -6,6 +6,7 @@ RSpec.describe 'User reverts a commit', :js do
include RepoHelpers
let_it_be(:user) { create(:user) }
let!(:project) { create_default(:project, :repository, namespace: user.namespace) }
before do

View File

@ -7,6 +7,7 @@ RSpec.describe 'Project > Commit > View user status' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:commit_author) { create(:user, email: sample_commit.author_email) }
before do

View File

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe 'User views the Confluence page' do
let_it_be(:user) { create(:user) }
let(:project) { create(:project, :public) }
before do

View File

@ -6,6 +6,7 @@ RSpec.describe 'Projects > Files > User wants to add a .gitlab-ci.yml file' do
include Spec::Support::Helpers::Features::EditorLiteSpecHelpers
let_it_be(:namespace) { create(:namespace) }
let(:project) { create(:project, :repository, namespace: namespace) }
before do

View File

@ -8,6 +8,7 @@ RSpec.describe 'Users views raw design image files' do
let_it_be(:project) { create(:project, :public) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:design) { create(:design, :with_file, issue: issue, versions_count: 2) }
let(:newest_version) { design.versions.ordered.first }
let(:oldest_version) { design.versions.ordered.last }

View File

@ -51,6 +51,7 @@ RSpec.describe 'Projects > Labels > User sees links to issuables' do
context 'with a group label' do
let_it_be(:group) { create(:group) }
let(:label) { create(:group_label, group: group, title: 'bug') }
context 'when merge requests and issues are enabled for the project' do

View File

@ -5,6 +5,7 @@ require "spec_helper"
RSpec.describe "User views labels" do
let_it_be(:project) { create(:project_empty_repo, :public) }
let_it_be(:user) { create(:user) }
let(:label_titles) { %w[bug enhancement feature] }
let!(:prioritized_label) { create(:label, project: project, title: 'prioritized-label-name', priority: 1) }

View File

@ -9,6 +9,7 @@ RSpec.describe 'Projects > Members > Maintainer adds member with expiration date
let_it_be(:maintainer) { create(:user) }
let_it_be(:project) { create(:project) }
let(:new_member) { create(:user) }
before do

View File

@ -7,6 +7,7 @@ RSpec.describe 'Merge Request button' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public, :repository) }
let(:forked_project) { fork_project(project, user, repository: true) }
shared_examples 'Merge request button only shown when allowed' do

View File

@ -5,6 +5,7 @@ RSpec.describe 'User adds pages domain', :js do
include LetsEncryptHelpers
let_it_be(:project) { create(:project, pages_https_only: false) }
let(:user) { create(:user) }
before do

View File

@ -739,6 +739,7 @@ RSpec.describe 'Pipeline', :js do
context 'when build requires resource', :sidekiq_inline do
let_it_be(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:resource_group) { create(:ci_resource_group, project: project) }

View File

@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe 'Product Analytics > Events' do
let_it_be(:project) { create(:project_empty_repo) }
let_it_be(:user) { create(:user) }
let(:event) { create(:product_analytics_event, project: project) }
before do

View File

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe 'Projects settings' do
let_it_be(:project) { create(:project) }
let(:user) { project.owner }
let(:panel) { find('.general-settings', match: :first) }
let(:button) { panel.find('.btn.gl-button.js-settings-toggle') }

View File

@ -42,6 +42,7 @@ RSpec.describe 'Projects > Settings > Repository settings' do
context 'Deploy Keys', :js do
let_it_be(:private_deploy_key) { create(:deploy_key, title: 'private_deploy_key', public: false) }
let_it_be(:public_deploy_key) { create(:another_deploy_key, title: 'public_deploy_key', public: true) }
let(:new_ssh_key) { attributes_for(:key)[:key] }
it 'get list of keys' do

View File

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe 'Projects > Snippets > User views snippets' do
let_it_be(:project) { create(:project) }
let(:user) { create(:user) }
def visit_project_snippets

View File

@ -6,6 +6,7 @@ RSpec.describe 'User sees user popover', :js do
include Spec::Support::Helpers::Features::NotesHelpers
let_it_be(:project) { create(:project, :repository) }
let(:user) { project.creator }
let(:merge_request) do
create(:merge_request, source_project: project, target_project: project)

View File

@ -62,6 +62,12 @@ RSpec.describe Projects::MergeRequestsController, '(JavaScript fixtures)', type:
remove_repository(project)
end
it 'merge_requests/merge_request_with_single_assignee_feature.html' do
stub_licensed_features(multiple_merge_request_assignees: false)
render_merge_request(merge_request)
end
it 'merge_requests/merge_request_of_current_user.html' do
merge_request.update(author: user)

View File

@ -1,145 +1,33 @@
import { waitFor } from '@testing-library/dom';
import MockAdapter from 'axios-mock-adapter';
import { cloneDeep } from 'lodash';
import { getJSONFixture } from 'helpers/fixtures';
import axios from '~/lib/utils/axios_utils';
import UsersSelect from '~/users_select';
// TODO: generate this from a fixture that guarantees the same output in CE and EE [(see issue)][1].
// Hardcoding this HTML temproarily fixes a FOSS ~"master::broken" [(see issue)][2].
// [1]: https://gitlab.com/gitlab-org/gitlab/-/issues/327809
// [2]: https://gitlab.com/gitlab-org/gitlab/-/issues/327805
const getUserSearchHTML = () => `
<div class="js-sidebar-assignee-data selectbox hide-collapsed">
<input type="hidden" name="merge_request[assignee_ids][]" value="0">
<div class="dropdown js-sidebar-assignee-dropdown">
<button class="dropdown-menu-toggle js-user-search js-author-search js-multiselect js-save-user-data js-invite-members-track" type="button" data-first-user="frontend-fixtures" data-current-user="true" data-iid="1" data-issuable-type="merge_request" data-project-id="1" data-author-id="1" data-field-name="merge_request[assignee_ids][]" data-issue-update="http://test.host/frontend-fixtures/merge-requests-project/-/merge_requests/1.json" data-ability-name="merge_request" data-null-user="true" data-display="static" data-multi-select="true" data-dropdown-title="Select assignee(s)" data-dropdown-header="Assignee(s)" data-track-event="show_invite_members" data-toggle="dropdown"><span class="dropdown-toggle-text ">Select assignee(s)</span><svg class="s16 dropdown-menu-toggle-icon gl-top-3" data-testid="chevron-down-icon"><use xlink:href="http://test.host/assets/icons-16c30bec0d8a57f0a33e6f6215c6aff7a6ec5e4a7e6b7de733a6b648541a336a.svg#chevron-down"></use></svg></button><div class="dropdown-menu dropdown-select dropdown-menu-user dropdown-menu-selectable dropdown-menu-author dropdown-extended-height">
<div class="dropdown-title gl-display-flex">
<span class="gl-ml-auto">Assign to</span><button class="dropdown-title-button dropdown-menu-close gl-ml-auto" aria-label="Close" type="button"><svg class="s16 dropdown-menu-close-icon" data-testid="close-icon"><use xlink:href="http://test.host/assets/icons-16c30bec0d8a57f0a33e6f6215c6aff7a6ec5e4a7e6b7de733a6b648541a336a.svg#close"></use></svg></button>
</div>
<div class="dropdown-input">
<input type="search" id="" data-qa-selector="dropdown_input_field" class="dropdown-input-field" placeholder="Search users" autocomplete="off"><svg class="s16 dropdown-input-search" data-testid="search-icon"><use xlink:href="http://test.host/assets/icons-16c30bec0d8a57f0a33e6f6215c6aff7a6ec5e4a7e6b7de733a6b648541a336a.svg#search"></use></svg><svg class="s16 dropdown-input-clear js-dropdown-input-clear" data-testid="close-icon"><use xlink:href="http://test.host/assets/icons-16c30bec0d8a57f0a33e6f6215c6aff7a6ec5e4a7e6b7de733a6b648541a336a.svg#close"></use></svg>
</div>
<div data-qa-selector="dropdown_list_content" class="dropdown-content "></div>
<div class="dropdown-footer">
<ul class="dropdown-footer-list">
<li>
<div class="js-invite-members-trigger" data-display-text="Invite Members" data-event="click_invite_members" data-label="edit_assignee" data-trigger-element="anchor"></div>
</li>
</ul>
</div>
<div class="dropdown-loading"><div class="gl-spinner-container"><span class="gl-spinner gl-spinner-orange gl-spinner-md gl-mt-7" aria-label="Loading"></span></div></div>
</div>
</div>
</div>
`;
const USER_SEARCH_HTML = getUserSearchHTML();
const AUTOCOMPLETE_USERS = getJSONFixture('autocomplete/users.json');
import {
createInputsModelExpectation,
createUnassignedExpectation,
createAssignedExpectation,
createTestContext,
findDropdownItemsModel,
findDropdownItem,
findAssigneesInputsModel,
getUsersFixtureAt,
setAssignees,
toggleDropdown,
waitForDropdownItems,
} from './test_helper';
describe('~/users_select/index', () => {
let subject;
let mock;
const createSubject = (currentUser = null) => {
if (subject) {
throw new Error('test subject is already created');
}
subject = new UsersSelect(currentUser);
};
// finders -------------------------------------------------------------------
const findAssigneesInputs = () =>
document.querySelectorAll('input[name="merge_request[assignee_ids][]');
const findAssigneesInputsModel = () =>
Array.from(findAssigneesInputs()).map((input) => ({
value: input.value,
dataset: { ...input.dataset },
}));
const findUserSearchButton = () => document.querySelector('.js-user-search');
const findDropdownItem = ({ id }) => document.querySelector(`li[data-user-id="${id}"] a`);
const findDropdownItemsModel = () =>
Array.from(document.querySelectorAll('.dropdown-content li')).map((el) => {
if (el.classList.contains('divider')) {
return {
type: 'divider',
};
} else if (el.classList.contains('dropdown-header')) {
return {
type: 'dropdown-header',
text: el.textContent,
};
}
return {
type: 'user',
userId: el.dataset.userId,
};
});
// arrange/act helpers -------------------------------------------------------
const setAssignees = (...users) => {
findAssigneesInputs().forEach((x) => x.remove());
const container = document.querySelector('.js-sidebar-assignee-data');
container.prepend(
...users.map((user) => {
const input = document.createElement('input');
input.name = 'merge_request[assignee_ids][]';
input.value = user.id.toString();
input.setAttribute('data-avatar-url', user.avatar_url);
input.setAttribute('data-name', user.name);
input.setAttribute('data-username', user.username);
input.setAttribute('data-can-merge', user.can_merge);
return input;
}),
);
};
const toggleDropdown = () => findUserSearchButton().click();
const waitForDropdownItems = () =>
waitFor(() => expect(findDropdownItem(AUTOCOMPLETE_USERS[0])).not.toBeNull());
// assertion helpers ---------------------------------------------------------
const createUnassignedExpectation = () => {
return [
{ type: 'user', userId: '0' },
{ type: 'divider' },
...AUTOCOMPLETE_USERS.map((x) => ({ type: 'user', userId: x.id.toString() })),
];
};
const createAssignedExpectation = (...selectedUsers) => {
const selectedIds = new Set(selectedUsers.map((x) => x.id));
const unselectedUsers = AUTOCOMPLETE_USERS.filter((x) => !selectedIds.has(x.id));
return [
{ type: 'user', userId: '0' },
{ type: 'divider' },
{ type: 'dropdown-header', text: 'Assignee(s)' },
...selectedUsers.map((x) => ({ type: 'user', userId: x.id.toString() })),
{ type: 'divider' },
...unselectedUsers.map((x) => ({ type: 'user', userId: x.id.toString() })),
];
};
const context = createTestContext({
fixturePath: 'merge_requests/merge_request_with_single_assignee_feature.html',
});
beforeEach(() => {
const rootEl = document.createElement('div');
rootEl.innerHTML = USER_SEARCH_HTML;
document.body.appendChild(rootEl);
mock = new MockAdapter(axios);
mock.onGet('/-/autocomplete/users.json').reply(200, cloneDeep(AUTOCOMPLETE_USERS));
context.setup();
});
afterEach(() => {
document.body.innerHTML = '';
subject = null;
context.teardown();
});
describe('when opened', () => {
beforeEach(async () => {
createSubject();
context.createSubject();
toggleDropdown();
await waitForDropdownItems();
@ -150,8 +38,12 @@ describe('~/users_select/index', () => {
});
describe('when users are selected', () => {
const selectedUsers = [AUTOCOMPLETE_USERS[2], AUTOCOMPLETE_USERS[4]];
const expectation = createAssignedExpectation(...selectedUsers);
const selectedUsers = [getUsersFixtureAt(2), getUsersFixtureAt(4)];
const lastSelected = selectedUsers[selectedUsers.length - 1];
const expectation = createAssignedExpectation({
header: 'Assignee',
assigned: [lastSelected],
});
beforeEach(() => {
selectedUsers.forEach((user) => {
@ -163,42 +55,22 @@ describe('~/users_select/index', () => {
expect(findDropdownItemsModel()).toEqual(expectation);
});
it('shows assignee even after close and open', () => {
toggleDropdown();
toggleDropdown();
expect(findDropdownItemsModel()).toEqual(expectation);
});
it('updates field', () => {
expect(findAssigneesInputsModel()).toEqual(
selectedUsers.map((user) => ({
value: user.id.toString(),
dataset: {
approved: user.approved.toString(),
avatar_url: user.avatar_url,
can_merge: user.can_merge.toString(),
can_update_merge_request: user.can_update_merge_request.toString(),
id: user.id.toString(),
name: user.name,
show_status: user.show_status.toString(),
state: user.state,
username: user.username,
web_url: user.web_url,
},
})),
);
expect(findAssigneesInputsModel()).toEqual(createInputsModelExpectation([lastSelected]));
});
});
});
describe('with preselected user and opened', () => {
const expectation = createAssignedExpectation(AUTOCOMPLETE_USERS[0]);
const expectation = createAssignedExpectation({
header: 'Assignee',
assigned: [getUsersFixtureAt(0)],
});
beforeEach(async () => {
setAssignees(AUTOCOMPLETE_USERS[0]);
setAssignees(getUsersFixtureAt(0));
createSubject();
context.createSubject();
toggleDropdown();
await waitForDropdownItems();

View File

@ -0,0 +1,150 @@
import { waitFor } from '@testing-library/dom';
import MockAdapter from 'axios-mock-adapter';
import { memoize, cloneDeep } from 'lodash';
import { getFixture, getJSONFixture } from 'helpers/fixtures';
import axios from '~/lib/utils/axios_utils';
import UsersSelect from '~/users_select';
// fixtures -------------------------------------------------------------------
const getUserSearchHTML = memoize((fixturePath) => {
const html = getFixture(fixturePath);
const parser = new DOMParser();
const el = parser.parseFromString(html, 'text/html').querySelector('.assignee');
return el.outerHTML;
});
const getUsersFixture = memoize(() => getJSONFixture('autocomplete/users.json'));
export const getUsersFixtureAt = (idx) => getUsersFixture()[idx];
// test context ---------------------------------------------------------------
export const createTestContext = ({ fixturePath }) => {
let mock = null;
let subject = null;
const setup = () => {
const rootEl = document.createElement('div');
rootEl.innerHTML = getUserSearchHTML(fixturePath);
document.body.appendChild(rootEl);
mock = new MockAdapter(axios);
mock.onGet('/-/autocomplete/users.json').reply(200, cloneDeep(getUsersFixture()));
};
const teardown = () => {
mock.restore();
document.body.innerHTML = '';
subject = null;
};
const createSubject = () => {
if (subject) {
throw new Error('test subject is already created');
}
subject = new UsersSelect(null);
};
return {
setup,
teardown,
createSubject,
};
};
// finders -------------------------------------------------------------------
export const findAssigneesInputs = () =>
document.querySelectorAll('input[name="merge_request[assignee_ids][]');
export const findAssigneesInputsModel = () =>
Array.from(findAssigneesInputs()).map((input) => ({
value: input.value,
dataset: { ...input.dataset },
}));
export const findUserSearchButton = () => document.querySelector('.js-user-search');
export const findDropdownItem = ({ id }) => document.querySelector(`li[data-user-id="${id}"] a`);
export const findDropdownItemsModel = () =>
Array.from(document.querySelectorAll('.dropdown-content li')).map((el) => {
if (el.classList.contains('divider')) {
return {
type: 'divider',
};
} else if (el.classList.contains('dropdown-header')) {
return {
type: 'dropdown-header',
text: el.textContent,
};
}
return {
type: 'user',
userId: el.dataset.userId,
};
});
// arrange/act helpers -------------------------------------------------------
export const setAssignees = (...users) => {
findAssigneesInputs().forEach((x) => x.remove());
const container = document.querySelector('.js-sidebar-assignee-data');
container.prepend(
...users.map((user) => {
const input = document.createElement('input');
input.name = 'merge_request[assignee_ids][]';
input.value = user.id.toString();
input.setAttribute('data-avatar-url', user.avatar_url);
input.setAttribute('data-name', user.name);
input.setAttribute('data-username', user.username);
input.setAttribute('data-can-merge', user.can_merge);
return input;
}),
);
};
export const toggleDropdown = () => findUserSearchButton().click();
export const waitForDropdownItems = () =>
waitFor(() => expect(findDropdownItem(getUsersFixtureAt(0))).not.toBeNull());
// assertion helpers ---------------------------------------------------------
export const createUnassignedExpectation = () => {
return [
{ type: 'user', userId: '0' },
{ type: 'divider' },
...getUsersFixture().map((x) => ({
type: 'user',
userId: x.id.toString(),
})),
];
};
export const createAssignedExpectation = ({ header, assigned }) => {
const assignedIds = new Set(assigned.map((x) => x.id));
const unassignedIds = getUsersFixture().filter((x) => !assignedIds.has(x.id));
return [
{ type: 'user', userId: '0' },
{ type: 'divider' },
{ type: 'dropdown-header', text: header },
...assigned.map((x) => ({ type: 'user', userId: x.id.toString() })),
{ type: 'divider' },
...unassignedIds.map((x) => ({ type: 'user', userId: x.id.toString() })),
];
};
export const createInputsModelExpectation = (users) =>
users.map((user) => ({
value: user.id.toString(),
dataset: {
approved: user.approved.toString(),
avatar_url: user.avatar_url,
can_merge: user.can_merge.toString(),
can_update_merge_request: user.can_update_merge_request.toString(),
id: user.id.toString(),
name: user.name,
show_status: user.show_status.toString(),
state: user.state,
username: user.username,
web_url: user.web_url,
},
}));

View File

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Git::LfsChanges do
let_it_be(:project) { create(:project, :repository) }
let(:newrev) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' }
let(:blob_object_id) { '0c304a93cb8430108629bbbcaa27db3343299bc0' }

View File

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Git::MergeBase do
let_it_be(:project) { create(:project, :repository) }
let(:repository) { project.repository }
subject(:merge_base) { described_class.new(repository, refs) }

View File

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Git::Push do
let_it_be(:project) { create(:project, :repository) }
let(:oldrev) { project.commit('HEAD~2').id }
let(:newrev) { project.commit.id }
let(:ref) { 'refs/heads/some-branch' }

View File

@ -6,6 +6,7 @@ RSpec.describe Gitlab::GitAccessDesign do
let_it_be(:project) { create(:project) }
let_it_be(:user) { project.owner }
let(:protocol) { 'web' }
let(:actor) { user }

View File

@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::GitAccessProject do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
let(:container) { project }
let(:actor) { user }
let(:project_path) { project.path }

View File

@ -6,6 +6,7 @@ RSpec.describe Gitlab::GitAccessWiki do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :wiki_repo) }
let_it_be(:wiki) { create(:project_wiki, project: project) }
let(:changes) { ['6f6d7e7ed 570e7b2ab refs/heads/master'] }
let(:authentication_abilities) { %i[read_project download_code push_code] }
let(:redirected_path) { nil }

View File

@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::ObjectHierarchy do
let_it_be(:parent) { create(:group) }
let_it_be(:parent, reload: true) { create(:group) }
let_it_be(:child1) { create(:group, parent: parent) }
let_it_be(:child2) { create(:group, parent: child1) }

View File

@ -880,7 +880,7 @@ RSpec.describe Namespace do
end
describe '#use_traversal_ids?' do
let_it_be(:namespace) { build(:namespace) }
let_it_be(:namespace, reload: true) { create(:namespace) }
subject { namespace.use_traversal_ids? }
@ -902,6 +902,8 @@ RSpec.describe Namespace do
end
context 'when use_traversal_ids feature flag is true' do
let_it_be(:namespace, reload: true) { create(:namespace) }
it_behaves_like 'namespace traversal'
describe '#self_and_descendants' do

View File

@ -4973,14 +4973,6 @@ expect@^26.5.2:
jest-message-util "^26.5.2"
jest-regex-util "^26.0.0"
exports-loader@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/exports-loader/-/exports-loader-0.7.0.tgz#84881c784dea6036b8e1cd1dac3da9b6409e21a5"
integrity sha512-RKwCrO4A6IiKm0pG3c9V46JxIHcDplwwGJn6+JJ1RcVnh/WSGJa0xkmk5cRVtgOPzCAtTMGj2F7nluh9L0vpSA==
dependencies:
loader-utils "^1.1.0"
source-map "0.5.0"
express@^4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
@ -10853,11 +10845,6 @@ source-map-url@^0.4.0:
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=
source-map@0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.0.tgz#0fe96503ac86a5adb5de63f4e412ae4872cdbe86"
integrity sha1-D+llA6yGpa213mP05BKuSHLNvoY=
source-map@0.5.6, source-map@^0.5.0, source-map@^0.5.6:
version "0.5.6"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"