Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-07-19 17:16:28 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-07-19 17:16:28 +0300
commite4384360a16dd9a19d4d2d25d0ef1f2b862ed2a6 (patch)
tree2fcdfa7dcdb9db8f5208b2562f4b4e803d671243 /app/controllers/projects
parentffda4e7bcac36987f936b4ba515995a6698698f0 (diff)
Add latest changes from gitlab-org/gitlab@16-2-stable-eev16.2.0-rc42
Diffstat (limited to 'app/controllers/projects')
-rw-r--r--app/controllers/projects/alerting/notifications_controller.rb2
-rw-r--r--app/controllers/projects/blob_controller.rb15
-rw-r--r--app/controllers/projects/environments_controller.rb10
-rw-r--r--app/controllers/projects/forks_controller.rb11
-rw-r--r--app/controllers/projects/grafana_api_controller.rb46
-rw-r--r--app/controllers/projects/incidents_controller.rb1
-rw-r--r--app/controllers/projects/issues_controller.rb4
-rw-r--r--app/controllers/projects/jobs_controller.rb6
-rw-r--r--app/controllers/projects/merge_requests/conflicts_controller.rb14
-rw-r--r--app/controllers/projects/merge_requests/creations_controller.rb20
-rw-r--r--app/controllers/projects/merge_requests/diffs_controller.rb11
-rw-r--r--app/controllers/projects/merge_requests/drafts_controller.rb27
-rw-r--r--app/controllers/projects/merge_requests_controller.rb75
-rw-r--r--app/controllers/projects/milestones_controller.rb4
-rw-r--r--app/controllers/projects/ml/candidates_controller.rb10
-rw-r--r--app/controllers/projects/ml/experiments_controller.rb9
-rw-r--r--app/controllers/projects/ml/models_controller.rb20
-rw-r--r--app/controllers/projects/notes_controller.rb23
-rw-r--r--app/controllers/projects/pages_controller.rb10
-rw-r--r--app/controllers/projects/pipeline_schedules_controller.rb25
-rw-r--r--app/controllers/projects/pipelines_controller.rb5
-rw-r--r--app/controllers/projects/runners_controller.rb16
-rw-r--r--app/controllers/projects/service_desk/custom_email_controller.rb84
-rw-r--r--app/controllers/projects/service_desk_controller.rb6
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb4
-rw-r--r--app/controllers/projects/tracing_controller.rb19
-rw-r--r--app/controllers/projects/tree_controller.rb35
-rw-r--r--app/controllers/projects/uploads_controller.rb2
28 files changed, 366 insertions, 148 deletions
diff --git a/app/controllers/projects/alerting/notifications_controller.rb b/app/controllers/projects/alerting/notifications_controller.rb
index 89e8a261288..281ac14d3ce 100644
--- a/app/controllers/projects/alerting/notifications_controller.rb
+++ b/app/controllers/projects/alerting/notifications_controller.rb
@@ -72,7 +72,7 @@ module Projects
end
def endpoint_identifier
- params[:endpoint_identifier] || AlertManagement::HttpIntegration::LEGACY_IDENTIFIER
+ params[:endpoint_identifier] || AlertManagement::HttpIntegration::LEGACY_IDENTIFIERS
end
def notification_payload
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 28393e1f365..b41e4d11d24 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -49,6 +49,7 @@ class Projects::BlobController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:highlight_js, @project)
+ push_frontend_feature_flag(:highlight_js_worker, @project)
push_frontend_feature_flag(:explain_code_chat, current_user)
push_licensed_feature(:file_locks) if @project.licensed_feature_available?(:file_locks)
end
@@ -160,6 +161,8 @@ class Projects::BlobController < Projects::ApplicationController
end
def check_for_ambiguous_ref
+ return if Feature.enabled?(:redirect_with_ref_type, @project)
+
@ref_type = ref_type
if @ref_type == ExtractsRef::BRANCH_REF_TYPE && ambiguous_ref?(@project, @ref)
@@ -169,7 +172,17 @@ class Projects::BlobController < Projects::ApplicationController
end
def commit
- @commit ||= @repository.commit(@ref)
+ if Feature.enabled?(:redirect_with_ref_type, @project)
+ response = ::ExtractsRef::RequestedRef.new(@repository, ref_type: ref_type, ref: @ref).find
+ @commit = response[:commit]
+ @ref_type = response[:ref_type]
+
+ if response[:ambiguous]
+ return redirect_to(project_blob_path(@project, File.join(@ref_type ? @ref : @commit.id, @path), ref_type: @ref_type))
+ end
+ else
+ @commit ||= @repository.commit(@ref)
+ end
return render_404 unless @commit
end
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index 10d0d03e56d..4cc1ed092d2 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -12,12 +12,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController
push_frontend_feature_flag(:environment_details_vue, @project)
end
- before_action only: [:index] do
- push_frontend_feature_flag(:kas_user_access_project, @project)
- end
-
- before_action only: [:edit, :new] do
- push_frontend_feature_flag(:environment_settings_to_graphql, @project)
+ before_action only: [:index, :edit, :new] do
+ push_frontend_feature_flag(:kubernetes_namespace_for_environment)
end
before_action :authorize_read_environment!
@@ -28,7 +24,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize, :cancel_auto_stop]
before_action :verify_api_request!, only: :terminal_websocket_authorize
before_action :expire_etag_cache, only: [:index], unless: -> { request.format.json? }
- before_action :set_kas_cookie, only: [:index], if: -> { current_user && request.format.html? }
+ before_action :set_kas_cookie, only: [:index, :edit, :new], if: -> { current_user && request.format.html? }
after_action :expire_etag_cache, only: [:cancel_auto_stop]
track_event :index, :folder, :show, :new, :edit, :create, :update, :stop, :cancel_auto_stop, :terminal,
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
index ff3dc71b6cc..de2040afff3 100644
--- a/app/controllers/projects/forks_controller.rb
+++ b/app/controllers/projects/forks_controller.rb
@@ -65,9 +65,8 @@ class Projects::ForksController < Projects::ApplicationController
end
end
- # rubocop: disable CodeReuse/ActiveRecord
def create
- @forked_project = fork_namespace.projects.find_by(path: project.path)
+ @forked_project = fork_namespace.projects.find_by(path: project.path) # rubocop: disable CodeReuse/ActiveRecord
@forked_project = nil unless @forked_project && @forked_project.forked_from_project == project
@forked_project ||= fork_service.execute
@@ -96,7 +95,9 @@ class Projects::ForksController < Projects::ApplicationController
current_user: current_user
).execute
+ # rubocop: disable CodeReuse/ActiveRecord
forks.includes(:route, :creator, :group, :topics, namespace: [:route, :owner])
+ # rubocop: enable CodeReuse/ActiveRecord
end
def fork_service
@@ -130,15 +131,21 @@ class Projects::ForksController < Projects::ApplicationController
end
def load_namespaces_with_associations
+ # rubocop: disable CodeReuse/ActiveRecord
@load_namespaces_with_associations ||= fork_service.valid_fork_targets(only_groups: true).preload(:route)
+ # rubocop: enable CodeReuse/ActiveRecord
end
def memberships_hash
+ # rubocop: disable CodeReuse/ActiveRecord
current_user.members.where(source: load_namespaces_with_associations).index_by(&:source_id)
+ # rubocop: enable CodeReuse/ActiveRecord
end
def forked_projects_by_namespace(namespaces)
+ # rubocop: disable CodeReuse/ActiveRecord
project.forks.where(namespace: namespaces).includes(:namespace).index_by(&:namespace_id)
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/controllers/projects/grafana_api_controller.rb b/app/controllers/projects/grafana_api_controller.rb
deleted file mode 100644
index 2cc6c6c35ba..00000000000
--- a/app/controllers/projects/grafana_api_controller.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-class Projects::GrafanaApiController < Projects::ApplicationController
- include RenderServiceResults
- include MetricsDashboard
-
- before_action :authorize_read_grafana!, only: :proxy
-
- feature_category :metrics
- urgency :low
-
- def proxy
- return not_found if Feature.enabled?(:remove_monitor_metrics)
-
- result = ::Grafana::ProxyService.new(
- project,
- params[:datasource_id],
- params[:proxy_path],
- prometheus_params
- ).execute
-
- return continue_polling_response if result.nil?
- return error_response(result) if result[:status] == :error
-
- success_response(result)
- end
-
- private
-
- def metrics_dashboard_params
- params.permit(:embedded, :grafana_url)
- end
-
- def query_params
- params.permit(:query, :start_time, :end_time, :step)
- end
-
- def prometheus_params
- query_params.to_h
- .except(:start_time, :end_time)
- .merge(
- start: query_params[:start_time],
- end: query_params[:end_time]
- )
- end
-end
diff --git a/app/controllers/projects/incidents_controller.rb b/app/controllers/projects/incidents_controller.rb
index 7121096bd77..6109e29b169 100644
--- a/app/controllers/projects/incidents_controller.rb
+++ b/app/controllers/projects/incidents_controller.rb
@@ -11,6 +11,7 @@ class Projects::IncidentsController < Projects::ApplicationController
push_force_frontend_feature_flag(:work_items_mvc, @project&.work_items_mvc_feature_flag_enabled?)
push_force_frontend_feature_flag(:work_items_mvc_2, @project&.work_items_mvc_2_feature_flag_enabled?)
push_frontend_feature_flag(:moved_mr_sidebar, project)
+ push_frontend_feature_flag(:move_close_into_dropdown, project)
end
feature_category :incident_management
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 6311907a859..6a45595580f 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -51,13 +51,14 @@ class Projects::IssuesController < Projects::ApplicationController
push_frontend_feature_flag(:service_desk_new_note_email_native_attachments, project)
push_frontend_feature_flag(:saved_replies, current_user)
push_frontend_feature_flag(:issues_grid_view)
+ push_frontend_feature_flag(:service_desk_ticket)
end
before_action only: [:index, :show] do
push_force_frontend_feature_flag(:work_items, project&.work_items_feature_flag_enabled?)
end
- before_action only: :index do
+ before_action only: [:index, :service_desk] do
push_frontend_feature_flag(:or_issuable_queries, project)
push_frontend_feature_flag(:frontend_caching, project&.group)
end
@@ -69,6 +70,7 @@ class Projects::IssuesController < Projects::ApplicationController
push_force_frontend_feature_flag(:work_items_mvc_2, project&.work_items_mvc_2_feature_flag_enabled?)
push_frontend_feature_flag(:epic_widget_edit_confirmation, project)
push_frontend_feature_flag(:moved_mr_sidebar, project)
+ push_frontend_feature_flag(:move_close_into_dropdown, project)
end
around_action :allow_gitaly_ref_name_caching, only: [:discussions]
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index 79ddcbf732d..4e0b304a2ee 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -21,7 +21,7 @@ class Projects::JobsController < Projects::ApplicationController
before_action :verify_proxy_request!, only: :proxy_websocket_authorize
before_action :push_job_log_jump_to_failures, only: [:show]
before_action :reject_if_build_artifacts_size_refreshing!, only: [:erase]
-
+ before_action :push_ai_build_failure_cause, only: [:show]
layout 'project'
feature_category :continuous_integration
@@ -258,4 +258,8 @@ class Projects::JobsController < Projects::ApplicationController
def push_job_log_jump_to_failures
push_frontend_feature_flag(:job_log_jump_to_failures, @project)
end
+
+ def push_ai_build_failure_cause
+ push_frontend_feature_flag(:ai_build_failure_cause, @project)
+ end
end
diff --git a/app/controllers/projects/merge_requests/conflicts_controller.rb b/app/controllers/projects/merge_requests/conflicts_controller.rb
index 76a233afa13..66a358963e2 100644
--- a/app/controllers/projects/merge_requests/conflicts_controller.rb
+++ b/app/controllers/projects/merge_requests/conflicts_controller.rb
@@ -15,7 +15,8 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
respond_to do |format|
format.html do
@issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar')
- Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter.track_loading_conflict_ui_action(user: current_user)
+ Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter
+ .track_loading_conflict_ui_action(user: current_user)
end
format.json do
@@ -23,12 +24,14 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
render json: @conflicts_list
elsif @merge_request.can_be_merged?
render json: {
- message: _('The merge conflicts for this merge request have already been resolved. Please return to the merge request.'),
+ message: _('The merge conflicts for this merge request have already been resolved. ' \
+ 'Please return to the merge request.'),
type: 'error'
}
else
render json: {
- message: _('The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally.'),
+ message: _('The merge conflicts for this merge request cannot be resolved through GitLab. ' \
+ 'Please try to resolve them locally.'),
type: 'error'
}
end
@@ -52,7 +55,8 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter.track_resolve_conflict_action(user: current_user)
if @merge_request.can_be_merged?
- render status: :bad_request, json: { message: _('The merge conflicts for this merge request have already been resolved.') }
+ render status: :bad_request,
+ json: { message: _('The merge conflicts for this merge request have already been resolved.') }
return
end
@@ -71,6 +75,8 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
private
+ alias_method :issuable, :merge_request
+
def authorize_can_resolve_conflicts!
@conflicts_list = ::MergeRequests::Conflicts::ListService.new(@merge_request)
diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb
index 06381315614..6a3523b82d9 100644
--- a/app/controllers/projects/merge_requests/creations_controller.rb
+++ b/app/controllers/projects/merge_requests/creations_controller.rb
@@ -11,6 +11,12 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
before_action :apply_diff_view_cookie!, only: [:diffs, :diff_for_path]
before_action :build_merge_request, except: [:create]
+ before_action only: [:new] do
+ if can?(current_user, :fill_in_merge_request_template, project)
+ push_frontend_feature_flag(:fill_in_mr_template, project)
+ end
+ end
+
urgency :low, [
:new,
:create,
@@ -25,7 +31,9 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
end
def create
- @merge_request = ::MergeRequests::CreateService.new(project: project, current_user: current_user, params: merge_request_params).execute
+ @merge_request = ::MergeRequests::CreateService
+ .new(project: project, current_user: current_user, params: merge_request_params)
+ .execute
if @merge_request.valid?
incr_count_webide_merge_request
@@ -82,7 +90,10 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
def branch_to
@target_project = selected_target_project
- if @target_project && params[:ref].present? && Ability.allowed?(current_user, :create_merge_request_in, @target_project)
+ if @target_project &&
+ params[:ref].present? &&
+ Ability.allowed?(current_user, :create_merge_request_in, @target_project)
+
@ref = params[:ref]
@commit = @target_project.commit(Gitlab::Git::BRANCH_REF_PREFIX + @ref)
end
@@ -104,10 +115,13 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
def build_merge_request
params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
+ new_params = merge_request_params.merge(diff_options: diff_options)
# Gitaly N+1 issue: https://gitlab.com/gitlab-org/gitlab-foss/issues/58096
Gitlab::GitalyClient.allow_n_plus_1_calls do
- @merge_request = ::MergeRequests::BuildService.new(project: project, current_user: current_user, params: merge_request_params.merge(diff_options: diff_options)).execute
+ @merge_request = ::MergeRequests::BuildService
+ .new(project: project, current_user: current_user, params: new_params)
+ .execute
end
end
diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb
index f3a01fd3223..5bd0063ab95 100644
--- a/app/controllers/projects/merge_requests/diffs_controller.rb
+++ b/app/controllers/projects/merge_requests/diffs_controller.rb
@@ -49,7 +49,8 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
pagination_data: diffs.pagination_data
}
- # NOTE: Any variables that would affect the resulting json needs to be added to the cache_context to avoid stale cache issues.
+ # NOTE: Any variables that would affect the resulting json needs to be added to the cache_context
+ # to avoid stale cache issues.
cache_context = [
current_user&.cache_key,
unfoldable_positions.map(&:to_h),
@@ -130,7 +131,8 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
# rubocop: disable CodeReuse/ActiveRecord
def commit
return unless commit_id = params[:commit_id].presence
- return unless @merge_request.all_commits.exists?(sha: commit_id) || @merge_request.recent_context_commits.map(&:id).include?(commit_id)
+ return unless @merge_request.all_commits.exists?(sha: commit_id) ||
+ @merge_request.recent_context_commits.map(&:id).include?(commit_id)
@commit ||= @project.commit(commit_id)
end
@@ -160,7 +162,10 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
end
end
- return @merge_request.context_commits_diff if show_only_context_commits? && !@merge_request.context_commits_diff.empty?
+ if show_only_context_commits? && !@merge_request.context_commits_diff.empty?
+ return @merge_request.context_commits_diff
+ end
+
return @merge_request.merge_head_diff if render_merge_ref_head_diff?
if @start_sha
diff --git a/app/controllers/projects/merge_requests/drafts_controller.rb b/app/controllers/projects/merge_requests/drafts_controller.rb
index ca6ab83b877..74c495261a3 100644
--- a/app/controllers/projects/merge_requests/drafts_controller.rb
+++ b/app/controllers/projects/merge_requests/drafts_controller.rb
@@ -27,17 +27,23 @@ class Projects::MergeRequests::DraftsController < Projects::MergeRequests::Appli
draft_note = create_service.execute
+ if draft_note.errors.present?
+ render json: { errors: draft_note.errors.full_messages.to_sentence }, status: :unprocessable_entity
+ return
+ end
+
prepare_notes_for_rendering(draft_note)
render json: DraftNoteSerializer.new(current_user: current_user).represent(draft_note)
end
def update
- draft_note.update!(draft_note_params)
-
- prepare_notes_for_rendering(draft_note)
-
- render json: DraftNoteSerializer.new(current_user: current_user).represent(draft_note)
+ if draft_note.update(draft_note_params)
+ prepare_notes_for_rendering(draft_note)
+ render json: DraftNoteSerializer.new(current_user: current_user).represent(draft_note)
+ else
+ render json: { errors: draft_note.errors.full_messages.to_sentence }, status: :unprocessable_entity
+ end
end
def destroy
@@ -57,10 +63,13 @@ class Projects::MergeRequests::DraftsController < Projects::MergeRequests::Appli
if Gitlab::Utils.to_boolean(approve_params[:approve])
unless merge_request.approved_by?(current_user)
- success = ::MergeRequests::ApprovalService.new(project: @project, current_user: current_user, params: approve_params).execute(merge_request)
+ success = ::MergeRequests::ApprovalService
+ .new(project: @project, current_user: current_user, params: approve_params)
+ .execute(merge_request)
unless success
- return render json: { message: _('An error occurred while approving, please try again.') }, status: :internal_server_error
+ return render json: { message: _('An error occurred while approving, please try again.') },
+ status: :internal_server_error
end
end
@@ -101,7 +110,9 @@ class Projects::MergeRequests::DraftsController < Projects::MergeRequests::Appli
# rubocop: disable CodeReuse/ActiveRecord
def merge_request
- @merge_request ||= MergeRequestsFinder.new(current_user, project_id: @project.id).find_by!(iid: params[:merge_request_id])
+ @merge_request ||= MergeRequestsFinder
+ .new(current_user, project_id: @project.id)
+ .find_by!(iid: params[:merge_request_id])
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 60f619a8d20..2172c91fc76 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -41,19 +41,23 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_force_frontend_feature_flag(:content_editor_on_issues, project&.content_editor_on_issues_feature_flag_enabled?)
push_frontend_feature_flag(:core_security_mr_widget_counts, project)
push_frontend_feature_flag(:issue_assignees_widget, @project)
- push_frontend_feature_flag(:deprecate_vulnerabilities_feedback, @project)
push_frontend_feature_flag(:moved_mr_sidebar, project)
+ push_frontend_feature_flag(:sast_reports_in_inline_diff, project)
push_frontend_feature_flag(:mr_experience_survey, project)
push_frontend_feature_flag(:saved_replies, current_user)
push_frontend_feature_flag(:code_quality_inline_drawer, project)
- push_frontend_feature_flag(:auto_merge_labels_mr_widget, project)
push_force_frontend_feature_flag(:summarize_my_code_review, summarize_my_code_review_enabled?)
push_frontend_feature_flag(:mr_activity_filters, current_user)
push_frontend_feature_flag(:review_apps_redeploy_mr_widget, project)
- push_frontend_feature_flag(:comment_on_files, current_user)
push_frontend_feature_flag(:ci_job_failures_in_mr, project)
end
+ before_action only: [:edit] do
+ if can?(current_user, :fill_in_merge_request_template, project)
+ push_frontend_feature_flag(:fill_in_mr_template, project)
+ end
+ end
+
around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :diffs, :discussions]
after_action :log_merge_request_show, only: [:show, :diffs]
@@ -124,16 +128,31 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@merge_request.recent_context_commits
)
- per_page = [(params[:per_page] || MergeRequestDiff::COMMITS_SAFE_SIZE).to_i, MergeRequestDiff::COMMITS_SAFE_SIZE].min
- recent_commits = @merge_request.recent_commits(load_from_gitaly: true, limit: per_page, page: params[:page]).with_latest_pipeline(@merge_request.source_branch).with_markdown_cache
+ per_page = [
+ (params[:per_page] || MergeRequestDiff::COMMITS_SAFE_SIZE).to_i,
+ MergeRequestDiff::COMMITS_SAFE_SIZE
+ ].min
+ recent_commits = @merge_request
+ .recent_commits(load_from_gitaly: true, limit: per_page, page: params[:page])
+ .with_latest_pipeline(@merge_request.source_branch)
+ .with_markdown_cache
@next_page = recent_commits.next_page
@commits = set_commits_for_rendering(
recent_commits,
commits_count: @merge_request.commits_count
)
- commits_count = @merge_request.preparing? ? '-' : @merge_request.commits_count + @merge_request.context_commits_count
- render json: { html: view_to_html_string('projects/merge_requests/_commits'), next_page: @next_page, count: commits_count }
+ commits_count = if @merge_request.preparing?
+ '-'
+ else
+ @merge_request.commits_count + @merge_request.context_commits_count
+ end
+
+ render json: {
+ html: view_to_html_string('projects/merge_requests/_commits'),
+ next_page: @next_page,
+ count: commits_count
+ }
end
def pipelines
@@ -221,7 +240,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
def update
- @merge_request = ::MergeRequests::UpdateService.new(project: project, current_user: current_user, params: merge_request_update_params).execute(@merge_request)
+ @merge_request = ::MergeRequests::UpdateService
+ .new(project: project, current_user: current_user, params: merge_request_update_params)
+ .execute(@merge_request)
respond_to do |format|
format.html do
@@ -287,7 +308,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
def assign_related_issues
- result = ::MergeRequests::AssignIssuesService.new(project: project, current_user: current_user, params: { merge_request: @merge_request }).execute
+ result = ::MergeRequests::AssignIssuesService
+ .new(project: project, current_user: current_user, params: { merge_request: @merge_request })
+ .execute
case result[:count]
when 0
@@ -317,7 +340,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
def rebase
- @merge_request.rebase_async(current_user.id, skip_ci: Gitlab::Utils.to_boolean(merge_params[:skip_ci], default: false))
+ @merge_request
+ .rebase_async(current_user.id, skip_ci: Gitlab::Utils.to_boolean(merge_params[:skip_ci], default: false))
head :ok
rescue MergeRequest::RebaseLockTimeout => e
@@ -334,7 +358,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
IssuableExportCsvWorker.perform_async(:merge_request, current_user.id, project.id, finder_options.to_h) # rubocop:disable CodeReuse/Worker
index_path = project_merge_requests_path(project)
- message = _('Your CSV export has started. It will be emailed to %{email} when complete.') % { email: current_user.notification_email_or_default }
+ message = _('Your CSV export has started. It will be emailed to %{email} when complete.') %
+ { email: current_user.notification_email_or_default }
redirect_to(index_path, notice: message)
end
@@ -432,10 +457,15 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@commits_count = @merge_request.commits_count + @merge_request.context_commits_count
@diffs_count = get_diffs_count
@issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar')
- @current_user_data = Gitlab::Json.dump(UserSerializer.new(project: @project).represent(current_user, {}, MergeRequestCurrentUserEntity))
+ @current_user_data = Gitlab::Json
+ .dump(UserSerializer.new(project: @project)
+ .represent(current_user, {}, MergeRequestCurrentUserEntity))
@show_whitespace_default = current_user.nil? || current_user.show_whitespace_in_diffs
@file_by_file_default = current_user&.view_diffs_file_by_file
- @coverage_path = coverage_reports_project_merge_request_path(@project, @merge_request, format: :json) if @merge_request.has_coverage_reports?
+ if @merge_request.has_coverage_reports?
+ @coverage_path = coverage_reports_project_merge_request_path(@project, @merge_request, format: :json)
+ end
+
@update_current_user_path = expose_path(api_v4_user_preferences_path)
@endpoint_metadata_url = endpoint_metadata_url(@project, @merge_request)
@endpoint_diff_batch_url = endpoint_diff_batch_url(@project, @merge_request)
@@ -478,12 +508,18 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
def merge!
# Disable the CI check if auto_merge_strategy is specified since we have
# to wait until CI completes to know
- unless @merge_request.mergeable?(skip_ci_check: auto_merge_requested?)
+ skipped_checks = @merge_request.skipped_mergeable_checks(
+ auto_merge_requested: auto_merge_requested?,
+ auto_merge_strategy: params[:auto_merge_strategy]
+ )
+
+ unless @merge_request.mergeable?(**skipped_checks)
return :failed
end
squashing = params.fetch(:squash, false)
- merge_service = ::MergeRequests::MergeService.new(project: @project, current_user: current_user, params: merge_params)
+ merge_service = ::MergeRequests::MergeService
+ .new(project: @project, current_user: current_user, params: merge_params)
unless merge_service.hooks_validation_pass?(@merge_request, validate_squash_message: squashing)
return :hook_validation_error
@@ -500,7 +536,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
AutoMergeService.new(project, current_user, merge_params).update(merge_request)
else
AutoMergeService.new(project, current_user, merge_params)
- .execute(merge_request, params[:auto_merge_strategy] || AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
+ .execute(
+ merge_request,
+ params[:auto_merge_strategy] || AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS
+ )
end
else
@merge_request.merge_async(current_user.id, merge_params)
@@ -595,7 +634,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
def endpoint_diff_batch_url(project, merge_request)
per_page = current_user&.view_diffs_file_by_file ? '1' : '5'
- params = request.query_parameters.merge(view: 'inline', diff_head: true, w: show_whitespace, page: '0', per_page: per_page)
+ params = request
+ .query_parameters
+ .merge(view: 'inline', diff_head: true, w: show_whitespace, page: '0', per_page: per_page)
params[:ck] = merge_request.merge_head_diff&.id if merge_request.diffs_batch_cache_with_max_age?
diffs_batch_project_json_merge_request_path(project, merge_request, 'json', params)
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index 35b65dbce7e..1f4e5b54500 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -24,6 +24,10 @@ class Projects::MilestonesController < Projects::ApplicationController
feature_category :team_planning
urgency :low
+ before_action do
+ push_frontend_feature_flag(:content_editor_on_issues, @project)
+ end
+
def index
@sort = params[:sort] || 'due_date_asc'
@milestones = milestones.sort_by_attribute(@sort)
diff --git a/app/controllers/projects/ml/candidates_controller.rb b/app/controllers/projects/ml/candidates_controller.rb
index ed7155fc5f4..9905e454acb 100644
--- a/app/controllers/projects/ml/candidates_controller.rb
+++ b/app/controllers/projects/ml/candidates_controller.rb
@@ -3,7 +3,9 @@
module Projects
module Ml
class CandidatesController < ApplicationController
- before_action :check_feature_enabled, :set_candidate
+ before_action :set_candidate
+ before_action :check_read, only: [:show]
+ before_action :check_write, only: [:destroy]
feature_category :mlops
@@ -26,9 +28,13 @@ module Projects
render_404 unless @candidate.present?
end
- def check_feature_enabled
+ def check_read
render_404 unless can?(current_user, :read_model_experiments, @project)
end
+
+ def check_write
+ render_404 unless can?(current_user, :write_model_experiments, @project)
+ end
end
end
end
diff --git a/app/controllers/projects/ml/experiments_controller.rb b/app/controllers/projects/ml/experiments_controller.rb
index a620e9919e7..85e7f63779c 100644
--- a/app/controllers/projects/ml/experiments_controller.rb
+++ b/app/controllers/projects/ml/experiments_controller.rb
@@ -5,7 +5,8 @@ module Projects
class ExperimentsController < ::Projects::ApplicationController
include Projects::Ml::ExperimentsHelper
- before_action :check_feature_enabled
+ before_action :check_read, only: [:show, :index]
+ before_action :check_write, only: [:destroy]
before_action :set_experiment, only: [:show, :destroy]
feature_category :mlops
@@ -55,10 +56,14 @@ module Projects
private
- def check_feature_enabled
+ def check_read
render_404 unless can?(current_user, :read_model_experiments, @project)
end
+ def check_write
+ render_404 unless can?(current_user, :write_model_experiments, @project)
+ end
+
def set_experiment
@experiment = ::Ml::Experiment.by_project_id_and_iid(@project.id, params[:iid])
diff --git a/app/controllers/projects/ml/models_controller.rb b/app/controllers/projects/ml/models_controller.rb
new file mode 100644
index 00000000000..77855b73cbd
--- /dev/null
+++ b/app/controllers/projects/ml/models_controller.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Projects
+ module Ml
+ class ModelsController < ::Projects::ApplicationController
+ before_action :check_feature_enabled
+ feature_category :mlops
+
+ def index
+ @models = ::Projects::Ml::ModelFinder.new(@project).execute
+ end
+
+ private
+
+ def check_feature_enabled
+ render_404 unless can?(current_user, :read_model_registry, @project)
+ end
+ end
+ end
+end
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 054e8c302c9..7fcdf220bd2 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
class Projects::NotesController < Projects::ApplicationController
+ extend Gitlab::Utils::Override
include RendersNotes
include NotesActions
include NotesHelper
@@ -11,10 +12,30 @@ class Projects::NotesController < Projects::ApplicationController
before_action :authorize_create_note!, only: [:create]
before_action :authorize_resolve_note!, only: [:resolve, :unresolve]
- feature_category :team_planning
+ feature_category :team_planning, [:index, :create, :update, :destroy, :delete_attachment, :toggle_award_emoji]
+ feature_category :code_review_workflow, [:resolve, :unresolve, :outdated_line_change]
urgency :medium, [:index]
urgency :low, [:create, :update, :destroy, :resolve, :unresolve, :toggle_award_emoji, :outdated_line_change]
+ override :feature_category
+ def feature_category
+ if %w[index create].include?(params[:action])
+ category = feature_category_override_for_target_type(params[:target_type])
+ return category if category
+ end
+
+ super
+ end
+
+ def feature_category_override_for_target_type(target_type)
+ case target_type
+ when 'merge_request'
+ 'code_review_workflow'
+ when 'commit', 'project_snippet'
+ 'source_code_management'
+ end
+ end
+
def delete_attachment
note.remove_attachment!
note.update_attribute(:attachment, nil)
diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb
index 332d33b8e52..6cfbb61fbb2 100644
--- a/app/controllers/projects/pages_controller.rb
+++ b/app/controllers/projects/pages_controller.rb
@@ -1,8 +1,6 @@
# frozen_string_literal: true
class Projects::PagesController < Projects::ApplicationController
- layout :resolve_layout
-
before_action :require_pages_enabled!
before_action :authorize_read_pages!, only: [:show]
before_action :authorize_update_pages!, except: [:show, :destroy]
@@ -10,10 +8,6 @@ class Projects::PagesController < Projects::ApplicationController
feature_category :pages
- before_action do
- push_frontend_feature_flag(:show_pages_in_deployments_menu, current_user, type: :experiment)
- end
-
def new
@pipeline_wizard_data = {
project_path: @project.full_path,
@@ -66,10 +60,6 @@ class Projects::PagesController < Projects::ApplicationController
private
- def resolve_layout
- 'project_settings' unless Feature.enabled?(:show_pages_in_deployments_menu, current_user, type: :experiment)
- end
-
def project_params
params.require(:project).permit(project_params_attributes)
end
diff --git a/app/controllers/projects/pipeline_schedules_controller.rb b/app/controllers/projects/pipeline_schedules_controller.rb
index fb332fec3b5..4fd307b5105 100644
--- a/app/controllers/projects/pipeline_schedules_controller.rb
+++ b/app/controllers/projects/pipeline_schedules_controller.rb
@@ -25,14 +25,25 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
end
def create
- @schedule = Ci::CreatePipelineScheduleService
- .new(@project, current_user, schedule_params)
- .execute
-
- if @schedule.persisted?
- redirect_to pipeline_schedules_path(@project)
+ if ::Feature.enabled?(:ci_refactoring_pipeline_schedule_create_service, @project)
+ response = Ci::PipelineSchedules::CreateService.new(@project, current_user, schedule_params).execute
+ @schedule = response.payload
+
+ if response.success?
+ redirect_to pipeline_schedules_path(@project)
+ else
+ render :new
+ end
else
- render :new
+ @schedule = Ci::CreatePipelineScheduleService
+ .new(@project, current_user, schedule_params)
+ .execute
+
+ if @schedule.persisted?
+ redirect_to pipeline_schedules_path(@project)
+ else
+ render :new
+ end
end
end
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 98e6459b543..a96ee2215c2 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -22,7 +22,6 @@ class Projects::PipelinesController < Projects::ApplicationController
before_action :authorize_update_pipeline!, only: [:retry, :cancel]
before_action :ensure_pipeline, only: [:show, :downloadable_artifacts]
before_action :reject_if_build_artifacts_size_refreshing!, only: [:destroy]
- before_action :push_frontend_feature_flags, only: [:show, :builds, :dag, :failures, :test_report]
# Will be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/225596
before_action :redirect_for_legacy_scope_filter, only: [:index], if: -> { request.format.html? }
@@ -350,10 +349,6 @@ class Projects::PipelinesController < Projects::ApplicationController
def tracking_project_source
project
end
-
- def push_frontend_feature_flags
- push_frontend_feature_flag(:pipeline_details_header_vue, @project)
- end
end
Projects::PipelinesController.prepend_mod_with('Projects::PipelinesController')
diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb
index 2b2c2cef8e2..db19ca23e9f 100644
--- a/app/controllers/projects/runners_controller.rb
+++ b/app/controllers/projects/runners_controller.rb
@@ -12,8 +12,7 @@ class Projects::RunnersController < Projects::ApplicationController
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-runners-settings')
end
- def edit
- end
+ def edit; end
def update
if Ci::Runners::UpdateRunnerService.new(@runner).execute(runner_params).success?
@@ -23,12 +22,10 @@ class Projects::RunnersController < Projects::ApplicationController
end
end
- def new
- render_404 unless create_runner_workflow_for_namespace_enabled?
- end
+ def new; end
def register
- render_404 unless create_runner_workflow_for_namespace_enabled? && runner.registration_available?
+ render_404 unless runner.registration_available?
end
def destroy
@@ -55,8 +52,7 @@ class Projects::RunnersController < Projects::ApplicationController
end
end
- def show
- end
+ def show; end
def toggle_shared_runners
update_params = { shared_runners_enabled: !project.shared_runners_enabled }
@@ -84,8 +80,4 @@ class Projects::RunnersController < Projects::ApplicationController
def runner_params
params.require(:runner).permit(Ci::Runner::FORM_EDITABLE)
end
-
- def create_runner_workflow_for_namespace_enabled?
- Feature.enabled?(:create_runner_workflow_for_namespace, project.namespace)
- end
end
diff --git a/app/controllers/projects/service_desk/custom_email_controller.rb b/app/controllers/projects/service_desk/custom_email_controller.rb
new file mode 100644
index 00000000000..fb5e87f9a97
--- /dev/null
+++ b/app/controllers/projects/service_desk/custom_email_controller.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+module Projects
+ module ServiceDesk
+ class CustomEmailController < Projects::ApplicationController
+ before_action :check_feature_flag_enabled
+ before_action :authorize_admin_project!
+
+ feature_category :service_desk
+ urgency :low
+
+ def create
+ response = ::ServiceDesk::CustomEmails::CreateService.new(
+ project: project,
+ current_user: current_user,
+ params: params
+ ).execute
+
+ json_response(service_response: response)
+ end
+
+ def update
+ response = ServiceDeskSettings::UpdateService.new(project, current_user, update_setting_params).execute
+
+ if response.error?
+ json_response(
+ error_message: s_("ServiceDesk|Cannot update custom email"),
+ status: :unprocessable_entity
+ )
+ return
+ end
+
+ json_response
+ end
+
+ def destroy
+ response = ::ServiceDesk::CustomEmails::DestroyService.new(
+ project: project,
+ current_user: current_user
+ ).execute
+
+ json_response(service_response: response)
+ end
+
+ def show
+ json_response
+ end
+
+ private
+
+ def update_setting_params
+ params.permit(:custom_email_enabled)
+ end
+
+ def json_response(error_message: nil, status: :ok, service_response: nil)
+ if service_response.present?
+ status = service_response.success? ? :ok : :unprocessable_entity
+ error_message = service_response.message
+ end
+
+ respond_to do |format|
+ format.json { render json: custom_email_attributes(error_message: error_message), status: status }
+ end
+ end
+
+ def custom_email_attributes(error_message:)
+ setting = project.service_desk_setting
+
+ {
+ custom_email: setting&.custom_email,
+ custom_email_enabled: setting&.custom_email_enabled || false,
+ custom_email_verification_state: setting&.custom_email_verification&.state,
+ custom_email_verification_error: setting&.custom_email_verification&.error,
+ custom_email_smtp_address: setting&.custom_email_credential&.smtp_address,
+ error_message: error_message
+ }
+ end
+
+ def check_feature_flag_enabled
+ render_404 unless Feature.enabled?(:service_desk_custom_email, @project)
+ end
+ end
+ end
+end
diff --git a/app/controllers/projects/service_desk_controller.rb b/app/controllers/projects/service_desk_controller.rb
index 8f576b8d72b..b1e30e7a45b 100644
--- a/app/controllers/projects/service_desk_controller.rb
+++ b/app/controllers/projects/service_desk_controller.rb
@@ -13,12 +13,12 @@ class Projects::ServiceDeskController < Projects::ApplicationController
def update
Projects::UpdateService.new(project, current_user, { service_desk_enabled: params[:service_desk_enabled] }).execute
- result = ServiceDeskSettings::UpdateService.new(project, current_user, setting_params).execute
+ response = ServiceDeskSettings::UpdateService.new(project, current_user, setting_params).execute
- if result[:status] == :success
+ if response.success?
json_response
else
- render json: { message: result[:message] }, status: :unprocessable_entity
+ render json: { message: response.message }, status: :unprocessable_entity
end
end
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index ce760051f79..0e892ef3faa 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -14,10 +14,6 @@ module Projects
before_action do
push_frontend_feature_flag(:ci_variables_pages, current_user)
- push_frontend_feature_flag(:ci_limit_environment_scope, @project)
- push_frontend_feature_flag(:create_runner_workflow_for_namespace, @project.namespace)
- push_frontend_feature_flag(:frozen_outbound_job_token_scopes, @project)
- push_frontend_feature_flag(:frozen_outbound_job_token_scopes_override, @project)
end
helper_method :highlight_badge
diff --git a/app/controllers/projects/tracing_controller.rb b/app/controllers/projects/tracing_controller.rb
new file mode 100644
index 00000000000..d1218ebf344
--- /dev/null
+++ b/app/controllers/projects/tracing_controller.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Projects
+ class TracingController < Projects::ApplicationController
+ include ::Observability::ContentSecurityPolicy
+
+ feature_category :tracing
+
+ before_action :check_tracing_enabled
+
+ def index; end
+
+ private
+
+ def check_tracing_enabled
+ render_404 unless Gitlab::Observability.tracing_enabled?(project)
+ end
+ end
+end
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index c8f698d6193..b961339111b 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -12,12 +12,14 @@ class Projects::TreeController < Projects::ApplicationController
before_action :require_non_empty_project, except: [:new, :create]
before_action :assign_ref_vars
+ before_action :find_requested_ref, only: [:show]
before_action :assign_dir_vars, only: [:create_dir]
before_action :authorize_read_code!
before_action :authorize_edit_tree!, only: [:create_dir]
before_action do
push_frontend_feature_flag(:highlight_js, @project)
+ push_frontend_feature_flag(:highlight_js_worker, @project)
push_frontend_feature_flag(:explain_code_chat, current_user)
push_licensed_feature(:file_locks) if @project.licensed_feature_available?(:file_locks)
end
@@ -28,18 +30,20 @@ class Projects::TreeController < Projects::ApplicationController
def show
return render_404 unless @commit
- @ref_type = ref_type
- if @ref_type == BRANCH_REF_TYPE && ambiguous_ref?(@project, @ref)
- branch = @project.repository.find_branch(@ref)
- if branch
- redirect_to project_tree_path(@project, branch.target)
- return
+ unless Feature.enabled?(:redirect_with_ref_type, @project)
+ @ref_type = ref_type
+ if @ref_type == BRANCH_REF_TYPE && ambiguous_ref?(@project, @ref)
+ branch = @project.repository.find_branch(@ref)
+ if branch
+ redirect_to project_tree_path(@project, branch.target)
+ return
+ end
end
end
if tree.entries.empty?
if @repository.blob_at(@commit.id, @path)
- redirect_to project_blob_path(@project, File.join(@ref, @path))
+ redirect_to project_blob_path(@project, File.join(@ref, @path), ref_type: @ref_type)
elsif @path.present?
redirect_to_tree_root_for_missing_path(@project, @ref, @path)
end
@@ -59,6 +63,23 @@ class Projects::TreeController < Projects::ApplicationController
private
+ def find_requested_ref
+ return unless Feature.enabled?(:redirect_with_ref_type, @project)
+
+ @ref_type = ref_type
+ if @ref_type.present?
+ @tree = @repo.tree(@ref, @path, ref_type: @ref_type)
+ else
+ response = ExtractsPath::RequestedRef.new(@repository, ref_type: nil, ref: @ref).find
+ @ref_type = response[:ref_type]
+ @commit = response[:commit]
+
+ if response[:ambiguous]
+ redirect_to(project_tree_path(@project, File.join(@ref_type ? @ref : @commit.id, @path), ref_type: @ref_type))
+ end
+ end
+ end
+
def redirect_renamed_default_branch?
action_name == 'show'
end
diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb
index 8f4987a07f6..48399e17b25 100644
--- a/app/controllers/projects/uploads_controller.rb
+++ b/app/controllers/projects/uploads_controller.rb
@@ -11,7 +11,7 @@ class Projects::UploadsController < Projects::ApplicationController
before_action :authorize_upload_file!, only: [:create, :authorize]
before_action :verify_workhorse_api!, only: [:authorize]
- feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
+ feature_category :team_planning
private