Move Multiple Issue Boards for Projects to Core
Refactor code to allow multiple issue boards management for projects in CE
This commit is contained in:
parent
2b9ddc2f99
commit
0f6c42c5ce
19 changed files with 188 additions and 42 deletions
|
@ -3,8 +3,9 @@
|
|||
module BoardsResponses
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
# Overridden on EE module
|
||||
def board_params
|
||||
params.require(:board).permit(:name, :weight, :milestone_id, :assignee_id, label_ids: [])
|
||||
params.require(:board).permit(:name)
|
||||
end
|
||||
|
||||
def parent
|
||||
|
|
90
app/controllers/concerns/multiple_boards_actions.rb
Normal file
90
app/controllers/concerns/multiple_boards_actions.rb
Normal file
|
@ -0,0 +1,90 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module MultipleBoardsActions
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include BoardsActions
|
||||
|
||||
before_action :redirect_to_recent_board, only: [:index]
|
||||
before_action :authenticate_user!, only: [:recent]
|
||||
before_action :authorize_create_board!, only: [:create]
|
||||
before_action :authorize_admin_board!, only: [:create, :update, :destroy]
|
||||
end
|
||||
|
||||
def recent
|
||||
recent_visits = ::Boards::VisitsFinder.new(parent, current_user).latest(4)
|
||||
recent_boards = recent_visits.map(&:board)
|
||||
|
||||
render json: serialize_as_json(recent_boards)
|
||||
end
|
||||
|
||||
def create
|
||||
board = Boards::CreateService.new(parent, current_user, board_params).execute
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
if board.persisted?
|
||||
extra_json = { board_path: board_path(board) }
|
||||
render json: serialize_as_json(board).merge(extra_json)
|
||||
else
|
||||
render json: board.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
service = Boards::UpdateService.new(parent, current_user, board_params)
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
if service.execute(board)
|
||||
extra_json = { board_path: board_path(board) }
|
||||
render json: serialize_as_json(board).merge(extra_json)
|
||||
else
|
||||
render json: board.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
service = Boards::DestroyService.new(parent, current_user)
|
||||
service.execute(board)
|
||||
|
||||
respond_to do |format|
|
||||
format.json { head :ok }
|
||||
format.html { redirect_to boards_path, status: :found }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def redirect_to_recent_board
|
||||
return if request.format.json? || !parent.multiple_issue_boards_available? || !latest_visited_board
|
||||
|
||||
redirect_to board_path(latest_visited_board.board)
|
||||
end
|
||||
|
||||
def latest_visited_board
|
||||
@latest_visited_board ||= Boards::VisitsFinder.new(parent, current_user).latest
|
||||
end
|
||||
|
||||
def authorize_create_board!
|
||||
check_multiple_group_issue_boards_available! if group?
|
||||
end
|
||||
|
||||
def authorize_admin_board!
|
||||
return render_404 unless can?(current_user, :admin_board, parent)
|
||||
end
|
||||
|
||||
def serializer
|
||||
BoardSerializer.new(current_user: current_user)
|
||||
end
|
||||
|
||||
def serialize_as_json(resource)
|
||||
serializer.represent(resource, serializer: 'board', include_full_project_path: board.group_board?)
|
||||
end
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Projects::BoardsController < Projects::ApplicationController
|
||||
include BoardsActions
|
||||
include MultipleBoardsActions
|
||||
include IssuableCollections
|
||||
|
||||
before_action :check_issues_available!
|
||||
|
|
26
app/finders/boards/visits_finder.rb
Normal file
26
app/finders/boards/visits_finder.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Boards
|
||||
class VisitsFinder
|
||||
attr_accessor :params, :current_user, :parent
|
||||
|
||||
def initialize(parent, current_user)
|
||||
@current_user = current_user
|
||||
@parent = parent
|
||||
end
|
||||
|
||||
def execute(count = nil)
|
||||
return unless current_user
|
||||
|
||||
recent_visit_model.latest(current_user, parent, count: count)
|
||||
end
|
||||
|
||||
alias_method :latest, :execute
|
||||
|
||||
private
|
||||
|
||||
def recent_visit_model
|
||||
parent.is_a?(Group) ? BoardGroupRecentVisit : BoardProjectRecentVisit
|
||||
end
|
||||
end
|
||||
end
|
|
@ -15,7 +15,8 @@ module BoardsHelper
|
|||
root_path: root_path,
|
||||
bulk_update_path: @bulk_issues_path,
|
||||
default_avatar: image_path(default_avatar),
|
||||
time_tracking_limit_to_hours: Gitlab::CurrentSettings.time_tracking_limit_to_hours.to_s
|
||||
time_tracking_limit_to_hours: Gitlab::CurrentSettings.time_tracking_limit_to_hours.to_s,
|
||||
recent_boards_endpoint: recent_boards_path
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -87,6 +88,18 @@ module BoardsHelper
|
|||
end
|
||||
|
||||
def boards_link_text
|
||||
s_("IssueBoards|Board")
|
||||
if current_board_parent.multiple_issue_boards_available?
|
||||
s_("IssueBoards|Boards")
|
||||
else
|
||||
s_("IssueBoards|Board")
|
||||
end
|
||||
end
|
||||
|
||||
def recent_boards_path
|
||||
recent_project_boards_path(@project) if current_board_parent.is_a?(Project)
|
||||
end
|
||||
|
||||
def current_board_json
|
||||
board.to_json
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1949,9 +1949,8 @@ class Project < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
# Overridden on EE module
|
||||
def multiple_issue_boards_available?
|
||||
false
|
||||
true
|
||||
end
|
||||
|
||||
def full_path_before_last_save
|
||||
|
|
|
@ -196,6 +196,7 @@ class ProjectPolicy < BasePolicy
|
|||
rule { guest & can?(:read_container_image) }.enable :build_read_container_image
|
||||
|
||||
rule { can?(:reporter_access) }.policy do
|
||||
enable :admin_board
|
||||
enable :download_code
|
||||
enable :read_statistics
|
||||
enable :download_wiki_code
|
||||
|
@ -240,6 +241,7 @@ class ProjectPolicy < BasePolicy
|
|||
rule { can?(:developer_access) & can?(:create_issue) }.enable :import_issues
|
||||
|
||||
rule { can?(:developer_access) }.policy do
|
||||
enable :admin_board
|
||||
enable :admin_merge_request
|
||||
enable :admin_milestone
|
||||
enable :update_merge_request
|
||||
|
@ -266,6 +268,7 @@ class ProjectPolicy < BasePolicy
|
|||
end
|
||||
|
||||
rule { can?(:maintainer_access) }.policy do
|
||||
enable :admin_board
|
||||
enable :push_to_delete_protected_branch
|
||||
enable :update_project_snippet
|
||||
enable :update_environment
|
||||
|
|
|
@ -2,4 +2,5 @@
|
|||
|
||||
class BoardSimpleEntity < Grape::Entity
|
||||
expose :id
|
||||
expose :name
|
||||
end
|
||||
|
|
|
@ -9,7 +9,7 @@ module Boards
|
|||
private
|
||||
|
||||
def can_create_board?
|
||||
parent.boards.empty?
|
||||
parent.boards.empty? || parent.multiple_issue_boards_available?
|
||||
end
|
||||
|
||||
def create_board!
|
||||
|
|
11
app/services/boards/destroy_service.rb
Normal file
11
app/services/boards/destroy_service.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Boards
|
||||
class DestroyService < Boards::BaseService
|
||||
def execute(board)
|
||||
return false if parent.boards.size == 1
|
||||
|
||||
board.destroy
|
||||
end
|
||||
end
|
||||
end
|
9
app/services/boards/update_service.rb
Normal file
9
app/services/boards/update_service.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Boards
|
||||
class UpdateService < Boards::BaseService
|
||||
def execute(board)
|
||||
board.update(params)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,19 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Boards
|
||||
module Visits
|
||||
class LatestService < Boards::BaseService
|
||||
def execute
|
||||
return unless current_user
|
||||
|
||||
recent_visit_model.latest(current_user, parent, count: params[:count])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def recent_visit_model
|
||||
parent.is_a?(Group) ? BoardGroupRecentVisit : BoardProjectRecentVisit
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Move Multiple Issue Boards for Projects to Core
|
||||
merge_request:
|
||||
author:
|
||||
type: added
|
|
@ -155,7 +155,11 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
|
|||
end
|
||||
end
|
||||
|
||||
resources :boards, only: [:index, :show], constraints: { id: /\d+/ }
|
||||
resources :boards, only: [:index, :show, :create, :update, :destroy], constraints: { id: /\d+/ } do
|
||||
collection do
|
||||
get :recent
|
||||
end
|
||||
end
|
||||
resources :releases, only: [:index]
|
||||
resources :forks, only: [:index, :new, :create]
|
||||
resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ }
|
||||
|
|
|
@ -5479,6 +5479,9 @@ msgstr ""
|
|||
msgid "IssueBoards|Board"
|
||||
msgstr ""
|
||||
|
||||
msgid "IssueBoards|Boards"
|
||||
msgstr ""
|
||||
|
||||
msgid "Issues"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ describe Groups::BoardsController do
|
|||
it 'return an array with one group board' do
|
||||
create(:board, group: group)
|
||||
|
||||
expect(Boards::Visits::LatestService).not_to receive(:new)
|
||||
expect(Boards::VisitsFinder).not_to receive(:new)
|
||||
|
||||
list_boards format: :json
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ describe Projects::BoardsController do
|
|||
it 'returns a list of project boards' do
|
||||
create_list(:board, 2, project: project)
|
||||
|
||||
expect(Boards::Visits::LatestService).not_to receive(:new)
|
||||
expect(Boards::VisitsFinder).not_to receive(:new)
|
||||
|
||||
list_boards format: :json
|
||||
|
||||
|
|
|
@ -2,32 +2,32 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Boards::Visits::LatestService do
|
||||
describe '#execute' do
|
||||
describe Boards::VisitsFinder do
|
||||
describe '#latest' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
context 'when a project board' do
|
||||
let(:project) { create(:project) }
|
||||
let(:project_board) { create(:board, project: project) }
|
||||
|
||||
subject(:service) { described_class.new(project_board.parent, user) }
|
||||
subject(:finder) { described_class.new(project_board.parent, user) }
|
||||
|
||||
it 'returns nil when there is no user' do
|
||||
service.current_user = nil
|
||||
finder.current_user = nil
|
||||
|
||||
expect(service.execute).to eq nil
|
||||
expect(finder.execute).to eq nil
|
||||
end
|
||||
|
||||
it 'queries for most recent visit' do
|
||||
expect(BoardProjectRecentVisit).to receive(:latest).once
|
||||
|
||||
service.execute
|
||||
finder.execute
|
||||
end
|
||||
|
||||
it 'queries for last N visits' do
|
||||
expect(BoardProjectRecentVisit).to receive(:latest).with(user, project, count: 5).once
|
||||
|
||||
described_class.new(project_board.parent, user, count: 5).execute
|
||||
described_class.new(project_board.parent, user).latest(5)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -35,24 +35,24 @@ describe Boards::Visits::LatestService do
|
|||
let(:group) { create(:group) }
|
||||
let(:group_board) { create(:board, group: group) }
|
||||
|
||||
subject(:service) { described_class.new(group_board.parent, user) }
|
||||
subject(:finder) { described_class.new(group_board.parent, user) }
|
||||
|
||||
it 'returns nil when there is no user' do
|
||||
service.current_user = nil
|
||||
finder.current_user = nil
|
||||
|
||||
expect(service.execute).to eq nil
|
||||
expect(finder.execute).to eq nil
|
||||
end
|
||||
|
||||
it 'queries for most recent visit' do
|
||||
expect(BoardGroupRecentVisit).to receive(:latest).once
|
||||
|
||||
service.execute
|
||||
finder.latest
|
||||
end
|
||||
|
||||
it 'queries for last N visits' do
|
||||
expect(BoardGroupRecentVisit).to receive(:latest).with(user, group, count: 5).once
|
||||
|
||||
described_class.new(group_board.parent, user, count: 5).execute
|
||||
described_class.new(group_board.parent, user).latest(5)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -17,7 +17,7 @@ describe 'layouts/nav/sidebar/_project' do
|
|||
it 'has board tab' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_css('a[title="Board"]')
|
||||
expect(rendered).to have_css('a[title="Boards"]')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue