Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-05-05 21:09:10 +00:00
parent 0b789f95a3
commit a297076878
64 changed files with 578 additions and 148 deletions

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 386 KiB

After

Width:  |  Height:  |  Size: 387 KiB

View File

@ -44,6 +44,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:issue_assignees_widget, @project, default_enabled: :yaml)
push_frontend_feature_flag(:realtime_labels, project, default_enabled: :yaml)
push_frontend_feature_flag(:updated_diff_expansion_buttons, project, default_enabled: :yaml)
push_frontend_feature_flag(:mr_attention_requests, current_user, default_enabled: :yaml)
end
before_action do

View File

@ -13,6 +13,8 @@ module Mutations
DESC
def resolve(project_path:, iid:, user:)
raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'Feature disabled' unless feature_enabled?
merge_request = authorized_find!(project_path: project_path, iid: iid)
result = ::MergeRequests::RemoveAttentionRequestedService.new(
@ -27,6 +29,12 @@ module Mutations
errors: Array(result[:message])
}
end
private
def feature_enabled?
current_user&.mr_attention_requests_enabled?
end
end
end
end

View File

@ -13,6 +13,8 @@ module Mutations
DESC
def resolve(project_path:, iid:, user:)
raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'Feature disabled' unless feature_enabled?
merge_request = authorized_find!(project_path: project_path, iid: iid)
result = ::MergeRequests::RequestAttentionService.new(
@ -27,6 +29,12 @@ module Mutations
errors: Array(result[:message])
}
end
private
def feature_enabled?
current_user&.mr_attention_requests_enabled?
end
end
end
end

View File

@ -13,6 +13,8 @@ module Mutations
DESC
def resolve(project_path:, iid:, user:)
raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'Feature disabled' unless current_user&.mr_attention_requests_enabled?
merge_request = authorized_find!(project_path: project_path, iid: iid)
result = ::MergeRequests::ToggleAttentionRequestedService.new(project: merge_request.project, current_user: current_user, merge_request: merge_request, user: user).execute

View File

@ -69,9 +69,9 @@ module Types
mount_mutation Mutations::MergeRequests::SetDraft, calls_gitaly: true
mount_mutation Mutations::MergeRequests::SetAssignees
mount_mutation Mutations::MergeRequests::ReviewerRereview
mount_mutation Mutations::MergeRequests::RequestAttention, feature_flag: :mr_attention_requests
mount_mutation Mutations::MergeRequests::RemoveAttentionRequest, feature_flag: :mr_attention_requests
mount_mutation Mutations::MergeRequests::ToggleAttentionRequested, feature_flag: :mr_attention_requests
mount_mutation Mutations::MergeRequests::RequestAttention
mount_mutation Mutations::MergeRequests::RemoveAttentionRequest
mount_mutation Mutations::MergeRequests::ToggleAttentionRequested
mount_mutation Mutations::Metrics::Dashboard::Annotations::Create
mount_mutation Mutations::Metrics::Dashboard::Annotations::Delete
mount_mutation Mutations::Notes::Create::Note, calls_gitaly: true

View File

@ -156,7 +156,7 @@ module MergeRequestsHelper
total: total_count
}
if Feature.enabled?(:mr_attention_requests, default_enabled: :yaml)
if current_user&.mr_attention_requests_enabled?
attention_requested_count = attention_requested_merge_requests_count
counts[:attention_requested_count] = attention_requested_count

View File

@ -119,6 +119,10 @@ module AlertManagement
end
end
def self.find_ongoing_alert(project, fingerprint)
for_fingerprint(project, fingerprint).not_resolved.take
end
def self.last_prometheus_alert_by_project_id
ids = select(arel_table[:id].maximum).group(:project_id)
with_prometheus_alert.where(id: ids)

View File

@ -16,8 +16,6 @@ module MergeRequestReviewerState
belongs_to :updated_state_by, class_name: 'User', foreign_key: :updated_state_by_user_id
after_initialize :set_state, unless: :persisted?
def attention_requested_by
return unless attention_requested?

View File

@ -1977,10 +1977,6 @@ class MergeRequest < ApplicationRecord
end
end
def attention_requested_enabled?
Feature.enabled?(:mr_attention_requests, project, default_enabled: :yaml)
end
private
attr_accessor :skip_fetch_ref

View File

@ -10,12 +10,6 @@ class MergeRequestAssignee < ApplicationRecord
scope :in_projects, ->(project_ids) { joins(:merge_request).where(merge_requests: { target_project_id: project_ids }) }
def set_state
if Feature.enabled?(:mr_attention_requests, self.merge_request&.project, default_enabled: :yaml)
self.state = MergeRequestReviewer.find_by(user_id: self.user_id, merge_request_id: self.merge_request_id)&.state || :attention_requested
end
end
def cache_key
[model_name.cache_key, id, state, assignee.cache_key]
end

View File

@ -6,12 +6,6 @@ class MergeRequestReviewer < ApplicationRecord
belongs_to :merge_request
belongs_to :reviewer, class_name: 'User', foreign_key: :user_id, inverse_of: :merge_request_reviewers
def set_state
if Feature.enabled?(:mr_attention_requests, self.merge_request&.project, default_enabled: :yaml)
self.state = MergeRequestAssignee.find_by(user_id: self.user_id, merge_request_id: self.merge_request_id)&.state || :attention_requested
end
end
def cache_key
[model_name.cache_key, id, state, reviewer.cache_key]
end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
class ProjectSetting < ApplicationRecord
ALLOWED_TARGET_PLATFORMS = %w(ios osx tvos watchos).freeze
ALLOWED_TARGET_PLATFORMS = %w(ios osx tvos watchos android).freeze
belongs_to :project, inverse_of: :project_setting

View File

@ -2070,6 +2070,10 @@ class User < ApplicationRecord
end
end
def mr_attention_requests_enabled?
Feature.enabled?(:mr_attention_requests, self, default_enabled: :yaml)
end
protected
# override, from Devise::Validatable

View File

@ -20,7 +20,7 @@ class MergeRequestUserEntity < ::API::Entities::UserBasic
find_reviewer_or_assignee(user, options)&.reviewed?
end
expose :attention_requested, if: satisfies(:present?, :allows_reviewers?, :attention_requested_enabled?) do |user, options|
expose :attention_requested, if: ->(_, options) { options[:merge_request].present? && options[:merge_request].allows_reviewers? && request.current_user&.mr_attention_requests_enabled? } do |user, options|
find_reviewer_or_assignee(user, options)&.attention_requested?
end

View File

@ -104,7 +104,7 @@ module AlertManagement
def find_existing_alert
return unless incoming_payload.gitlab_fingerprint
AlertManagement::Alert.not_resolved.for_fingerprint(project, incoming_payload.gitlab_fingerprint).first
AlertManagement::Alert.find_ongoing_alert(project, incoming_payload.gitlab_fingerprint)
end
def build_new_alert

View File

@ -43,6 +43,8 @@ module MergeRequests
end
def handle_assignees_change(merge_request, old_assignees)
bulk_update_assignees_state(merge_request, merge_request.assignees - old_assignees)
MergeRequests::HandleAssigneesChangeService
.new(project: project, current_user: current_user)
.async_execute(merge_request, old_assignees)
@ -58,11 +60,10 @@ module MergeRequests
new_reviewers = merge_request.reviewers - old_reviewers
merge_request_activity_counter.track_users_review_requested(users: new_reviewers)
merge_request_activity_counter.track_reviewers_changed_action(user: current_user)
bulk_update_reviewers_state(merge_request, new_reviewers)
unless new_reviewers.include?(current_user)
remove_attention_requested(merge_request)
merge_request.merge_request_reviewers_with(new_reviewers).update_all(updated_state_by_user_id: current_user.id)
end
end
@ -246,7 +247,7 @@ module MergeRequests
end
def remove_all_attention_requests(merge_request)
return unless merge_request.attention_requested_enabled?
return unless current_user.mr_attention_requests_enabled?
users = merge_request.reviewers + merge_request.assignees
@ -254,10 +255,42 @@ module MergeRequests
end
def remove_attention_requested(merge_request)
return unless merge_request.attention_requested_enabled?
return unless current_user.mr_attention_requests_enabled?
::MergeRequests::RemoveAttentionRequestedService.new(project: merge_request.project, current_user: current_user, merge_request: merge_request, user: current_user).execute
end
def bulk_update_assignees_state(merge_request, new_assignees)
return unless current_user.mr_attention_requests_enabled?
return if new_assignees.empty?
assignees_map = merge_request.merge_request_assignees_with(new_assignees).to_h do |assignee|
state = merge_request.find_reviewer(assignee.assignee)&.state || :attention_requested
[
assignee,
{ state: MergeRequestAssignee.states[state], updated_state_by_user_id: current_user.id }
]
end
::Gitlab::Database::BulkUpdate.execute(%i[state updated_state_by_user_id], assignees_map)
end
def bulk_update_reviewers_state(merge_request, new_reviewers)
return unless current_user.mr_attention_requests_enabled?
return if new_reviewers.empty?
reviewers_map = merge_request.merge_request_reviewers_with(new_reviewers).to_h do |reviewer|
state = merge_request.find_assignee(reviewer.reviewer)&.state || :attention_requested
[
reviewer,
{ state: MergeRequestReviewer.states[state], updated_state_by_user_id: current_user.id }
]
end
::Gitlab::Database::BulkUpdate.execute(%i[state updated_state_by_user_id], reviewers_map)
end
end
end

View File

@ -21,8 +21,6 @@ module MergeRequests
merge_request_activity_counter.track_users_assigned_to_mr(users: new_assignees)
merge_request_activity_counter.track_assignees_changed_action(user: current_user)
merge_request.merge_request_assignees_with(new_assignees).update_all(updated_state_by_user_id: current_user.id)
execute_assignees_hooks(merge_request, old_assignees) if options[:execute_hooks]
unless new_assignees.include?(current_user)

View File

@ -20,6 +20,8 @@ module MergeRequests
attrs = update_attrs.merge(assignee_ids: new_ids)
merge_request.update!(**attrs)
bulk_update_assignees_state(merge_request, merge_request.assignees - old_assignees)
# Defer the more expensive operations (handle_assignee_changes) to the background
MergeRequests::HandleAssigneesChangeService
.new(project: project, current_user: current_user)

View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
module Projects
# Service class to detect if a project is made to run on the Android platform.
#
# This service searches for an AndroidManifest.xml file which all Android app
# project must have. It returns the symbol :android if the given project is an
# Android app project.
#
# Ref: https://developer.android.com/guide/topics/manifest/manifest-intro
#
# Example usage:
# > AndroidTargetPlatformDetectorService.new(a_project).execute
# => nil
# > AndroidTargetPlatformDetectorService.new(an_android_project).execute
# => :android
class AndroidTargetPlatformDetectorService < BaseService
# <manifest> element is required and must occur once inside AndroidManifest.xml
MANIFEST_FILE_SEARCH_QUERY = '<manifest filename:AndroidManifest.xml'
def execute
detect
end
private
def file_finder
@file_finder ||= ::Gitlab::FileFinder.new(project, project.default_branch)
end
def detect
return :android if file_finder.find(MANIFEST_FILE_SEARCH_QUERY).present?
end
end
end

View File

@ -4,15 +4,22 @@ module Projects
class RecordTargetPlatformsService < BaseService
include Gitlab::Utils::StrongMemoize
def initialize(project, detector_service)
@project = project
@detector_service = detector_service
end
def execute
record_target_platforms
end
private
attr_reader :project, :detector_service
def target_platforms
strong_memoize(:target_platforms) do
AppleTargetPlatformDetectorService.new(project).execute
Array(detector_service.new(project).execute)
end
end
@ -36,6 +43,7 @@ module Projects
end
def send_build_ios_app_guide_email
return unless target_platforms.include? :ios
return unless experiment_candidate?
campaign = Users::InProductMarketingEmail::BUILD_IOS_APP_GUIDE

View File

@ -60,7 +60,7 @@
= number_with_delimiter(issues_count)
- if header_link?(:merge_requests)
= nav_link(path: 'dashboard#merge_requests', html_options: { class: "user-counter dropdown" }) do
- top_level_link = Feature.enabled?(:mr_attention_requests, default_enabled: :yaml) ? attention_requested_mrs_dashboard_path : assigned_mrs_dashboard_path
- top_level_link = current_user.mr_attention_requests_enabled? ? attention_requested_mrs_dashboard_path : assigned_mrs_dashboard_path
= link_to top_level_link, class: 'dashboard-shortcuts-merge_requests', title: _('Merge requests'), aria: { label: _('Merge requests') },
data: { qa_selector: 'merge_requests_shortcut_button',
toggle: "dropdown",
@ -77,7 +77,7 @@
%ul
%li.dropdown-header
= _('Merge requests')
- if Feature.enabled?(:mr_attention_requests, default_enabled: :yaml)
- if current_user.mr_attention_requests_enabled?
%li#js-need-attention-nav
#js-need-attention-nav-onboarding
= link_to attention_requested_mrs_dashboard_path, class: 'gl-display-flex! gl-align-items-center js-prefetch-document' do

View File

@ -92,7 +92,7 @@
#js-review-bar
- if Feature.enabled?(:mr_attention_requests, default_enabled: :yaml)
- if current_user&.mr_attention_requests_enabled?
#js-need-attention-sidebar-onboarding
= render 'projects/invite_members_modal', project: @project

View File

@ -3,7 +3,7 @@
- render_count = assignees_rendering_overflow ? max_render - 1 : max_render
- more_assignees_count = issuable.assignees.size - render_count
- if issuable.instance_of?(MergeRequest) && Feature.enabled?(:mr_attention_requests, default_enabled: :yaml)
- if issuable.instance_of?(MergeRequest) && current_user&.mr_attention_requests_enabled?
= render 'shared/issuable/merge_request_assignees', issuable: issuable, count: render_count
- else
- issuable.assignees.take(render_count).each do |assignee| # rubocop: disable CodeReuse/ActiveRecord

View File

@ -3,7 +3,7 @@
- render_count = reviewers_rendering_overflow ? max_render - 1 : max_render
- more_reviewers_count = issuable.reviewers.size - render_count
- if issuable.instance_of?(MergeRequest) && Feature.enabled?(:mr_attention_requests, default_enabled: :yaml)
- if issuable.instance_of?(MergeRequest) && current_user&.mr_attention_requests_enabled?
= render 'shared/issuable/merge_request_reviewers', issuable: issuable, count: render_count
- else
- issuable.reviewers.take(render_count).each do |reviewer| # rubocop: disable CodeReuse/ActiveRecord

View File

@ -88,7 +88,7 @@
= render 'shared/issuable/user_dropdown_item',
user: User.new(username: '{{username}}', name: '{{name}}'),
avatar: { lazy: true, url: '{{avatar_url}}' }
- if Feature.enabled?(:mr_attention_requests, default_enabled: :yaml)
- if current_user&.mr_attention_requests_enabled?
#js-dropdown-attention-requested.filtered-search-input-dropdown-menu.dropdown-menu
- if current_user
%ul{ data: { dropdown: true } }

View File

@ -7,6 +7,7 @@ module Projects
LEASE_TIMEOUT = 1.hour.to_i
APPLE_PLATFORM_LANGUAGES = %w(swift objective-c).freeze
ANDROID_PLATFORM_LANGUAGES = %w(java kotlin).freeze
feature_category :experimentation_activation
data_consistency :always
@ -18,10 +19,10 @@ module Projects
@project = Project.find_by_id(project_id)
return unless project
return unless uses_apple_platform_languages?
return unless detector_service
try_obtain_lease do
@target_platforms = Projects::RecordTargetPlatformsService.new(project).execute
@target_platforms = Projects::RecordTargetPlatformsService.new(project, detector_service).execute
log_target_platforms_metadata
end
end
@ -30,8 +31,29 @@ module Projects
attr_reader :target_platforms, :project
def detector_service
if uses_apple_platform_languages?
AppleTargetPlatformDetectorService
elsif uses_android_platform_languages? && detect_android_projects_enabled?
AndroidTargetPlatformDetectorService
end
end
def detect_android_projects_enabled?
Feature.enabled?(:detect_android_projects, project)
end
def uses_apple_platform_languages?
project.repository_languages.with_programming_language(*APPLE_PLATFORM_LANGUAGES).present?
target_languages.with_programming_language(*APPLE_PLATFORM_LANGUAGES).present?
end
def uses_android_platform_languages?
target_languages.with_programming_language(*ANDROID_PLATFORM_LANGUAGES).present?
end
def target_languages
languages = APPLE_PLATFORM_LANGUAGES + ANDROID_PLATFORM_LANGUAGES
@target_languages ||= project.repository_languages.with_programming_language(*languages)
end
def log_target_platforms_metadata

View File

@ -0,0 +1,8 @@
---
name: detect_android_projects
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85681
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/360902
milestone: '15.0'
type: development
group: group::activation
default_enabled: false

View File

@ -0,0 +1,15 @@
- name: "`artifacts:report:cobertura` keyword"
announcement_milestone: "14.8"
announcement_date: "2022-02-22"
removal_milestone: "15.0"
removal_date: "2022-05-22"
breaking_change: false
body: |
As of GitLab 15.0, the `artifacts:report:cobertura` keyword has been replaced by
[`artifacts:reports:coverage_report`](https://gitlab.com/gitlab-org/gitlab/-/issues/344533). Cobertura is the only
supported report file, but this is the first step towards GitLab supporting other report types.
# The following items are not published on the docs page, but may be used in the future.
stage: Verify
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/348980
documentation_url: https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportscobertura

View File

@ -12,6 +12,7 @@ level: warning
ignorecase: true
swap:
air(?:-| )?gapped: offline environment
bullet: list item
click: select
code base: codebase
config: configuration

View File

@ -3441,8 +3441,6 @@ Input type: `MergeRequestCreateInput`
### `Mutation.mergeRequestRemoveAttentionRequest`
Available only when feature flag `mr_attention_requests` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice.
Input type: `MergeRequestRemoveAttentionRequestInput`
#### Arguments
@ -3464,8 +3462,6 @@ Input type: `MergeRequestRemoveAttentionRequestInput`
### `Mutation.mergeRequestRequestAttention`
Available only when feature flag `mr_attention_requests` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice.
Input type: `MergeRequestRequestAttentionInput`
#### Arguments
@ -3636,8 +3632,6 @@ Input type: `MergeRequestSetSubscriptionInput`
### `Mutation.mergeRequestToggleAttentionRequested`
Available only when feature flag `mr_attention_requests` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice.
Input type: `MergeRequestToggleAttentionRequestedInput`
#### Arguments

View File

@ -13,7 +13,7 @@ Generally, if a style is not covered by [existing Rubocop rules or style guides]
Before adding a new cop to enforce a given style, make sure to discuss it with your team.
When the style is approved by a backend EM or by a BE staff eng, add a new section to this page to
document the new rule. For every new guideline, add it in a new section and link the discussion from the section's
[version history note](../documentation/versions.md#add-a-version-history-bullet)
[version history note](../documentation/versions.md#add-a-version-history-item)
to provide context and serve as a reference.
Just because something is listed here does not mean it cannot be reopened for discussion.

View File

@ -75,7 +75,7 @@ Example response:
```
````
Adjust the [version history note accordingly](versions.md#add-a-version-history-bullet)
Adjust the [version history note accordingly](versions.md#add-a-version-history-item)
to describe the GitLab release that introduced the API call.
## Method description

View File

@ -237,7 +237,7 @@ consider using subsections for each distinct task.
### Related topics
If inline links are not sufficient, you can create a topic called **Related topics**
and include a bulleted list of related topics. This topic should be above the Troubleshooting section.
and include an unordered list of related topics. This topic should be above the Troubleshooting section.
```markdown
# Related topics

View File

@ -980,7 +980,7 @@ If you are documenting multiple fields and only one field needs explanation, do
1. Expand **Push rules**.
1. Complete the fields. **Branch name** must be a regular expression.
To describe multiple fields, use bullets:
To describe multiple fields, use unordered list items:
1. Expand **General pipelines**.
1. Complete the fields.

View File

@ -166,6 +166,14 @@ Use **text box** to refer to the UI field. Do not use **field** or **box**. For
- In the **Variable name** text box, enter a value.
## bullet
Don't refer to individual items in an ordered or unordered list as **bullets**. Use **list item** instead. If you need to be less ambiguous, you can use:
- **Ordered list item** for items in an ordered list.
- **Unordered list item** for items in an unordered list.
## button
Don't use a descriptor with **button**.

View File

@ -29,9 +29,9 @@ either as a **Version history** bullet or as an inline text reference.
You do not need to add version information on the pages in the `/development` directory.
### Add a **Version history** bullet
### Add a **Version history** item
If all content in a topic is related, add a version history bullet after the topic heading.
If all content in a topic is related, add a version history item after the topic heading.
For example:
```markdown
@ -42,7 +42,7 @@ For example:
This feature does something.
```
The bullet text must include these words in order. Capitalization doesn't matter.
The item text must include these words in order. Capitalization doesn't matter.
- `introduced`, `enabled`, `deprecated`, `changed`, `moved`, `recommended`, `removed`, or `renamed`
- `in` or `to`
@ -122,7 +122,7 @@ The title and a removed indicator remains until three months after the removal.
To remove a page:
1. Leave the page title. Remove all other content, including the version history bullets and the word `WARNING:`.
1. Leave the page title. Remove all other content, including the version history items and the word `WARNING:`.
1. After the title, change `(deprecated)` to `(removed)`.
1. Update the YAML metadata:
- For `remove_date`, set the value to a date three months after
@ -156,7 +156,7 @@ This content is removed from the documentation as part of the Technical Writing
To remove a topic:
1. Leave the title and the details of the deprecation and removal. Remove all other content,
including the version history bullets and the word `WARNING:`.
including the version history items and the word `WARNING:`.
1. Add `(removed)` after the title.
1. Add the following HTML comments above and below the topic.
For the `remove_date`, set a date three months after the release where it was removed.
@ -184,7 +184,7 @@ GitLab 14.0, 13.0 and 12.0 are supported.
[View the list of supported versions](https://about.gitlab.com/support/statement-of-support.html#version-support).
If you see version history bullets or inline text that refers to unsupported versions, you can remove it.
If you see version history items or inline text that refers to unsupported versions, you can remove it.
Historical feature information is available in [release posts](https://about.gitlab.com/releases/)
or by searching for the issue or merge request where the work was done.

View File

@ -188,6 +188,12 @@ The new security approvals feature is similar to vulnerability check. For exampl
- A two-step approval process can be enforced for any desired changes to security approval rules.
- A single set of security policies can be applied to multiple development projects to allow for ease in maintaining a single, centralized ruleset.
### `artifacts:report:cobertura` keyword
As of GitLab 15.0, the `artifacts:report:cobertura` keyword has been replaced by
[`artifacts:reports:coverage_report`](https://gitlab.com/gitlab-org/gitlab/-/issues/344533). Cobertura is the only
supported report file, but this is the first step towards GitLab supporting other report types.
## 14.10
### Permissions change for downloading Composer dependencies

View File

@ -19,7 +19,7 @@ module API
todos: current_user.todos_pending_count
}
if Feature.enabled?(:mr_attention_requests, default_enabled: :yaml)
if current_user&.mr_attention_requests_enabled?
counts[:attention_requests] = current_user.attention_requested_open_merge_requests_count
end

View File

@ -58,7 +58,7 @@ module Gitlab
push_frontend_feature_flag(:sandboxed_mermaid, default_enabled: :yaml)
push_frontend_feature_flag(:source_editor_toolbar, default_enabled: :yaml)
push_frontend_feature_flag(:gl_avatar_for_all_user_avatars, default_enabled: :yaml)
push_frontend_feature_flag(:mr_attention_requests, default_enabled: :yaml)
push_frontend_feature_flag(:mr_attention_requests, current_user, default_enabled: :yaml)
push_frontend_feature_flag(:follow_in_user_popover, current_user, default_enabled: :yaml)
end

View File

@ -285,7 +285,7 @@ module Gitlab
end
types MergeRequest
condition do
Feature.enabled?(:mr_attention_requests, project, default_enabled: :yaml) &&
current_user.mr_attention_requests_enabled? &&
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
end
parse_params do |attention_param|
@ -321,7 +321,7 @@ module Gitlab
end
types MergeRequest
condition do
Feature.enabled?(:mr_attention_requests, project, default_enabled: :yaml) &&
current_user.mr_attention_requests_enabled? &&
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
end
parse_params do |attention_param|

View File

@ -53,6 +53,11 @@ RSpec.describe 'Navigation bar counter', :use_clean_rails_memory_store_caching d
describe 'feature flag mr_attention_requests is enabled' do
before do
merge_request.update!(assignees: [user])
merge_request.find_assignee(user).update!(state: :attention_requested)
user.invalidate_attention_requested_count
sign_in(user)
end

View File

@ -112,8 +112,8 @@ RSpec.describe 'Dashboard Merge Requests' do
end
it 'includes assigned and reviewers in badge' do
within("span[aria-label='#{n_("%d merge request", "%d merge requests", 3) % 3}']") do
expect(page).to have_content('3')
within("span[aria-label='#{n_("%d merge request", "%d merge requests", 0) % 0}']") do
expect(page).to have_content('0')
end
expect(find('.js-assigned-mr-count')).to have_content('2')
expect(find('.js-reviewer-mr-count')).to have_content('1')

View File

@ -414,6 +414,9 @@ RSpec.describe MergeRequestsFinder do
before do
reviewer = merge_request1.find_reviewer(user2)
reviewer.update!(state: :reviewed)
merge_request2.find_reviewer(user2).update!(state: :attention_requested)
merge_request3.find_assignee(user2).update!(state: :attention_requested)
end
context 'by username' do

View File

@ -76,6 +76,7 @@ RSpec.describe GitlabSchema.types['UserMergeRequestInteraction'] do
context 'when the user has been asked to review the MR' do
before do
merge_request.reviewers << user
merge_request.find_reviewer(user).update!(state: :attention_requested)
end
it { is_expected.to eq(Types::MergeRequestReviewStateEnum.values['ATTENTION_REQUESTED'].value) }

View File

@ -84,7 +84,7 @@ RSpec.describe MergeRequestsHelper do
describe 'mr_attention_requests disabled' do
before do
stub_feature_flags(mr_attention_requests: false)
allow(user).to receive(:mr_attention_requests_enabled?).and_return(false)
end
it "returns assigned, review requested and total merge request counts" do
@ -97,6 +97,10 @@ RSpec.describe MergeRequestsHelper do
end
describe 'mr_attention_requests enabled' do
before do
allow(user).to receive(:mr_attention_requests_enabled?).and_return(true)
end
it "returns assigned, review requested, attention requests and total merge request counts" do
expect(subject).to eq(
assigned: user.assigned_open_merge_requests_count,

View File

@ -233,6 +233,17 @@ RSpec.describe AlertManagement::Alert do
end
end
describe '.find_ongoing_alert' do
let_it_be(:fingerprint) { SecureRandom.hex }
let_it_be(:resolved_alert_with_fingerprint) { create(:alert_management_alert, :resolved, project: project, fingerprint: fingerprint) }
let_it_be(:alert_with_fingerprint_in_other_project) { create(:alert_management_alert, project: project2, fingerprint: fingerprint) }
let_it_be(:alert_with_fingerprint) { create(:alert_management_alert, project: project, fingerprint: fingerprint) }
subject { described_class.find_ongoing_alert(project, fingerprint) }
it { is_expected.to eq(alert_with_fingerprint) }
end
describe '.last_prometheus_alert_by_project_id' do
subject { described_class.last_prometheus_alert_by_project_id }

View File

@ -41,22 +41,11 @@ RSpec.describe MergeRequestAssignee do
it_behaves_like 'having unique enum values'
it_behaves_like 'having reviewer state'
describe 'syncs to reviewer state' do
before do
reviewer = merge_request.merge_request_reviewers.build(reviewer: assignee)
reviewer.update!(state: :reviewed)
end
it { is_expected.to have_attributes(state: 'reviewed') }
end
describe '#attention_requested_by' do
let(:current_user) { create(:user) }
before do
subject.update!(updated_state_by: current_user)
subject.update!(updated_state_by: current_user, state: :attention_requested)
end
context 'attention requested' do

View File

@ -10,17 +10,6 @@ RSpec.describe MergeRequestReviewer do
it_behaves_like 'having unique enum values'
it_behaves_like 'having reviewer state'
describe 'syncs to assignee state' do
before do
assignee = merge_request.merge_request_assignees.build(assignee: reviewer)
assignee.update!(state: :reviewed)
end
it { is_expected.to have_attributes(state: 'reviewed') }
end
describe 'associations' do
it { is_expected.to belong_to(:merge_request).class_name('MergeRequest') }
it { is_expected.to belong_to(:reviewer).class_name('User').inverse_of(:merge_request_reviewers) }
@ -30,7 +19,7 @@ RSpec.describe MergeRequestReviewer do
let(:current_user) { create(:user) }
before do
subject.update!(updated_state_by: current_user)
subject.update!(updated_state_by: current_user, state: :attention_requested)
end
context 'attention requested' do

View File

@ -151,6 +151,8 @@ RSpec.describe MergeRequest, factory_default: :keep do
before do
assignee = merge_request6.find_assignee(user2)
assignee.update!(state: :reviewed)
merge_request2.find_reviewer(user2).update!(state: :attention_requested)
merge_request5.find_assignee(user2).update!(state: :attention_requested)
end
it 'returns MRs that have any attention requests' do

View File

@ -5006,9 +5006,13 @@ RSpec.describe User do
let(:archived_project) { create(:project, :public, :archived) }
before do
create(:merge_request, source_project: project, author: user, reviewers: [user])
create(:merge_request, :closed, source_project: project, author: user, reviewers: [user])
create(:merge_request, source_project: archived_project, author: user, reviewers: [user])
mr1 = create(:merge_request, source_project: project, author: user, reviewers: [user])
mr2 = create(:merge_request, :closed, source_project: project, author: user, reviewers: [user])
mr3 = create(:merge_request, source_project: archived_project, author: user, reviewers: [user])
mr1.find_reviewer(user).update!(state: :attention_requested)
mr2.find_reviewer(user).update!(state: :attention_requested)
mr3.find_reviewer(user).update!(state: :attention_requested)
end
it 'returns number of open merge requests from non-archived projects' do
@ -6817,4 +6821,23 @@ RSpec.describe User do
it_behaves_like 'it has loose foreign keys' do
let(:factory_name) { :user }
end
describe 'mr_attention_requests_enabled?' do
let(:user) { create(:user) }
before do
stub_feature_flags(mr_attention_requests: false)
end
it { expect(user.mr_attention_requests_enabled?).to be(false) }
it 'feature flag is enabled for user' do
stub_feature_flags(mr_attention_requests: user)
another_user = create(:user)
expect(user.mr_attention_requests_enabled?).to be(true)
expect(another_user.mr_attention_requests_enabled?).to be(false)
end
end
end

View File

@ -59,6 +59,7 @@ RSpec.describe ::Users::MergeRequestInteraction do
context 'when the user has been asked to review the MR' do
before do
merge_request.reviewers << user
merge_request.find_reviewer(user).update!(state: :attention_requested)
end
it { is_expected.to eq(Types::MergeRequestReviewStateEnum.values['ATTENTION_REQUESTED'].value) }

View File

@ -63,4 +63,17 @@ RSpec.describe 'Request attention' do
expect(mutation_errors).not_to be_empty
end
end
context 'feature flag is disabled' do
before do
stub_feature_flags(mr_attention_requests: false)
end
it 'returns an error' do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
expect(graphql_errors[0]["message"]).to eq "Feature disabled"
end
end
end

View File

@ -63,4 +63,17 @@ RSpec.describe 'Remove attention request' do
expect(mutation_errors).not_to be_empty
end
end
context 'feature flag is disabled' do
before do
stub_feature_flags(mr_attention_requests: false)
end
it 'returns an error' do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
expect(graphql_errors[0]["message"]).to eq "Feature disabled"
end
end
end

View File

@ -425,7 +425,7 @@ RSpec.describe 'getting merge request information nested in a project' do
other_users.each do |user|
assign_user(user)
merge_request.merge_request_reviewers.find_or_create_by!(reviewer: user)
merge_request.merge_request_reviewers.find_or_create_by!(reviewer: user, state: :attention_requested)
end
expect { post_graphql(query) }.not_to exceed_query_limit(baseline)
@ -466,7 +466,7 @@ RSpec.describe 'getting merge request information nested in a project' do
let(:can_update) { false }
def assign_user(user)
merge_request.merge_request_reviewers.create!(reviewer: user)
merge_request.merge_request_reviewers.create!(reviewer: user, state: :attention_requested)
end
end

View File

@ -43,7 +43,7 @@ RSpec.describe API::UserCounts do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_a Hash
expect(json_response['merge_requests']).to eq(2)
expect(json_response['attention_requests']).to eq(2)
expect(json_response['attention_requests']).to eq(0)
end
describe 'mr_attention_requests is disabled' do

View File

@ -58,6 +58,10 @@ RSpec.describe MergeRequestUserEntity do
end
context 'attention_requested' do
before do
merge_request.find_assignee(user).update!(state: :attention_requested)
end
it { is_expected.to include(attention_requested: true ) }
end

View File

@ -95,12 +95,6 @@ RSpec.describe MergeRequests::HandleAssigneesChangeService do
execute
end
it 'updates attention requested by of assignee' do
execute
expect(merge_request.find_assignee(assignee).updated_state_by).to eq(user)
end
it 'tracks users assigned event' do
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
.to receive(:track_users_assigned_to_mr).once.with(users: [assignee])

View File

@ -113,6 +113,49 @@ RSpec.describe MergeRequests::UpdateAssigneesService do
expect { service.execute(merge_request) }
.to issue_fewer_queries_than { update_service.execute(other_mr) }
end
context 'setting state of assignees' do
before do
stub_feature_flags(mr_attention_requests: false)
end
it 'does not set state as attention_requested if feature flag is disabled' do
update_merge_request
expect(merge_request.merge_request_assignees[0].state).not_to eq('attention_requested')
end
context 'feature flag is enabled for current_user' do
before do
stub_feature_flags(mr_attention_requests: user)
end
it 'sets state as attention_requested' do
update_merge_request
expect(merge_request.merge_request_assignees[0].state).to eq('attention_requested')
expect(merge_request.merge_request_assignees[0].updated_state_by).to eq(user)
end
it 'uses reviewers state if it is same user as new assignee' do
merge_request.reviewers << user2
update_merge_request
expect(merge_request.merge_request_assignees[0].state).to eq('unreviewed')
end
context 'when assignee_ids matches existing assignee' do
let(:opts) { { assignee_ids: [user3.id] } }
it 'keeps original assignees state' do
update_merge_request
expect(merge_request.find_assignee(user3).state).to eq('unreviewed')
end
end
end
end
end
end
end

View File

@ -328,6 +328,49 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
update_merge_request(reviewer_ids: [user.id])
end
context 'setting state of reviewers' do
before do
stub_feature_flags(mr_attention_requests: false)
end
it 'does not set state as attention_requested if feature flag is disabled' do
update_merge_request(reviewer_ids: [user.id])
expect(merge_request.merge_request_reviewers[0].state).not_to eq('attention_requested')
end
context 'feature flag is enabled for current_user' do
before do
stub_feature_flags(mr_attention_requests: user)
end
it 'sets state as attention_requested' do
update_merge_request(reviewer_ids: [user.id])
expect(merge_request.merge_request_reviewers[0].state).to eq('attention_requested')
expect(merge_request.merge_request_reviewers[0].updated_state_by).to eq(user)
end
it 'keeps original reviewers state' do
merge_request.find_reviewer(user2).update!(state: :unreviewed)
update_merge_request({
reviewer_ids: [user2.id]
})
expect(merge_request.find_reviewer(user2).state).to eq('unreviewed')
end
it 'uses reviewers state if it is same user as new assignee' do
merge_request.assignees << user
update_merge_request(reviewer_ids: [user.id])
expect(merge_request.merge_request_reviewers[0].state).to eq('unreviewed')
end
end
end
end
it 'creates a resource label event' do
@ -1066,6 +1109,53 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
end
end
end
context 'setting state of assignees' do
before do
stub_feature_flags(mr_attention_requests: false)
end
it 'does not set state as attention_requested if feature flag is disabled' do
update_merge_request({
assignee_ids: [user2.id]
})
expect(merge_request.merge_request_assignees[0].state).not_to eq('attention_requested')
end
context 'feature flag is enabled for current_user' do
before do
stub_feature_flags(mr_attention_requests: user)
end
it 'sets state as attention_requested' do
update_merge_request({
assignee_ids: [user2.id]
})
expect(merge_request.merge_request_assignees[0].state).to eq('attention_requested')
expect(merge_request.merge_request_assignees[0].updated_state_by).to eq(user)
end
it 'keeps original assignees state' do
update_merge_request({
assignee_ids: [user3.id]
})
expect(merge_request.find_assignee(user3).state).to eq('unreviewed')
end
it 'uses reviewers state if it is same user as new assignee' do
merge_request.reviewers << user2
update_merge_request({
assignee_ids: [user2.id]
})
expect(merge_request.merge_request_assignees[0].state).to eq('unreviewed')
end
end
end
end
context 'when adding time spent' do

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::AndroidTargetPlatformDetectorService do
let_it_be(:project) { build(:project) }
subject { described_class.new(project).execute }
before do
allow(Gitlab::FileFinder).to receive(:new) { finder }
end
context 'when project is not an Android project' do
let(:finder) { instance_double(Gitlab::FileFinder, find: []) }
it { is_expected.to be_nil }
end
context 'when project is an Android project' do
let(:finder) { instance_double(Gitlab::FileFinder) }
before do
query = described_class::MANIFEST_FILE_SEARCH_QUERY
allow(finder).to receive(:find).with(query) { [instance_double(Gitlab::Search::FoundBlob)] }
end
it { is_expected.to eq :android }
end
end

View File

@ -5,25 +5,38 @@ require 'spec_helper'
RSpec.describe Projects::RecordTargetPlatformsService, '#execute' do
let_it_be(:project) { create(:project) }
subject(:execute) { described_class.new(project).execute }
let(:detector_service) { Projects::AppleTargetPlatformDetectorService }
context 'when project is an XCode project' do
def project_setting
ProjectSetting.find_by_project_id(project.id)
end
subject(:execute) { described_class.new(project, detector_service).execute }
context 'when detector returns target platform values' do
let(:detector_result) { [:ios, :osx] }
let(:service_result) { detector_result.map(&:to_s) }
before do
double = instance_double(Projects::AppleTargetPlatformDetectorService, execute: [:ios, :osx])
allow(Projects::AppleTargetPlatformDetectorService).to receive(:new) { double }
double = instance_double(detector_service, execute: detector_result)
allow(detector_service).to receive(:new) { double }
end
it 'creates a new setting record for the project', :aggregate_failures do
expect { execute }.to change { ProjectSetting.count }.from(0).to(1)
expect(ProjectSetting.last.target_platforms).to match_array(%w(ios osx))
shared_examples 'saves and returns detected target platforms' do
it 'creates a new setting record for the project', :aggregate_failures do
expect { execute }.to change { ProjectSetting.count }.from(0).to(1)
expect(ProjectSetting.last.target_platforms).to match_array(service_result)
end
it 'returns the array of stored target platforms' do
expect(execute).to match_array service_result
end
end
it 'returns array of detected target platforms' do
expect(execute).to match_array %w(ios osx)
it_behaves_like 'saves and returns detected target platforms'
context 'when detector returns a non-array value' do
let(:detector_service) { Projects::AndroidTargetPlatformDetectorService }
let(:detector_result) { :android }
let(:service_result) { [detector_result.to_s] }
it_behaves_like 'saves and returns detected target platforms'
end
context 'when a project has an existing setting record' do
@ -31,6 +44,10 @@ RSpec.describe Projects::RecordTargetPlatformsService, '#execute' do
create(:project_setting, project: project, target_platforms: saved_target_platforms)
end
def project_setting
ProjectSetting.find_by_project_id(project.id)
end
context 'when target platforms changed' do
let(:saved_target_platforms) { %w(tvos) }
@ -81,23 +98,44 @@ RSpec.describe Projects::RecordTargetPlatformsService, '#execute' do
it_behaves_like 'tracks experiment assignment event'
end
context 'experiment control' do
before do
stub_experiments(build_ios_app_guide_email: :control)
end
shared_examples 'does not send email' do
it 'does not execute a Projects::InProductMarketingCampaignEmailsService' do
expect(Projects::InProductMarketingCampaignEmailsService).not_to receive(:new)
execute
end
end
context 'experiment control' do
before do
stub_experiments(build_ios_app_guide_email: :control)
end
it_behaves_like 'does not send email'
it_behaves_like 'tracks experiment assignment event'
end
context 'when project is not an iOS project' do
let(:detector_service) { Projects::AppleTargetPlatformDetectorService }
let(:detector_result) { :android }
before do
stub_experiments(build_ios_app_guide_email: :candidate)
end
it_behaves_like 'does not send email'
it 'does not track experiment assignment event', :experiment do
expect(experiment(:build_ios_app_guide_email))
.not_to track(:assignment)
execute
end
end
end
end
context 'when project is not an XCode project' do
context 'when detector does not return any target platform values' do
before do
double = instance_double(Projects::AppleTargetPlatformDetectorService, execute: [])
allow(Projects::AppleTargetPlatformDetectorService).to receive(:new).with(project) { double }

View File

@ -1,15 +0,0 @@
# frozen_string_literal: true
RSpec.shared_examples 'having reviewer state' do
describe 'mr_attention_requests feature flag is disabled' do
before do
stub_feature_flags(mr_attention_requests: false)
end
it { is_expected.to have_attributes(state: 'unreviewed') }
end
describe 'mr_attention_requests feature flag is enabled' do
it { is_expected.to have_attributes(state: 'attention_requested') }
end
end

View File

@ -7,11 +7,11 @@ RSpec.describe Projects::RecordTargetPlatformsWorker do
let_it_be(:swift) { create(:programming_language, name: 'Swift') }
let_it_be(:objective_c) { create(:programming_language, name: 'Objective-C') }
let_it_be(:java) { create(:programming_language, name: 'Java') }
let_it_be(:kotlin) { create(:programming_language, name: 'Kotlin') }
let_it_be(:project) { create(:project, :repository, detected_repository_languages: true) }
let(:worker) { described_class.new }
let(:service_result) { %w(ios osx watchos) }
let(:service_double) { instance_double(Projects::RecordTargetPlatformsService, execute: service_result) }
let(:lease_key) { "#{described_class.name.underscore}:#{project.id}" }
let(:lease_timeout) { described_class::LEASE_TIMEOUT }
@ -21,16 +21,20 @@ RSpec.describe Projects::RecordTargetPlatformsWorker do
stub_exclusive_lease(lease_key, timeout: lease_timeout)
end
shared_examples 'performs detection' do
it 'creates and executes a Projects::RecordTargetPlatformService instance for the project', :aggregate_failures do
expect(Projects::RecordTargetPlatformsService).to receive(:new).with(project) { service_double }
shared_examples 'performs detection' do |detector_service_class|
let(:service_double) { instance_double(detector_service_class, execute: service_result) }
it "creates and executes a #{detector_service_class} instance for the project", :aggregate_failures do
expect(Projects::RecordTargetPlatformsService).to receive(:new)
.with(project, detector_service_class) { service_double }
expect(service_double).to receive(:execute)
perform
end
it 'logs extra metadata on done', :aggregate_failures do
expect(Projects::RecordTargetPlatformsService).to receive(:new).with(project) { service_double }
expect(Projects::RecordTargetPlatformsService).to receive(:new)
.with(project, detector_service_class) { service_double }
expect(worker).to receive(:log_extra_metadata_on_done).with(:target_platforms, service_result)
perform
@ -45,19 +49,68 @@ RSpec.describe Projects::RecordTargetPlatformsWorker do
end
end
context 'when project uses Swift programming language' do
let!(:repository_language) { create(:repository_language, project: project, programming_language: swift) }
include_examples 'performs detection'
def create_language(language)
create(:repository_language, project: project, programming_language: language)
end
context 'when project uses Objective-C programming language' do
let!(:repository_language) { create(:repository_language, project: project, programming_language: objective_c) }
context 'when project uses programming language for Apple platform' do
let(:service_result) { %w(ios osx watchos) }
include_examples 'performs detection'
context 'when project uses Swift programming language' do
before do
create_language(swift)
end
it_behaves_like 'performs detection', Projects::AppleTargetPlatformDetectorService
end
context 'when project uses Objective-C programming language' do
before do
create_language(objective_c)
end
it_behaves_like 'performs detection', Projects::AppleTargetPlatformDetectorService
end
end
context 'when the project does not contain programming languages for Apple platforms' do
context 'when project uses programming language for Android platform' do
let(:feature_enabled) { true }
let(:service_result) { %w(android) }
before do
stub_feature_flags(detect_android_projects: feature_enabled)
end
context 'when project uses Java' do
before do
create_language(java)
end
it_behaves_like 'performs detection', Projects::AndroidTargetPlatformDetectorService
context 'when feature flag is disabled' do
let(:feature_enabled) { false }
it_behaves_like 'does nothing'
end
end
context 'when project uses Kotlin' do
before do
create_language(kotlin)
end
it_behaves_like 'performs detection', Projects::AndroidTargetPlatformDetectorService
context 'when feature flag is disabled' do
let(:feature_enabled) { false }
it_behaves_like 'does nothing'
end
end
end
context 'when the project does not use programming languages for Apple or Android platforms' do
it_behaves_like 'does nothing'
end