Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
3a72ac7750
commit
25ba0c04e9
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Resolvers
|
||||
module Organizations
|
||||
class OrganizationsResolver < BaseResolver
|
||||
include Gitlab::Graphql::Authorize::AuthorizeResource
|
||||
|
||||
type Types::Organizations::OrganizationType.connection_type, null: true
|
||||
authorize :read_organization
|
||||
|
||||
def resolve
|
||||
# For the Organization MVC, all the organizations are public. We need to change this to only accessible
|
||||
# organizations once we start supporting private organizations.
|
||||
# See https://gitlab.com/groups/gitlab-org/-/epics/10649.
|
||||
::Organizations::Organization.all
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -122,6 +122,11 @@ module Types
|
|||
resolver: Resolvers::Organizations::OrganizationResolver,
|
||||
description: "Find an organization.",
|
||||
alpha: { milestone: '16.4' }
|
||||
field :organizations, Types::Organizations::OrganizationType.connection_type,
|
||||
null: true,
|
||||
resolver: Resolvers::Organizations::OrganizationsResolver,
|
||||
description: "List organizations.",
|
||||
alpha: { milestone: '16.8' }
|
||||
field :package,
|
||||
description: 'Find a package. This field can only be resolved for one query in any single request. Returns `null` if a package has no `default` status.',
|
||||
resolver: Resolvers::PackageDetailsResolver
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
class IssueEmailParticipant < ApplicationRecord
|
||||
include BulkInsertSafe
|
||||
include Presentable
|
||||
include CaseSensitivity
|
||||
|
||||
belongs_to :issue
|
||||
|
||||
|
|
@ -10,6 +11,8 @@ class IssueEmailParticipant < ApplicationRecord
|
|||
validates :issue, presence: true
|
||||
validate :validate_email_format
|
||||
|
||||
scope :with_emails, ->(emails) { iwhere(email: emails) }
|
||||
|
||||
def validate_email_format
|
||||
self.errors.add(:email, I18n.t(:invalid, scope: 'valid_email.validations.email')) unless ValidateEmail.valid?(self.email)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module IssueEmailParticipants
|
||||
class BaseService < ::BaseProjectService
|
||||
MAX_NUMBER_OF_EMAILS = 6
|
||||
|
||||
attr_reader :target, :emails
|
||||
|
||||
def initialize(target:, current_user:, emails:)
|
||||
super(project: target.project, current_user: current_user)
|
||||
|
||||
@target = target
|
||||
@emails = emails
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def response_from_guard_checks
|
||||
return error_feature_flag unless Feature.enabled?(:issue_email_participants, target.project)
|
||||
return error_underprivileged unless current_user.can?(:"admin_#{target.to_ability_name}", target)
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def add_system_note(emails)
|
||||
message = format(system_note_text, emails: emails.to_sentence)
|
||||
::SystemNoteService.email_participants(target, project, current_user, message)
|
||||
|
||||
message
|
||||
end
|
||||
|
||||
def error(message)
|
||||
ServiceResponse.error(message: message)
|
||||
end
|
||||
|
||||
def error_feature_flag
|
||||
# Don't translate feature flag error because it's temporary.
|
||||
error("Feature flag issue_email_participants is not enabled for this project.")
|
||||
end
|
||||
|
||||
def error_underprivileged
|
||||
error(_("You don't have permission to manage email participants."))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,25 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module IssueEmailParticipants
|
||||
class CreateService < ::BaseProjectService
|
||||
class CreateService < BaseService
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
MAX_NUMBER_OF_EMAILS = 6
|
||||
MAX_NUMBER_OF_RECORDS = 10
|
||||
|
||||
attr_reader :target, :emails
|
||||
|
||||
def initialize(target:, current_user:, emails:)
|
||||
super(project: target.project, current_user: current_user)
|
||||
|
||||
@target = target
|
||||
@emails = emails
|
||||
end
|
||||
|
||||
def execute
|
||||
return error_feature_flag unless Feature.enabled?(:issue_email_participants, target.project)
|
||||
return error_underprivileged unless current_user.can?(:"admin_#{target.to_ability_name}", target)
|
||||
return error_no_participants unless emails.present?
|
||||
response = response_from_guard_checks
|
||||
return response unless response.nil?
|
||||
return error_no_participants_added unless emails.present?
|
||||
|
||||
added_emails = add_participants(deduplicate_and_limit_emails)
|
||||
|
||||
|
|
@ -27,7 +17,7 @@ module IssueEmailParticipants
|
|||
message = add_system_note(added_emails)
|
||||
ServiceResponse.success(message: message.upcase_first << ".")
|
||||
else
|
||||
error_no_participants
|
||||
error_no_participants_added
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -60,13 +50,6 @@ module IssueEmailParticipants
|
|||
added_emails
|
||||
end
|
||||
|
||||
def add_system_note(added_emails)
|
||||
message = format(_("added %{emails}"), emails: added_emails.to_sentence)
|
||||
::SystemNoteService.add_email_participants(target, project, current_user, message)
|
||||
|
||||
message
|
||||
end
|
||||
|
||||
def existing_emails
|
||||
target.email_participants_emails_downcase
|
||||
end
|
||||
|
|
@ -78,20 +61,11 @@ module IssueEmailParticipants
|
|||
end
|
||||
end
|
||||
|
||||
def error(message)
|
||||
ServiceResponse.error(message: message)
|
||||
def system_note_text
|
||||
_("added %{emails}")
|
||||
end
|
||||
|
||||
def error_feature_flag
|
||||
# Don't translate feature flag error because it's temporary.
|
||||
error("Feature flag issue_email_participants is not enabled for this project.")
|
||||
end
|
||||
|
||||
def error_underprivileged
|
||||
error(_("You don't have permission to add email participants."))
|
||||
end
|
||||
|
||||
def error_no_participants
|
||||
def error_no_participants_added
|
||||
error(_("No email participants were added. Either none were provided, or they already exist."))
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module IssueEmailParticipants
|
||||
class DestroyService < BaseService
|
||||
def execute
|
||||
response = response_from_guard_checks
|
||||
return response unless response.nil?
|
||||
return error_no_participants_removed unless emails.present?
|
||||
|
||||
removed_emails = remove_participants(emails.first(MAX_NUMBER_OF_EMAILS))
|
||||
|
||||
if removed_emails.any?
|
||||
message = add_system_note(removed_emails)
|
||||
ServiceResponse.success(message: message.upcase_first << ".")
|
||||
else
|
||||
error_no_participants_removed
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def remove_participants(emails_to_remove)
|
||||
participants = target
|
||||
.issue_email_participants
|
||||
.with_emails(emails_to_remove)
|
||||
.load # to avoid additional query
|
||||
|
||||
emails = participants.map(&:email)
|
||||
return [] if emails.empty?
|
||||
|
||||
participants.delete_all
|
||||
|
||||
emails
|
||||
end
|
||||
|
||||
def system_note_text
|
||||
_("removed %{emails}")
|
||||
end
|
||||
|
||||
def error_no_participants_removed
|
||||
error(_("No email participants were removed. Either none were provided, or they don't exist."))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -282,8 +282,8 @@ module SystemNoteService
|
|||
::SystemNotes::IssuablesService.new(noteable: noteable, project: project, author: author).mark_canonical_issue_of_duplicate(duplicate_issue)
|
||||
end
|
||||
|
||||
def add_email_participants(noteable, project, author, body)
|
||||
::SystemNotes::IssuablesService.new(noteable: noteable, project: project, author: author).add_email_participants(body)
|
||||
def email_participants(noteable, project, author, body)
|
||||
::SystemNotes::IssuablesService.new(noteable: noteable, project: project, author: author).email_participants(body)
|
||||
end
|
||||
|
||||
def discussion_lock(issuable, author)
|
||||
|
|
|
|||
|
|
@ -431,7 +431,7 @@ module SystemNotes
|
|||
create_note(NoteSummary.new(noteable, project, author, body, action: 'duplicate'))
|
||||
end
|
||||
|
||||
def add_email_participants(body)
|
||||
def email_participants(body)
|
||||
create_note(NoteSummary.new(noteable, project, author, body))
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
description: Count usage of /remove_email quickaction with multiple arguments
|
||||
category: InternalEventTracking
|
||||
action: i_quickactions_remove_email_multiple
|
||||
label_description:
|
||||
property_description:
|
||||
value_description:
|
||||
extra_properties:
|
||||
identifiers:
|
||||
- project
|
||||
- user
|
||||
- namespace
|
||||
product_section: seg
|
||||
product_stage: service
|
||||
product_group: respond
|
||||
milestone: "16.7"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/138178
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
description: Count usage of /remove_email quickaction with a single argument
|
||||
category: InternalEventTracking
|
||||
action: i_quickactions_remove_email_single
|
||||
label_description:
|
||||
property_description:
|
||||
value_description:
|
||||
extra_properties:
|
||||
identifiers:
|
||||
- project
|
||||
- user
|
||||
- namespace
|
||||
product_section: seg
|
||||
product_stage: service
|
||||
product_group: respond
|
||||
milestone: "16.7"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/138178
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
key_path: count_distinct_user_id_from_i_quickactions_remove_email_multiple_28d
|
||||
description: Unique users using the /remove_email quick action to remove multiple email participants from an issue within 28 days
|
||||
product_section: seg
|
||||
product_stage: service
|
||||
product_group: respond
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "16.7"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/138178
|
||||
time_frame: 28d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
options:
|
||||
events:
|
||||
- i_quickactions_remove_email_multiple
|
||||
events:
|
||||
- name: i_quickactions_remove_email_multiple
|
||||
unique: user.id
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
key_path: count_distinct_user_id_from_i_quickactions_remove_email_single_28d
|
||||
description: Unique users using the /remove_email quick action to remove a single email participant from an issue within 28 days
|
||||
product_section: seg
|
||||
product_stage: service
|
||||
product_group: respond
|
||||
performance_indicator_type: []
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "16.7"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/138178
|
||||
time_frame: 28d
|
||||
data_source: internal_events
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
options:
|
||||
events:
|
||||
- i_quickactions_remove_email_single
|
||||
events:
|
||||
- name: i_quickactions_remove_email_single
|
||||
unique: user.id
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ReplaceFkOnEpicsIssueId < Gitlab::Database::Migration[2.2]
|
||||
disable_ddl_transaction!
|
||||
|
||||
milestone '16.8'
|
||||
|
||||
FK_NAME = :fk_epics_issue_id_with_on_delete_nullify
|
||||
|
||||
def up
|
||||
# This will replace the existing fk_893ee302e5
|
||||
add_concurrent_foreign_key(:epics, :issues, column: :issue_id, on_delete: :nullify, validate: false, name: FK_NAME)
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key_if_exists(:epics, column: :issue_id, on_delete: :nullify, name: FK_NAME)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ValidateFkEpicsIssueIdWithOnDeleteNullify < Gitlab::Database::Migration[2.2]
|
||||
milestone '16.8'
|
||||
|
||||
FK_NAME = :fk_epics_issue_id_with_on_delete_nullify
|
||||
|
||||
# foreign key added in db/migrate/20231227103059_replace_fk_on_epics_issue_id.rb
|
||||
def up
|
||||
validate_foreign_key(:epics, :issue_id, name: FK_NAME)
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveFkEpicsIssueId < Gitlab::Database::Migration[2.2]
|
||||
disable_ddl_transaction!
|
||||
|
||||
milestone '16.8'
|
||||
|
||||
FK_NAME = :fk_893ee302e5
|
||||
|
||||
# new foreign key added in db/migrate/20231227103059_replace_fk_on_epics_issue_id.rb
|
||||
# and validated in db/migrate/20231227104408_validate_fk_epics_issue_id_with_on_delete_nullify.rb
|
||||
def up
|
||||
with_lock_retries do
|
||||
remove_foreign_key_if_exists(:epics, column: :issue_id, on_delete: :cascade, name: FK_NAME)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_foreign_key(:epics, :issues, column: :issue_id, on_delete: :cascade, validate: false, name: FK_NAME)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
d9f963d252141e1fe5bab5dd8f6b67964253788771d45bf343459014864919b5
|
||||
|
|
@ -0,0 +1 @@
|
|||
ffeb813c94ff0fdefae162e32f56083125248e8b3f34535f9f4252dcb09b1412
|
||||
|
|
@ -0,0 +1 @@
|
|||
22e7c4fe8821e07a6ec5c48c32007849faa673eee203689dd51753bf38004077
|
||||
|
|
@ -37895,9 +37895,6 @@ ALTER TABLE ONLY bulk_import_entities
|
|||
ALTER TABLE ONLY requirements_management_test_reports
|
||||
ADD CONSTRAINT fk_88f30752fc FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY epics
|
||||
ADD CONSTRAINT fk_893ee302e5 FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY issues
|
||||
ADD CONSTRAINT fk_899c8f3231 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
@ -38393,6 +38390,9 @@ ALTER TABLE ONLY approval_group_rules_groups
|
|||
ALTER TABLE ONLY emails
|
||||
ADD CONSTRAINT fk_emails_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY epics
|
||||
ADD CONSTRAINT fk_epics_issue_id_with_on_delete_nullify FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY clusters
|
||||
ADD CONSTRAINT fk_f05c5e5a42 FOREIGN KEY (management_project_id) REFERENCES projects(id) ON DELETE SET NULL;
|
||||
|
||||
|
|
|
|||
|
|
@ -687,6 +687,20 @@ Returns [`Organization`](#organization).
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="queryorganizationid"></a>`id` | [`OrganizationsOrganizationID!`](#organizationsorganizationid) | ID of the organization. |
|
||||
|
||||
### `Query.organizations`
|
||||
|
||||
List organizations.
|
||||
|
||||
WARNING:
|
||||
**Introduced** in 16.8.
|
||||
This feature is an Experiment. It can be changed or removed at any time.
|
||||
|
||||
Returns [`OrganizationConnection`](#organizationconnection).
|
||||
|
||||
This field returns a [connection](#connections). It accepts the
|
||||
four standard [pagination arguments](#connection-pagination-arguments):
|
||||
`before: String`, `after: String`, `first: Int`, and `last: Int`.
|
||||
|
||||
### `Query.package`
|
||||
|
||||
Find a package. This field can only be resolved for one query in any single request. Returns `null` if a package has no `default` status.
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ GitLab instances, JSON API, and gRPC differ on these items:
|
|||
| + A new Ruby-gRPC server for vscode: likely faster because we can limit dependencies to load ([modular monolith](https://gitlab.com/gitlab-org/gitlab/-/issues/365293)) | - Existing Grape API for vscode: meaning slow boot time and unneeded resources loaded |
|
||||
| + Bi-directional streaming | - Straight forward way to stream requests and responses (could still be added) |
|
||||
| - A new Python-gRPC server: we don't have experience running gRPC-Python servers | + Existing Python fastapi server, already running for Code Suggestions to extend |
|
||||
| - Hard to pass on unknown messages from vscode through GitLab to ai-gateway | + Easier support for newer vscode + newer ai-gatway, through old GitLab instance |
|
||||
| - Hard to pass on unknown messages from vscode through GitLab to ai-gateway | + Easier support for newer VS Code + newer AI-gateway, through old GitLab instance |
|
||||
| - Unknown support for gRPC in other clients (vscode, jetbrains, other editors) | + Support in all external clients |
|
||||
| - Possible protocol mismatch (VSCode --REST--> Rails --gRPC--> AI gateway) | + Same protocol across the stack |
|
||||
|
||||
|
|
@ -264,7 +264,7 @@ Another example use case includes 2 versions of a prompt passed in the `prompt_c
|
|||
a field in the gateway, and keep them around for at least 2 major
|
||||
versions of GitLab.**
|
||||
|
||||
A good practise that might help support backwards compatibility is to provide building blocks for the prompt inside the `prompt_components` rather then a complete prompt. By moving responsibility of compiling prompt out of building blocks on the AI-Gateway, one can achive more flexibility in terms of prompt adjustments in the future.
|
||||
A good practice that might help support backward compatibility: provide building blocks for the prompt inside the `prompt_components`, rather then a complete prompt. By moving responsibility of compiling the prompt out of building blocks and into the AI-Gateway, more flexible prompt adjustments are possible in the future.
|
||||
|
||||
#### Example feature: Code Suggestions
|
||||
|
||||
|
|
@ -503,7 +503,7 @@ It is deployed to a Kubernetes cluster in it's own project. There is a
|
|||
staging environment that is currently used directly by engineers for
|
||||
testing.
|
||||
|
||||
In the future, this will be deloyed using
|
||||
In the future, this will be deployed using
|
||||
[Runway](https://gitlab.com/gitlab-com/gl-infra/platform/runway/). At
|
||||
that time, there will be a production and staging deployment. The
|
||||
staging deployment can be used for automated QA-runs that will have
|
||||
|
|
|
|||
|
|
@ -65,10 +65,6 @@ Consider updating to Docker `19.03.1` or greater. Older versions are not
|
|||
affected. Read more in
|
||||
[this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/13830#note_211354992 "Current SAST container fails").
|
||||
|
||||
## Getting warning message `gl-dependency-scanning-report.json: no matching files`
|
||||
|
||||
For information, see the [general Application Security troubleshooting section](../../../ci/jobs/job_artifacts_troubleshooting.md#error-message-no-files-to-upload).
|
||||
|
||||
## Limitation when using rules:exists
|
||||
|
||||
The [dependency scanning CI template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml)
|
||||
|
|
|
|||
|
|
@ -162,6 +162,13 @@ per conflicted file on the merge request diff:
|
|||
|
||||

|
||||
|
||||
## Show scanner findings in diff **(ULTIMATE ALL)**
|
||||
|
||||
You can show scanner findings in the diff. For details, see:
|
||||
|
||||
- [Code Quality findings](../../../ci/testing/code_quality.md#merge-request-changes-view)
|
||||
- [Static Analysis findings](../../application_security/sast/index.md#merge-request-changes-view)
|
||||
|
||||
## Add a comment to a merge request file
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123515) in GitLab 16.1 [with a flag](../../../administration/feature_flags.md) named `comment_on_files`. Enabled by default.
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ author can either retry any failed jobs, or push new commits to fix the failure:
|
|||
- If a retried job succeeds on the second try, the merge request is merged.
|
||||
- If new commits are added to the merge request, GitLab cancels the request
|
||||
to ensure the new changes are reviewed before merge.
|
||||
- If new commits are added to the target branch of the merge request and
|
||||
fast-forward only merge request is configured, GitLab cancels the request
|
||||
to prevent merge conflicts.
|
||||
|
||||
## Auto-merge a merge request
|
||||
|
||||
|
|
|
|||
|
|
@ -240,6 +240,26 @@ module Gitlab
|
|||
@execution_message[:invite_email] = response.message
|
||||
end
|
||||
|
||||
desc { _('Remove email participant(s)') }
|
||||
explanation { _('Removes email participant(s).') }
|
||||
params 'email1@example.com email2@example.com (up to 6 emails)'
|
||||
types Issue
|
||||
condition do
|
||||
quick_action_target.persisted? &&
|
||||
Feature.enabled?(:issue_email_participants, parent) &&
|
||||
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target) &&
|
||||
quick_action_target.issue_email_participants.any?
|
||||
end
|
||||
command :remove_email do |emails = ""|
|
||||
response = ::IssueEmailParticipants::DestroyService.new(
|
||||
target: quick_action_target,
|
||||
current_user: current_user,
|
||||
emails: emails.split(' ')
|
||||
).execute
|
||||
|
||||
@execution_message[:remove_email] = response.message
|
||||
end
|
||||
|
||||
desc { _('Promote issue to incident') }
|
||||
explanation { _('Promotes issue to incident') }
|
||||
execution_message { _('Issue has been promoted to incident') }
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ module Gitlab
|
|||
event_name_for_unlabel(args)
|
||||
when 'invite_email'
|
||||
'invite_email' + event_name_quantifier(args.split)
|
||||
when 'remove_email'
|
||||
'remove_email' + event_name_quantifier(args.split)
|
||||
else
|
||||
name
|
||||
end
|
||||
|
|
|
|||
|
|
@ -32229,6 +32229,9 @@ msgstr ""
|
|||
msgid "No email participants were added. Either none were provided, or they already exist."
|
||||
msgstr ""
|
||||
|
||||
msgid "No email participants were removed. Either none were provided, or they don't exist."
|
||||
msgstr ""
|
||||
|
||||
msgid "No endpoint provided"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -40280,6 +40283,9 @@ msgstr ""
|
|||
msgid "Remove due date"
|
||||
msgstr ""
|
||||
|
||||
msgid "Remove email participant(s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Remove epic reference"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -40463,6 +40469,9 @@ msgstr ""
|
|||
msgid "Removes an issue from an epic."
|
||||
msgstr ""
|
||||
|
||||
msgid "Removes email participant(s)."
|
||||
msgstr ""
|
||||
|
||||
msgid "Removes link with %{issue_ref}."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -56189,10 +56198,10 @@ msgstr ""
|
|||
msgid "You don't have any recent searches"
|
||||
msgstr ""
|
||||
|
||||
msgid "You don't have permission to add email participants."
|
||||
msgid "You don't have permission to approve this deployment. Contact the project or group owner for help."
|
||||
msgstr ""
|
||||
|
||||
msgid "You don't have permission to approve this deployment. Contact the project or group owner for help."
|
||||
msgid "You don't have permission to manage email participants."
|
||||
msgstr ""
|
||||
|
||||
msgid "You don't have permission to view this epic"
|
||||
|
|
@ -58870,6 +58879,9 @@ msgstr ""
|
|||
msgid "remove weight"
|
||||
msgstr ""
|
||||
|
||||
msgid "removed %{emails}"
|
||||
msgstr ""
|
||||
|
||||
msgid "removed a %{link_type} link"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ gem 'rspec-retry', '~> 0.6.2', require: 'rspec/retry'
|
|||
gem 'rspec_junit_formatter', '~> 0.6.0'
|
||||
gem 'faker', '~> 3.2', '>= 3.2.2'
|
||||
gem 'knapsack', '~> 4.0'
|
||||
gem 'parallel_tests', '~> 4.3'
|
||||
gem 'parallel_tests', '~> 4.4'
|
||||
gem 'rotp', '~> 6.3.0'
|
||||
gem 'parallel', '~> 1.24'
|
||||
gem 'rainbow', '~> 3.1.1'
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ GEM
|
|||
sawyer (~> 0.9)
|
||||
os (1.1.4)
|
||||
parallel (1.24.0)
|
||||
parallel_tests (4.3.0)
|
||||
parallel_tests (4.4.0)
|
||||
parallel
|
||||
parser (3.2.2.1)
|
||||
ast (~> 2.4.1)
|
||||
|
|
@ -367,7 +367,7 @@ DEPENDENCIES
|
|||
nokogiri (~> 1.15, >= 1.15.5)
|
||||
octokit (~> 8.0.0)
|
||||
parallel (~> 1.24)
|
||||
parallel_tests (~> 4.3)
|
||||
parallel_tests (~> 4.4)
|
||||
pry-byebug (~> 3.10.1)
|
||||
rainbow (~> 3.1.1)
|
||||
rake (~> 13, >= 13.1.0)
|
||||
|
|
@ -385,4 +385,4 @@ DEPENDENCIES
|
|||
zeitwerk (~> 2.6, >= 2.6.12)
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.1
|
||||
2.5.2
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe IssueEmailParticipant do
|
||||
RSpec.describe IssueEmailParticipant, feature_category: :service_desk do
|
||||
describe "Associations" do
|
||||
it { is_expected.to belong_to(:issue) }
|
||||
end
|
||||
|
|
@ -27,4 +27,18 @@ RSpec.describe IssueEmailParticipant do
|
|||
expect(subject).to be_invalid
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Scopes' do
|
||||
describe '.with_emails' do
|
||||
let!(:participant) { create(:issue_email_participant, email: 'user@example.com') }
|
||||
let!(:participant1) { create(:issue_email_participant, email: 'user1@example.com') }
|
||||
let!(:participant2) { create(:issue_email_participant, email: 'user2@example.com') }
|
||||
|
||||
it 'returns only participant with matching emails' do
|
||||
expect(described_class.with_emails([participant.email, participant1.email])).to match_array(
|
||||
[participant, participant1]
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'getting organizations information', feature_category: :cell do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
let(:query) { graphql_query_for(:organizations, organizations_fields) }
|
||||
let(:organizations) { graphql_data_at(:organizations, :nodes) }
|
||||
let(:organizations_fields) do
|
||||
<<~FIELDS
|
||||
nodes {
|
||||
id
|
||||
path
|
||||
}
|
||||
FIELDS
|
||||
end
|
||||
|
||||
before_all { create_list(:organization, 3) }
|
||||
|
||||
subject(:request_organization) { post_graphql(query, current_user: current_user) }
|
||||
|
||||
context 'without authenticated user' do
|
||||
let(:current_user) { nil }
|
||||
|
||||
it_behaves_like 'a working graphql query' do
|
||||
before do
|
||||
request_organization
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with authenticated user' do
|
||||
let(:current_user) { user }
|
||||
|
||||
it_behaves_like 'a working graphql query' do
|
||||
before do
|
||||
request_organization
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'sorted paginated query' do
|
||||
include_context 'no sort argument'
|
||||
|
||||
let(:first_param) { 2 }
|
||||
let(:data_path) { [:organizations] }
|
||||
let(:all_records) { Organizations::Organization.order(id: :desc).map { |o| global_id_of(o).to_s } }
|
||||
end
|
||||
|
||||
def pagination_query(params)
|
||||
graphql_query_for(:organizations, params, "#{page_info} nodes { id }")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -41,8 +41,8 @@ RSpec.describe IssueEmailParticipants::CreateService, feature_category: :service
|
|||
let(:expected_emails) { emails }
|
||||
|
||||
let(:error_feature_flag) { "Feature flag issue_email_participants is not enabled for this project." }
|
||||
let(:error_underprivileged) { _("You don't have permission to add email participants.") }
|
||||
let(:error_no_participants) do
|
||||
let(:error_underprivileged) { _("You don't have permission to manage email participants.") }
|
||||
let(:error_no_participants_added) do
|
||||
_("No email participants were added. Either none were provided, or they already exist.")
|
||||
end
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ RSpec.describe IssueEmailParticipants::CreateService, feature_category: :service
|
|||
end
|
||||
|
||||
context 'when no emails are provided' do
|
||||
let(:error_message) { error_no_participants }
|
||||
let(:error_message) { error_no_participants_added }
|
||||
|
||||
it_behaves_like 'a failed service execution'
|
||||
end
|
||||
|
|
@ -69,7 +69,7 @@ RSpec.describe IssueEmailParticipants::CreateService, feature_category: :service
|
|||
it_behaves_like 'a successful service execution'
|
||||
|
||||
context 'when email is already a participant of the issue' do
|
||||
let(:error_message) { error_no_participants }
|
||||
let(:error_message) { error_no_participants_added }
|
||||
|
||||
before do
|
||||
issue.issue_email_participants.create!(email: emails.first)
|
||||
|
|
@ -89,7 +89,7 @@ RSpec.describe IssueEmailParticipants::CreateService, feature_category: :service
|
|||
end
|
||||
|
||||
let(:emails) { ['over-max@example.com'] }
|
||||
let(:error_message) { error_no_participants }
|
||||
let(:error_message) { error_no_participants_added }
|
||||
|
||||
it_behaves_like 'a failed service execution'
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,147 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe IssueEmailParticipants::DestroyService, feature_category: :service_desk do
|
||||
shared_examples 'a successful service execution' do
|
||||
it 'removes participants', :aggregate_failures do
|
||||
expect(response).to be_success
|
||||
|
||||
issue.reset
|
||||
note = issue.notes.last
|
||||
expect(note.system?).to be true
|
||||
expect(note.author).to eq(user)
|
||||
|
||||
participants_emails = issue.email_participants_emails_downcase
|
||||
|
||||
expected_emails.each do |email|
|
||||
expect(participants_emails).not_to include(email)
|
||||
expect(response.message).to include(email)
|
||||
expect(note.note).to include(email)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'a failed service execution' do
|
||||
it 'returns error ServiceResponse with message', :aggregate_failures do
|
||||
expect(response).to be_error
|
||||
expect(response.message).to eq(error_message)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
let_it_be_with_reload(:project) { create(:project) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be_with_reload(:issue) { create(:issue, project: project) }
|
||||
|
||||
let(:emails) { nil }
|
||||
let(:service) { described_class.new(target: issue, current_user: user, emails: emails) }
|
||||
let(:expected_emails) { emails }
|
||||
|
||||
let(:error_feature_flag) { "Feature flag issue_email_participants is not enabled for this project." }
|
||||
let(:error_underprivileged) { _("You don't have permission to manage email participants.") }
|
||||
let(:error_no_participants_removed) do
|
||||
_("No email participants were removed. Either none were provided, or they don't exist.")
|
||||
end
|
||||
|
||||
subject(:response) { service.execute }
|
||||
|
||||
context 'when the user is not a project member' do
|
||||
let(:error_message) { error_underprivileged }
|
||||
|
||||
it_behaves_like 'a failed service execution'
|
||||
end
|
||||
|
||||
context 'when user has reporter role in project' do
|
||||
before_all do
|
||||
project.add_reporter(user)
|
||||
end
|
||||
|
||||
context 'when no emails are provided' do
|
||||
let(:error_message) { error_no_participants_removed }
|
||||
|
||||
it_behaves_like 'a failed service execution'
|
||||
end
|
||||
|
||||
context 'when one email is provided' do
|
||||
let(:emails) { ['user@example.com'] }
|
||||
let(:error_message) { error_no_participants_removed }
|
||||
|
||||
it_behaves_like 'a failed service execution'
|
||||
|
||||
context 'when email is a participant of the issue' do
|
||||
before do
|
||||
issue.issue_email_participants.create!(email: 'user@example.com')
|
||||
end
|
||||
|
||||
it_behaves_like 'a successful service execution'
|
||||
|
||||
context 'when email is formatted in a different case' do
|
||||
let(:emails) { ['USER@example.com'] }
|
||||
let(:expected_emails) { emails.map(&:downcase) }
|
||||
let(:error_message) { error_no_participants_removed }
|
||||
|
||||
it_behaves_like 'a successful service execution'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when multiple emails are provided' do
|
||||
let(:emails) { ['user@example.com', 'user2@example.com'] }
|
||||
let(:error_message) { error_no_participants_removed }
|
||||
|
||||
it_behaves_like 'a failed service execution'
|
||||
|
||||
context 'when duplicate email provided' do
|
||||
let(:emails) { ['user@example.com', 'user@example.com'] }
|
||||
let(:expected_emails) { emails[...-1] }
|
||||
|
||||
it_behaves_like 'a failed service execution'
|
||||
end
|
||||
|
||||
context 'when one email is a participant of the issue' do
|
||||
let(:expected_emails) { emails[...-1] }
|
||||
|
||||
before do
|
||||
issue.issue_email_participants.create!(email: emails.first)
|
||||
end
|
||||
|
||||
it_behaves_like 'a successful service execution'
|
||||
end
|
||||
|
||||
context 'when both emails are a participant of the issue' do
|
||||
before do
|
||||
emails.each do |email|
|
||||
issue.issue_email_participants.create!(email: email)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'a successful service execution'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when more than the allowed number of emails are provided' do
|
||||
let(:emails) { (1..7).map { |i| "user#{i}@example.com" } }
|
||||
let(:expected_emails) { emails[...-1] }
|
||||
|
||||
before do
|
||||
emails.each do |email|
|
||||
issue.issue_email_participants.create!(email: email)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'a successful service execution'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature flag issue_email_participants is disabled' do
|
||||
let(:error_message) { error_feature_flag }
|
||||
|
||||
before do
|
||||
stub_feature_flags(issue_email_participants: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'a failed service execution'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2324,7 +2324,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
end
|
||||
end
|
||||
|
||||
context 'invite_email command' do
|
||||
describe 'invite_email command' do
|
||||
let_it_be(:issuable) { issue }
|
||||
|
||||
it_behaves_like 'failed command', "No email participants were added. Either none were provided, or they already exist." do
|
||||
|
|
@ -2454,6 +2454,102 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
end
|
||||
end
|
||||
|
||||
describe 'remove_email command' do
|
||||
let_it_be_with_reload(:issuable) { issue }
|
||||
|
||||
it 'is not part of the available commands' do
|
||||
expect(service.available_commands(issuable)).not_to include(a_hash_including(name: :remove_email))
|
||||
end
|
||||
|
||||
context 'with existing email participant' do
|
||||
let(:content) { '/remove_email user@example.com' }
|
||||
|
||||
subject(:remove_email) { service.execute(content, issuable) }
|
||||
|
||||
before do
|
||||
issuable.issue_email_participants.create!(email: "user@example.com")
|
||||
end
|
||||
|
||||
it 'returns message' do
|
||||
_, _, message = service.execute(content, issuable)
|
||||
|
||||
expect(message).to eq('Removed user@example.com.')
|
||||
end
|
||||
|
||||
it 'removes 1 participant' do
|
||||
expect { remove_email }.to change { issue.issue_email_participants.count }.by(-1)
|
||||
end
|
||||
|
||||
context 'with mixed case email' do
|
||||
let(:content) { '/remove_email FirstLast@GitLab.com' }
|
||||
|
||||
before do
|
||||
issuable.issue_email_participants.create!(email: "FirstLast@GitLab.com")
|
||||
end
|
||||
|
||||
it 'returns correctly cased message' do
|
||||
_, _, message = service.execute(content, issuable)
|
||||
|
||||
expect(message).to eq('Removed FirstLast@GitLab.com.')
|
||||
end
|
||||
|
||||
it 'removes 1 participant' do
|
||||
expect { remove_email }.to change { issue.issue_email_participants.count }.by(-1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid email' do
|
||||
let(:content) { '/remove_email user@example.com bad_email' }
|
||||
|
||||
it 'only removes valid emails' do
|
||||
expect { remove_email }.to change { issue.issue_email_participants.count }.by(-1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with non-existing email address' do
|
||||
let(:content) { '/remove_email NonExistent@gitlab.com' }
|
||||
|
||||
it 'returns message' do
|
||||
_, _, message = service.execute(content, issuable)
|
||||
|
||||
expect(message).to eq("No email participants were removed. Either none were provided, or they don't exist.")
|
||||
end
|
||||
end
|
||||
|
||||
context 'with more than the max number of emails' do
|
||||
let(:content) { '/remove_email user@example.com user1@example.com' }
|
||||
|
||||
before do
|
||||
stub_const("IssueEmailParticipants::DestroyService::MAX_NUMBER_OF_EMAILS", 1)
|
||||
# user@example.com has already been added above
|
||||
issuable.issue_email_participants.create!(email: "user1@example.com")
|
||||
end
|
||||
|
||||
it 'only removes the max allowed number of emails' do
|
||||
expect { remove_email }.to change { issue.issue_email_participants.count }.by(-1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with non-persisted issue' do
|
||||
let(:issuable) { build(:issue) }
|
||||
|
||||
it 'is not part of the available commands' do
|
||||
expect(service.available_commands(issuable)).not_to include(a_hash_including(name: :remove_email))
|
||||
end
|
||||
end
|
||||
|
||||
context 'with feature flag disabled' do
|
||||
before do
|
||||
stub_feature_flags(issue_email_participants: false)
|
||||
end
|
||||
|
||||
it 'is not part of the available commands' do
|
||||
expect(service.available_commands(issuable)).not_to include(a_hash_including(name: :remove_email))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'severity command' do
|
||||
let_it_be_with_reload(:issuable) { create(:incident, project: project) }
|
||||
|
||||
|
|
|
|||
|
|
@ -515,6 +515,18 @@ RSpec.describe SystemNoteService, feature_category: :shared do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.email_participants' do
|
||||
let(:body) { 'added user@example.com' }
|
||||
|
||||
it 'calls IssuableService' do
|
||||
expect_next_instance_of(::SystemNotes::IssuablesService) do |service|
|
||||
expect(service).to receive(:email_participants).with(body)
|
||||
end
|
||||
|
||||
described_class.email_participants(noteable, project, author, body)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.discussion_lock' do
|
||||
let(:issuable) { double }
|
||||
|
||||
|
|
|
|||
|
|
@ -770,6 +770,14 @@ RSpec.describe ::SystemNotes::IssuablesService, feature_category: :team_planning
|
|||
end
|
||||
end
|
||||
|
||||
describe '#email_participants' do
|
||||
let(:body) { "added user@example.com" }
|
||||
|
||||
subject(:system_note) { service.email_participants(body) }
|
||||
|
||||
it { expect(system_note.note).to eq(body) }
|
||||
end
|
||||
|
||||
describe '#discussion_lock' do
|
||||
subject { service.discussion_lock }
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ RSpec.shared_context 'with FOSS query type fields' do
|
|||
:milestone,
|
||||
:namespace,
|
||||
:note,
|
||||
:organization,
|
||||
:organizations,
|
||||
:package,
|
||||
:project,
|
||||
:projects,
|
||||
|
|
|
|||
Loading…
Reference in New Issue