diff --git a/app/controllers/concerns/integrations/params.rb b/app/controllers/concerns/integrations/params.rb
index bfe5855b8f2..fa8fbc37588 100644
--- a/app/controllers/concerns/integrations/params.rb
+++ b/app/controllers/concerns/integrations/params.rb
@@ -50,6 +50,7 @@ module Integrations
:google_play_protected_refs,
:group_confidential_mention_events,
:group_mention_events,
+ :hostname,
:incident_events,
:inherit_from_id,
# We're using `issues_events` and `merge_requests_events`
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 3d59d531e59..75417c90674 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -48,8 +48,9 @@ class Projects::PipelinesController < Projects::ApplicationController
feature_category :continuous_integration, [
:charts, :show, :stage, :cancel, :retry,
:builds, :dag, :failures, :status,
- :index, :create, :new, :destroy
+ :index, :new, :destroy
]
+ feature_category :pipeline_composition, [:create]
feature_category :code_testing, [:test_report]
feature_category :build_artifacts, [:downloadable_artifacts]
diff --git a/app/finders/members/pending_invitations_finder.rb b/app/finders/members/pending_invitations_finder.rb
new file mode 100644
index 00000000000..c484add9fe6
--- /dev/null
+++ b/app/finders/members/pending_invitations_finder.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Members
+ class PendingInvitationsFinder
+ def initialize(invite_emails)
+ @invite_emails = invite_emails
+ end
+
+ def execute
+ Member.with_case_insensitive_invite_emails(invite_emails)
+ .invite
+ .distinct_on_source_and_case_insensitive_invite_email
+ .order_updated_desc
+ end
+
+ private
+
+ attr_reader :invite_emails
+ end
+end
diff --git a/app/models/integrations/telegram.rb b/app/models/integrations/telegram.rb
index 8d54abb7cd6..3174ab2a764 100644
--- a/app/models/integrations/telegram.rb
+++ b/app/models/integrations/telegram.rb
@@ -3,7 +3,14 @@
module Integrations
class Telegram < BaseChatNotification
include HasAvatar
- TELEGRAM_HOSTNAME = "https://api.telegram.org/bot%{token}/sendMessage"
+ TELEGRAM_HOSTNAME = "%{hostname}/bot%{token}/sendMessage"
+
+ field :hostname,
+ section: SECTION_TYPE_CONNECTION,
+ help: 'Custom hostname of the Telegram API. The default value is `https://api.telegram.org`.',
+ placeholder: 'https://api.telegram.org',
+ exposes_secrets: true,
+ required: false
field :token,
section: SECTION_TYPE_CONNECTION,
@@ -78,7 +85,8 @@ module Integrations
private
def set_webhook
- self.webhook = format(TELEGRAM_HOSTNAME, token: token) if token.present?
+ hostname = self.hostname.presence || 'https://api.telegram.org'
+ self.webhook = format(TELEGRAM_HOSTNAME, hostname: hostname, token: token) if token.present?
end
def notify(message, _opts)
diff --git a/app/models/member.rb b/app/models/member.rb
index 216e50d63f3..13a89174f35 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -143,6 +143,9 @@ class Member < ApplicationRecord
scope :invite, -> { where.not(invite_token: nil) }
scope :non_invite, -> { where(invite_token: nil) }
+ scope :with_case_insensitive_invite_emails, ->(emails) do
+ where(arel_table[:invite_email].lower.in(emails.map(&:downcase)))
+ end
scope :request, -> { where.not(requested_at: nil) }
scope :non_request, -> { where(requested_at: nil) }
@@ -208,6 +211,11 @@ class Member < ApplicationRecord
unscoped.from(distinct_members, :members)
end
+ scope :distinct_on_source_and_case_insensitive_invite_email, -> do
+ select('DISTINCT ON (source_id, source_type, LOWER(invite_email)) members.*')
+ .order('source_id, source_type, LOWER(invite_email)')
+ end
+
scope :order_name_asc, -> do
build_keyset_order_on_joined_column(
scope: left_join_users,
@@ -288,6 +296,8 @@ class Member < ApplicationRecord
)
end
+ scope :order_updated_desc, -> { order(updated_at: :desc) }
+
scope :on_project_and_ancestors, ->(project) { where(source: [project] + project.ancestors) }
before_validation :set_member_namespace_id, on: :create
diff --git a/app/models/user.rb b/app/models/user.rb
index 120694b638f..e58e57645fd 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1644,7 +1644,7 @@ class User < MainClusterwide::ApplicationRecord
end
def pending_invitations
- Member.where(invite_email: verified_emails).invite
+ Members::PendingInvitationsFinder.new(verified_emails).execute
end
def all_emails(include_private_email: true)
diff --git a/app/services/design_management/delete_designs_service.rb b/app/services/design_management/delete_designs_service.rb
index a6a0f5e0252..c28c4e70a60 100644
--- a/app/services/design_management/delete_designs_service.rb
+++ b/app/services/design_management/delete_designs_service.rb
@@ -4,6 +4,7 @@ module DesignManagement
class DeleteDesignsService < DesignService
include RunsDesignActions
include OnSuccessCallbacks
+ include Gitlab::InternalEventsTracking
def initialize(project, user, params = {})
super
@@ -55,16 +56,12 @@ module DesignManagement
def design_action(design)
on_success do
- counter.count(:delete)
+ track_internal_event('delete_design_management_design', user: current_user, project: project)
end
DesignManagement::DesignAction.new(design, :delete)
end
- def counter
- ::Gitlab::UsageDataCounters::DesignsCounter
- end
-
def formatted_file_list
designs.map { |design| "- #{design.full_path}" }.join("\n")
end
diff --git a/app/services/design_management/save_designs_service.rb b/app/services/design_management/save_designs_service.rb
index f9f2f4bf290..438b07c1b1f 100644
--- a/app/services/design_management/save_designs_service.rb
+++ b/app/services/design_management/save_designs_service.rb
@@ -4,6 +4,7 @@ module DesignManagement
class SaveDesignsService < DesignService
include RunsDesignActions
include OnSuccessCallbacks
+ include Gitlab::InternalEventsTracking
MAX_FILES = 10
@@ -133,12 +134,12 @@ module DesignManagement
if action == :update
::Gitlab::UsageDataCounters::IssueActivityUniqueCounter
.track_issue_designs_modified_action(author: current_user, project: project)
+ track_internal_event('update_design_management_design', user: current_user, project: project)
else
::Gitlab::UsageDataCounters::IssueActivityUniqueCounter
.track_issue_designs_added_action(author: current_user, project: project)
+ track_internal_event('create_design_management_design', user: current_user, project: project)
end
-
- ::Gitlab::UsageDataCounters::DesignsCounter.count(action)
end
end
end
diff --git a/app/validators/feature_flag_strategies_validator.rb b/app/validators/feature_flag_strategies_validator.rb
index 6008074414b..ffc8b645a0d 100644
--- a/app/validators/feature_flag_strategies_validator.rb
+++ b/app/validators/feature_flag_strategies_validator.rb
@@ -28,9 +28,9 @@ class FeatureFlagStrategiesValidator < ActiveModel::EachValidator
def strategy_validations(record, attribute, strategy)
validate_name(record, attribute, strategy) &&
- validate_parameters_type(record, attribute, strategy) &&
- validate_parameters_keys(record, attribute, strategy) &&
- validate_parameters_values(record, attribute, strategy)
+ validate_parameters_type(record, attribute, strategy) &&
+ validate_parameters_keys(record, attribute, strategy) &&
+ validate_parameters_values(record, attribute, strategy)
end
def validate_name(record, attribute, strategy)
@@ -79,13 +79,13 @@ class FeatureFlagStrategiesValidator < ActiveModel::EachValidator
def valid_ids?(user_ids)
user_ids.uniq.length == user_ids.length &&
- user_ids.all? { |id| valid_id?(id) }
+ user_ids.all? { |id| valid_id?(id) }
end
def valid_id?(user_id)
user_id.present? &&
- user_id.strip == user_id &&
- user_id.length <= USERID_MAX_LENGTH
+ user_id.strip == user_id &&
+ user_id.length <= USERID_MAX_LENGTH
end
def error(record, attribute, msg)
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 027ebc85130..0c9db9952e4 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -2012,7 +2012,7 @@
:tags: []
- :name: pipeline_creation:ci_external_pull_requests_create_pipeline
:worker_name: Ci::ExternalPullRequests::CreatePipelineWorker
- :feature_category: :continuous_integration
+ :feature_category: :pipeline_composition
:has_external_dependencies: false
:urgency: :high
:resource_boundary: :cpu
@@ -2021,7 +2021,7 @@
:tags: []
- :name: pipeline_creation:create_pipeline
:worker_name: CreatePipelineWorker
- :feature_category: :continuous_integration
+ :feature_category: :pipeline_composition
:has_external_dependencies: false
:urgency: :high
:resource_boundary: :cpu
@@ -2030,7 +2030,7 @@
:tags: []
- :name: pipeline_creation:merge_requests_create_pipeline
:worker_name: MergeRequests::CreatePipelineWorker
- :feature_category: :continuous_integration
+ :feature_category: :pipeline_composition
:has_external_dependencies: false
:urgency: :high
:resource_boundary: :cpu
@@ -2039,7 +2039,7 @@
:tags: []
- :name: pipeline_creation:run_pipeline_schedule
:worker_name: RunPipelineScheduleWorker
- :feature_category: :continuous_integration
+ :feature_category: :pipeline_composition
:has_external_dependencies: false
:urgency: :low
:resource_boundary: :unknown
diff --git a/app/workers/ci/external_pull_requests/create_pipeline_worker.rb b/app/workers/ci/external_pull_requests/create_pipeline_worker.rb
index 334ff099ea2..87df13a105b 100644
--- a/app/workers/ci/external_pull_requests/create_pipeline_worker.rb
+++ b/app/workers/ci/external_pull_requests/create_pipeline_worker.rb
@@ -7,7 +7,7 @@ module Ci
data_consistency :always
queue_namespace :pipeline_creation
- feature_category :continuous_integration
+ feature_category :pipeline_composition
urgency :high
worker_resource_boundary :cpu
diff --git a/app/workers/container_expiration_policies/cleanup_container_repository_worker.rb b/app/workers/container_expiration_policies/cleanup_container_repository_worker.rb
index 4e946b09720..44aa8081e1f 100644
--- a/app/workers/container_expiration_policies/cleanup_container_repository_worker.rb
+++ b/app/workers/container_expiration_policies/cleanup_container_repository_worker.rb
@@ -171,8 +171,8 @@ module ContainerExpirationPolicies
before_truncate_size = result.payload[:cleanup_tags_service_before_truncate_size]
after_truncate_size = result.payload[:cleanup_tags_service_after_truncate_size]
truncated = before_truncate_size &&
- after_truncate_size &&
- before_truncate_size != after_truncate_size
+ after_truncate_size &&
+ before_truncate_size != after_truncate_size
log_extra_metadata_on_done(:cleanup_tags_service_truncated, !!truncated)
end
diff --git a/app/workers/create_pipeline_worker.rb b/app/workers/create_pipeline_worker.rb
index eb02fe72294..3c442760d38 100644
--- a/app/workers/create_pipeline_worker.rb
+++ b/app/workers/create_pipeline_worker.rb
@@ -9,7 +9,7 @@ class CreatePipelineWorker # rubocop:disable Scalability/IdempotentWorker
include PipelineQueue
queue_namespace :pipeline_creation
- feature_category :continuous_integration
+ feature_category :pipeline_composition
urgency :high
worker_resource_boundary :cpu
loggable_arguments 2, 3, 4
diff --git a/app/workers/merge_requests/create_pipeline_worker.rb b/app/workers/merge_requests/create_pipeline_worker.rb
index 63bd6e43206..e92e557284c 100644
--- a/app/workers/merge_requests/create_pipeline_worker.rb
+++ b/app/workers/merge_requests/create_pipeline_worker.rb
@@ -10,7 +10,7 @@ module MergeRequests
include PipelineQueue
queue_namespace :pipeline_creation
- feature_category :continuous_integration
+ feature_category :pipeline_composition
urgency :high
worker_resource_boundary :cpu
idempotent!
diff --git a/app/workers/run_pipeline_schedule_worker.rb b/app/workers/run_pipeline_schedule_worker.rb
index 99d36390aa8..deef7c46d3a 100644
--- a/app/workers/run_pipeline_schedule_worker.rb
+++ b/app/workers/run_pipeline_schedule_worker.rb
@@ -9,7 +9,7 @@ class RunPipelineScheduleWorker # rubocop:disable Scalability/IdempotentWorker
include PipelineQueue
queue_namespace :pipeline_creation
- feature_category :continuous_integration
+ feature_category :pipeline_composition
deduplicate :until_executed, including_scheduled: true
idempotent!
diff --git a/config/events/create_design_management_design.yml b/config/events/create_design_management_design.yml
new file mode 100644
index 00000000000..ded70341bf9
--- /dev/null
+++ b/config/events/create_design_management_design.yml
@@ -0,0 +1,20 @@
+---
+description: A design is created
+internal_events: true
+action: create_design_management_design
+identifiers:
+- project
+- namespace
+- user
+product_section: dev
+product_stage: plan
+product_group: product_planning
+milestone: '17.0'
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/150994
+distributions:
+- ce
+- ee
+tiers:
+- free
+- premium
+- ultimate
diff --git a/config/events/delete_design_management_design.yml b/config/events/delete_design_management_design.yml
new file mode 100644
index 00000000000..20bbc114128
--- /dev/null
+++ b/config/events/delete_design_management_design.yml
@@ -0,0 +1,20 @@
+---
+description: A design is deleted
+internal_events: true
+action: delete_design_management_design
+identifiers:
+- project
+- namespace
+- user
+product_section: dev
+product_stage: plan
+product_group: product_planning
+milestone: '17.0'
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/150994
+distributions:
+- ce
+- ee
+tiers:
+- free
+- premium
+- ultimate
diff --git a/config/events/update_design_management_design.yml b/config/events/update_design_management_design.yml
new file mode 100644
index 00000000000..bb0c6e7ae64
--- /dev/null
+++ b/config/events/update_design_management_design.yml
@@ -0,0 +1,20 @@
+---
+description: A design is updated
+internal_events: true
+action: update_design_management_design
+identifiers:
+- project
+- namespace
+- user
+product_section: dev
+product_stage: plan
+product_group: product_planning
+milestone: '17.0'
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/150994
+distributions:
+- ce
+- ee
+tiers:
+- free
+- premium
+- ultimate
diff --git a/config/initializers/devise_dynamic_password_length_validation.rb b/config/initializers/devise_dynamic_password_length_validation.rb
index 03d613da929..59bfbe3a166 100644
--- a/config/initializers/devise_dynamic_password_length_validation.rb
+++ b/config/initializers/devise_dynamic_password_length_validation.rb
@@ -12,7 +12,7 @@
def length_validator_supports_dynamic_length_checks?(validator)
validator.options[:minimum].is_a?(Proc) &&
- validator.options[:maximum].is_a?(Proc)
+ validator.options[:maximum].is_a?(Proc)
end
# Get the in-built Devise validator on password length.
diff --git a/config/metrics/counts_all/20210216180740_design_management_designs_create.yml b/config/metrics/counts_all/20210216180740_design_management_designs_create.yml
index 6b015860613..20f37904d13 100644
--- a/config/metrics/counts_all/20210216180740_design_management_designs_create.yml
+++ b/config/metrics/counts_all/20210216180740_design_management_designs_create.yml
@@ -8,11 +8,9 @@ product_group: product_planning
value_type: number
status: active
time_frame: all
-data_source: redis
-instrumentation_class: RedisMetric
-options:
- prefix: design_management_designs
- event: create
+data_source: internal_events
+events:
+ - name: create_design_management_design
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20210216180741_design_management_designs_update.yml b/config/metrics/counts_all/20210216180741_design_management_designs_update.yml
index 320921048fd..6d972a82ffd 100644
--- a/config/metrics/counts_all/20210216180741_design_management_designs_update.yml
+++ b/config/metrics/counts_all/20210216180741_design_management_designs_update.yml
@@ -8,11 +8,9 @@ product_group: product_planning
value_type: number
status: active
time_frame: all
-data_source: redis
-instrumentation_class: RedisMetric
-options:
- prefix: design_management_designs
- event: update
+data_source: internal_events
+events:
+ - name: update_design_management_design
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20210216180743_design_management_designs_delete.yml b/config/metrics/counts_all/20210216180743_design_management_designs_delete.yml
index 156c1a481e6..65eec1d3e15 100644
--- a/config/metrics/counts_all/20210216180743_design_management_designs_delete.yml
+++ b/config/metrics/counts_all/20210216180743_design_management_designs_delete.yml
@@ -8,11 +8,9 @@ product_group: product_planning
value_type: number
status: active
time_frame: all
-data_source: redis
-instrumentation_class: RedisMetric
-options:
- prefix: design_management_designs
- event: delete
+data_source: internal_events
+events:
+ - name: delete_design_management_design
distribution:
- ce
- ee
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 725c6616ab6..4503c43140a 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -701,6 +701,8 @@
- 1
- - search_zoekt_delete_project
- 1
+- - search_zoekt_indexing_task
+ - 1
- - search_zoekt_namespace_indexer
- 1
- - search_zoekt_project_transfer
diff --git a/danger/feature_flag/Dangerfile b/danger/feature_flag/Dangerfile
index 3ecd7541b14..307c7d23b68 100644
--- a/danger/feature_flag/Dangerfile
+++ b/danger/feature_flag/Dangerfile
@@ -1,132 +1,219 @@
# frozen_string_literal: true
-FF_SEE_DOC = "See the [feature flag documentation](https://docs.gitlab.com/ee/development/feature_flags#feature-flag-definition-and-validation)."
-FEATURE_FLAG_LABEL = "feature flag"
-FEATURE_FLAG_EXISTS_LABEL = "#{FEATURE_FLAG_LABEL}::exists"
-FEATURE_FLAG_SKIPPED_LABEL = "#{FEATURE_FLAG_LABEL}::skipped"
-DEVOPS_LABELS_REQUIRING_FEATURE_FLAG_REVIEW = ["devops::verify"]
+module Tooling
+ class FeatureFlagDangerfile
+ SEE_DOC = "See the [feature flag documentation](https://docs.gitlab.com/ee/development/feature_flags#feature-flag-definition-and-validation)."
+ FEATURE_FLAG_LABEL = "feature flag"
+ FEATURE_FLAG_EXISTS_LABEL = "#{FEATURE_FLAG_LABEL}::exists".freeze
+ FEATURE_FLAG_SKIPPED_LABEL = "#{FEATURE_FLAG_LABEL}::skipped".freeze
+ DEVOPS_LABELS_REQUIRING_FEATURE_FLAG_REVIEW = ["devops::verify"].freeze
-FF_SUGGEST_MR_COMMENT = <<~SUGGEST_COMMENT
-```suggestion
-group: "%s"
-```
+ SUGGEST_MR_COMMENT = <<~SUGGEST_COMMENT.freeze
+ ```suggestion
+ group: "%s"
+ ```
-#{FF_SEE_DOC}
-SUGGEST_COMMENT
+ #{SEE_DOC}
+ SUGGEST_COMMENT
-FEATURE_FLAG_ENFORCEMENT_WARNING = <<~WARNING_MESSAGE
-There were no new or modified feature flag YAML files detected in this MR.
+ FEATURE_FLAG_ENFORCEMENT_WARNING = <<~WARNING_MESSAGE.freeze
+ There were no new or modified feature flag YAML files detected in this MR.
-If the changes here are already controlled under an existing feature flag, please add
-the ~"#{FEATURE_FLAG_EXISTS_LABEL}". Otherwise, if you think the changes here don't need
-to be under a feature flag, please add the label ~"#{FEATURE_FLAG_SKIPPED_LABEL}", and
-add a short comment about why we skipped the feature flag.
+ If the changes here are already controlled under an existing feature flag, please add
+ the ~"#{FEATURE_FLAG_EXISTS_LABEL}". Otherwise, if you think the changes here don't need
+ to be under a feature flag, please add the label ~"#{FEATURE_FLAG_SKIPPED_LABEL}", and
+ add a short comment about why we skipped the feature flag.
-For guidance on when to use a feature flag, please see the [documentation](https://about.gitlab.com/handbook/product-development-flow/feature-flag-lifecycle/#when-to-use-feature-flags).
-WARNING_MESSAGE
+ For guidance on when to use a feature flag, please see the [documentation](https://about.gitlab.com/handbook/product-development-flow/feature-flag-lifecycle/#when-to-use-feature-flags).
+ WARNING_MESSAGE
-def check_feature_flag_yaml(feature_flag)
- mr_group_label = helper.group_label
+ def initialize(context:, added_files:, modified_files:, helper:)
+ @context = context
+ @added_files = added_files
+ @modified_files = modified_files
+ @helper = helper
+ end
- if feature_flag.group.nil?
- message_for_feature_flag_missing_group!(feature_flag: feature_flag, mr_group_label: mr_group_label)
- else
- message_for_feature_flag_with_group!(feature_flag: feature_flag, mr_group_label: mr_group_label)
- end
-rescue Psych::Exception
- # YAML could not be parsed, fail the build.
- fail "#{helper.html_link(feature_flag.path)} isn't valid YAML! #{FF_SEE_DOC}"
-rescue StandardError => e
- warn "There was a problem trying to check the Feature Flag file. Exception: #{e.class.name} - #{e.message}"
-end
+ def check_touched_feature_flag_files
+ touched_feature_flag_files.each do |feature_flag|
+ check_feature_flag_yaml(feature_flag)
+ end
+ end
-def message_for_feature_flag_missing_group!(feature_flag:, mr_group_label:)
- if mr_group_label.nil?
- warn "Consider setting `group` in #{helper.html_link(feature_flag.path)}. #{FF_SEE_DOC}"
- else
- mr_line = feature_flag.raw.lines.find_index("group:\n")
+ def feature_flag_file_touched?
+ touched_feature_flag_files.any?
+ end
- if mr_line
- markdown(format(FF_SUGGEST_MR_COMMENT, group: mr_group_label), file: feature_flag.path, line: mr_line.succ)
- else
- warn %(Consider setting `group: "#{mr_group_label}"` in #{helper.html_link(feature_flag.path)}. #{FF_SEE_DOC})
+ def mr_has_backend_or_frontend_changes?
+ changes = helper.changes_by_category
+ changes.has_key?(:backend) || changes.has_key?(:frontend)
+ end
+
+ def stage_requires_feature_flag_review?
+ DEVOPS_LABELS_REQUIRING_FEATURE_FLAG_REVIEW.include?(helper.stage_label)
+ end
+
+ def mr_missing_feature_flag_status_label?
+ ([FEATURE_FLAG_EXISTS_LABEL, FEATURE_FLAG_SKIPPED_LABEL] & helper.mr_labels).none?
+ end
+
+ private
+
+ attr_reader :context, :added_files, :modified_files, :helper
+
+ def check_feature_flag_yaml(feature_flag)
+ unless feature_flag.valid?
+ context.failure("#{helper.html_link(feature_flag.path)} isn't valid YAML! #{SEE_DOC}")
+ return
+ end
+
+ check_group(feature_flag)
+ check_feature_issue_url(feature_flag)
+ # Note: we don't check introduced_by_url as it's already done by danger/config_files/Dangerfile
+ check_rollout_issue_url(feature_flag)
+ check_milestone(feature_flag)
+ check_default_enabled(feature_flag)
+ end
+
+ def touched_feature_flag_files
+ added_files + modified_files
+ end
+
+ def check_group(feature_flag)
+ mr_group_label = helper.group_label
+
+ if feature_flag.missing_group?
+ message_for_feature_flag_missing_group!(feature_flag: feature_flag, mr_group_label: mr_group_label)
+ else
+ message_for_feature_flag_with_group!(feature_flag: feature_flag, mr_group_label: mr_group_label)
+ end
+ end
+
+ def message_for_feature_flag_missing_group!(feature_flag:, mr_group_label:)
+ if mr_group_label.nil?
+ context.failure("Please specify a valid `group` label in #{helper.html_link(feature_flag.path)}. #{SEE_DOC}")
+ return
+ end
+
+ add_message_on_line(
+ feature_flag: feature_flag,
+ needle: "group:",
+ note: format(SUGGEST_MR_COMMENT, group: mr_group_label),
+ fallback_note: %(Please add `group: "#{mr_group_label}"` in #{helper.html_link(feature_flag.path)}. #{SEE_DOC}),
+ message_method: :failure
+ )
+ end
+
+ def message_for_feature_flag_with_group!(feature_flag:, mr_group_label:)
+ return if feature_flag.group_match_mr_label?(mr_group_label)
+
+ if mr_group_label.nil?
+ helper.labels_to_add << feature_flag.group
+ else
+ note = <<~FAILURE_MESSAGE
+ `group` is set to ~"#{feature_flag.group}" in #{helper.html_link(feature_flag.path)},
+ which does not match ~"#{mr_group_label}" set on the MR!
+ FAILURE_MESSAGE
+
+ add_message_on_line(
+ feature_flag: feature_flag,
+ needle: "group:",
+ note: note,
+ message_method: :failure
+ )
+ end
+ end
+
+ def check_feature_issue_url(feature_flag)
+ return unless feature_flag.missing_feature_issue_url?
+
+ add_message_on_line(
+ feature_flag: feature_flag,
+ needle: "feature_issue_url:",
+ note: "Consider filling `feature_issue_url:`"
+ )
+ end
+
+ def add_message_on_line(feature_flag:, needle:, note:, fallback_note: note, message_method: :message)
+ mr_line = feature_flag.find_line_index(needle)
+
+ # rubocop:disable GitlabSecurity/PublicSend -- we allow calling context.message, context.warning & context.failure
+ if mr_line
+ context.public_send(message_method, note, file: feature_flag.path, line: mr_line.succ)
+ else
+ context.public_send(message_method, fallback_note)
+ end
+ # rubocop:enable GitlabSecurity/PublicSend
+ end
+
+ def check_rollout_issue_url(feature_flag)
+ return unless ::Feature::Shared::TYPES.dig(feature_flag.name.to_sym, :rollout_issue)
+ return unless feature_flag.missing_rollout_issue_url?
+
+ missing_field_error(feature_flag: feature_flag, field: :rollout_issue_url)
+ end
+
+ def check_milestone(feature_flag)
+ return unless feature_flag.missing_milestone?
+
+ missing_field_error(feature_flag: feature_flag, field: :milestone)
+ end
+
+ def check_default_enabled(feature_flag)
+ return unless feature_flag.default_enabled?
+
+ if ::Feature::Shared.can_be_default_enabled?(feature_flag.type)
+ note = <<~SUGGEST_COMMENT
+ You're about to [release the feature with the feature flag](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/issue_templates/Feature%20Flag%20Roll%20Out.md#optional-release-the-feature-with-the-feature-flag).
+ This process can only be done **after** the [global rollout on production](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/issue_templates/Feature%20Flag%20Roll%20Out.md#global-rollout-on-production).
+ Please make sure in [the rollout issue](#{feature_flag.rollout_issue_url}) that the preliminary steps have already been done. Otherwise, changing the YAML definition might not have the desired effect.
+ SUGGEST_COMMENT
+
+ mr_line = feature_flag.find_line_index("default_enabled: true")
+ context.markdown(note, file: feature_flag.path, line: mr_line.succ) if mr_line
+ else
+ context.failure(
+ "[Feature flag with the `#{feature_flag.type}` type must not be enabled by default](https://docs.gitlab.com/ee/development/feature_flags/##{feature_flag.type}-type). " \
+ "Consider changing the feature flag type if it's ready to be enabled by default."
+ )
+ end
+ end
+
+ def missing_field_error(feature_flag:, field:)
+ note = <<~MISSING_FIELD_ERROR
+ [Feature flag with the `#{feature_flag.type}` type must have `:#{field}` set](https://docs.gitlab.com/ee/development/feature_flags/##{feature_flag.type}-type).
+ MISSING_FIELD_ERROR
+ mr_line = feature_flag.find_line_index("#{field}:")
+
+ if mr_line
+ context.message(note, file: feature_flag.path, line: mr_line.succ)
+ else
+ context.message(note)
+ end
end
end
end
-def message_for_global_rollout(feature_flag)
- return unless feature_flag.default_enabled == true
+feature_flag_dangerfile = Tooling::FeatureFlagDangerfile.new(
+ context: self,
+ added_files: feature_flag.feature_flag_files(danger_helper: helper, change_type: :added),
+ modified_files: feature_flag.feature_flag_files(danger_helper: helper, change_type: :modified),
+ helper: helper
+)
- message = <<~SUGGEST_COMMENT
- You're about to [release the feature with the feature flag](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/issue_templates/Feature%20Flag%20Roll%20Out.md#optional-release-the-feature-with-the-feature-flag).
- This process can only be done **after** the [global rollout on production](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/issue_templates/Feature%20Flag%20Roll%20Out.md#global-rollout-on-production).
- Please make sure in [the rollout issue](#{feature_flag.rollout_issue_url}) that the preliminary steps have already been done. Otherwise, changing the YAML definition might not have the desired effect.
- SUGGEST_COMMENT
+feature_flag_dangerfile.check_touched_feature_flag_files
- mr_line = feature_flag.raw.lines.find_index { |l| l.include?('default_enabled:') }
- markdown(message, file: feature_flag.path, line: mr_line.succ)
+if helper.security_mr? && feature_flag_dangerfile.feature_flag_file_touched?
+ failure("Feature flags are discouraged from security merge requests. Read the [security documentation](https://gitlab.com/gitlab-org/release/docs/-/blob/master/general/security/utilities/feature_flags.md) for details.")
end
-def message_for_feature_flag_with_group!(feature_flag:, mr_group_label:)
- return if feature_flag.group_match_mr_label?(mr_group_label)
-
- if mr_group_label.nil?
- helper.labels_to_add << feature_flag.group
- else
- fail %(`group` is set to ~"#{feature_flag.group}" in #{helper.html_link(feature_flag.path)}, which does not match ~"#{mr_group_label}" set on the MR!)
- end
-end
-
-def added_feature_flag_files
- feature_flag.feature_flag_files(change_type: :added)
-end
-
-def modified_feature_flag_files
- feature_flag.feature_flag_files(change_type: :modified)
-end
-
-def feature_flag_file_added?
- added_feature_flag_files.any?
-end
-
-def feature_flag_file_modified?
- modified_feature_flag_files.any?
-end
-
-def feature_flag_file_added_or_modified?
- feature_flag_file_added? || feature_flag_file_modified?
-end
-
-def mr_has_backend_or_frontend_changes?
- changes = helper.changes_by_category
- changes.has_key?(:backend) || changes.has_key?(:frontend)
-end
-
-def mr_missing_feature_flag_status_label?
- ([FEATURE_FLAG_EXISTS_LABEL, FEATURE_FLAG_SKIPPED_LABEL] & helper.mr_labels).none?
-end
-
-def stage_requires_feature_flag_review?
- DEVOPS_LABELS_REQUIRING_FEATURE_FLAG_REVIEW.include?(helper.stage_label)
-end
-
-added_feature_flag_files.each do |feature_flag|
- check_feature_flag_yaml(feature_flag)
-end
-
-modified_feature_flag_files.each do |feature_flag|
- message_for_global_rollout(feature_flag)
-end
-
-if helper.security_mr? && feature_flag_file_added?
- fail "Feature flags are discouraged from security merge requests. Read the [security documentation](https://gitlab.com/gitlab-org/release/docs/-/blob/master/general/security/utilities/feature_flags.md) for details."
-end
-
-if !helper.security_mr? && mr_has_backend_or_frontend_changes? && stage_requires_feature_flag_review?
- if feature_flag_file_added_or_modified? && !helper.mr_has_labels?(FEATURE_FLAG_EXISTS_LABEL)
+if !helper.security_mr? && feature_flag_dangerfile.mr_has_backend_or_frontend_changes? && feature_flag_dangerfile.stage_requires_feature_flag_review?
+ if feature_flag_dangerfile.feature_flag_file_touched? && !helper.mr_has_labels?(Tooling::FeatureFlagDangerfile::FEATURE_FLAG_EXISTS_LABEL)
# Feature flag config file touched in this MR, so let's add the label to avoid the warning.
- helper.labels_to_add << FEATURE_FLAG_EXISTS_LABEL
+ helper.labels_to_add << Tooling::FeatureFlagDangerfile::FEATURE_FLAG_EXISTS_LABEL
end
- warn FEATURE_FLAG_ENFORCEMENT_WARNING if mr_missing_feature_flag_status_label?
+ if feature_flag_dangerfile.mr_missing_feature_flag_status_label?
+ warn(Tooling::FeatureFlagDangerfile::FEATURE_FLAG_ENFORCEMENT_WARNING)
+ end
end
diff --git a/danger/utility_css/Dangerfile b/danger/utility_css/Dangerfile
index 4f56c40379e..20c6bd18060 100644
--- a/danger/utility_css/Dangerfile
+++ b/danger/utility_css/Dangerfile
@@ -6,7 +6,7 @@ utilities = 'app/assets/stylesheets/utilities.scss'
def get_css_files(files, common_filepath, utilities_filepath)
files.select do |file|
file.include?(common_filepath) ||
- file.include?(utilities_filepath)
+ file.include?(utilities_filepath)
end
end
diff --git a/db/docs/ai_self_hosted_models.yml b/db/docs/ai_self_hosted_models.yml
new file mode 100644
index 00000000000..cd03b9fb4a9
--- /dev/null
+++ b/db/docs/ai_self_hosted_models.yml
@@ -0,0 +1,10 @@
+---
+table_name: ai_self_hosted_models
+classes:
+- Ai::SelfHostedModel
+feature_categories:
+- custom_models
+description: An AI Self Hosted Model definition
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/151793
+milestone: '17.0'
+gitlab_schema: gitlab_main_clusterwide
diff --git a/db/migrate/20240419082037_create_ai_self_hosted_models.rb b/db/migrate/20240419082037_create_ai_self_hosted_models.rb
new file mode 100644
index 00000000000..346c297b731
--- /dev/null
+++ b/db/migrate/20240419082037_create_ai_self_hosted_models.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class CreateAiSelfHostedModels < Gitlab::Database::Migration[2.2]
+ milestone '17.0'
+
+ def change
+ create_table :ai_self_hosted_models do |t|
+ t.timestamps_with_timezone null: false
+ t.integer :model, limit: 2, null: false
+ t.text :endpoint, limit: 2048, null: false
+ t.text :name, limit: 255, null: false, index: { unique: true }
+ t.binary :encrypted_api_token
+ t.binary :encrypted_api_token_iv
+ end
+ end
+end
diff --git a/db/post_migrate/20240513111937_swap_columns_for_p_ci_builds_trigger_request_and_erased_by.rb b/db/post_migrate/20240513111937_swap_columns_for_p_ci_builds_trigger_request_and_erased_by.rb
new file mode 100644
index 00000000000..bc9d12c08d7
--- /dev/null
+++ b/db/post_migrate/20240513111937_swap_columns_for_p_ci_builds_trigger_request_and_erased_by.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+class SwapColumnsForPCiBuildsTriggerRequestAndErasedBy < Gitlab::Database::Migration[2.2]
+ include ::Gitlab::Database::MigrationHelpers::Swapping
+ milestone '17.1'
+ disable_ddl_transaction!
+
+ TABLE = :p_ci_builds
+ COLUMNS = [
+ { name: :trigger_request_id_convert_to_bigint, old_name: :trigger_request_id },
+ { name: :erased_by_id_convert_to_bigint, old_name: :erased_by_id }
+ ]
+ TRIGGER_FUNCTION = :trigger_10ee1357e825
+
+ def up
+ with_lock_retries(raise_on_exhaustion: true) do
+ swap # rubocop:disable Migration/WithLockRetriesDisallowedMethod -- custom implementation
+ end
+ end
+
+ def down
+ with_lock_retries(raise_on_exhaustion: true) do
+ swap # rubocop:disable Migration/WithLockRetriesDisallowedMethod -- custom implementation
+ end
+ end
+
+ private
+
+ def swap
+ lock_tables(TABLE)
+
+ COLUMNS.each do |column|
+ swap_columns(TABLE, column[:name], column[:old_name])
+ end
+ reset_trigger_function(TRIGGER_FUNCTION)
+ end
+end
diff --git a/db/post_migrate/20240513151225_add_index_members_on_lower_invite_email_with_token.rb b/db/post_migrate/20240513151225_add_index_members_on_lower_invite_email_with_token.rb
new file mode 100644
index 00000000000..19bc8e8a27e
--- /dev/null
+++ b/db/post_migrate/20240513151225_add_index_members_on_lower_invite_email_with_token.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class AddIndexMembersOnLowerInviteEmailWithToken < Gitlab::Database::Migration[2.2]
+ milestone '17.1'
+
+ OLD_INDEX_NAME = 'index_members_on_lower_invite_email'
+ INDEX_NAME = 'index_members_on_lower_invite_email_with_token'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :members, '(lower(invite_email))', where: 'invite_token IS NOT NULL', name: INDEX_NAME
+
+ remove_concurrent_index_by_name :members, OLD_INDEX_NAME
+ end
+
+ def down
+ add_concurrent_index :members, '(lower(invite_email))', name: OLD_INDEX_NAME
+
+ remove_concurrent_index_by_name :members, INDEX_NAME
+ end
+end
diff --git a/db/post_migrate/20240514005857_prepare_async_index_for_ci_pipeline_partition_id.rb b/db/post_migrate/20240514005857_prepare_async_index_for_ci_pipeline_partition_id.rb
new file mode 100644
index 00000000000..f65ec3daa9b
--- /dev/null
+++ b/db/post_migrate/20240514005857_prepare_async_index_for_ci_pipeline_partition_id.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+class PrepareAsyncIndexForCiPipelinePartitionId < Gitlab::Database::Migration[2.2]
+ milestone '17.1'
+ disable_ddl_transaction!
+
+ TABLE = :ci_pipelines
+ INDEXES = [
+ {
+ name: :index_ci_pipelines_on_id_and_partition_id,
+ columns: [:id, :partition_id],
+ options: { unique: true }
+ },
+ {
+ name: :index_ci_pipelines_on_project_id_and_iid_and_partition_id,
+ columns: [:project_id, :iid, :partition_id],
+ options: { unique: true, where: 'iid IS NOT NULL' }
+ }
+ ]
+
+ def up
+ INDEXES.each do |definition|
+ name, columns, options = definition.values_at(:name, :columns, :options)
+ prepare_async_index(TABLE, columns, name: name, **options)
+ end
+ end
+
+ def down
+ INDEXES.each do |definition|
+ name, columns, options = definition.values_at(:name, :columns, :options)
+ unprepare_async_index(TABLE, columns, name: name, **options)
+ end
+ end
+end
diff --git a/db/post_migrate/20240514081440_swap_vulnerability_occurrence_pipelines_pipeline_id_convert_to_bigint.rb b/db/post_migrate/20240514081440_swap_vulnerability_occurrence_pipelines_pipeline_id_convert_to_bigint.rb
new file mode 100644
index 00000000000..f57375e8ef4
--- /dev/null
+++ b/db/post_migrate/20240514081440_swap_vulnerability_occurrence_pipelines_pipeline_id_convert_to_bigint.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+class SwapVulnerabilityOccurrencePipelinesPipelineIdConvertToBigint < Gitlab::Database::Migration[2.2]
+ include Gitlab::Database::MigrationHelpers::Swapping
+ milestone '17.1'
+ disable_ddl_transaction!
+
+ TABLE_NAME = 'vulnerability_occurrence_pipelines'
+ COLUMN_NAME = 'pipeline_id'
+
+ COLUMN_TO_BIGINT_NAME = 'pipeline_id_convert_to_bigint'
+ INDEX_TO_BIGINT_NAME = 'index_vulnerability_occurrence_pipelines_on_pipeline_id_bigint'
+ INDEX_NAME = 'index_vulnerability_occurrence_pipelines_on_pipeline_id'
+
+ UNIQUE_KEYS_INDEX_NAME_BIGINT = 'vulnerability_occurrence_pipelines_on_unique_keys_bigint'
+ UNIQUE_KEYS_INDEX_NAME = 'vulnerability_occurrence_pipelines_on_unique_keys'
+ UNIQUE_INDEX_COLUMNS = %w[occurrence_id pipeline_id_convert_to_bigint]
+
+ TRIGGER_NAME = :trigger_2ac3d66ed1d3
+
+ def up
+ add_concurrent_index TABLE_NAME, COLUMN_TO_BIGINT_NAME, name: INDEX_TO_BIGINT_NAME
+ add_concurrent_index TABLE_NAME, UNIQUE_INDEX_COLUMNS,
+ name: UNIQUE_KEYS_INDEX_NAME_BIGINT,
+ unique: true
+
+ swap
+ end
+
+ def down
+ add_concurrent_index TABLE_NAME, COLUMN_TO_BIGINT_NAME, name: INDEX_TO_BIGINT_NAME
+ add_concurrent_index TABLE_NAME, UNIQUE_INDEX_COLUMNS,
+ name: UNIQUE_KEYS_INDEX_NAME_BIGINT,
+ unique: true
+
+ swap
+ end
+
+ def swap
+ with_lock_retries(raise_on_exhaustion: true) do
+ # Not locking ci_pipelines as it's an LFK column
+ lock_tables(TABLE_NAME)
+
+ swap_columns(TABLE_NAME, COLUMN_NAME, COLUMN_TO_BIGINT_NAME)
+
+ reset_trigger_function(TRIGGER_NAME)
+
+ change_column_default TABLE_NAME, COLUMN_TO_BIGINT_NAME, 0
+ change_column_default TABLE_NAME, COLUMN_NAME, nil
+
+ execute "DROP INDEX #{INDEX_NAME}"
+ rename_index TABLE_NAME, INDEX_TO_BIGINT_NAME, INDEX_NAME
+
+ execute "DROP INDEX #{UNIQUE_KEYS_INDEX_NAME}"
+ rename_index TABLE_NAME, UNIQUE_KEYS_INDEX_NAME_BIGINT, UNIQUE_KEYS_INDEX_NAME
+ end
+ end
+end
diff --git a/db/schema_migrations/20240419082037 b/db/schema_migrations/20240419082037
new file mode 100644
index 00000000000..93942654ba4
--- /dev/null
+++ b/db/schema_migrations/20240419082037
@@ -0,0 +1 @@
+6e174bfc24df22fdef6dd45151a18ba918160f8ccc10ae63bce4976bd4f8a12e
\ No newline at end of file
diff --git a/db/schema_migrations/20240513111937 b/db/schema_migrations/20240513111937
new file mode 100644
index 00000000000..f3d5116d8a2
--- /dev/null
+++ b/db/schema_migrations/20240513111937
@@ -0,0 +1 @@
+71dace2cf277ea15641a14a1460e35faf43002fc18d64f022c481fc546cc6b57
\ No newline at end of file
diff --git a/db/schema_migrations/20240513151225 b/db/schema_migrations/20240513151225
new file mode 100644
index 00000000000..ec629048715
--- /dev/null
+++ b/db/schema_migrations/20240513151225
@@ -0,0 +1 @@
+9e383a3d9750d101c6b9fc185f20c5e81e21806ab238e55de9315856f292091f
\ No newline at end of file
diff --git a/db/schema_migrations/20240514005857 b/db/schema_migrations/20240514005857
new file mode 100644
index 00000000000..ba7244b1e48
--- /dev/null
+++ b/db/schema_migrations/20240514005857
@@ -0,0 +1 @@
+ec882df025640d6c08006027aa08f8ec5d987259ed9dcd3de4eaaea8cd20d533
\ No newline at end of file
diff --git a/db/schema_migrations/20240514081440 b/db/schema_migrations/20240514081440
new file mode 100644
index 00000000000..8cb36818128
--- /dev/null
+++ b/db/schema_migrations/20240514081440
@@ -0,0 +1 @@
+4eed015cddbe337bda02a94b26398f14f885ab11c78bb08a29c467767788e309
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 04db88b456f..74839ef0db0 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -1122,7 +1122,7 @@ CREATE TABLE p_ci_builds (
options text,
allow_failure boolean DEFAULT false NOT NULL,
stage character varying,
- trigger_request_id integer,
+ trigger_request_id_convert_to_bigint integer,
stage_idx integer,
tag boolean,
ref character varying,
@@ -1131,7 +1131,7 @@ CREATE TABLE p_ci_builds (
target_url character varying,
description character varying,
project_id_convert_to_bigint integer,
- erased_by_id integer,
+ erased_by_id_convert_to_bigint integer,
erased_at timestamp without time zone,
artifacts_expire_at timestamp without time zone,
environment character varying,
@@ -1157,10 +1157,10 @@ CREATE TABLE p_ci_builds (
auto_canceled_by_partition_id bigint DEFAULT 100 NOT NULL,
auto_canceled_by_id bigint,
commit_id bigint,
- erased_by_id_convert_to_bigint bigint,
+ erased_by_id bigint,
project_id bigint,
runner_id bigint,
- trigger_request_id_convert_to_bigint bigint,
+ trigger_request_id bigint,
upstream_pipeline_id bigint,
user_id bigint,
execution_config_id bigint,
@@ -3593,6 +3593,28 @@ CREATE SEQUENCE ai_agents_id_seq
ALTER SEQUENCE ai_agents_id_seq OWNED BY ai_agents.id;
+CREATE TABLE ai_self_hosted_models (
+ id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ model smallint NOT NULL,
+ endpoint text NOT NULL,
+ name text NOT NULL,
+ encrypted_api_token bytea,
+ encrypted_api_token_iv bytea,
+ CONSTRAINT check_a28005edb2 CHECK ((char_length(endpoint) <= 2048)),
+ CONSTRAINT check_cccb37e0de CHECK ((char_length(name) <= 255))
+);
+
+CREATE SEQUENCE ai_self_hosted_models_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE ai_self_hosted_models_id_seq OWNED BY ai_self_hosted_models.id;
+
CREATE TABLE ai_vectorizable_files (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
@@ -6267,7 +6289,7 @@ CREATE TABLE ci_builds (
options text,
allow_failure boolean DEFAULT false NOT NULL,
stage character varying,
- trigger_request_id integer,
+ trigger_request_id_convert_to_bigint integer,
stage_idx integer,
tag boolean,
ref character varying,
@@ -6276,7 +6298,7 @@ CREATE TABLE ci_builds (
target_url character varying,
description character varying,
project_id_convert_to_bigint integer,
- erased_by_id integer,
+ erased_by_id_convert_to_bigint integer,
erased_at timestamp without time zone,
artifacts_expire_at timestamp without time zone,
environment character varying,
@@ -6302,10 +6324,10 @@ CREATE TABLE ci_builds (
auto_canceled_by_partition_id bigint DEFAULT 100 NOT NULL,
auto_canceled_by_id bigint,
commit_id bigint,
- erased_by_id_convert_to_bigint bigint,
+ erased_by_id bigint,
project_id bigint,
runner_id bigint,
- trigger_request_id_convert_to_bigint bigint,
+ trigger_request_id bigint,
upstream_pipeline_id bigint,
user_id bigint,
execution_config_id bigint,
@@ -17823,8 +17845,8 @@ CREATE TABLE vulnerability_occurrence_pipelines (
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
occurrence_id bigint NOT NULL,
- pipeline_id integer NOT NULL,
- pipeline_id_convert_to_bigint bigint DEFAULT 0 NOT NULL
+ pipeline_id_convert_to_bigint integer DEFAULT 0 NOT NULL,
+ pipeline_id bigint NOT NULL
);
CREATE SEQUENCE vulnerability_occurrence_pipelines_id_seq
@@ -18972,6 +18994,8 @@ ALTER TABLE ONLY ai_agent_versions ALTER COLUMN id SET DEFAULT nextval('ai_agent
ALTER TABLE ONLY ai_agents ALTER COLUMN id SET DEFAULT nextval('ai_agents_id_seq'::regclass);
+ALTER TABLE ONLY ai_self_hosted_models ALTER COLUMN id SET DEFAULT nextval('ai_self_hosted_models_id_seq'::regclass);
+
ALTER TABLE ONLY ai_vectorizable_files ALTER COLUMN id SET DEFAULT nextval('ai_vectorizable_files_id_seq'::regclass);
ALTER TABLE ONLY alert_management_alert_assignees ALTER COLUMN id SET DEFAULT nextval('alert_management_alert_assignees_id_seq'::regclass);
@@ -20710,6 +20734,9 @@ ALTER TABLE ONLY ai_agent_versions
ALTER TABLE ONLY ai_agents
ADD CONSTRAINT ai_agents_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY ai_self_hosted_models
+ ADD CONSTRAINT ai_self_hosted_models_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY ai_vectorizable_files
ADD CONSTRAINT ai_vectorizable_files_pkey PRIMARY KEY (id);
@@ -24440,6 +24467,8 @@ CREATE INDEX index_ai_agent_versions_on_project_id ON ai_agent_versions USING bt
CREATE UNIQUE INDEX index_ai_agents_on_project_id_and_name ON ai_agents USING btree (project_id, name);
+CREATE UNIQUE INDEX index_ai_self_hosted_models_on_name ON ai_self_hosted_models USING btree (name);
+
CREATE INDEX index_ai_vectorizable_files_on_project_id ON ai_vectorizable_files USING btree (project_id);
CREATE INDEX index_alert_assignees_on_alert_id ON alert_management_alert_assignees USING btree (alert_id);
@@ -26142,7 +26171,7 @@ CREATE INDEX index_members_on_invite_email ON members USING btree (invite_email)
CREATE UNIQUE INDEX index_members_on_invite_token ON members USING btree (invite_token);
-CREATE INDEX index_members_on_lower_invite_email ON members USING btree (lower((invite_email)::text));
+CREATE INDEX index_members_on_lower_invite_email_with_token ON members USING btree (lower((invite_email)::text)) WHERE (invite_token IS NOT NULL);
CREATE INDEX index_members_on_member_namespace_id_compound ON members USING btree (member_namespace_id, type, requested_at, id);
diff --git a/doc/api/integrations.md b/doc/api/integrations.md
index f8e3194b9e5..0ad9a95210b 100644
--- a/doc/api/integrations.md
+++ b/doc/api/integrations.md
@@ -1802,6 +1802,7 @@ Parameters:
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `hostname` | string | false | Custom hostname of the Telegram API ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/461313) in GitLab 17.1). The default value is `https://api.telegram.org`. |
| `token` | string | true | The Telegram bot token (for example, `123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11`). |
| `room` | string | true | Unique identifier for the target chat or the username of the target channel (in the format `@channelusername`). |
| `thread` | integer | false | Unique identifier for the target message thread (topic in a forum supergroup). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/441097) in GitLab 16.11. |
diff --git a/doc/development/fe_guide/customizable_dashboards.md b/doc/development/fe_guide/customizable_dashboards.md
index 2875c81786e..46e6633ac54 100644
--- a/doc/development/fe_guide/customizable_dashboards.md
+++ b/doc/development/fe_guide/customizable_dashboards.md
@@ -137,6 +137,7 @@ export const dashboard = {
slug: 'my_dashboard', // Used to set the URL path for the dashboard.
title: 'My dashboard title', // The title to display.
description: 'This is a description of the dashboard', // A description of the dashboard
+ userDefined: true, // The dashboard editor is only available when true.
// Each dashboard consists of an array of panels to display.
panels: [
{
@@ -173,12 +174,14 @@ Here is an example component that renders a customizable dashboard:
```vue