diff --git a/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js b/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js index 698302c5209408011f46d608ff2c7eb1c3398ec4..bda2b05fb8fa8fbbe1f10ee7a00b86ecd749858b 100644 --- a/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js +++ b/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js @@ -1,8 +1,10 @@ import { __ } from '~/locale'; import { TOKEN_TITLE_APPROVED_BY, + TOKEN_TITLE_MERGE_USER, TOKEN_TITLE_REVIEWER, TOKEN_TYPE_APPROVED_BY, + TOKEN_TYPE_MERGE_USER, TOKEN_TYPE_REVIEWER, TOKEN_TYPE_TARGET_BRANCH, TOKEN_TYPE_SOURCE_BRANCH, @@ -174,6 +176,21 @@ export default ( ); IssuableTokenKeys.conditions.push(...approvedBy.condition); + const mergeUserToken = { + formattedKey: TOKEN_TITLE_MERGE_USER, + key: TOKEN_TYPE_MERGE_USER, + type: 'string', + param: 'username', + symbol: '@', + icon: 'merge', + tag: '@merge_user', + hideNotEqual: true, + }; + if (gon.features.mrMergeUserFilter) { + IssuableTokenKeys.tokenKeys.splice(3, 0, mergeUserToken); + IssuableTokenKeys.tokenKeysWithAlternative.splice(3, 0, mergeUserToken); + } + if (!disableEnvironmentFilter) { const environmentToken = { formattedKey: __('Environment'), diff --git a/app/assets/javascripts/filtered_search/available_dropdown_mappings.js b/app/assets/javascripts/filtered_search/available_dropdown_mappings.js index a1782c549d62fb15313d64f0d886c0bf77119543..e1a5b269fa70ec1c727e0b4f1e325300bc214a7d 100644 --- a/app/assets/javascripts/filtered_search/available_dropdown_mappings.js +++ b/app/assets/javascripts/filtered_search/available_dropdown_mappings.js @@ -2,6 +2,7 @@ import { sortMilestonesByDueDate } from '~/milestones/utils'; import { mergeUrlParams } from '~/lib/utils/url_utility'; import { TOKEN_TYPE_APPROVED_BY, + TOKEN_TYPE_MERGE_USER, TOKEN_TYPE_ASSIGNEE, TOKEN_TYPE_AUTHOR, TOKEN_TYPE_CONFIDENTIAL, @@ -86,6 +87,11 @@ export default class AvailableDropdownMappings { gl: DropdownUser, element: this.container.querySelector('#js-dropdown-reviewer'), }, + [TOKEN_TYPE_MERGE_USER]: { + reference: null, + gl: DropdownUser, + element: this.container.querySelector('#js-dropdown-merge-user'), + }, attention: { reference: null, gl: DropdownUser, diff --git a/app/assets/javascripts/filtered_search/constants.js b/app/assets/javascripts/filtered_search/constants.js index b328ae6a8721ca70fb57d085d1ba6a205100f28f..39fa936895c4193d51b22c0e2146a296e3205ab6 100644 --- a/app/assets/javascripts/filtered_search/constants.js +++ b/app/assets/javascripts/filtered_search/constants.js @@ -1,5 +1,6 @@ import { TOKEN_TYPE_APPROVED_BY, + TOKEN_TYPE_MERGE_USER, TOKEN_TYPE_ASSIGNEE, TOKEN_TYPE_AUTHOR, TOKEN_TYPE_REVIEWER, @@ -9,6 +10,7 @@ export const USER_TOKEN_TYPES = [ TOKEN_TYPE_AUTHOR, TOKEN_TYPE_ASSIGNEE, TOKEN_TYPE_APPROVED_BY, + TOKEN_TYPE_MERGE_USER, TOKEN_TYPE_REVIEWER, 'attention', ]; diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js b/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js index 952af4eeb5d92359a36dcdb6b948cdba74b4494d..8fc4de1798febe893dcfd991d8ae09d80bfba9c7 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js @@ -62,6 +62,7 @@ export const TOKEN_EMPTY_SEARCH_TERM = { }; export const TOKEN_TITLE_APPROVED_BY = __('Approved-By'); +export const TOKEN_TITLE_MERGE_USER = __('Merged-By'); export const TOKEN_TITLE_ASSIGNEE = s__('SearchToken|Assignee'); export const TOKEN_TITLE_AUTHOR = __('Author'); export const TOKEN_TITLE_CONFIDENTIAL = __('Confidential'); @@ -84,6 +85,7 @@ export const TOKEN_TITLE_CREATED = __('Created date'); export const TOKEN_TITLE_CLOSED = __('Closed date'); export const TOKEN_TYPE_APPROVED_BY = 'approved-by'; +export const TOKEN_TYPE_MERGE_USER = 'merge-user'; export const TOKEN_TYPE_ASSIGNEE = 'assignee'; export const TOKEN_TYPE_AUTHOR = 'author'; export const TOKEN_TYPE_CONFIDENTIAL = 'confidential'; diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index a0997484c5839532521cfbf5c4c729fbfd41f7c4..3cdfba61dadf888445cb632c6e1bed5e3a4078cb 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -19,6 +19,7 @@ class DashboardController < Dashboard::ApplicationController before_action only: :merge_requests do push_frontend_feature_flag(:mr_approved_filter, type: :ops) + push_frontend_feature_flag(:mr_merge_user_filter, type: :development) end respond_to :html diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 5b9b3b7de119495908566c04327a272c9c74d249..b343c8d3beffc022c80125a09032101ccaed6787 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -45,6 +45,7 @@ class GroupsController < Groups::ApplicationController before_action only: :merge_requests do push_frontend_feature_flag(:mr_approved_filter, type: :ops) + push_frontend_feature_flag(:mr_merge_user_filter, type: :development) end helper_method :captcha_required? diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 0899e30330573c9b5f30cc04a4260d7ed76d2d86..69bb32d75a7cd48a4cadc0d6f70d9c8b21c5fa9c 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -34,6 +34,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo before_action only: :index do push_frontend_feature_flag(:mr_approved_filter, type: :ops) + push_frontend_feature_flag(:mr_merge_user_filter, type: :development) end before_action only: [:show, :diffs] do diff --git a/app/finders/concerns/merge_user_filter.rb b/app/finders/concerns/merge_user_filter.rb new file mode 100644 index 0000000000000000000000000000000000000000..bd35f12b5c7454753b9947bafb77dc115d85764a --- /dev/null +++ b/app/finders/concerns/merge_user_filter.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module MergeUserFilter + private + + def by_merge_user(items) + return items unless params.merge_user_id? || params.merge_user_username? + return items if Feature.disabled?(:mr_merge_user_filter, type: :development) + + mr_metrics_scope = MergeRequest::Metrics + mr_metrics_scope = mr_metrics_scope.merged_by(params.merge_user) + + if params.merge_user + items.join_metrics.merge(mr_metrics_scope) + else # merge_user user not found + items.none + end + end +end diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb index b7de1c08f8628486ad7852a181ac8065900a9921..c19d8b68f605a59e909ba788b88f1deac67385d8 100644 --- a/app/finders/merge_requests_finder.rb +++ b/app/finders/merge_requests_finder.rb @@ -33,6 +33,7 @@ class MergeRequestsFinder < IssuableFinder extend ::Gitlab::Utils::Override include MergedAtFilter + include MergeUserFilter def self.scalar_params @scalar_params ||= super + [ @@ -42,6 +43,8 @@ def self.scalar_params :deployed_before, :draft, :environment, + :merge_user_id, + :merge_user_username, :merged_after, :merged_before, :reviewer_id, @@ -69,6 +72,7 @@ def filter_items(_items) items = by_source_branch(items) items = by_draft(items) items = by_target_branch(items) + items = by_merge_user(items) items = by_merged_at(items) items = by_approvals(items) items = by_deployments(items) diff --git a/app/finders/merge_requests_finder/params.rb b/app/finders/merge_requests_finder/params.rb index 2c218898dcf60aec4899569763d66f613458e034..22ebe73abaccd45e4761f37b9a913c47defc1da7 100644 --- a/app/finders/merge_requests_finder/params.rb +++ b/app/finders/merge_requests_finder/params.rb @@ -19,5 +19,15 @@ def reviewer end end end + + def merge_user + strong_memoize(:merge_user) do + if merge_user_id? + User.find_by_id(params[:merge_user_id]) + elsif merge_user_username? + User.find_by_username(params[:merge_user_username]) + end + end + end end end diff --git a/app/models/merge_request/metrics.rb b/app/models/merge_request/metrics.rb index 3c592c0008f38a7596f5e183bb0d6bdb5b9465ee..3581b6ddb4181801914fae5efe230ef130cfa5af 100644 --- a/app/models/merge_request/metrics.rb +++ b/app/models/merge_request/metrics.rb @@ -13,6 +13,7 @@ class MergeRequest::Metrics < ApplicationRecord scope :merged_after, ->(date) { where(arel_table[:merged_at].gteq(date)) } scope :merged_before, ->(date) { where(arel_table[:merged_at].lteq(date.is_a?(Time) ? date.end_of_day : date)) } + scope :merged_by, ->(user) { where(merged_by_id: user) } scope :with_valid_time_to_merge, -> { where(arel_table[:merged_at].gt(arel_table[:created_at])) } scope :by_target_project, ->(project) { where(target_project_id: project) } diff --git a/app/views/shared/issuable/_merge_user_dropdown.html.haml b/app/views/shared/issuable/_merge_user_dropdown.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..8577eb821e62f7b48b81570d1aa0dd88920fdbb1 --- /dev/null +++ b/app/views/shared/issuable/_merge_user_dropdown.html.haml @@ -0,0 +1,9 @@ +#js-dropdown-merge-user.filtered-search-input-dropdown-menu.dropdown-menu + %ul{ data: { dropdown: true } } + - if current_user + = render 'shared/issuable/user_dropdown_item', + user: current_user + %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } + = render 'shared/issuable/user_dropdown_item', + user: User.new(username: '{{username}}', name: '{{name}}'), + avatar: { lazy: true, url: '{{avatar_url}}' } diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 8db7f7345f4da6aa4a35796a43ad729499ad7789..33975ebbb42fff63896d5fdc9c161cd02da0bad6 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -93,6 +93,8 @@ avatar: { lazy: true, url: '{{avatar_url}}' } = render_if_exists 'shared/issuable/approver_dropdown' = render_if_exists 'shared/issuable/approved_by_dropdown' + - if ::Feature.enabled?(:mr_merge_user_filter, type: :development) + = render_if_exists 'shared/issuable/merge_user_dropdown' #js-dropdown-milestone.filtered-search-input-dropdown-menu.dropdown-menu %ul{ data: { dropdown: true } } %li.filter-dropdown-item{ data: { value: 'None' } } diff --git a/config/feature_flags/development/mr_merge_user_filter.yml b/config/feature_flags/development/mr_merge_user_filter.yml new file mode 100644 index 0000000000000000000000000000000000000000..147de6b0eaa1a72b594fb5a59b73b9386c58b193 --- /dev/null +++ b/config/feature_flags/development/mr_merge_user_filter.yml @@ -0,0 +1,8 @@ +--- +name: mr_merge_user_filter +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140002 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/437871 +milestone: '16.9' +type: development +group: group::code review +default_enabled: false diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index a852c7c0b961c54f17f702126dc8724135d92d6e..62e106885d2f8e3cdb857325d6449566a78caf50 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -64,6 +64,8 @@ Supported attributes: | `environment` | string | No | Returns merge requests deployed to the given environment. | | `in` | string | No | Modify the scope of the `search` attribute. `title`, `description`, or a string joining them with comma. Default is `title,description`. | | `labels` | string | No | Returns merge requests matching a comma-separated list of labels. `None` lists all merge requests with no labels. `Any` lists all merge requests with at least one label. Predefined names are case-insensitive. | +| `merge_user_id` | integer | No | Returns merge requests which have been merged by the user with the given user `id`. Mutually exclusive with `merge_user_username`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140002) in GitLab 16.9. Available only when the feature flag `mr_merge_user_filter` is enabled. | +| `merge_user_username` | string | No | Returns merge requests which have been merged by the user with the given `username`. Mutually exclusive with `merge_user_id`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140002) in GitLab 16.9. Available only when the feature flag `mr_merge_user_filter` is enabled. | | `milestone` | string | No | Returns merge requests for a specific milestone. `None` returns merge requests with no milestone. `Any` returns merge requests that have an assigned milestone. | | `my_reaction_emoji` | string | No | Returns merge requests reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. | | `not` | Hash | No | Returns merge requests that do not match the parameters supplied. Accepts: `labels`, `milestone`, `author_id`, `author_username`, `assignee_id`, `assignee_username`, `reviewer_id`, `reviewer_username`, `my_reaction_emoji`. | @@ -244,6 +246,8 @@ Supported attributes: | `environment` | string | No | Returns merge requests deployed to the given environment. | | `iids[]` | integer array | No | Returns the request having the given `iid`. | | `labels` | string | No | Returns merge requests matching a comma-separated list of labels. `None` lists all merge requests with no labels. `Any` lists all merge requests with at least one label. Predefined names are case-insensitive. | +| `merge_user_id` | integer | No | Returns merge requests which have been merged by the user with the given user `id`. Mutually exclusive with `merge_user_username`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140002) in GitLab 16.9. Available only when the feature flag `mr_merge_user_filter` is enabled. | +| `merge_user_username` | string | No | Returns merge requests which have been merged by the user with the given `username`. Mutually exclusive with `merge_user_id`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140002) in GitLab 16.9. Available only when the feature flag `mr_merge_user_filter` is enabled. | | `milestone` | string | No | Returns merge requests for a specific milestone. `None` returns merge requests with no milestone. `Any` returns merge requests that have an assigned milestone. | | `my_reaction_emoji` | string | No | Returns merge requests reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. | | `not` | Hash | No | Returns merge requests that do not match the parameters supplied. Accepts: `labels`, `milestone`, `author_id`, `author_username`, `assignee_id`, `assignee_username`, `reviewer_id`, `reviewer_username`, `my_reaction_emoji`. | @@ -428,6 +432,8 @@ Supported attributes: | `created_after` | datetime | No | Returns merge requests created on or after the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). | | `created_before` | datetime | No | Returns merge requests created on or before the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). | | `labels` | string | No | Returns merge requests matching a comma-separated list of labels. `None` lists all merge requests with no labels. `Any` lists all merge requests with at least one label. Predefined names are case-insensitive. | +| `merge_user_id` | integer | No | Returns merge requests which have been merged by the user with the given user `id`. Mutually exclusive with `merge_user_username`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140002) in GitLab 16.9. Available only when the feature flag `mr_merge_user_filter` is enabled. | +| `merge_user_username` | string | No | Returns merge requests which have been merged by the user with the given `username`. Mutually exclusive with `merge_user_id`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140002) in GitLab 16.9. Available only when the feature flag `mr_merge_user_filter` is enabled. | | `milestone` | string | No | Returns merge requests for a specific milestone. `None` returns merge requests with no milestone. `Any` returns merge requests that have an assigned milestone. | | `my_reaction_emoji` | string | No | Returns merge requests reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. | | `non_archived` | boolean | No | Returns merge requests from non archived projects only. Default is `true`. | diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md index 3555d9ffa01bbc1248e75db474ad659f60111d25..dece5600b0d2f94b25e848e618d973235f5fc1fb 100644 --- a/doc/user/project/merge_requests/index.md +++ b/doc/user/project/merge_requests/index.md @@ -82,6 +82,7 @@ or: > - Filtering by potential approvers was moved to GitLab Premium in 13.9. > - Filtering by `approved-by` moved to GitLab Premium in 13.9. > - Filtering by `source-branch` [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134555) in GitLab 16.6. +> - Filtering by `merged-by` [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140002) in GitLab 16.9. Available only when the feature flag `mr_merge_user_filter` is enabled. To filter the list of merge requests: @@ -95,6 +96,7 @@ To filter the list of merge requests: - **Approved-By**, for merge requests already approved by a user. **(PREMIUM ALL)**. - **Approver**, for merge requests that this user is eligible to approve. (For more information, read about [Code owners](../codeowners/index.md)). **(PREMIUM ALL)** + - **Merged-By**, for merge requests merged by this user. - **Reviewer**, for merge requests reviewed by this user. 1. Select or type the operator to use for filtering the attribute. The following operators are available: diff --git a/ee/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js b/ee/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js index 615ae6e448b4e14d08d99cb610bc4cd2290e9dd5..b272737b824763814f3cde0d68a0a586e9fe03ba 100644 --- a/ee/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js +++ b/ee/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js @@ -49,7 +49,7 @@ const approvers = { export default (IssuableTokenKeys, disableBranchFilter = false) => { addExtraTokensForMergeRequests(IssuableTokenKeys, disableBranchFilter); - const tokenPosition = 3; + const tokenPosition = gon.features.mrMergeUserFilter ? 4 : 3; IssuableTokenKeys.tokenKeys.splice(tokenPosition, 0, ...[approvers.token]); IssuableTokenKeys.tokenKeysWithAlternative.splice( diff --git a/lib/api/helpers/merge_requests_helpers.rb b/lib/api/helpers/merge_requests_helpers.rb index 0a0d70520efa6efb6f93f60a05b9bfed096aa63c..fbb52e224343736678fe46ee2ed958c25453fa30 100644 --- a/lib/api/helpers/merge_requests_helpers.rb +++ b/lib/api/helpers/merge_requests_helpers.rb @@ -108,6 +108,11 @@ module MergeRequestsHelpers optional :approved, type: String, values: %w[yes no], desc: 'Filters merge requests by their `approved` status. `yes` returns only approved merge requests. `no` returns only non-approved merge requests.' + optional :merge_user_id, type: Integer, + desc: "Returns merge requests which have been merged by the user with the given user `id`. Mutually exclusive with `merge_user_username`." + optional :merge_user_username, type: String, + desc: "Returns merge requests which have been merged by the user with the given `username`. Mutually exclusive with `merge_user_id`." + mutually_exclusive :merge_user_id, :merge_user_username end params :optional_scope_param do diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 2d958691959f54135ad080e232edc22a0c3905f3..c486a32cd5da8c05b5ef7c589c84d98cfa076ca3 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -30402,6 +30402,9 @@ msgstr "" msgid "Merged this merge request." msgstr "" +msgid "Merged-By" +msgstr "" + msgid "Merged: %{merged}" msgstr "" diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index 3f9c1baec8271114bcb21c92c1e4919614c6be42..d85ae92f95413eb5368209773af6f97d83bf5a19 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -170,6 +170,73 @@ end end + context 'merge_user filtering' do + before do + merge_request1.update!(state_id: MergeRequest.available_states[:merged]) + merge_request1.metrics.update!(merged_by_id: user.id) + merge_request2.update!(state_id: MergeRequest.available_states[:merged]) + merge_request2.metrics.update!(merged_by_id: user.id) + merge_request3.update!(state_id: MergeRequest.available_states[:merged]) + merge_request3.metrics.update!(merged_by_id: user2.id) + merge_request4.update!(state_id: MergeRequest.available_states[:merged]) + merge_request4.metrics.update!(merged_by_id: user2.id) + end + + subject { described_class.new(user, params).execute } + + context 'when flag `mr_merge_user_filter` is disabled' do + before do + stub_feature_flags(mr_merge_user_filter: false) + end + + describe 'by merge_user_id' do + let(:params) { { merge_user_id: user.id } } + let(:expected_mr) { [merge_request1, merge_request2, merge_request3, merge_request4, merge_request5] } + + it { is_expected.to contain_exactly(*expected_mr) } + end + + describe 'by merge_user_username' do + let(:params) { { merge_user_username: user.username } } + let(:expected_mr) { [merge_request1, merge_request2, merge_request3, merge_request4, merge_request5] } + + it { is_expected.to contain_exactly(*expected_mr) } + end + end + + context 'when flag `mr_merge_user_filter` is enabled' do + before do + stub_feature_flags(mr_merge_user_filter: true) + end + + describe 'by merge_user_id' do + let(:params) { { merge_user_id: user.id } } + let(:expected_mr) { [merge_request1, merge_request2] } + + it { is_expected.to contain_exactly(*expected_mr) } + end + + describe 'by merge_user_username' do + let(:params) { { merge_user_username: user.username } } + let(:expected_mr) { [merge_request1, merge_request2] } + + it { is_expected.to contain_exactly(*expected_mr) } + end + + describe 'by merge_user_id with unknown user id' do + let(:params) { { merge_user_id: 99999 } } + + it { is_expected.to be_empty } + end + + describe 'by merge_user_username with unknown user name' do + let(:params) { { merge_user_username: 'does-not-exist' } } + + it { is_expected.to be_empty } + end + end + end + context 'filtering by group' do it 'includes all merge requests when user has access excluding merge requests from projects the user does not have access to' do private_project = allow_gitaly_n_plus_1 { create(:project, :private, group: group) } diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 6000fa29dc4dca9d7fa9a03721e89f929d5682b2..19dbfeb03bd13bde2803e13d3b11100ddbfb6a7a 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -27,7 +27,7 @@ shared_context 'with merge requests' do let_it_be(:milestone1) { create(:milestone, title: '0.9', project: project) } - let_it_be(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignees: [user], source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds, updated_at: base_time + 1.hour, merge_commit_sha: '9999999999999999999999999999999999999999') } + let_it_be(:merge_request_merged) { create(:merge_request, :with_merged_metrics, state: "merged", author: user, assignees: [user], source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds, updated_at: base_time + 1.hour, merge_commit_sha: '9999999999999999999999999999999999999999', merged_by: user) } let_it_be(:merge_request) { create(:merge_request, :simple, milestone: milestone1, author: user, assignees: [user], source_project: project, target_project: project, source_branch: 'markdown', title: "Test", created_at: base_time, updated_at: base_time + 3.hours) } let_it_be(:merge_request_closed) { create(:merge_request, state: "closed", milestone: milestone1, author: user, assignees: [user], source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.second, updated_at: base_time) } let_it_be(:merge_request_locked) { create(:merge_request, state: "locked", milestone: milestone1, author: user, assignees: [user], source_project: project, target_project: project, title: "Locked test", created_at: base_time + 1.second, updated_at: base_time + 2.hours) } @@ -931,6 +931,85 @@ end end + context 'filter by merge_user' do + let(:params) { { scope: :all } } + + context 'when flag `mr_merge_user_filter` is disabled' do + before do + stub_feature_flags(mr_merge_user_filter: false) + end + + context 'with merge_user_id' do + let(:params) { super().merge(merge_user_id: user.id) } + + it 'returns merged merge requests for the given user' do + get api('/merge_requests', user), params: params + + expect_response_contain_exactly( + merge_request.id, + merge_request_closed.id, + merge_request_merged.id, + merge_request_locked.id, + merge_request2.id + ) + end + end + + context 'with merge_user_username' do + let(:params) { super().merge(merge_user_username: user.username) } + + it 'returns merged merge requests for the given user' do + get api('/merge_requests', user), params: params + + expect_response_contain_exactly( + merge_request.id, + merge_request_closed.id, + merge_request_merged.id, + merge_request_locked.id, + merge_request2.id + ) + end + end + end + + context 'when flag `mr_merge_user_filter` is enabled' do + before do + stub_feature_flags(mr_merge_user_filter: true) + end + + context 'with merge_user_id' do + let(:params) { super().merge(merge_user_id: user.id) } + + it 'returns merged merge requests for the given user' do + get api('/merge_requests', user), params: params + + expect_response_contain_exactly(merge_request_merged.id) + end + end + + context 'with merge_user_username' do + let(:params) { super().merge(merge_user_username: user.username) } + + it 'returns merged merge requests for the given user' do + get api('/merge_requests', user), params: params + + expect_response_contain_exactly(merge_request_merged.id) + end + end + + context 'with both merge_user_id and merge_user_username' do + let(:params) { super().merge(merge_user_id: user.id, merge_user_username: user.username) } + + it 'returns a 400' do + get api('/merge_requests', user), params: params + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['error']).to eq('merge_user_id, merge_user_username are mutually exclusive') + end + end + end + end + it 'returns an array of merge requests assigned to the given user' do merge_request3 = create(:merge_request, :simple, author: user, assignees: [user2], source_project: project2, target_project: project2, source_branch: 'other-branch')