Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									606d784b5b
								
							
						
					
					
						commit
						ba7e516994
					
				|  | @ -1 +1 @@ | |||
| c29c5ba968d141237000185734baa95e122db80b | ||||
| 1a16fa05c2645a0abba4e2f028e1fdbe5d85be2f | ||||
|  |  | |||
|  | @ -180,7 +180,6 @@ export default { | |||
|       return { | ||||
|         'is-replying gl-pt-0!': this.isReplying, | ||||
|         'internal-note': this.isDiscussionInternal, | ||||
|         'public-note': !this.isDiscussionInternal, | ||||
|         'gl-pt-0!': !this.discussion.diff_discussion && this.isReplying, | ||||
|       }; | ||||
|     }, | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| import { padWithZeros } from '~/lib/utils/datetime/date_format_utility'; | ||||
| import { isValidDate, differenceInMinutes } from '~/lib/utils/datetime_utility'; | ||||
| import { mergeUrlParams } from '~/lib/utils/url_utility'; | ||||
| 
 | ||||
| import { | ||||
|   CUSTOM_DATE_RANGE_OPTION, | ||||
|  | @ -193,3 +194,9 @@ export function isTracingDateRangeOutOfBounds({ value, startDate, endDate }) { | |||
|   } | ||||
|   return false; | ||||
| } | ||||
| 
 | ||||
| export function urlWithStringifiedPayloadParam(url, payload, paramName) { | ||||
|   return mergeUrlParams({ [paramName]: JSON.stringify(payload) }, url, { | ||||
|     spreadArrays: true, | ||||
|   }); | ||||
| } | ||||
|  |  | |||
|  | @ -43,7 +43,7 @@ | |||
|       background-color: $line-target-blue; | ||||
|     } | ||||
| 
 | ||||
|     + .public-note.discussion-reply-holder { | ||||
|     + .discussion-reply-holder { | ||||
|       padding-top: $gl-padding-12 !important; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -27,10 +27,20 @@ module Ci | |||
|       record = find_by(build_id: build_id, partition_id: partition_id) | ||||
|       return record if record | ||||
| 
 | ||||
|       upsert({ build_id: build_id, partition_id: partition_id }, unique_by: :build_id) | ||||
|       upsert({ build_id: build_id, partition_id: partition_id }, unique_by: unique_by_columns_for_upsert) | ||||
|       find_by!(build_id: build_id, partition_id: partition_id) | ||||
|     end | ||||
| 
 | ||||
|     # Temporary fix to correctly identify an unique index for upsert | ||||
|     # should be replaced with `%w[build_id partition_id]` in %17.5 | ||||
|     def self.unique_by_columns_for_upsert | ||||
|       unique_indexes = connection.schema_cache.indexes(table_name).select(&:unique) | ||||
|       columns = %w[partition_id build_id] | ||||
|       return columns if unique_indexes.any? { |index| index.columns == columns } | ||||
| 
 | ||||
|       columns.reverse | ||||
|     end | ||||
| 
 | ||||
|     # The job is retried around 5 times during the 7 days retention period for | ||||
|     # trace chunks as defined in `Ci::BuildTraceChunks::RedisBase::CHUNK_REDIS_TTL` | ||||
|     def can_attempt_archival_now? | ||||
|  |  | |||
|  | @ -18,7 +18,7 @@ module VirtualRegistries | |||
|           prevent(*create_read_update_admin_destroy(:virtual_registry)) | ||||
|         end | ||||
| 
 | ||||
|         rule { can?(:read_group) }.policy do | ||||
|         rule { group.guest | admin | group.has_projects }.policy do | ||||
|           enable :read_virtual_registry | ||||
|         end | ||||
| 
 | ||||
|  |  | |||
|  | @ -60,16 +60,13 @@ module WorkItems | |||
|     def interpret_quick_actions!(work_item, widget_params, attributes = {}) | ||||
|       return unless work_item.has_widget?(:description) | ||||
| 
 | ||||
|       widget_description_param = widget_params[::WorkItems::Widgets::Description.api_symbol] | ||||
|       return unless widget_description_param | ||||
|       description_widget_params = widget_params[::WorkItems::Widgets::Description.api_symbol] | ||||
|       return unless description_widget_params | ||||
| 
 | ||||
|       merge_quick_actions_into_params!(work_item, params: widget_description_param) | ||||
|       merge_quick_actions_into_params!(work_item, params: description_widget_params) | ||||
| 
 | ||||
|       # cleanup `description` param so that it is not passed into common params after transform_quick_action_params | ||||
|       quick_action_params = widget_description_param.dup | ||||
|       quick_action_params.delete(:description) | ||||
| 
 | ||||
|       parsed_params = work_item.transform_quick_action_params(quick_action_params) | ||||
|       # exclude `description` param so that it is not passed into common params after transform_quick_action_params | ||||
|       parsed_params = work_item.transform_quick_action_params(description_widget_params.except(:description)) | ||||
| 
 | ||||
|       widget_params.merge!(parsed_params[:widgets]) | ||||
|       attributes.merge!(parsed_params[:common]) | ||||
|  |  | |||
|  | @ -217,42 +217,24 @@ class IssuableBaseService < ::BaseContainerService | |||
|   # If the description has not been edited, then just remove any quick actions | ||||
|   # in the current description. | ||||
|   def merge_quick_actions_into_params!(issuable, params:, only: nil) | ||||
|     interpret_params = quick_action_options | ||||
|     unedited_description = issuable.description | ||||
|     edited_description = params.fetch(:description, issuable.description) | ||||
|     target_description = params.fetch(:description, issuable.description) | ||||
| 
 | ||||
|     target_text = issuable.new_record? || params[:description] ? edited_description : unedited_description | ||||
| 
 | ||||
|     # only set the original_text if we're editing the issuable | ||||
|     original_text = params[:description] && !issuable.new_record? ? unedited_description : nil | ||||
| 
 | ||||
|     sanitized_description, sanitized_command_params = interpret_quick_actions(target_text, issuable, params: interpret_params, only: only, original_text: original_text) | ||||
| 
 | ||||
|     unless issuable.new_record? || params[:description] | ||||
|       edited_description = unedited_description | ||||
|       sanitized_command_params = nil | ||||
|     end | ||||
|     description, command_params = QuickActions::InterpretService.new( | ||||
|       container: container, | ||||
|       current_user: current_user, | ||||
|       params: quick_action_options | ||||
|     ).execute_with_original_text(target_description, issuable, only: only, original_text: issuable.description_was) | ||||
| 
 | ||||
|     # Avoid a description already set on an issuable to be overwritten by a nil | ||||
|     params[:description] = sanitized_description if sanitized_description && sanitized_description != edited_description | ||||
|     params[:description] = description if description && description != target_description | ||||
| 
 | ||||
|     params.merge!(sanitized_command_params) if sanitized_command_params | ||||
|     params.merge!(command_params) | ||||
|   end | ||||
| 
 | ||||
|   def quick_action_options | ||||
|     {} | ||||
|   end | ||||
| 
 | ||||
|   def interpret_quick_actions(new_text, issuable, params:, only:, original_text: nil) | ||||
|     sanitized_new_text, new_command_params = QuickActions::InterpretService.new( | ||||
|       container: container, | ||||
|       current_user: current_user, | ||||
|       params: params | ||||
|     ).execute_with_original_text(new_text, issuable, only: only, original_text: original_text) | ||||
| 
 | ||||
|     [sanitized_new_text, new_command_params] | ||||
|   end | ||||
| 
 | ||||
|   def create(issuable, skip_system_notes: false) | ||||
|     initialize_callbacks!(issuable) | ||||
| 
 | ||||
|  |  | |||
|  | @ -212,6 +212,10 @@ dast_site_validations: | |||
|   - table: projects | ||||
|     column: project_id | ||||
|     on_delete: async_delete | ||||
| dast_sites: | ||||
|   - table: projects | ||||
|     column: project_id | ||||
|     on_delete: async_delete | ||||
| deployment_clusters: | ||||
|   - table: clusters | ||||
|     column: cluster_id | ||||
|  |  | |||
|  | @ -0,0 +1,27 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| class CreateCiFinishedPipelinesHourly < ClickHouse::Migration | ||||
|   def up | ||||
|     execute <<~SQL | ||||
|       CREATE TABLE IF NOT EXISTS ci_finished_pipelines_hourly | ||||
|       ( | ||||
|         `path` String DEFAULT '0/', | ||||
| 
 | ||||
|         `status` LowCardinality(String) DEFAULT '', | ||||
|         `source` LowCardinality(String) DEFAULT '', | ||||
|         `ref` String DEFAULT '', | ||||
|         `started_at_bucket` DateTime64(6, 'UTC') DEFAULT now64(), | ||||
| 
 | ||||
|         count_pipelines AggregateFunction(count) | ||||
|       ) | ||||
|       ENGINE = AggregatingMergeTree() | ||||
|       ORDER BY (started_at_bucket, path, status, source, ref) | ||||
|     SQL | ||||
|   end | ||||
| 
 | ||||
|   def down | ||||
|     execute <<~SQL | ||||
|       DROP TABLE IF EXISTS ci_finished_pipelines_hourly | ||||
|     SQL | ||||
|   end | ||||
| end | ||||
|  | @ -0,0 +1,27 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| class CreateCiFinishedPipelinesHourlyMv < ClickHouse::Migration | ||||
|   def up | ||||
|     execute <<~SQL | ||||
|       CREATE MATERIALIZED VIEW IF NOT EXISTS ci_finished_pipelines_hourly_mv | ||||
|       TO ci_finished_pipelines_hourly | ||||
|       AS | ||||
|       SELECT | ||||
|         path, | ||||
|         status, | ||||
|         source, | ||||
|         ref, | ||||
|         toStartOfInterval(started_at, INTERVAL 1 hour) AS started_at_bucket, | ||||
| 
 | ||||
|         countState() AS count_pipelines | ||||
|       FROM ci_finished_pipelines | ||||
|       GROUP BY path, status, source, ref, started_at_bucket | ||||
|     SQL | ||||
|   end | ||||
| 
 | ||||
|   def down | ||||
|     execute <<~SQL | ||||
|       DROP VIEW ci_finished_pipelines_hourly_mv | ||||
|     SQL | ||||
|   end | ||||
| end | ||||
|  | @ -7,7 +7,7 @@ feature_categories: | |||
| description: Site to run dast scan on | ||||
| introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36659 | ||||
| milestone: '13.2' | ||||
| gitlab_schema: gitlab_main_cell | ||||
| gitlab_schema: gitlab_sec | ||||
| allow_cross_foreign_keys: | ||||
| - gitlab_main_clusterwide | ||||
| sharding_key: | ||||
|  |  | |||
|  | @ -0,0 +1,11 @@ | |||
| --- | ||||
| table_name: p_ci_build_trace_metadata | ||||
| classes: | ||||
| - Ci::BuildTraceMetadata | ||||
| feature_categories: | ||||
| - continuous_integration | ||||
| description: Routing table for ci_build_trace_metadata | ||||
| introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162310 | ||||
| milestone: '14.2' | ||||
| gitlab_schema: gitlab_ci | ||||
| sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/458479 | ||||
|  | @ -0,0 +1,23 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| class CreatePCiBuildTraceMetadata < Gitlab::Database::Migration[2.2] | ||||
|   milestone '17.4' | ||||
| 
 | ||||
|   # rubocop:disable Migration/EnsureFactoryForTable -- No factory needed | ||||
|   def change | ||||
|     create_table(:p_ci_build_trace_metadata, primary_key: [:build_id, :partition_id], | ||||
|       options: 'PARTITION BY LIST (partition_id)', if_not_exists: true) do |t| | ||||
|       t.bigint :build_id, null: false | ||||
|       t.bigint :partition_id, null: false | ||||
|       t.bigint :trace_artifact_id | ||||
|       t.datetime_with_timezone :last_archival_attempt_at | ||||
|       t.datetime_with_timezone :archived_at | ||||
|       t.integer :archival_attempts, default: 0, null: false, limit: 2 | ||||
|       t.binary :checksum | ||||
|       t.binary :remote_checksum | ||||
| 
 | ||||
|       t.index :trace_artifact_id | ||||
|     end | ||||
|   end | ||||
|   # rubocop:enable Migration/EnsureFactoryForTable -- No factory needed | ||||
| end | ||||
|  | @ -0,0 +1,19 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| class DropIdxVulnerabilityOccurrencesDedup < Gitlab::Database::Migration[2.2] | ||||
|   milestone '17.4' | ||||
| 
 | ||||
|   disable_ddl_transaction! | ||||
| 
 | ||||
|   TABLE = :vulnerability_occurrences | ||||
|   INDEX_NAME = 'index_vulnerability_occurrences_deduplication' | ||||
|   COLUMNS = %i[project_id report_type project_fingerprint] | ||||
| 
 | ||||
|   def up | ||||
|     remove_concurrent_index TABLE, COLUMNS, name: INDEX_NAME | ||||
|   end | ||||
| 
 | ||||
|   def down | ||||
|     add_concurrent_index TABLE, COLUMNS, name: INDEX_NAME | ||||
|   end | ||||
| end | ||||
|  | @ -0,0 +1,21 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| class RemoveProjectsDastSitesProjectIdFk < Gitlab::Database::Migration[2.2] | ||||
|   milestone '17.4' | ||||
|   disable_ddl_transaction! | ||||
| 
 | ||||
|   FOREIGN_KEY_NAME = "fk_rails_6febb6ea9c" | ||||
| 
 | ||||
|   def up | ||||
|     with_lock_retries do | ||||
|       remove_foreign_key_if_exists(:dast_sites, :projects, | ||||
|         name: FOREIGN_KEY_NAME, reverse_lock_order: true) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def down | ||||
|     add_concurrent_foreign_key(:dast_sites, :projects, | ||||
|       name: FOREIGN_KEY_NAME, column: :project_id, | ||||
|       target_column: :id, on_delete: :cascade) | ||||
|   end | ||||
| end | ||||
|  | @ -0,0 +1,27 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| class FkCiBuildsCiBuildTraceMetadata < Gitlab::Database::Migration[2.2] | ||||
|   include Gitlab::Database::PartitioningMigrationHelpers | ||||
| 
 | ||||
|   disable_ddl_transaction! | ||||
|   milestone '17.4' | ||||
| 
 | ||||
|   def up | ||||
|     add_concurrent_partitioned_foreign_key( | ||||
|       :p_ci_build_trace_metadata, :p_ci_builds, | ||||
|       name: :fk_rails_aebc78111f_p, | ||||
|       column: [:partition_id, :build_id], | ||||
|       target_column: [:partition_id, :id], | ||||
|       on_update: :cascade, | ||||
|       on_delete: :cascade, | ||||
|       reverse_lock_order: true | ||||
|     ) | ||||
|   end | ||||
| 
 | ||||
|   def down | ||||
|     with_lock_retries do | ||||
|       remove_foreign_key_if_exists :p_ci_build_trace_metadata, :p_ci_builds, | ||||
|         name: :fk_rails_aebc78111f_p, reverse_lock_order: true | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -0,0 +1,27 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| class FkCiJobsArtifactsCiBuildTraceMetadata < Gitlab::Database::Migration[2.2] | ||||
|   include Gitlab::Database::PartitioningMigrationHelpers | ||||
| 
 | ||||
|   disable_ddl_transaction! | ||||
|   milestone '17.4' | ||||
| 
 | ||||
|   def up | ||||
|     add_concurrent_partitioned_foreign_key( | ||||
|       :p_ci_build_trace_metadata, :p_ci_job_artifacts, | ||||
|       name: :fk_21d25cac1a_p, | ||||
|       column: [:partition_id, :trace_artifact_id], | ||||
|       target_column: [:partition_id, :id], | ||||
|       on_update: :cascade, | ||||
|       on_delete: :cascade, | ||||
|       reverse_lock_order: true | ||||
|     ) | ||||
|   end | ||||
| 
 | ||||
|   def down | ||||
|     with_lock_retries do | ||||
|       remove_foreign_key_if_exists :p_ci_build_trace_metadata, :p_ci_job_artifacts, | ||||
|         name: :fk_21d25cac1a_p, reverse_lock_order: true | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -0,0 +1,62 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| class PartitionCiBuildTraceMetadata < Gitlab::Database::Migration[2.2] | ||||
|   milestone '17.4' | ||||
|   disable_ddl_transaction! | ||||
| 
 | ||||
|   # rubocop:disable Migration/SchemaAdditionMethodsNoPost -- recreates the table in the same transaction | ||||
|   def up | ||||
|     with_lock_retries do | ||||
|       lock_tables(:p_ci_builds, :p_ci_job_artifacts, :ci_build_trace_metadata, mode: :access_exclusive) | ||||
| 
 | ||||
|       drop_table(:ci_build_trace_metadata) | ||||
| 
 | ||||
|       connection.execute(<<~SQL) | ||||
|         CREATE TABLE IF NOT EXISTS ci_build_trace_metadata | ||||
|           PARTITION OF p_ci_build_trace_metadata | ||||
|           FOR VALUES IN (100, 101, 102); | ||||
|       SQL | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def down | ||||
|     drop_table(:ci_build_trace_metadata, if_exists: true) | ||||
| 
 | ||||
|     create_table(:ci_build_trace_metadata, id: false, if_not_exists: true) do |t| | ||||
|       t.bigint :build_id, null: false, default: nil, primary_key: true | ||||
|       t.bigint :trace_artifact_id | ||||
|       t.integer :archival_attempts, default: 0, null: false, limit: 2 | ||||
|       t.binary :checksum | ||||
|       t.binary :remote_checksum | ||||
|       t.datetime_with_timezone :last_archival_attempt_at | ||||
|       t.datetime_with_timezone :archived_at | ||||
|       t.bigint :partition_id, null: false | ||||
| 
 | ||||
|       t.index [:trace_artifact_id, :partition_id], | ||||
|         name: :index_ci_build_trace_metadata_on_trace_artifact_id_partition_id | ||||
|       t.index [:partition_id, :build_id], unique: true, | ||||
|         name: :index_ci_build_trace_metadata_on_partition_id_build_id | ||||
|     end | ||||
| 
 | ||||
|     add_concurrent_foreign_key( | ||||
|       :ci_build_trace_metadata, :p_ci_builds, | ||||
|       column: [:partition_id, :build_id], | ||||
|       target_column: [:partition_id, :id], | ||||
|       on_update: :cascade, | ||||
|       on_delete: :cascade, | ||||
|       reverse_lock_order: true, | ||||
|       name: :fk_rails_aebc78111f_p | ||||
|     ) | ||||
| 
 | ||||
|     add_concurrent_foreign_key( | ||||
|       :ci_build_trace_metadata, :p_ci_job_artifacts, | ||||
|       column: [:partition_id, :trace_artifact_id], | ||||
|       target_column: [:partition_id, :id], | ||||
|       on_update: :cascade, | ||||
|       on_delete: :cascade, | ||||
|       reverse_lock_order: true, | ||||
|       name: :fk_21d25cac1a_p | ||||
|     ) | ||||
|   end | ||||
|   # rubocop:enable Migration/SchemaAdditionMethodsNoPost | ||||
| end | ||||
|  | @ -0,0 +1 @@ | |||
| 519360df6518a135db8b7f543819bfa1984ec0fc3f12cbf3324e0f99602c2987 | ||||
|  | @ -0,0 +1 @@ | |||
| 3711a08edad1fa122bfce2c5c1183e6514430c1ea3253c707dbdf7c84eebeee2 | ||||
|  | @ -0,0 +1 @@ | |||
| 76ac52c3eeb536a7a0393bf52d0dfe10029d70927cd28697b7bb57969b3ec47b | ||||
|  | @ -0,0 +1 @@ | |||
| 24f8129c565f649626fb34a05075a3c43acb3336020add263d0595facd45fc25 | ||||
|  | @ -0,0 +1 @@ | |||
| c7e5dc7f7ca4dbbf62ae0d53eef6beba539ddd75a556a1b20e3f8612997c1204 | ||||
|  | @ -0,0 +1 @@ | |||
| bf7bd395aac7d28c0982682b5356383d0de4d7b868fa5690c79adc6da4b70022 | ||||
|  | @ -7743,15 +7743,27 @@ CREATE SEQUENCE ci_build_trace_chunks_id_seq | |||
| 
 | ||||
| ALTER SEQUENCE ci_build_trace_chunks_id_seq OWNED BY ci_build_trace_chunks.id; | ||||
| 
 | ||||
| CREATE TABLE ci_build_trace_metadata ( | ||||
| CREATE TABLE p_ci_build_trace_metadata ( | ||||
|     build_id bigint NOT NULL, | ||||
|     partition_id bigint NOT NULL, | ||||
|     trace_artifact_id bigint, | ||||
|     archival_attempts smallint DEFAULT 0 NOT NULL, | ||||
|     checksum bytea, | ||||
|     remote_checksum bytea, | ||||
|     last_archival_attempt_at timestamp with time zone, | ||||
|     archived_at timestamp with time zone, | ||||
|     partition_id bigint NOT NULL | ||||
|     archival_attempts smallint DEFAULT 0 NOT NULL, | ||||
|     checksum bytea, | ||||
|     remote_checksum bytea | ||||
| ) | ||||
| PARTITION BY LIST (partition_id); | ||||
| 
 | ||||
| CREATE TABLE ci_build_trace_metadata ( | ||||
|     build_id bigint NOT NULL, | ||||
|     partition_id bigint NOT NULL, | ||||
|     trace_artifact_id bigint, | ||||
|     last_archival_attempt_at timestamp with time zone, | ||||
|     archived_at timestamp with time zone, | ||||
|     archival_attempts smallint DEFAULT 0 NOT NULL, | ||||
|     checksum bytea, | ||||
|     remote_checksum bytea | ||||
| ); | ||||
| 
 | ||||
| CREATE TABLE ci_builds ( | ||||
|  | @ -20970,6 +20982,8 @@ ALTER TABLE ONLY namespace_descendants ATTACH PARTITION gitlab_partitions_static | |||
| 
 | ||||
| ALTER TABLE ONLY namespace_descendants ATTACH PARTITION gitlab_partitions_static.namespace_descendants_31 FOR VALUES WITH (modulus 32, remainder 31); | ||||
| 
 | ||||
| ALTER TABLE ONLY p_ci_build_trace_metadata ATTACH PARTITION ci_build_trace_metadata FOR VALUES IN ('100', '101', '102'); | ||||
| 
 | ||||
| ALTER TABLE ONLY p_ci_builds ATTACH PARTITION ci_builds FOR VALUES IN ('100'); | ||||
| 
 | ||||
| ALTER TABLE ONLY p_ci_builds_metadata ATTACH PARTITION ci_builds_metadata FOR VALUES IN ('100'); | ||||
|  | @ -23143,8 +23157,11 @@ ALTER TABLE ONLY ci_build_report_results | |||
| ALTER TABLE ONLY ci_build_trace_chunks | ||||
|     ADD CONSTRAINT ci_build_trace_chunks_pkey PRIMARY KEY (id); | ||||
| 
 | ||||
| ALTER TABLE ONLY p_ci_build_trace_metadata | ||||
|     ADD CONSTRAINT p_ci_build_trace_metadata_pkey PRIMARY KEY (build_id, partition_id); | ||||
| 
 | ||||
| ALTER TABLE ONLY ci_build_trace_metadata | ||||
|     ADD CONSTRAINT ci_build_trace_metadata_pkey PRIMARY KEY (build_id); | ||||
|     ADD CONSTRAINT ci_build_trace_metadata_pkey PRIMARY KEY (build_id, partition_id); | ||||
| 
 | ||||
| ALTER TABLE ONLY p_ci_builds_metadata | ||||
|     ADD CONSTRAINT p_ci_builds_metadata_pkey PRIMARY KEY (id, partition_id); | ||||
|  | @ -26196,6 +26213,10 @@ CREATE INDEX ca_aggregations_last_full_run_at ON analytics_cycle_analytics_aggre | |||
| 
 | ||||
| CREATE INDEX ca_aggregations_last_incremental_run_at ON analytics_cycle_analytics_aggregations USING btree (last_incremental_run_at NULLS FIRST) WHERE (enabled IS TRUE); | ||||
| 
 | ||||
| CREATE INDEX index_p_ci_build_trace_metadata_on_trace_artifact_id ON ONLY p_ci_build_trace_metadata USING btree (trace_artifact_id); | ||||
| 
 | ||||
| CREATE INDEX ci_build_trace_metadata_trace_artifact_id_idx ON ci_build_trace_metadata USING btree (trace_artifact_id); | ||||
| 
 | ||||
| CREATE INDEX p_ci_builds_status_created_at_project_id_idx ON ONLY p_ci_builds USING btree (status, created_at, project_id) WHERE ((type)::text = 'Ci::Build'::text); | ||||
| 
 | ||||
| CREATE INDEX ci_builds_gitlab_monitor_metrics ON ci_builds USING btree (status, created_at, project_id) WHERE ((type)::text = 'Ci::Build'::text); | ||||
|  | @ -27022,10 +27043,6 @@ CREATE UNIQUE INDEX index_ci_build_trace_chunks_on_build_id_and_chunk_index ON c | |||
| 
 | ||||
| CREATE INDEX index_ci_build_trace_chunks_on_partition_id_build_id ON ci_build_trace_chunks USING btree (partition_id, build_id); | ||||
| 
 | ||||
| CREATE UNIQUE INDEX index_ci_build_trace_metadata_on_partition_id_build_id ON ci_build_trace_metadata USING btree (partition_id, build_id); | ||||
| 
 | ||||
| CREATE INDEX index_ci_build_trace_metadata_on_trace_artifact_id_partition_id ON ci_build_trace_metadata USING btree (trace_artifact_id, partition_id); | ||||
| 
 | ||||
| CREATE INDEX p_ci_builds_metadata_build_id_idx ON ONLY p_ci_builds_metadata USING btree (build_id) WHERE (has_exposed_artifacts IS TRUE); | ||||
| 
 | ||||
| CREATE INDEX index_ci_builds_metadata_on_build_id_and_has_exposed_artifacts ON ci_builds_metadata USING btree (build_id) WHERE (has_exposed_artifacts IS TRUE); | ||||
|  | @ -30380,8 +30397,6 @@ CREATE INDEX index_vulnerability_occurrence_pipelines_occurrence_id_and_id ON vu | |||
| 
 | ||||
| CREATE INDEX index_vulnerability_occurrence_pipelines_on_pipeline_id ON vulnerability_occurrence_pipelines USING btree (pipeline_id); | ||||
| 
 | ||||
| CREATE INDEX index_vulnerability_occurrences_deduplication ON vulnerability_occurrences USING btree (project_id, report_type, project_fingerprint); | ||||
| 
 | ||||
| CREATE INDEX index_vulnerability_occurrences_for_override_uuids_logic ON vulnerability_occurrences USING btree (project_id, report_type, location_fingerprint); | ||||
| 
 | ||||
| CREATE INDEX index_vulnerability_occurrences_on_initial_pipeline_id ON vulnerability_occurrences USING btree (initial_pipeline_id); | ||||
|  | @ -32254,6 +32269,10 @@ ALTER INDEX index_on_namespace_descendants_outdated ATTACH PARTITION gitlab_part | |||
| 
 | ||||
| ALTER INDEX namespace_descendants_pkey ATTACH PARTITION gitlab_partitions_static.namespace_descendants_31_pkey; | ||||
| 
 | ||||
| ALTER INDEX p_ci_build_trace_metadata_pkey ATTACH PARTITION ci_build_trace_metadata_pkey; | ||||
| 
 | ||||
| ALTER INDEX index_p_ci_build_trace_metadata_on_trace_artifact_id ATTACH PARTITION ci_build_trace_metadata_trace_artifact_id_idx; | ||||
| 
 | ||||
| ALTER INDEX p_ci_builds_status_created_at_project_id_idx ATTACH PARTITION ci_builds_gitlab_monitor_metrics; | ||||
| 
 | ||||
| ALTER INDEX p_ci_builds_metadata_pkey ATTACH PARTITION ci_builds_metadata_pkey; | ||||
|  | @ -32844,7 +32863,7 @@ ALTER TABLE ONLY coverage_fuzzing_corpuses | |||
| ALTER TABLE ONLY namespace_settings | ||||
|     ADD CONSTRAINT fk_20cf0eb2f9 FOREIGN KEY (default_compliance_framework_id) REFERENCES compliance_management_frameworks(id) ON DELETE SET NULL; | ||||
| 
 | ||||
| ALTER TABLE ONLY ci_build_trace_metadata | ||||
| ALTER TABLE p_ci_build_trace_metadata | ||||
|     ADD CONSTRAINT fk_21d25cac1a_p FOREIGN KEY (partition_id, trace_artifact_id) REFERENCES p_ci_job_artifacts(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE; | ||||
| 
 | ||||
| ALTER TABLE ONLY users_star_projects | ||||
|  | @ -35028,9 +35047,6 @@ ALTER TABLE ONLY project_compliance_framework_settings | |||
| ALTER TABLE ONLY users_security_dashboard_projects | ||||
|     ADD CONSTRAINT fk_rails_6f6cf8e66e FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; | ||||
| 
 | ||||
| ALTER TABLE ONLY dast_sites | ||||
|     ADD CONSTRAINT fk_rails_6febb6ea9c FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; | ||||
| 
 | ||||
| ALTER TABLE ONLY analytics_dashboards_pointers | ||||
|     ADD CONSTRAINT fk_rails_7027b7eaa9 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE; | ||||
| 
 | ||||
|  | @ -35502,7 +35518,7 @@ ALTER TABLE ONLY analytics_cycle_analytics_group_stages | |||
| ALTER TABLE ONLY metrics_dashboard_annotations | ||||
|     ADD CONSTRAINT fk_rails_aeb11a7643 FOREIGN KEY (environment_id) REFERENCES environments(id) ON DELETE CASCADE; | ||||
| 
 | ||||
| ALTER TABLE ONLY ci_build_trace_metadata | ||||
| ALTER TABLE p_ci_build_trace_metadata | ||||
|     ADD CONSTRAINT fk_rails_aebc78111f_p FOREIGN KEY (partition_id, build_id) REFERENCES p_ci_builds(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE; | ||||
| 
 | ||||
| ALTER TABLE ONLY bulk_import_trackers | ||||
|  |  | |||
|  | @ -669,8 +669,7 @@ WARNING: | |||
|     [very specific cases](https://handbook.gitlab.com/handbook/engineering/workflow/#criteria-for-merging-during-broken-master). | ||||
|     For other cases, follow these [handbook instructions](https://handbook.gitlab.com/handbook/engineering/workflow/#merging-during-broken-master). | ||||
|   - If the latest pipeline was created before the merge request was approved, start a new pipeline to ensure that full RSpec suite has been run. You may skip this step only if the merge request does not contain any backend change. | ||||
|   - If the **latest [merged results pipeline](../ci/pipelines/merged_results_pipelines.md)** was **created less than 8 hours ago**, you | ||||
|     may merge without starting a new pipeline as the merge request is close enough to the target branch. | ||||
|   - If the **latest [merged results pipeline](../ci/pipelines/merged_results_pipelines.md)** was **created less than 8 hours ago (72 hours for stable branches)**, you may merge without starting a new pipeline as the merge request is close enough to the target branch. | ||||
| - When you set the MR to auto-merge, you should take over | ||||
|   subsequent revisions for anything that would be spotted after that. | ||||
| - For merge requests that have had [Squash and merge](../user/project/merge_requests/squash_and_merge.md) set, | ||||
|  |  | |||
|  | @ -246,7 +246,7 @@ Merge train pipelines run a single `pre-merge-checks` job which ensures the late | |||
| 
 | ||||
| 1. A [Merged Results pipeline](../../ci/pipelines/merged_results_pipelines.md) | ||||
| 1. A [`tier-3` pipeline](#pipeline-tiers) (i.e. full pipeline, not predictive one) | ||||
| 1. Created at most 8 hours ago | ||||
| 1. Created at most 8 hours ago (72 hours for stable branches) | ||||
| 
 | ||||
| We opened [a feedback issue](https://gitlab.com/gitlab-org/quality/engineering-productivity/team/-/issues/513) | ||||
| to iterate on this solution. | ||||
|  |  | |||
|  | @ -102,3 +102,23 @@ The following table shows what type of aggregation is used for each search perio | |||
| ### Data retention | ||||
| 
 | ||||
| GitLab has a retention limit of 30 days for all ingested metrics. | ||||
| 
 | ||||
| ### Create an issue for a metric | ||||
| 
 | ||||
| You can create an issue to track any action taken to resolve or investigate a metric. To create an issue for a metric: | ||||
| 
 | ||||
| 1. On the left sidebar, select **Search or go to** and find your project. | ||||
| 1. Select **Monitor > Metrics**. | ||||
| 1. From the list of metrics, select a metric. | ||||
| 1. Select **Create issue**. | ||||
| 
 | ||||
| The issue is created in the selected project and pre-filled with information from the metric. | ||||
| You can edit the issue title and description. | ||||
| 
 | ||||
| ### View issues related to a metric | ||||
| 
 | ||||
| 1. On the left sidebar, select **Search or go to** and find your project. | ||||
| 1. Select **Monitor > Metrics**. | ||||
| 1. From the list of metrics, select a metric. | ||||
| 1. Scroll to **Related issues**. | ||||
| 1. Optional. To view the issue details, select an issue. | ||||
|  |  | |||
|  | @ -277,7 +277,11 @@ A job is queued. When the job finishes, the subscription details are updated. | |||
| 
 | ||||
| ## View your subscription | ||||
| 
 | ||||
| If you are an administrator, you can view the status of your subscription: | ||||
| Prerequisites: | ||||
| 
 | ||||
| - You must be an administrator. | ||||
| 
 | ||||
| You can view the status of your subscription: | ||||
| 
 | ||||
| 1. On the left sidebar, at the bottom, select **Admin**. | ||||
| 1. Select **Subscription**. | ||||
|  |  | |||
|  | @ -17,7 +17,7 @@ platform for software development. GitLab adds many powerful | |||
| 
 | ||||
| | | | | | ||||
| |--|--|--| | ||||
| | [**Get started**](get_started.md) **{chevron-right}** | [**Install Git**](how_to_install_git/index.md) **{chevron-right}** | [**Tutorial: Create your first commit**](../../tutorials/make_first_git_commit/index.md) **{chevron-right}** | | ||||
| | [**Clone a repository to your local machine**](clone.md) **{chevron-right}** | [**Create a branch for your changes**](branch.md) **{chevron-right}** | [**Add files to your branch**](../../gitlab-basics/add-file.md) **{chevron-right}** | | ||||
| | [**Stash changes for later**](stash.md) **{chevron-right}** | [**Undo changes**](undo.md) **{chevron-right}** | [**Tutorial: Update Git commit messages**](../../tutorials/update_commit_messages/index.md) **{chevron-right}** | | ||||
| | [**Rebase to address merge conflicts**](git_rebase.md) **{chevron-right}** | [**Common Git commands**](../../gitlab-basics/start-using-git.md) **{chevron-right}** | [**Troubleshooting**](troubleshooting_git.md) **{chevron-right}** | | ||||
| | [**Get started**](get_started.md) **{chevron-right}**<br><br>Overview of how features fit together. | [**Install Git**](how_to_install_git/index.md) **{chevron-right}**<br><br>Download, configuration, system requirements. | [**Tutorial: Create your first commit**](../../tutorials/make_first_git_commit/index.md) **{chevron-right}**<br><br>Initial commit, Git basics, repository setup. | | ||||
| | [**Clone a repository to your local machine**](clone.md) **{chevron-right}**<br><br>Local repository, clone, remote repository, SSH. | [**Create a branch for your changes**](branch.md) **{chevron-right}**<br><br>Branching, branch switch, checkout. | [**Add files to your branch**](../../gitlab-basics/add-file.md) **{chevron-right}**<br><br>Git add, staging changes, file management, commits. | | ||||
| | [**Stash changes for later**](stash.md) **{chevron-right}**<br><br>Temporary storage, work in progress, context switching. | [**Undo changes**](undo.md) **{chevron-right}**<br><br>Reverting commits, removing changes, Git reset, unstage. | [**Tutorial: Update Git commit messages**](../../tutorials/update_commit_messages/index.md) **{chevron-right}**<br><br>Commit message editing, version history, best practices. | | ||||
| | [**Rebase to address merge conflicts**](git_rebase.md) **{chevron-right}**<br><br>Conflict resolution, rebase, branch management. | [**Common Git commands**](../../gitlab-basics/start-using-git.md) **{chevron-right}**<br><br>Git cheatsheet, basic operations, command line. | [**Troubleshooting**](troubleshooting_git.md) **{chevron-right}**<br><br>Error resolution, common issues, debugging, Git problems. | | ||||
|  |  | |||
|  | @ -336,10 +336,13 @@ return this error: | |||
| 
 | ||||
| To resolve this issue, you can update the password expiration by either: | ||||
| 
 | ||||
| - Using the `gitlab-rails console`: | ||||
| - Using the [GitLab Rails console](../../administration/operations/rails_console.md) | ||||
|   to check and update the user data: | ||||
| 
 | ||||
|   ```ruby | ||||
|   gitlab-rails console | ||||
|   user = User.find_by_username('<USERNAME>') | ||||
|   user.password_expired? | ||||
|   user.password_expires_at | ||||
|   user.update!(password_expires_at: nil) | ||||
|   ``` | ||||
| 
 | ||||
|  |  | |||
|  | @ -11,5 +11,5 @@ Store your source files in a repository and create merge requests. Write, debug, | |||
| 
 | ||||
| | | | | | ||||
| |--|--|--| | ||||
| | [**Getting started**](../user/get_started/get_started_managing_code.md) **{chevron-right}** | [**Repositories**](../user/project/repository/index.md) **{chevron-right}** | [**Merge requests**](../user/project/merge_requests/index.md) **{chevron-right}** | | ||||
| | [**Remote development**](../user/project/remote_development/index.md) **{chevron-right}** | | | | ||||
| | [**Getting started**](../user/get_started/get_started_managing_code.md) **{chevron-right}**<br><br>Overview of how features fit together. | [**Repositories**](../user/project/repository/index.md) **{chevron-right}**<br><br>Version control, code storage, Git repositories, repository monitoring. | [**Merge requests**](../user/project/merge_requests/index.md) **{chevron-right}**<br><br>Code review, collaboration, branch merging, commits. | | ||||
| | [**Remote development**](../user/project/remote_development/index.md) **{chevron-right}**<br><br>Web IDE, workspaces. | | | | ||||
|  |  | |||
|  | @ -24,11 +24,11 @@ with [Scaled Agile Framework (SAFe)](https://handbook.gitlab.com/handbook/market | |||
| 
 | ||||
| | | | | | ||||
| |--|--|--| | ||||
| | [**Getting started**](../user/get_started/get_started_planning_work.md) **{chevron-right}** | [**Tutorial: Use GitLab for scrum**](../tutorials/scrum_events/index.md) **{chevron-right}** | [**Tutorial: Use GitLab for Kanban**](../tutorials/kanban/index.md) **{chevron-right}** | | ||||
| | [**Labels**](../user/project/labels.md) **{chevron-right}** | [**Iterations**](../user/group/iterations/index.md) **{chevron-right}** | [**Milestones**](../user/project/milestones/index.md) **{chevron-right}** | | ||||
| | [**Issues**](../user/project/issues/index.md) **{chevron-right}** | [**Issue boards**](../user/project/issue_board.md) **{chevron-right}** | [**Comments and threads**](../user/discussions/index.md) **{chevron-right}** | | ||||
| | [**Tasks**](../user/tasks.md) **{chevron-right}** | [**Requirements**](../user/project/requirements/index.md) **{chevron-right}** | [**Time tracking**](../user/project/time_tracking.md) **{chevron-right}** | | ||||
| | [**CRM**](../user/crm/index.md) **{chevron-right}** | [**Wikis**](../user/project/wiki/index.md) **{chevron-right}** | [**Epics**](../user/group/epics/index.md) **{chevron-right}** | | ||||
| | [**Roadmaps**](../user/group/roadmap/index.md) **{chevron-right}** | [**Planning hierarchies**](../user/group/planning_hierarchy/index.md) **{chevron-right}** | [**Objectives and key results**](../user/okrs.md) **{chevron-right}** | | ||||
| | [**Keyboard shortcuts**](../user/shortcuts.md) **{chevron-right}** | [**Quick actions**](../user/project/quick_actions.md) **{chevron-right}** | [**Markdown**](../user/markdown.md) **{chevron-right}** | | ||||
| | [**To-Do List**](../user/todos.md) **{chevron-right}** | | | | ||||
| | [**Getting started**](../user/get_started/get_started_planning_work.md) **{chevron-right}** <br><br>Overview of how features fit together. | [**Tutorial: Use GitLab for scrum**](../tutorials/scrum_events/index.md) **{chevron-right}** <br><br>Sprints, backlog, user stories, scrum lifecycle. | [**Tutorial: Use GitLab for Kanban**](../tutorials/kanban/index.md) **{chevron-right}** <br><br>Kanban board, columns, work in progress, flow, distribution. | | ||||
| | [**Labels**](../user/project/labels.md) **{chevron-right}** <br><br>Project labels, group labels, nested scopes, filtering. | [**Iterations**](../user/group/iterations/index.md) **{chevron-right}** <br><br>Time-boxed workflow, program increments, cadence, sprints. | [**Milestones**](../user/project/milestones/index.md) **{chevron-right}** <br><br>Burndown charts, goals, progress tracking, releases. | | ||||
| | [**Issues**](../user/project/issues/index.md) **{chevron-right}** <br><br>Tasks, bug reports, feature requests, tracking. | [**Issue boards**](../user/project/issue_board.md) **{chevron-right}** <br><br>Visualization, workflow, Kanban, prioritization. | [**Comments and threads**](../user/discussions/index.md) **{chevron-right}** <br><br> Mentions, locked discussions, internal notes, thread resolution. | | ||||
| | [**Tasks**](../user/tasks.md) **{chevron-right}** <br><br>Task labels, confidential tasks, linked items, task weights. | [**Requirements**](../user/project/requirements/index.md) **{chevron-right}** <br><br>Acceptance criteria, requirements test reports, CSV import. | [**Time tracking**](../user/project/time_tracking.md) **{chevron-right}** <br><br>Estimates, time spent, reporting. | | ||||
| | [**CRM**](../user/crm/index.md) **{chevron-right}** <br><br>Customer management, organizations, contacts, permissions. | [**Wikis**](../user/project/wiki/index.md) **{chevron-right}** <br><br>Documentation, external wikis, wiki events, history. | [**Epics**](../user/group/epics/index.md) **{chevron-right}** <br><br>Roadmaps, hierarchies, planning, issue progress. | | ||||
| | [**Roadmaps**](../user/group/roadmap/index.md) **{chevron-right}** <br><br>Epic progress, timelines, milestones, goals. | [**Planning hierarchies**](../user/group/planning_hierarchy/index.md) **{chevron-right}** <br><br>Organization, structure, multi-level epics, nesting. | [**Objectives and key results**](../user/okrs.md) **{chevron-right}** <br><br>Goal setting, performance tracking, child objectives, health status. | | ||||
| | [**Keyboard shortcuts**](../user/shortcuts.md) **{chevron-right}** <br><br>Global shortcuts, navigation, quick access. | [**Quick actions**](../user/project/quick_actions.md) **{chevron-right}** <br><br>Commands, shortcuts, inline actions. | [**Markdown**](../user/markdown.md) **{chevron-right}** <br><br>Formatting, inline HTML, GitLab-specific references, diagrams and flowcharts. | | ||||
| | [**To-Do List**](../user/todos.md) **{chevron-right}** <br><br>Task management, actions, access changes. | | | | ||||
|  |  | |||
|  | @ -12,7 +12,7 @@ and give everyone access to the projects they need. | |||
| 
 | ||||
| | | | | | ||||
| |--|--|--| | ||||
| | [**Tutorial: Set up your organization**](../tutorials/manage_user/index.md) **{chevron-right}** | [**Namespaces**](../user/namespace/index.md) **{chevron-right}** | [**Members**](../user/project/members/index.md) **{chevron-right}** | | ||||
| | [**Organization** (in development)](../user/organization/index.md) **{chevron-right}** | [**Groups**](../user/group/index.md) **{chevron-right}** | [**Sharing projects and groups**](../user/project/members/sharing_projects_groups.md) **{chevron-right}** | | ||||
| | [**Compliance**](../administration/compliance.md) **{chevron-right}** | [**Enterprise users**](../user/enterprise_user/index.md) **{chevron-right}** | [**Service accounts**](../user/profile/service_accounts.md) **{chevron-right}** | | ||||
| | [**User account options**](../user/profile/index.md) **{chevron-right}** | [**SSH keys**](../user/ssh.md) **{chevron-right}** | [**GitLab.com settings**](../user/gitlab_com/index.md) **{chevron-right}** | | ||||
| | [**Tutorial: Set up your organization**](../tutorials/manage_user/index.md) **{chevron-right}**<br><br>Setup, configuration, onboarding, organization structure. | [**Namespaces**](../user/namespace/index.md) **{chevron-right}**<br><br>Organization, hierarchy, project grouping. | [**Members**](../user/project/members/index.md) **{chevron-right}**<br><br>User management, roles, permissions, access levels. | | ||||
| | [**Organization** (in development)](../user/organization/index.md) **{chevron-right}**<br><br>Namespace hierarchy. | [**Groups**](../user/group/index.md) **{chevron-right}**<br><br>Project management, access control, client groups, team groups. | [**Sharing projects and groups**](../user/project/members/sharing_projects_groups.md) **{chevron-right}**<br><br>Invitations, group inheritance, project visibility. | | ||||
| | [**Compliance**](../administration/compliance.md) **{chevron-right}**<br><br>Compliance center, audit events, security policies, compliance frameworks. | [**Enterprise users**](../user/enterprise_user/index.md) **{chevron-right}**<br><br>Domain verification, two-factor authentication, enterprise user management, SAML response. | [**Service accounts**](../user/profile/service_accounts.md) **{chevron-right}**<br><br>Machine user, rate limits, personal access tokens. | | ||||
| | [**User account options**](../user/profile/index.md) **{chevron-right}**<br><br>Profile settings, preferences, authentication, notifications. | [**SSH keys**](../user/ssh.md) **{chevron-right}**<br><br>Authentication, permissions, key types, ownership. | [**GitLab.com settings**](../user/gitlab_com/index.md) **{chevron-right}**<br><br>Instance configurations. | | ||||
|  |  | |||
|  | @ -61,7 +61,7 @@ DETAILS: | |||
| - Helps you understand the selected code by explaining it more clearly. | ||||
| - LLM: Anthropic [Claude 3.5 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet) | ||||
| - View documentation for explaining code in: | ||||
|   - [The IDE](../gitlab_duo_chat/examples.md#explain-code). | ||||
|   - [The IDE](../gitlab_duo_chat/examples.md#explain-selected-code). | ||||
|   - [A file](../../user/project/repository/code_explain.md). | ||||
|   - [A merge request](../../user/project/merge_requests/changes.md#explain-code-in-a-merge-request). | ||||
| 
 | ||||
|  |  | |||
|  | @ -34,10 +34,46 @@ its knowledge base is updated daily. | |||
| - On GitLab.com the, the most recent version of the documentation is used. | ||||
| - On Self-managed and GitLab Dedicated, the documentation for the version of the instance is used. | ||||
| 
 | ||||
| ## Ask about code | ||||
| ## Explain selected code | ||||
| 
 | ||||
| DETAILS: | ||||
| **Tier GitLab.com and Self-managed:** Premium or Ultimate for a limited time. In the future, Premium with GitLab Duo Pro or Ultimate with [GitLab Duo Pro or Enterprise](../../subscriptions/subscription-add-ons.md). **GitLab Dedicated:** GitLab Duo Pro or Enterprise. | ||||
| **Tier: GitLab.com and Self-managed:** Premium or Ultimate for a limited time. In the future, Premium with GitLab Duo Pro or Ultimate with [GitLab Duo Pro or Enterprise](../../subscriptions/subscription-add-ons.md). **GitLab Dedicated:** GitLab Duo Pro or Enterprise. | ||||
| **Offering:** GitLab.com, Self-managed, GitLab Dedicated | ||||
| **Editors:** GitLab UI, Web IDE, VS Code, JetBrains IDEs | ||||
| **LLMs:** Anthropic: [`claude-3-5-sonnet-20240620`](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet) | ||||
| 
 | ||||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/429915) for GitLab.com in GitLab 16.7. | ||||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/429915) for self-managed and GitLab Dedicated in GitLab 16.8. | ||||
| 
 | ||||
| You can ask GitLab Duo Chat to explain selected code: | ||||
| 
 | ||||
| 1. Select some code in your IDE. | ||||
| 1. In Duo Chat, type `/explain`. | ||||
| 
 | ||||
|     | ||||
| 
 | ||||
| You can also add additional instructions to be considered, for example: | ||||
| 
 | ||||
| - `/explain the performance` | ||||
| - `/explain focus on the algorithm` | ||||
| - `/explain the performance gains or losses using this code` | ||||
| - `/explain the object inheritance` (classes, object-oriented) | ||||
| - `/explain why a static variable is used here` (C++) | ||||
| - `/explain how this function would cause a segmentation fault` (C) | ||||
| - `/explain how concurrency works in this context` (Go) | ||||
| - `/explain how the request reaches the client` (REST API, database) | ||||
| 
 | ||||
| For more information, see [Use GitLab Duo Chat in VS Code](index.md#use-gitlab-duo-chat-in-vs-code). | ||||
| 
 | ||||
| In the GitLab UI, you can also explain code in: | ||||
| 
 | ||||
| - A [file](../project/repository/code_explain.md). | ||||
| - A [merge request](../project/merge_requests/changes.md#explain-code-in-a-merge-request). | ||||
| 
 | ||||
| ## Ask about or generate code | ||||
| 
 | ||||
| DETAILS: | ||||
| **Tier: GitLab.com and Self-managed:** Premium or Ultimate for a limited time. In the future, Premium with GitLab Duo Pro or Ultimate with [GitLab Duo Pro or Enterprise](../../subscriptions/subscription-add-ons.md). **GitLab Dedicated:** GitLab Duo Pro or Enterprise. | ||||
| **Offering:** GitLab.com, Self-managed, GitLab Dedicated | ||||
| **Editors:** GitLab UI, Web IDE, VS Code, JetBrains IDEs | ||||
| **LLMs:** Anthropic: [`claude-3-5-sonnet-20240620`](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet) | ||||
|  | @ -45,7 +81,15 @@ DETAILS: | |||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122235) for GitLab.com in GitLab 16.1. | ||||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122235) for self-managed and GitLab Dedicated in GitLab 16.8. | ||||
| 
 | ||||
| You can also ask GitLab Duo Chat to generate code: | ||||
| You can ask GitLab Duo Chat questions about code by pasting that code into | ||||
| the Duo Chat window. For example: | ||||
| 
 | ||||
| ```plaintext | ||||
| Provide a clear explanation of this Ruby code: def sum(a, b) a + b end. | ||||
| Describe what this code does and how it works. | ||||
| ``` | ||||
| 
 | ||||
| You can also ask Chat to generate code. For example: | ||||
| 
 | ||||
| - `Write a Ruby function that prints 'Hello, World!' when called.` | ||||
| - `Develop a JavaScript program that simulates a two-player Tic-Tac-Toe game. Provide both game logic and user interface, if applicable.` | ||||
|  | @ -54,11 +98,45 @@ You can also ask GitLab Duo Chat to generate code: | |||
| - `Create a product-consumer example with threads and shared memory in C++. Use atomic locks when possible.` | ||||
| - `Generate Rust code for high performance gRPC calls. Provide a source code example for a server and client.` | ||||
| 
 | ||||
| And you can ask GitLab Duo Chat to explain code: | ||||
| ## Refactor code in the IDE | ||||
| 
 | ||||
| - `Provide a clear explanation of the given Ruby code: def sum(a, b) a + b end. Describe what this code does and how it works.` | ||||
| DETAILS: | ||||
| **Tier: GitLab.com and Self-managed:** Premium or Ultimate for a limited time. In the future, Premium with GitLab Duo Pro or Ultimate with [GitLab Duo Pro or Enterprise](../../subscriptions/subscription-add-ons.md). **GitLab Dedicated:** GitLab Duo Pro or Enterprise. | ||||
| **Offering:** GitLab.com, Self-managed, GitLab Dedicated | ||||
| **Editors:** Web IDE, VS Code, JetBrains IDEs | ||||
| **LLMs:** Anthropic: [`claude-3-5-sonnet-20240620`](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet) | ||||
| 
 | ||||
| Alternatively, you can use the [`/explain` command](examples.md#explain-code) to explain the selected code in your editor. | ||||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/429915) for GitLab.com in GitLab 16.7. | ||||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/429915) for self-managed and GitLab Dedicated in GitLab 16.8. | ||||
| 
 | ||||
| `/refactor` is a special command to generate a refactoring suggestion for the selected code in your editor. | ||||
| You can include additional instructions to be considered. For example: | ||||
| 
 | ||||
| - Use a specific coding pattern, for example `/refactor with ActiveRecord` or `/refactor into a class providing static functions`. | ||||
| - Use a specific library, for example `/refactor using mysql`. | ||||
| - Use a specific function/algorithm, for example `/refactor into a stringstream with multiple lines` in C++. | ||||
| - Refactor to a different programming language, for example `/refactor to TypeScript`. | ||||
| - Focus on performance, for example `/refactor improving performance`. | ||||
| - Focus on potential vulnerabilities, for example `/refactor avoiding memory leaks and exploits`. | ||||
| 
 | ||||
| ## Fix code in the IDE | ||||
| 
 | ||||
| DETAILS: | ||||
| **Tier: GitLab.com and Self-managed:** Premium or Ultimate for a limited time. In the future, Premium with GitLab Duo Pro or Ultimate with [GitLab Duo Pro or Enterprise](../../subscriptions/subscription-add-ons.md). **GitLab Dedicated:** GitLab Duo Pro or Enterprise. | ||||
| **Offering:** GitLab.com, Self-managed, GitLab Dedicated | ||||
| **Editors:** Web IDE, VS Code, JetBrains IDEs | ||||
| **LLMs:** Anthropic: [`claude-3-5-sonnet-20240620`](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet) | ||||
| 
 | ||||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/429915) for GitLab.com, self-managed and GitLab Dedicated in GitLab 17.3. | ||||
| 
 | ||||
| `/fix` is a special command to generate a fix suggestion for the selected code in your editor. | ||||
| You can include additional instructions to be considered. For example: | ||||
| 
 | ||||
| - Focus on grammar and typos, for example, `/fix grammar mistakes and typos`. | ||||
| - Focus on a concrete algorithm or problem description, for example, `/fix duplicate database inserts` or `/fix race conditions`. | ||||
| - Focus on potential bugs that are not directly visible, for example, `/fix potential bugs`. | ||||
| - Focus on code performance problems, for example, `/fix performance problems`. | ||||
| - Focus on fixing the build when the code does not compile, for example, `/fix the build`. | ||||
| 
 | ||||
| ## Ask about CI/CD | ||||
| 
 | ||||
|  | @ -128,74 +206,6 @@ DETAILS: | |||
| 
 | ||||
| [Learn more](../application_security/vulnerabilities/index.md#explaining-a-vulnerability). | ||||
| 
 | ||||
| ## Explain code | ||||
| 
 | ||||
| DETAILS: | ||||
| **Tier: GitLab.com and Self-managed:** Premium or Ultimate for a limited time. In the future, Premium with GitLab Duo Pro or Ultimate with [GitLab Duo Pro or Enterprise](../../subscriptions/subscription-add-ons.md). **GitLab Dedicated:** GitLab Duo Pro or Enterprise. | ||||
| **Offering:** GitLab.com, Self-managed, GitLab Dedicated | ||||
| **Editors:** GitLab UI, Web IDE, VS Code, JetBrains IDEs | ||||
| **LLMs:** Anthropic: [`claude-3-5-sonnet-20240620`](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet) | ||||
| 
 | ||||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/429915) for GitLab.com in GitLab 16.7. | ||||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/429915) for self-managed and GitLab Dedicated in GitLab 16.8. | ||||
| 
 | ||||
| `/explain` is a special command to explain the selected code in your editor. | ||||
| You can also add additional instructions to be considered, for example: `/explain the performance` | ||||
| See [Use GitLab Duo Chat in VS Code](index.md#use-gitlab-duo-chat-in-vs-code) for more information. | ||||
| 
 | ||||
| - `/explain focus on the algorithm` | ||||
| - `/explain the performance gains or losses using this code` | ||||
| - `/explain the object inheritance` (classes, object-oriented) | ||||
| - `/explain why a static variable is used here` (C++) | ||||
| - `/explain how this function would cause a segmentation fault` (C) | ||||
| - `/explain how concurrency works in this context` (Go) | ||||
| - `/explain how the request reaches the client` (REST API, database) | ||||
| 
 | ||||
| You can also use the Web UI to explain code in: | ||||
| 
 | ||||
| - A [file](../project/repository/code_explain.md). | ||||
| - A [merge request](../project/merge_requests/changes.md#explain-code-in-a-merge-request). | ||||
| 
 | ||||
| ## Refactor code in the IDE | ||||
| 
 | ||||
| DETAILS: | ||||
| **Tier: GitLab.com and Self-managed:** Premium or Ultimate for a limited time. In the future, Premium with GitLab Duo Pro or Ultimate with [GitLab Duo Pro or Enterprise](../../subscriptions/subscription-add-ons.md). **GitLab Dedicated:** GitLab Duo Pro or Enterprise. | ||||
| **Offering:** GitLab.com, Self-managed, GitLab Dedicated | ||||
| **Editors:** Web IDE, VS Code, JetBrains IDEs | ||||
| **LLMs:** Anthropic: [`claude-3-5-sonnet-20240620`](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet) | ||||
| 
 | ||||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/429915) for GitLab.com in GitLab 16.7. | ||||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/429915) for self-managed and GitLab Dedicated in GitLab 16.8. | ||||
| 
 | ||||
| `/refactor` is a special command to generate a refactoring suggestion for the selected code in your editor. | ||||
| You can include additional instructions to be considered. For example: | ||||
| 
 | ||||
| - Use a specific coding pattern, for example `/refactor with ActiveRecord` or `/refactor into a class providing static functions`. | ||||
| - Use a specific library, for example `/refactor using mysql`. | ||||
| - Use a specific function/algorithm, for example `/refactor into a stringstream with multiple lines` in C++. | ||||
| - Refactor to a different programming language, for example `/refactor to TypeScript`. | ||||
| - Focus on performance, for example `/refactor improving performance`. | ||||
| - Focus on potential vulnerabilities, for example `/refactor avoiding memory leaks and exploits`. | ||||
| 
 | ||||
| ## Fix code in the IDE | ||||
| 
 | ||||
| DETAILS: | ||||
| **Tier: GitLab.com and Self-managed:** Premium or Ultimate for a limited time. In the future, Premium with GitLab Duo Pro or Ultimate with [GitLab Duo Pro or Enterprise](../../subscriptions/subscription-add-ons.md). **GitLab Dedicated:** GitLab Duo Pro or Enterprise. | ||||
| **Offering:** GitLab.com, Self-managed, GitLab Dedicated | ||||
| **Editors:** Web IDE, VS Code, JetBrains IDEs | ||||
| **LLMs:** Anthropic: [`claude-3-5-sonnet-20240620`](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet) | ||||
| 
 | ||||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/429915) for GitLab.com, self-managed and GitLab Dedicated in GitLab 17.3. | ||||
| 
 | ||||
| `/fix` is a special command to generate a fix suggestion for the selected code in your editor. | ||||
| You can include additional instructions to be considered. For example: | ||||
| 
 | ||||
| - Focus on grammar and typos, for example, `/fix grammar mistakes and typos`. | ||||
| - Focus on a concrete algorithm or problem description, for example, `/fix duplicate database inserts` or `/fix race conditions`. | ||||
| - Focus on potential bugs that are not directly visible, for example, `/fix potential bugs`. | ||||
| - Focus on code performance problems, for example, `/fix performance problems`. | ||||
| - Focus on fixing the build when the code does not compile, for example, `/fix the build`. | ||||
| 
 | ||||
| ## Write tests in the IDE | ||||
| 
 | ||||
| DETAILS: | ||||
|  | @ -307,8 +317,8 @@ Use the following commands to quickly accomplish specific tasks. | |||
| | /clear                 | [Delete all conversations permanently and clear the chat window](#delete-or-reset-the-conversation)  | | ||||
| | /reset                 | [Start a new conversation, but keep the previous conversations visible in the chat window](#delete-or-reset-the-conversation)  | | ||||
| | /tests                 | [Write tests](#write-tests-in-the-ide)                                              | | ||||
| | /explain               | [Explain code](../gitlab_duo_chat/examples.md#explain-code)              | | ||||
| | /vulnerability_explain | [Explain current vulnerability](../gitlab_duo/index.md#vulnerability-explanation)   | | ||||
| | /refactor              | [Refactor the code](../gitlab_duo_chat/examples.md#refactor-code-in-the-ide)        | | ||||
| | /explain               | [Explain code](#explain-selected-code)              | | ||||
| | /vulnerability_explain | [Explain current vulnerability](../application_security/vulnerabilities/index.md#explaining-a-vulnerability)   | | ||||
| | /refactor              | [Refactor the code](#refactor-code-in-the-ide)        | | ||||
| | /troubleshoot          | [Troubleshoot failed CI/CD jobs with root cause analysis](#troubleshoot-failed-cicd-jobs-with-root-cause-analysis) | | ||||
| | /fix                   | [Fix the code](../gitlab_duo_chat/examples.md#fix-code-in-the-ide)        | | ||||
| | /fix                   | [Fix the code](#fix-code-in-the-ide)        | | ||||
|  |  | |||
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 56 KiB | 
|  | @ -82,7 +82,7 @@ For more information about slash commands, refer to the documentation: | |||
| - [/tests](../gitlab_duo_chat/examples.md#write-tests-in-the-ide) | ||||
| - [/refactor](../gitlab_duo_chat/examples.md#refactor-code-in-the-ide) | ||||
| - [/fix](../gitlab_duo_chat/examples.md#fix-code-in-the-ide) | ||||
| - [/explain](../gitlab_duo_chat/examples.md#explain-code) | ||||
| - [/explain](../gitlab_duo_chat/examples.md#explain-selected-code) | ||||
| 
 | ||||
| ## `Error M4001` | ||||
| 
 | ||||
|  |  | |||
|  | @ -261,7 +261,7 @@ We cannot guarantee that the large language model produces results that are corr | |||
| You can also explain code in: | ||||
| 
 | ||||
| - A [file](../../../user/project/repository/code_explain.md). | ||||
| - The [IDE](../../../user/gitlab_duo_chat/examples.md#explain-code). | ||||
| - The [IDE](../../../user/gitlab_duo_chat/examples.md#explain-selected-code). | ||||
| 
 | ||||
| ## Expand or collapse comments | ||||
| 
 | ||||
|  |  | |||
|  | @ -17,10 +17,10 @@ GitLab does not limit the number of private projects you can create. | |||
| 
 | ||||
| | | | | | ||||
| |--|--|--| | ||||
| | [**Getting started**](../../user/get_started/get_started_projects.md) **{chevron-right}** | [**Create a project**](index.md) **{chevron-right}** | [**Manage projects**](working_with_projects.md) **{chevron-right}** | | ||||
| | [**Project visibility**](../public_access.md) **{chevron-right}** | [**Project settings**](working_with_projects.md) **{chevron-right}** | [**Description templates**](../../user/project/description_templates.md) **{chevron-right}** | | ||||
| | [**Project access tokens**](../project/settings/project_access_tokens.md) **{chevron-right}** | [**Deploy keys**](../../user/project/deploy_keys/index.md) **{chevron-right}** | [**Deploy tokens**](../../user/project/deploy_tokens/index.md) **{chevron-right}** | | ||||
| | [**Share projects**](../project/members/share_project_with_groups.md) **{chevron-right}** | [**Reserved project and group names**](../../user/reserved_names.md) **{chevron-right}** | [**Search**](../../user/search/index.md) **{chevron-right}** | | ||||
| | [**Badges**](../../user/project/badges.md) **{chevron-right}** | [**Project topics**](../../user/project/project_topics.md) **{chevron-right}** | [**Code intelligence**](../../user/project/code_intelligence.md) **{chevron-right}** | | ||||
| | [**Import and migrate**](../../user/project/import/index.md) **{chevron-right}** | [**System notes**](../../user/project/system_notes.md) **{chevron-right}** | [**Transfer a project to another namespace**](../../user/project/import/index.md) **{chevron-right}** | | ||||
| | [**Use a project as a Go package**](../../user/project/use_project_as_go_package.md) **{chevron-right}** | [**Tutorial: Build a protected workflow for your project**](../../tutorials/protected_workflow/index.md) **{chevron-right}** | [**Troubleshooting**](../../user/project/troubleshooting.md) **{chevron-right}** | | ||||
| | [**Getting started**](../../user/get_started/get_started_projects.md) **{chevron-right}**<br><br>Overview of how features fit together. | [**Create a project**](index.md) **{chevron-right}**<br><br>New project, project templates. | [**Manage projects**](working_with_projects.md) **{chevron-right}**<br><br>Settings, configuration, project activity, project deletion.  | | ||||
| | [**Project visibility**](../public_access.md) **{chevron-right}****<br><br>Public, private, internal. | [**Project settings**](working_with_projects.md) **{chevron-right}**<br><br>Project features, analytics, project permissions. | [**Description templates**](../../user/project/description_templates.md) **{chevron-right}**<br><br>Issue templates, merge request templates, instance and group templates. | | ||||
| | [**Project access tokens**](../project/settings/project_access_tokens.md) **{chevron-right}**<br><br>Authentication, create, revoke, token expiration. | [**Deploy keys**](../../user/project/deploy_keys/index.md) **{chevron-right}**<br><br>Public SSH keys, repository access, bot users, read-only access.  | [**Deploy tokens**](../../user/project/deploy_tokens/index.md) **{chevron-right}**<br><br>Repository cloning, token creation, container registry. | | ||||
| | [**Share projects**](../project/members/share_project_with_groups.md)**{chevron-right}**<br><br>Member roles, invitations, group access. | [**Reserved project and group names**](../../user/reserved_names.md) **{chevron-right}**<br><br>Naming conventions, restrictions, reserved names. | [**Search**](../../user/search/index.md) **{chevron-right}**<br><br>Basic, advanced, exact, search scope, commit SHA search. | | ||||
| | [**Badges**](../../user/project/badges.md) **{chevron-right}**<br><br>Pipeline status, group, project, custom badges. | [**Project topics**](../../user/project/project_topics.md) **{chevron-right}**<br><br>Project organization, subscribe, view.  | [**Code intelligence**](../../user/project/code_intelligence.md) **{chevron-right}**<br><br>Type signatures, symbol documentation, go-to definition. | | ||||
| | [**Import and migrate**](../../user/project/import/index.md) **{chevron-right}**<br><br>Repository migration, third-party repositories, user contribution mapping. | [**System notes**](../../user/project/system_notes.md) **{chevron-right}**<br><br>Event history, activity log, comments history. | [**Transfer a project to another namespace**](../../user/project/import/index.md) **{chevron-right}**<br><br>Namespace transfer, subscription transfer. | | ||||
| | [**Use a project as a Go package**](../../user/project/use_project_as_go_package.md) **{chevron-right}**<br><br>Go modules, import calls. | [**Tutorial: Build a protected workflow for your project**](../../tutorials/protected_workflow/index.md) **{chevron-right}**<br><br>Security, approval rules, branch protection. | [**Troubleshooting**](../../user/project/troubleshooting.md) **{chevron-right}**<br><br>Problem solving, common issues, debugging, error resolution. | | ||||
|  |  | |||
|  | @ -42,4 +42,4 @@ We cannot guarantee that the large language model produces results that are corr | |||
| You can also explain code in: | ||||
| 
 | ||||
| - A [merge request](../../../user/project/merge_requests/changes.md#explain-code-in-a-merge-request). | ||||
| - The [IDE](../../../user/gitlab_duo_chat/examples.md#explain-code). | ||||
| - The [IDE](../../../user/gitlab_duo_chat/examples.md#explain-selected-code). | ||||
|  |  | |||
|  | @ -199,6 +199,13 @@ e.send(:design_repo_saver).send(:save) | |||
| # The following line should show you the export_path similar to /var/opt/gitlab/gitlab-rails/shared/tmp/gitlab_exports/@hashed/49/94/4994.... | ||||
| s = Gitlab::ImportExport::Saver.new(exportable: p, shared: p.import_export_shared, user: u) | ||||
| 
 | ||||
| # Prior to GitLab 17.0, the `user` parameter was not supported. If you encounter an | ||||
| # error with the above or are unsure whether or not to supply the `user` | ||||
| # argument, use the following check: | ||||
| Gitlab::ImportExport::Saver.instance_method(:initialize).parameters.include?([:keyreq, :user]) | ||||
| # If the preceding check returns false, omit the user argument: | ||||
| s = Gitlab::ImportExport::Saver.new(exportable: p, shared: p.import_export_shared) | ||||
| 
 | ||||
| # To try and upload use: | ||||
| s.send(:compress_and_save) | ||||
| s.send(:save_upload) | ||||
|  |  | |||
|  | @ -32,6 +32,10 @@ module ClickHouse | |||
|         self.class.new(@query_builder.offset(count)) | ||||
|       end | ||||
| 
 | ||||
|       def group(...) | ||||
|         self.class.new(@query_builder.group(...)) | ||||
|       end | ||||
| 
 | ||||
|       def select(...) | ||||
|         self.class.new(@query_builder.select(...)) | ||||
|       end | ||||
|  |  | |||
|  | @ -0,0 +1,41 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| module ClickHouse # rubocop:disable Gitlab/BoundedContexts -- Existing module | ||||
|   module Models | ||||
|     module Ci | ||||
|       class FinishedPipelinesHourly < ClickHouse::Models::BaseModel | ||||
|         def self.table_name | ||||
|           'ci_finished_pipelines_hourly' | ||||
|         end | ||||
| 
 | ||||
|         def self.for_project(project) | ||||
|           new.for_project(project) | ||||
|         end | ||||
| 
 | ||||
|         def self.by_status(statuses) | ||||
|           new.by_status(statuses) | ||||
|         end | ||||
| 
 | ||||
|         def self.group_by_status | ||||
|           new.group_by_status | ||||
|         end | ||||
| 
 | ||||
|         def for_project(project) | ||||
|           where(path: project.project_namespace.traversal_path) | ||||
|         end | ||||
| 
 | ||||
|         def by_status(statuses) | ||||
|           where(status: statuses) | ||||
|         end | ||||
| 
 | ||||
|         def group_by_status | ||||
|           group([@query_builder.table[:status]]) | ||||
|         end | ||||
| 
 | ||||
|         def count_pipelines_function | ||||
|           Arel::Nodes::NamedFunction.new('countMerge', [@query_builder.table[:count_pipelines]]) | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -90,6 +90,14 @@ module ClickHouse | |||
|       new_instance | ||||
|     end | ||||
| 
 | ||||
|     def group(*columns) | ||||
|       new_instance = deep_clone | ||||
| 
 | ||||
|       new_instance.manager.group(*columns) | ||||
| 
 | ||||
|       new_instance | ||||
|     end | ||||
| 
 | ||||
|     def limit(count) | ||||
|       manager.take(count) | ||||
|       self | ||||
|  |  | |||
|  | @ -16,10 +16,12 @@ require 'time' | |||
| class PreMergeChecks | ||||
|   DEFAULT_API_ENDPOINT                                  = "https://gitlab.com/api/v4" | ||||
|   MERGE_TRAIN_REF_REGEX                                 = %r{\Arefs/merge-requests/\d+/train\z} | ||||
|   TIER_IDENTIFIER_REGEX = /tier:\d/ | ||||
|   REQUIRED_TIER_IDENTIFIER = 'tier:3' | ||||
|   PIPELINE_FRESHNESS_DEFAULT_THRESHOLD_IN_HOURS         = 8 | ||||
|   PIPELINE_FRESHNESS_STABLE_BRANCHES_THRESHOLD_IN_HOURS = 72 | ||||
|   PREDICTIVE_PIPELINE_IDENTIFIER                        = 'predictive' | ||||
|   PIPELINE_FRESHNESS_THRESHOLD_IN_HOURS = 8 | ||||
|   REQUIRED_TIER_IDENTIFIER                              = 'tier:3' | ||||
|   STABLE_BRANCH_SUFFIX                                  = '-stable-ee' | ||||
|   TIER_IDENTIFIER_REGEX                                 = /tier:\d/ | ||||
| 
 | ||||
|   PreMergeChecksFailedError = Class.new(StandardError) | ||||
|   PreMergeChecksStatus = Struct.new(:exitstatus, :message) do | ||||
|  | @ -31,10 +33,12 @@ class PreMergeChecks | |||
|   def initialize( | ||||
|     api_endpoint: ENV.fetch('CI_API_V4_URL', DEFAULT_API_ENDPOINT), | ||||
|     project_id: ENV['CI_PROJECT_ID'], | ||||
|     merge_request_iid: ENV['CI_MERGE_REQUEST_IID']) | ||||
|     merge_request_iid: ENV['CI_MERGE_REQUEST_IID'], | ||||
|     target_branch: ENV['CI_MERGE_REQUEST_TARGET_BRANCH_NAME']) | ||||
|     @api_endpoint        = api_endpoint | ||||
|     @project_id          = project_id | ||||
|     @merge_request_iid   = merge_request_iid.to_i | ||||
|     @target_branch       = target_branch | ||||
|   end | ||||
| 
 | ||||
|   def execute | ||||
|  | @ -62,7 +66,7 @@ class PreMergeChecks | |||
| 
 | ||||
|   private | ||||
| 
 | ||||
|   attr_reader :api_endpoint, :project_id, :merge_request_iid | ||||
|   attr_reader :api_endpoint, :project_id, :merge_request_iid, :target_branch | ||||
| 
 | ||||
|   def api_client | ||||
|     @api_client ||= begin | ||||
|  | @ -77,6 +81,7 @@ class PreMergeChecks | |||
|   def check_required_ids! | ||||
|     fail_check!("Missing project_id!") unless project_id | ||||
|     fail_check!("Missing merge_request_iid!") if merge_request_iid == 0 | ||||
|     fail_check!("Missing target_branch!") unless target_branch | ||||
|   end | ||||
| 
 | ||||
|   def check_pipeline_for_merged_results!(pipeline) | ||||
|  | @ -101,10 +106,17 @@ class PreMergeChecks | |||
| 
 | ||||
|   def check_pipeline_freshness!(pipeline) | ||||
|     hours_ago = ((Time.now - Time.parse(pipeline.created_at)) / 3600).ceil(2) | ||||
|     return if hours_ago < PIPELINE_FRESHNESS_THRESHOLD_IN_HOURS | ||||
|     threshold = | ||||
|       if target_branch_is_stable_branch? | ||||
|         PIPELINE_FRESHNESS_STABLE_BRANCHES_THRESHOLD_IN_HOURS | ||||
|       else | ||||
|         PIPELINE_FRESHNESS_DEFAULT_THRESHOLD_IN_HOURS | ||||
|       end | ||||
| 
 | ||||
|     return if hours_ago < threshold | ||||
| 
 | ||||
|     fail_check! <<~TEXT | ||||
|       Expected latest pipeline (#{pipeline.web_url}) to be created within the last #{PIPELINE_FRESHNESS_THRESHOLD_IN_HOURS} hours (it was created #{hours_ago} hours ago)! | ||||
|       Expected latest pipeline (#{pipeline.web_url}) to be created within the last #{threshold} hours (it was created #{hours_ago} hours ago)! | ||||
| 
 | ||||
|       Please start a new pipeline. | ||||
|     TEXT | ||||
|  | @ -126,6 +138,10 @@ class PreMergeChecks | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def target_branch_is_stable_branch? | ||||
|     target_branch.end_with?(STABLE_BRANCH_SUFFIX) | ||||
|   end | ||||
| 
 | ||||
|   def fail_check!(text) | ||||
|     raise PreMergeChecksFailedError, text | ||||
|   end | ||||
|  | @ -144,12 +160,18 @@ if $PROGRAM_NAME == __FILE__ | |||
|       options[:merge_request_iid] = value | ||||
|     end | ||||
| 
 | ||||
|     opts.on("-t", "--target-branch [string]", String, "Target branch name") do |value| | ||||
|       options[:target_branch] = value | ||||
|     end | ||||
| 
 | ||||
|     opts.on("-h", "--help") do | ||||
|       puts "Usage: #{File.basename(__FILE__)} [--project_id <PROJECT_ID>] [--merge_request_iid <MERGE_REQUEST_IID>]" | ||||
|       puts "Usage: #{File.basename(__FILE__)} [--project_id <PROJECT_ID>] " \ | ||||
|         "[--merge_request_iid <MERGE_REQUEST_IID>] [--target-branch <TARGET_BRANCH>]" | ||||
|       puts | ||||
|       puts "Examples:" | ||||
|       puts | ||||
|       puts "#{File.basename(__FILE__)} --project_id \"gitlab-org/gitlab\" --merge_request_iid \"1\"" | ||||
|       puts "#{File.basename(__FILE__)} --project_id \"gitlab-org/gitlab\" " \ | ||||
|         "--merge_request_iid \"1\" --target-branch \"master\"" | ||||
| 
 | ||||
|       exit | ||||
|     end | ||||
|  |  | |||
|  | @ -10,31 +10,33 @@ RSpec.describe 'Database schema', feature_category: :database do | |||
|   let(:columns_name_with_jsonb) { retrieve_columns_name_with_jsonb } | ||||
| 
 | ||||
|   IGNORED_INDEXES_ON_FKS = { | ||||
|     ai_testing_terms_acceptances: %w[user_id], # testing terms only have 1 entry, and if the user is deleted the record should remain | ||||
|     application_settings: %w[instance_administration_project_id instance_administrators_group_id], | ||||
|     ci_build_trace_metadata: [%w[partition_id build_id], %w[partition_id trace_artifact_id]], # the index on build_id is enough | ||||
|     ci_builds: [%w[partition_id stage_id], %w[partition_id execution_config_id], %w[auto_canceled_by_partition_id auto_canceled_by_id], %w[upstream_pipeline_partition_id upstream_pipeline_id], %w[partition_id commit_id]], # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/142804#note_1745483081 | ||||
|     ci_daily_build_group_report_results: [%w[partition_id last_pipeline_id]], # index on last_pipeline_id is sufficient | ||||
|     ci_pipeline_artifacts: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient | ||||
|     ci_pipeline_chat_data: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient | ||||
|     ci_pipeline_messages: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient | ||||
|     ci_pipeline_metadata: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient | ||||
|     ci_pipeline_variables: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient | ||||
|     ci_pipelines: [%w[auto_canceled_by_partition_id auto_canceled_by_id]], # index on auto_canceled_by_id is sufficient | ||||
|     ci_pipelines_config: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient | ||||
|     ci_sources_pipelines: [%w[source_partition_id source_pipeline_id], %w[partition_id pipeline_id]], | ||||
|     ci_sources_projects: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient | ||||
|     ci_stages: [%w[partition_id pipeline_id]], # the index on pipeline_id is sufficient | ||||
|     notes: %w[namespace_id], # this index is added in an async manner, hence it needs to be ignored in the first phase. | ||||
|     p_ci_build_trace_metadata: [%w[partition_id build_id], %w[partition_id trace_artifact_id]], # the index on build_id is enough | ||||
|     p_ci_builds: [%w[partition_id stage_id], %w[partition_id execution_config_id], %w[auto_canceled_by_partition_id auto_canceled_by_id], %w[upstream_pipeline_partition_id upstream_pipeline_id], %w[partition_id commit_id]], # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/142804#note_1745483081 | ||||
|     p_ci_builds_execution_configs: [%w[partition_id pipeline_id]], # the index on pipeline_id is enough | ||||
|     p_ci_pipeline_variables: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient | ||||
|     p_ci_stages: [%w[partition_id pipeline_id]], # the index on pipeline_id is sufficient | ||||
|     # `search_index_id index_type` is the composite foreign key configured for `search_namespace_index_assignments`, | ||||
|     # but in Search::NamespaceIndexAssignment model, only `search_index_id` is used as foreign key and indexed | ||||
|     search_namespace_index_assignments: [%w[search_index_id index_type]], | ||||
|     slack_integrations_scopes: [%w[slack_api_scope_id]], | ||||
|     notes: %w[namespace_id], # this index is added in an async manner, hence it needs to be ignored in the first phase. | ||||
|     users: [%w[accepted_term_id]], | ||||
|     ci_pipeline_artifacts: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient | ||||
|     ci_sources_projects: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient | ||||
|     ci_daily_build_group_report_results: [%w[partition_id last_pipeline_id]], # index on last_pipeline_id is sufficient | ||||
|     ci_builds: [%w[partition_id stage_id], %w[partition_id execution_config_id], %w[auto_canceled_by_partition_id auto_canceled_by_id], %w[upstream_pipeline_partition_id upstream_pipeline_id], %w[partition_id commit_id]], # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/142804#note_1745483081 | ||||
|     ci_pipeline_variables: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient | ||||
|     p_ci_pipeline_variables: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient | ||||
|     ci_pipelines: [%w[auto_canceled_by_partition_id auto_canceled_by_id]], # index on auto_canceled_by_id is sufficient | ||||
|     ci_pipelines_config: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient | ||||
|     ci_pipeline_metadata: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient | ||||
|     ci_pipeline_messages: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient | ||||
|     p_ci_builds: [%w[partition_id stage_id], %w[partition_id execution_config_id], %w[auto_canceled_by_partition_id auto_canceled_by_id], %w[upstream_pipeline_partition_id upstream_pipeline_id], %w[partition_id commit_id]], # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/142804#note_1745483081 | ||||
|     ci_stages: [%w[partition_id pipeline_id]], # the index on pipeline_id is sufficient | ||||
|     p_ci_stages: [%w[partition_id pipeline_id]], # the index on pipeline_id is sufficient | ||||
|     ai_testing_terms_acceptances: %w[user_id], # testing terms only have 1 entry, and if the user is deleted the record should remain | ||||
|     p_ci_builds_execution_configs: [%w[partition_id pipeline_id]], # the index on pipeline_id is enough | ||||
|     ci_sources_pipelines: [%w[source_partition_id source_pipeline_id], %w[partition_id pipeline_id]], | ||||
|     snippets: %w[organization_id] # this index is added in an async manner, hence it needs to be ignored in the first phase. | ||||
|     snippets: %w[organization_id], # this index is added in an async manner, hence it needs to be ignored in the first phase. | ||||
|     users: [%w[accepted_term_id]] | ||||
|   }.with_indifferent_access.freeze | ||||
| 
 | ||||
|   # If splitting FK and table removal into two MRs as suggested in the docs, use this constant in the initial FK removal MR. | ||||
|  |  | |||
|  | @ -207,23 +207,6 @@ describe('noteable_discussion component', () => { | |||
|       expect(replyWrapper.exists()).toBe(true); | ||||
|       expect(replyWrapper.classes('internal-note')).toBe(true); | ||||
|     }); | ||||
| 
 | ||||
|     it('should add `public-note` class when the discussion is not internal', async () => { | ||||
|       const softCopyInternalNotes = [...discussionMock.notes]; | ||||
|       const mockPublicNotes = softCopyInternalNotes.splice(0, 2); | ||||
|       mockPublicNotes[0].internal = false; | ||||
| 
 | ||||
|       const mockDiscussion = { | ||||
|         ...discussionMock, | ||||
|         notes: [...mockPublicNotes], | ||||
|       }; | ||||
|       wrapper.setProps({ discussion: mockDiscussion }); | ||||
|       await nextTick(); | ||||
| 
 | ||||
|       const replyWrapper = wrapper.find('[data-testid="reply-wrapper"]'); | ||||
|       expect(replyWrapper.exists()).toBe(true); | ||||
|       expect(replyWrapper.classes('public-note')).toBe(true); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   describe('for resolved thread', () => { | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import { | |||
|   formattedTimeFromDate, | ||||
|   isTracingDateRangeOutOfBounds, | ||||
|   validatedDateRangeQuery, | ||||
|   urlWithStringifiedPayloadParam, | ||||
| } from '~/observability/utils'; | ||||
| import { | ||||
|   CUSTOM_DATE_RANGE_OPTION, | ||||
|  | @ -271,3 +272,15 @@ describe('validatedDateRangeQuery', () => { | |||
|     expect(result).toEqual({ value: '1h' }); | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
| describe('urlWithStringifiedPayloadParam', () => { | ||||
|   it('returns the url with a stringified param', () => { | ||||
|     expect( | ||||
|       urlWithStringifiedPayloadParam( | ||||
|         'http://gdk.test:3443/?foo=bar', | ||||
|         { a: 'b', c: 'd' }, | ||||
|         'my_param', | ||||
|       ), | ||||
|     ).toBe('http://gdk.test:3443/?foo=bar&my_param=%7B%22a%22%3A%22b%22%2C%22c%22%3A%22d%22%7D'); | ||||
|   }); | ||||
| }); | ||||
|  |  | |||
|  | @ -90,6 +90,19 @@ RSpec.describe ClickHouse::Models::BaseModel, feature_category: :database do | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#group' do | ||||
|     it 'returns a new instance with grouped results' do | ||||
|       dummy_instance = dummy_class.new(query_builder) | ||||
| 
 | ||||
|       expect(query_builder).to receive(:group).with(:id, :name).and_return(updated_query_builder) | ||||
| 
 | ||||
|       new_instance = dummy_instance.group(:id, :name) | ||||
| 
 | ||||
|       expect(new_instance).to be_a(dummy_class) | ||||
|       expect(new_instance).not_to eq(dummy_instance) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#select' do | ||||
|     it 'returns a new instance with selected fields' do | ||||
|       dummy_instance = dummy_class.new(query_builder) | ||||
|  |  | |||
|  | @ -0,0 +1,111 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| require 'spec_helper' | ||||
| 
 | ||||
| RSpec.describe ClickHouse::Models::Ci::FinishedPipelinesHourly, feature_category: :fleet_visibility do | ||||
|   let(:instance) { described_class.new } | ||||
| 
 | ||||
|   let_it_be(:group) { create(:group, :nested) } | ||||
|   let_it_be(:project) { create(:project, group: group) } | ||||
|   let_it_be(:path) { project.reload.project_namespace.traversal_path } | ||||
| 
 | ||||
|   specify { expect(path).to match(%r{\A(\d+/){3}\z}) } | ||||
| 
 | ||||
|   describe '#for_project' do | ||||
|     it 'builds the correct SQL' do | ||||
|       expected_sql = <<~SQL.lines(chomp: true).join(' ') | ||||
|         SELECT * FROM "ci_finished_pipelines_hourly" | ||||
|         WHERE "ci_finished_pipelines_hourly"."path" = '#{path}' | ||||
|       SQL | ||||
| 
 | ||||
|       result_sql = instance.for_project(project).to_sql | ||||
| 
 | ||||
|       expect(result_sql.strip).to eq(expected_sql.strip) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#by_status' do | ||||
|     it 'builds the correct SQL' do | ||||
|       expected_sql = <<~SQL.lines(chomp: true).join(' ') | ||||
|         SELECT * FROM "ci_finished_pipelines_hourly" | ||||
|         WHERE "ci_finished_pipelines_hourly"."status" IN ('failed', 'success') | ||||
|       SQL | ||||
| 
 | ||||
|       result_sql = instance.by_status(%i[failed success]).to_sql | ||||
| 
 | ||||
|       expect(result_sql.strip).to eq(expected_sql.strip) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#group_by_status' do | ||||
|     it 'builds the correct SQL' do | ||||
|       expected_sql = <<~SQL.lines(chomp: true).join(' ') | ||||
|         SELECT "ci_finished_pipelines_hourly"."status" | ||||
|         FROM "ci_finished_pipelines_hourly" | ||||
|         GROUP BY "ci_finished_pipelines_hourly"."status" | ||||
|       SQL | ||||
| 
 | ||||
|       result_sql = instance.select(:status).group_by_status.to_sql | ||||
| 
 | ||||
|       expect(result_sql.strip).to eq(expected_sql.strip) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#count_pipelines_function' do | ||||
|     it 'builds the correct SQL' do | ||||
|       expected_sql = <<~SQL.lines(chomp: true).join(' ') | ||||
|         SELECT "ci_finished_pipelines_hourly"."status", countMerge("ci_finished_pipelines_hourly"."count_pipelines") | ||||
|         FROM "ci_finished_pipelines_hourly" | ||||
|       SQL | ||||
| 
 | ||||
|       result_sql = instance.select(:status, instance.count_pipelines_function).to_sql | ||||
| 
 | ||||
|       expect(result_sql.strip).to eq(expected_sql.strip) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe 'class methods' do | ||||
|     before do | ||||
|       allow(described_class).to receive(:new).and_return(instance) | ||||
|     end | ||||
| 
 | ||||
|     describe '.for_project' do | ||||
|       it 'calls the corresponding instance method' do | ||||
|         expect(instance).to receive(:for_project).with(project) | ||||
| 
 | ||||
|         described_class.for_project(project) | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     describe '.by_status' do | ||||
|       it 'calls the corresponding instance method' do | ||||
|         expect(instance).to receive(:by_status).with(:success) | ||||
| 
 | ||||
|         described_class.by_status(:success) | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     describe '.group_by_status' do | ||||
|       it 'calls the corresponding instance method' do | ||||
|         expect(instance).to receive(:group_by_status) | ||||
| 
 | ||||
|         described_class.group_by_status | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe 'method chaining' do | ||||
|     it 'builds the correct SQL with chained methods' do | ||||
|       expected_sql = <<~SQL.lines(chomp: true).join(' ') | ||||
|         SELECT "ci_finished_pipelines_hourly"."status" FROM "ci_finished_pipelines_hourly" | ||||
|         WHERE "ci_finished_pipelines_hourly"."path" = '#{path}' | ||||
|         AND "ci_finished_pipelines_hourly"."status" IN ('failed', 'success') | ||||
|         GROUP BY "ci_finished_pipelines_hourly"."status" | ||||
|       SQL | ||||
| 
 | ||||
|       result_sql = instance.for_project(project).select(:status).by_status(%i[failed success]).group_by_status.to_sql | ||||
| 
 | ||||
|       expect(result_sql.strip).to eq(expected_sql.strip) | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -278,6 +278,30 @@ RSpec.describe ClickHouse::QueryBuilder, feature_category: :database do | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#group' do | ||||
|     it 'builds correct group query' do | ||||
|       expected_sql = <<~SQL.lines(chomp: true).join(' ') | ||||
|         SELECT * FROM "test_table" | ||||
|         GROUP BY column1 | ||||
|       SQL | ||||
| 
 | ||||
|       sql = builder.group(:column1).to_sql | ||||
| 
 | ||||
|       expect(sql).to eq(expected_sql) | ||||
|     end | ||||
| 
 | ||||
|     it 'chains multiple groups when called multiple times' do | ||||
|       expected_sql = <<~SQL.lines(chomp: true).join(' ') | ||||
|         SELECT * FROM "test_table" | ||||
|         GROUP BY column1, column2 | ||||
|       SQL | ||||
| 
 | ||||
|       sql = builder.group(:column1).group(:column2).to_sql | ||||
| 
 | ||||
|       expect(sql).to eq(expected_sql) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#to_sql' do | ||||
|     it 'delegates to the Arel::SelectManager' do | ||||
|       expect(builder.send(:manager)).to receive(:to_sql) | ||||
|  |  | |||
|  | @ -46,7 +46,11 @@ RSpec.describe Gitlab::Database::PostgresPartition, type: :model, feature_catego | |||
|     end | ||||
| 
 | ||||
|     describe '.with_list_constraint' do | ||||
|       subject(:with_list_constraint) { described_class.with_list_constraint(partition_id) } | ||||
|       subject(:with_list_constraint) do | ||||
|         described_class | ||||
|           .with_parent_tables(Ci::Partitionable.registered_models.map(&:table_name)) | ||||
|           .with_list_constraint(partition_id) | ||||
|       end | ||||
| 
 | ||||
|       context 'when condition matches' do | ||||
|         let(:partition_id) { '102' } | ||||
|  |  | |||
|  | @ -14,20 +14,22 @@ RSpec.describe VirtualRegistries::Packages::Policies::GroupPolicy, feature_categ | |||
|   describe 'read_virtual_registry' do | ||||
|     where(:group_visibility, :current_user, :allowed?) do | ||||
|       'PUBLIC' | nil                      | false | ||||
|       'PUBLIC' | ref(:non_group_member) | true | ||||
|       'PUBLIC' | ref(:non_group_member)   | false | ||||
|       'PUBLIC' | ref(:guest)              | true | ||||
|       'PUBLIC' | ref(:reporter)           | true | ||||
|       'PUBLIC' | ref(:developer)          | true | ||||
|       'PUBLIC' | ref(:maintainer)         | true | ||||
|       'PUBLIC' | ref(:owner)              | true | ||||
|       'PUBLIC' | ref(:organization_owner) | true | ||||
| 
 | ||||
|       'INTERNAL' | nil                      | false | ||||
|       'INTERNAL' | ref(:non_group_member) | true | ||||
|       'INTERNAL' | ref(:non_group_member)   | false | ||||
|       'INTERNAL' | ref(:guest)              | true | ||||
|       'INTERNAL' | ref(:reporter)           | true | ||||
|       'INTERNAL' | ref(:developer)          | true | ||||
|       'INTERNAL' | ref(:maintainer)         | true | ||||
|       'INTERNAL' | ref(:owner)              | true | ||||
|       'INTERNAL' | ref(:organization_owner) | true | ||||
| 
 | ||||
|       'PRIVATE' | nil                      | false | ||||
|       'PRIVATE' | ref(:non_group_member)   | false | ||||
|  | @ -36,6 +38,7 @@ RSpec.describe VirtualRegistries::Packages::Policies::GroupPolicy, feature_categ | |||
|       'PRIVATE' | ref(:developer)          | true | ||||
|       'PRIVATE' | ref(:maintainer)         | true | ||||
|       'PRIVATE' | ref(:owner)              | true | ||||
|       'PRIVATE' | ref(:organization_owner) | true | ||||
|     end | ||||
| 
 | ||||
|     with_them do | ||||
|  | @ -46,14 +49,42 @@ RSpec.describe VirtualRegistries::Packages::Policies::GroupPolicy, feature_categ | |||
|       it { is_expected.to public_send(allowed? ? :be_allowed : :be_disallowed, :read_virtual_registry) } | ||||
|     end | ||||
| 
 | ||||
|     context 'for deploy token' do | ||||
|       let(:deploy_token) do | ||||
|         create(:deploy_token, :group).tap do |token| | ||||
|           create(:group_deploy_token, group: target, deploy_token: token) | ||||
|     context 'with project membership' do | ||||
|       let_it_be(:project) { create(:project, group: group) } | ||||
|       let(:current_user) { non_group_member } | ||||
| 
 | ||||
|       %i[ | ||||
|         guest | ||||
|         reporter | ||||
|         developer | ||||
|         maintainer | ||||
|         owner | ||||
|       ].each do |role| | ||||
|         context "for #{role}" do | ||||
|           before do | ||||
|             project.send(:"add_#{role}", current_user) | ||||
|           end | ||||
| 
 | ||||
|           it { expect_allowed(:read_virtual_registry) } | ||||
|         end | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|       subject { described_class.new(deploy_token, policy_subject) } | ||||
|     context 'for admin' do | ||||
|       let(:current_user) { admin } | ||||
| 
 | ||||
|       context 'when admin mode is enabled', :enable_admin_mode do | ||||
|         it { expect_allowed(:read_virtual_registry) } | ||||
|       end | ||||
| 
 | ||||
|       context 'when admin mode is disabled' do | ||||
|         it { expect_disallowed(:read_virtual_registry) } | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'for deploy token' do | ||||
|       let(:deploy_token) { create(:deploy_token, :group, groups: [target]) } | ||||
|       let(:current_user) { deploy_token } | ||||
| 
 | ||||
|       where(:target, :group_visibility, :read_virtual_registry, :allowed?) do | ||||
|         ref(:group) | 'PUBLIC'   | true  | true | ||||
|  | @ -92,6 +123,7 @@ RSpec.describe VirtualRegistries::Packages::Policies::GroupPolicy, feature_categ | |||
|         'PUBLIC' | ref(:developer)          | false | ||||
|         'PUBLIC' | ref(:maintainer)         | true | ||||
|         'PUBLIC' | ref(:owner)              | true | ||||
|         'PUBLIC' | ref(:organization_owner) | true | ||||
| 
 | ||||
|         'INTERNAL' | nil                      | false | ||||
|         'INTERNAL' | ref(:non_group_member)   | false | ||||
|  | @ -100,6 +132,7 @@ RSpec.describe VirtualRegistries::Packages::Policies::GroupPolicy, feature_categ | |||
|         'INTERNAL' | ref(:developer)          | false | ||||
|         'INTERNAL' | ref(:maintainer)         | true | ||||
|         'INTERNAL' | ref(:owner)              | true | ||||
|         'INTERNAL' | ref(:organization_owner) | true | ||||
| 
 | ||||
|         'PRIVATE' | nil                      | false | ||||
|         'PRIVATE' | ref(:non_group_member)   | false | ||||
|  | @ -108,6 +141,7 @@ RSpec.describe VirtualRegistries::Packages::Policies::GroupPolicy, feature_categ | |||
|         'PRIVATE' | ref(:developer)          | false | ||||
|         'PRIVATE' | ref(:maintainer)         | true | ||||
|         'PRIVATE' | ref(:owner)              | true | ||||
|         'PRIVATE' | ref(:organization_owner) | true | ||||
|       end | ||||
| 
 | ||||
|       with_them do | ||||
|  | @ -118,5 +152,17 @@ RSpec.describe VirtualRegistries::Packages::Policies::GroupPolicy, feature_categ | |||
|         it { is_expected.to public_send(allowed? ? :be_allowed : :be_disallowed, :"#{action}_virtual_registry") } | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'for admin' do | ||||
|       let(:current_user) { admin } | ||||
| 
 | ||||
|       context 'when admin mode is enabled', :enable_admin_mode do | ||||
|         it { expect_allowed(:"#{action}_virtual_registry") } | ||||
|       end | ||||
| 
 | ||||
|       context 'when admin mode is disabled' do | ||||
|         it { expect_disallowed(:"#{action}_virtual_registry") } | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -7,18 +7,18 @@ RSpec.describe API::VirtualRegistries::Packages::Maven, feature_category: :virtu | |||
|   include WorkhorseHelpers | ||||
|   include HttpBasicAuthHelpers | ||||
| 
 | ||||
|   let_it_be_with_reload(:registry) { create(:virtual_registries_packages_maven_registry) } | ||||
|   let_it_be(:group) { create(:group) } | ||||
|   let_it_be_with_reload(:registry) { create(:virtual_registries_packages_maven_registry, group: group) } | ||||
|   let_it_be(:upstream) { create(:virtual_registries_packages_maven_upstream, registry: registry) } | ||||
|   let_it_be(:group) { registry.group } | ||||
|   let_it_be(:project) { create(:project, namespace: group) } | ||||
|   let_it_be(:user) { project.creator } | ||||
|   let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } | ||||
|   let_it_be(:user) { create(:user, owner_of: project) } | ||||
|   let_it_be(:job) { create(:ci_build, :running, user: user, project: project) } | ||||
|   let_it_be(:deploy_token) do | ||||
|     create(:deploy_token, :group, groups: [group], read_virtual_registry: true) | ||||
|   end | ||||
| 
 | ||||
|   let_it_be(:headers) { user_basic_auth_header(user, personal_access_token) } | ||||
|   let(:personal_access_token) { create(:personal_access_token, user: user) } | ||||
|   let(:headers) { user_basic_auth_header(user, personal_access_token) } | ||||
| 
 | ||||
|   shared_examples 'disabled feature flag' do | ||||
|     before do | ||||
|  | @ -95,11 +95,11 @@ RSpec.describe API::VirtualRegistries::Packages::Maven, feature_category: :virtu | |||
|     end | ||||
| 
 | ||||
|     context 'with a non member user' do | ||||
|       let_it_be(:user) { build_stubbed(:user) } | ||||
|       let_it_be(:user) { create(:user) } | ||||
| 
 | ||||
|       where(:group_access_level, :status) do | ||||
|         'PUBLIC'   | :ok | ||||
|         'INTERNAL' | :ok | ||||
|         'PUBLIC'   | :forbidden | ||||
|         'INTERNAL' | :forbidden | ||||
|         'PRIVATE'  | :not_found | ||||
|       end | ||||
| 
 | ||||
|  | @ -108,13 +108,9 @@ RSpec.describe API::VirtualRegistries::Packages::Maven, feature_category: :virtu | |||
|           group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_access_level, false)) | ||||
|         end | ||||
| 
 | ||||
|         if params[:status] == :ok | ||||
|           it_behaves_like 'successful response' | ||||
|         else | ||||
|         it_behaves_like 'returning response status', params[:status] | ||||
|       end | ||||
|     end | ||||
|     end | ||||
| 
 | ||||
|     context 'for authentication' do | ||||
|       where(:token, :sent_as, :status) do | ||||
|  | @ -323,11 +319,11 @@ RSpec.describe API::VirtualRegistries::Packages::Maven, feature_category: :virtu | |||
|     end | ||||
| 
 | ||||
|     context 'with a non member user' do | ||||
|       let_it_be(:user) { build_stubbed(:user) } | ||||
|       let_it_be(:user) { create(:user) } | ||||
| 
 | ||||
|       where(:group_access_level, :status) do | ||||
|         'PUBLIC'   | :ok | ||||
|         'INTERNAL' | :ok | ||||
|         'PUBLIC'   | :forbidden | ||||
|         'INTERNAL' | :forbidden | ||||
|         'PRIVATE'  | :forbidden | ||||
|       end | ||||
|       with_them do | ||||
|  | @ -335,13 +331,9 @@ RSpec.describe API::VirtualRegistries::Packages::Maven, feature_category: :virtu | |||
|           group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_access_level, false)) | ||||
|         end | ||||
| 
 | ||||
|         if params[:status] == :ok | ||||
|           it_behaves_like 'successful response' | ||||
|         else | ||||
|         it_behaves_like 'returning response status', params[:status] | ||||
|       end | ||||
|     end | ||||
|     end | ||||
| 
 | ||||
|     context 'for authentication' do | ||||
|       where(:token, :sent_as, :status) do | ||||
|  | @ -588,8 +580,8 @@ RSpec.describe API::VirtualRegistries::Packages::Maven, feature_category: :virtu | |||
|       let_it_be(:user) { create(:user) } | ||||
| 
 | ||||
|       where(:group_access_level, :status) do | ||||
|         'PUBLIC'   | :ok | ||||
|         'INTERNAL' | :ok | ||||
|         'PUBLIC'   | :forbidden | ||||
|         'INTERNAL' | :forbidden | ||||
|         'PRIVATE'  | :forbidden | ||||
|       end | ||||
| 
 | ||||
|  | @ -598,13 +590,9 @@ RSpec.describe API::VirtualRegistries::Packages::Maven, feature_category: :virtu | |||
|           group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_access_level, false)) | ||||
|         end | ||||
| 
 | ||||
|         if params[:status] == :ok | ||||
|           it_behaves_like 'successful response' | ||||
|         else | ||||
|         it_behaves_like 'returning response status', params[:status] | ||||
|       end | ||||
|     end | ||||
|     end | ||||
| 
 | ||||
|     context 'for authentication' do | ||||
|       where(:token, :sent_as, :status) do | ||||
|  | @ -782,11 +770,11 @@ RSpec.describe API::VirtualRegistries::Packages::Maven, feature_category: :virtu | |||
|     end | ||||
| 
 | ||||
|     context 'with a non member user' do | ||||
|       let_it_be(:user) { build_stubbed(:user) } | ||||
|       let_it_be(:user) { create(:user) } | ||||
| 
 | ||||
|       where(:group_access_level, :status) do | ||||
|         'PUBLIC'   | :ok | ||||
|         'INTERNAL' | :ok | ||||
|         'PUBLIC'   | :forbidden | ||||
|         'INTERNAL' | :forbidden | ||||
|         'PRIVATE'  | :forbidden | ||||
|       end | ||||
| 
 | ||||
|  | @ -795,13 +783,9 @@ RSpec.describe API::VirtualRegistries::Packages::Maven, feature_category: :virtu | |||
|           group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_access_level, false)) | ||||
|         end | ||||
| 
 | ||||
|         if params[:status] == :ok | ||||
|           it_behaves_like 'successful response' | ||||
|         else | ||||
|         it_behaves_like 'returning response status', params[:status] | ||||
|       end | ||||
|     end | ||||
|     end | ||||
| 
 | ||||
|     context 'for authentication' do | ||||
|       where(:token, :sent_as, :status) do | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ RSpec.describe PreMergeChecks, time_travel_to: Time.parse('2024-05-29T10:00:00 U | |||
|   let(:instance)           { described_class.new } | ||||
|   let(:project_id)         { '42' } | ||||
|   let(:merge_request_iid)  { '1' } | ||||
|   let(:target_branch)      { 'master' } | ||||
|   let(:mr_pipelines_url)   { "https://gitlab.test/api/v4/projects/#{project_id}/merge_requests/#{merge_request_iid}/pipelines" } | ||||
| 
 | ||||
|   let(:latest_mr_pipeline_ref)        { "refs/merge-requests/1/merge" } | ||||
|  | @ -78,7 +79,8 @@ RSpec.describe PreMergeChecks, time_travel_to: Time.parse('2024-05-29T10:00:00 U | |||
|     stub_env( | ||||
|       'CI_API_V4_URL' => 'https://gitlab.test/api/v4', | ||||
|       'CI_PROJECT_ID' => project_id, | ||||
|       'CI_MERGE_REQUEST_IID' => merge_request_iid | ||||
|       'CI_MERGE_REQUEST_IID' => merge_request_iid, | ||||
|       'CI_MERGE_REQUEST_TARGET_BRANCH_NAME' => target_branch | ||||
|     ) | ||||
|   end | ||||
| 
 | ||||
|  | @ -102,6 +104,16 @@ RSpec.describe PreMergeChecks, time_travel_to: Time.parse('2024-05-29T10:00:00 U | |||
|         expect(instance.execute.message).to include("Missing merge_request_iid") | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'when target_branch is missing' do | ||||
|       let(:target_branch) { nil } | ||||
| 
 | ||||
|       it 'returns a failed PreMergeChecksStatus' do | ||||
|         expect(instance.execute).to be_a(described_class::PreMergeChecksStatus) | ||||
|         expect(instance.execute).not_to be_success | ||||
|         expect(instance.execute.message).to include("Missing target_branch") | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#execute' do | ||||
|  | @ -138,6 +150,33 @@ RSpec.describe PreMergeChecks, time_travel_to: Time.parse('2024-05-29T10:00:00 U | |||
|           allow(api_client).to receive(:pipeline).with(project_id, mr_pipelines[2][:id]).and_return(latest_mr_pipeline) | ||||
|         end | ||||
| 
 | ||||
|         context 'and the target branch is a stable branch' do | ||||
|           let(:target_branch) { 'a-stable-branch-stable-ee' } | ||||
| 
 | ||||
|           context 'and the latest pipeline is not fresh enough' do | ||||
|             let(:latest_mr_pipeline_created_at) { "2024-05-26T09:30:00 UTC" } | ||||
| 
 | ||||
|             it 'returns a failed PreMergeChecksStatus' do | ||||
|               expect(instance.execute).to be_a(described_class::PreMergeChecksStatus) | ||||
|               expect(instance.execute).not_to be_success | ||||
|               expect(instance.execute.message) | ||||
|                 .to include( | ||||
|                   "Expected latest pipeline (#{latest_mr_pipeline_web_url}) to be created within the last 72 hours " \ | ||||
|                     "(it was created 72.5 hours ago)!" | ||||
|                 ) | ||||
|             end | ||||
|           end | ||||
| 
 | ||||
|           context 'and the latest pipeline is fresh enough' do | ||||
|             let(:latest_mr_pipeline_created_at) { "2024-05-26T10:30:00 UTC" } | ||||
| 
 | ||||
|             it 'returns a successful PreMergeChecksStatus' do | ||||
|               expect(instance.execute).to be_a(described_class::PreMergeChecksStatus) | ||||
|               expect(instance.execute).to be_success | ||||
|             end | ||||
|           end | ||||
|         end | ||||
| 
 | ||||
|         context 'and it passes all the checks' do | ||||
|           it 'returns a successful PreMergeChecksStatus' do | ||||
|             expect(instance.execute).to be_a(described_class::PreMergeChecksStatus) | ||||
|  |  | |||
|  | @ -5,8 +5,8 @@ require 'spec_helper' | |||
| RSpec.describe VirtualRegistries::Packages::Maven::HandleFileRequestService, :aggregate_failures, feature_category: :virtual_registry do | ||||
|   let_it_be(:registry) { create(:virtual_registries_packages_maven_registry, :with_upstream) } | ||||
|   let_it_be(:project) { create(:project, namespace: registry.group) } | ||||
|   let_it_be(:user) { create(:user, owner_of: project) } | ||||
| 
 | ||||
|   let(:user) { project.creator } | ||||
|   let(:upstream) { registry.upstream } | ||||
|   let(:path) { 'com/test/package/1.2.3/package-1.2.3.pom' } | ||||
|   let(:upstream_resource_url) { upstream.url_for(path) } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue