Add latest changes from gitlab-org/gitlab@master
|
@ -118,7 +118,6 @@ linters:
|
|||
- Layout/TrailingEmptyLines
|
||||
- Lint/LiteralInInterpolation
|
||||
- Lint/ParenthesesAsGroupedExpression
|
||||
- Lint/RedundantWithIndex
|
||||
- Lint/SafeNavigationConsistency
|
||||
- Metrics/BlockNesting
|
||||
- Naming/VariableName
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix EmptyLineAfterFinalLetItBe offenses in spec/lib/gitlab/git
|
||||
merge_request: 58254
|
||||
author: Huzaifa Iftikhar @huzaifaiftikhar
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix EmptyLineAfterFinalLetItBe Rubocop offenses for projects module
|
||||
merge_request: 58187
|
||||
author: Huzaifa Iftikhar @huzaifaiftikhar
|
||||
type: fixed
|
|
@ -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
|
||||
|
|
|
@ -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. |
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
Before Width: | Height: | Size: 121 KiB |
After Width: | Height: | Size: 65 KiB |
|
@ -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
|
||||
|
||||
|
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 103 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 96 KiB |
After Width: | Height: | Size: 48 KiB |
|
@ -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
|
||||
|
||||
|
|
Before Width: | Height: | Size: 130 KiB |
After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 68 KiB |
After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 8.7 KiB |
|
@ -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)
|
||||
|
|
|
@ -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)**
|
||||
|
||||
|
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 100 KiB |
After Width: | Height: | Size: 15 KiB |
|
@ -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
|
||||
|
||||
|
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 16 KiB |
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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') }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}));
|
|
@ -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' }
|
||||
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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' }
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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) }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
13
yarn.lock
|
@ -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"
|
||||
|
|