From a56971e97f0385640c2f3568017bd221897b78ef Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 26 Aug 2021 18:10:35 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .../queries/repository/files.query.graphql | 1 + .../repository/path_last_commit.query.graphql | 1 + .../repository/permissions.query.graphql | 1 + .../types/dependency_proxy/blob_type.rb | 16 +++ .../dependency_proxy/group_setting_type.rb | 13 ++ .../types/dependency_proxy/manifest_type.rb | 22 +++ app/graphql/types/group_type.rb | 44 ++++++ app/models/ci/bridge.rb | 18 +-- app/models/note.rb | 2 +- app/policies/dependency_proxy/blob_policy.rb | 6 + .../dependency_proxy/group_setting_policy.rb | 6 + .../dependency_proxy/manifest_policy.rb | 6 + .../move_deploy_keys_projects_service.rb | 2 +- app/services/projects/move_forks_service.rb | 2 +- .../move_lfs_objects_projects_service.rb | 2 +- .../move_notification_settings_service.rb | 2 +- .../move_project_authorizations_service.rb | 2 +- .../move_project_group_links_service.rb | 2 +- .../projects/move_project_members_service.rb | 2 +- .../move_users_star_projects_service.rb | 2 +- .../new_route_storage_purchase.yml | 8 ++ config/metrics/schema.json | 2 +- ...ract_project_topics_into_separate_table.rb | 2 +- ...lds_metadata_and_ci_build_async_indexes.rb | 55 ++++++++ db/schema_migrations/20210824174615 | 1 + doc/api/graphql/reference/index.md | 90 +++++++++++++ doc/api/index.md | 5 +- doc/api/lint.md | 2 +- doc/api/usage_data.md | 2 +- .../service_ping/metrics_dictionary.md | 9 +- doc/user/project/merge_requests/versions.md | 4 +- .../project/settings/project_access_tokens.md | 5 +- lib/banzai/reference_parser/base_parser.rb | 3 +- .../async_indexes/migration_helpers.rb | 2 - lib/gitlab/reference_extractor.rb | 25 +++- lib/tasks/gitlab/product_intelligence.rake | 24 ---- locale/gitlab.pot | 3 + .../cop/migration/prevent_index_creation.rb | 2 +- spec/factories/dependency_proxy.rb | 2 + .../types/dependency_proxy/blob_type_spec.rb | 13 ++ .../group_setting_type_spec.rb | 13 ++ .../dependency_proxy/manifest_type_spec.rb | 13 ++ spec/graphql/types/group_type_spec.rb | 5 +- .../reference_parser/base_parser_spec.rb | 12 +- .../mentioned_group_parser_spec.rb | 6 +- .../mentioned_project_parser_spec.rb | 6 +- .../mentioned_user_parser_spec.rb | 6 +- .../reference_parser/project_parser_spec.rb | 8 +- spec/lib/gitlab/reference_extractor_spec.rb | 57 +++++++- .../gitlab/usage/metric_definition_spec.rb | 8 +- spec/lib/gitlab/usage/metric_spec.rb | 2 +- spec/models/ci/bridge_spec.rb | 8 +- spec/models/note_spec.rb | 48 +++++-- .../group/dependency_proxy_blobs_spec.rb | 127 ++++++++++++++++++ .../dependency_proxy_group_setting_spec.rb | 78 +++++++++++ .../group/dependency_proxy_manifests_spec.rb | 119 ++++++++++++++++ .../migration/prevent_index_creation_spec.rb | 35 +++-- .../shared_processing_service.rb | 33 +++++ .../helpers/reference_parser_helpers.rb | 5 +- .../gitlab/product_intelligence_rake_spec.rb | 80 ----------- 60 files changed, 868 insertions(+), 212 deletions(-) create mode 100644 app/graphql/types/dependency_proxy/blob_type.rb create mode 100644 app/graphql/types/dependency_proxy/group_setting_type.rb create mode 100644 app/graphql/types/dependency_proxy/manifest_type.rb create mode 100644 app/policies/dependency_proxy/blob_policy.rb create mode 100644 app/policies/dependency_proxy/group_setting_policy.rb create mode 100644 app/policies/dependency_proxy/manifest_policy.rb create mode 100644 config/feature_flags/development/new_route_storage_purchase.yml create mode 100644 db/post_migrate/20210824174615_prepare_ci_builds_metadata_and_ci_build_async_indexes.rb create mode 100644 db/schema_migrations/20210824174615 delete mode 100644 lib/tasks/gitlab/product_intelligence.rake create mode 100644 spec/graphql/types/dependency_proxy/blob_type_spec.rb create mode 100644 spec/graphql/types/dependency_proxy/group_setting_type_spec.rb create mode 100644 spec/graphql/types/dependency_proxy/manifest_type_spec.rb create mode 100644 spec/requests/api/graphql/group/dependency_proxy_blobs_spec.rb create mode 100644 spec/requests/api/graphql/group/dependency_proxy_group_setting_spec.rb create mode 100644 spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb delete mode 100644 spec/tasks/gitlab/product_intelligence_rake_spec.rb diff --git a/app/graphql/queries/repository/files.query.graphql b/app/graphql/queries/repository/files.query.graphql index 232d98a932c..a83880ce696 100644 --- a/app/graphql/queries/repository/files.query.graphql +++ b/app/graphql/queries/repository/files.query.graphql @@ -23,6 +23,7 @@ query getFiles( $nextPageCursor: String ) { project(fullPath: $projectPath) { + id __typename repository { __typename diff --git a/app/graphql/queries/repository/path_last_commit.query.graphql b/app/graphql/queries/repository/path_last_commit.query.graphql index d845f7c6224..b5c5f653429 100644 --- a/app/graphql/queries/repository/path_last_commit.query.graphql +++ b/app/graphql/queries/repository/path_last_commit.query.graphql @@ -1,5 +1,6 @@ query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) { project(fullPath: $projectPath) { + id __typename repository { __typename diff --git a/app/graphql/queries/repository/permissions.query.graphql b/app/graphql/queries/repository/permissions.query.graphql index c0262a882cd..6d2ae362e31 100644 --- a/app/graphql/queries/repository/permissions.query.graphql +++ b/app/graphql/queries/repository/permissions.query.graphql @@ -1,5 +1,6 @@ query getPermissions($projectPath: ID!) { project(fullPath: $projectPath) { + id __typename userPermissions { __typename diff --git a/app/graphql/types/dependency_proxy/blob_type.rb b/app/graphql/types/dependency_proxy/blob_type.rb new file mode 100644 index 00000000000..f5a78fbb3ba --- /dev/null +++ b/app/graphql/types/dependency_proxy/blob_type.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Types + class DependencyProxy::BlobType < BaseObject + graphql_name 'DependencyProxyBlob' + + description 'Dependency proxy blob' + + authorize :read_dependency_proxy + + field :created_at, Types::TimeType, null: false, description: 'Date of creation.' + field :updated_at, Types::TimeType, null: false, description: 'Date of most recent update.' + field :file_name, GraphQL::Types::String, null: false, description: 'Name of the blob.' + field :size, GraphQL::Types::String, null: false, description: 'Size of the blob file.' + end +end diff --git a/app/graphql/types/dependency_proxy/group_setting_type.rb b/app/graphql/types/dependency_proxy/group_setting_type.rb new file mode 100644 index 00000000000..8b8b8572aa9 --- /dev/null +++ b/app/graphql/types/dependency_proxy/group_setting_type.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Types + class DependencyProxy::GroupSettingType < BaseObject + graphql_name 'DependencyProxySetting' + + description 'Group-level Dependency Proxy settings' + + authorize :read_dependency_proxy + + field :enabled, GraphQL::Types::Boolean, null: false, description: 'Indicates whether the dependency proxy is enabled for the group.' + end +end diff --git a/app/graphql/types/dependency_proxy/manifest_type.rb b/app/graphql/types/dependency_proxy/manifest_type.rb new file mode 100644 index 00000000000..9aa62266ef7 --- /dev/null +++ b/app/graphql/types/dependency_proxy/manifest_type.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Types + class DependencyProxy::ManifestType < BaseObject + graphql_name 'DependencyProxyManifest' + + description 'Dependency proxy manifest' + + authorize :read_dependency_proxy + + field :created_at, Types::TimeType, null: false, description: 'Date of creation.' + field :updated_at, Types::TimeType, null: false, description: 'Date of most recent update.' + field :file_name, GraphQL::Types::String, null: false, description: 'Name of the manifest.' + field :image_name, GraphQL::Types::String, null: false, description: 'Name of the image.' + field :size, GraphQL::Types::String, null: false, description: 'Size of the manifest file.' + field :digest, GraphQL::Types::String, null: false, description: 'Digest of the manifest.' + + def image_name + object.file_name.chomp(File.extname(object.file_name)) + end + end +end diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb index 46b8833e5fb..58bc83fdd3c 100644 --- a/app/graphql/types/group_type.rb +++ b/app/graphql/types/group_type.rb @@ -128,6 +128,36 @@ module Types description: 'Packages of the group.', resolver: Resolvers::GroupPackagesResolver + field :dependency_proxy_setting, + Types::DependencyProxy::GroupSettingType, + null: true, + description: 'Dependency Proxy settings for the group.' + + field :dependency_proxy_manifests, + Types::DependencyProxy::ManifestType.connection_type, + null: true, + description: 'Dependency Proxy manifests.' + + field :dependency_proxy_blobs, + Types::DependencyProxy::BlobType.connection_type, + null: true, + description: 'Dependency Proxy blobs.' + + field :dependency_proxy_image_count, + GraphQL::Types::Int, + null: false, + description: 'Number of dependency proxy images cached in the group.' + + field :dependency_proxy_blob_count, + GraphQL::Types::Int, + null: false, + description: 'Number of dependency proxy blobs cached in the group.' + + field :dependency_proxy_total_size, + GraphQL::Types::String, + null: false, + description: 'Total size of the dependency proxy cached images.' + def label(title:) BatchLoader::GraphQL.for(title).batch(key: group) do |titles, loader, args| LabelsFinder @@ -172,6 +202,20 @@ module Types group.container_repositories.size end + def dependency_proxy_image_count + group.dependency_proxy_manifests.count + end + + def dependency_proxy_blob_count + group.dependency_proxy_blobs.count + end + + def dependency_proxy_total_size + ActiveSupport::NumberHelper.number_to_human_size( + group.dependency_proxy_manifests.sum(:size) + group.dependency_proxy_blobs.sum(:size) + ) + end + private def group diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb index 577bca282ef..97fb8233d34 100644 --- a/app/models/ci/bridge.rb +++ b/app/models/ci/bridge.rb @@ -28,10 +28,10 @@ module Ci state_machine :status do after_transition [:created, :manual, :waiting_for_resource] => :pending do |bridge| - next unless bridge.downstream_project + next unless bridge.triggers_downstream_pipeline? bridge.run_after_commit do - bridge.schedule_downstream_pipeline! + ::Ci::CreateCrossProjectPipelineWorker.perform_async(bridge.id) end end @@ -64,12 +64,6 @@ module Ci ) end - def schedule_downstream_pipeline! - raise InvalidBridgeTypeError unless downstream_project - - ::Ci::CreateCrossProjectPipelineWorker.perform_async(self.id) - end - def inherit_status_from_downstream!(pipeline) case pipeline.status when 'success' @@ -112,10 +106,18 @@ module Ci pipeline if triggers_child_pipeline? end + def triggers_downstream_pipeline? + triggers_child_pipeline? || triggers_cross_project_pipeline? + end + def triggers_child_pipeline? yaml_for_downstream.present? end + def triggers_cross_project_pipeline? + downstream_project_path.present? + end + def tags [:bridge] end diff --git a/app/models/note.rb b/app/models/note.rb index 6c5bbf36bd6..7619ace6dce 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -643,7 +643,7 @@ class Note < ApplicationRecord user_visible_reference_count > 0 && user_visible_reference_count == total_reference_count else refs = all_references(user) - refs.all.any? && refs.stateful_not_visible_counter == 0 + refs.all.any? && refs.all_visible? end end diff --git a/app/policies/dependency_proxy/blob_policy.rb b/app/policies/dependency_proxy/blob_policy.rb new file mode 100644 index 00000000000..42e023952d0 --- /dev/null +++ b/app/policies/dependency_proxy/blob_policy.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true +module DependencyProxy + class BlobPolicy < BasePolicy + delegate { @subject.group } + end +end diff --git a/app/policies/dependency_proxy/group_setting_policy.rb b/app/policies/dependency_proxy/group_setting_policy.rb new file mode 100644 index 00000000000..71de3cf93bd --- /dev/null +++ b/app/policies/dependency_proxy/group_setting_policy.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true +module DependencyProxy + class GroupSettingPolicy < BasePolicy + delegate { @subject.group } + end +end diff --git a/app/policies/dependency_proxy/manifest_policy.rb b/app/policies/dependency_proxy/manifest_policy.rb new file mode 100644 index 00000000000..f2e91e45327 --- /dev/null +++ b/app/policies/dependency_proxy/manifest_policy.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true +module DependencyProxy + class ManifestPolicy < BasePolicy + delegate { @subject.group } + end +end diff --git a/app/services/projects/move_deploy_keys_projects_service.rb b/app/services/projects/move_deploy_keys_projects_service.rb index 17513f0ba11..98ba5eb3f13 100644 --- a/app/services/projects/move_deploy_keys_projects_service.rb +++ b/app/services/projects/move_deploy_keys_projects_service.rb @@ -5,7 +5,7 @@ module Projects def execute(source_project, remove_remaining_elements: true) return unless super - Project.transaction(requires_new: true) do # rubocop:disable Performance/ActiveRecordSubtransactions + Project.transaction do move_deploy_keys_projects remove_remaining_deploy_keys_projects if remove_remaining_elements diff --git a/app/services/projects/move_forks_service.rb b/app/services/projects/move_forks_service.rb index f61552f661f..a96cf4dd3ea 100644 --- a/app/services/projects/move_forks_service.rb +++ b/app/services/projects/move_forks_service.rb @@ -5,7 +5,7 @@ module Projects def execute(source_project, remove_remaining_elements: true) return unless super && source_project.fork_network - Project.transaction(requires_new: true) do # rubocop:disable Performance/ActiveRecordSubtransactions + Project.transaction do move_fork_network_members update_root_project refresh_forks_count diff --git a/app/services/projects/move_lfs_objects_projects_service.rb b/app/services/projects/move_lfs_objects_projects_service.rb index ca8b4bfde68..7107ecc6c95 100644 --- a/app/services/projects/move_lfs_objects_projects_service.rb +++ b/app/services/projects/move_lfs_objects_projects_service.rb @@ -5,7 +5,7 @@ module Projects def execute(source_project, remove_remaining_elements: true) return unless super - Project.transaction(requires_new: true) do # rubocop:disable Performance/ActiveRecordSubtransactions + Project.transaction do move_lfs_objects_projects remove_remaining_lfs_objects_project if remove_remaining_elements diff --git a/app/services/projects/move_notification_settings_service.rb b/app/services/projects/move_notification_settings_service.rb index fc268f5762b..fb84f10207d 100644 --- a/app/services/projects/move_notification_settings_service.rb +++ b/app/services/projects/move_notification_settings_service.rb @@ -5,7 +5,7 @@ module Projects def execute(source_project, remove_remaining_elements: true) return unless super - Project.transaction(requires_new: true) do # rubocop:disable Performance/ActiveRecordSubtransactions + Project.transaction do move_notification_settings remove_remaining_notification_settings if remove_remaining_elements diff --git a/app/services/projects/move_project_authorizations_service.rb b/app/services/projects/move_project_authorizations_service.rb index 30f3b892131..6ac173a20fc 100644 --- a/app/services/projects/move_project_authorizations_service.rb +++ b/app/services/projects/move_project_authorizations_service.rb @@ -9,7 +9,7 @@ module Projects def execute(source_project, remove_remaining_elements: true) return unless super - Project.transaction(requires_new: true) do # rubocop:disable Performance/ActiveRecordSubtransactions + Project.transaction do move_project_authorizations remove_remaining_authorizations if remove_remaining_elements diff --git a/app/services/projects/move_project_group_links_service.rb b/app/services/projects/move_project_group_links_service.rb index 8344b0b404b..5f6a7dd09e1 100644 --- a/app/services/projects/move_project_group_links_service.rb +++ b/app/services/projects/move_project_group_links_service.rb @@ -9,7 +9,7 @@ module Projects def execute(source_project, remove_remaining_elements: true) return unless super - Project.transaction(requires_new: true) do # rubocop:disable Performance/ActiveRecordSubtransactions + Project.transaction do move_group_links remove_remaining_project_group_links if remove_remaining_elements diff --git a/app/services/projects/move_project_members_service.rb b/app/services/projects/move_project_members_service.rb index 11629c3fd7e..011bd17c8cc 100644 --- a/app/services/projects/move_project_members_service.rb +++ b/app/services/projects/move_project_members_service.rb @@ -9,7 +9,7 @@ module Projects def execute(source_project, remove_remaining_elements: true) return unless super - Project.transaction(requires_new: true) do # rubocop:disable Performance/ActiveRecordSubtransactions + Project.transaction do move_project_members remove_remaining_members if remove_remaining_elements diff --git a/app/services/projects/move_users_star_projects_service.rb b/app/services/projects/move_users_star_projects_service.rb index b8564a02301..5490448553f 100644 --- a/app/services/projects/move_users_star_projects_service.rb +++ b/app/services/projects/move_users_star_projects_service.rb @@ -9,7 +9,7 @@ module Projects return unless user_stars.any? - Project.transaction(requires_new: true) do # rubocop:disable Performance/ActiveRecordSubtransactions + Project.transaction do user_stars.update_all(project_id: @project.id) Project.reset_counters @project.id, :users_star_projects diff --git a/config/feature_flags/development/new_route_storage_purchase.yml b/config/feature_flags/development/new_route_storage_purchase.yml new file mode 100644 index 00000000000..b248a5f7d09 --- /dev/null +++ b/config/feature_flags/development/new_route_storage_purchase.yml @@ -0,0 +1,8 @@ +--- +name: new_route_storage_purchase +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68834 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/327896 +milestone: '14.3' +type: development +group: group::purchase +default_enabled: false diff --git a/config/metrics/schema.json b/config/metrics/schema.json index 24ea41d3f16..55edf2c74fd 100644 --- a/config/metrics/schema.json +++ b/config/metrics/schema.json @@ -30,7 +30,7 @@ }, "status": { "type": ["string"], - "enum": ["data_available", "implemented", "not_used", "deprecated", "removed", "broken"] + "enum": ["active", "data_available", "implemented", "deprecated", "removed", "broken"] }, "milestone": { "type": ["string", "null"], diff --git a/db/post_migrate/20210730104800_schedule_extract_project_topics_into_separate_table.rb b/db/post_migrate/20210730104800_schedule_extract_project_topics_into_separate_table.rb index bb498f89015..3102561a129 100644 --- a/db/post_migrate/20210730104800_schedule_extract_project_topics_into_separate_table.rb +++ b/db/post_migrate/20210730104800_schedule_extract_project_topics_into_separate_table.rb @@ -20,7 +20,7 @@ class ScheduleExtractProjectTopicsIntoSeparateTable < ActiveRecord::Migration[6. def up # this index is used in 20210730104800_schedule_extract_project_topics_into_separate_table - add_concurrent_index :taggings, :id, where: INDEX_CONDITION, name: INDEX_NAME + add_concurrent_index :taggings, :id, where: INDEX_CONDITION, name: INDEX_NAME # rubocop:disable Migration/PreventIndexCreation queue_background_migration_jobs_by_range_at_intervals( Tagging.where(taggable_type: 'Project'), diff --git a/db/post_migrate/20210824174615_prepare_ci_builds_metadata_and_ci_build_async_indexes.rb b/db/post_migrate/20210824174615_prepare_ci_builds_metadata_and_ci_build_async_indexes.rb new file mode 100644 index 00000000000..0a0fda7e870 --- /dev/null +++ b/db/post_migrate/20210824174615_prepare_ci_builds_metadata_and_ci_build_async_indexes.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +class PrepareCiBuildsMetadataAndCiBuildAsyncIndexes < ActiveRecord::Migration[6.1] + include Gitlab::Database::MigrationHelpers + + def up + prepare_async_index :ci_builds_metadata, :id_convert_to_bigint, unique: true, + name: :index_ci_builds_metadata_on_id_convert_to_bigint + + prepare_async_index :ci_builds_metadata, :build_id_convert_to_bigint, unique: true, + name: :index_ci_builds_metadata_on_build_id_convert_to_bigint + + prepare_async_index :ci_builds_metadata, :build_id_convert_to_bigint, where: 'has_exposed_artifacts IS TRUE', + name: :index_ci_builds_metadata_on_build_id_int8_and_exposed_artifacts + + prepare_async_index_from_sql(:ci_builds_metadata, :index_ci_builds_metadata_on_build_id_int8_where_interruptible, <<~SQL.squish) + CREATE INDEX CONCURRENTLY "index_ci_builds_metadata_on_build_id_int8_where_interruptible" + ON "ci_builds_metadata" ("build_id_convert_to_bigint") INCLUDE ("id_convert_to_bigint") + WHERE interruptible = true + SQL + + prepare_async_index :ci_builds, :id_convert_to_bigint, unique: true, + name: :index_ci_builds_on_converted_id + end + + def down + unprepare_async_index_by_name :ci_builds, :index_ci_builds_on_converted_id + + unprepare_async_index_by_name :ci_builds_metadata, :index_ci_builds_metadata_on_build_id_int8_where_interruptible + + unprepare_async_index_by_name :ci_builds_metadata, :index_ci_builds_metadata_on_build_id_int8_and_exposed_artifacts + + unprepare_async_index_by_name :ci_builds_metadata, :index_ci_builds_metadata_on_build_id_convert_to_bigint + + unprepare_async_index_by_name :ci_builds_metadata, :index_ci_builds_metadata_on_id_convert_to_bigint + end + + private + + def prepare_async_index_from_sql(table_name, index_name, definition) + return unless async_index_creation_available? + + return if index_name_exists?(table_name, index_name) + + async_index = Gitlab::Database::AsyncIndexes::PostgresAsyncIndex.safe_find_or_create_by!(name: index_name) do |rec| + rec.table_name = table_name + rec.definition = definition + end + + Gitlab::AppLogger.info( + message: 'Prepared index for async creation', + table_name: async_index.table_name, + index_name: async_index.name) + end +end diff --git a/db/schema_migrations/20210824174615 b/db/schema_migrations/20210824174615 new file mode 100644 index 00000000000..56160c801f1 --- /dev/null +++ b/db/schema_migrations/20210824174615 @@ -0,0 +1 @@ +830cf08352b0d1f0c7f08ea67107466ea1d6a478c6f47d5e19f0ffa6c57f5641 \ No newline at end of file diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 3b09810f59f..faa2e2fd28c 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -5408,6 +5408,52 @@ The edge type for [`DastSiteValidation`](#dastsitevalidation). | `cursor` | [`String!`](#string) | A cursor for use in pagination. | | `node` | [`DastSiteValidation`](#dastsitevalidation) | The item at the end of the edge. | +#### `DependencyProxyBlobConnection` + +The connection type for [`DependencyProxyBlob`](#dependencyproxyblob). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `edges` | [`[DependencyProxyBlobEdge]`](#dependencyproxyblobedge) | A list of edges. | +| `nodes` | [`[DependencyProxyBlob]`](#dependencyproxyblob) | A list of nodes. | +| `pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. | + +#### `DependencyProxyBlobEdge` + +The edge type for [`DependencyProxyBlob`](#dependencyproxyblob). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `cursor` | [`String!`](#string) | A cursor for use in pagination. | +| `node` | [`DependencyProxyBlob`](#dependencyproxyblob) | The item at the end of the edge. | + +#### `DependencyProxyManifestConnection` + +The connection type for [`DependencyProxyManifest`](#dependencyproxymanifest). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `edges` | [`[DependencyProxyManifestEdge]`](#dependencyproxymanifestedge) | A list of edges. | +| `nodes` | [`[DependencyProxyManifest]`](#dependencyproxymanifest) | A list of nodes. | +| `pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. | + +#### `DependencyProxyManifestEdge` + +The edge type for [`DependencyProxyManifest`](#dependencyproxymanifest). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `cursor` | [`String!`](#string) | A cursor for use in pagination. | +| `node` | [`DependencyProxyManifest`](#dependencyproxymanifest) | The item at the end of the edge. | + #### `DesignAtVersionConnection` The connection type for [`DesignAtVersion`](#designatversion). @@ -8565,6 +8611,44 @@ The response from the AdminSidekiqQueuesDeleteJobs mutation. | `deletedJobs` | [`Int`](#int) | Number of matching jobs deleted. | | `queueSize` | [`Int`](#int) | Queue size after processing. | +### `DependencyProxyBlob` + +Dependency proxy blob. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `createdAt` | [`Time!`](#time) | Date of creation. | +| `fileName` | [`String!`](#string) | Name of the blob. | +| `size` | [`String!`](#string) | Size of the blob file. | +| `updatedAt` | [`Time!`](#time) | Date of most recent update. | + +### `DependencyProxyManifest` + +Dependency proxy manifest. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `createdAt` | [`Time!`](#time) | Date of creation. | +| `digest` | [`String!`](#string) | Digest of the manifest. | +| `fileName` | [`String!`](#string) | Name of the manifest. | +| `imageName` | [`String!`](#string) | Name of the image. | +| `size` | [`String!`](#string) | Size of the manifest file. | +| `updatedAt` | [`Time!`](#time) | Date of most recent update. | + +### `DependencyProxySetting` + +Group-level Dependency Proxy settings. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `enabled` | [`Boolean!`](#boolean) | Indicates whether the dependency proxy is enabled for the group. | + ### `Design` A single design. @@ -9596,6 +9680,12 @@ four standard [pagination arguments](#connection-pagination-arguments): | `containerRepositoriesCount` | [`Int!`](#int) | Number of container repositories in the group. | | `containsLockedProjects` | [`Boolean!`](#boolean) | Includes at least one project where the repository size exceeds the limit. | | `customEmoji` | [`CustomEmojiConnection`](#customemojiconnection) | Custom emoji within this namespace. Available only when feature flag `custom_emoji` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. (see [Connections](#connections)) | +| `dependencyProxyBlobCount` | [`Int!`](#int) | Number of dependency proxy blobs cached in the group. | +| `dependencyProxyBlobs` | [`DependencyProxyBlobConnection`](#dependencyproxyblobconnection) | Dependency Proxy blobs. (see [Connections](#connections)) | +| `dependencyProxyImageCount` | [`Int!`](#int) | Number of dependency proxy images cached in the group. | +| `dependencyProxyManifests` | [`DependencyProxyManifestConnection`](#dependencyproxymanifestconnection) | Dependency Proxy manifests. (see [Connections](#connections)) | +| `dependencyProxySetting` | [`DependencyProxySetting`](#dependencyproxysetting) | Dependency Proxy settings for the group. | +| `dependencyProxyTotalSize` | [`String!`](#string) | Total size of the dependency proxy cached images. | | `description` | [`String`](#string) | Description of the namespace. | | `descriptionHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `description`. | | `dora` | [`Dora`](#dora) | The group's DORA metrics. | diff --git a/doc/api/index.md b/doc/api/index.md index 38083f1dcf0..92af68645ef 100644 --- a/doc/api/index.md +++ b/doc/api/index.md @@ -220,7 +220,8 @@ You can use a GitLab CI/CD job token to authenticate with specific API endpoints Package Registry, you can use [deploy tokens](../user/project/deploy_tokens/index.md). - [Container Registry](../user/packages/container_registry/index.md) (the `$CI_REGISTRY_PASSWORD` is `$CI_JOB_TOKEN`). - - [Container Registry API](container_registry.md) (scoped to the job's project, when the `ci_job_token_scope` feature flag is enabled) + - [Container Registry API](container_registry.md) + (scoped to the job's project, when the `ci_job_token_scope` feature flag is enabled). - [Get job artifacts](job_artifacts.md#get-job-artifacts). - [Get job token's job](jobs.md#get-job-tokens-job). - [Pipeline triggers](pipeline_triggers.md), using the `token=` parameter. @@ -682,7 +683,7 @@ send the payload body: ```shell curl --request POST --header "Content-Type: application/json" \ - --data '{"name":"", "description":"", "description":""}' "https://gitlab/api/v4/projects" ``` URL encoded query strings have a length limitation. Requests that are too large diff --git a/doc/api/lint.md b/doc/api/lint.md index a47bb028248..57e214e7034 100644 --- a/doc/api/lint.md +++ b/doc/api/lint.md @@ -9,7 +9,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w ## Validate the CI YAML configuration Checks if CI/CD YAML configuration is valid. This endpoint validates basic CI/CD -configuration syntax. It doesn't have any namespace specific context. +configuration syntax. It doesn't have any namespace-specific context. Access to this endpoint does not require authentication when the instance [allows new sign ups](../user/admin_area/settings/sign_up_restrictions.md#disable-new-sign-ups) diff --git a/doc/api/usage_data.md b/doc/api/usage_data.md index 00d87c89faf..5e5517eb1de 100644 --- a/doc/api/usage_data.md +++ b/doc/api/usage_data.md @@ -37,7 +37,7 @@ Example response: product_group: group::global search product_category: global_search value_type: number - status: data_available + status: active time_frame: 28d data_source: redis_hll distribution: diff --git a/doc/development/service_ping/metrics_dictionary.md b/doc/development/service_ping/metrics_dictionary.md index 7778f417b61..56c564ee41a 100644 --- a/doc/development/service_ping/metrics_dictionary.md +++ b/doc/development/service_ping/metrics_dictionary.md @@ -34,7 +34,7 @@ Each metric is defined in a separate YAML file consisting of a number of fields: | `product_group` | yes | The [group](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml) that owns the metric. | | `product_category` | no | The [product category](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/categories.yml) for the metric. | | `value_type` | yes | `string`; one of [`string`, `number`, `boolean`, `object`](https://json-schema.org/understanding-json-schema/reference/type.html). | -| `status` | yes | `string`; [status](#metric-statuses) of the metric, may be set to `data_available`, `implemented`, `not_used`, `deprecated`, `removed`, `broken`. | +| `status` | yes | `string`; [status](#metric-statuses) of the metric, may be set to `active`, `deprecated`, `removed`, `broken`. | | `time_frame` | yes | `string`; may be set to a value like `7d`, `28d`, `all`, `none`. | | `data_source` | yes | `string`; may be set to a value like `database`, `redis`, `redis_hll`, `prometheus`, `system`. | | `data_category` | yes | `string`; [categories](#data-category) of the metric, may be set to `operational`, `optional`, `subscription`, `standard`. The default value is `optional`.| @@ -53,11 +53,8 @@ Each metric is defined in a separate YAML file consisting of a number of fields: Metric definitions can have one of the following statuses: -- `data_available`: Metric data is available and used in a Sisense dashboard. -- `implemented`: Metric is implemented but data is not yet available. This is a temporary - status for newly added metrics awaiting inclusion in a new release. +- `active`: Metric is used and reports data. - `broken`: Metric reports broken data (for example, -1 fallback), or does not report data at all. A metric marked as `broken` must also have the `repair_issue_url` attribute. -- `not_used`: Metric is not used in any dashboard. - `deprecated`: Metric is deprecated and possibly planned to be removed. - `removed`: Metric was removed, but it may appear in Service Ping payloads sent from instances running on older versions of GitLab. @@ -177,7 +174,7 @@ product_section: growth product_stage: growth product_group: group::product intelligence value_type: string -status: data_available +status: active milestone: 9.1 introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/1521 time_frame: none diff --git a/doc/user/project/merge_requests/versions.md b/doc/user/project/merge_requests/versions.md index 1d196de36e2..3922ee4d770 100644 --- a/doc/user/project/merge_requests/versions.md +++ b/doc/user/project/merge_requests/versions.md @@ -1,6 +1,6 @@ --- -stage: none -group: unassigned +stage: Create +group: Code Review info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments type: reference, concepts --- diff --git a/doc/user/project/settings/project_access_tokens.md b/doc/user/project/settings/project_access_tokens.md index 643042cb96a..b5a9537d827 100644 --- a/doc/user/project/settings/project_access_tokens.md +++ b/doc/user/project/settings/project_access_tokens.md @@ -60,10 +60,7 @@ API calls made with a project access token are associated with the corresponding These bot users are included in a project's **Project information > Members** list but cannot be modified. Also, a bot user cannot be added to any other project. -- The username is set to `project_{project_id}_bot` for the first access token, such as `project_123_bot`. -- The username is set to `project_{project_id}_bot{bot_count}` for further access tokens, such as `project_123_bot1`. - -When the project access token is [revoked](#revoking-a-project-access-token) the bot user is deleted +When the project access token is [revoked](#revoking-a-project-access-token), the bot user is deleted and all records are moved to a system-wide user with the username "Ghost User". For more information, see [Associated Records](../../profile/account/delete_account.md#associated-records). diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb index 0c015ba00c7..831baa9a778 100644 --- a/lib/banzai/reference_parser/base_parser.rb +++ b/lib/banzai/reference_parser/base_parser.rb @@ -212,9 +212,8 @@ module Banzai def gather_references(nodes, ids_only: false) nodes = nodes_user_can_reference(current_user, nodes) visible = nodes_visible_to_user(current_user, nodes) - not_visible = nodes - visible - { visible: referenced_by(visible, ids_only: ids_only), not_visible: not_visible } + { visible: referenced_by(visible, ids_only: ids_only), nodes: nodes, visible_nodes: visible } end # Returns a Hash containing the projects for a given list of HTML nodes. diff --git a/lib/gitlab/database/async_indexes/migration_helpers.rb b/lib/gitlab/database/async_indexes/migration_helpers.rb index dff6376270a..0afb47ef16d 100644 --- a/lib/gitlab/database/async_indexes/migration_helpers.rb +++ b/lib/gitlab/database/async_indexes/migration_helpers.rb @@ -68,8 +68,6 @@ module Gitlab async_index end - private - def async_index_creation_available? ApplicationRecord.connection.table_exists?(:postgres_async_indexes) && Feature.enabled?(:database_async_index_creation, type: :ops) diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index 547549361be..f914123a94d 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -6,16 +6,11 @@ module Gitlab REFERABLES = %i(user issue label milestone mentioned_user mentioned_group mentioned_project merge_request snippet commit commit_range directly_addressed_user epic iteration vulnerability).freeze attr_accessor :project, :current_user, :author - # This counter is increased by a number of references filtered out by - # banzai reference exctractor. Note that this counter is stateful and - # not idempotent and is increased whenever you call `references`. - attr_reader :stateful_not_visible_counter def initialize(project, current_user = nil) @project = project @current_user = current_user @references = {} - @stateful_not_visible_counter = 0 super() end @@ -26,14 +21,19 @@ module Gitlab def references(type, ids_only: false) refs = super(type, project, current_user, ids_only: ids_only) - @stateful_not_visible_counter += refs[:not_visible].count + update_visible_nodes_set(refs[:nodes], refs[:visible_nodes]) refs[:visible] end + # this method is stateful, it tracks if all nodes from `references` + # calls are visible or not + def all_visible? + not_visible_nodes.empty? + end + def reset_memoized_values @references = {} - @stateful_not_visible_counter = 0 super() end @@ -76,5 +76,16 @@ module Gitlab @pattern = Regexp.union(patterns.compact) end + + private + + def update_visible_nodes_set(all, visible) + not_visible_nodes.merge(all) + not_visible_nodes.subtract(visible) + end + + def not_visible_nodes + @not_visible_nodes ||= Set.new + end end end diff --git a/lib/tasks/gitlab/product_intelligence.rake b/lib/tasks/gitlab/product_intelligence.rake deleted file mode 100644 index 329cd9c8c2a..00000000000 --- a/lib/tasks/gitlab/product_intelligence.rake +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -namespace :gitlab do - namespace :product_intelligence do - # @example - # bundle exec rake gitlab:product_intelligence:activate_metrics MILESTONE=14.0 - - desc 'GitLab | Product Intelligence | Update milestone metrics status to data_available' - task activate_metrics: :environment do - milestone = ENV['MILESTONE'] - raise "Please supply the MILESTONE env var".color(:red) unless milestone.present? - - Gitlab::Usage::MetricDefinition.definitions.values.each do |metric| - next if metric.attributes[:milestone] != milestone || metric.attributes[:status] != 'implemented' - - metric.attributes[:status] = 'data_available' - path = metric.path - File.open(path, "w") { |file| file << metric.to_h.deep_stringify_keys.to_yaml } - end - - puts "Task completed successfully" - end - end -end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 56966bbfa18..1d9ae54dbde 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -5888,6 +5888,9 @@ msgstr "" msgid "Buy CI Minutes" msgstr "" +msgid "Buy Storage" +msgstr "" + msgid "Buy more Pipeline minutes" msgstr "" diff --git a/rubocop/cop/migration/prevent_index_creation.rb b/rubocop/cop/migration/prevent_index_creation.rb index c90f911d24e..1486607acbe 100644 --- a/rubocop/cop/migration/prevent_index_creation.rb +++ b/rubocop/cop/migration/prevent_index_creation.rb @@ -8,7 +8,7 @@ module RuboCop class PreventIndexCreation < RuboCop::Cop::Cop include MigrationHelpers - FORBIDDEN_TABLES = %i[ci_builds].freeze + FORBIDDEN_TABLES = %i[ci_builds taggings ci_builds_metadata events].freeze MSG = "Adding new index to #{FORBIDDEN_TABLES.join(", ")} is forbidden, see https://gitlab.com/gitlab-org/gitlab/-/issues/332886" diff --git a/spec/factories/dependency_proxy.rb b/spec/factories/dependency_proxy.rb index 94a7986a8fa..c2873ce9b5e 100644 --- a/spec/factories/dependency_proxy.rb +++ b/spec/factories/dependency_proxy.rb @@ -3,12 +3,14 @@ FactoryBot.define do factory :dependency_proxy_blob, class: 'DependencyProxy::Blob' do group + size { 1234 } file { fixture_file_upload('spec/fixtures/dependency_proxy/a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4.gz') } file_name { 'a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4.gz' } end factory :dependency_proxy_manifest, class: 'DependencyProxy::Manifest' do group + size { 1234 } file { fixture_file_upload('spec/fixtures/dependency_proxy/manifest') } digest { 'sha256:d0710affa17fad5f466a70159cc458227bd25d4afb39514ef662ead3e6c99515' } file_name { 'alpine:latest.json' } diff --git a/spec/graphql/types/dependency_proxy/blob_type_spec.rb b/spec/graphql/types/dependency_proxy/blob_type_spec.rb new file mode 100644 index 00000000000..e1c8471975e --- /dev/null +++ b/spec/graphql/types/dependency_proxy/blob_type_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['DependencyProxyBlob'] do + it 'includes dependency proxy blob fields' do + expected_fields = %w[ + file_name size created_at updated_at + ] + + expect(described_class).to include_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/dependency_proxy/group_setting_type_spec.rb b/spec/graphql/types/dependency_proxy/group_setting_type_spec.rb new file mode 100644 index 00000000000..7c6d7b8aece --- /dev/null +++ b/spec/graphql/types/dependency_proxy/group_setting_type_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['DependencyProxySetting'] do + it 'includes dependency proxy blob fields' do + expected_fields = %w[ + enabled + ] + + expect(described_class).to include_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/dependency_proxy/manifest_type_spec.rb b/spec/graphql/types/dependency_proxy/manifest_type_spec.rb new file mode 100644 index 00000000000..18cc89adfcb --- /dev/null +++ b/spec/graphql/types/dependency_proxy/manifest_type_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['DependencyProxyManifest'] do + it 'includes dependency proxy manifest fields' do + expected_fields = %w[ + file_name image_name size created_at updated_at digest + ] + + expect(described_class).to include_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/group_type_spec.rb b/spec/graphql/types/group_type_spec.rb index 33250f8e6af..cee9136676f 100644 --- a/spec/graphql/types/group_type_spec.rb +++ b/spec/graphql/types/group_type_spec.rb @@ -18,7 +18,10 @@ RSpec.describe GitlabSchema.types['Group'] do two_factor_grace_period auto_devops_enabled emails_disabled mentions_disabled parent boards milestones group_members merge_requests container_repositories container_repositories_count - packages shared_runners_setting timelogs + packages dependency_proxy_setting dependency_proxy_manifests + dependency_proxy_blobs dependency_proxy_image_count + dependency_proxy_blob_count dependency_proxy_total_size + shared_runners_setting timelogs ] expect(described_class).to include_graphql_fields(*expected_fields) diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb index 095500cdc53..4701caa0667 100644 --- a/spec/lib/banzai/reference_parser/base_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb @@ -247,15 +247,15 @@ RSpec.describe Banzai::ReferenceParser::BaseParser do end end - it 'returns referenceable and visible objects, alongside nodes that are referenceable but not visible' do - expect(subject.gather_references(nodes)).to match( - visible: contain_exactly(6, 8, 10), - not_visible: match_array(nodes.select { |n| n.id.even? && n.id <= 5 }) - ) + it 'returns referenceable and visible objects, alongside all and visible nodes' do + referenceable = nodes.select { |n| n.id.even? } + visible = nodes.select { |n| [6, 8, 10].include?(n.id) } + + expect_gathered_references(subject.gather_references(nodes), [6, 8, 10], referenceable, visible) end it 'is always empty if the input is empty' do - expect(subject.gather_references([])) .to match(visible: be_empty, not_visible: be_empty) + expect_gathered_references(subject.gather_references([]), [], [], []) end end diff --git a/spec/lib/banzai/reference_parser/mentioned_group_parser_spec.rb b/spec/lib/banzai/reference_parser/mentioned_group_parser_spec.rb index 4610da7cbe6..576e629d271 100644 --- a/spec/lib/banzai/reference_parser/mentioned_group_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/mentioned_group_parser_spec.rb @@ -19,7 +19,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedGroupParser do it 'returns empty array' do link['data-group'] = project.group.id.to_s - expect_gathered_references(subject.gather_references([link]), [], 1) + expect_gathered_references(subject.gather_references([link]), [], [link], []) end end @@ -30,7 +30,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedGroupParser do end it 'returns groups' do - expect_gathered_references(subject.gather_references([link]), [group], 0) + expect_gathered_references(subject.gather_references([link]), [group], [link], [link]) end end @@ -38,7 +38,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedGroupParser do it 'returns an empty Array' do link['data-group'] = 'test-non-existing' - expect_gathered_references(subject.gather_references([link]), [], 1) + expect_gathered_references(subject.gather_references([link]), [], [link], []) end end end diff --git a/spec/lib/banzai/reference_parser/mentioned_project_parser_spec.rb b/spec/lib/banzai/reference_parser/mentioned_project_parser_spec.rb index 7eb58ee40d3..983407addce 100644 --- a/spec/lib/banzai/reference_parser/mentioned_project_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/mentioned_project_parser_spec.rb @@ -19,7 +19,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedProjectParser do it 'returns empty Array' do link['data-project'] = project.id.to_s - expect_gathered_references(subject.gather_references([link]), [], 1) + expect_gathered_references(subject.gather_references([link]), [], [link], []) end end @@ -30,7 +30,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedProjectParser do end it 'returns an Array of referenced projects' do - expect_gathered_references(subject.gather_references([link]), [project], 0) + expect_gathered_references(subject.gather_references([link]), [project], [link], [link]) end end @@ -38,7 +38,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedProjectParser do it 'returns an empty Array' do link['data-project'] = 'inexisting-project-id' - expect_gathered_references(subject.gather_references([link]), [], 1) + expect_gathered_references(subject.gather_references([link]), [], [link], []) end end end diff --git a/spec/lib/banzai/reference_parser/mentioned_user_parser_spec.rb b/spec/lib/banzai/reference_parser/mentioned_user_parser_spec.rb index 4be07866db1..f117d796dad 100644 --- a/spec/lib/banzai/reference_parser/mentioned_user_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/mentioned_user_parser_spec.rb @@ -22,7 +22,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedUserParser do end it 'returns empty list of users' do - expect_gathered_references(subject.gather_references([link]), [], 0) + expect_gathered_references(subject.gather_references([link]), [], [link], [link]) end end end @@ -35,7 +35,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedUserParser do end it 'returns empty list of users' do - expect_gathered_references(subject.gather_references([link]), [], 0) + expect_gathered_references(subject.gather_references([link]), [], [link], [link]) end end end @@ -44,7 +44,7 @@ RSpec.describe Banzai::ReferenceParser::MentionedUserParser do it 'returns an Array of users' do link['data-user'] = user.id.to_s - expect_gathered_references(subject.gather_references([link]), [user], 0) + expect_gathered_references(subject.gather_references([link]), [user], [link], [link]) end end end diff --git a/spec/lib/banzai/reference_parser/project_parser_spec.rb b/spec/lib/banzai/reference_parser/project_parser_spec.rb index 6358a04f12a..2c0b6c417b0 100644 --- a/spec/lib/banzai/reference_parser/project_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/project_parser_spec.rb @@ -17,7 +17,7 @@ RSpec.describe Banzai::ReferenceParser::ProjectParser do it 'returns an Array of projects' do link['data-project'] = project.id.to_s - expect_gathered_references(subject.gather_references([link]), [project], 0) + expect_gathered_references(subject.gather_references([link]), [project], [link], [link]) end end @@ -25,7 +25,7 @@ RSpec.describe Banzai::ReferenceParser::ProjectParser do it 'returns an empty Array' do link['data-project'] = '' - expect_gathered_references(subject.gather_references([link]), [], 1) + expect_gathered_references(subject.gather_references([link]), [], [link], []) end end @@ -35,7 +35,7 @@ RSpec.describe Banzai::ReferenceParser::ProjectParser do link['data-project'] = private_project.id.to_s - expect_gathered_references(subject.gather_references([link]), [], 1) + expect_gathered_references(subject.gather_references([link]), [], [link], []) end it 'returns an Array when authorized' do @@ -43,7 +43,7 @@ RSpec.describe Banzai::ReferenceParser::ProjectParser do link['data-project'] = private_project.id.to_s - expect_gathered_references(subject.gather_references([link]), [private_project], 0) + expect_gathered_references(subject.gather_references([link]), [private_project], [link], [link]) end end end diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index f6e69aa6533..177e9d346b6 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -332,14 +332,59 @@ RSpec.describe Gitlab::ReferenceExtractor do it 'returns visible references of given type' do expect(subject.references(:issue)).to eq([issue]) end - - it 'does not increase stateful_not_visible_counter' do - expect { subject.references(:issue) }.not_to change { subject.stateful_not_visible_counter } - end end - it 'increases stateful_not_visible_counter' do - expect { subject.references(:issue) }.to change { subject.stateful_not_visible_counter }.by(1) + it 'does not return any references' do + expect(subject.references(:issue)).to be_empty + end + end + + describe '#all_visible?' do + let_it_be(:user) { create(:user) } + let_it_be(:project2) { create(:project) } + let_it_be(:issue) { create(:issue, project: project) } + let_it_be(:issue2) { create(:issue, project: project2) } + + let(:text) { "Ref. #{issue.to_reference} and #{issue2.to_reference(project)}" } + + subject { described_class.new(project, user) } + + before do + subject.analyze(text) + end + + it 'returns true if no references were parsed yet' do + expect(subject.all_visible?).to be_truthy + end + + context 'when references was already called' do + let(:membership) { [] } + + before do + membership.each { |p| p.add_developer(user) } + + subject.references(:issue) + end + + it 'returns false' do + expect(subject.all_visible?).to be_falsey + end + + context 'when user can access only some references' do + let(:membership) { [project] } + + it 'returns false' do + expect(subject.all_visible?).to be_falsey + end + end + + context 'when user can access all references' do + let(:membership) { [project, project2] } + + it 'returns true' do + expect(subject.all_visible?).to be_truthy + end + end end end end diff --git a/spec/lib/gitlab/usage/metric_definition_spec.rb b/spec/lib/gitlab/usage/metric_definition_spec.rb index 1ae8a0881ef..f19e893762f 100644 --- a/spec/lib/gitlab/usage/metric_definition_spec.rb +++ b/spec/lib/gitlab/usage/metric_definition_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Gitlab::Usage::MetricDefinition do value_type: 'string', product_category: 'collection', product_stage: 'growth', - status: 'data_available', + status: 'active', default_generation: 'generation_1', key_path: 'uuid', product_group: 'group::product analytics', @@ -127,9 +127,7 @@ RSpec.describe Gitlab::Usage::MetricDefinition do where(:status, :skip_validation?) do 'deprecated' | true 'removed' | true - 'data_available' | false - 'implemented' | false - 'not_used' | false + 'active' | false end with_them do @@ -191,7 +189,7 @@ RSpec.describe Gitlab::Usage::MetricDefinition do value_type: 'string', product_category: 'collection', product_stage: 'growth', - status: 'data_available', + status: 'active', default_generation: 'generation_1', key_path: 'counter.category.event', product_group: 'group::product analytics', diff --git a/spec/lib/gitlab/usage/metric_spec.rb b/spec/lib/gitlab/usage/metric_spec.rb index d83f59e4a7d..ea8d1a135a6 100644 --- a/spec/lib/gitlab/usage/metric_spec.rb +++ b/spec/lib/gitlab/usage/metric_spec.rb @@ -15,7 +15,7 @@ RSpec.describe Gitlab::Usage::Metric do product_group: "group::plan", product_category: "issue_tracking", value_type: "number", - status: "data_available", + status: "active", time_frame: "all", data_source: "database", instrumentation_class: "CountIssuesMetric", diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb index db956b26b6b..6dd3c40f228 100644 --- a/spec/models/ci/bridge_spec.rb +++ b/spec/models/ci/bridge_spec.rb @@ -74,18 +74,18 @@ RSpec.describe Ci::Bridge do it "schedules downstream pipeline creation when the status is #{status}" do bridge.status = status - expect(bridge).to receive(:schedule_downstream_pipeline!) - bridge.enqueue! + + expect(::Ci::CreateCrossProjectPipelineWorker.jobs.last['args']).to eq([bridge.id]) end end it "schedules downstream pipeline creation when the status is waiting for resource" do bridge.status = :waiting_for_resource - expect(bridge).to receive(:schedule_downstream_pipeline!) - bridge.enqueue_waiting_for_resource! + + expect(::Ci::CreateCrossProjectPipelineWorker.jobs.last['args']).to eq([bridge.id]) end it 'raises error when the status is failed' do diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 0afdae2fc93..e26355b1eb4 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -500,15 +500,15 @@ RSpec.describe Note do let_it_be(:ext_issue) { create(:issue, project: ext_proj) } shared_examples "checks references" do - it "returns true" do + it "returns false" do expect(note.system_note_with_references_visible_for?(ext_issue.author)).to be_falsy end - it "returns false" do + it "returns true" do expect(note.system_note_with_references_visible_for?(private_user)).to be_truthy end - it "returns false if user visible reference count set" do + it "returns true if user visible reference count set" do note.user_visible_reference_count = 1 note.total_reference_count = 1 @@ -516,7 +516,15 @@ RSpec.describe Note do expect(note.system_note_with_references_visible_for?(ext_issue.author)).to be_truthy end - it "returns true if ref count is 0" do + it "returns false if user visible reference count set but does not match total reference count" do + note.user_visible_reference_count = 1 + note.total_reference_count = 2 + + expect(note).not_to receive(:reference_mentionables) + expect(note.system_note_with_references_visible_for?(ext_issue.author)).to be_falsy + end + + it "returns false if ref count is 0" do note.user_visible_reference_count = 0 expect(note).not_to receive(:reference_mentionables) @@ -562,13 +570,35 @@ RSpec.describe Note do end it_behaves_like "checks references" + end - it "returns true if user visible reference count set and there is a private reference" do - note.user_visible_reference_count = 1 - note.total_reference_count = 2 + context "when there is a private issue and user reference" do + let_it_be(:ext_issue2) { create(:issue, project: ext_proj) } - expect(note).not_to receive(:reference_mentionables) - expect(note.system_note_with_references_visible_for?(ext_issue.author)).to be_falsy + let(:note) do + create :note, + noteable: ext_issue2, project: ext_proj, + note: "mentioned in #{private_issue.to_reference(ext_proj)} and pinged user #{private_user.to_reference}", + system: true + end + + it_behaves_like "checks references" + end + + context "when there is a publicly visible user reference" do + let(:note) do + create :note, + noteable: ext_issue, project: ext_proj, + note: "mentioned in #{ext_proj.owner.to_reference}", + system: true + end + + it "returns true for other users" do + expect(note.system_note_with_references_visible_for?(ext_issue.author)).to be_truthy + end + + it "returns true for anonymous users" do + expect(note.system_note_with_references_visible_for?(nil)).to be_truthy end end end diff --git a/spec/requests/api/graphql/group/dependency_proxy_blobs_spec.rb b/spec/requests/api/graphql/group/dependency_proxy_blobs_spec.rb new file mode 100644 index 00000000000..cdb21512894 --- /dev/null +++ b/spec/requests/api/graphql/group/dependency_proxy_blobs_spec.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe 'getting dependency proxy blobs in a group' do + using RSpec::Parameterized::TableSyntax + include GraphqlHelpers + + let_it_be(:owner) { create(:user) } + let_it_be_with_reload(:group) { create(:group) } + let_it_be(:blob) { create(:dependency_proxy_blob, group: group) } + let_it_be(:blob2) { create(:dependency_proxy_blob, file_name: 'blob2.json', group: group) } + let_it_be(:blobs) { [blob, blob2].flatten } + + let(:dependency_proxy_blob_fields) do + <<~GQL + edges { + node { + #{all_graphql_fields_for('dependency_proxy_blobs'.classify, max_depth: 1)} + } + } + GQL + end + + let(:fields) do + <<~GQL + #{query_graphql_field('dependency_proxy_blobs', {}, dependency_proxy_blob_fields)} + dependencyProxyBlobCount + dependencyProxyTotalSize + GQL + end + + let(:query) do + graphql_query_for( + 'group', + { 'fullPath' => group.full_path }, + fields + ) + end + + let(:user) { owner } + let(:variables) { {} } + let(:dependency_proxy_blobs_response) { graphql_data.dig('group', 'dependencyProxyBlobs', 'edges') } + let(:dependency_proxy_blob_count_response) { graphql_data.dig('group', 'dependencyProxyBlobCount') } + let(:dependency_proxy_total_size_response) { graphql_data.dig('group', 'dependencyProxyTotalSize') } + + before do + stub_config(dependency_proxy: { enabled: true }) + group.add_owner(owner) + end + + subject { post_graphql(query, current_user: user, variables: variables) } + + it_behaves_like 'a working graphql query' do + before do + subject + end + end + + context 'with different permissions' do + let_it_be(:user) { create(:user) } + + where(:group_visibility, :role, :access_granted) do + :private | :maintainer | true + :private | :developer | true + :private | :reporter | true + :private | :guest | true + :private | :anonymous | false + :public | :maintainer | true + :public | :developer | true + :public | :reporter | true + :public | :guest | true + :public | :anonymous | false + end + + with_them do + before do + group.update_column(:visibility_level, Gitlab::VisibilityLevel.const_get(group_visibility.to_s.upcase, false)) + group.add_user(user, role) unless role == :anonymous + end + + it 'return the proper response' do + subject + + if access_granted + expect(dependency_proxy_blobs_response.size).to eq(blobs.size) + else + expect(dependency_proxy_blobs_response).to be_blank + end + end + end + end + + context 'limiting the number of blobs' do + let(:limit) { 1 } + let(:variables) do + { path: group.full_path, n: limit } + end + + let(:query) do + <<~GQL + query($path: ID!, $n: Int) { + group(fullPath: $path) { + dependencyProxyBlobs(first: $n) { #{dependency_proxy_blob_fields} } + } + } + GQL + end + + it 'only returns N blobs' do + subject + + expect(dependency_proxy_blobs_response.size).to eq(limit) + end + end + + it 'returns the total count of blobs' do + subject + + expect(dependency_proxy_blob_count_response).to eq(blobs.size) + end + + it 'returns the total size' do + subject + expected_size = blobs.inject(0) { |sum, blob| sum + blob.size } + expect(dependency_proxy_total_size_response).to eq(ActiveSupport::NumberHelper.number_to_human_size(expected_size)) + end +end diff --git a/spec/requests/api/graphql/group/dependency_proxy_group_setting_spec.rb b/spec/requests/api/graphql/group/dependency_proxy_group_setting_spec.rb new file mode 100644 index 00000000000..c5c6d85d1e6 --- /dev/null +++ b/spec/requests/api/graphql/group/dependency_proxy_group_setting_spec.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe 'getting dependency proxy settings for a group' do + using RSpec::Parameterized::TableSyntax + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be_with_reload(:group) { create(:group) } + + let(:dependency_proxy_group_setting_fields) do + <<~GQL + #{all_graphql_fields_for('dependency_proxy_setting'.classify, max_depth: 1)} + GQL + end + + let(:fields) do + <<~GQL + #{query_graphql_field('dependency_proxy_setting', {}, dependency_proxy_group_setting_fields)} + GQL + end + + let(:query) do + graphql_query_for( + 'group', + { 'fullPath' => group.full_path }, + fields + ) + end + + let(:variables) { {} } + let(:dependency_proxy_group_setting_response) { graphql_data.dig('group', 'dependencyProxySetting') } + + before do + stub_config(dependency_proxy: { enabled: true }) + group.create_dependency_proxy_setting!(enabled: true) + end + + subject { post_graphql(query, current_user: user, variables: variables) } + + it_behaves_like 'a working graphql query' do + before do + subject + end + end + + context 'with different permissions' do + where(:group_visibility, :role, :access_granted) do + :private | :maintainer | true + :private | :developer | true + :private | :reporter | true + :private | :guest | true + :private | :anonymous | false + :public | :maintainer | true + :public | :developer | true + :public | :reporter | true + :public | :guest | true + :public | :anonymous | false + end + + with_them do + before do + group.update_column(:visibility_level, Gitlab::VisibilityLevel.const_get(group_visibility.to_s.upcase, false)) + group.add_user(user, role) unless role == :anonymous + end + + it 'return the proper response' do + subject + + if access_granted + expect(dependency_proxy_group_setting_response).to eq('enabled' => true) + else + expect(dependency_proxy_group_setting_response).to be_blank + end + end + end + end +end diff --git a/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb b/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb new file mode 100644 index 00000000000..30e704adb92 --- /dev/null +++ b/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe 'getting dependency proxy manifests in a group' do + using RSpec::Parameterized::TableSyntax + include GraphqlHelpers + + let_it_be(:owner) { create(:user) } + let_it_be_with_reload(:group) { create(:group) } + let_it_be(:manifest) { create(:dependency_proxy_manifest, group: group) } + let_it_be(:manifest2) { create(:dependency_proxy_manifest, file_name: 'image2.json', group: group) } + let_it_be(:manifests) { [manifest, manifest2].flatten } + + let(:dependency_proxy_manifest_fields) do + <<~GQL + edges { + node { + #{all_graphql_fields_for('dependency_proxy_manifests'.classify, max_depth: 1)} + } + } + GQL + end + + let(:fields) do + <<~GQL + #{query_graphql_field('dependency_proxy_manifests', {}, dependency_proxy_manifest_fields)} + dependencyProxyImageCount + GQL + end + + let(:query) do + graphql_query_for( + 'group', + { 'fullPath' => group.full_path }, + fields + ) + end + + let(:user) { owner } + let(:variables) { {} } + let(:dependency_proxy_manifests_response) { graphql_data.dig('group', 'dependencyProxyManifests', 'edges') } + let(:dependency_proxy_image_count_response) { graphql_data.dig('group', 'dependencyProxyImageCount') } + + before do + stub_config(dependency_proxy: { enabled: true }) + group.add_owner(owner) + end + + subject { post_graphql(query, current_user: user, variables: variables) } + + it_behaves_like 'a working graphql query' do + before do + subject + end + end + + context 'with different permissions' do + let_it_be(:user) { create(:user) } + + where(:group_visibility, :role, :access_granted) do + :private | :maintainer | true + :private | :developer | true + :private | :reporter | true + :private | :guest | true + :private | :anonymous | false + :public | :maintainer | true + :public | :developer | true + :public | :reporter | true + :public | :guest | true + :public | :anonymous | false + end + + with_them do + before do + group.update_column(:visibility_level, Gitlab::VisibilityLevel.const_get(group_visibility.to_s.upcase, false)) + group.add_user(user, role) unless role == :anonymous + end + + it 'return the proper response' do + subject + + if access_granted + expect(dependency_proxy_manifests_response.size).to eq(manifests.size) + else + expect(dependency_proxy_manifests_response).to be_blank + end + end + end + end + + context 'limiting the number of manifests' do + let(:limit) { 1 } + let(:variables) do + { path: group.full_path, n: limit } + end + + let(:query) do + <<~GQL + query($path: ID!, $n: Int) { + group(fullPath: $path) { + dependencyProxyManifests(first: $n) { #{dependency_proxy_manifest_fields} } + } + } + GQL + end + + it 'only returns N manifests' do + subject + + expect(dependency_proxy_manifests_response.size).to eq(limit) + end + end + + it 'returns the total count of manifests' do + subject + + expect(dependency_proxy_image_count_response).to eq(manifests.size) + end +end diff --git a/spec/rubocop/cop/migration/prevent_index_creation_spec.rb b/spec/rubocop/cop/migration/prevent_index_creation_spec.rb index a3965f54bbd..aa1779a8cd5 100644 --- a/spec/rubocop/cop/migration/prevent_index_creation_spec.rb +++ b/spec/rubocop/cop/migration/prevent_index_creation_spec.rb @@ -6,28 +6,35 @@ require_relative '../../../../rubocop/cop/migration/prevent_index_creation' RSpec.describe RuboCop::Cop::Migration::PreventIndexCreation do subject(:cop) { described_class.new } + let(:forbidden_tables) { %w(ci_builds taggings ci_builds_metadata events) } + let(:forbidden_tables_list) { forbidden_tables.join(', ') } + context 'when in migration' do before do allow(cop).to receive(:in_migration?).and_return(true) end context 'when adding an index to a forbidden table' do - it 'registers an offense when add_index is used' do - expect_offense(<<~RUBY) - def change - add_index :ci_builds, :protected - ^^^^^^^^^ Adding new index to ci_builds is forbidden, see https://gitlab.com/gitlab-org/gitlab/-/issues/332886 - end - RUBY + it "registers an offense when add_index is used", :aggregate_failures do + forbidden_tables.each do |table| + expect_offense(<<~RUBY) + def change + add_index :#{table}, :protected + ^^^^^^^^^ Adding new index to #{forbidden_tables_list} is forbidden, see https://gitlab.com/gitlab-org/gitlab/-/issues/332886 + end + RUBY + end end - it 'registers an offense when add_concurrent_index is used' do - expect_offense(<<~RUBY) - def change - add_concurrent_index :ci_builds, :protected - ^^^^^^^^^^^^^^^^^^^^ Adding new index to ci_builds is forbidden, see https://gitlab.com/gitlab-org/gitlab/-/issues/332886 - end - RUBY + it "registers an offense when add_concurrent_index is used", :aggregate_failures do + forbidden_tables.each do |table| + expect_offense(<<~RUBY) + def change + add_concurrent_index :#{table}, :protected + ^^^^^^^^^^^^^^^^^^^^ Adding new index to #{forbidden_tables_list} is forbidden, see https://gitlab.com/gitlab-org/gitlab/-/issues/332886 + end + RUBY + end end end diff --git a/spec/services/ci/pipeline_processing/shared_processing_service.rb b/spec/services/ci/pipeline_processing/shared_processing_service.rb index a4bc8e68b2d..8de9b308429 100644 --- a/spec/services/ci/pipeline_processing/shared_processing_service.rb +++ b/spec/services/ci/pipeline_processing/shared_processing_service.rb @@ -908,6 +908,39 @@ RSpec.shared_examples 'Pipeline Processing Service' do end end + context 'when a bridge job has invalid downstream project', :sidekiq_inline do + let(:config) do + <<-EOY + test: + stage: test + script: echo test + + deploy: + stage: deploy + trigger: + project: invalid-project + EOY + end + + let(:pipeline) do + Ci::CreatePipelineService.new(project, user, { ref: 'master' }).execute(:push).payload + end + + before do + stub_ci_pipeline_yaml_file(config) + end + + it 'creates a pipeline, then fails the bridge job' do + expect(all_builds_names).to contain_exactly('test', 'deploy') + expect(all_builds_statuses).to contain_exactly('pending', 'created') + + succeed_pending + + expect(all_builds_names).to contain_exactly('test', 'deploy') + expect(all_builds_statuses).to contain_exactly('success', 'failed') + end + end + private def all_builds diff --git a/spec/support/helpers/reference_parser_helpers.rb b/spec/support/helpers/reference_parser_helpers.rb index a6a7948d9d9..b9796ebbe62 100644 --- a/spec/support/helpers/reference_parser_helpers.rb +++ b/spec/support/helpers/reference_parser_helpers.rb @@ -5,9 +5,10 @@ module ReferenceParserHelpers Nokogiri::HTML.fragment('').children[0] end - def expect_gathered_references(result, visible, not_visible_count) + def expect_gathered_references(result, visible, nodes, visible_nodes) expect(result[:visible]).to eq(visible) - expect(result[:not_visible].count).to eq(not_visible_count) + expect(result[:nodes]).to eq(nodes) + expect(result[:visible_nodes]).to eq(visible_nodes) end RSpec.shared_examples 'no project N+1 queries' do diff --git a/spec/tasks/gitlab/product_intelligence_rake_spec.rb b/spec/tasks/gitlab/product_intelligence_rake_spec.rb deleted file mode 100644 index 029e181ad06..00000000000 --- a/spec/tasks/gitlab/product_intelligence_rake_spec.rb +++ /dev/null @@ -1,80 +0,0 @@ -# frozen_string_literal: true - -require 'rake_helper' - -RSpec.describe 'gitlab:product_intelligence:activate_metrics', :silence_stdout do - def fake_metric(key_path, milestone: 'test_milestone', status: 'implemented') - Gitlab::Usage::MetricDefinition.new(key_path, { key_path: key_path, milestone: milestone, status: status }) - end - - before do - Rake.application.rake_require 'tasks/gitlab/product_intelligence' - stub_warn_user_is_not_gitlab - end - - describe 'activate_metrics' do - it 'fails if the MILESTONE env var is not set' do - stub_env('MILESTONE' => nil) - - expect { run_rake_task('gitlab:product_intelligence:activate_metrics') }.to raise_error(RuntimeError, 'Please supply the MILESTONE env var') - end - - context 'with MILESTONE env var' do - subject do - updated_metrics = [] - - file = double('file') - allow(file).to receive(:<<) { |contents| updated_metrics << YAML.safe_load(contents) } - allow(File).to receive(:open).and_yield(file) - - stub_env('MILESTONE' => 'test_milestone') - run_rake_task('gitlab:product_intelligence:activate_metrics') - - updated_metrics - end - - let(:metric_definitions) do - { - matching_metric: fake_metric('matching_metric'), - matching_metric2: fake_metric('matching_metric2'), - other_status_metric: fake_metric('other_status_metric', status: 'deprecated'), - other_milestone_metric: fake_metric('other_milestone_metric', milestone: 'other_milestone') - } - end - - before do - allow(Gitlab::Usage::MetricDefinition).to receive(:definitions).and_return(metric_definitions) - end - - context 'with metric matching status and milestone' do - it 'updates matching_metric yaml file' do - expect(subject).to eq([ - { 'key_path' => 'matching_metric', 'milestone' => 'test_milestone', 'status' => 'data_available' }, - { 'key_path' => 'matching_metric2', 'milestone' => 'test_milestone', 'status' => 'data_available' } - ]) - end - end - - context 'without metrics definitions' do - let(:metric_definitions) { {} } - - it 'runs successfully with no updates' do - expect(subject).to eq([]) - end - end - - context 'without matching metrics' do - let(:metric_definitions) do - { - other_status_metric: fake_metric('other_status_metric', status: 'deprecated'), - other_milestone_metric: fake_metric('other_milestone_metric', milestone: 'other_milestone') - } - end - - it 'runs successfully with no updates' do - expect(subject).to eq([]) - end - end - end - end -end