diff --git a/app/graphql/mutations/ci/pipeline_schedule/base.rb b/app/graphql/mutations/ci/pipeline_schedule/base.rb
new file mode 100644
index 00000000000..a737ccce575
--- /dev/null
+++ b/app/graphql/mutations/ci/pipeline_schedule/base.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Ci
+ module PipelineSchedule
+ class Base < BaseMutation
+ PipelineScheduleID = ::Types::GlobalIDType[::Ci::PipelineSchedule]
+
+ argument :id, PipelineScheduleID,
+ required: true,
+ description: 'ID of the pipeline schedule to mutate.'
+
+ private
+
+ def find_object(id:)
+ GlobalID::Locator.locate(id)
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/ci/pipeline_schedule/delete.rb b/app/graphql/mutations/ci/pipeline_schedule/delete.rb
new file mode 100644
index 00000000000..ead9a43161d
--- /dev/null
+++ b/app/graphql/mutations/ci/pipeline_schedule/delete.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Ci
+ module PipelineSchedule
+ class Delete < Base
+ graphql_name 'PipelineScheduleDelete'
+
+ authorize :admin_pipeline_schedule
+
+ def resolve(id:)
+ schedule = authorized_find!(id: id)
+
+ if schedule.destroy
+ {
+ errors: []
+ }
+ else
+ {
+ errors: ['Failed to remove the pipeline schedule']
+ }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index 304bb278afb..109152a2f8a 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -114,6 +114,7 @@ module Types
mount_mutation Mutations::Ci::Pipeline::Cancel
mount_mutation Mutations::Ci::Pipeline::Destroy
mount_mutation Mutations::Ci::Pipeline::Retry
+ mount_mutation Mutations::Ci::PipelineSchedule::Delete
mount_mutation Mutations::Ci::CiCdSettingsUpdate, deprecated: {
reason: :renamed,
replacement: 'ProjectCiCdSettingsUpdate',
diff --git a/app/models/user.rb b/app/models/user.rb
index 9ddb19b56df..bb8d5d24b3b 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1738,7 +1738,7 @@ class User < ApplicationRecord
end
def authorized_project_mirrors(level)
- projects = Ci::ProjectMirror.by_project_id(ci_project_mirrors_for_project_members(level))
+ projects = Ci::ProjectMirror.by_project_id(ci_project_ids_for_project_members(level))
namespace_projects = Ci::ProjectMirror.by_namespace_id(ci_namespace_mirrors_for_group_members(level).select(:namespace_id))
@@ -2210,7 +2210,7 @@ class User < ApplicationRecord
end
# rubocop: enable CodeReuse/ServiceClass
- def ci_project_mirrors_for_project_members(level)
+ def ci_project_ids_for_project_members(level)
project_members.where('access_level >= ?', level).pluck(:source_id)
end
@@ -2364,7 +2364,7 @@ class User < ApplicationRecord
end
def ci_owned_project_runners_from_project_members
- project_ids = ci_project_mirrors_for_project_members(Gitlab::Access::MAINTAINER)
+ project_ids = ci_project_ids_for_project_members(Gitlab::Access::MAINTAINER)
Ci::Runner
.joins(:runner_projects)
diff --git a/app/workers/gitlab/github_import/stage/import_repository_worker.rb b/app/workers/gitlab/github_import/stage/import_repository_worker.rb
index a0e420b4a41..3e914cc7590 100644
--- a/app/workers/gitlab/github_import/stage/import_repository_worker.rb
+++ b/app/workers/gitlab/github_import/stage/import_repository_worker.rb
@@ -26,7 +26,6 @@ module Gitlab
RefreshImportJidWorker.perform_in_the_future(project.id, jid)
info(project.id, message: "starting importer", importer: 'Importer::RepositoryImporter')
-
importer = Importer::RepositoryImporter.new(project, client)
importer.execute
diff --git a/app/workers/merge_requests/delete_source_branch_worker.rb b/app/workers/merge_requests/delete_source_branch_worker.rb
index 69bd3949e9d..66392c670b5 100644
--- a/app/workers/merge_requests/delete_source_branch_worker.rb
+++ b/app/workers/merge_requests/delete_source_branch_worker.rb
@@ -18,9 +18,13 @@ class MergeRequests::DeleteSourceBranchWorker
# Source branch changed while it's being removed
return if merge_request.source_branch_sha != source_branch_sha
- ::Branches::DeleteService.new(merge_request.source_project, user)
+ delete_service_result = ::Branches::DeleteService.new(merge_request.source_project, user)
.execute(merge_request.source_branch)
+ if Feature.enabled?(:track_delete_source_errors, merge_request.source_project)
+ delete_service_result.track_exception if delete_service_result&.error?
+ end
+
::MergeRequests::RetargetChainService.new(project: merge_request.source_project, current_user: user)
.execute(merge_request)
rescue ActiveRecord::RecordNotFound
diff --git a/config/feature_flags/development/track_delete_source_errors.yml b/config/feature_flags/development/track_delete_source_errors.yml
new file mode 100644
index 00000000000..57152ed86cd
--- /dev/null
+++ b/config/feature_flags/development/track_delete_source_errors.yml
@@ -0,0 +1,8 @@
+---
+name: track_delete_source_errors
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/99028
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/377258
+milestone: '15.5'
+type: development
+group: group::code review
+default_enabled: false
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 337c5c077f8..bd9905ca652 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -4158,6 +4158,24 @@ Input type: `PipelineRetryInput`
| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| `pipeline` | [`Pipeline`](#pipeline) | Pipeline after mutation. |
+### `Mutation.pipelineScheduleDelete`
+
+Input type: `PipelineScheduleDeleteInput`
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| `id` | [`CiPipelineScheduleID!`](#cipipelinescheduleid) | ID of the pipeline schedule to mutate. |
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
+
### `Mutation.projectCiCdSettingsUpdate`
Input type: `ProjectCiCdSettingsUpdateInput`
@@ -21810,6 +21828,12 @@ A `CiPipelineID` is a global ID. It is encoded as a string.
An example `CiPipelineID` is: `"gid://gitlab/Ci::Pipeline/1"`.
+### `CiPipelineScheduleID`
+
+A `CiPipelineScheduleID` is a global ID. It is encoded as a string.
+
+An example `CiPipelineScheduleID` is: `"gid://gitlab/Ci::PipelineSchedule/1"`.
+
### `CiRunnerID`
A `CiRunnerID` is a global ID. It is encoded as a string.
diff --git a/doc/user/group/manage.md b/doc/user/group/manage.md
index 8a6cb42f247..72dc6f30e79 100644
--- a/doc/user/group/manage.md
+++ b/doc/user/group/manage.md
@@ -158,6 +158,10 @@ You can sort members by **Account**, **Access granted**, **Max role**, or **Last
You can give a user access to all projects in a group.
+Prerequisite:
+
+- You must have the Owner role.
+
1. On the top bar, select **Main menu > Groups** and find your group.
1. On the left sidebar, select **Group information > Members**.
1. Select **Invite members**.
diff --git a/lib/gitlab/github_import/importer/issue_importer.rb b/lib/gitlab/github_import/importer/issue_importer.rb
index 896106a6f1f..d964bae3dd2 100644
--- a/lib/gitlab/github_import/importer/issue_importer.rb
+++ b/lib/gitlab/github_import/importer/issue_importer.rb
@@ -60,10 +60,6 @@ module Gitlab
work_item_type_id: issue.work_item_type_id
}
- Issue.with_project_iid_supply(project) do |supply|
- attributes[:iid] = supply.next_value
- end
-
insert_and_return_id(attributes, project.issues)
rescue ActiveRecord::InvalidForeignKey
# It's possible the project has been deleted since scheduling this
diff --git a/lib/gitlab/jira_import/issues_importer.rb b/lib/gitlab/jira_import/issues_importer.rb
index e41c5331c68..5057317ae01 100644
--- a/lib/gitlab/jira_import/issues_importer.rb
+++ b/lib/gitlab/jira_import/issues_importer.rb
@@ -48,7 +48,7 @@ module Gitlab
end
def schedule_issue_import_workers(issues)
- next_iid = project.issues.maximum(:iid).to_i + 1
+ next_iid = Issue.with_project_iid_supply(project, &:next_value)
issues.each do |jira_issue|
# Technically it's possible that the same work is performed multiple
@@ -70,7 +70,8 @@ module Gitlab
Gitlab::JiraImport::ImportIssueWorker.perform_async(project.id, jira_issue.id, issue_attrs, job_waiter.key)
job_waiter.jobs_remaining += 1
- next_iid += 1
+
+ next_iid = Issue.with_project_iid_supply(project, &:next_value)
# Mark the issue as imported immediately so we don't end up
# importing it multiple times within same import.
diff --git a/lib/gitlab/usage_data_counters/hll_redis_counter.rb b/lib/gitlab/usage_data_counters/hll_redis_counter.rb
index 56b6832f702..24a87ae01f4 100644
--- a/lib/gitlab/usage_data_counters/hll_redis_counter.rb
+++ b/lib/gitlab/usage_data_counters/hll_redis_counter.rb
@@ -19,7 +19,6 @@ module Gitlab
ALLOWED_AGGREGATIONS = %i(daily weekly).freeze
CATEGORIES_FOR_TOTALS = %w[
- analytics
compliance
error_tracking
ide_edit
@@ -27,6 +26,7 @@ module Gitlab
].freeze
CATEGORIES_COLLECTED_FROM_METRICS_DEFINITIONS = %w[
+ analytics
ci_users
deploy_token_packages
code_review
diff --git a/lib/gitlab/usage_data_counters/known_events/analytics.yml b/lib/gitlab/usage_data_counters/known_events/analytics.yml
index 76c97a974d7..85524c766ca 100644
--- a/lib/gitlab/usage_data_counters/known_events/analytics.yml
+++ b/lib/gitlab/usage_data_counters/known_events/analytics.yml
@@ -10,54 +10,18 @@
category: analytics
redis_slot: analytics
aggregation: weekly
-- name: p_analytics_merge_request
- category: analytics
- redis_slot: analytics
- aggregation: weekly
- name: i_analytics_instance_statistics
category: analytics
redis_slot: analytics
aggregation: weekly
-- name: g_analytics_contribution
- category: analytics
- redis_slot: analytics
- aggregation: weekly
-- name: g_analytics_insights
- category: analytics
- redis_slot: analytics
- aggregation: weekly
-- name: g_analytics_issues
- category: analytics
- redis_slot: analytics
- aggregation: weekly
-- name: g_analytics_productivity
- category: analytics
- redis_slot: analytics
- aggregation: weekly
-- name: g_analytics_valuestream
- category: analytics
- redis_slot: analytics
- aggregation: weekly
- name: p_analytics_pipelines
category: analytics
redis_slot: analytics
aggregation: weekly
-- name: p_analytics_code_reviews
- category: analytics
- redis_slot: analytics
- aggregation: weekly
- name: p_analytics_valuestream
category: analytics
redis_slot: analytics
aggregation: weekly
-- name: p_analytics_insights
- category: analytics
- redis_slot: analytics
- aggregation: weekly
-- name: p_analytics_issues
- category: analytics
- redis_slot: analytics
- aggregation: weekly
- name: p_analytics_repo
category: analytics
redis_slot: analytics
@@ -86,23 +50,3 @@
category: analytics
redis_slot: analytics
aggregation: weekly
-- name: g_analytics_ci_cd_release_statistics
- category: analytics
- redis_slot: analytics
- aggregation: weekly
-- name: g_analytics_ci_cd_deployment_frequency
- category: analytics
- redis_slot: analytics
- aggregation: weekly
-- name: g_analytics_ci_cd_lead_time
- category: analytics
- redis_slot: analytics
- aggregation: weekly
-- name: g_analytics_ci_cd_time_to_restore_service
- category: analytics
- redis_slot: analytics
- aggregation: weekly
-- name: g_analytics_ci_cd_change_failure_rate
- category: analytics
- redis_slot: analytics
- aggregation: weekly
diff --git a/scripts/lib/glfm/render_static_html.rb b/scripts/lib/glfm/render_static_html.rb
index 949eab72537..040c62c42f2 100644
--- a/scripts/lib/glfm/render_static_html.rb
+++ b/scripts/lib/glfm/render_static_html.rb
@@ -20,7 +20,7 @@ require_relative 'shared'
# Factorybot factory methods to create persisted model objects with stable
# and consistent data values, to ensure consistent example snapshot HTML
# across various machines and environments. RSpec also makes it easy to invoke
-# the API # and obtain the response.
+# the API and obtain the response.
#
# It is intended to be invoked as a helper subprocess from the `update_example_snapshots.rb`
# script class. It's not intended to be run or used directly. This usage is also reinforced
@@ -32,7 +32,7 @@ RSpec.describe 'Render Static HTML', :api, type: :request do # rubocop:disable R
# noinspection RailsParamDefResolve (RubyMine can't find the shared context from this file location)
include_context 'with GLFM example snapshot fixtures'
- it 'can create a project dependency graph using factories' do
+ it do
markdown_hash = YAML.safe_load(File.open(ENV.fetch('INPUT_MARKDOWN_YML_PATH')), symbolize_names: true)
metadata_hash = YAML.safe_load(File.open(ENV.fetch('INPUT_METADATA_YML_PATH')), symbolize_names: true) || {}
diff --git a/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
index 5d27a64bda0..1692aac49f2 100644
--- a/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
@@ -141,7 +141,7 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redi
.to receive(:insert_and_return_id)
.with(
{
- iid: 1,
+ iid: 42,
title: 'My Issue',
author_id: user.id,
project_id: project.id,
@@ -172,7 +172,7 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redi
.to receive(:insert_and_return_id)
.with(
{
- iid: 1,
+ iid: 42,
title: 'My Issue',
author_id: project.creator_id,
project_id: project.id,
diff --git a/spec/lib/gitlab/jira_import/issues_importer_spec.rb b/spec/lib/gitlab/jira_import/issues_importer_spec.rb
index 1bc052ee0b6..9f654bbcd15 100644
--- a/spec/lib/gitlab/jira_import/issues_importer_spec.rb
+++ b/spec/lib/gitlab/jira_import/issues_importer_spec.rb
@@ -44,7 +44,7 @@ RSpec.describe Gitlab::JiraImport::IssuesImporter do
def mock_issue_serializer(count, raise_exception_on_even_mocks: false)
serializer = instance_double(Gitlab::JiraImport::IssueSerializer, execute: { key: 'data' })
- next_iid = project.issues.maximum(:iid).to_i
+ allow(Issue).to receive(:with_project_iid_supply).and_return('issue_iid')
count.times do |i|
if raise_exception_on_even_mocks && i.even?
@@ -53,16 +53,15 @@ RSpec.describe Gitlab::JiraImport::IssuesImporter do
jira_issues[i],
current_user.id,
default_issue_type_id,
- { iid: next_iid + 1 }
+ { iid: 'issue_iid' }
).and_raise('Some error')
else
- next_iid += 1
expect(Gitlab::JiraImport::IssueSerializer).to receive(:new).with(
project,
jira_issues[i],
current_user.id,
default_issue_type_id,
- { iid: next_iid }
+ { iid: 'issue_iid' }
).and_return(serializer)
end
end
diff --git a/spec/requests/api/graphql/mutations/ci/pipeline_schedule_delete_spec.rb b/spec/requests/api/graphql/mutations/ci/pipeline_schedule_delete_spec.rb
new file mode 100644
index 00000000000..b197d223463
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/ci/pipeline_schedule_delete_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'PipelineScheduleDelete' do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: user) }
+
+ let(:mutation) do
+ graphql_mutation(
+ :pipeline_schedule_delete,
+ { id: pipeline_schedule_id },
+ <<-QL
+ errors
+ QL
+ )
+ end
+
+ let(:pipeline_schedule_id) { pipeline_schedule.to_global_id.to_s }
+ let(:mutation_response) { graphql_mutation_response(:pipeline_schedule_delete) }
+
+ context 'when unauthorized' do
+ it 'returns an error' do
+ post_graphql_mutation(mutation, current_user: create(:user))
+
+ expect(graphql_errors).not_to be_empty
+ expect(graphql_errors[0]['message'])
+ .to eq(
+ "The resource that you are attempting to access does not exist " \
+ "or you don't have permission to perform this action"
+ )
+ end
+ end
+
+ context 'when authorized' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ context 'when success' do
+ it do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['errors']).to eq([])
+ end
+ end
+
+ context 'when failure' do
+ context 'when destroy fails' do
+ before do
+ allow_next_found_instance_of(Ci::PipelineSchedule) do |pipeline_schedule|
+ allow(pipeline_schedule).to receive(:destroy).and_return(false)
+ end
+ end
+
+ it do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(response).to have_gitlab_http_status(:success)
+
+ expect(mutation_response['errors']).to match_array(['Failed to remove the pipeline schedule'])
+ end
+ end
+
+ context 'when pipeline schedule not found' do
+ let(:pipeline_schedule_id) { 'gid://gitlab/Ci::PipelineSchedule/0' }
+
+ it do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(graphql_errors).not_to be_empty
+ expect(graphql_errors[0]['message'])
+ .to eq("Internal server error: Couldn't find Ci::PipelineSchedule with 'id'=0")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/workers/merge_requests/delete_source_branch_worker_spec.rb b/spec/workers/merge_requests/delete_source_branch_worker_spec.rb
index 957adbbbd6e..fe677103fd0 100644
--- a/spec/workers/merge_requests/delete_source_branch_worker_spec.rb
+++ b/spec/workers/merge_requests/delete_source_branch_worker_spec.rb
@@ -53,6 +53,48 @@ RSpec.describe MergeRequests::DeleteSourceBranchWorker do
worker.perform(merge_request.id, 'new-source-branch-sha', user.id)
end
end
+
+ context 'when delete service returns an error' do
+ let(:service_result) { ServiceResponse.error(message: 'placeholder') }
+
+ it 'tracks the exception' do
+ expect_next_instance_of(::Branches::DeleteService) do |instance|
+ expect(instance).to receive(:execute).with(merge_request.source_branch).and_return(service_result)
+ end
+
+ expect(service_result).to receive(:track_exception).and_call_original
+
+ worker.perform(merge_request.id, sha, user.id)
+ end
+
+ context 'when track_delete_source_errors is disabled' do
+ before do
+ stub_feature_flags(track_delete_source_errors: false)
+ end
+
+ it 'does not track the exception' do
+ expect_next_instance_of(::Branches::DeleteService) do |instance|
+ expect(instance).to receive(:execute).with(merge_request.source_branch).and_return(service_result)
+ end
+
+ expect(service_result).not_to receive(:track_exception)
+
+ worker.perform(merge_request.id, sha, user.id)
+ end
+ end
+
+ it 'still retargets the merge request' do
+ expect_next_instance_of(::Branches::DeleteService) do |instance|
+ expect(instance).to receive(:execute).with(merge_request.source_branch).and_return(service_result)
+ end
+
+ expect_next_instance_of(::MergeRequests::RetargetChainService) do |instance|
+ expect(instance).to receive(:execute).with(merge_request)
+ end
+
+ worker.perform(merge_request.id, sha, user.id)
+ end
+ end
end
it_behaves_like 'an idempotent worker' do