app/controllers/groups_controller.rb (320 lines of code) (raw):
# frozen_string_literal: true
class GroupsController < Groups::ApplicationController
include API::Helpers::RelatedResourcesHelpers
include Groups::Params
include IssuableCollectionsAction
include ParamsBackwardCompatibility
include PreviewMarkdown
include RecordUserLastActivity
include SendFileUpload
include FiltersEvents
extend ::Gitlab::Utils::Override
respond_to :html
prepend_before_action(only: [:show, :issues]) { authenticate_sessionless_user!(:rss) }
prepend_before_action(only: [:issues_calendar]) { authenticate_sessionless_user!(:ics) }
before_action :authenticate_user!, only: [:new, :create]
before_action :group, except: [:index, :new, :create]
# Authorize
before_action :authorize_admin_group!, only: [:update, :transfer, :export, :download_export]
before_action :authorize_view_edit_page!, only: :edit
before_action :authorize_remove_group!, only: [:destroy, :restore]
before_action :authorize_create_group!, only: [:new]
before_action :group_projects, only: [:activity, :issues, :merge_requests]
before_action :event_filter, only: [:activity]
before_action :user_actions, only: [:show]
before_action :check_export_rate_limit!, only: [:export, :download_export]
before_action only: :issues do
push_frontend_feature_flag(:frontend_caching, group)
push_force_frontend_feature_flag(:work_items, group.work_items_feature_flag_enabled?)
push_force_frontend_feature_flag(:work_items_beta, group.work_items_beta_feature_flag_enabled?)
push_force_frontend_feature_flag(:work_items_alpha, group.work_items_alpha_feature_flag_enabled?)
push_frontend_feature_flag(:issues_grid_view)
push_frontend_feature_flag(:issues_list_drawer, group)
push_frontend_feature_flag(:work_item_status_feature_flag, group&.root_ancestor)
push_force_frontend_feature_flag(:namespace_level_work_items, group.namespace_work_items_enabled?)
end
before_action only: :merge_requests do
push_frontend_feature_flag(:mr_approved_filter, type: :ops)
end
skip_cross_project_access_check :index, :new, :create, :edit, :update, :destroy
# When loading show as an atom feed, we render events that could leak cross
# project information
skip_cross_project_access_check :show, if: -> { request.format.html? }
layout :determine_layout
feature_category :groups_and_projects, [
:index, :new, :create, :show, :edit, :update,
:destroy, :details, :transfer, :activity, :restore
]
feature_category :team_planning, [:issues, :issues_calendar, :preview_markdown]
feature_category :code_review_workflow, [:merge_requests, :unfoldered_environment_names]
feature_category :importers, [:export, :download_export]
urgency :low, [:export, :download_export]
urgency :high, [:unfoldered_environment_names]
urgency :low, [:issues, :issues_calendar, :preview_markdown]
# TODO: Set #show to higher urgency after resolving https://gitlab.com/gitlab-org/gitlab/-/issues/334795
urgency :low, [:merge_requests, :show, :create, :new, :update, :destroy, :edit, :activity]
def index
redirect_to(current_user ? dashboard_groups_path : explore_groups_path)
end
def new
@parent_group = Group.find_by_id(params[:parent_id])
@group = Group.new(params.permit(:parent_id))
@group.build_namespace_settings
end
def create
response = Groups::CreateService.new(
current_user,
group_params.merge(organization_id: Current.organization.id)
).execute
@group = response[:group]
if response.success?
successful_creation_hooks
notice = if @group.chat_team.present?
format(
_("Group %{group_name} and its Mattermost team were successfully created."),
group_name: @group.name
)
else
format(
_("Group %{group_name} was successfully created."),
group_name: @group.name
)
end
redirect_to @group, notice: notice
else
render action: "new"
end
end
def show
respond_to do |format|
format.html do
if @group.import_state&.in_progress?
redirect_to group_import_path(@group)
else
render_show_html
end
end
format.atom do
render_details_view_atom
end
end
end
def details
respond_to do |format|
format.html do
redirect_to group_path(group)
end
format.atom do
render_details_view_atom
end
end
end
def activity
respond_to do |format|
format.html
format.json do
load_events
pager_json("events/_events", @events.count { |event| event.visible_to_user?(current_user) })
end
end
end
def edit
@badge_api_endpoint = expose_path(api_v4_groups_badges_path(id: @group.id))
end
def merge_requests; end
def update
if Groups::UpdateService.new(@group, current_user, group_params).execute
if @group.namespace_settings.errors.present?
flash[:alert] = group.namespace_settings.errors.full_messages.to_sentence
else
flash[:notice] = "Group '#{@group.name}' was successfully updated."
end
redirect_to edit_group_origin_location
else
@group.reset
render action: "edit"
end
end
def edit_group_origin_location
if params.dig(:group, :redirect_target) == 'repository_settings'
group_settings_repository_path(@group, anchor: 'js-default-branch-name')
else
edit_group_path(@group, anchor: params[:update_section])
end
end
def destroy
return destroy_immediately unless group.adjourned_deletion?
return destroy_immediately if group.marked_for_deletion? && ::Gitlab::Utils.to_boolean(params[:permanently_remove])
result = ::Groups::MarkForDeletionService.new(group, current_user).execute
if result[:status] == :success
respond_to do |format|
format.html do
redirect_to group_path(group), status: :found
end
format.json do
render json: {
message: format(
_("'%{group_name}' has been scheduled for deletion and will be deleted on %{date}."),
group_name: group.name,
date: helpers.permanent_deletion_date_formatted(group)
)
}
end
end
else
respond_to do |format|
format.html do
redirect_to edit_group_path(group), status: :found, alert: result[:message]
end
format.json do
render json: { message: result[:message] }, status: :unprocessable_entity
end
end
end
end
def restore
return render_404 unless group.marked_for_deletion?
result = ::Groups::RestoreService.new(group, current_user).execute
if result[:status] == :success
redirect_to edit_group_path(group),
notice: format(_("Group '%{group_name}' has been successfully restored."), group_name: group.full_name)
else
redirect_to edit_group_path(group), alert: result[:message]
end
end
# rubocop: disable CodeReuse/ActiveRecord
def transfer
parent_group = Group.find_by(id: params[:new_parent_group_id])
service = ::Groups::TransferService.new(@group, current_user)
if service.execute(parent_group)
flash[:notice] = "Group '#{@group.name}' was successfully transferred."
redirect_to group_path(@group)
else
flash[:alert] = service.error.html_safe
redirect_to edit_group_path(@group)
end
end
# rubocop: enable CodeReuse/ActiveRecord
def export
export_service = Groups::ImportExport::ExportService.new(
group: @group,
user: current_user,
exported_by_admin: current_user.can_admin_all_resources?
)
if export_service.async_execute
redirect_to edit_group_path(@group),
notice: _('Group export started. A download link will be sent by email and made available on this page.')
else
redirect_to edit_group_path(@group), alert: _('Group export could not be started.')
end
end
def download_export
if @group.export_file_exists?(current_user)
if @group.export_archive_exists?(current_user)
export_file = @group.export_file(current_user)
send_upload(export_file, attachment: export_file.filename)
else
redirect_to edit_group_path(@group),
alert: _(
'The file containing the export is not available yet; it may still be transferring. Please try again later.'
)
end
else
redirect_to edit_group_path(@group),
alert: _('Group export link has expired. Please generate a new export from your group settings.')
end
end
def unfoldered_environment_names
respond_to do |format|
format.json do
render json: Environments::EnvironmentNamesFinder.new(@group, current_user).execute
end
end
end
def issues
return super unless html_request?
@has_issues = IssuesFinder.new(current_user, group_id: group.id, include_subgroups: true).execute
.non_archived
.exists?
@has_projects = group_projects.exists?
set_sort_order
respond_to do |format|
format.html
end
end
protected
def render_show_html
Gitlab::Tracking.event('group_overview', 'render', user: current_user, namespace: @group)
render 'groups/show', locals: { trial: params[:trial] }
end
def render_details_view_atom
load_events
render layout: 'xml', template: 'groups/show'
end
# rubocop: disable CodeReuse/ActiveRecord
def authorize_create_group!
allowed = if params[:parent_id].present?
parent = Group.find_by(id: params[:parent_id])
can?(current_user, :create_subgroup, parent)
else
can?(current_user, :create_group)
end
render_404 unless allowed
end
# rubocop: enable CodeReuse/ActiveRecord
def determine_layout
if [:new, :create].include?(action_name.to_sym)
'dashboard'
elsif [:edit, :update].include?(action_name.to_sym)
'group_settings'
else
'group'
end
end
# rubocop: disable CodeReuse/ActiveRecord
def load_events
params[:sort] ||= 'latest_activity_desc'
options = { include_subgroups: true }
projects = GroupProjectsFinder.new(params: params, group: group, options: options, current_user: current_user)
.execute
.includes(:namespace)
@events = EventCollection
.new(projects, offset: params[:offset].to_i, filter: event_filter, groups: groups)
.to_a
.map(&:present)
Events::RenderService
.new(current_user)
.execute(@events, atom_request: request.format.atom?)
end
# rubocop: enable CodeReuse/ActiveRecord
def user_actions
@notification_setting = current_user.notification_settings_for(group) if current_user
end
def build_canonical_path(group)
return group_path(group) if action_name == 'show' # root group path
params[:id] = group.to_param
url_for(safe_params)
end
def check_export_rate_limit!
prefixed_action = "group_#{params[:action]}".to_sym
scope = params[:action] == :download_export ? @group : nil
check_rate_limit!(prefixed_action, scope: [current_user, scope].compact)
end
private
def successful_creation_hooks
# overwritten in EE
end
def groups
@group.self_and_descendants.public_or_visible_to_user(current_user) if @group.supports_events?
end
override :resource_parent
def resource_parent
group
end
override :has_project_list?
def has_project_list?
%w[details show index].include?(action_name)
end
def destroy_immediately
Groups::DestroyService.new(@group, current_user).async_execute
message = format(_("Group '%{group_name}' is being deleted."), group_name: @group.full_name)
respond_to do |format|
format.html do
flash[:toast] = message
redirect_to root_path, status: :found
end
format.json do
render json: { message: message }
end
end
end
end
GroupsController.prepend_mod