From cd5b3de7a8de949e7c8c320f1fdf93167cf43c60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20B=C4=82LTEANU?= Date: Sun, 14 Jun 2026 00:40:31 +0300 Subject: [PATCH] wip --- .../context_menus/base_controller.rb | 44 +++++++ .../context_menus/projects_controller.rb | 43 ++++++ .../context_menus/time_entries_controller.rb | 65 ++++++++++ app/controllers/context_menus_controller.rb | 49 ------- config/routes.rb | 4 +- .../context_menus/projects_controller.rb | 50 +++++++ .../context_menus/time_entries_controller.rb | 120 +++++++++++++++++ .../context_menus_controller_test.rb | 122 ------------------ 8 files changed, 324 insertions(+), 173 deletions(-) create mode 100644 app/controllers/context_menus/base_controller.rb create mode 100644 app/controllers/context_menus/projects_controller.rb create mode 100644 app/controllers/context_menus/time_entries_controller.rb create mode 100644 test/functional/context_menus/projects_controller.rb create mode 100644 test/functional/context_menus/time_entries_controller.rb diff --git a/app/controllers/context_menus/base_controller.rb b/app/controllers/context_menus/base_controller.rb new file mode 100644 index 000000000..038e48a43 --- /dev/null +++ b/app/controllers/context_menus/base_controller.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006- Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module ContextMenus + class BaseController < ApplicationController + layout false + helper :context_menus + helper_method :url_for + + def url_for(options = nil) + if options.is_a?(Hash) && options[:controller].present? + controller_name = options[:controller].to_s + unless controller_name.start_with?('/') + options = options.dup + options[:controller] = "/#{controller_name}" + end + end + super + end + + private + + def render_context_menu(template_name) + @back = back_url + render :template => "context_menus/#{template_name}" + end + end +end diff --git a/app/controllers/context_menus/projects_controller.rb b/app/controllers/context_menus/projects_controller.rb new file mode 100644 index 000000000..c39bf586f --- /dev/null +++ b/app/controllers/context_menus/projects_controller.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006- Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module ContextMenus + class ProjectsController < BaseController + before_action :require_admin + before_action :find_projects + + def index + render_context_menu 'projects' + end + + private + + def find_projects + @projects = Project.where(id: params[:ids]).to_a + if @projects.empty? + render_404 + return + end + + if @projects.size == 1 + @project = @projects.first + end + end + end +end diff --git a/app/controllers/context_menus/time_entries_controller.rb b/app/controllers/context_menus/time_entries_controller.rb new file mode 100644 index 000000000..3e0fd1477 --- /dev/null +++ b/app/controllers/context_menus/time_entries_controller.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006- Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module ContextMenus + class TimeEntriesController < BaseController + before_action :find_time_entries + + def index + @activities = @projects.map(&:activities).reduce(:&) + + edit_allowed = @time_entries.all? {|t| t.editable_by?(User.current)} + @can = {:edit => edit_allowed, :delete => edit_allowed} + @back = back_url + + @options_by_custom_field = {} + if @can[:edit] + custom_fields = @time_entries.map(&:editable_custom_fields).reduce(:&).reject(&:multiple?).select {|field| field.format.bulk_edit_supported} + custom_fields.each do |field| + values = field.possible_values_options(@projects) + if values.present? + @options_by_custom_field[field] = values + end + end + end + + render_context_menu 'time_entries' + end + + private + + def find_time_entries + @time_entries = TimeEntry.where(:id => params[:ids]). + preload(:project => :time_entry_activities). + preload(:user).to_a + + if @time_entries.blank? || !@time_entries.all?(&:visible?) + render_404; + return + end + + if @time_entries.size == 1 + @time_entry = @time_entries.first + end + + @projects = @time_entries.filter_map(&:project).uniq + @project = @projects.first if @projects.size == 1 + end + end +end diff --git a/app/controllers/context_menus_controller.rb b/app/controllers/context_menus_controller.rb index 9932d5e6d..63cdd11da 100644 --- a/app/controllers/context_menus_controller.rb +++ b/app/controllers/context_menus_controller.rb @@ -83,55 +83,6 @@ class ContextMenusController < ApplicationController render :layout => false end - def time_entries - @time_entries = TimeEntry.where(:id => params[:ids]). - preload(:project => :time_entry_activities). - preload(:user).to_a - - if @time_entries.blank? || !@time_entries.all?(&:visible?) - render_404; - return - end - - if @time_entries.size == 1 - @time_entry = @time_entries.first - end - - @projects = @time_entries.filter_map(&:project).uniq - @project = @projects.first if @projects.size == 1 - @activities = @projects.map(&:activities).reduce(:&) - - edit_allowed = @time_entries.all? {|t| t.editable_by?(User.current)} - @can = {:edit => edit_allowed, :delete => edit_allowed} - @back = back_url - - @options_by_custom_field = {} - if @can[:edit] - custom_fields = @time_entries.map(&:editable_custom_fields).reduce(:&).reject(&:multiple?).select {|field| field.format.bulk_edit_supported} - custom_fields.each do |field| - values = field.possible_values_options(@projects) - if values.present? - @options_by_custom_field[field] = values - end - end - end - - render :layout => false - end - - def projects - @projects = Project.where(id: params[:ids]).to_a - if @projects.empty? - render_404 - return - end - - if @projects.size == 1 - @project = @projects.first - end - render layout: false - end - def users @users = User.where(id: params[:ids]).to_a diff --git a/config/routes.rb b/config/routes.rb index 9edb2f855..6916cd7b8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -258,7 +258,7 @@ Rails.application.routes.draw do post 'add_attachment', :on => :member end - match '/time_entries/context_menu', :to => 'context_menus#time_entries', :as => :time_entries_context_menu, :via => [:get, :post] + match '/time_entries/context_menu', :to => 'context_menus/time_entries#index', :as => :time_entries_context_menu, :via => [:get, :post] resources :time_entries, :controller => 'timelog', :except => :destroy do member do @@ -384,7 +384,7 @@ Rails.application.routes.draw do post 'admin/test_email', :to => 'admin#test_email', :as => 'test_email' post 'admin/default_configuration', :to => 'admin#default_configuration' - match '/admin/projects_context_menu', :to => 'context_menus#projects', :as => 'projects_context_menu', :via => [:get, :post] + match '/admin/projects_context_menu', :to => 'context_menus/projects#index', :as => 'projects_context_menu', :via => [:get, :post] resources :auth_sources do member do diff --git a/test/functional/context_menus/projects_controller.rb b/test/functional/context_menus/projects_controller.rb new file mode 100644 index 000000000..38d51a93e --- /dev/null +++ b/test/functional/context_menus/projects_controller.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006- Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require_relative '../../test_helper' + +module ContextMenus + class ProjectsControllerTest < Redmine::ControllerTest + def test_index_admin_user + @request.session[:user_id] = 1 + + get( + :index, + :params => { + :ids => [1, 2] + } + ) + + assert_response :success + end + + def test_index_not_admin_user + @request.session[:user_id] = 2 + + get( + :index, + :params => { + :ids => [1, 2] + } + ) + + assert_response :forbidden + end + end +end diff --git a/test/functional/context_menus/time_entries_controller.rb b/test/functional/context_menus/time_entries_controller.rb new file mode 100644 index 000000000..9df0e34d5 --- /dev/null +++ b/test/functional/context_menus/time_entries_controller.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006- Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require_relative '../../test_helper' + +module ContextMenus + class TimeEntriesControllerTest < Redmine::ControllerTest + def test_context_menu_for_one_time_entry + @request.session[:user_id] = 2 + get( + :index, + :params => { + :ids => [1] + } + ) + assert_response :success + + assert_select 'a:not(.disabled)', :text => 'Edit' + end + + def test_time_entries_context_menu + @request.session[:user_id] = 2 + get( + :index, + :params => { + :ids => [1, 2] + } + ) + assert_response :success + + assert_select 'a:not(.disabled)', :text => 'Bulk edit' + end + + def test_time_entries_context_menu_should_include_custom_fields + field = TimeEntryCustomField.generate!(:name => "Field", :field_format => "list", :possible_values => ["foo", "bar"]) + + @request.session[:user_id] = 2 + get( + :index, + :params => { + :ids => [1, 2] + } + ) + assert_response :success + + assert_select "li.cf_#{field.id}" do + assert_select 'a[href="#"]', :text => "Field" + assert_select 'ul' do + assert_select 'a', 3 + assert_select 'a[href=?]', "/time_entries/bulk_update?ids%5B%5D=1&ids%5B%5D=2&time_entry%5Bcustom_field_values%5D%5B#{field.id}%5D=foo", :text => 'foo' + assert_select 'a[href=?]', "/time_entries/bulk_update?ids%5B%5D=1&ids%5B%5D=2&time_entry%5Bcustom_field_values%5D%5B#{field.id}%5D=bar", :text => 'bar' + assert_select 'a[href=?]', "/time_entries/bulk_update?ids%5B%5D=1&ids%5B%5D=2&time_entry%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none' + end + end + end + + def test_time_entries_context_menu_with_time_entry_that_is_not_visible_should_fail + project = Project.find(2) + project.enable_module!(:time_tracking) + time_entry = TimeEntry.generate!(project: project) + + @request.session[:user_id] = 2 + + get( + :index, + :params => { + :ids => [1, 5, time_entry.id] + } + ) + + assert_response :not_found + end + + def test_time_entries_context_menu_with_edit_own_time_entries_permission + @request.session[:user_id] = 2 + Role.find_by_name('Manager').remove_permission! :edit_time_entries + Role.find_by_name('Manager').add_permission! :edit_own_time_entries + ids = (0..1).map {TimeEntry.generate!(:user => User.find(2)).id} + get( + :index, + :params => { + :ids => ids + } + ) + assert_response :success + + assert_select 'a:not(.disabled)', :text => 'Bulk edit' + end + + def test_time_entries_context_menu_without_edit_permission + @request.session[:user_id] = 2 + Role.find_by_name('Manager').remove_permission! :edit_time_entries + get( + :index, + :params => { + :ids => [1, 2] + } + ) + assert_response :success + + assert_select 'a.disabled', :text => 'Bulk edit' + end + end +end diff --git a/test/functional/context_menus_controller_test.rb b/test/functional/context_menus_controller_test.rb index 0db361328..41cbe346f 100644 --- a/test/functional/context_menus_controller_test.rb +++ b/test/functional/context_menus_controller_test.rb @@ -400,128 +400,6 @@ class ContextMenusControllerTest < Redmine::ControllerTest assert_response :not_found end - def test_time_entries_context_menu - @request.session[:user_id] = 2 - get( - :time_entries, - :params => { - :ids => [1, 2] - } - ) - assert_response :success - - assert_select 'a:not(.disabled)', :text => 'Bulk edit' - end - - def test_context_menu_for_one_time_entry - @request.session[:user_id] = 2 - get( - :time_entries, - :params => { - :ids => [1] - } - ) - assert_response :success - - assert_select 'a:not(.disabled)', :text => 'Edit' - end - - def test_time_entries_context_menu_should_include_custom_fields - field = TimeEntryCustomField.generate!(:name => "Field", :field_format => "list", :possible_values => ["foo", "bar"]) - - @request.session[:user_id] = 2 - get( - :time_entries, - :params => { - :ids => [1, 2] - } - ) - assert_response :success - - assert_select "li.cf_#{field.id}" do - assert_select 'a[href="#"]', :text => "Field" - assert_select 'ul' do - assert_select 'a', 3 - assert_select 'a[href=?]', "/time_entries/bulk_update?ids%5B%5D=1&ids%5B%5D=2&time_entry%5Bcustom_field_values%5D%5B#{field.id}%5D=foo", :text => 'foo' - assert_select 'a[href=?]', "/time_entries/bulk_update?ids%5B%5D=1&ids%5B%5D=2&time_entry%5Bcustom_field_values%5D%5B#{field.id}%5D=bar", :text => 'bar' - assert_select 'a[href=?]', "/time_entries/bulk_update?ids%5B%5D=1&ids%5B%5D=2&time_entry%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none' - end - end - end - - def test_projects_context_menu_admin_user - @request.session[:user_id] = 1 - - get( - :projects, - :params => { - :ids => [1, 2] - } - ) - - assert_response :success - end - - def test_projects_context_menu_not_admin_user - @request.session[:user_id] = 2 - - get( - :projects, - :params => { - :ids => [1, 2] - } - ) - - assert_response :forbidden - end - - def test_time_entries_context_menu_with_time_entry_that_is_not_visible_should_fail - project = Project.find(2) - project.enable_module!(:time_tracking) - time_entry = TimeEntry.generate!(project: project) - - @request.session[:user_id] = 2 - - get( - :time_entries, - :params => { - :ids => [1, 5, time_entry.id] - } - ) - - assert_response :not_found - end - - def test_time_entries_context_menu_with_edit_own_time_entries_permission - @request.session[:user_id] = 2 - Role.find_by_name('Manager').remove_permission! :edit_time_entries - Role.find_by_name('Manager').add_permission! :edit_own_time_entries - ids = (0..1).map {TimeEntry.generate!(:user => User.find(2)).id} - get( - :time_entries, - :params => { - :ids => ids - } - ) - assert_response :success - - assert_select 'a:not(.disabled)', :text => 'Bulk edit' - end - - def test_time_entries_context_menu_without_edit_permission - @request.session[:user_id] = 2 - Role.find_by_name('Manager').remove_permission! :edit_time_entries - get( - :time_entries, - :params => { - :ids => [1, 2] - } - ) - assert_response :success - - assert_select 'a.disabled', :text => 'Bulk edit' - end - def test_context_menu_should_include_delete_for_allowed_back_urls @request.session[:user_id] = 2 %w[ -- 2.50.1 (Apple Git-155)