Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									08931747cc
								
							
						
					
					
						commit
						8e28b42532
					
				|  | @ -218,26 +218,36 @@ export const isMetaKey = e => e.metaKey || e.ctrlKey || e.altKey || e.shiftKey; | ||||||
| export const isMetaClick = e => e.metaKey || e.ctrlKey || e.which === 2; | export const isMetaClick = e => e.metaKey || e.ctrlKey || e.which === 2; | ||||||
| 
 | 
 | ||||||
| export const contentTop = () => { | export const contentTop = () => { | ||||||
|  |   const isDesktop = breakpointInstance.isDesktop(); | ||||||
|   const heightCalculators = [ |   const heightCalculators = [ | ||||||
|     () => $('#js-peek').outerHeight(), |     () => $('#js-peek').outerHeight(), | ||||||
|     () => $('.navbar-gitlab').outerHeight(), |     () => $('.navbar-gitlab').outerHeight(), | ||||||
|  |     ({ desktop }) => { | ||||||
|  |       const container = document.querySelector('.line-resolve-all-container'); | ||||||
|  |       let size = 0; | ||||||
|  | 
 | ||||||
|  |       if (!desktop && container) { | ||||||
|  |         size = container.offsetHeight; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return size; | ||||||
|  |     }, | ||||||
|     () => $('.merge-request-tabs').outerHeight(), |     () => $('.merge-request-tabs').outerHeight(), | ||||||
|     () => $('.js-diff-files-changed').outerHeight(), |     () => $('.js-diff-files-changed').outerHeight(), | ||||||
|     () => { |     ({ desktop }) => { | ||||||
|       const isDesktop = breakpointInstance.isDesktop(); |  | ||||||
|       const diffsTabIsActive = window.mrTabs?.currentAction === 'diffs'; |       const diffsTabIsActive = window.mrTabs?.currentAction === 'diffs'; | ||||||
|       let size; |       let size; | ||||||
| 
 | 
 | ||||||
|       if (isDesktop && diffsTabIsActive) { |       if (desktop && diffsTabIsActive) { | ||||||
|         size = $('.diff-file .file-title-flex-parent:visible').outerHeight(); |         size = $('.diff-file .file-title-flex-parent:visible').outerHeight(); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       return size; |       return size; | ||||||
|     }, |     }, | ||||||
|     () => { |     ({ desktop }) => { | ||||||
|       let size; |       let size; | ||||||
| 
 | 
 | ||||||
|       if (breakpointInstance.isDesktop()) { |       if (desktop) { | ||||||
|         size = $('.mr-version-controls').outerHeight(); |         size = $('.mr-version-controls').outerHeight(); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|  | @ -246,7 +256,7 @@ export const contentTop = () => { | ||||||
|   ]; |   ]; | ||||||
| 
 | 
 | ||||||
|   return heightCalculators.reduce((totalHeight, calculator) => { |   return heightCalculators.reduce((totalHeight, calculator) => { | ||||||
|     return totalHeight + (calculator() || 0); |     return totalHeight + (calculator({ desktop: isDesktop }) || 0); | ||||||
|   }, 0); |   }, 0); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -23,6 +23,9 @@ module ServiceParams | ||||||
|     :comment_detail, |     :comment_detail, | ||||||
|     :confidential_issues_events, |     :confidential_issues_events, | ||||||
|     :confluence_url, |     :confluence_url, | ||||||
|  |     :datadog_site, | ||||||
|  |     :datadog_env, | ||||||
|  |     :datadog_service, | ||||||
|     :default_irc_uri, |     :default_irc_uri, | ||||||
|     :device, |     :device, | ||||||
|     :disable_diffs, |     :disable_diffs, | ||||||
|  |  | ||||||
|  | @ -147,6 +147,7 @@ class Project < ApplicationRecord | ||||||
|   # Project services |   # Project services | ||||||
|   has_one :alerts_service |   has_one :alerts_service | ||||||
|   has_one :campfire_service |   has_one :campfire_service | ||||||
|  |   has_one :datadog_service | ||||||
|   has_one :discord_service |   has_one :discord_service | ||||||
|   has_one :drone_ci_service |   has_one :drone_ci_service | ||||||
|   has_one :emails_on_push_service |   has_one :emails_on_push_service | ||||||
|  | @ -1353,6 +1354,8 @@ class Project < ApplicationRecord | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def disabled_services |   def disabled_services | ||||||
|  |     return ['datadog'] unless Feature.enabled?(:datadog_ci_integration, self) | ||||||
|  | 
 | ||||||
|     [] |     [] | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,124 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | class DatadogService < Service | ||||||
|  |   DEFAULT_SITE = 'datadoghq.com'.freeze | ||||||
|  |   URL_TEMPLATE = 'https://webhooks-http-intake.logs.%{datadog_site}/v1/input/'.freeze | ||||||
|  |   URL_TEMPLATE_API_KEYS = 'https://app.%{datadog_site}/account/settings#api'.freeze | ||||||
|  |   URL_API_KEYS_DOCS = "https://docs.#{DEFAULT_SITE}/account_management/api-app-keys/".freeze | ||||||
|  | 
 | ||||||
|  |   SUPPORTED_EVENTS = %w[ | ||||||
|  |     pipeline job | ||||||
|  |   ].freeze | ||||||
|  | 
 | ||||||
|  |   prop_accessor :datadog_site, :api_url, :api_key, :datadog_service, :datadog_env | ||||||
|  | 
 | ||||||
|  |   with_options presence: true, if: :activated? do | ||||||
|  |     validates :api_key, format: { with: /\A\w+\z/ } | ||||||
|  |     validates :datadog_site, format: { with: /\A[\w\.]+\z/ }, unless: :api_url | ||||||
|  |     validates :api_url, public_url: true, unless: :datadog_site | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   after_save :compose_service_hook, if: :activated? | ||||||
|  | 
 | ||||||
|  |   def self.supported_events | ||||||
|  |     SUPPORTED_EVENTS | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def self.default_test_event | ||||||
|  |     'pipeline' | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def configurable_events | ||||||
|  |     [] # do not allow to opt out of required hooks | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def title | ||||||
|  |     'Datadog' | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def description | ||||||
|  |     'Trace your GitLab pipelines with Datadog' | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def help | ||||||
|  |     nil | ||||||
|  |     # Maybe adding something in the future | ||||||
|  |     # We could link to static help pages as well | ||||||
|  |     # [More information](#{Gitlab::Routing.url_helpers.help_page_url('integration/datadog')})" | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def self.to_param | ||||||
|  |     'datadog' | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def fields | ||||||
|  |     [ | ||||||
|  |       { | ||||||
|  |         type: 'text', name: 'datadog_site', | ||||||
|  |         placeholder: DEFAULT_SITE, default: DEFAULT_SITE, | ||||||
|  |         help: 'Choose the Datadog site to send data to. Set to "datadoghq.eu" to send data to the EU site', | ||||||
|  |         required: false | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         type: 'text', name: 'api_url', title: 'Custom URL', | ||||||
|  |         help: '(Advanced) Define the full URL for your Datadog site directly', | ||||||
|  |         required: false | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         type: 'password', name: 'api_key', title: 'API key', | ||||||
|  |         help: "<a href=\"#{api_keys_url}\" target=\"_blank\">API key</a> used for authentication with Datadog", | ||||||
|  |         required: true | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         type: 'text', name: 'datadog_service', title: 'Service', placeholder: 'gitlab-ci', | ||||||
|  |         help: 'Name of this GitLab instance that all data will be tagged with' | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         type: 'text', name: 'datadog_env', title: 'Env', | ||||||
|  |         help: 'The environment tag that traces will be tagged with' | ||||||
|  |       } | ||||||
|  |     ] | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def compose_service_hook | ||||||
|  |     hook = service_hook || build_service_hook | ||||||
|  |     hook.url = hook_url | ||||||
|  |     hook.save | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def hook_url | ||||||
|  |     url = api_url.presence || sprintf(URL_TEMPLATE, datadog_site: datadog_site) | ||||||
|  |     url = URI.parse(url) | ||||||
|  |     url.path = File.join(url.path || '/', api_key) | ||||||
|  |     query = { service: datadog_service, env: datadog_env }.compact | ||||||
|  |     url.query = query.to_query unless query.empty? | ||||||
|  |     url.to_s | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def api_keys_url | ||||||
|  |     return URL_API_KEYS_DOCS unless datadog_site.presence | ||||||
|  | 
 | ||||||
|  |     sprintf(URL_TEMPLATE_API_KEYS, datadog_site: datadog_site) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def execute(data) | ||||||
|  |     return if project.disabled_services.include?(to_param) | ||||||
|  | 
 | ||||||
|  |     object_kind = data[:object_kind] | ||||||
|  |     object_kind = 'job' if object_kind == 'build' | ||||||
|  |     return unless supported_events.include?(object_kind) | ||||||
|  | 
 | ||||||
|  |     service_hook.execute(data, "#{object_kind} hook") | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def test(data) | ||||||
|  |     begin | ||||||
|  |       result = execute(data) | ||||||
|  |       return { success: false, result: result[:message] } if result[:http_status] != 200 | ||||||
|  |     rescue StandardError => error | ||||||
|  |       return { success: false, result: error } | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     { success: true, result: result[:message] } | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -40,6 +40,10 @@ class PipelinesEmailService < Service | ||||||
|     %w[pipeline] |     %w[pipeline] | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   def self.default_test_event | ||||||
|  |     'pipeline' | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   def execute(data, force: false) |   def execute(data, force: false) | ||||||
|     return unless supported_events.include?(data[:object_kind]) |     return unless supported_events.include?(data[:object_kind]) | ||||||
|     return unless force || should_pipeline_be_notified?(data) |     return unless force || should_pipeline_be_notified?(data) | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ class Service < ApplicationRecord | ||||||
|   include EachBatch |   include EachBatch | ||||||
| 
 | 
 | ||||||
|   SERVICE_NAMES = %w[ |   SERVICE_NAMES = %w[ | ||||||
|     alerts asana assembla bamboo bugzilla buildkite campfire confluence custom_issue_tracker discord |     alerts asana assembla bamboo bugzilla buildkite campfire confluence custom_issue_tracker datadog discord | ||||||
|     drone_ci emails_on_push ewm external_wiki flowdock hangouts_chat hipchat irker jira |     drone_ci emails_on_push ewm external_wiki flowdock hangouts_chat hipchat irker jira | ||||||
|     mattermost mattermost_slash_commands microsoft_teams packagist pipelines_email |     mattermost mattermost_slash_commands microsoft_teams packagist pipelines_email | ||||||
|     pivotaltracker prometheus pushover redmine slack slack_slash_commands teamcity unify_circuit webex_teams youtrack |     pivotaltracker prometheus pushover redmine slack slack_slash_commands teamcity unify_circuit webex_teams youtrack | ||||||
|  | @ -151,6 +151,10 @@ class Service < ApplicationRecord | ||||||
|     %w[commit push tag_push issue confidential_issue merge_request wiki_page] |     %w[commit push tag_push issue confidential_issue merge_request wiki_page] | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   def self.default_test_event | ||||||
|  |     'push' | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   def self.event_description(event) |   def self.event_description(event) | ||||||
|     ServicesHelper.service_event_description(event) |     ServicesHelper.service_event_description(event) | ||||||
|   end |   end | ||||||
|  | @ -390,6 +394,10 @@ class Service < ApplicationRecord | ||||||
|     self.class.supported_events |     self.class.supported_events | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   def default_test_event | ||||||
|  |     self.class.default_test_event | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   def execute(data) |   def execute(data) | ||||||
|     # implement inside child |     # implement inside child | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -16,9 +16,7 @@ module Integrations | ||||||
| 
 | 
 | ||||||
|       def data |       def data | ||||||
|         strong_memoize(:data) do |         strong_memoize(:data) do | ||||||
|           next pipeline_events_data if integration.is_a?(::PipelinesEmailService) |           case event || integration.default_test_event | ||||||
| 
 |  | ||||||
|           case event |  | ||||||
|           when 'push', 'tag_push' |           when 'push', 'tag_push' | ||||||
|             push_events_data |             push_events_data | ||||||
|           when 'note', 'confidential_note' |           when 'note', 'confidential_note' | ||||||
|  | @ -37,8 +35,6 @@ module Integrations | ||||||
|             deployment_events_data |             deployment_events_data | ||||||
|           when 'release' |           when 'release' | ||||||
|             releases_events_data |             releases_events_data | ||||||
|           else |  | ||||||
|             push_events_data |  | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
|  |  | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | --- | ||||||
|  | title: Fix overscroll for MR diffs in mobile view | ||||||
|  | merge_request: 48091 | ||||||
|  | author: | ||||||
|  | type: fixed | ||||||
|  | @ -0,0 +1,7 @@ | ||||||
|  | --- | ||||||
|  | name: datadog_ci_integration | ||||||
|  | introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46564 | ||||||
|  | rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/284088 | ||||||
|  | type: development | ||||||
|  | group: group::ecosystem | ||||||
|  | default_enabled: false | ||||||
|  | @ -0,0 +1,9 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | ActiveRecord::Tasks::DatabaseTasks.structure_load_flags ||= [] | ||||||
|  | 
 | ||||||
|  | flag = '--single-transaction' | ||||||
|  | 
 | ||||||
|  | unless ActiveRecord::Tasks::DatabaseTasks.structure_load_flags.include?(flag) | ||||||
|  |   ActiveRecord::Tasks::DatabaseTasks.structure_load_flags << flag | ||||||
|  | end | ||||||
|  | @ -20593,6 +20593,7 @@ enum ServiceType { | ||||||
|   CAMPFIRE_SERVICE |   CAMPFIRE_SERVICE | ||||||
|   CONFLUENCE_SERVICE |   CONFLUENCE_SERVICE | ||||||
|   CUSTOM_ISSUE_TRACKER_SERVICE |   CUSTOM_ISSUE_TRACKER_SERVICE | ||||||
|  |   DATADOG_SERVICE | ||||||
|   DISCORD_SERVICE |   DISCORD_SERVICE | ||||||
|   DRONE_CI_SERVICE |   DRONE_CI_SERVICE | ||||||
|   EMAILS_ON_PUSH_SERVICE |   EMAILS_ON_PUSH_SERVICE | ||||||
|  |  | ||||||
|  | @ -59807,6 +59807,12 @@ | ||||||
|               "isDeprecated": false, |               "isDeprecated": false, | ||||||
|               "deprecationReason": null |               "deprecationReason": null | ||||||
|             }, |             }, | ||||||
|  |             { | ||||||
|  |               "name": "DATADOG_SERVICE", | ||||||
|  |               "description": null, | ||||||
|  |               "isDeprecated": false, | ||||||
|  |               "deprecationReason": null | ||||||
|  |             }, | ||||||
|             { |             { | ||||||
|               "name": "DISCORD_SERVICE", |               "name": "DISCORD_SERVICE", | ||||||
|               "description": null, |               "description": null, | ||||||
|  |  | ||||||
|  | @ -4427,6 +4427,7 @@ State of a Sentry error. | ||||||
| | `CAMPFIRE_SERVICE` |  | | | `CAMPFIRE_SERVICE` |  | | ||||||
| | `CONFLUENCE_SERVICE` |  | | | `CONFLUENCE_SERVICE` |  | | ||||||
| | `CUSTOM_ISSUE_TRACKER_SERVICE` |  | | | `CUSTOM_ISSUE_TRACKER_SERVICE` |  | | ||||||
|  | | `DATADOG_SERVICE` |  | | ||||||
| | `DISCORD_SERVICE` |  | | | `DISCORD_SERVICE` |  | | ||||||
| | `DRONE_CI_SERVICE` |  | | | `DRONE_CI_SERVICE` |  | | ||||||
| | `EMAILS_ON_PUSH_SERVICE` |  | | | `EMAILS_ON_PUSH_SERVICE` |  | | ||||||
|  |  | ||||||
|  | @ -304,6 +304,38 @@ module API | ||||||
|               desc: 'Project URL' |               desc: 'Project URL' | ||||||
|             } |             } | ||||||
|           ], |           ], | ||||||
|  |           'datadog' => [ | ||||||
|  |             { | ||||||
|  |               required: true, | ||||||
|  |               name: :api_key, | ||||||
|  |               type: String, | ||||||
|  |               desc: 'API key used for authentication with Datadog' | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               required: false, | ||||||
|  |               name: :datadog_site, | ||||||
|  |               type: String, | ||||||
|  |               desc: 'Choose the Datadog site to send data to. Set to "datadoghq.eu" to send data to the EU site' | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               required: false, | ||||||
|  |               name: :api_url, | ||||||
|  |               type: String, | ||||||
|  |               desc: '(Advanced) Define the full URL for your Datadog site directly' | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               required: false, | ||||||
|  |               name: :datadog_service, | ||||||
|  |               type: String, | ||||||
|  |               desc: 'Name of this GitLab instance that all data will be tagged with' | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               required: false, | ||||||
|  |               name: :datadog_env, | ||||||
|  |               type: String, | ||||||
|  |               desc: 'The environment tag that traces will be tagged with' | ||||||
|  |             } | ||||||
|  |           ], | ||||||
|           'discord' => [ |           'discord' => [ | ||||||
|             { |             { | ||||||
|               required: true, |               required: true, | ||||||
|  | @ -784,6 +816,7 @@ module API | ||||||
|           ::ConfluenceService, |           ::ConfluenceService, | ||||||
|           ::CampfireService, |           ::CampfireService, | ||||||
|           ::CustomIssueTrackerService, |           ::CustomIssueTrackerService, | ||||||
|  |           ::DatadogService, | ||||||
|           ::DiscordService, |           ::DiscordService, | ||||||
|           ::DroneCiService, |           ::DroneCiService, | ||||||
|           ::EmailsOnPushService, |           ::EmailsOnPushService, | ||||||
|  |  | ||||||
|  | @ -350,6 +350,7 @@ project: | ||||||
| - services | - services | ||||||
| - campfire_service | - campfire_service | ||||||
| - confluence_service | - confluence_service | ||||||
|  | - datadog_service | ||||||
| - discord_service | - discord_service | ||||||
| - drone_ci_service | - drone_ci_service | ||||||
| - emails_on_push_service | - emails_on_push_service | ||||||
|  |  | ||||||
|  | @ -0,0 +1,179 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | require 'securerandom' | ||||||
|  | 
 | ||||||
|  | require 'spec_helper' | ||||||
|  | 
 | ||||||
|  | RSpec.describe DatadogService, :model do | ||||||
|  |   let_it_be(:project) { create(:project) } | ||||||
|  |   let_it_be(:pipeline) { create(:ci_pipeline, project: project) } | ||||||
|  |   let_it_be(:build) { create(:ci_build, project: project) } | ||||||
|  | 
 | ||||||
|  |   let(:active) { true } | ||||||
|  |   let(:dd_site) { 'datadoghq.com' } | ||||||
|  |   let(:default_url) { 'https://webhooks-http-intake.logs.datadoghq.com/v1/input/' } | ||||||
|  |   let(:api_url) { nil } | ||||||
|  |   let(:api_key) { SecureRandom.hex(32) } | ||||||
|  |   let(:dd_env) { 'ci' } | ||||||
|  |   let(:dd_service) { 'awesome-gitlab' } | ||||||
|  | 
 | ||||||
|  |   let(:expected_hook_url) { default_url + api_key + "?env=#{dd_env}&service=#{dd_service}" } | ||||||
|  | 
 | ||||||
|  |   let(:instance) do | ||||||
|  |     described_class.new( | ||||||
|  |       active: active, | ||||||
|  |       project: project, | ||||||
|  |       properties: { | ||||||
|  |         datadog_site: dd_site, | ||||||
|  |         api_url: api_url, | ||||||
|  |         api_key: api_key, | ||||||
|  |         datadog_env: dd_env, | ||||||
|  |         datadog_service: dd_service | ||||||
|  |       } | ||||||
|  |     ) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   let(:saved_instance) do | ||||||
|  |     instance.save! | ||||||
|  |     instance | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   let(:pipeline_data) { Gitlab::DataBuilder::Pipeline.build(pipeline) } | ||||||
|  |   let(:build_data) { Gitlab::DataBuilder::Build.build(build) } | ||||||
|  | 
 | ||||||
|  |   describe 'associations' do | ||||||
|  |     it { is_expected.to belong_to(:project) } | ||||||
|  |     it { is_expected.to have_one(:service_hook) } | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   describe 'validations' do | ||||||
|  |     subject { instance } | ||||||
|  | 
 | ||||||
|  |     context 'when service is active' do | ||||||
|  |       let(:active) { true } | ||||||
|  | 
 | ||||||
|  |       it { is_expected.to validate_presence_of(:api_key) } | ||||||
|  |       it { is_expected.to allow_value(api_key).for(:api_key) } | ||||||
|  |       it { is_expected.not_to allow_value('87dab2403c9d462 87aec4d9214edb1e').for(:api_key) } | ||||||
|  |       it { is_expected.not_to allow_value('................................').for(:api_key) } | ||||||
|  | 
 | ||||||
|  |       context 'when selecting site' do | ||||||
|  |         let(:dd_site) { 'datadoghq.com' } | ||||||
|  |         let(:api_url) { nil } | ||||||
|  | 
 | ||||||
|  |         it { is_expected.to validate_presence_of(:datadog_site) } | ||||||
|  |         it { is_expected.not_to validate_presence_of(:api_url) } | ||||||
|  |         it { is_expected.not_to allow_value('datadog hq.com').for(:datadog_site) } | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       context 'with custom api_url' do | ||||||
|  |         let(:dd_site) { nil } | ||||||
|  |         let(:api_url) { 'https://webhooks-http-intake.logs.datad0g.com/v1/input/' } | ||||||
|  | 
 | ||||||
|  |         it { is_expected.not_to validate_presence_of(:datadog_site) } | ||||||
|  |         it { is_expected.to validate_presence_of(:api_url) } | ||||||
|  |         it { is_expected.to allow_value(api_url).for(:api_url) } | ||||||
|  |         it { is_expected.not_to allow_value('example.com').for(:api_url) } | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       context 'when missing site and api_url' do | ||||||
|  |         let(:dd_site) { nil } | ||||||
|  |         let(:api_url) { nil } | ||||||
|  | 
 | ||||||
|  |         it { is_expected.not_to be_valid } | ||||||
|  |         it { is_expected.to validate_presence_of(:datadog_site) } | ||||||
|  |         it { is_expected.to validate_presence_of(:api_url) } | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'when service is not active' do | ||||||
|  |       let(:active) { false } | ||||||
|  | 
 | ||||||
|  |       it { is_expected.to be_valid } | ||||||
|  |       it { is_expected.not_to validate_presence_of(:api_key) } | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   describe '#hook_url' do | ||||||
|  |     subject { instance.hook_url } | ||||||
|  | 
 | ||||||
|  |     context 'with standard site URL' do | ||||||
|  |       it { is_expected.to eq(expected_hook_url) } | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'with custom URL' do | ||||||
|  |       let(:api_url) { 'https://webhooks-http-intake.logs.datad0g.com/v1/input/' } | ||||||
|  | 
 | ||||||
|  |       it { is_expected.to eq(api_url + api_key + "?env=#{dd_env}&service=#{dd_service}") } | ||||||
|  | 
 | ||||||
|  |       context 'blank' do | ||||||
|  |         let(:api_url) { '' } | ||||||
|  | 
 | ||||||
|  |         it { is_expected.to eq(expected_hook_url) } | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'without optional params' do | ||||||
|  |       let(:dd_service) { nil } | ||||||
|  |       let(:dd_env) { nil } | ||||||
|  | 
 | ||||||
|  |       it { is_expected.to eq(default_url + api_key) } | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   describe '#api_keys_url' do | ||||||
|  |     subject { instance.api_keys_url } | ||||||
|  | 
 | ||||||
|  |     it { is_expected.to eq("https://app.#{dd_site}/account/settings#api") } | ||||||
|  | 
 | ||||||
|  |     context 'with unset datadog_site' do | ||||||
|  |       let(:dd_site) { nil } | ||||||
|  | 
 | ||||||
|  |       it { is_expected.to eq("https://docs.datadoghq.com/account_management/api-app-keys/") } | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   describe '#test' do | ||||||
|  |     context 'when request is succesful' do | ||||||
|  |       subject { saved_instance.test(pipeline_data) } | ||||||
|  | 
 | ||||||
|  |       before do | ||||||
|  |         stub_request(:post, expected_hook_url).to_return(body: 'OK') | ||||||
|  |       end | ||||||
|  |       it { is_expected.to eq({ success: true, result: 'OK' }) } | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'when request fails' do | ||||||
|  |       subject { saved_instance.test(pipeline_data) } | ||||||
|  | 
 | ||||||
|  |       before do | ||||||
|  |         stub_request(:post, expected_hook_url).to_return(body: 'CRASH!!!', status: 500) | ||||||
|  |       end | ||||||
|  |       it { is_expected.to eq({ success: false, result: 'CRASH!!!' }) } | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   describe '#execute' do | ||||||
|  |     before do | ||||||
|  |       stub_request(:post, expected_hook_url) | ||||||
|  |       saved_instance.execute(data) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'with pipeline data' do | ||||||
|  |       let(:data) { pipeline_data } | ||||||
|  |       let(:expected_headers) do | ||||||
|  |         { WebHookService::GITLAB_EVENT_HEADER => 'Pipeline Hook' } | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it { expect(a_request(:post, expected_hook_url).with(headers: expected_headers)).to have_been_made } | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'with job data' do | ||||||
|  |       let(:data) { build_data } | ||||||
|  |       let(:expected_headers) do | ||||||
|  |         { WebHookService::GITLAB_EVENT_HEADER => 'Job Hook' } | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it { expect(a_request(:post, expected_hook_url).with(headers: expected_headers)).to have_been_made } | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -5580,6 +5580,26 @@ RSpec.describe Project, factory_default: :keep do | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   describe '#disabled_services' do | ||||||
|  |     subject { build(:project).disabled_services } | ||||||
|  | 
 | ||||||
|  |     context 'without datadog_ci_integration' do | ||||||
|  |       before do | ||||||
|  |         stub_feature_flags(datadog_ci_integration: false) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it { is_expected.to include('datadog') } | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'with datadog_ci_integration' do | ||||||
|  |       before do | ||||||
|  |         stub_feature_flags(datadog_ci_integration: true) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it { is_expected.not_to include('datadog') } | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   describe '#find_or_initialize_service' do |   describe '#find_or_initialize_service' do | ||||||
|     it 'avoids N+1 database queries' do |     it 'avoids N+1 database queries' do | ||||||
|       allow(Service).to receive(:available_services_names).and_return(%w[prometheus pushover]) |       allow(Service).to receive(:available_services_names).and_return(%w[prometheus pushover]) | ||||||
|  |  | ||||||
|  | @ -76,7 +76,9 @@ RSpec.describe API::Services do | ||||||
| 
 | 
 | ||||||
|         required_attributes = service_attrs_list.select do |attr| |         required_attributes = service_attrs_list.select do |attr| | ||||||
|           service_klass.validators_on(attr).any? do |v| |           service_klass.validators_on(attr).any? do |v| | ||||||
|             v.class == ActiveRecord::Validations::PresenceValidator |             v.class == ActiveRecord::Validations::PresenceValidator && | ||||||
|  |             # exclude presence validators with conditional since those are not really required | ||||||
|  |             ![:if, :unless].any? { |cond| v.options.include?(cond) } | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -16,6 +16,8 @@ Service.available_services_names.each do |service| | ||||||
|           hash.merge!(k => 'secrettoken') |           hash.merge!(k => 'secrettoken') | ||||||
|         elsif service == 'confluence' && k == :confluence_url |         elsif service == 'confluence' && k == :confluence_url | ||||||
|           hash.merge!(k => 'https://example.atlassian.net/wiki') |           hash.merge!(k => 'https://example.atlassian.net/wiki') | ||||||
|  |         elsif service == 'datadog' && k == :datadog_site | ||||||
|  |           hash.merge!(k => 'datadoghq.com') | ||||||
|         elsif k =~ /^(.*_url|url|webhook)/ |         elsif k =~ /^(.*_url|url|webhook)/ | ||||||
|           hash.merge!(k => "http://example.com") |           hash.merge!(k => "http://example.com") | ||||||
|         elsif service_klass.method_defined?("#{k}?") |         elsif service_klass.method_defined?("#{k}?") | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue