Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
d3fafe0995
commit
a56971e97f
|
@ -23,6 +23,7 @@ query getFiles(
|
|||
$nextPageCursor: String
|
||||
) {
|
||||
project(fullPath: $projectPath) {
|
||||
id
|
||||
__typename
|
||||
repository {
|
||||
__typename
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) {
|
||||
project(fullPath: $projectPath) {
|
||||
id
|
||||
__typename
|
||||
repository {
|
||||
__typename
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
query getPermissions($projectPath: ID!) {
|
||||
project(fullPath: $projectPath) {
|
||||
id
|
||||
__typename
|
||||
userPermissions {
|
||||
__typename
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
module DependencyProxy
|
||||
class BlobPolicy < BasePolicy
|
||||
delegate { @subject.group }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
module DependencyProxy
|
||||
class GroupSettingPolicy < BasePolicy
|
||||
delegate { @subject.group }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
module DependencyProxy
|
||||
class ManifestPolicy < BasePolicy
|
||||
delegate { @subject.group }
|
||||
end
|
||||
end
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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"],
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
830cf08352b0d1f0c7f08ea67107466ea1d6a478c6f47d5e19f0ffa6c57f5641
|
|
@ -5408,6 +5408,52 @@ The edge type for [`DastSiteValidation`](#dastsitevalidation).
|
|||
| <a id="dastsitevalidationedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="dastsitevalidationedgenode"></a>`node` | [`DastSiteValidation`](#dastsitevalidation) | The item at the end of the edge. |
|
||||
|
||||
#### `DependencyProxyBlobConnection`
|
||||
|
||||
The connection type for [`DependencyProxyBlob`](#dependencyproxyblob).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="dependencyproxyblobconnectionedges"></a>`edges` | [`[DependencyProxyBlobEdge]`](#dependencyproxyblobedge) | A list of edges. |
|
||||
| <a id="dependencyproxyblobconnectionnodes"></a>`nodes` | [`[DependencyProxyBlob]`](#dependencyproxyblob) | A list of nodes. |
|
||||
| <a id="dependencyproxyblobconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||
|
||||
#### `DependencyProxyBlobEdge`
|
||||
|
||||
The edge type for [`DependencyProxyBlob`](#dependencyproxyblob).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="dependencyproxyblobedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="dependencyproxyblobedgenode"></a>`node` | [`DependencyProxyBlob`](#dependencyproxyblob) | The item at the end of the edge. |
|
||||
|
||||
#### `DependencyProxyManifestConnection`
|
||||
|
||||
The connection type for [`DependencyProxyManifest`](#dependencyproxymanifest).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="dependencyproxymanifestconnectionedges"></a>`edges` | [`[DependencyProxyManifestEdge]`](#dependencyproxymanifestedge) | A list of edges. |
|
||||
| <a id="dependencyproxymanifestconnectionnodes"></a>`nodes` | [`[DependencyProxyManifest]`](#dependencyproxymanifest) | A list of nodes. |
|
||||
| <a id="dependencyproxymanifestconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||
|
||||
#### `DependencyProxyManifestEdge`
|
||||
|
||||
The edge type for [`DependencyProxyManifest`](#dependencyproxymanifest).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="dependencyproxymanifestedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="dependencyproxymanifestedgenode"></a>`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.
|
|||
| <a id="deletejobsresponsedeletedjobs"></a>`deletedJobs` | [`Int`](#int) | Number of matching jobs deleted. |
|
||||
| <a id="deletejobsresponsequeuesize"></a>`queueSize` | [`Int`](#int) | Queue size after processing. |
|
||||
|
||||
### `DependencyProxyBlob`
|
||||
|
||||
Dependency proxy blob.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="dependencyproxyblobcreatedat"></a>`createdAt` | [`Time!`](#time) | Date of creation. |
|
||||
| <a id="dependencyproxyblobfilename"></a>`fileName` | [`String!`](#string) | Name of the blob. |
|
||||
| <a id="dependencyproxyblobsize"></a>`size` | [`String!`](#string) | Size of the blob file. |
|
||||
| <a id="dependencyproxyblobupdatedat"></a>`updatedAt` | [`Time!`](#time) | Date of most recent update. |
|
||||
|
||||
### `DependencyProxyManifest`
|
||||
|
||||
Dependency proxy manifest.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="dependencyproxymanifestcreatedat"></a>`createdAt` | [`Time!`](#time) | Date of creation. |
|
||||
| <a id="dependencyproxymanifestdigest"></a>`digest` | [`String!`](#string) | Digest of the manifest. |
|
||||
| <a id="dependencyproxymanifestfilename"></a>`fileName` | [`String!`](#string) | Name of the manifest. |
|
||||
| <a id="dependencyproxymanifestimagename"></a>`imageName` | [`String!`](#string) | Name of the image. |
|
||||
| <a id="dependencyproxymanifestsize"></a>`size` | [`String!`](#string) | Size of the manifest file. |
|
||||
| <a id="dependencyproxymanifestupdatedat"></a>`updatedAt` | [`Time!`](#time) | Date of most recent update. |
|
||||
|
||||
### `DependencyProxySetting`
|
||||
|
||||
Group-level Dependency Proxy settings.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="dependencyproxysettingenabled"></a>`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):
|
|||
| <a id="groupcontainerrepositoriescount"></a>`containerRepositoriesCount` | [`Int!`](#int) | Number of container repositories in the group. |
|
||||
| <a id="groupcontainslockedprojects"></a>`containsLockedProjects` | [`Boolean!`](#boolean) | Includes at least one project where the repository size exceeds the limit. |
|
||||
| <a id="groupcustomemoji"></a>`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)) |
|
||||
| <a id="groupdependencyproxyblobcount"></a>`dependencyProxyBlobCount` | [`Int!`](#int) | Number of dependency proxy blobs cached in the group. |
|
||||
| <a id="groupdependencyproxyblobs"></a>`dependencyProxyBlobs` | [`DependencyProxyBlobConnection`](#dependencyproxyblobconnection) | Dependency Proxy blobs. (see [Connections](#connections)) |
|
||||
| <a id="groupdependencyproxyimagecount"></a>`dependencyProxyImageCount` | [`Int!`](#int) | Number of dependency proxy images cached in the group. |
|
||||
| <a id="groupdependencyproxymanifests"></a>`dependencyProxyManifests` | [`DependencyProxyManifestConnection`](#dependencyproxymanifestconnection) | Dependency Proxy manifests. (see [Connections](#connections)) |
|
||||
| <a id="groupdependencyproxysetting"></a>`dependencyProxySetting` | [`DependencyProxySetting`](#dependencyproxysetting) | Dependency Proxy settings for the group. |
|
||||
| <a id="groupdependencyproxytotalsize"></a>`dependencyProxyTotalSize` | [`String!`](#string) | Total size of the dependency proxy cached images. |
|
||||
| <a id="groupdescription"></a>`description` | [`String`](#string) | Description of the namespace. |
|
||||
| <a id="groupdescriptionhtml"></a>`descriptionHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `description`. |
|
||||
| <a id="groupdora"></a>`dora` | [`Dora`](#dora) | The group's DORA metrics. |
|
||||
|
|
|
@ -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":"<example-name>", "description":"<example-description"}' "https://gitlab/api/v4/projects"
|
||||
--data '{"name":"<example-name>", "description":"<example-description>"}' "https://gitlab/api/v4/projects"
|
||||
```
|
||||
|
||||
URL encoded query strings have a length limitation. Requests that are too large
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
---
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -5888,6 +5888,9 @@ msgstr ""
|
|||
msgid "Buy CI Minutes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Buy Storage"
|
||||
msgstr ""
|
||||
|
||||
msgid "Buy more Pipeline minutes"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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' }
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,9 +5,10 @@ module ReferenceParserHelpers
|
|||
Nokogiri::HTML.fragment('<a></a>').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
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue