diff options
Diffstat (limited to 'app')
10 files changed, 151 insertions, 176 deletions
diff --git a/app/assets/stylesheets/page_bundles/oncall_schedules.scss b/app/assets/stylesheets/page_bundles/oncall_schedules.scss index 51bffd99dd0..10cc6cbd78e 100644 --- a/app/assets/stylesheets/page_bundles/oncall_schedules.scss +++ b/app/assets/stylesheets/page_bundles/oncall_schedules.scss @@ -4,17 +4,6 @@ box-shadow: inset 0 0 0 $gl-border-size-1 $red-500 if-important($important); } -.timezone-dropdown { - .gl-dropdown-item-text-primary { - @include gl-overflow-hidden; - @include gl-text-overflow-ellipsis; - } - - .btn-block { - margin-bottom: 0; - } -} - .modal-footer { @include gl-bg-gray-10; } @@ -52,65 +41,17 @@ $scroll-top-gradient: linear-gradient(to bottom, $gradient-dark-gray 0%, $gradie $scroll-bottom-gradient: linear-gradient(to bottom, $gradient-gray 0%, $gradient-dark-gray 100%); $column-right-gradient: linear-gradient(to right, $gradient-dark-gray 0%, $gradient-gray 100%); -.schedule-shell { - @include gl-relative; - @include gl-h-full; - @include gl-w-full; - @include gl-overflow-x-auto; -} - .timeline-section { - @include gl-sticky; - @include gl-top-0; z-index: 20; - .timeline-header-label, - .timeline-header-item { - @include gl-float-left; - } - .timeline-header-label { - @include gl-sticky; - @include gl-top-0; - @include gl-left-0; width: $details-cell-width; - z-index: 2; } .timeline-header-item { - .item-sublabel .sublabel-value { - color: var(--gray-700, $gray-700); - @include gl-font-weight-normal; - - &.label-dark { - color: var(--gray-900, $gray-900); - } - - &.label-bold { - @include gl-font-weight-bold; - } - } - - .item-sublabel { - @include gl-relative; - @include gl-display-flex; - - .sublabel-value { - @include gl-flex-grow-1; - @include gl-flex-basis-0; - - text-align: center; - @include gl-font-base; - } - } - .current-day-indicator-header { - @include gl-absolute; - @include gl-bottom-0; height: $grid-size; width: $grid-size; - background-color: var(--red-500, $red-500); - @include gl-rounded-full; transform: translate(-50%, 50%); } @@ -137,35 +78,19 @@ $column-right-gradient: linear-gradient(to right, $gradient-dark-gray 0%, $gradi .details-cell, .timeline-cell { - @include gl-float-left; height: $item-height; } .details-cell { - @include gl-sticky; - @include gl-left-0; width: $details-cell-width; - @include gl-font-base; z-index: 10; } .timeline-cell { - @include gl-relative; - @include gl-bg-transparent; - border-right: $border-style; - - &:last-child { - @include gl-border-r-0; - } - .current-day-indicator { - @include gl-absolute; top: -1px; width: $gl-spacing-scale-1; height: calc(100% + 1px); - background-color: var(--red-500, $red-500); - @include gl-pointer-events-none; - transform: translateX(-50%); } } diff --git a/app/finders/alert_management/http_integrations_finder.rb b/app/finders/alert_management/http_integrations_finder.rb index e8e85da11b7..77a3824576f 100644 --- a/app/finders/alert_management/http_integrations_finder.rb +++ b/app/finders/alert_management/http_integrations_finder.rb @@ -2,7 +2,9 @@ module AlertManagement class HttpIntegrationsFinder - def initialize(project, params) + TYPE_IDENTIFIERS = ::AlertManagement::HttpIntegration.type_identifiers + + def initialize(project, params = {}) @project = project @params = params end @@ -13,6 +15,7 @@ module AlertManagement filter_by_availability filter_by_endpoint_identifier filter_by_active + filter_by_type collection end @@ -21,15 +24,13 @@ module AlertManagement attr_reader :project, :params, :collection + # Overridden in EE def filter_by_availability - return if multiple_alert_http_integrations? - - first_id = project.alert_management_http_integrations - .ordered_by_id - .select(:id) - .limit(1) - - @collection = collection.id_in(first_id) + # Re-find by id so subsequent filters don't expose unavailable records + @collection = collection.id_in(collection + .select('DISTINCT ON (type_identifier) id') + .ordered_by_type_and_id + .limit(TYPE_IDENTIFIERS.length)) end def filter_by_endpoint_identifier @@ -44,9 +45,11 @@ module AlertManagement @collection = collection.active end - # Overridden in EE - def multiple_alert_http_integrations? - false + def filter_by_type + return unless params[:type_identifier] + return unless TYPE_IDENTIFIERS.include?(params[:type_identifier]) + + @collection = collection.for_type(params[:type_identifier]) end end end diff --git a/app/models/alert_management/http_integration.rb b/app/models/alert_management/http_integration.rb index 906855d6dfc..d5162865a79 100644 --- a/app/models/alert_management/http_integration.rb +++ b/app/models/alert_management/http_integration.rb @@ -3,8 +3,8 @@ module AlertManagement class HttpIntegration < ApplicationRecord include ::Gitlab::Routing + LEGACY_IDENTIFIER = 'legacy' - DEFAULT_NAME_SLUG = 'http-endpoint' belongs_to :project, inverse_of: :alert_management_http_integrations @@ -19,6 +19,7 @@ module AlertManagement validates :active, inclusion: { in: [true, false] } validates :token, presence: true, format: { with: /\A\h{32}\z/ } validates :name, presence: true, length: { maximum: 255 } + validates :type_identifier, presence: true validates :endpoint_identifier, presence: true, length: { maximum: 255 }, format: { with: /\A[A-Za-z0-9]+\z/ } validates :endpoint_identifier, uniqueness: { scope: [:project_id, :active] }, if: :active? validates :payload_attribute_mapping, json_schema: { filename: 'http_integration_payload_attribute_mapping' } @@ -29,15 +30,30 @@ module AlertManagement before_validation :ensure_payload_example_not_nil scope :for_endpoint_identifier, ->(endpoint_identifier) { where(endpoint_identifier: endpoint_identifier) } + scope :for_type, ->(type) { where(type_identifier: type) } + scope :for_project, ->(project_ids) { where(project: project_ids) } scope :active, -> { where(active: true) } - scope :ordered_by_id, -> { order(:id) } + scope :legacy, -> { for_endpoint_identifier(LEGACY_IDENTIFIER) } + scope :ordered_by_type_and_id, -> { order(:type_identifier, :id) } + + enum type_identifier: { + http: 0, + prometheus: 1 + } def url - return project_alerts_notify_url(project, format: :json) if legacy? + if legacy? + return project_alerts_notify_url(project, format: :json) if http? + return notify_project_prometheus_alerts_url(project, format: :json) if prometheus? + end project_alert_http_integration_url(project, name_slug, endpoint_identifier, format: :json) end + def legacy? + endpoint_identifier == LEGACY_IDENTIFIER + end + private def self.generate_token @@ -45,11 +61,7 @@ module AlertManagement end def name_slug - (name && Gitlab::Utils.slugify(name)) || DEFAULT_NAME_SLUG - end - - def legacy? - endpoint_identifier == LEGACY_IDENTIFIER + (name && Gitlab::Utils.slugify(name)) || "#{type_identifier}-endpoint" end # Blank token assignment triggers token reset diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 7b1d4b97d3b..b7e39423e85 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -95,9 +95,9 @@ class MergeRequest < ApplicationRecord dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent has_many :cached_closes_issues, through: :merge_requests_closing_issues, source: :issue - has_many :pipelines_for_merge_request, foreign_key: 'merge_request_id', class_name: 'Ci::Pipeline' + has_many :pipelines_for_merge_request, foreign_key: 'merge_request_id', class_name: 'Ci::Pipeline', inverse_of: :merge_request has_many :suggestions, through: :notes - has_many :unresolved_notes, -> { unresolved }, as: :noteable, class_name: 'Note' + has_many :unresolved_notes, -> { unresolved }, as: :noteable, class_name: 'Note', inverse_of: :noteable has_many :merge_request_assignees has_many :assignees, class_name: "User", through: :merge_request_assignees diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 0e699d7a81d..d36857fc94a 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -32,7 +32,7 @@ class MergeRequestDiff < ApplicationRecord -> { order(:merge_request_diff_id, :relative_order) }, inverse_of: :merge_request_diff - has_many :merge_request_diff_commits, -> { order(:merge_request_diff_id, :relative_order) } + has_many :merge_request_diff_commits, -> { order(:merge_request_diff_id, :relative_order) }, inverse_of: :merge_request_diff validates :base_commit_sha, :head_commit_sha, :start_commit_sha, sha: true validates :merge_request_id, uniqueness: { scope: :diff_type }, if: :merge_head? diff --git a/app/services/alert_management/http_integrations/base_service.rb b/app/services/alert_management/http_integrations/base_service.rb new file mode 100644 index 00000000000..980f18631c0 --- /dev/null +++ b/app/services/alert_management/http_integrations/base_service.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module AlertManagement + module HttpIntegrations + class BaseService < BaseProjectService + # @param project [Project] + # @param current_user [User] + # @param params [Hash] + def initialize(project, current_user, params) + @response = nil + + super(project: project, current_user: current_user, params: params.with_indifferent_access) + end + + private + + def allowed? + current_user&.can?(:admin_operations, project) + end + + def too_many_integrations?(integration) + AlertManagement::HttpIntegration + .for_project(integration.project_id) + .for_type(integration.type_identifier) + .id_not_in(integration.id) + .any? + end + + def permitted_params + params.slice(*permitted_params_keys) + end + + # overriden in EE + def permitted_params_keys + %i[name active type_identifier] + end + + def error(message) + ServiceResponse.error(message: message) + end + + def success(integration) + ServiceResponse.success(payload: { integration: integration.reset }) + end + + def error_multiple_integrations + error(_('Multiple integrations of a single type are not supported for this project')) + end + + def error_on_save(integration) + error(integration.errors.full_messages.to_sentence) + end + end + end +end + +::AlertManagement::HttpIntegrations::BaseService.prepend_mod diff --git a/app/services/alert_management/http_integrations/create_service.rb b/app/services/alert_management/http_integrations/create_service.rb index 1abe0548c45..17e39577c29 100644 --- a/app/services/alert_management/http_integrations/create_service.rb +++ b/app/services/alert_management/http_integrations/create_service.rb @@ -2,68 +2,34 @@ module AlertManagement module HttpIntegrations - class CreateService - # @param project [Project] - # @param current_user [User] - # @param params [Hash] - def initialize(project, current_user, params) - @project = project - @current_user = current_user - @params = params.with_indifferent_access - end - + class CreateService < BaseService def execute return error_no_permissions unless allowed? - return error_multiple_integrations unless creation_allowed? - - integration = project.alert_management_http_integrations.create(permitted_params) - return error_in_create(integration) unless integration.valid? - - success(integration) - end - private + ::AlertManagement::HttpIntegration.transaction do + integration = project.alert_management_http_integrations.build(permitted_params) - attr_reader :project, :current_user, :params + if integration.save + @response = success(integration) - def allowed? - current_user&.can?(:admin_operations, project) - end + if too_many_integrations?(integration) + @response = error_multiple_integrations - def creation_allowed? - project.alert_management_http_integrations.empty? - end - - def permitted_params - params.slice(*permitted_params_keys) - end + raise ActiveRecord::Rollback + end + else + @response = error_on_save(integration) + end + end - # overriden in EE - def permitted_params_keys - %i[name active] + @response end - def error(message) - ServiceResponse.error(message: message) - end - - def success(integration) - ServiceResponse.success(payload: { integration: integration }) - end + private def error_no_permissions error(_('You have insufficient permissions to create an HTTP integration for this project')) end - - def error_multiple_integrations - error(_('Multiple HTTP integrations are not supported for this project')) - end - - def error_in_create(integration) - error(integration.errors.full_messages.to_sentence) - end end end end - -::AlertManagement::HttpIntegrations::CreateService.prepend_mod_with('AlertManagement::HttpIntegrations::CreateService') diff --git a/app/services/alert_management/http_integrations/destroy_service.rb b/app/services/alert_management/http_integrations/destroy_service.rb index aeb3f6cb807..1bd73ca46e4 100644 --- a/app/services/alert_management/http_integrations/destroy_service.rb +++ b/app/services/alert_management/http_integrations/destroy_service.rb @@ -12,6 +12,7 @@ module AlertManagement def execute return error_no_permissions unless allowed? + return error_legacy_prometheus unless destroy_allowed? if integration.destroy success @@ -28,6 +29,12 @@ module AlertManagement current_user&.can?(:admin_operations, integration) end + # Prevents downtime while migrating from Integrations::Prometheus. + # Remove with https://gitlab.com/gitlab-org/gitlab/-/issues/409734 + def destroy_allowed? + !(integration.legacy? && integration.prometheus?) + end + def error(message) ServiceResponse.error(message: message) end @@ -39,6 +46,10 @@ module AlertManagement def error_no_permissions error(_('You have insufficient permissions to remove this HTTP integration')) end + + def error_legacy_prometheus + error(_('Legacy Prometheus integrations cannot currently be removed')) + end end end end diff --git a/app/services/alert_management/http_integrations/update_service.rb b/app/services/alert_management/http_integrations/update_service.rb index 8662f966a2e..f7a079576e4 100644 --- a/app/services/alert_management/http_integrations/update_service.rb +++ b/app/services/alert_management/http_integrations/update_service.rb @@ -2,51 +2,48 @@ module AlertManagement module HttpIntegrations - class UpdateService + class UpdateService < BaseService # @param integration [AlertManagement::HttpIntegration] # @param current_user [User] # @param params [Hash] def initialize(integration, current_user, params) @integration = integration - @current_user = current_user - @params = params.with_indifferent_access + + super(integration.project, current_user, params) end def execute return error_no_permissions unless allowed? - params[:token] = nil if params.delete(:regenerate_token) + integration.transaction do + if integration.update(permitted_params.merge(token_params)) + @response = success(integration) + + if type_update? && too_many_integrations?(integration) + @response = error_multiple_integrations - if integration.update(permitted_params) - success - else - error(integration.errors.full_messages.to_sentence) + raise ActiveRecord::Rollback + end + else + @response = error_on_save(integration) + end end + + @response end private - attr_reader :integration, :current_user, :params + attr_reader :integration - def allowed? - current_user&.can?(:admin_operations, integration) - end + def token_params + return {} unless params[:regenerate_token] - def permitted_params - params.slice(*permitted_params_keys) + { token: nil } end - # overriden in EE - def permitted_params_keys - %i[name active token] - end - - def error(message) - ServiceResponse.error(message: message) - end - - def success - ServiceResponse.success(payload: { integration: integration.reset }) + def type_update? + params[:type_identifier].present? end def error_no_permissions @@ -55,5 +52,3 @@ module AlertManagement end end end - -::AlertManagement::HttpIntegrations::UpdateService.prepend_mod_with('AlertManagement::HttpIntegrations::UpdateService') diff --git a/app/services/projects/prometheus/alerts/notify_service.rb b/app/services/projects/prometheus/alerts/notify_service.rb index 1e084c0e5eb..1d24a113e05 100644 --- a/app/services/projects/prometheus/alerts/notify_service.rb +++ b/app/services/projects/prometheus/alerts/notify_service.rb @@ -79,12 +79,18 @@ module Projects end def valid_alert_manager_token?(token, integration) - valid_for_manual?(token) || - valid_for_alerts_endpoint?(token, integration) || + valid_for_alerts_endpoint?(token, integration) || + valid_for_manual?(token) || valid_for_cluster?(token) end def valid_for_manual?(token) + # If migration from Integrations::Prometheus to + # AlertManagement::HttpIntegrations is complete, + # we should use use the HttpIntegration as SSOT. + # Remove with https://gitlab.com/gitlab-org/gitlab/-/issues/409734 + return false if project.alert_management_http_integrations.legacy.prometheus.any? + prometheus = project.find_or_initialize_integration('prometheus') return false unless prometheus.manual_configuration? |