openSUSE/open-build-service

View on GitHub
src/api/app/controllers/webui/project_controller.rb

Summary

Maintainability
C
7 hrs
Test Coverage
B
83%
class Webui::ProjectController < Webui::WebuiController
  include Webui::RequestHelper
  include Webui::ProjectHelper
  include Webui::ManageRelationships
  include Webui::NotificationsHandler
  include Webui::ProjectBuildResultParsing

  before_action :lockout_spiders, only: %i[requests buildresults]

  before_action :require_login, only: %i[create destroy new release_request
                                         new_release_request edit_comment]

  # rubocop:disable Rails/LexicallyScopedActionFilter
  # The methods save_person, save_group and remove_role are defined in Webui::ManageRelationships
  before_action :set_project, only: %i[autocomplete_repositories users subprojects
                                       edit release_request
                                       show buildresult
                                       destroy remove_path_from_target
                                       requests save monitor edit_comment
                                       unlock save_person save_group remove_role
                                       move_path clear_failed_comment pulse]
  # rubocop:enable Rails/LexicallyScopedActionFilter
  before_action :set_project_by_id, only: :update

  before_action :load_project_info, only: :show

  before_action :check_ajax, only: %i[buildresult edit_comment_form]

  after_action :verify_authorized, except: %i[index autocomplete_projects autocomplete_incidents autocomplete_packages
                                              autocomplete_repositories users subprojects new show
                                              buildresult requests monitor new_release_request
                                              remove_target_request edit_comment edit_comment_form preview_description]

  def index
    respond_to do |format|
      format.html do
        render :index,
               locals: { important_projects: Project.very_important_projects_with_categories }
      end
      format.json { render json: ProjectDatatable.new(params, view_context: view_context, show_all: show_all?) }
    end
  end

  def show
    @release_targets = @project.release_targets

    @has_patchinfo = @project.patchinfos.exists?
    @comments = @project.comments
    @comment = Comment.new
    @current_notification = handle_notification

    respond_to do |format|
      format.html
      format.js
    end
  end

  def new
    @project = Project.new
    @project.name = params[:name] if params[:name]
    @namespace = params[:namespace]

    @show_restore_message = params[:restore_option] && Project.deleted?(params[:name])
  end

  def edit
    authorize @project, :update?
    respond_to do |format|
      format.js
    end
  end

  def create
    params[:project][:name] = "#{params[:namespace]}:#{params[:project][:name]}" if params[:namespace]

    @project = Project.new(project_params)
    authorize(@project, :create?)

    if Project.deleted?(@project.name) && !params[:restore_option_provided]
      redirect_to(new_project_path(name: @project.name, restore_option: true))
      return
    end

    @project.relationships.build(user: User.session,
                                 role: Role.find_by_title('maintainer'))

    @project.kind = 'maintenance' if params[:maintenance_project]

    # TODO: do this with nested attributes
    @project.flags.new(status: 'disable', flag: 'access') if params[:access_protection]

    # TODO: do this with nested attributes
    @project.flags.new(status: 'disable', flag: 'sourceaccess') if params[:source_protection]

    # TODO: do this with nested attributes
    @project.flags.new(status: 'disable', flag: 'publish') if params[:disable_publishing]

    if @project.valid? && @project.store
      flash[:success] = "Project '#{elide(@project.name)}' was created successfully"
      redirect_to action: 'show', project: @project.name
    else
      flash[:error] = "Failed to save project '#{elide(@project.name)}'. #{@project.errors.full_messages.to_sentence}."
      redirect_back_or_to root_path
    end
  end

  def update
    authorize @project, :update?
    respond_to do |format|
      format.js do
        if @project.update(project_params)
          @project.store
          flash.now[:success] = 'Project was successfully updated.'
        else
          flash.now[:error] = 'Failed to update the project.'
        end
      end
    end
  end

  def destroy
    authorize @project, :destroy?
    if @project.check_weak_dependencies?
      parent = @project.parent
      @project.destroy
      flash[:success] = 'Project was successfully removed.'
      if parent
        redirect_to project_show_path(parent)
      else
        redirect_to(action: :index)
      end
    else
      redirect_to project_show_path(@project), error: "Project can't be removed: #{@project.errors.full_messages.to_sentence}"
    end
  end

  def autocomplete_projects
    render json: Project.autocomplete(params[:term], params[:local]).not_maintenance_incident.pluck(:name)
  end

  def autocomplete_incidents
    render json: Project.autocomplete(params[:term]).maintenance_incident.pluck(:name)
  end

  def autocomplete_packages
    @project = Project.find_by(name: params[:project])
    if @project
      render json: @project.packages.autocomplete(params[:term]).pluck(:name)
    else
      render json: nil
    end
  end

  def autocomplete_repositories
    render json: @project.repositories.order(:name).pluck(:name)
  end

  def users
    @users = @project.users
    @groups = @project.groups
    @roles = Role.local_roles
    if User.session && params[:notification_id]
      @current_notification = Notification.find(params[:notification_id])
      authorize @current_notification, :update?, policy_class: NotificationCommentPolicy
    end
    @current_request_action = BsRequestAction.find(params[:request_action_id]) if User.session && params[:request_action_id]
  end

  def subprojects
    respond_to do |format|
      format.html
      format.json do
        render json: ProjectDatatable.new(params, view_context: view_context, projects: project_for_datatable)
      end
    end
  end

  def release_request
    authorize @project, :update?
  end

  def new_release_request
    if params[:skiprequest]
      # FIXME2.3: do it directly here, api function missing
    else
      begin
        req = nil
        BsRequest.transaction do
          req = BsRequest.new
          req.description = params[:description]

          action = BsRequestActionMaintenanceRelease.new(source_project: params[:project])
          req.bs_request_actions << action

          req.save!
        end
        flash[:success] = 'Created maintenance release request ' \
                          "<a href='#{url_for(controller: 'request', action: 'show', number: req.number)}'>#{req.number}</a>"
      rescue ArchitectureOrderMissmatch,
             Patchinfo::IncompletePatchinfo,
             BsRequestActionMaintenanceRelease::OpenReleaseRequests,
             BsRequestActionMaintenanceRelease::RepositoryWithoutReleaseTarget,
             BsRequestActionMaintenanceRelease::RepositoryWithoutArchitecture,
             BsRequestAction::BuildNotFinished,
             BsRequestAction::VersionReleaseDiffers,
             BsRequestAction::UnknownProject,
             BsRequestAction::Errors::UnknownTargetProject,
             BsRequestAction::UnknownTargetPackage => e
        flash[:error] = e.message
        redirect_back_or_to({ action: 'show', project: params[:project] }) && return
      rescue APIError
        flash[:error] = 'Internal problem while release request creation'
        redirect_back_or_to({ action: 'show', project: params[:project] }) && return
      end
    end
    redirect_to action: 'show', project: params[:project]
  end

  def buildresult
    render partial: 'buildstatus', locals: { project: @project,
                                             buildresults: @project.buildresults,
                                             collapsed_repositories: params.fetch(:collapsedRepositories, {}) }
  end

  # TODO: Remove this once request_index beta is rolled out
  def requests
    @default_request_type = params[:type] if params[:type]
    @default_request_state = params[:state] if params[:state]
  end

  def restore
    project = Project.new(name: params[:project])
    authorize(project, :create?)

    if Project.deleted?(project.name)
      project = Project.restore(project.name)

      flash[:success] = "Project '#{elide(project.name)}' was restored successfully"
      redirect_to action: 'show', project: project.name
    else
      flash[:error] = 'Project was never deleted.'
      redirect_back_or_to root_path
    end
  end

  def remove_target_request
    req = nil
    begin
      BsRequest.transaction do
        req = BsRequest.new
        req.description = params[:description]

        opts = { target_project: params[:project] }
        opts[:target_repository] = params[:repository] if params[:repository]
        action = BsRequestActionDelete.new(opts)
        req.bs_request_actions << action

        req.save!
      end
      flash[:success] = "Created <a href='#{url_for(controller: 'request',
                                                    action: 'show',
                                                    number: req.number)}'>repository delete request #{req.number}</a>"
    rescue BsRequestAction::Errors::UnknownTargetProject,
           BsRequestAction::UnknownTargetPackage => e
      flash[:error] = e.message
      redirect_to(action: :index, controller: :repositories, project: params[:project]) && return
    end
    redirect_to controller: :request, action: :show, number: req.number
  end

  def remove_path_from_target
    authorize @project, :update?

    repository = @project.repositories.find(params[:repository])
    path_element = repository.path_elements.find(params[:path])
    path_element.destroy
    if @project.valid?
      @project.store
      redirect_to({ action: :index, controller: :repositories, project: @project }, success: 'Successfully removed path')
    else
      redirect_back_or_to root_path, error: "Can not remove path: #{@project.errors.full_messages.to_sentence}"
    end
  end

  def move_path
    authorize @project, :update?

    params.require(:direction)
    repository = @project.repositories.find(params[:repository])
    path_element = repository.path_elements.find(params[:path])

    if params[:direction] == 'up'
      PathElement.transaction do
        path_element.move_higher
      end
    end
    if params[:direction] == 'down'
      PathElement.transaction do
        path_element.move_lower
      end
    end

    @project.store
    redirect_to({ action: :index, controller: :repositories, project: @project }, success: "Path moved #{params[:direction]} successfully")
  end

  def monitor
    build_results = monitor_buildresult

    if build_results
      # This method sets a bunch of instance variables used in the view and below...
      monitor_parse_buildresult(build_results)

      # extract repos
      repohash = {}
      @statushash.each do |repo, arch_hash|
        repohash[repo] = arch_hash.keys.sort!
      end
      @repoarray = repohash.sort
    else
      @buildresult_unavailable = true
    end
  end

  def clear_failed_comment
    # FIXME: This should authorize destroy for all the attributes
    authorize @project, :update?

    packages = Package.where(project: @project, name: params[:package])
    packages.each do |package|
      package.attribs.where(attrib_type: AttribType.find_by_namespace_and_name('OBS', 'ProjectStatusPackageFailComment')).destroy_all
    end

    flash.now[:success] = 'Cleared comments for packages'

    respond_to do |format|
      format.html { redirect_to(project_status_path(@project), success: 'Cleared comments for packages.') }
      format.js { render 'clear_failed_comment' }
    end
  end

  # FIXME: This should authorize create on this attribute
  def edit_comment
    @package = @project.find_package(params[:package])

    at = AttribType.find_by_namespace_and_name!('OBS', 'ProjectStatusPackageFailComment')
    unless User.session.can_create_attribute_in?(@package, at)
      @comment = params[:last_comment]
      flash.now[:error] = "Can't create attributes in #{elide(@package.name)}"
      return
    end

    attr = @package.attribs.where(attrib_type: at).first_or_initialize
    v = attr.values.first_or_initialize
    v.value = params[:text]
    v.position = 1
    attr.save!
    v.save!
    @comment = params[:text]
  end

  def unlock
    authorize @project, :unlock?
    if @project.unlock(params[:comment])
      redirect_to project_show_path(@project), success: 'Successfully unlocked project'
    else
      redirect_to project_show_path(@project), error: "Project can't be unlocked: #{@project.errors.full_messages.to_sentence}"
    end
  end

  def preview_description
    markdown = helpers.render_as_markdown(params[:project][:description])
    respond_to do |format|
      format.json { render json: { markdown: markdown } }
    end
  end

  def buildresults; end
  def save; end
  def pulse; end
  def edit_comment_form; end

  private

  def show_all?
    params[:all].to_s.casecmp?('true')
  end

  def project_for_datatable
    case params[:type]
    when 'sibling project'
      @project.siblingprojects
    when 'subproject'
      @project.subprojects
    when 'parent project'
      @project.ancestors
    end
  end

  def set_project_by_id
    @project = Project.find(params[:id])
  end

  def main_object
    @project # used by mixins
  end

  def project_params
    params.require(:project).permit(
      :name,
      :namespace,
      :title,
      :description,
      :maintenance_project,
      :access_protection,
      :source_protection,
      :disable_publishing,
      :url,
      :report_bug_url
    )
  end

  ################################### Before filters ###################################

  def set_maintained_project
    @maintained_project = Project.find_by(name: params[:maintained_project])
    raise ActiveRecord::RecordNotFound unless @maintained_project
  end

  def load_project_info
    find_maintenance_infos

    @packages = @project.packages.pluck(:name)
    @inherited_packages = @project.expand_all_packages.find_all { |inherited_package| @packages.exclude?(inherited_package[0]) }
    @linking_projects = @project.linked_by_projects.pluck(:name)

    reqs = @project.open_requests
    @requests = (reqs[:reviews] + reqs[:targets] + reqs[:incidents] + reqs[:maintenance_release]).sort!.uniq
    @incoming_requests_size = OpenRequestsFinder.new(BsRequest, @project.name).incoming_requests(reqs.values.sum).count
    @outgoing_requests_size = OpenRequestsFinder.new(BsRequest, @project.name).outgoing_requests(reqs.values.sum).count

    @nr_of_problem_packages = @project.number_of_build_problems
  end

  def require_maintenance_project
    unless @is_maintenance_project
      redirect_back_or_to({ action: 'show', project: @project })
      return false
    end
    true
  end

  ################################### Helper methods ###################################

  def find_maintenance_infos
    @project.maintenance_projects.each do |pm|
      # FIXME: skip the non official ones
      @project_maintenance_project = pm.maintenance_project.name
    end

    @is_maintenance_project = @project.is_maintenance?
    if @is_maintenance_project
      @open_maintenance_incidents = @project.maintenance_incidents.distinct.order('projects.name').pluck('projects.name')

      @maintained_projects = @project.maintained_project_names
    end
    @is_incident_project = @project.is_maintenance_incident?
    return unless @is_incident_project

    @open_release_requests = BsRequest::FindFor::Query.new(project: @project.name,
                                                           states: %w[new review],
                                                           types: ['maintenance_release'],
                                                           roles: ['source']).all.pluck(:number) # rubocop:disable Rails/RedundantActiveRecordAllMethod
  end

  def valid_target_name?(name)
    name =~ /^\w[-.\w&]*$/
  end

  def set_project_by_name
    @project = Project.get_by_name(params['project'])
  rescue Project::UnknownObjectError
    @project = nil
  end
end