diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml
index fe7b3e6d591..18c4d450ef3 100644
--- a/.rubocop_todo/layout/line_length.yml
+++ b/.rubocop_todo/layout/line_length.yml
@@ -3499,7 +3499,6 @@ Layout/LineLength:
- 'spec/models/postgresql/replication_slot_spec.rb'
- 'spec/models/preloaders/environments/deployment_preloader_spec.rb'
- 'spec/models/preloaders/group_policy_preloader_spec.rb'
- - 'spec/models/preloaders/group_root_ancestor_preloader_spec.rb'
- 'spec/models/preloaders/user_max_access_level_in_groups_preloader_spec.rb'
- 'spec/models/project_authorization_spec.rb'
- 'spec/models/project_feature_spec.rb'
diff --git a/.rubocop_todo/rspec/feature_category.yml b/.rubocop_todo/rspec/feature_category.yml
index 58154511eb9..69709584641 100644
--- a/.rubocop_todo/rspec/feature_category.yml
+++ b/.rubocop_todo/rspec/feature_category.yml
@@ -3374,7 +3374,6 @@ RSpec/FeatureCategory:
- 'spec/models/preloaders/commit_status_preloader_spec.rb'
- 'spec/models/preloaders/environments/deployment_preloader_spec.rb'
- 'spec/models/preloaders/group_policy_preloader_spec.rb'
- - 'spec/models/preloaders/group_root_ancestor_preloader_spec.rb'
- 'spec/models/preloaders/labels_preloader_spec.rb'
- 'spec/models/preloaders/merge_request_diff_preloader_spec.rb'
- 'spec/models/preloaders/project_policy_preloader_spec.rb'
diff --git a/app/assets/javascripts/graphql_shared/possible_types.json b/app/assets/javascripts/graphql_shared/possible_types.json
index aa2028c4380..c581aae59ed 100644
--- a/app/assets/javascripts/graphql_shared/possible_types.json
+++ b/app/assets/javascripts/graphql_shared/possible_types.json
@@ -281,6 +281,7 @@
"WorkItemWidgetDesigns",
"WorkItemWidgetDevelopment",
"WorkItemWidgetEmailParticipants",
+ "WorkItemWidgetErrorTracking",
"WorkItemWidgetHealthStatus",
"WorkItemWidgetHierarchy",
"WorkItemWidgetIteration",
diff --git a/app/graphql/types/work_items/widget_interface.rb b/app/graphql/types/work_items/widget_interface.rb
index 573fff53f2e..abf30bb21c1 100644
--- a/app/graphql/types/work_items/widget_interface.rb
+++ b/app/graphql/types/work_items/widget_interface.rb
@@ -34,7 +34,8 @@ module Types
::WorkItems::Widgets::CrmContacts => ::Types::WorkItems::Widgets::CrmContactsType,
::WorkItems::Widgets::EmailParticipants => ::Types::WorkItems::Widgets::EmailParticipantsType,
::WorkItems::Widgets::CustomStatus => ::Types::WorkItems::Widgets::CustomStatusType,
- ::WorkItems::Widgets::LinkedResources => ::Types::WorkItems::Widgets::LinkedResourcesType
+ ::WorkItems::Widgets::LinkedResources => ::Types::WorkItems::Widgets::LinkedResourcesType,
+ ::WorkItems::Widgets::ErrorTracking => ::Types::WorkItems::Widgets::ErrorTrackingType
}.freeze
def self.type_mappings
diff --git a/app/graphql/types/work_items/widgets/error_tracking_type.rb b/app/graphql/types/work_items/widgets/error_tracking_type.rb
new file mode 100644
index 00000000000..02cfe3bbdfc
--- /dev/null
+++ b/app/graphql/types/work_items/widgets/error_tracking_type.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ module Widgets
+ # Disabling widget level authorization as it might be too granular
+ # and we already authorize the parent work item
+ # rubocop:disable Graphql/AuthorizeTypes -- reason above
+ class ErrorTrackingType < BaseObject
+ graphql_name 'WorkItemWidgetErrorTracking'
+ description 'Represents the error tracking widget'
+
+ implements ::Types::WorkItems::WidgetInterface
+
+ field :identifier, GraphQL::Types::BigInt, null: true,
+ description: 'Error tracking issue id.', method: :sentry_issue_identifier
+ end
+ # rubocop:enable Graphql/AuthorizeTypes
+ end
+ end
+end
diff --git a/app/models/group.rb b/app/models/group.rb
index aa37ae26a00..a2fd4d4a1f3 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -898,6 +898,10 @@ class Group < Namespace
# TODO: group hooks https://gitlab.com/gitlab-org/gitlab/-/issues/216904
end
+ def find_or_initialize_integration(integration)
+ Integration.find_or_initialize_non_project_specific_integration(integration, group_id: id)
+ end
+
def execute_integrations(data, hooks_scope)
integrations.public_send(hooks_scope).each do |integration| # rubocop:disable GitlabSecurity/PublicSend
integration.async_execute(data)
diff --git a/app/models/work_items/widget_definition.rb b/app/models/work_items/widget_definition.rb
index 0fe67c848ec..fa2c602a3c7 100644
--- a/app/models/work_items/widget_definition.rb
+++ b/app/models/work_items/widget_definition.rb
@@ -44,7 +44,8 @@ module WorkItems
email_participants: 25,
custom_status: 26,
linked_resources: 27,
- custom_fields: 28 # EE-only
+ custom_fields: 28, # EE-only
+ error_tracking: 29
}
attribute :widget_options, ::Gitlab::Database::Type::IndifferentJsonb.new
diff --git a/app/models/work_items/widgets/error_tracking.rb b/app/models/work_items/widgets/error_tracking.rb
new file mode 100644
index 00000000000..16d75efe1af
--- /dev/null
+++ b/app/models/work_items/widgets/error_tracking.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module WorkItems
+ module Widgets
+ class ErrorTracking < Base
+ delegate :sentry_issue, to: :work_item, allow_nil: true
+
+ delegate :sentry_issue_identifier, to: :sentry_issue, allow_nil: true
+ end
+ end
+end
diff --git a/app/services/integrations/update_service.rb b/app/services/integrations/update_service.rb
index ba8af9c5cfb..ba5a547814e 100644
--- a/app/services/integrations/update_service.rb
+++ b/app/services/integrations/update_service.rb
@@ -14,11 +14,15 @@ module Integrations
def execute
return error('Integration not found.', :not_found) unless integration
- if handle_inherited_settings?
- handle_inherited_settings
- else
- handle_default_settings
- end
+ response = if handle_inherited_settings?
+ handle_inherited_settings
+ else
+ handle_default_settings
+ end
+
+ PropagateIntegrationWorker.perform_async(integration.id) unless response.error? || integration.project_level?
+
+ response
end
private
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index eec89adfb60..4a19069b279 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -97,8 +97,7 @@ module Notes
end
end
- def when_saved(
- note, skip_capture_diff_note_position: false, skip_merge_status_trigger: false)
+ def when_saved(note, skip_capture_diff_note_position: false, skip_merge_status_trigger: false)
todo_service.new_note(note, current_user)
clear_noteable_diffs_cache(note)
Suggestions::CreateService.new(note).execute
diff --git a/app/services/work_items/data_sync/widgets/error_tracking.rb b/app/services/work_items/data_sync/widgets/error_tracking.rb
new file mode 100644
index 00000000000..684d89a4de3
--- /dev/null
+++ b/app/services/work_items/data_sync/widgets/error_tracking.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module WorkItems
+ module DataSync
+ module Widgets
+ class ErrorTracking < Base
+ # Placeholder, see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/174793#note_2246113161
+ # Need this class to make spec pass at https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/spec/models/ee/work_items/widget_definition_spec.rb#L49
+ end
+ end
+ end
+end
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index facf8ae9442..c090be27050 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -1000,7 +1000,7 @@ Gitlab.ee do
Settings.cron_jobs['gitlab_subscriptions_schedule_refresh_seats_worker']['job_class'] = 'GitlabSubscriptions::ScheduleRefreshSeatsWorker'
Settings.cron_jobs['namespaces_schedule_dormant_member_removal_worker'] ||= {}
Settings.cron_jobs['namespaces_schedule_dormant_member_removal_worker']['cron'] ||= "0 */6 * * *"
- Settings.cron_jobs['namespaces_schedule_dormant_member_removal_worker']['job_class'] = 'Namespaces::ScheduleDormantMemberRemoval'
+ Settings.cron_jobs['namespaces_schedule_dormant_member_removal_worker']['job_class'] = 'Namespaces::ScheduleDormantMemberRemovalWorker'
Settings.cron_jobs['gitlab_subscriptions_offline_cloud_license_provision_worker']['status'] = 'disabled'
Settings.cron_jobs['send_recurring_notifications_worker'] ||= {}
Settings.cron_jobs['send_recurring_notifications_worker']['cron'] ||= '0 7 * * *'
diff --git a/db/migrate/20250128163345_add_error_tracking_to_work_item_widget.rb b/db/migrate/20250128163345_add_error_tracking_to_work_item_widget.rb
new file mode 100644
index 00000000000..98b542753f7
--- /dev/null
+++ b/db/migrate/20250128163345_add_error_tracking_to_work_item_widget.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class AddErrorTrackingToWorkItemWidget < Gitlab::Database::Migration[2.2]
+ include Gitlab::Database::MigrationHelpers::WorkItems::Widgets
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+ disable_ddl_transaction!
+ milestone '17.9'
+
+ WORK_ITEM_TYPE_ENUM_VALUE = 0 # issue
+ WIDGETS = [
+ {
+ name: 'Error Tracking',
+ widget_type: 29
+ }
+ ]
+
+ def up
+ add_widget_definitions(type_enum_value: WORK_ITEM_TYPE_ENUM_VALUE, widgets: WIDGETS)
+ end
+
+ def down
+ remove_widget_definitions(type_enum_value: WORK_ITEM_TYPE_ENUM_VALUE, widgets: WIDGETS)
+ end
+end
diff --git a/db/schema_migrations/20250128163345 b/db/schema_migrations/20250128163345
new file mode 100644
index 00000000000..ad6ef33b055
--- /dev/null
+++ b/db/schema_migrations/20250128163345
@@ -0,0 +1 @@
+5b03997dd2a4dd27bdf78aa63130438cffcbc37b8234225c1cf780e599e25f09
\ No newline at end of file
diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md
index 03abc562315..dcb66adc088 100644
--- a/doc/api/graphql/reference/_index.md
+++ b/doc/api/graphql/reference/_index.md
@@ -12014,6 +12014,7 @@ Input type: `WorkItemCreateInput`
| `projectPath` **{warning-solid}** | [`ID`](#id) | **Deprecated:** Please use namespacePath instead. That will cover for both projects and groups. Deprecated in GitLab 15.10. |
| `startAndDueDateWidget` | [`WorkItemWidgetStartAndDueDateUpdateInput`](#workitemwidgetstartandduedateupdateinput) | Input for start and due date widget. |
| `title` | [`String!`](#string) | Title of the work item. |
+| `vulnerabilityId` **{warning-solid}** | [`VulnerabilityID`](#vulnerabilityid) | **Deprecated:** **Status**: Experiment. Introduced in GitLab 17.9. |
| `weightWidget` | [`WorkItemWidgetWeightInput`](#workitemwidgetweightinput) | Input for weight widget. |
| `workItemTypeId` | [`WorkItemsTypeID!`](#workitemstypeid) | Global ID of a work item type. |
@@ -39278,6 +39279,17 @@ Represents email participants widget.
| `emailParticipants` | [`EmailParticipantTypeConnection`](#emailparticipanttypeconnection) | Collection of email participants associated with the work item. (see [Connections](#connections)) |
| `type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. |
+### `WorkItemWidgetErrorTracking`
+
+Represents the error tracking widget.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `identifier` | [`BigInt`](#bigint) | Error tracking issue id. |
+| `type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. |
+
### `WorkItemWidgetHealthStatus`
Represents a health status widget.
@@ -43395,6 +43407,7 @@ Type of a work item widget.
| `DESIGNS` | Designs widget. |
| `DEVELOPMENT` | Development widget. |
| `EMAIL_PARTICIPANTS` | Email Participants widget. |
+| `ERROR_TRACKING` | Error Tracking widget. |
| `HEALTH_STATUS` | Health Status widget. |
| `HIERARCHY` | Hierarchy widget. |
| `ITERATION` | Iteration widget. |
@@ -45790,6 +45803,7 @@ Implementations:
- [`WorkItemWidgetDesigns`](#workitemwidgetdesigns)
- [`WorkItemWidgetDevelopment`](#workitemwidgetdevelopment)
- [`WorkItemWidgetEmailParticipants`](#workitemwidgetemailparticipants)
+- [`WorkItemWidgetErrorTracking`](#workitemwidgeterrortracking)
- [`WorkItemWidgetHealthStatus`](#workitemwidgethealthstatus)
- [`WorkItemWidgetHierarchy`](#workitemwidgethierarchy)
- [`WorkItemWidgetIteration`](#workitemwidgetiteration)
diff --git a/doc/api/group_integrations.md b/doc/api/group_integrations.md
new file mode 100644
index 00000000000..638b6e997ab
--- /dev/null
+++ b/doc/api/group_integrations.md
@@ -0,0 +1,1968 @@
+---
+stage: Foundations
+group: Import and Integrate
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
+title: Group integrations API
+---
+
+DETAILS:
+**Tier:** Free, Premium, Ultimate
+**Offering:** GitLab.com, GitLab Self-Managed, GitLab Dedicated
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/328496) in GitLab 17.9.
+
+Use this API to work with external services that integrate with GitLab.
+
+This API requires an access token with the Maintainer or Owner role.
+
+## List all active integrations
+
+Get a list of all active group integrations. The `vulnerability_events` field is only available for GitLab Enterprise Edition.
+
+```plaintext
+GET /groups/:id/integrations
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 75,
+ "title": "Jenkins CI",
+ "slug": "jenkins",
+ "created_at": "2019-11-20T11:20:25.297Z",
+ "updated_at": "2019-11-20T12:24:37.498Z",
+ "active": true,
+ "commit_events": true,
+ "push_events": true,
+ "issues_events": true,
+ "alert_events": true,
+ "confidential_issues_events": true,
+ "merge_requests_events": true,
+ "tag_push_events": false,
+ "deployment_events": false,
+ "note_events": true,
+ "confidential_note_events": true,
+ "pipeline_events": true,
+ "wiki_page_events": true,
+ "job_events": true,
+ "comment_on_event_enabled": true,
+ "inherited": false,
+ "vulnerability_events": true
+ },
+ {
+ "id": 76,
+ "title": "Alerts endpoint",
+ "slug": "alerts",
+ "created_at": "2019-11-20T11:20:25.297Z",
+ "updated_at": "2019-11-20T12:24:37.498Z",
+ "active": true,
+ "commit_events": true,
+ "push_events": true,
+ "issues_events": true,
+ "alert_events": true,
+ "confidential_issues_events": true,
+ "merge_requests_events": true,
+ "tag_push_events": true,
+ "deployment_events": false,
+ "note_events": true,
+ "confidential_note_events": true,
+ "pipeline_events": true,
+ "wiki_page_events": true,
+ "job_events": true,
+ "comment_on_event_enabled": true,
+ "inherited": false,
+ "vulnerability_events": true
+ }
+]
+```
+
+## Asana
+
+### Set up Asana
+
+Set up the Asana integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/asana
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `api_key` | string | yes | User API token. The user must have access to the task. All comments are attributed to this user. |
+| `restrict_to_branch` | string | no | Comma-separated list of branches to be automatically inspected. Leave blank to include all branches. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Asana
+
+Disable the Asana integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/asana
+```
+
+### Get Asana settings
+
+Get the Asana integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/asana
+```
+
+## Assembla
+
+### Set up Assembla
+
+Set up the Assembla integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/assembla
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `token` | string | yes | The authentication token. |
+| `subdomain` | string | no | The subdomain setting. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Assembla
+
+Disable the Assembla integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/assembla
+```
+
+### Get Assembla settings
+
+Get the Assembla integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/assembla
+```
+
+## Atlassian Bamboo
+
+### Set up Atlassian Bamboo
+
+Set up the Atlassian Bamboo integration for a group.
+
+You must configure automatic revision labeling and a repository trigger in Bamboo.
+
+```plaintext
+PUT /groups/:id/integrations/bamboo
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `bamboo_url` | string | yes | Bamboo root URL (for example, `https://bamboo.example.com`). |
+| `enable_ssl_verification` | boolean | no | Enable SSL verification. Defaults to `true` (enabled). |
+| `build_key` | string | yes | Bamboo build plan key (for example, `KEY`). |
+| `username` | string | yes | User with API access to the Bamboo server. |
+| `password` | string | yes | Password of the user. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Atlassian Bamboo
+
+Disable the Atlassian Bamboo integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/bamboo
+```
+
+### Get Atlassian Bamboo settings
+
+Get the Atlassian Bamboo integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/bamboo
+```
+
+## Bugzilla
+
+### Set up Bugzilla
+
+Set up the Bugzilla integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/bugzilla
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `new_issue_url` | string | yes | URL of the new issue. |
+| `issues_url` | string | yes | URL of the issue. |
+| `project_url` | string | yes | URL of the project. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Bugzilla
+
+Disable the Bugzilla integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/bugzilla
+```
+
+### Get Bugzilla settings
+
+Get the Bugzilla integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/bugzilla
+```
+
+## Buildkite
+
+### Set up Buildkite
+
+Set up the Buildkite integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/buildkite
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `token` | string | yes | Buildkite project GitLab token. |
+| `project_url` | string | yes | Pipeline URL (for example, `https://buildkite.com/example/pipeline`). |
+| `enable_ssl_verification` | boolean | no | **Deprecated:** This parameter has no effect because SSL verification is always enabled. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Buildkite
+
+Disable the Buildkite integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/buildkite
+```
+
+### Get Buildkite settings
+
+Get the Buildkite integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/buildkite
+```
+
+## Campfire Classic
+
+You can integrate with Campfire Classic. However, Campfire Classic is an old product that is
+[no longer sold](https://gitlab.com/gitlab-org/gitlab/-/issues/329337) by Basecamp.
+
+### Set up Campfire Classic
+
+Set up the Campfire Classic integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/campfire
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+|---------------|---------|----------|---------------------------------------------------------------------------------------------|
+| `token` | string | yes | API authentication token from Campfire Classic. To get the token, sign in to Campfire Classic and select **My info**. |
+| `subdomain` | string | no | `.campfirenow.com` subdomain when you're signed in. |
+| `room` | string | no | ID portion of the Campfire Classic room URL. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Campfire Classic
+
+Disable the Campfire Classic integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/campfire
+```
+
+### Get Campfire Classic settings
+
+Get the Campfire Classic integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/campfire
+```
+
+## ClickUp
+
+### Set up ClickUp
+
+Set up the ClickUp integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/clickup
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| ------------- | ------ | -------- | -------------- |
+| `issues_url` | string | yes | URL of the issue. |
+| `project_url` | string | yes | URL of the project. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable ClickUp
+
+Disable the ClickUp integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/clickup
+```
+
+### Get ClickUp settings
+
+Get the ClickUp integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/clickup
+```
+
+## Confluence Workspace
+
+### Set up Confluence Workspace
+
+Set up the Confluence Workspace integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/confluence
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `confluence_url` | string | yes | URL of the Confluence Workspace hosted on `atlassian.net`. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Confluence Workspace
+
+Disable the Confluence Workspace integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/confluence
+```
+
+### Get Confluence Workspace settings
+
+Get the Confluence Workspace integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/confluence
+```
+
+## Custom issue tracker
+
+### Set up a custom issue tracker
+
+Set up a custom issue tracker for a group.
+
+```plaintext
+PUT /groups/:id/integrations/custom-issue-tracker
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `new_issue_url` | string | yes | URL of the new issue. |
+| `issues_url` | string | yes | URL of the issue. |
+| `project_url` | string | yes | URL of the project. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable a custom issue tracker
+
+Disable a custom issue tracker for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/custom-issue-tracker
+```
+
+### Get custom issue tracker settings
+
+Get the custom issue tracker settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/custom-issue-tracker
+```
+
+## Datadog
+
+### Set up Datadog
+
+Set up the Datadog integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/datadog
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+|------------------------|---------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `api_key` | string | yes | API key used for authentication with Datadog. |
+| `api_url` | string | no | (Advanced) The full URL for your Datadog site. |
+| `datadog_env` | string | no | For self-managed deployments, set the `env%` tag for all the data sent to Datadog. |
+| `datadog_service` | string | no | Tag all data from this GitLab instance in Datadog. Can be used when managing several self-managed deployments. |
+| `datadog_site` | string | no | The Datadog site to send data to. To send data to the EU site, use `datadoghq.eu`. |
+| `datadog_tags` | string | no | Custom tags in Datadog. Specify one tag per line in the format `key:value\nkey2:value2` |
+| `archive_trace_events` | boolean | no | When enabled, job logs are collected by Datadog and displayed along with pipeline execution traces. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Datadog
+
+Disable the Datadog integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/datadog
+```
+
+### Get Datadog settings
+
+Get the Datadog integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/datadog
+```
+
+## Diffblue Cover
+
+### Set up Diffblue Cover
+
+Set up the Diffblue Cover integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/diffblue-cover
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `diffblue_license_key` | string | yes | Diffblue Cover license key. |
+| `diffblue_access_token_name` | string | yes | Access token name used by Diffblue Cover in pipelines. |
+| `diffblue_access_token_secret` | string | yes | Access token secret used by Diffblue Cover in pipelines. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Diffblue Cover
+
+Disable the Diffblue Cover integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/diffblue-cover
+```
+
+### Get Diffblue Cover settings
+
+Get the Diffblue Cover integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/diffblue-cover
+```
+
+## Discord Notifications
+
+### Set up Discord Notifications
+
+Set up Discord Notifications for a group.
+
+```plaintext
+PUT /groups/:id/integrations/discord
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `webhook` | string | yes | Discord webhook (for example, `https://discord.com/api/webhooks/...`). |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. |
+| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. |
+| `confidential_issue_channel` | string | no | The webhook override to receive notifications for confidential issue events. |
+| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. |
+| `confidential_note_channel` | string | no | The webhook override to receive notifications for confidential note events. |
+| `deployment_events` | boolean | no | Enable notifications for deployment events. |
+| `deployment_channel` | string | no | The webhook override to receive notifications for deployment events. |
+| `group_confidential_mentions_events` | boolean | no | Enable notifications for group confidential mention events. |
+| `group_confidential_mentions_channel` | string | no | The webhook override to receive notifications for group confidential mention events. |
+| `group_mentions_events` | boolean | no | Enable notifications for group mention events. |
+| `group_mentions_channel` | string | no | The webhook override to receive notifications for group mention events. |
+| `issues_events` | boolean | no | Enable notifications for issue events. |
+| `issue_channel` | string | no | The webhook override to receive notifications for issue events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `merge_request_channel` | string | no | The webhook override to receive notifications for merge request events. |
+| `note_events` | boolean | no | Enable notifications for note events. |
+| `note_channel` | string | no | The webhook override to receive notifications for note events. |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `pipeline_events` | boolean | no | Enable notifications for pipeline events. |
+| `pipeline_channel` | string | no | The webhook override to receive notifications for pipeline events. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `push_channel` | string | no | The webhook override to receive notifications for push events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `tag_push_channel` | string | no | The webhook override to receive notifications for tag push events. |
+| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. |
+| `wiki_page_channel` | string | no | The webhook override to receive notifications for wiki page events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Discord Notifications
+
+Disable Discord Notifications for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/discord
+```
+
+### Get Discord Notifications settings
+
+Get the Discord Notifications settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/discord
+```
+
+## Drone
+
+### Set up Drone
+
+Set up the Drone integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/drone-ci
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `token` | string | yes | Drone CI project specific token. |
+| `drone_url` | string | yes | `http://drone.example.com`. |
+| `enable_ssl_verification` | boolean | no | Enable SSL verification. Defaults to `true` (enabled). |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Drone
+
+Disable the Drone integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/drone-ci
+```
+
+### Get Drone settings
+
+Get the Drone integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/drone-ci
+```
+
+## Emails on push
+
+### Set up emails on push
+
+Set up the emails on push integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/emails-on-push
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `recipients` | string | yes | Emails separated by whitespace. |
+| `disable_diffs` | boolean | no | Disable code diffs. |
+| `send_from_committer_email` | boolean | no | Send from committer. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. Notifications are always fired for tag pushes. The default value is `all`. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable emails on push
+
+Disable the emails on push integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/emails-on-push
+```
+
+### Get emails on push settings
+
+Get the emails on push integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/emails-on-push
+```
+
+## Engineering Workflow Management (EWM)
+
+### Set up EWM
+
+Set up the EWM integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/ewm
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `new_issue_url` | string | yes | URL of the new issue. |
+| `project_url` | string | yes | URL of the project. |
+| `issues_url` | string | yes | URL of the issue. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable EWM
+
+Disable the EWM integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/ewm
+```
+
+### Get EWM settings
+
+Get the EWM integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/ewm
+```
+
+## External wiki
+
+### Set up an external wiki
+
+Set up an external wiki for a group.
+
+```plaintext
+PUT /groups/:id/integrations/external-wiki
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `external_wiki_url` | string | yes | URL of the external wiki. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable an external wiki
+
+Disable an external wiki for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/external-wiki
+```
+
+### Get external wiki settings
+
+Get the external wiki settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/external-wiki
+```
+
+## GitGuardian
+
+DETAILS:
+**Tier:** Premium, Ultimate
+**Offering:** GitLab Self-Managed, GitLab Dedicated
+
+FLAG:
+On GitLab Self-Managed, by default this feature is available. To hide the feature, ask an administrator to [disable the feature flag](../administration/feature_flags.md) named `git_guardian_integration`.
+On GitLab.com, this feature is not available. On GitLab Dedicated, this feature is available.
+
+[GitGuardian](https://www.gitguardian.com/) is a cybersecurity service that detects sensitive data such as API keys
+and passwords in source code repositories.
+It scans Git repositories, alerts on policy violations, and helps organizations
+fix security issues before hackers can exploit them.
+
+You can configure GitLab to reject commits based on GitGuardian policies.
+
+### Known issues
+
+- Pushes can be delayed or can time out. With the GitGuardian integration, pushes are sent to a third-party, and GitLab has no control over the connection with GitGuardian or the GitGuardian process.
+- Due to a [GitGuardian API limitation](https://api.gitguardian.com/docs#operation/multiple_scan), the integration ignores files over the size of 1 MB. They are not scanned.
+- If a pushed file has a name over 256 characters long the push won't go through.
+ For more information, see [GitGuardian API documentation](https://api.gitguardian.com/docs#operation/multiple_scan) .
+
+Troubleshooting steps on [the integration page](../user/project/integrations/git_guardian.md#troubleshooting)
+show how to mitigate some of these problems.
+
+### Set up GitGuardian
+
+Set up the GitGuardian integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/git-guardian
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- |-----------------------------------------------|
+| `token` | string | yes | GitGuardian API token with `scan` scope. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable GitGuardian
+
+Disable the GitGuardian integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/git-guardian
+```
+
+### Get GitGuardian settings
+
+Get the GitGuardian integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/git-guardian
+```
+
+## GitHub
+
+DETAILS:
+**Tier:** Premium, Ultimate
+**Offering:** GitLab.com, GitLab Self-Managed, GitLab Dedicated
+
+### Set up GitHub
+
+Set up the GitHub integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/github
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `token` | string | yes | GitHub API token with `repo:status` OAuth scope. |
+| `repository_url` | string | yes | GitHub repository URL. |
+| `static_context` | boolean | no | Append the hostname of your GitLab instance to the [status check name](../user/project/integrations/github.md#static-or-dynamic-status-check-names). |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable GitHub
+
+Disable the GitHub integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/github
+```
+
+### Get GitHub settings
+
+Get the GitHub integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/github
+```
+
+## GitLab for Jira Cloud app
+
+The GitLab for Jira Cloud app integration is enabled or disabled automatically through [group linking and unlinking in Jira](../integration/jira/connect-app.md#configure-the-gitlab-for-jira-cloud-app). You cannot enable or disable the integration with the GitLab integrations form or the API.
+
+### Update integration for a group
+
+Use this API endpoint to update an integration you create with group linking in Jira.
+
+```plaintext
+PUT /groups/:id/integrations/jira-cloud-app
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `jira_cloud_app_service_ids` | string | no | Jira Service Management Service IDs. Use commas (`,`) to separate multiple IDs. |
+| `jira_cloud_app_enable_deployment_gating` | boolean | no | Enables deployment gating for blocked GitLab deployments from Jira Service Management. |
+| `jira_cloud_app_deployment_gating_environments` | string | no | The environments (production, staging, testing, or development) to enable deployment gating. Required if deployment gating is enabled. Use commas (`,`) to separate multiple environments. |
+
+### Get GitLab for Jira Cloud app settings
+
+Get the GitLab for Jira Cloud app integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/jira-cloud-app
+```
+
+## GitLab for Slack app
+
+### Set up GitLab for Slack app
+
+Update the GitLab for Slack app integration for a group.
+
+You cannot create a GitLab for Slack app through the API because the integration
+requires an OAuth 2.0 token that you cannot get from the GitLab API alone.
+Instead, you must [install the app](../user/project/integrations/gitlab_slack_application.md#install-the-gitlab-for-slack-app) from the GitLab UI.
+You can then use this API endpoint to update the integration.
+
+```plaintext
+PUT /groups/:id/integrations/gitlab-slack-application
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `channel` | string | no | Default channel to use if no other channel is configured. |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `notify_only_default_branch` | boolean | no | **Deprecated:** This parameter has been replaced with `branches_to_be_notified`. |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. |
+| `alert_events` | boolean | no | Enable notifications for alert events. |
+| `issues_events` | boolean | no | Enable notifications for issue events. |
+| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `note_events` | boolean | no | Enable notifications for note events. |
+| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. |
+| `deployment_events` | boolean | no | Enable notifications for deployment events. |
+| `incidents_events` | boolean | no | Enable notifications for incident events. |
+| `pipeline_events` | boolean | no | Enable notifications for pipeline events. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `vulnerability_events` | boolean | no | Enable notifications for vulnerability events. |
+| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. |
+| `labels_to_be_notified` | string | no | Labels to send notifications for. If not set, receive notifications for all events. |
+| `labels_to_be_notified_behavior` | string | no | Labels to be notified for. Valid options are `match_any` and `match_all`. Defaults to `match_any`. |
+| `push_channel` | string | no | Name of the channel to receive notifications for push events. |
+| `issue_channel` | string | no | Name of the channel to receive notifications for issue events. |
+| `confidential_issue_channel` | string | no | Name of the channel to receive notifications for confidential issue events. |
+| `merge_request_channel` | string | no | Name of the channel to receive notifications for merge request events. |
+| `note_channel` | string | no | Name of the channel to receive notifications for note events. |
+| `confidential_note_channel` | string | no | Name of the channel to receive notifications for confidential note events. |
+| `tag_push_channel` | string | no | Name of the channel to receive notifications for tag push events. |
+| `pipeline_channel` | string | no | Name of the channel to receive notifications for pipeline events. |
+| `wiki_page_channel` | string | no | Name of the channel to receive notifications for wiki page events. |
+| `deployment_channel` | string | no | Name of the channel to receive notifications for deployment events. |
+| `incident_channel` | string | no | Name of the channel to receive notifications for incident events. |
+| `vulnerability_channel` | string | no | Name of the channel to receive notifications for vulnerability events. |
+| `alert_channel` | string | no | Name of the channel to receive notifications for alert events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable GitLab for Slack app
+
+Disable the GitLab for Slack app integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/gitlab-slack-application
+```
+
+### Get GitLab for Slack app settings
+
+Get the GitLab for Slack app integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/gitlab-slack-application
+```
+
+## Google Chat
+
+### Set up Google Chat
+
+Set up the Google Chat integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/hangouts-chat
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `webhook` | string | yes | The Hangouts Chat webhook (for example, `https://chat.googleapis.com/v1/spaces...`). |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `notify_only_default_branch` | boolean | no | **Deprecated:** This parameter has been replaced with `branches_to_be_notified`. |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `issues_events` | boolean | no | Enable notifications for issue events. |
+| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `note_events` | boolean | no | Enable notifications for note events. |
+| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. |
+| `pipeline_events` | boolean | no | Enable notifications for pipeline events. |
+| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Google Chat
+
+Disable the Google Chat integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/hangouts-chat
+```
+
+### Get Google Chat settings
+
+Get the Google Chat integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/hangouts-chat
+```
+
+## Google Artifact Management
+
+DETAILS:
+**Tier:** Free, Premium, Ultimate
+**Offering:** GitLab.com
+**Status:** Beta
+
+This feature is in [beta](../policy/experiment-beta-support.md).
+
+### Set up Google Artifact Management
+
+Set up the Google Artifact Management integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/google-cloud-platform-artifact-registry
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `artifact_registry_project_id` | string | yes | ID of the Google Cloud project. |
+| `artifact_registry_location` | string | yes | Location of the Artifact Registry repository. |
+| `artifact_registry_repositories` | string | yes | Repository of Artifact Registry. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Google Artifact Management
+
+Disable the Google Artifact Management integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/google-cloud-platform-artifact-registry
+```
+
+### Get Google Artifact Management settings
+
+Get the Google Artifact Management integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/google-cloud-platform-artifact-registry
+```
+
+## Google Cloud Identity and Access Management (IAM)
+
+DETAILS:
+**Tier:** Free, Premium, Ultimate
+**Offering:** GitLab.com
+**Status:** Beta
+
+This feature is in [beta](../policy/experiment-beta-support.md).
+
+### Set up Google Cloud Identity and Access Management
+
+Set up the Google Cloud Identity and Access Management integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/google-cloud-platform-workload-identity-federation
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `workload_identity_federation_project_id` | string | yes | Google Cloud project ID for the Workload Identity Federation. |
+| `workload_identity_federation_project_number` | integer | yes | Google Cloud project number for the Workload Identity Federation. |
+| `workload_identity_pool_id` | string | yes | ID of the Workload Identity Pool. |
+| `workload_identity_pool_provider_id` | string | yes | ID of the Workload Identity Pool provider. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Google Cloud Identity and Access Management
+
+Disable the Google Cloud Identity and Access Management integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/google-cloud-platform-workload-identity-federation
+```
+
+### Get Google Cloud Identity and Access Management
+
+Get the settings for the Google Cloud Identity and Access Management for a group.
+
+```plaintext
+GET /groups/:id/integration/google-cloud-platform-workload-identity-federation
+```
+
+## Harbor
+
+### Set up Harbor
+
+Set up the Harbor integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/harbor
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `url` | string | yes | The base URL to the Harbor instance linked to the GitLab project. For example, `https://demo.goharbor.io`. |
+| `project_name` | string | yes | The name of the project in the Harbor instance. For example, `testproject`. |
+| `username` | string | yes | The username created in the Harbor interface. |
+| `password` | string | yes | The password of the user. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Harbor
+
+Disable the Harbor integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/harbor
+```
+
+### Get Harbor settings
+
+Get the Harbor integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/harbor
+```
+
+## irker (IRC gateway)
+
+### Set up irker
+
+Set up the irker integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/irker
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `recipients` | string | yes | Recipients or channels separated by whitespaces. |
+| `default_irc_uri` | string | no | `irc://irc.network.net:6697/`. |
+| `server_host` | string | no | localhost. |
+| `server_port` | integer | no | 6659. |
+| `colorize_messages` | boolean | no | Colorize messages. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable irker
+
+Disable the irker integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/irker
+```
+
+### Get irker settings
+
+Get the irker integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/irker
+```
+
+## JetBrains TeamCity
+
+### Set up JetBrains TeamCity
+
+Set up the JetBrains TeamCity integration for a group.
+
+The build configuration in TeamCity must use the build number format `%build.vcs.number%`.
+In the advanced settings for VCS root, configure monitoring for all branches so merge requests can build.
+
+```plaintext
+PUT /groups/:id/integrations/teamcity
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `teamcity_url` | string | yes | TeamCity root URL (for example, `https://teamcity.example.com`). |
+| `enable_ssl_verification` | boolean | no | Enable SSL verification. Defaults to `true` (enabled). |
+| `build_type` | string | yes | Build configuration ID. |
+| `username` | string | yes | A user with permissions to trigger a manual build. |
+| `password` | string | yes | The password of the user. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable JetBrains TeamCity
+
+Disable the JetBrains TeamCity integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/teamcity
+```
+
+### Get JetBrains TeamCity settings
+
+Get the JetBrains TeamCity integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/teamcity
+```
+
+## Jira
+
+### Set up Jira
+
+Set up the Jira integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/jira
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `url` | string | yes | The URL to the Jira project which is being linked to this GitLab project (for example, `https://jira.example.com`). |
+| `api_url` | string | no | The base URL to the Jira instance API. Web URL value is used if not set (for example, `https://jira-api.example.com`). |
+| `username` | string | no | The email or username to be used with Jira. For Jira Cloud use an email, for Jira Data Center and Jira Server use a username. Required when using Basic authentication (`jira_auth_type` is `0`). |
+| `password` | string | yes | The Jira API token, password, or personal access token to be used with Jira. When your authentication method is basic (`jira_auth_type` is `0`), use an API token for Jira Cloud or a password for Jira Data Center or Jira Server. When your authentication method is a Jira personal access token (`jira_auth_type` is `1`), use the personal access token. |
+| `active` | boolean | no | Activates or deactivates the integration. Defaults to `false` (deactivated). |
+| `jira_auth_type`| integer | no | The authentication method to be used with Jira. `0` means Basic Authentication. `1` means Jira personal access token. Defaults to `0`. |
+| `jira_issue_prefix` | string | no | Prefix to match Jira issue keys. |
+| `jira_issue_regex` | string | no | Regular expression to match Jira issue keys. |
+| `jira_issue_transition_automatic` | boolean | no | Enable [automatic issue transitions](../integration/jira/issues.md#automatic-issue-transitions). Takes precedence over `jira_issue_transition_id` if enabled. Defaults to `false`. |
+| `jira_issue_transition_id` | string | no | The ID of one or more transitions for [custom issue transitions](../integration/jira/issues.md#custom-issue-transitions). Ignored if `jira_issue_transition_automatic` is enabled. Defaults to a blank string, which disables custom transitions. |
+| `commit_events` | boolean | no | Enable notifications for commit events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `comment_on_event_enabled` | boolean | no | Enable comments in Jira issues on each GitLab event (commit or merge request). |
+| `issues_enabled` | boolean | no | Enable viewing Jira issues in GitLab. |
+| `project_keys` | array of strings | no | Keys of Jira projects. When `issues_enabled` is `true`, this setting specifies which Jira projects to view issues from in GitLab. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Jira
+
+Disable the Jira integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/jira
+```
+
+### Get Jira settings
+
+Get the Jira integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/jira
+```
+
+## Matrix notifications
+
+### Set up Matrix notifications
+
+Set up Matrix notifications for a group.
+
+```plaintext
+PUT /groups/:id/integrations/matrix
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `hostname` | string | no | Custom hostname of the Matrix server. The default value is `https://matrix.org`. |
+| `token` | string | yes | The Matrix access token (for example, `syt-zyx57W2v1u123ew11`). |
+| `room` | string | yes | Unique identifier for the target room (in the format `!qPKKM111FFKKsfoCVy:matrix.org`). |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `issues_events` | boolean | no | Enable notifications for issue events. |
+| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `note_events` | boolean | no | Enable notifications for note events. |
+| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. |
+| `pipeline_events` | boolean | no | Enable notifications for pipeline events. |
+| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Matrix notifications
+
+Disable Matrix notifications for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/matrix
+```
+
+### Get Matrix notifications settings
+
+Get the Matrix notifications settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/matrix
+```
+
+## Mattermost notifications
+
+### Set up Mattermost notifications
+
+Set up Mattermost notifications for a group.
+
+```plaintext
+PUT /groups/:id/integrations/mattermost
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `webhook` | string | yes | Mattermost notifications webhook (for example, `http://mattermost.example.com/hooks/...`). |
+| `username` | string | no | Mattermost notifications username. |
+| `channel` | string | no | Default channel to use if no other channel is configured. |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `notify_only_default_branch` | boolean | no | **Deprecated:** This parameter has been replaced with `branches_to_be_notified`. |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. |
+| `labels_to_be_notified` | string | no | Labels to send notifications for. Leave blank to receive notifications for all events. |
+| `labels_to_be_notified_behavior` | string | no | Labels to be notified for. Valid options are `match_any` and `match_all`. The default value is `match_any`. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `issues_events` | boolean | no | Enable notifications for issue events. |
+| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `note_events` | boolean | no | Enable notifications for note events. |
+| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. |
+| `pipeline_events` | boolean | no | Enable notifications for pipeline events. |
+| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. |
+| `push_channel` | string | no | The name of the channel to receive notifications for push events. |
+| `issue_channel` | string | no | The name of the channel to receive notifications for issue events. |
+| `confidential_issue_channel` | string | no | The name of the channel to receive notifications for confidential issue events. |
+| `merge_request_channel` | string | no | The name of the channel to receive notifications for merge request events. |
+| `note_channel` | string | no | The name of the channel to receive notifications for note events. |
+| `confidential_note_channel` | string | no | The name of the channel to receive notifications for confidential note events. |
+| `tag_push_channel` | string | no | The name of the channel to receive notifications for tag push events. |
+| `pipeline_channel` | string | no | The name of the channel to receive notifications for pipeline events. |
+| `wiki_page_channel` | string | no | The name of the channel to receive notifications for wiki page events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Mattermost notifications
+
+Disable Mattermost notifications for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/mattermost
+```
+
+### Get Mattermost notifications settings
+
+Get the Mattermost notifications settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/mattermost
+```
+
+## Mattermost slash commands
+
+### Set up Mattermost slash commands
+
+Set up Mattermost slash commands for a group.
+
+```plaintext
+PUT /groups/:id/integrations/mattermost-slash-commands
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ------ | -------- | --------------------- |
+| `token` | string | yes | The Mattermost token. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Mattermost slash commands
+
+Disable Mattermost slash commands for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/mattermost-slash-commands
+```
+
+### Get Mattermost slash commands settings
+
+Get the Mattermost slash commands settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/mattermost-slash-commands
+```
+
+## Microsoft Teams notifications
+
+### Set up Microsoft Teams notifications
+
+Set up Microsoft Teams notifications for a group.
+
+```plaintext
+PUT /groups/:id/integrations/microsoft-teams
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `webhook` | string | yes | The Microsoft Teams webhook (for example, `https://outlook.office.com/webhook/...`). |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `notify_only_default_branch` | boolean | no | **Deprecated:** This parameter has been replaced with `branches_to_be_notified`. |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `issues_events` | boolean | no | Enable notifications for issue events. |
+| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `note_events` | boolean | no | Enable notifications for note events. |
+| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. |
+| `pipeline_events` | boolean | no | Enable notifications for pipeline events. |
+| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Microsoft Teams notifications
+
+Disable Microsoft Teams notifications for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/microsoft-teams
+```
+
+### Get Microsoft Teams notifications settings
+
+Get the Microsoft Teams notifications settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/microsoft-teams
+```
+
+## Mock CI
+
+This integration is only available in a development environment.
+For an example Mock CI server, see [`gitlab-org/gitlab-mock-ci-service`](https://gitlab.com/gitlab-org/gitlab-mock-ci-service).
+
+### Set up Mock CI
+
+Set up the Mock CI integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/mock-ci
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `mock_service_url` | string | yes | URL of the Mock CI integration. |
+| `enable_ssl_verification` | boolean | no | Enable SSL verification. Defaults to `true` (enabled). |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Mock CI
+
+Disable the Mock CI integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/mock-ci
+```
+
+### Get Mock CI settings
+
+Get the Mock CI integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/mock-ci
+```
+
+## Packagist
+
+### Set up Packagist
+
+Set up the Packagist integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/packagist
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `username` | string | yes | The username of a Packagist account. |
+| `token` | string | yes | API token to the Packagist server. |
+| `server` | boolean | no | URL of the Packagist server. Leave blank for the default ``. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Packagist
+
+Disable the Packagist integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/packagist
+```
+
+### Get Packagist settings
+
+Get the Packagist integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/packagist
+```
+
+## Phorge
+
+### Set up Phorge
+
+Set up the Phorge integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/phorge
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+|-----------------|--------|----------|-----------------------|
+| `issues_url` | string | yes | URL of the issue. |
+| `project_url` | string | yes | URL of the project. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Phorge
+
+Disable the Phorge integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/phorge
+```
+
+### Get Phorge settings
+
+Get the Phorge integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/phorge
+```
+
+## Pipeline status emails
+
+### Set up pipeline status emails
+
+Set up pipeline status emails for a group.
+
+```plaintext
+PUT /groups/:id/integrations/pipelines-email
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `recipients` | string | yes | Comma-separated list of recipient email addresses. |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. |
+| `notify_only_default_branch` | boolean | no | Send notifications for the default branch. |
+| `pipeline_events` | boolean | no | Enable notifications for pipeline events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable pipeline status emails
+
+Disable pipeline status emails for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/pipelines-email
+```
+
+### Get pipeline status emails settings
+
+Get the pipeline status emails settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/pipelines-email
+```
+
+## Pivotal Tracker
+
+### Set up Pivotal Tracker
+
+Set up the Pivotal Tracker integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/pivotaltracker
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `token` | string | yes | The Pivotal Tracker token. |
+| `restrict_to_branch` | boolean | no | Comma-separated list of branches to automatically inspect. Leave blank to include all branches. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Pivotal Tracker
+
+Disable the Pivotal Tracker integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/pivotaltracker
+```
+
+### Get Pivotal Tracker settings
+
+Get the Pivotal Tracker integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/pivotaltracker
+```
+
+## Pumble
+
+### Set up Pumble
+
+Set up the Pumble integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/pumble
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `webhook` | string | yes | The Pumble webhook (for example, `https://api.pumble.com/workspaces/x/...`). |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default is `default`. |
+| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. |
+| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. |
+| `issues_events` | boolean | no | Enable notifications for issue events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `note_events` | boolean | no | Enable notifications for note events. |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `pipeline_events` | boolean | no | Enable notifications for pipeline events. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Pumble
+
+Disable the Pumble integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/pumble
+```
+
+### Get Pumble settings
+
+Get the Pumble integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/pumble
+```
+
+## Pushover
+
+### Set up Pushover
+
+Set up the Pushover integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/pushover
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `api_key` | string | yes | Your application key. |
+| `user_key` | string | yes | Your user key. |
+| `priority` | string | yes | The priority. |
+| `device` | string | no | Leave blank for all active devices. |
+| `sound` | string | no | The sound of the notification. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Pushover
+
+Disable the Pushover integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/pushover
+```
+
+### Get Pushover settings
+
+Get the Pushover integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/pushover
+```
+
+## Redmine
+
+### Set up Redmine
+
+Set up the Redmine integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/redmine
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `new_issue_url` | string | yes | URL of the new issue. |
+| `project_url` | string | yes | URL of the project. |
+| `issues_url` | string | yes | URL of the issue. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Redmine
+
+Disable the Redmine integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/redmine
+```
+
+### Get Redmine settings
+
+Get the Redmine integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/redmine
+```
+
+## Slack notifications
+
+### Set up Slack notifications
+
+Set up Slack notifications for a group.
+
+```plaintext
+PUT /groups/:id/integrations/slack
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `webhook` | string | yes | Slack notifications webhook (for example, `https://hooks.slack.com/services/...`). |
+| `username` | string | no | Slack notifications username. |
+| `channel` | string | no | Default channel to use if no other channel is configured. |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `notify_only_default_branch` | boolean | no | **Deprecated:** This parameter has been replaced with `branches_to_be_notified`. |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. |
+| `labels_to_be_notified` | string | no | Labels to send notifications for. Leave blank to receive notifications for all events. |
+| `labels_to_be_notified_behavior` | string | no | Labels to be notified for. Valid options are `match_any` and `match_all`. The default value is `match_any`. |
+| `alert_channel` | string | no | The name of the channel to receive notifications for alert events. |
+| `alert_events` | boolean | no | Enable notifications for alert events. |
+| `commit_events` | boolean | no | Enable notifications for commit events. |
+| `confidential_issue_channel` | string | no | The name of the channel to receive notifications for confidential issue events. |
+| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. |
+| `confidential_note_channel` | string | no | The name of the channel to receive notifications for confidential note events. |
+| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. |
+| `deployment_channel` | string | no | The name of the channel to receive notifications for deployment events. |
+| `deployment_events` | boolean | no | Enable notifications for deployment events. |
+| `incident_channel` | string | no | The name of the channel to receive notifications for incident events. |
+| `incidents_events` | boolean | no | Enable notifications for incident events. |
+| `issue_channel` | string | no | The name of the channel to receive notifications for issue events. |
+| `issues_events` | boolean | no | Enable notifications for issue events. |
+| `job_events` | boolean | no | Enable notifications for job events. |
+| `merge_request_channel` | string | no | The name of the channel to receive notifications for merge request events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `note_channel` | string | no | The name of the channel to receive notifications for note events. |
+| `note_events` | boolean | no | Enable notifications for note events. |
+| `pipeline_channel` | string | no | The name of the channel to receive notifications for pipeline events. |
+| `pipeline_events` | boolean | no | Enable notifications for pipeline events. |
+| `push_channel` | string | no | The name of the channel to receive notifications for push events. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `tag_push_channel` | string | no | The name of the channel to receive notifications for tag push events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `wiki_page_channel` | string | no | The name of the channel to receive notifications for wiki page events. |
+| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Slack notifications
+
+Disable Slack notifications for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/slack
+```
+
+### Get Slack notifications settings
+
+Get the Slack notifications settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/slack
+```
+
+## Slack slash commands
+
+### Set up Slack slash commands
+
+Set up Slack slash commands for a group.
+
+```plaintext
+PUT /groups/:id/integrations/slack-slash-commands
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `token` | string | yes | The Slack token. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Slack slash commands
+
+Disable Slack slash commands for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/slack-slash-commands
+```
+
+### Get Slack slash commands settings
+
+Get the Slack slash commands settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/slack-slash-commands
+```
+
+Example response:
+
+```json
+{
+ "id": 4,
+ "title": "Slack slash commands",
+ "slug": "slack-slash-commands",
+ "created_at": "2017-06-27T05:51:39-07:00",
+ "updated_at": "2017-06-27T05:51:39-07:00",
+ "active": true,
+ "push_events": true,
+ "issues_events": true,
+ "confidential_issues_events": true,
+ "merge_requests_events": true,
+ "tag_push_events": true,
+ "note_events": true,
+ "job_events": true,
+ "pipeline_events": true,
+ "comment_on_event_enabled": false,
+ "inherited": false,
+ "properties": {
+ "token": ""
+ }
+}
+```
+
+## Squash TM
+
+### Set up Squash TM
+
+Set up the Squash TM integration settings for a group.
+
+```plaintext
+PUT /groups/:id/integrations/squash-tm
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+|-------------------------|--------|----------|-------------------------------|
+| `url` | string | yes | URL of the Squash TM webhook. |
+| `token` | string | no | Secret token. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Squash TM
+
+Disable the Squash TM integration for a group. Integration settings are preserved.
+
+```plaintext
+DELETE /groups/:id/integrations/squash-tm
+```
+
+### Get Squash TM settings
+
+Get the Squash TM integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/squash-tm
+```
+
+## Telegram
+
+### Set up Telegram
+
+Set up the Telegram integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/telegram
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `hostname` | string | no | Custom hostname of the Telegram API. The default value is `https://api.telegram.org`. |
+| `token` | string | yes | The Telegram bot token (for example, `123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11`). |
+| `room` | string | yes | Unique identifier for the target chat or the username of the target channel (in the format `@channelusername`). |
+| `thread` | integer | no | Unique identifier for the target message thread (topic in a forum supergroup). |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. |
+| `push_events` | boolean | yes | Enable notifications for push events. |
+| `issues_events` | boolean | yes | Enable notifications for issue events. |
+| `confidential_issues_events` | boolean | yes | Enable notifications for confidential issue events. |
+| `merge_requests_events` | boolean | yes | Enable notifications for merge request events. |
+| `tag_push_events` | boolean | yes | Enable notifications for tag push events. |
+| `note_events` | boolean | yes | Enable notifications for note events. |
+| `confidential_note_events` | boolean | yes | Enable notifications for confidential note events. |
+| `pipeline_events` | boolean | yes | Enable notifications for pipeline events. |
+| `wiki_page_events` | boolean | yes | Enable notifications for wiki page events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Telegram
+
+Disable the Telegram integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/telegram
+```
+
+### Get Telegram settings
+
+Get the Telegram integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/telegram
+```
+
+## Unify Circuit
+
+### Set up Unify Circuit
+
+Set up the Unify Circuit integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/unify-circuit
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `webhook` | string | yes | The Unify Circuit webhook (for example, `https://circuit.com/rest/v2/webhooks/incoming/...`). |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `issues_events` | boolean | no | Enable notifications for issue events. |
+| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `note_events` | boolean | no | Enable notifications for note events. |
+| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. |
+| `pipeline_events` | boolean | no | Enable notifications for pipeline events. |
+| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Unify Circuit
+
+Disable the Unify Circuit integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/unify-circuit
+```
+
+### Get Unify Circuit settings
+
+Get the Unify Circuit integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/unify-circuit
+```
+
+## Webex Teams
+
+### Set up Webex Teams
+
+Set up Webex Teams for a group.
+
+```plaintext
+PUT /groups/:id/integrations/webex-teams
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `webhook` | string | yes | The Webex Teams webhook (for example, `https://api.ciscospark.com/v1/webhooks/incoming/...`). |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `issues_events` | boolean | no | Enable notifications for issue events. |
+| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `note_events` | boolean | no | Enable notifications for note events. |
+| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. |
+| `pipeline_events` | boolean | no | Enable notifications for pipeline events. |
+| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Webex Teams
+
+Disable Webex Teams for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/webex-teams
+```
+
+### Get Webex Teams settings
+
+Get the Webex Teams settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/webex-teams
+```
+
+## YouTrack
+
+### Set up YouTrack
+
+Set up the YouTrack integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/youtrack
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `issues_url` | string | yes | URL of the issue. |
+| `project_url` | string | yes | URL of the project. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable YouTrack
+
+Disable the YouTrack integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/youtrack
+```
+
+### Get YouTrack settings
+
+Get the YouTrack integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/youtrack
+```
diff --git a/doc/api/integrations.md b/doc/api/integrations.md
index 58338c8d009..3f4e7b2eee7 100644
--- a/doc/api/integrations.md
+++ b/doc/api/integrations.md
@@ -2,14 +2,14 @@
stage: Foundations
group: Import and Integrate
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
-title: Integrations API
+title: Project integrations API
---
DETAILS:
**Tier:** Free, Premium, Ultimate
**Offering:** GitLab.com, GitLab Self-Managed, GitLab Dedicated
-This API enables you to work with external services that integrate with GitLab.
+Use this API to work with external services that integrate with GitLab.
This API requires an access token with the Maintainer or Owner role.
diff --git a/doc/api/rest/deprecations.md b/doc/api/rest/deprecations.md
index ffb16217423..ce9f30f7088 100644
--- a/doc/api/rest/deprecations.md
+++ b/doc/api/rest/deprecations.md
@@ -116,14 +116,6 @@ Breaking change. [Related issue](https://gitlab.com/gitlab-org/gitlab/-/issues/4
In GitLab 17.0, the [Runners API](../runners.md) will return `""` in place of `ip_address` for runners.
In v5 of the REST API, the field will be removed.
-## Runner will not return `version`, `revision`, `platform`, or `architecture`
-
-Breaking change. [Related issue](https://gitlab.com/gitlab-org/gitlab/-/issues/457128).
-
-In GitLab 18.0, the [Runners API](../runners.md) will return `""` in place of `version`, `revision`, `platform`,
-and `architecture` for runners.
-In v5 of the REST API, the fields will be removed.
-
## `default_branch_protection` API field
Breaking change. [Related issue](https://gitlab.com/gitlab-org/gitlab/-/issues/408315).
diff --git a/doc/ci/yaml/_index.md b/doc/ci/yaml/_index.md
index 03d9a5e339e..ea86d412dbc 100644
--- a/doc/ci/yaml/_index.md
+++ b/doc/ci/yaml/_index.md
@@ -5180,8 +5180,8 @@ job4:
#### `stage: .pre`
-Use the `.pre` stage to make a job run at the start of a pipeline. `.pre` is
-always the first stage in a pipeline. User-defined stages execute after `.pre`.
+Use the `.pre` stage to make a job run at the start of a pipeline. By default, `.pre` is
+the first stage in a pipeline. User-defined stages execute after `.pre`.
You do not have to define `.pre` in [`stages`](#stages).
If a pipeline contains only jobs in the `.pre` or `.post` stages, it does not run.
@@ -5212,10 +5212,17 @@ job2:
- echo "This job runs in the test stage."
```
+**Additional details:**
+
+- If a pipeline has jobs with [`needs: []`](#needs) and jobs in the `.pre` stage, they will
+ all start as soon as the pipeline is created. Jobs with `needs: []` start immediately,
+ ignoring any stage configuration.
+- A [pipeline execution policy](../../user/application_security/policies/pipeline_execution_policies.md) can define a `.pipeline-policy-pre` stage which runs before `.pre`.
+
#### `stage: .post`
-Use the `.post` stage to make a job run at the end of a pipeline. `.post`
-is always the last stage in a pipeline. User-defined stages execute before `.post`.
+Use the `.post` stage to make a job run at the end of a pipeline. By default, `.post`
+is the last stage in a pipeline. User-defined stages execute before `.post`.
You do not have to define `.post` in [`stages`](#stages).
If a pipeline contains only jobs in the `.pre` or `.post` stages, it does not run.
@@ -5248,9 +5255,7 @@ job2:
**Additional details:**
-- If a pipeline has jobs with [`needs: []`](#needs) and jobs in the `.pre` stage, they will
- all start as soon as the pipeline is created. Jobs with `needs: []` start immediately,
- ignoring any stage configuration.
+- A [pipeline execution policy](../../user/application_security/policies/pipeline_execution_policies.md) can define a `.pipeline-policy-post` stage which runs after `.post`.
### `tags`
diff --git a/doc/development/integrations/_index.md b/doc/development/integrations/_index.md
index c6435ecc2ab..69ce8123b75 100644
--- a/doc/development/integrations/_index.md
+++ b/doc/development/integrations/_index.md
@@ -360,7 +360,7 @@ To expose the integration in the [REST API](../../api/integrations.md):
'foo-bar' => ::Integrations::FooBar.api_arguments
```
-1. Update the reference documentation in `doc/api/integrations.md`, add a new section for your integration, and document all properties.
+1. Update the reference documentation in `doc/api/integrations.md` and `doc/api/group_integrations.md`, add a new section for your integration, and document all properties.
You can also refer to our [REST API style guide](../api_styleguide.md).
@@ -450,8 +450,8 @@ In the major milestone of intended removal (M.0), disable the integration and de
- Remove the integration from `Integration::INTEGRATION_NAMES`.
- Delete the integration model's `#execute` and `#test` methods (if defined), but keep the model.
- Add a post-migration to delete the integration records from PostgreSQL (see [example merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114721)).
-- [Mark the integration documentation as removed](../documentation/styleguide/deprecations_and_removals.md#remove-a-page).
-- [Update the integration API documentation](../../api/integrations.md).
+- [Mark the integration documentation as removed](../../development/documentation/styleguide/deprecations_and_removals.md#remove-a-page).
+- Update the [project](../../api/integrations.md) and [group](../../api/group_integrations.md) integrations API pages.
In the next minor release (M.1):
diff --git a/doc/development/work_items_widgets.md b/doc/development/work_items_widgets.md
index 3b7dd7805b4..46b232da482 100644
--- a/doc/development/work_items_widgets.md
+++ b/doc/development/work_items_widgets.md
@@ -377,7 +377,6 @@ Refer to [merge request #158688](https://gitlab.com/gitlab-org/gitlab/-/merge_re
include Gitlab::Database::MigrationHelpers::WorkItems::Widgets
restrict_gitlab_migration gitlab_schema: :gitlab_main
- disable_ddl_transaction!
milestone '17.9'
WORK_ITEM_TYPE_ENUM_VALUES = 8 # ticket, use [8,9] for multiple types
diff --git a/doc/operations/incident_management/alerts.md b/doc/operations/incident_management/alerts.md
index f659d3e4095..f7d11efc42e 100644
--- a/doc/operations/incident_management/alerts.md
+++ b/doc/operations/incident_management/alerts.md
@@ -93,7 +93,7 @@ The following actions result in a system note:
- [Assignment of an alert to a user](#assign-an-alert)
- [Escalation of an alert to on-call responders](paging.md#escalating-an-alert)
-
+
## Alert actions
diff --git a/lib/api/entities/ci/runner_details.rb b/lib/api/entities/ci/runner_details.rb
index 0438cbffadd..5b8ee1ca403 100644
--- a/lib/api/entities/ci/runner_details.rb
+++ b/lib/api/entities/ci/runner_details.rb
@@ -11,7 +11,7 @@ module API
expose :locked
expose :maximum_timeout
expose :access_level
- # TODO: return nil in 18.0 and remove in v5 https://gitlab.com/gitlab-org/gitlab/-/issues/457128
+ # TODO: remove in v5 https://gitlab.com/gitlab-org/gitlab/-/issues/457128
expose(:version) { |runner, _options| latest_runner_manager(runner)&.version }
expose(:revision) { |runner, _options| latest_runner_manager(runner)&.revision }
expose(:platform) { |runner, _options| latest_runner_manager(runner)&.platform }
diff --git a/lib/api/entities/project_integration.rb b/lib/api/entities/integration.rb
similarity index 64%
rename from lib/api/entities/project_integration.rb
rename to lib/api/entities/integration.rb
index f4709ce6dab..48ade64963c 100644
--- a/lib/api/entities/project_integration.rb
+++ b/lib/api/entities/integration.rb
@@ -2,11 +2,11 @@
module API
module Entities
- class ProjectIntegration < Entities::ProjectIntegrationBasic
+ class Integration < Entities::IntegrationBasic
# Expose serialized properties
- expose :properties, documentation: { type: 'Hash', example: { "token" => "secr3t" } } do |integration, options|
+ expose :properties, documentation: { type: 'Hash', example: { "token" => "secr3t" } } do |integration, _|
integration.api_field_names.index_with do |name|
- integration.public_send(name) # rubocop:disable GitlabSecurity/PublicSend
+ integration.public_send(name) # rubocop:disable GitlabSecurity/PublicSend -- we're exposing the fields that are allowed to be exposed
end
end
end
diff --git a/lib/api/entities/project_integration_basic.rb b/lib/api/entities/integration_basic.rb
similarity index 94%
rename from lib/api/entities/project_integration_basic.rb
rename to lib/api/entities/integration_basic.rb
index e57263fafa2..633687c0a6b 100644
--- a/lib/api/entities/project_integration_basic.rb
+++ b/lib/api/entities/integration_basic.rb
@@ -2,7 +2,7 @@
module API
module Entities
- class ProjectIntegrationBasic < Grape::Entity
+ class IntegrationBasic < Grape::Entity
expose :id, documentation: { type: 'integer', example: 75 }
expose :title, documentation: { type: 'string', example: 'Jenkins CI' }
expose :slug, documentation: { type: 'integer', example: 'jenkins' } do |integration|
@@ -33,4 +33,4 @@ module API
end
end
-API::Entities::ProjectIntegrationBasic.prepend_mod
+API::Entities::IntegrationBasic.prepend_mod
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index b32d7d89e36..74a9120a477 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -399,10 +399,14 @@ module API
authorize! :admin_project, user_project
end
- def authorize_admin_integrations
+ def authorize_admin_project_integrations
authorize! :admin_integrations, user_project
end
+ def authorize_admin_group_integrations
+ authorize! :admin_integrations, user_group
+ end
+
def authorize_admin_group
authorize! :admin_group, user_group
end
diff --git a/lib/api/integrations.rb b/lib/api/integrations.rb
index d5284c56d3d..c32f77983f1 100644
--- a/lib/api/integrations.rb
+++ b/lib/api/integrations.rb
@@ -45,7 +45,7 @@ module API
end
# The API officially documents only the `:id/integrations` API paths.
- # We support the older `id:/services` path for backwards-compatibility in API V4.
+ # We support the older `id:/services` path for project integrations for backwards-compatibility in API V4.
# The support for `:id/services` can be dropped if we create an API V5.
[':id/services', ':id/integrations'].each do |path|
params do
@@ -53,134 +53,15 @@ module API
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
before { authenticate! }
- before { authorize_admin_integrations }
+ before { authorize_admin_project_integrations }
- desc 'List all active integrations' do
- detail 'Get a list of all active project integrations.'
- success Entities::ProjectIntegrationBasic
- failure [
- { code: 401, message: 'Unauthorized' },
- { code: 404, message: 'Not found' }
- ]
- is_array true
- tags INTEGRATIONS_TAGS
- end
- get path do
- integrations = user_project.integrations.active
-
- present integrations, with: Entities::ProjectIntegrationBasic
- end
-
- INTEGRATIONS.each do |slug, settings|
- desc "Create/Edit #{slug.titleize} integration" do
- detail "Set #{slug.titleize} integration for a project."
- success Entities::ProjectIntegrationBasic
- failure [
- { code: 400, message: 'Bad request' },
- { code: 401, message: 'Unauthorized' },
- { code: 404, message: 'Not found' },
- { code: 422, message: 'Unprocessable entity' }
- ]
- tags INTEGRATIONS_TAGS
- end
- params do
- settings.each do |setting|
- if setting[:required]
- requires setting[:name], type: setting[:type], desc: setting[:desc]
- else
- optional setting[:name], type: setting[:type], desc: setting[:desc]
- end
- end
- end
- put "#{path}/#{slug}" do
- integration = user_project.find_or_initialize_integration(slug.underscore)
-
- render_api_error!('400 Integration not available', 400) if integration.nil?
-
- params = declared_params(include_missing: false).merge(active: true)
-
- unless integration.manual_activation? || integration.is_a?(::Integrations::Prometheus)
- if integration.new_record?
- render_api_error!("You cannot create the #{integration.class.title} integration from the API", 422)
- end
-
- params.delete(:active)
- end
-
- result = ::Integrations::UpdateService.new(
- current_user: current_user, integration: integration, attributes: params
- ).execute
-
- if result.success?
- present integration, with: Entities::ProjectIntegration
- else
- render_api_error!(result.message, 400)
- end
+ helpers do
+ def fetch_parent_resource
+ user_project
end
end
- desc "Disable an integration" do
- detail "Disable the integration for a project. Integration settings are preserved."
- success code: 204
- failure [
- { code: 400, message: 'Bad request' },
- { code: 401, message: 'Unauthorized' },
- { code: 404, message: 'Not found' }
- ]
- tags INTEGRATIONS_TAGS
- end
- params do
- requires :slug, type: String, values: INTEGRATIONS.keys, desc: 'The name of the integration'
- end
- delete "#{path}/:slug" do
- integration = user_project.find_or_initialize_integration(params[:slug].underscore)
-
- not_found!('Integration') unless integration&.persisted?
-
- if integration.is_a?(::Integrations::JiraCloudApp)
- render_api_error!("You cannot disable the #{integration.class.title} integration from the API", 422)
- end
-
- destroy_conditionally!(integration) do
- attrs = integration_attributes(integration).index_with do |attr|
- column = if integration.attribute_present?(attr)
- integration.column_for_attribute(attr)
- elsif integration.data_fields_present?
- integration.data_fields.column_for_attribute(attr)
- end
-
- case column
- when nil, ActiveRecord::ConnectionAdapters::NullColumn
- nil
- else
- column.default
- end
- end.merge(active: false)
-
- render_api_error!('400 Bad Request', 400) unless integration.update(attrs)
- end
- end
-
- desc "Get an integration settings" do
- detail "Get the integration settings for a project."
- success Entities::ProjectIntegration
- failure [
- { code: 400, message: 'Bad request' },
- { code: 401, message: 'Unauthorized' },
- { code: 404, message: 'Not found' }
- ]
- tags INTEGRATIONS_TAGS
- end
- params do
- requires :slug, type: String, values: INTEGRATIONS.keys, desc: 'The name of the integration'
- end
- get "#{path}/:slug" do
- integration = user_project.find_or_initialize_integration(params[:slug].underscore)
-
- not_found!('Integration') unless integration&.persisted?
-
- present integration, with: Entities::ProjectIntegration
- end
+ mount IntegratableOperations, with: { path: path }
end
SLASH_COMMAND_INTEGRATIONS.each do |integration_slug, settings|
@@ -229,6 +110,26 @@ module API
end
end
+ # New API endpoints should use the `:id/integrations` path exclusively.
+ ':id/integrations'.tap do |path|
+ params do
+ requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the group'
+ end
+
+ resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ before { authenticate! }
+ before { authorize_admin_group_integrations }
+
+ helpers do
+ def fetch_parent_resource
+ user_group
+ end
+ end
+
+ mount IntegratableOperations, with: { path: path }
+ end
+ end
+
desc "Trigger a global slack command" do
detail 'Added in GitLab 9.4'
failure [
diff --git a/lib/api/integrations/integratable_operations.rb b/lib/api/integrations/integratable_operations.rb
new file mode 100644
index 00000000000..f1806f3bdd0
--- /dev/null
+++ b/lib/api/integrations/integratable_operations.rb
@@ -0,0 +1,176 @@
+# frozen_string_literal: true
+
+module API
+ class Integrations
+ class IntegratableOperations < Grape::API # rubocop:disable API/Base -- subclassing from Grape::API is required for mounting in the main API.
+ INTEGRATIONS_TAGS = %w[integrations].freeze
+
+ before do
+ render_api_error!('400 Integration not available', 400) if disallowed_integration_at_group_level?(params[:slug])
+ end
+
+ mounted do
+ helpers do
+ def disallowed_integration_at_group_level?(slug)
+ return unless slug.present?
+
+ parent_resource.is_a?(Group) && Integration.project_specific_integration_names.include?(slug.underscore)
+ end
+
+ def integration_attributes(integration)
+ integration.fields.inject([]) do |arr, hash|
+ arr << hash[:name].to_sym
+ end
+ end
+
+ def find_or_initialize_integration(slug)
+ parent_resource.find_or_initialize_integration(slug.underscore)
+ end
+
+ def parent_resource
+ unless respond_to?(:fetch_parent_resource, true)
+ raise NotImplementedError,
+ "You must implement a `fetch_parent_resource` method that returns " \
+ "the integratable resource in the namespace that mounts this API."
+ end
+
+ @parent_resource ||= fetch_parent_resource
+ end
+ end
+
+ desc 'List all active integrations' do
+ detail "Get a list of all active integrations."
+ success Entities::IntegrationBasic
+ failure [
+ { code: 401, message: 'Unauthorized' },
+ { code: 404, message: 'Not found' }
+ ]
+ is_array true
+ tags INTEGRATIONS_TAGS
+ end
+
+ get(configuration[:path]) do
+ integrations = parent_resource.integrations.active
+
+ present integrations, with: Entities::IntegrationBasic
+ end
+
+ INTEGRATIONS.each do |slug, settings|
+ desc "Create/Edit #{slug.titleize} integration" do
+ detail "Set #{slug.titleize} integration."
+ success Entities::IntegrationBasic
+ failure [
+ { code: 400, message: 'Bad request' },
+ { code: 401, message: 'Unauthorized' },
+ { code: 404, message: 'Not found' },
+ { code: 422, message: 'Unprocessable entity' }
+ ]
+ tags INTEGRATIONS_TAGS
+ end
+ params do
+ settings.each do |setting|
+ if setting[:required]
+ requires setting[:name], type: setting[:type], desc: setting[:desc]
+ else
+ optional setting[:name], type: setting[:type], desc: setting[:desc]
+ end
+ end
+ end
+ put("#{configuration[:path]}/#{slug}") do
+ integration = find_or_initialize_integration(slug)
+
+ params = declared_params(include_missing: false).merge(active: true)
+
+ render_api_error!('400 Integration not available', 400) if integration.nil?
+
+ manual_or_special = integration.manual_activation? || integration.is_a?(::Integrations::Prometheus)
+
+ unless manual_or_special
+ if integration.new_record?
+ render_api_error!("You cannot create the #{integration.class.title} integration from the API", 422)
+ end
+
+ params.delete(:active)
+ end
+
+ result = ::Integrations::UpdateService.new(
+ current_user: current_user, integration: integration, attributes: params
+ ).execute
+
+ if result.success?
+ present integration, with: Entities::Integration
+ else
+ message = result.message || '400 Bad Request'
+ render_api_error!(message, 400)
+ end
+ end
+ end
+
+ desc "Disable an integration" do
+ detail "Disable the integration. Integration settings are preserved."
+ success code: 204
+ failure [
+ { code: 400, message: 'Bad request' },
+ { code: 401, message: 'Unauthorized' },
+ { code: 404, message: 'Not found' }
+ ]
+ tags INTEGRATIONS_TAGS
+ end
+ params do
+ requires :slug, type: String, values: INTEGRATIONS.keys,
+ desc: 'The name of the integration'
+ end
+ delete("#{configuration[:path]}/:slug") do
+ integration = find_or_initialize_integration(params[:slug])
+
+ not_found!('Integration') unless integration&.persisted?
+
+ if integration.is_a?(::Integrations::JiraCloudApp)
+ render_api_error!("You cannot disable the #{integration.class.title} integration from the API", 422)
+ end
+
+ destroy_conditionally!(integration) do
+ attrs = integration_attributes(integration).index_with do |attr|
+ column = if integration.attribute_present?(attr)
+ integration.column_for_attribute(attr)
+ elsif integration.data_fields_present?
+ integration.data_fields.column_for_attribute(attr)
+ end
+
+ case column
+ when nil, ActiveRecord::ConnectionAdapters::NullColumn
+ nil
+ else
+ column.default
+ end
+ end.merge(active: false)
+
+ render_api_error!('400 Bad Request', 400) unless integration.update(attrs)
+ end
+ end
+
+ desc "Get an integration settings" do
+ detail "Get the integration settings."
+ success Entities::Integration
+ failure [
+ { code: 400, message: 'Bad request' },
+ { code: 401, message: 'Unauthorized' },
+ { code: 404, message: 'Not found' }
+ ]
+ tags INTEGRATIONS_TAGS
+ end
+ params do
+ requires :slug, type: String, values: INTEGRATIONS.keys,
+ desc: 'The name of the integration'
+ end
+ get("#{configuration[:path]}/:slug") do
+ integration = find_or_initialize_integration(params[:slug])
+
+ not_found!('Integration') unless integration&.persisted?
+
+ present integration, with: Entities::Integration
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database_importers/work_items/base_type_importer.rb b/lib/gitlab/database_importers/work_items/base_type_importer.rb
index 257e3d35c80..b4eb7298c14 100644
--- a/lib/gitlab/database_importers/work_items/base_type_importer.rb
+++ b/lib/gitlab/database_importers/work_items/base_type_importer.rb
@@ -31,7 +31,8 @@ module Gitlab
crm_contacts: 'CRM contacts',
email_participants: 'Email participants',
custom_status: 'Custom status',
- custom_fields: 'Custom fields'
+ custom_fields: 'Custom fields',
+ error_tracking: 'Error tracking'
}.freeze
WIDGETS_FOR_TYPE = {
@@ -45,6 +46,7 @@ module Gitlab
:designs,
:development,
:email_participants,
+ :error_tracking,
:health_status,
:hierarchy,
:iteration,
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index ac4d67ff14a..e286f5bb0e8 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -19473,6 +19473,9 @@ msgstr ""
msgid "Dependencies|Direct dependents"
msgstr ""
+msgid "Dependencies|Enter at least 3 characters to view available components."
+msgstr ""
+
msgid "Dependencies|Error exporting the dependency list. Please reload the page."
msgstr ""
@@ -19500,6 +19503,9 @@ msgstr ""
msgid "Dependencies|Location"
msgstr ""
+msgid "Dependencies|No components found."
+msgstr ""
+
msgid "Dependencies|Packager"
msgstr ""
@@ -52773,6 +52779,18 @@ msgstr ""
msgid "SecurityReports|%{firstProject}, %{secondProject}, and %{rest}"
msgstr ""
+msgid "SecurityReports|%{nrVulnerabilities} vulnerabilities attached to %{nrIssues} issues"
+msgstr ""
+
+msgid "SecurityReports|%{nrVulnerabilities} vulnerabilities attached to 1 issue"
+msgstr ""
+
+msgid "SecurityReports|1 vulnerability attached to %{nrIssues} issues"
+msgstr ""
+
+msgid "SecurityReports|1 vulnerability attached to 1 issue"
+msgstr ""
+
msgid "SecurityReports|A comment is required when changing the severity."
msgstr ""
diff --git a/spec/graphql/types/work_items/widget_interface_spec.rb b/spec/graphql/types/work_items/widget_interface_spec.rb
index e2235b02468..ee7085c6f2c 100644
--- a/spec/graphql/types/work_items/widget_interface_spec.rb
+++ b/spec/graphql/types/work_items/widget_interface_spec.rb
@@ -31,6 +31,7 @@ RSpec.describe Types::WorkItems::WidgetInterface, feature_category: :team_planni
WorkItems::Widgets::CrmContacts | Types::WorkItems::Widgets::CrmContactsType
WorkItems::Widgets::EmailParticipants | Types::WorkItems::Widgets::EmailParticipantsType
WorkItems::Widgets::CustomStatus | Types::WorkItems::Widgets::CustomStatusType
+ WorkItems::Widgets::ErrorTracking | Types::WorkItems::Widgets::ErrorTrackingType
end
with_them do
diff --git a/spec/graphql/types/work_items/widgets/error_tracking_type_spec.rb b/spec/graphql/types/work_items/widgets/error_tracking_type_spec.rb
new file mode 100644
index 00000000000..98d830a364c
--- /dev/null
+++ b/spec/graphql/types/work_items/widgets/error_tracking_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::WorkItems::Widgets::ErrorTrackingType, feature_category: :team_planning do
+ it 'exposes the expected fields' do
+ expected_fields = %i[type identifier]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/migrations/20250128163345_add_error_tracking_to_work_item_widget_spec.rb b/spec/migrations/20250128163345_add_error_tracking_to_work_item_widget_spec.rb
new file mode 100644
index 00000000000..16fcd761f50
--- /dev/null
+++ b/spec/migrations/20250128163345_add_error_tracking_to_work_item_widget_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe AddErrorTrackingToWorkItemWidget, :migration, feature_category: :team_planning do
+ # Tests for `n` widgets in your migration when using the work items widgets migration helper
+ it_behaves_like 'migration that adds widgets to a work item type'
+end
diff --git a/spec/models/preloaders/group_root_ancestor_preloader_spec.rb b/spec/models/preloaders/group_root_ancestor_preloader_spec.rb
index 1f01df4a3ed..f5849d08625 100644
--- a/spec/models/preloaders/group_root_ancestor_preloader_spec.rb
+++ b/spec/models/preloaders/group_root_ancestor_preloader_spec.rb
@@ -2,14 +2,21 @@
require 'spec_helper'
-RSpec.describe Preloaders::GroupRootAncestorPreloader do
- let_it_be(:user) { create(:user) }
+RSpec.describe Preloaders::GroupRootAncestorPreloader, feature_category: :groups_and_projects do
let_it_be(:root_parent1) { create(:group, :private, name: 'root-1', path: 'root-1') }
let_it_be(:root_parent2) { create(:group, :private, name: 'root-2', path: 'root-2') }
let_it_be(:guest_group) { create(:group, name: 'public guest', path: 'public-guest') }
- let_it_be(:private_maintainer_group) { create(:group, :private, name: 'b private maintainer', path: 'b-private-maintainer', parent: root_parent1) }
- let_it_be(:private_developer_group) { create(:group, :private, project_creation_level: nil, name: 'c public developer', path: 'c-public-developer') }
- let_it_be(:public_maintainer_group) { create(:group, :private, name: 'a public maintainer', path: 'a-public-maintainer', parent: root_parent2) }
+ let_it_be(:private_maintainer_group) do
+ create(:group, :private, name: 'b private maintainer', path: 'b-private-maintainer', parent: root_parent1)
+ end
+
+ let_it_be(:private_developer_group) do
+ create(:group, :private, project_creation_level: nil, name: 'c public developer', path: 'c-public-developer')
+ end
+
+ let_it_be(:public_maintainer_group) do
+ create(:group, :private, name: 'a public maintainer', path: 'a-public-maintainer', parent: root_parent2)
+ end
let(:root_query_regex) do
if Feature.enabled?(:use_sql_functions_for_primary_key_lookups, Feature.current_request)
diff --git a/spec/models/preloaders/project_root_ancestor_preloader_spec.rb b/spec/models/preloaders/project_root_ancestor_preloader_spec.rb
index 2c9221846c8..d2f66f9d7cb 100644
--- a/spec/models/preloaders/project_root_ancestor_preloader_spec.rb
+++ b/spec/models/preloaders/project_root_ancestor_preloader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Preloaders::ProjectRootAncestorPreloader, feature_category: :system_access do
+RSpec.describe Preloaders::ProjectRootAncestorPreloader, feature_category: :groups_and_projects do
let_it_be(:root_parent1) { create(:group, :private, name: 'root-1', path: 'root-1') }
let_it_be(:root_parent2) { create(:group, name: 'root-2', path: 'root-2') }
let_it_be(:guest_project) { create(:project, name: 'public guest', path: 'public-guest') }
diff --git a/spec/models/work_items/widget_definition_spec.rb b/spec/models/work_items/widget_definition_spec.rb
index ad76b6ef73f..dd033c032a2 100644
--- a/spec/models/work_items/widget_definition_spec.rb
+++ b/spec/models/work_items/widget_definition_spec.rb
@@ -22,7 +22,8 @@ RSpec.describe WorkItems::WidgetDefinition, feature_category: :team_planning do
::WorkItems::Widgets::Development,
::WorkItems::Widgets::CrmContacts,
::WorkItems::Widgets::EmailParticipants,
- ::WorkItems::Widgets::CustomStatus
+ ::WorkItems::Widgets::CustomStatus,
+ ::WorkItems::Widgets::ErrorTracking
]
if Gitlab.ee?
diff --git a/spec/models/work_items/widgets/error_tracking_spec.rb b/spec/models/work_items/widgets/error_tracking_spec.rb
new file mode 100644
index 00000000000..22d12cfe3b4
--- /dev/null
+++ b/spec/models/work_items/widgets/error_tracking_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe WorkItems::Widgets::ErrorTracking, feature_category: :team_planning do
+ let_it_be(:work_item) { create(:work_item) }
+ let_it_be(:sentry_issue) { create(:sentry_issue, issue: work_item) }
+
+ describe '.type' do
+ it { expect(described_class.type).to eq(:error_tracking) }
+ end
+
+ describe '#type' do
+ it { expect(described_class.new(work_item).type).to eq(:error_tracking) }
+ end
+
+ describe '.sentry_issue' do
+ it { expect(described_class.new(work_item).sentry_issue).to eq(sentry_issue) }
+ end
+
+ describe '.sentry_issue_identifier' do
+ it { expect(described_class.new(work_item).sentry_issue_identifier).to eq(sentry_issue.sentry_issue_identifier) }
+ end
+end
diff --git a/spec/requests/api/integrations_spec.rb b/spec/requests/api/integrations_spec.rb
index 5d353bbb994..66ba6f87e98 100644
--- a/spec/requests/api/integrations_spec.rb
+++ b/spec/requests/api/integrations_spec.rb
@@ -7,38 +7,37 @@ RSpec.describe API::Integrations, feature_category: :integrations do
let_it_be(:user) { create(:user) }
let_it_be(:user2) { create(:user) }
- let_it_be(:project, reload: true) { create(:project, creator_id: user.id, namespace: user.namespace) }
-
- let_it_be(:available_integration_names) do
- excluded_integrations = [Integrations::GitlabSlackApplication.to_param, Integrations::Zentao.to_param]
-
- Integration.available_integration_names(include_instance_specific: false) - excluded_integrations
- end
-
- let_it_be(:project_integrations_map) do
- available_integration_names.index_with do |name|
- create(integration_factory(name), :inactive, project: project)
- end
- end
-
+ let_it_be(:project, reload: true) { create(:project, creator_id: user.id, namespace: user.namespace, owners: [user]) }
let_it_be(:project2) { create(:project, creator_id: user.id, namespace: user.namespace) }
+ let_it_be(:group, reload: true) { create(:group, owners: user) }
- %w[integrations services].each do |endpoint|
- describe "GET /projects/:id/#{endpoint}" do
- it 'returns authentication error when unauthenticated' do
- get api("/projects/#{project.id}/#{endpoint}")
+ context "Project level integrations" do
+ let_it_be(:available_integration_names) do
+ excluded_integrations = [Integrations::GitlabSlackApplication.to_param, Integrations::Zentao.to_param]
+ Integration.available_integration_names(include_instance_specific: false) - excluded_integrations
+ end
- expect(response).to have_gitlab_http_status(:unauthorized)
+ let_it_be(:project_integrations_map) do
+ available_integration_names.index_with do |name|
+ create(integration_factory(name), :inactive, project: project)
end
+ end
- it "returns error when authenticated but user is not a project owner" do
- project.add_developer(user2)
- get api("/projects/#{project.id}/#{endpoint}", user2)
+ %w[integrations services].each do |endpoint|
+ describe "GET /projects/:id/#{endpoint}" do
+ it 'returns authentication error when unauthenticated' do
+ get api("/projects/#{project.id}/#{endpoint}")
- expect(response).to have_gitlab_http_status(:forbidden)
- end
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it "returns error when authenticated but user is not a project owner" do
+ project.add_developer(user2)
+ get api("/projects/#{project.id}/#{endpoint}", user2)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
- context 'with integrations' do
it "returns a list of all active integrations" do
get api("/projects/#{project.id}/#{endpoint}", user)
@@ -51,391 +50,490 @@ RSpec.describe API::Integrations, feature_category: :integrations do
end
end
end
- end
- where(:integration) do
- # The integrations API supports all project integrations.
- # You cannot create a GitLab for Slack app. You must install the app from the GitLab UI.
- unavailable_integration_names = [
- Integrations::GitlabSlackApplication.to_param,
- Integrations::JiraCloudApp.to_param,
- Integrations::Prometheus.to_param,
- Integrations::Zentao.to_param
- ]
+ where(:integration) do
+ # The integrations API supports all project integrations.
+ # You cannot create a GitLab for Slack app. You must install the app from the GitLab UI.
+ unavailable_integration_names = [
+ Integrations::GitlabSlackApplication.to_param,
+ Integrations::JiraCloudApp.to_param,
+ Integrations::Prometheus.to_param,
+ Integrations::Zentao.to_param
+ ]
- names = Integration.available_integration_names(include_instance_specific: false)
- names.reject { |name| name.in?(unavailable_integration_names) }
- end
-
- with_them do
- integration = params[:integration]
-
- describe "PUT /projects/:id/#{endpoint}/#{integration.dasherize}" do
- it_behaves_like 'set up an integration', endpoint: endpoint, integration: integration
+ names = Integration.available_integration_names(include_instance_specific: false)
+ names.reject { |name| unavailable_integration_names.include?(name) }
end
- describe "DELETE /projects/:id/#{endpoint}/#{integration.dasherize}" do
- it_behaves_like 'disable an integration', endpoint: endpoint, integration: integration
+ with_them do
+ integration = params[:integration]
+
+ describe "PUT /projects/:id/#{endpoint}/#{integration.dasherize}" do
+ it_behaves_like 'set up an integration', endpoint: endpoint, integration: integration, parent_resource_name: 'project' do
+ let(:parent_resource) { project }
+ let(:integrations_map) { project_integrations_map }
+ end
+ end
+
+ describe "DELETE /projects/:id/#{endpoint}/#{integration.dasherize}" do
+ it_behaves_like 'disable an integration', endpoint: endpoint, integration: integration, parent_resource_name: 'project' do
+ let(:parent_resource) { project }
+ let(:integrations_map) { project_integrations_map }
+ end
+ end
+
+ describe "GET /projects/:id/#{endpoint}/#{integration.dasherize}" do
+ it_behaves_like 'get an integration settings', endpoint: endpoint, integration: integration, parent_resource_name: 'project' do
+ let(:parent_resource) { project }
+ let(:integrations_map) { project_integrations_map }
+ end
+ end
end
- describe "GET /projects/:id/#{endpoint}/#{integration.dasherize}" do
- it_behaves_like 'get an integration settings', endpoint: endpoint, integration: integration
- end
- end
+ describe "POST /projects/:id/#{endpoint}/:slug/trigger" do
+ describe 'Mattermost integration' do
+ let(:integration_name) { 'mattermost_slash_commands' }
+
+ context 'when no integration is available' do
+ it 'returns a not found message' do
+ post api("/projects/#{project.id}/#{endpoint}/idonotexist/trigger")
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response["error"]).to eq("404 Not Found")
+ end
+ end
+
+ context 'when the integration exists' do
+ let(:params) { { token: 'secrettoken' } }
+
+ context 'when the integration is not active' do
+ before do
+ project_integrations_map[integration_name].deactivate!
+ end
+
+ it 'when the integration is inactive' do
+ post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when the integration is active' do
+ before do
+ project_integrations_map[integration_name].activate!
+ end
+
+ it 'returns status 200' do
+ post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when the project can not be found' do
+ it 'returns a generic 404' do
+ post api("/projects/404/#{endpoint}/#{integration_name}/trigger"), params: params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response["message"]).to eq("404 Integration Not Found")
+ end
+ end
+ end
+ end
+
+ describe 'Slack Integration' do
+ let(:integration_name) { 'slack_slash_commands' }
+ let(:params) { { token: 'secrettoken', text: 'help' } }
+
+ context 'when no integration is available' do
+ it 'returns a not found message' do
+ post api("/projects/#{project.id}/#{endpoint}/idonotexist/trigger")
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response["error"]).to eq("404 Not Found")
+ end
+ end
+
+ context 'when the integration exists' do
+ context 'when the integration is not active' do
+ before do
+ project_integrations_map[integration_name].deactivate!
+ end
+
+ it 'when the integration is inactive' do
+ post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when the integration is active' do
+ before do
+ project_integrations_map[integration_name].activate!
+ end
+
+ it 'returns status 200' do
+ post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['response_type']).to eq("ephemeral")
+ end
+ end
+
+ context 'when the project can not be found' do
+ it 'returns a generic 404' do
+ post api("/projects/404/#{endpoint}/#{integration_name}/trigger"), params: params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response["message"]).to eq("404 Integration Not Found")
+ end
+ end
+ end
+ end
+ end
- describe "POST /projects/:id/#{endpoint}/:slug/trigger" do
describe 'Mattermost integration' do
- let(:integration_name) { 'mattermost_slash_commands' }
-
- context 'when no integration is available' do
- it 'returns a not found message' do
- post api("/projects/#{project.id}/#{endpoint}/idonotexist/trigger")
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response["error"]).to eq("404 Not Found")
- end
+ let(:integration_name) { 'mattermost' }
+ let(:params) do
+ { webhook: 'https://hook.example.com', username: 'username' }
end
- context 'when the integration exists' do
- let(:params) { { token: 'secrettoken' } }
+ before do
+ project_integrations_map[integration_name].activate!
+ end
- context 'when the integration is not active' do
- before do
- project_integrations_map[integration_name].deactivate!
- end
+ it 'accepts a username for update' do
+ put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: params.merge(username: 'new_username')
- it 'when the integration is inactive' do
- post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'when the integration is active' do
- before do
- project_integrations_map[integration_name].activate!
- end
-
- it 'returns status 200' do
- post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
-
- context 'when the project can not be found' do
- it 'returns a generic 404' do
- post api("/projects/404/#{endpoint}/#{integration_name}/trigger"), params: params
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response["message"]).to eq("404 Integration Not Found")
- end
- end
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['properties']['username']).to eq('new_username')
end
end
- describe 'Slack Integration' do
- let(:integration_name) { 'slack_slash_commands' }
- let(:params) { { token: 'secrettoken', text: 'help' } }
-
- context 'when no integration is available' do
- it 'returns a not found message' do
- post api("/projects/#{project.id}/#{endpoint}/idonotexist/trigger")
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response["error"]).to eq("404 Not Found")
- end
+ describe 'Microsoft Teams integration' do
+ let_it_be(:group) { create(:group) }
+ let(:integration_name) { 'microsoft-teams' }
+ let(:params) do
+ {
+ webhook: 'https://hook.example.com',
+ branches_to_be_notified: 'default',
+ notify_only_broken_pipelines: false
+ }
end
- context 'when the integration exists' do
- context 'when the integration is not active' do
- before do
- project_integrations_map[integration_name].deactivate!
- end
-
- it 'when the integration is inactive' do
- post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'when the integration is active' do
- before do
- project_integrations_map[integration_name].activate!
- end
-
- it 'returns status 200' do
- post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['response_type']).to eq("ephemeral")
- end
- end
-
- context 'when the project can not be found' do
- it 'returns a generic 404' do
- post api("/projects/404/#{endpoint}/#{integration_name}/trigger"), params: params
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response["message"]).to eq("404 Integration Not Found")
- end
- end
+ before do
+ create(:microsoft_teams_integration, group: group, project: nil)
+ project.update!(namespace: group)
+ project_integrations_map[integration_name.underscore].activate!
end
- end
- end
- describe 'Mattermost integration' do
- let(:integration_name) { 'mattermost' }
- let(:params) do
- { webhook: 'https://hook.example.com', username: 'username' }
- end
-
- before do
- project_integrations_map[integration_name].activate!
- end
-
- it 'accepts a username for update' do
- put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: params.merge(username: 'new_username')
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['properties']['username']).to eq('new_username')
- end
- end
-
- describe 'Microsoft Teams integration' do
- let_it_be(:group) { create(:group) }
- let(:integration_name) { 'microsoft-teams' }
- let(:params) do
- {
- webhook: 'https://hook.example.com',
- branches_to_be_notified: 'default',
- notify_only_broken_pipelines: false
- }
- end
-
- before do
- create(:microsoft_teams_integration, group: group, project: nil)
- project.update!(namespace: group)
- project_integrations_map[integration_name.underscore].activate!
- end
-
- it 'accepts branches_to_be_notified for update' do
- put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user),
- params: params.merge(branches_to_be_notified: 'all')
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['properties']['branches_to_be_notified']).to eq('all')
- end
-
- it 'accepts notify_only_broken_pipelines for update' do
- put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user),
- params: params.merge(notify_only_broken_pipelines: true)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['properties']['notify_only_broken_pipelines']).to eq(true)
- end
-
- it 'accepts `use_inherited_settings` for inheritance' do
- expect do
+ it 'accepts branches_to_be_notified for update' do
put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user),
- params: params.merge(use_inherited_settings: true)
- end.to change { project_integrations_map[integration_name.underscore].reload.inherit_from_id }.from(nil)
+ params: params.merge(branches_to_be_notified: 'all')
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['inherited']).to eq(true)
- end
- end
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['properties']['branches_to_be_notified']).to eq('all')
+ end
- describe 'Hangouts Chat integration' do
- let(:integration_name) { 'hangouts-chat' }
- let(:params) do
- {
- webhook: 'https://hook.example.com',
- branches_to_be_notified: 'default'
- }
+ it 'accepts notify_only_broken_pipelines for update' do
+ put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user),
+ params: params.merge(notify_only_broken_pipelines: true)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['properties']['notify_only_broken_pipelines']).to eq(true)
+ end
+
+ it 'accepts `use_inherited_settings` for inheritance' do
+ expect do
+ put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user),
+ params: params.merge(use_inherited_settings: true)
+ end.to change { project_integrations_map[integration_name.underscore].reload.inherit_from_id }.from(nil)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['inherited']).to eq(true)
+ end
end
- before do
- project_integrations_map[integration_name.underscore].activate!
- end
+ describe 'Hangouts Chat integration' do
+ let(:integration_name) { 'hangouts-chat' }
+ let(:params) do
+ {
+ webhook: 'https://hook.example.com',
+ branches_to_be_notified: 'default'
+ }
+ end
- it 'accepts branches_to_be_notified for update', :aggregate_failures do
- put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: params.merge(branches_to_be_notified: 'all')
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['properties']['branches_to_be_notified']).to eq('all')
- end
-
- it 'only requires the webhook param' do
- put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: { webhook: 'https://hook.example.com' }
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
-
- describe 'Jira integration' do
- let(:integration_name) { 'jira' }
- let(:params) do
- { url: 'https://jira.example.com', username: 'username', password: 'password', jira_auth_type: 0 }
- end
-
- before do
- project_integrations_map[integration_name].properties = params
- project_integrations_map[integration_name].activate!
- end
-
- it 'returns the jira_issue_transition_id for get request' do
- get api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['properties']).to include('jira_issue_transition_id' => '56-1')
- end
-
- it 'returns the jira_issue_transition_id for put request' do
- put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: params.merge(jira_issue_transition_id: '1')
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['properties']['jira_issue_transition_id']).to eq('1')
- end
- end
-
- describe 'Pipelines Email Integration' do
- let(:integration_name) { 'pipelines-email' }
-
- context 'notify_only_broken_pipelines property was saved as a string' do
before do
project_integrations_map[integration_name.underscore].activate!
end
- it 'returns boolean values for notify_only_broken_pipelines' do
+ it 'accepts branches_to_be_notified for update', :aggregate_failures do
+ put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: params.merge(branches_to_be_notified: 'all')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['properties']['branches_to_be_notified']).to eq('all')
+ end
+
+ it 'only requires the webhook param' do
+ put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: { webhook: 'https://hook.example.com' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ describe 'Jira integration' do
+ let(:integration_name) { 'jira' }
+ let(:params) do
+ { url: 'https://jira.example.com', username: 'username', password: 'password', jira_auth_type: 0 }
+ end
+
+ before do
+ project_integrations_map[integration_name].properties = params
+ project_integrations_map[integration_name].activate!
+ end
+
+ it 'returns the jira_issue_transition_id for get request' do
get api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user)
- expect(json_response['properties']['notify_only_broken_pipelines']).to eq(true)
- end
- end
- end
-
- describe 'GitLab for Slack app integration' do
- before do
- stub_application_setting(slack_app_enabled: true)
- create(:gitlab_slack_application_integration, project: project)
- end
-
- describe "PUT /projects/:id/#{endpoint}/gitlab-slack-application" do
- context 'for integration creation' do
- before do
- project.gitlab_slack_application_integration.destroy!
- end
-
- it 'returns 422' do
- put api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user)
-
- expect(response).to have_gitlab_http_status(:unprocessable_entity)
- expect(json_response['message']).to eq('You cannot create the GitLab for Slack app integration from the API')
- end
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['properties']).to include('jira_issue_transition_id' => '56-1')
end
- context 'for integration update' do
- before do
- project.gitlab_slack_application_integration.update!(active: false)
- end
-
- it "does not enable the integration" do
- put api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(project.gitlab_slack_application_integration.reload).to have_attributes(active: false)
- end
- end
- end
-
- describe "GET /projects/:id/#{endpoint}/gitlab-slack-application" do
- it "fetches the integration and returns the correct fields" do
- get api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user)
+ it 'returns the jira_issue_transition_id for put request' do
+ put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: params.merge(jira_issue_transition_id: '1')
expect(response).to have_gitlab_http_status(:ok)
- assert_correct_response_fields(json_response['properties'].keys, project.gitlab_slack_application_integration)
+ expect(json_response['properties']['jira_issue_transition_id']).to eq('1')
end
end
- describe "DELETE /projects/:id/#{endpoint}/gitlab-slack-application" do
- it "disables the integration" do
- expect { delete api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user) }
- .to change { project.gitlab_slack_application_integration.reload.activated? }.from(true).to(false)
+ describe 'Pipelines Email Integration' do
+ let(:integration_name) { 'pipelines-email' }
- expect(response).to have_gitlab_http_status(:no_content)
- end
- end
- end
-
- describe 'GitLab for Jira Cloud app integration' do
- before do
- stub_application_setting(jira_connect_application_key: 'mock_key')
- create(:jira_cloud_app_integration, project: project)
- end
-
- describe "PUT /projects/:id/#{endpoint}/jira-cloud-app" do
- context 'for integration creation' do
+ context 'notify_only_broken_pipelines property was saved as a string' do
before do
- project.jira_cloud_app_integration.destroy!
+ project_integrations_map[integration_name.underscore].activate!
end
- it 'returns 422' do
- put api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user)
+ it 'returns boolean values for notify_only_broken_pipelines' do
+ get api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user)
- expect(response).to have_gitlab_http_status(:unprocessable_entity)
- expect(json_response['message']).to eq('You cannot create the GitLab for Jira Cloud app integration from the API')
+ expect(json_response['properties']['notify_only_broken_pipelines']).to eq(true)
+ end
+ end
+ end
+
+ describe 'GitLab for Slack app integration' do
+ before do
+ stub_application_setting(slack_app_enabled: true)
+ create(:gitlab_slack_application_integration, project: project)
+ end
+
+ describe "PUT /projects/:id/#{endpoint}/gitlab-slack-application" do
+ context 'for integration creation' do
+ before do
+ project.gitlab_slack_application_integration.destroy!
+ end
+
+ it 'returns 422' do
+ put api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user)
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ expect(json_response['message']).to eq('You cannot create the GitLab for Slack app integration from the API')
+ end
+ end
+
+ context 'for integration update' do
+ before do
+ project.gitlab_slack_application_integration.update!(active: false)
+ end
+
+ it "does not enable the integration" do
+ put api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(project.gitlab_slack_application_integration.reload).to have_attributes(active: false)
+ end
end
end
- context 'for integration update' do
- before do
- project.jira_cloud_app_integration.update!(active: false)
- end
-
- it "does not enable the integration" do
- put api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user)
+ describe "GET /projects/:id/#{endpoint}/gitlab-slack-application" do
+ it "fetches the integration and returns the correct fields" do
+ get api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user)
expect(response).to have_gitlab_http_status(:ok)
- expect(project.jira_cloud_app_integration.reload).to have_attributes(active: false)
+ assert_correct_response_fields(json_response['properties'].keys, project.gitlab_slack_application_integration)
+ end
+ end
+
+ describe "DELETE /projects/:id/#{endpoint}/gitlab-slack-application" do
+ it "disables the integration" do
+ expect { delete api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user) }
+ .to change { project.gitlab_slack_application_integration.reload.activated? }.from(true).to(false)
+
+ expect(response).to have_gitlab_http_status(:no_content)
end
end
end
- describe "GET /projects/:id/#{endpoint}/jira-cloud-app" do
- it "fetches the integration and returns the correct fields" do
- get api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user)
+ describe 'GitLab for Jira Cloud app integration' do
+ before do
+ stub_application_setting(jira_connect_application_key: 'mock_key')
+ create(:jira_cloud_app_integration, project: project)
+ end
- expect(response).to have_gitlab_http_status(:ok)
- assert_correct_response_fields(json_response['properties'].keys, project.jira_cloud_app_integration)
+ describe "PUT /projects/:id/#{endpoint}/jira-cloud-app" do
+ context 'for integration creation' do
+ before do
+ project.jira_cloud_app_integration.destroy!
+ end
+
+ it 'returns 422' do
+ put api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user)
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ expect(json_response['message']).to eq('You cannot create the GitLab for Jira Cloud app integration from the API')
+ end
+ end
+
+ context 'for integration update' do
+ before do
+ project.jira_cloud_app_integration.update!(active: false)
+ end
+
+ it "does not enable the integration" do
+ put api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(project.jira_cloud_app_integration.reload).to have_attributes(active: false)
+ end
+ end
+ end
+
+ describe "GET /projects/:id/#{endpoint}/jira-cloud-app" do
+ it "fetches the integration and returns the correct fields" do
+ get api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ assert_correct_response_fields(json_response['properties'].keys, project.jira_cloud_app_integration)
+ end
+ end
+
+ describe "DELETE /projects/:id/#{endpoint}/jira-cloud-app" do
+ it "does not disable the integration" do
+ expect { delete api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user) }
+ .not_to change { project.jira_cloud_app_integration.reload.activated? }.from(true)
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ expect(json_response['message']).to eq('You cannot disable the GitLab for Jira Cloud app integration from the API')
+ end
end
end
- describe "DELETE /projects/:id/#{endpoint}/jira-cloud-app" do
- it "does not disable the integration" do
- expect { delete api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user) }
- .not_to change { project.jira_cloud_app_integration.reload.activated? }.from(true)
+ private
- expect(response).to have_gitlab_http_status(:unprocessable_entity)
- expect(json_response['message']).to eq('You cannot disable the GitLab for Jira Cloud app integration from the API')
- end
+ def assert_correct_response_fields(response_keys, integration)
+ assert_fields_match_integration(response_keys, integration)
+ assert_secret_fields_filtered(response_keys, integration)
+ end
+
+ def assert_fields_match_integration(response_keys, integration)
+ expect(response_keys).to match_array(integration.api_field_names)
+ end
+
+ def assert_secret_fields_filtered(response_keys, integration)
+ expect(response_keys).not_to include(*integration.secret_fields) unless integration.secret_fields.empty?
+ end
+ end
+ end
+
+ context "Group level integrations" do
+ let_it_be(:available_integration_names) do
+ excluded_integrations = [Integrations::GitlabSlackApplication.to_param, Integrations::Zentao.to_param]
+ Integration.available_integration_names(include_project_specific: false, include_instance_specific: false) - excluded_integrations
+ end
+
+ let_it_be(:group_integrations_map) do
+ available_integration_names.index_with do |name|
+ create(integration_factory(name), :inactive, :group, group: group)
end
end
- private
+ 'integrations'.tap do |endpoint|
+ describe "GET /groups/:id/#{endpoint}" do
+ let_it_be(:project_only_integration) { Integration::PROJECT_LEVEL_ONLY_INTEGRATION_NAMES }
- def assert_correct_response_fields(response_keys, integration)
- assert_fields_match_integration(response_keys, integration)
- assert_secret_fields_filtered(response_keys, integration)
- end
+ it 'returns authentication error when unauthenticated' do
+ get api("/groups/#{group.id}/#{endpoint}")
- def assert_fields_match_integration(response_keys, integration)
- expect(response_keys).to match_array(integration.api_field_names)
- end
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
- def assert_secret_fields_filtered(response_keys, integration)
- expect(response_keys).not_to include(*integration.secret_fields) unless integration.secret_fields.empty?
+ it "returns error when authenticated but user is not a group owner" do
+ group.add_developer(user2)
+ get api("/groups/#{group.id}/#{endpoint}", user2)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it "returns a list of all active integrations" do
+ get api("/groups/#{group.id}/#{endpoint}", user)
+
+ aggregate_failures 'expect successful response with all active integrations' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an Array
+ expect(json_response.count).to eq(1)
+ expect(json_response.first['slug']).to eq('prometheus')
+ expect(response).to match_response_schema('public_api/v4/integrations')
+ end
+ end
+
+ Integration::PROJECT_LEVEL_ONLY_INTEGRATION_NAMES.map(&:dasherize).each do |integration_name|
+ it "returns 400 when trying to access a project-only integration" do
+ get api("/groups/#{group.id}/integrations/#{integration_name}", user)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response["message"]).to eq("400 Integration not available")
+ end
+ end
+ end
+
+ where(:integration) do
+ # You cannot create a GitLab for Slack app. You must install the app from the GitLab UI.
+ unavailable_integration_names = [
+ Integrations::GitlabSlackApplication.to_param,
+ Integrations::Zentao.to_param,
+ Integrations::Prometheus.to_param
+ ]
+
+ names = Integration.available_integration_names(include_instance_specific: false, include_project_specific: false)
+ names.reject { |name| unavailable_integration_names.include?(name) }
+ end
+
+ with_them do
+ integration = params[:integration]
+
+ describe "PUT /groups/:id/#{endpoint}/#{integration.dasherize}" do
+ it_behaves_like 'set up an integration', endpoint: endpoint, integration: integration, parent_resource_name: 'group' do
+ let(:parent_resource) { group }
+ let(:integrations_map) { group_integrations_map }
+ end
+ end
+
+ describe "DELETE /groups/:id/#{endpoint}/#{integration.dasherize}" do
+ it_behaves_like 'disable an integration', endpoint: endpoint, integration: integration, parent_resource_name: 'group' do
+ let(:parent_resource) { group }
+ let(:integrations_map) { group_integrations_map }
+ end
+ end
+
+ describe "GET /groups/:id/#{endpoint}/#{integration.dasherize}" do
+ it_behaves_like 'get an integration settings', endpoint: endpoint, integration: integration, parent_resource_name: 'group' do
+ let(:parent_resource) { group }
+ let(:integrations_map) { group_integrations_map }
+ end
+ end
+ end
end
end
diff --git a/spec/services/integrations/update_service_spec.rb b/spec/services/integrations/update_service_spec.rb
index 273264bf394..e41af6ca486 100644
--- a/spec/services/integrations/update_service_spec.rb
+++ b/spec/services/integrations/update_service_spec.rb
@@ -18,11 +18,21 @@ RSpec.describe Integrations::UpdateService, feature_category: :integrations do
end
shared_examples 'success request' do
+ before do
+ allow(PropagateIntegrationWorker).to receive(:perform_async)
+ end
+
it 'returns a success response' do
result
expect(result).to be_success
expect(result.payload).to eq(integration)
+
+ if integration.project_level?
+ expect(PropagateIntegrationWorker).not_to have_received(:perform_async)
+ else
+ expect(PropagateIntegrationWorker).to have_received(:perform_async).with(integration.id)
+ end
end
end
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index c20153666e2..ec41169a921 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -6242,7 +6242,6 @@
- './spec/models/preloaders/commit_status_preloader_spec.rb'
- './spec/models/preloaders/environments/deployment_preloader_spec.rb'
- './spec/models/preloaders/group_policy_preloader_spec.rb'
-- './spec/models/preloaders/group_root_ancestor_preloader_spec.rb'
- './spec/models/preloaders/labels_preloader_spec.rb'
- './spec/models/preloaders/merge_request_diff_preloader_spec.rb'
- './spec/models/preloaders/user_max_access_level_in_groups_preloader_spec.rb'
diff --git a/spec/support/shared_examples/integrations_shared_examples.rb b/spec/support/shared_examples/integrations_shared_examples.rb
index 085679c9136..db58e955208 100644
--- a/spec/support/shared_examples/integrations_shared_examples.rb
+++ b/spec/support/shared_examples/integrations_shared_examples.rb
@@ -1,10 +1,16 @@
# frozen_string_literal: true
-RSpec.shared_examples 'set up an integration' do |endpoint:, integration:|
+RSpec.shared_examples 'set up an integration' do |endpoint:, integration:, parent_resource_name:|
include_context 'with integration'
- let(:integration_attrs) { attributes_for(integration_factory).without(:active, :type) }
- let(:url) { api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}", user) }
+ let(:integrations_map) { raise NotImplementedError, 'Define `integrations_map` in the calling context' }
+ let(:parent_resource) { raise NotImplementedError, 'Define `parent_resource` in the calling context' }
+
+ let(:integration_attrs) do
+ attributes_for(integration_factory).without(:active, :type)
+ end
+
+ let(:url) { api("/#{parent_resource_name.pluralize}/#{parent_resource.id}/#{endpoint}/#{dashed_integration}", user) }
subject(:request) { put url, params: integration_attrs }
@@ -14,7 +20,7 @@ RSpec.shared_examples 'set up an integration' do |endpoint:, integration:|
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['slug']).to eq(dashed_integration)
- current_integration = project.integrations.by_name(integration).first
+ current_integration = parent_resource.integrations.by_name(integration).first
expect(current_integration).to have_attributes(integration_attrs)
expect(json_response['properties'].keys).to match_array(current_integration.api_field_names)
@@ -32,7 +38,7 @@ RSpec.shared_examples 'set up an integration' do |endpoint:, integration:|
put url, params: flipped_attrs
expect(response).to have_gitlab_http_status(:ok)
- expect(project.integrations.by_name(integration).first).to have_attributes(flipped_attrs)
+ expect(parent_resource.integrations.by_name(integration).first).to have_attributes(flipped_attrs)
end
end
@@ -57,6 +63,56 @@ RSpec.shared_examples 'set up an integration' do |endpoint:, integration:|
expect(response).to have_gitlab_http_status(expected_code)
end
+ context "when updates fails" do
+ before do
+ allow_next_instance_of(::Integrations::UpdateService) do |instance|
+ allow(instance).to receive(:execute).and_return(
+ instance_double(ServiceResponse, success?: false, message: 'Update failed')
+ )
+ end
+ end
+
+ it 'returns 400 with correct message' do
+ request
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to eq('Update failed')
+ end
+ end
+
+ context 'when the integration does not exist' do
+ let(:fresh_parent_resource) { create(parent_resource_name.to_sym, owners: [user]) }
+ let(:parent_resource) { fresh_parent_resource }
+
+ it "creates #{integration} and returns the correct fields" do
+ initial_count = parent_resource.integrations.by_name(integration).count
+ expect(initial_count).to eq(0)
+
+ request
+
+ current_integration = parent_resource.integrations.by_name(integration).first
+ manual_or_special = current_integration&.manual_activation? ||
+ current_integration.is_a?(::Integrations::Prometheus)
+ delta = manual_or_special ? 1 : 0
+
+ expect(parent_resource.integrations.by_name(integration).count).to eq(initial_count + delta)
+
+ if manual_or_special
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['slug']).to eq(dashed_integration)
+
+ expect(current_integration).to have_attributes(integration_attrs)
+ expect(json_response['properties'].keys).to match_array(current_integration.api_field_names)
+
+ if current_integration.secret_fields.present?
+ expect(json_response['properties'].keys).not_to include(*current_integration.secret_fields)
+ end
+ else
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
+ end
+
context 'when an integration is disabled' do
before do
allow(Integration).to receive(:disabled_integration_names).and_return([integration.to_param])
@@ -69,52 +125,68 @@ RSpec.shared_examples 'set up an integration' do |endpoint:, integration:|
end
end
- context 'when an integration is disabled at the project-level' do
- before do
- allow_next_found_instance_of(Project) do |project|
- allow(project).to receive(:disabled_integrations).and_return([integration])
+ if parent_resource_name == 'project'
+ context 'when an integration is disabled at the parent_resource-level' do
+ before do
+ allow_next_found_instance_of(parent_resource.class) do |instance|
+ allow(instance).to receive(:disabled_integrations).and_return([integration])
+ end
end
- end
- it 'returns bad request' do
- put url, params: integration_attrs
+ it 'returns bad request' do
+ put url, params: integration_attrs
- expect(response).to have_gitlab_http_status(:bad_request)
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
end
end
end
-RSpec.shared_examples 'disable an integration' do |endpoint:, integration:|
+RSpec.shared_examples 'disable an integration' do |endpoint:, integration:, parent_resource_name:|
include_context 'with integration'
- subject(:request) { delete api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}", user) }
+ let(:fresh_parent_resource) { create(parent_resource_name.to_sym, owners: [user]) }
+ let(:parent_resource) { fresh_parent_resource }
+
+ subject(:request) do
+ delete api("/#{parent_resource_name.pluralize}/#{parent_resource.id}/#{endpoint}/#{dashed_integration}", user)
+ end
before do
- project_integrations_map[integration].activate!
+ integrations_map[integration].update_column(:active, true)
end
it "deletes #{integration}" do
- request
+ expect do
+ request
+ end.to change {
+ parent_resource.integrations.where(type_new: integration_klass.name, active: true).count
+ }.from(1).to(0)
expect(response).to have_gitlab_http_status(:no_content)
- project.send(integration_method).reload
- expect(project.send(integration_method).activated?).to be_falsey
end
it 'returns not found if integration does not exist' do
- delete api("/projects/#{project2.id}/#{endpoint}/#{dashed_integration}", user)
+ delete api("/#{parent_resource_name.pluralize}/#{fresh_parent_resource.id}/#{endpoint}/#{dashed_integration}", user)
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 Integration Not Found')
end
end
-RSpec.shared_examples 'get an integration settings' do |endpoint:, integration:|
+RSpec.shared_examples 'get an integration settings' do |endpoint:, integration:, parent_resource_name:|
include_context 'with integration'
- let(:initialized_integration) { project_integrations_map[integration] }
+ let(:initialized_integration) do
+ integrations_map[integration]
+ end
- subject(:request) { get api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}", user) }
+ let(:fresh_parent_resource) { create(parent_resource_name.to_sym, owners: [user]) }
+ let(:parent_resource) { fresh_parent_resource }
+
+ subject(:request) do
+ get api("/#{parent_resource_name.pluralize}/#{parent_resource.id}/#{endpoint}/#{dashed_integration}", user)
+ end
def deactive_integration!
return initialized_integration.deactivate! unless initialized_integration.is_a?(::Integrations::Prometheus)
@@ -142,10 +214,10 @@ RSpec.shared_examples 'get an integration settings' do |endpoint:, integration:|
expect(initialized_integration).not_to be_active
expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['properties'].keys).to match_array(integration_instance.api_field_names)
+ expect(json_response['properties'].keys).to match_array(initialized_integration.api_field_names)
- unless integration_instance.secret_fields.empty?
- expect(json_response['properties'].keys).not_to include(*integration_instance.secret_fields)
+ unless initialized_integration.secret_fields.empty?
+ expect(json_response['properties'].keys).not_to include(*initialized_integration.secret_fields)
end
end
end
@@ -160,40 +232,44 @@ RSpec.shared_examples 'get an integration settings' do |endpoint:, integration:|
expect(initialized_integration).to be_active
expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['properties'].keys).to match_array(integration_instance.api_field_names)
+ expect(json_response['properties'].keys).to match_array(initialized_integration.api_field_names)
- unless integration_instance.secret_fields.empty?
- expect(json_response['properties'].keys).not_to include(*integration_instance.secret_fields)
+ unless initialized_integration.secret_fields.empty?
+ expect(json_response['properties'].keys).not_to include(*initialized_integration.secret_fields)
end
end
end
it 'returns authentication error when unauthenticated' do
- get api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}")
+ get api("/#{parent_resource_name.pluralize}/#{fresh_parent_resource.id}/#{endpoint}/#{dashed_integration}")
+
expect(response).to have_gitlab_http_status(:unauthorized)
end
it "returns not found if integration does not exist" do
- get api("/projects/#{project2.id}/#{endpoint}/#{dashed_integration}", user)
+ get api("/#{parent_resource_name.pluralize}/#{fresh_parent_resource.id}/#{endpoint}/#{dashed_integration}", user)
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 Integration Not Found')
end
- it "returns not found if integration exists but is in `Project#disabled_integrations`" do
- allow_next_found_instance_of(Project) do |project|
- allow(project).to receive(:disabled_integrations).and_return([integration])
+ if parent_resource_name == 'project'
+ it "returns not found if integration exists but is in `#{parent_resource_name}#disabled_integrations`" do
+ allow_next_found_instance_of(parent_resource.class) do |instance|
+ allow(instance).to receive(:disabled_integrations).and_return([integration])
+ end
+
+ get api("/#{parent_resource_name.pluralize}/#{parent_resource.id}/#{endpoint}/#{dashed_integration}", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('404 Integration Not Found')
end
-
- request
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response['message']).to eq('404 Integration Not Found')
end
it "returns error when authenticated but not a project owner" do
- project.add_developer(user2)
- get api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}", user2)
+ parent_resource.add_developer(user2)
+
+ get api("/#{parent_resource_name.pluralize}/#{parent_resource.id}/#{endpoint}/#{dashed_integration}", user2)
expect(response).to have_gitlab_http_status(:forbidden)
end