├── app ├── views │ └── my │ │ ├── query_block.html.erb │ │ ├── _text_block.html.erb │ │ ├── update_text.js.erb │ │ ├── _text.html.erb │ │ ├── _block.html.erb │ │ ├── blocks │ │ └── _label_my_queries.html.erb │ │ ├── _my_page_text_modal.html.erb │ │ ├── page.html.erb │ │ ├── page_layout.html.erb │ │ ├── _query_block_2_1_0.html.erb │ │ └── _query_block.html.erb ├── helpers │ └── my_page_queries_helper.rb └── models │ └── query_presenter.rb ├── doc ├── my_page_queries_1.png └── my_page_queries_2.png ├── test ├── test_helper.rb ├── unit │ ├── helpers │ │ └── my_page_queries_helper_test.rb │ ├── my_page_queries │ │ ├── user_test.rb │ │ └── upgrade_service_test.rb │ └── query_presenter_test.rb └── functional │ └── my_page_queries │ ├── projects_controller_test.rb │ └── my_controller_test.rb ├── config ├── locales │ ├── ja.yml │ ├── tr.yml │ ├── zh.yml │ ├── ru.yml │ ├── cs.yml │ ├── en.yml │ ├── de.yml │ ├── pl.yml │ ├── pt-BR.yml │ └── fr.yml ├── database-mysql-travis.yml └── routes.rb ├── lib ├── my_page_queries.rb ├── tasks │ └── my_page_queries.rake └── my_page_queries │ ├── patches │ ├── user_patch.rb │ └── my_controller_patch.rb │ └── upgrade_service.rb ├── assets ├── stylesheets │ └── my_page_queries.css └── javascripts │ └── my_page_queries.js ├── init.rb ├── .travis.yml └── README.md /app/views/my/query_block.html.erb: -------------------------------------------------------------------------------- 1 | <%= render_block(@user, @block_name) %> 2 | -------------------------------------------------------------------------------- /doc/my_page_queries_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Restream/redmine_my_page_queries/HEAD/doc/my_page_queries_1.png -------------------------------------------------------------------------------- /doc/my_page_queries_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Restream/redmine_my_page_queries/HEAD/doc/my_page_queries_2.png -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper') 2 | 3 | module RedmineMyPageQueries 4 | end 5 | -------------------------------------------------------------------------------- /app/views/my/_text_block.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= render 'my/text', text: text, block_name: block_name %> 3 |
4 | -------------------------------------------------------------------------------- /app/views/my/update_text.js.erb: -------------------------------------------------------------------------------- 1 | $("#my_page_<%= block_name %>").html( 2 | '<%= escape_javascript render('my/text', text: text, block_name: block_name) %>'); 3 | -------------------------------------------------------------------------------- /config/locales/ja.yml: -------------------------------------------------------------------------------- 1 | ja: 2 | button_default_layout: "デフォルトに戻す" 3 | my_page_query_limit: "%{limits} 個まで表示する" 4 | my_page_query_compact: "簡易表示" 5 | my_page_query_full: "詳細表示" 6 | -------------------------------------------------------------------------------- /lib/my_page_queries.rb: -------------------------------------------------------------------------------- 1 | module MyPageQueries 2 | TEXT_BLOCK = 'text' 3 | end 4 | 5 | require 'my_page_queries/patches/user_patch' 6 | require 'my_page_queries/patches/my_controller_patch' 7 | -------------------------------------------------------------------------------- /config/database-mysql-travis.yml: -------------------------------------------------------------------------------- 1 | # http://about.travis-ci.org/docs/user/database-setup/#MySQL 2 | test: 3 | adapter: mysql2 4 | database: redmine 5 | username: travis 6 | encoding: utf8 7 | -------------------------------------------------------------------------------- /config/locales/tr.yml: -------------------------------------------------------------------------------- 1 | tr: 2 | button_default_layout: "Varsayılanı yükle" 3 | my_page_query_limit: "Yalnızca %{limits} tane iş göster" 4 | my_page_query_compact: "Daraltılmış görünüm" 5 | my_page_query_full: "Genişletilmiş görünüm" 6 | -------------------------------------------------------------------------------- /config/locales/zh.yml: -------------------------------------------------------------------------------- 1 | zh: 2 | button_default_layout: "重置默认" 3 | my_page_query_limit: "选择列表上所显示的问题数 %{limits}" 4 | my_page_query_compact: "简单视图" 5 | my_page_query_full: "详细视图" 6 | label_queries_from_my_projects: "我的项目-对我可见的自定义查询" 7 | label_queries_from_public_projects: "公开项目-对我可见的自定义查询" 8 | -------------------------------------------------------------------------------- /assets/stylesheets/my_page_queries.css: -------------------------------------------------------------------------------- 1 | div.block-receiver div.mypage-box { 2 | background-color: #FDFDFD; 3 | border: solid 1px #D3D3D3; 4 | margin: 1px; 5 | padding: 1px; 6 | } 7 | 8 | div.my-page-text-edit-button { 9 | font-size: 70%; 10 | display: inline; 11 | margin-left: 5px; 12 | } 13 | -------------------------------------------------------------------------------- /config/locales/ru.yml: -------------------------------------------------------------------------------- 1 | ru: 2 | button_default_layout: "По умолчанию" 3 | my_page_query_limit: "Показывать %{limits} задач" 4 | my_page_query_compact: "Компактно" 5 | my_page_query_full: "Развернуто" 6 | label_queries_from_my_projects: "Запросы из моих проектов" 7 | label_queries_from_public_projects: "Запросы из публичных проектов" 8 | -------------------------------------------------------------------------------- /config/locales/cs.yml: -------------------------------------------------------------------------------- 1 | cs: 2 | button_default_layout: "Obnovit výchozí" 3 | my_page_query_limit: "Zobraz jen %{limits} úkolů" 4 | my_page_query_compact: "Kompaktní vzhled" 5 | my_page_query_full: "Široký vzhled" 6 | label_queries_from_my_projects: "Úkoly z mých projektů" 7 | label_queries_from_public_projects: "Úkoly z veřejných projektů" 8 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | button_default_layout: "Restore default" 3 | my_page_query_limit: "Show only %{limits} issues" 4 | my_page_query_compact: "Compact view" 5 | my_page_query_full: "Extended view" 6 | label_queries_from_my_projects: "Queries from my projects" 7 | label_queries_from_public_projects: "Queries from public projects" 8 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | RedmineApp::Application.routes.draw do 2 | post 'my/default_layout', to: 'my#default_layout' 3 | put 'my/update_query_block/:query_id', 4 | to: 'my#update_query_block', 5 | as: :update_query_block 6 | put 'my/update_text_block/:block_name', 7 | to: 'my#update_text_block', 8 | as: :update_text_block 9 | end 10 | -------------------------------------------------------------------------------- /config/locales/de.yml: -------------------------------------------------------------------------------- 1 | de: 2 | button_default_layout: "Standard wiederherstellen" 3 | my_page_query_limit: "Nur %{limits} anzeigen" 4 | my_page_query_compact: "Kompaktansicht" 5 | my_page_query_full: "Erweiterte Ansicht" 6 | label_queries_from_my_projects: "Filter von meinen Projekten" 7 | label_queries_from_public_projects: "Filter von öffentlichen Projekten" 8 | -------------------------------------------------------------------------------- /config/locales/pl.yml: -------------------------------------------------------------------------------- 1 | pl: 2 | button_default_layout: "Przywróć domyślne" 3 | my_page_query_limit: "Pokaż tylko %{limits} zagadnień" 4 | my_page_query_compact: "Widok kompaktowy" 5 | my_page_query_full: "Widok rozszerzony" 6 | label_queries_from_my_projects: "Kwerendy z moich projektów" 7 | label_queries_from_public_projects: "Kwerendy z projektów publicznych" 8 | -------------------------------------------------------------------------------- /config/locales/pt-BR.yml: -------------------------------------------------------------------------------- 1 | pt-BR: 2 | button_default_layout: "Restaurar padrão" 3 | my_page_query_limit: "Mostrar somente %{limits} tarefas" 4 | my_page_query_compact: "Visão compacta" 5 | my_page_query_full: "Visão estendida" 6 | label_queries_from_my_projects: "Consultas dos meus projetos" 7 | label_queries_from_public_projects: "Consultas dos projetos públicos" 8 | -------------------------------------------------------------------------------- /app/views/my/_text.html.erb: -------------------------------------------------------------------------------- 1 | <% unless action_name == 'page' %> 2 |

3 |
4 | <%= link_to l(:button_edit), '#', 5 | class: 'edit-my-page-text', 6 | data: { text: text, url: update_text_block_path(block_name) } %> 7 |
8 |

9 | <% end %> 10 | <%= textilizable text %> 11 | -------------------------------------------------------------------------------- /config/locales/fr.yml: -------------------------------------------------------------------------------- 1 | fr: 2 | button_default_layout: "Restaurer la vue par défaut" 3 | my_page_query_limit: "Afficher seulement %{limits} demandes" 4 | my_page_query_compact: "Vue compacte" 5 | my_page_query_full: "Vue étendue" 6 | label_queries_from_my_projects: "Rapports depuis mes projets" 7 | label_queries_from_public_projects: "Rapports depuis les projets publics" 8 | -------------------------------------------------------------------------------- /app/views/my/_block.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= link_to '', 4 | { action: 'remove_block', block: block_name }, 5 | method: 'post', 6 | class: 'close-icon' %> 7 |
8 |
9 | <%= render_block(user, block_name) %> 10 |
11 |
12 | -------------------------------------------------------------------------------- /init.rb: -------------------------------------------------------------------------------- 1 | require 'redmine' 2 | 3 | ActionDispatch::Callbacks.to_prepare do 4 | require 'my_page_queries' 5 | end 6 | 7 | Redmine::Plugin.register :redmine_my_page_queries do 8 | name 'MyPage custom queries' 9 | description 'Adds custom queries onto My Page screen' 10 | version '2.1.7' 11 | author 'Restream' 12 | author_url 'https://github.com/Restream' 13 | url 'https://github.com/Restream/redmine_my_page_queries' 14 | end 15 | -------------------------------------------------------------------------------- /lib/tasks/my_page_queries.rake: -------------------------------------------------------------------------------- 1 | namespace :my_page_queries do 2 | 3 | desc 'Upgrade my_page custom_query user settings' 4 | task :upgrade => [:environment] do 5 | puts 'Find user that have old settings and convert them.' 6 | User.active.order(User.fields_for_order_statement).each do |user| 7 | old = user.pref.others.inspect 8 | MyPageQueries::UpgradeService.upgrade_settings(user) 9 | if old != user.pref.others.inspect 10 | puts "Settings of #{user.name} has converted (old/new): \n\t#{old}\n\t#{user.pref.others.inspect}\n" 11 | end 12 | end 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /app/views/my/blocks/_label_my_queries.html.erb: -------------------------------------------------------------------------------- 1 |

<%= l(:label_my_queries) %>

2 | 3 | <% 4 | queries_by_project = @user.my_visible_queries.group_by(&:project) 5 | global_queries = queries_by_project.delete(nil) || [] 6 | %> 7 | 8 | <% if global_queries.any? %> 9 |

<%= l(:field_is_for_all) %>

10 | 15 | <% end %> 16 | 17 | <% if queries_by_project.any? %> 18 | <% queries_by_project.each do |project, queries| %> 19 |

<%= link_to_project project %>

20 | 25 | <% end %> 26 | <% end %> 27 | -------------------------------------------------------------------------------- /assets/javascripts/my_page_queries.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | $("#content").on("ajax:complete", ".mypage-box", function(e, data) { 3 | if (data.status == 200) { 4 | var container = $(this).children('.handle')[0] || $(this); 5 | $(container).html(data.responseText); 6 | } 7 | }); 8 | 9 | $('#content').on('click', '.edit-my-page-text', function(event) { 10 | var text = $(this).data("text"); 11 | var url = $(this).data("url"); 12 | event.preventDefault(); 13 | $("#ajax-modal").html(window.myPageTextModal); 14 | $("#my_page_text_area").val(text); 15 | $("form#my-page-text-form").attr("action", url); 16 | showModal("ajax-modal", "60%"); 17 | $('#my_page_text_area').focus(); 18 | $("#ajax-modal").addClass("my-page-text"); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | 3 | services: 4 | - mysql 5 | 6 | rvm: 7 | - 2.3.1 8 | 9 | env: 10 | - REDMINE_VER=3.1.7 11 | - REDMINE_VER=3.2.4 12 | - REDMINE_VER=3.3.1 13 | 14 | before_install: 15 | - export PLUGIN_NAME=redmine_my_page_queries 16 | - export DB=mysql 17 | - export REDMINE_PATH=$HOME/redmine 18 | - svn co http://svn.redmine.org/redmine/tags/$REDMINE_VER $REDMINE_PATH 19 | - ln -s $TRAVIS_BUILD_DIR $REDMINE_PATH/plugins/$PLUGIN_NAME 20 | - cp config/database-$DB-travis.yml $REDMINE_PATH/config/database.yml 21 | - cd $REDMINE_PATH 22 | 23 | before_script: 24 | - export RAILS_ENV=test 25 | - bundle exec rake db:create db:migrate 26 | 27 | script: 28 | - export RAILS_ENV=test 29 | - export RUBYOPT="-W0" 30 | - bundle exec rake redmine:plugins:test NAME=$PLUGIN_NAME 31 | -------------------------------------------------------------------------------- /app/views/my/_my_page_text_modal.html.erb: -------------------------------------------------------------------------------- 1 |

<%= l(:label_string) %>

2 | 3 | <%= form_tag('block_path', 4 | remote: true, 5 | method: :put, 6 | id: 'my-page-text-form') do %> 7 | 8 |
9 | <%= text_area_tag 'my_page_text_area', 10 | 'text', 11 | rows: 10, 12 | accesskey: accesskey(:edit), 13 | class: 'wiki-edit', 14 | no_label: true %> 15 | <%= wikitoolbar_for 'my_page_text_area' %> 16 |
17 | 18 |

19 | <%= submit_tag l(:button_save), 20 | id: 'my-page-text-submit', 21 | name: nil, 22 | onclick: 'hideModal(this);' %> 23 | <%= submit_tag l(:button_cancel), 24 | id: 'my-page-text-cancel', 25 | name: nil, 26 | onclick: 'hideModal(this);', 27 | type: 'button' %> 28 |

29 | <% end %> 30 | -------------------------------------------------------------------------------- /app/views/my/page.html.erb: -------------------------------------------------------------------------------- 1 | <%= stylesheet_link_tag 'my_page_queries', plugin: 'redmine_my_page_queries' %> 2 | <%= javascript_include_tag 'my_page_queries', plugin: 'redmine_my_page_queries' %> 3 |
4 | <%= link_to l(:label_personalize_page), action: 'page_layout' %> 5 |
6 | 7 |

<%= l(:label_my_page) %>

8 | 9 |
10 | <% @blocks['top'].each do |b| 11 | next unless block_exists?(@user, b) %> 12 |
13 | <%= render_block(@user, b) %> 14 |
15 | <% end if @blocks['top'] %> 16 |
17 | 18 |
19 | <% @blocks['left'].each do |b| 20 | next unless block_exists?(@user, b) %> 21 |
22 | <%= render_block(@user, b) %> 23 |
24 | <% end if @blocks['left'] %> 25 |
26 | 27 |
28 | <% @blocks['right'].each do |b| 29 | next unless block_exists?(@user, b) %> 30 |
31 | <%= render_block(@user, b) %> 32 |
33 | <% end if @blocks['right'] %> 34 |
35 | 36 | <%= context_menu issues_context_menu_path %> 37 | 38 | <% html_title(l(:label_my_page)) -%> 39 | -------------------------------------------------------------------------------- /test/unit/helpers/my_page_queries_helper_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../test_helper', __FILE__) 2 | 3 | class MyPageQueriesHelperTest < ActionView::TestCase 4 | fixtures :users, :user_preferences, :roles, :projects, :members, :member_roles, 5 | :issues, :issue_statuses, :trackers, :enumerations, :custom_fields, 6 | :auth_sources, :queries 7 | 8 | def test_extract_query_id_from_block 9 | block = 'query_123' 10 | block_id = extract_query_id_from_block(block) 11 | assert_equal 123, block_id 12 | end 13 | 14 | def test_query_from_block 15 | user = User.find(4) 16 | block = 'query_4' 17 | query = query_from_block(user, block) 18 | assert query 19 | assert_equal 4, query.id 20 | end 21 | 22 | def test_block_exists_for_query 23 | user = User.find(4) 24 | block = 'query_4' 25 | assert block_exists?(user, block) 26 | end 27 | 28 | def test_block_exists_for_std_block 29 | user = User.find(4) 30 | block = 'issuesassignedtome' 31 | assert block_exists?(user, block) 32 | end 33 | 34 | def test_text_block 35 | assert text_block?('text_1') 36 | assert text_block?('text_12') 37 | refute text_block?('text_123n') 38 | end 39 | 40 | def test_block_exists_for_text_block 41 | user = User.find(4) 42 | block = 'text_1' 43 | assert block_exists?(user, block) 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/my_page_queries/patches/user_patch.rb: -------------------------------------------------------------------------------- 1 | require_dependency 'project' 2 | require_dependency 'principal' 3 | require_dependency 'user' 4 | 5 | module MyPageQueries::Patches::UserPatch 6 | extend ActiveSupport::Concern 7 | 8 | def detect_query(query_id) 9 | visible_queries.detect { |q| q.id == query_id.to_i } 10 | end 11 | 12 | def visible_queries 13 | @visible_queries ||= my_visible_queries.to_a + other_visible_queries.to_a 14 | end 15 | 16 | def my_visible_queries 17 | visible_queries_scope.where('queries.user_id = ?', self.id).order('queries.name') 18 | end 19 | 20 | def other_visible_queries 21 | visible_queries_scope.where('queries.user_id <> ?', self.id).order('queries.name') 22 | end 23 | 24 | def queries_from_my_projects 25 | @queries_from_my_projects ||= other_visible_queries.find_all do |q| 26 | q.is_public? && q.project && member_of?(q.project) 27 | end 28 | end 29 | 30 | def queries_from_public_projects 31 | @queries_from_public_projects ||= other_visible_queries.to_a - queries_from_my_projects 32 | end 33 | 34 | def visible_queries_scope 35 | kl = defined?(IssueQuery) ? IssueQuery : Query 36 | kl.visible(self) 37 | end 38 | 39 | def update_my_page_text_block(block_name, val) 40 | pref[:my_page_text_blocks] ||= {} 41 | pref[:my_page_text_blocks][block_name] = val 42 | pref.save 43 | end 44 | 45 | def my_page_text_block(block_name) 46 | pref[:my_page_text_blocks] && pref[:my_page_text_blocks][block_name] 47 | end 48 | end 49 | 50 | unless User.included_modules.include?(MyPageQueries::Patches::UserPatch) 51 | User.send :include, MyPageQueries::Patches::UserPatch 52 | end 53 | -------------------------------------------------------------------------------- /lib/my_page_queries/upgrade_service.rb: -------------------------------------------------------------------------------- 1 | class MyPageQueries::UpgradeService 2 | class << self 3 | def upgrade_settings(user) 4 | service = self.new(user) 5 | service.send :upgrade_settings 6 | end 7 | end 8 | 9 | private 10 | 11 | attr_reader :user 12 | 13 | OLD_QUERY_RE = /\Acustom_query(\d)\z/ 14 | OLD_BLOCK_RE = /\Aissues_custom_query_(\d)\z/ 15 | 16 | def initialize(_user) 17 | @user = _user 18 | end 19 | 20 | def upgrade_settings 21 | return unless my_page_pref 22 | old_settings = extract_old_settings 23 | replace_old_query_settings(old_settings) 24 | convert_blocks(old_settings) 25 | user.pref.save! 26 | end 27 | 28 | def my_page_pref 29 | user.pref.others[:my_page_layout] 30 | end 31 | 32 | def extract_old_settings 33 | result = {} 34 | user.pref.others.dup.each do |key, val| 35 | if key =~ OLD_QUERY_RE 36 | result[$1] = val 37 | user.pref.others.delete(key) 38 | end 39 | end 40 | result 41 | end 42 | 43 | def replace_old_query_settings(settings) 44 | settings.each do |_, options| 45 | new_query_key = "query_#{options[:id]}".to_sym 46 | user.pref.others[new_query_key] ||= {} 47 | user.pref.others[new_query_key][:limit] = options[:limit] 48 | end 49 | end 50 | 51 | def convert_blocks(mapping) 52 | user.pref.others[:my_page_layout].each do |zone, blocks| 53 | blocks.map! do |block| 54 | if block =~ OLD_BLOCK_RE 55 | query_id = mapping[$1] && mapping[$1][:id] 56 | "query_#{query_id}" if query_id 57 | else 58 | block 59 | end 60 | end 61 | blocks.compact! 62 | end 63 | end 64 | 65 | 66 | end 67 | -------------------------------------------------------------------------------- /test/unit/my_page_queries/user_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../test_helper', __FILE__) 2 | 3 | class MyPageQueries::UserTest < ActionView::TestCase 4 | fixtures :users, :user_preferences, :roles, :projects, :members, :member_roles, 5 | :issues, :issue_statuses, :trackers, :enumerations, :custom_fields, 6 | :auth_sources, :queries, :enabled_modules 7 | 8 | def setup 9 | @user = User.find(2) 10 | end 11 | 12 | def test_my_visible_queries 13 | query_ids = @user.my_visible_queries.map(&:id).sort 14 | assert_equal [4, 7, 8], query_ids 15 | end 16 | 17 | def test_other_visible_queries 18 | query_ids = @user.other_visible_queries.map(&:id).sort 19 | assert_equal [1, 5, 6, 9], query_ids 20 | end 21 | 22 | def test_visible_queries 23 | query_ids = @user.visible_queries.map(&:id).sort 24 | assert_equal [1, 4, 5, 6, 7, 8, 9], query_ids 25 | end 26 | 27 | def test_queries_from_my_projects 28 | query_ids = @user.queries_from_my_projects.map(&:id).sort 29 | assert_equal [1], query_ids 30 | end 31 | 32 | def test_queries_from_public_projects 33 | query_ids = @user.queries_from_public_projects.map(&:id).sort 34 | assert_equal [5, 6, 9], query_ids 35 | end 36 | 37 | def test_detect_query 38 | assert_equal 4, @user.detect_query(4).id 39 | assert_equal 5, @user.detect_query('5').id 40 | assert_equal 6, @user.detect_query(6).id 41 | assert_equal 9, @user.detect_query(9).id 42 | assert_nil @user.detect_query(2) # private query user_id: 3 43 | end 44 | 45 | def test_my_page_custom_text 46 | test_string = 'test string' 47 | @user.update_my_page_text_block('text_1', test_string) 48 | @user.reload 49 | assert_equal test_string, @user.my_page_text_block('text_1') 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /test/unit/query_presenter_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | class QueryPresenterTest < ActionView::TestCase 4 | fixtures :users, :user_preferences, :roles, :projects, :members, :member_roles, 5 | :issues, :issue_statuses, :trackers, :enumerations, :custom_fields, 6 | :auth_sources, :queries, :enabled_modules 7 | 8 | include ActionView::TestCase::Behavior 9 | 10 | def setup 11 | @user = User.find(2) 12 | User.current = @user 13 | @query = Query.find(5) 14 | @query_presenter = QueryPresenter.new(@query, view) 15 | end 16 | 17 | def test_query_methods_available 18 | assert_equal @query.issue_count, @query_presenter.issue_count 19 | end 20 | 21 | def test_title 22 | title = "#{@query.name} (#{@query.issue_count})" 23 | assert_equal title, @query_presenter.title 24 | end 25 | 26 | def test_title_with_project 27 | project_query = Query.find(7) 28 | project_query_presenter = QueryPresenter.new(project_query, view) 29 | title = "#{project_query.project.name} - #{project_query.name} (#{project_query.issue_count})" 30 | assert_equal title, project_query_presenter.title 31 | end 32 | 33 | def test_link 34 | assert_equal 'title', 35 | @query_presenter.link('title') 36 | end 37 | 38 | def test_issues 39 | assert_not_empty @query_presenter.issues 40 | end 41 | 42 | def test_default_limit 43 | assert_equal QueryPresenter::DEFAULT_LIMIT, @query_presenter.limit 44 | end 45 | 46 | def test_limit 47 | @user.pref[:query_5] = { limit: 20 } 48 | @user.pref.save! 49 | @user.pref.reload 50 | assert_equal 20, @query_presenter.limit 51 | end 52 | 53 | def test_available_limits 54 | limits = [1, 3, 5, 10, 25, 50, 100] 55 | assert_equal limits, @query_presenter.send(:available_limits) 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /test/unit/my_page_queries/upgrade_service_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../test_helper', __FILE__) 2 | 3 | class MyPageQueries::UpgradeServiceTest < ActionView::TestCase 4 | fixtures :users, :user_preferences, :roles, :projects, :members, :member_roles, 5 | :issues, :issue_statuses, :trackers, :enumerations, :custom_fields, 6 | :auth_sources, :queries 7 | 8 | def setup 9 | @user = User.find(2) 10 | @user.pref.save! 11 | end 12 | 13 | def test_upgrade_old_settings 14 | old_settings = { 15 | my_page_layout: { 16 | 'left' => ['issuesassignedtome', 'issues_custom_query_1'], 17 | 'right' => ['issues_custom_query_2'], 18 | 'top' => ['issues_custom_query_3', 'issuesreportedbyme'] 19 | }, 20 | custom_query1: { id: 21, limit: 30 }, 21 | custom_query2: { id: 22, limit: 20 }, 22 | custom_query3: { id: 23, limit: 10 }, 23 | query_23: { some: 'option' } 24 | } 25 | new_settings = { 26 | my_page_layout: { 27 | 'left' => ['issuesassignedtome', 'query_21'], 28 | 'right' => ['query_22'], 29 | 'top' => ['query_23', 'issuesreportedbyme'] 30 | }, 31 | query_21: { limit: 30 }, 32 | query_22: { limit: 20 }, 33 | query_23: { some: 'option', limit: 10 } 34 | } 35 | @user.pref.others.merge! old_settings 36 | @user.pref.save! 37 | MyPageQueries::UpgradeService.upgrade_settings(@user) 38 | user = User.find(2) 39 | 40 | assert_equal new_settings[:my_page_layout], user.pref.others[:my_page_layout] 41 | assert_equal new_settings[:query_21], user.pref.others[:query_21] 42 | assert_equal new_settings[:query_22], user.pref.others[:query_22] 43 | assert_equal new_settings[:query_23], user.pref.others[:query_23] 44 | refute user.pref.others[:custom_query1] 45 | refute user.pref.others[:custom_query2] 46 | refute user.pref.others[:custom_query3] 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /test/functional/my_page_queries/projects_controller_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../test_helper', __FILE__) 2 | 3 | #require 'projects_controller' 4 | 5 | class MyPageQueries::ProjectsControllerTest < ActionController::TestCase 6 | fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details, 7 | :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages, 8 | :attachments, :custom_fields, :custom_values, :time_entries 9 | 10 | def setup 11 | @controller = ProjectsController.new 12 | @request = ActionController::TestRequest.new 13 | @response = ActionController::TestResponse.new 14 | @request.session[:user_id] = nil 15 | Setting.default_language = 'en' 16 | end 17 | 18 | def test_create_project 19 | @request.session[:user_id] = 1 20 | post :create, 21 | project: { 22 | name: 'blog', 23 | description: 'weblog', 24 | homepage: 'http://weblog', 25 | identifier: 'blog', 26 | is_public: 1, 27 | custom_field_values: { '3' => 'Beta' }, 28 | tracker_ids: ['1', '3'], 29 | # an issue custom field that is not for all project 30 | issue_custom_field_ids: ['9'], 31 | enabled_module_names: ['issue_tracking', 'news', 'repository'] 32 | } 33 | assert_redirected_to '/projects/blog/settings' 34 | 35 | project = Project.find_by_name('blog') 36 | assert_kind_of Project, project 37 | assert project.active? 38 | assert_equal 'weblog', project.description 39 | assert_equal 'http://weblog', project.homepage 40 | assert_equal true, project.is_public? 41 | assert_nil project.parent 42 | assert_equal 'Beta', project.custom_value_for(3).value 43 | assert_equal [1, 3], project.trackers.map(&:id).sort 44 | assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort 45 | assert project.issue_custom_fields.include?(IssueCustomField.find(9)) 46 | end 47 | 48 | end 49 | -------------------------------------------------------------------------------- /app/views/my/page_layout.html.erb: -------------------------------------------------------------------------------- 1 | <%= stylesheet_link_tag 'my_page_queries', plugin: 'redmine_my_page_queries' %> 2 | <%= javascript_include_tag 'my_page_queries', plugin: 'redmine_my_page_queries' %> 3 |
4 | <% if @block_options.present? %> 5 | <%= form_tag({ action: 'add_block' }, id: 'block-form') do %> 6 | <%= label_tag('block-select', l(:label_my_page_block)) %>: 7 | <%= select_tag 'block', block_options_for_select, id: 'block-select' %> 8 | <%= link_to l(:button_add), '#', onclick: '$("#block-form").submit()', class: 'icon icon-add' %> 9 | <% end %> 10 | <% end %> 11 | <%= link_to l(:button_save), { action: 'page' }, class: 'icon icon-save' %> 12 | <%= form_tag({ action: 'default_layout' }, id: 'default-layout-form') do %> 13 | <%= link_to l(:button_default_layout), '#', onclick: '$("#default-layout-form").submit()', class: 'icon icon-reload' %> 14 | <% end %> 15 |
16 | 17 |

<%= l(:label_my_page) %>

18 | 19 |
20 | <% @blocks['top'].each do |b| 21 | next unless block_exists?(@user, b) %> 22 | <%= render 'block', user: @user, block_name: b %> 23 | <% end if @blocks['top'] %> 24 |
25 | 26 |
27 | <% @blocks['left'].each do |b| 28 | next unless block_exists?(@user, b) %> 29 | <%= render 'block', user: @user, block_name: b %> 30 | <% end if @blocks['left'] %> 31 |
32 | 33 |
34 | <% @blocks['right'].each do |b| 35 | next unless block_exists?(@user, b) %> 36 | <%= render 'block', user: @user, block_name: b %> 37 | <% end if @blocks['right'] %> 38 |
39 | 40 | <%= javascript_tag "initMyPageSortable('top', '#{ escape_javascript url_for(action: 'order_blocks', group: 'top') }');" %> 41 | <%= javascript_tag "initMyPageSortable('left', '#{ escape_javascript url_for(action: 'order_blocks', group: 'left') }');" %> 42 | <%= javascript_tag "initMyPageSortable('right', '#{ escape_javascript url_for(action: 'order_blocks', group: 'right') }');" %> 43 | 44 | <% html_title(l(:label_my_page)) -%> 45 | 46 | <%= javascript_tag do %> 47 | var myPageTextModal = '<%= escape_javascript render('my/my_page_text_modal') %>'; 48 | <% end %> 49 | -------------------------------------------------------------------------------- /app/views/my/_query_block_2_1_0.html.erb: -------------------------------------------------------------------------------- 1 |

<%= query.title %>

2 | <% if query.compact_view? -%> 3 | <%= render partial: 'issues/list_simple', locals: { issues: query.issues } %> 4 | <% else -%> 5 | <%= form_tag({}) do -%> 6 | <%= hidden_field_tag 'back_url', url_for(params), id: nil %> 7 |
8 | 9 | 10 | 11 | 16 | 17 | <% query.columns.each do |column| %> 18 | <%= query.column_header(column) %> 19 | <% end %> 20 | 21 | 22 | <% previous_group = false %> 23 | 24 | <% issue_list(query.issues) do |issue, level| -%> 25 | <% if query.grouped? && (group = query.group_by_column.value(issue)) != previous_group %> 26 | <% reset_cycle %> 27 | 28 | 34 | 35 | <% previous_group = group %> 36 | <% end %> 37 | "> 38 | 39 | 40 | <%= raw query.columns.map { |column| "" }.join %> 41 | 42 | <% end -%> 43 | 44 |
12 | <%= link_to image_tag('toggle_check.png'), {}, 13 | onclick: 'toggleIssuesSelection(this); return false;', 14 | title: "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %> 15 | #
29 |   30 | <%= group.blank? ? 'None' : column_content(query.group_by_column, issue) %> 31 | (<%= query.issue_count_by_group[group] %>) 32 | <%= link_to_function("#{l(:button_collapse_all)}/#{l(:button_expand_all)}", 'toggleAllRowGroups(this)', class: 'toggle-all') %> 33 |
<%= check_box_tag('ids[]', issue.id, false, id: nil) %><%= link_to issue.id, issue_path(issue) %>#{column_content(column, issue)}
45 |
46 | <% end -%> 47 | <% end -%> 48 |

49 | <%= query.pagination_links %> 50 |

51 | 52 | -------------------------------------------------------------------------------- /app/views/my/_query_block.html.erb: -------------------------------------------------------------------------------- 1 |

<%= query.title %>

2 | <% if query.compact_view? -%> 3 | <%= render partial: 'issues/list_simple', locals: { issues: query.issues } %> 4 | <% else -%> 5 | <%= form_tag({}) do -%> 6 | <%= hidden_field_tag 'back_url', url_for(params), id: nil %> 7 |
8 | 9 | 10 | 11 | 16 | <% query.inline_columns.each do |column| %> 17 | <%= query.column_header(column) %> 18 | <% end %> 19 | 20 | 21 | <% previous_group = false %> 22 | 23 | <% issue_list(query.issues) do |issue, level| -%> 24 | <% if query.grouped? && (group = query.group_by_column.value(issue)) != previous_group %> 25 | <% reset_cycle %> 26 | 27 | 34 | 35 | <% previous_group = group %> 36 | <% end %> 37 | "> 38 | 39 | <%= raw query.inline_columns.map { |column| "" }.join %> 40 | 41 | <% query.block_columns.each do |column| 42 | if (text = column_content(column, issue)) && text.present? -%> 43 | 44 | 45 | 46 | <% end -%> 47 | <% end -%> 48 | <% end -%> 49 | 50 |
12 | <%= link_to image_tag('toggle_check.png'), {}, 13 | onclick: 'toggleIssuesSelection(this); return false;', 14 | title: "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %> 15 |
28 |   29 | <%= group.blank? ? l(:label_none) : column_content(query.group_by_column, issue) %> 30 | <%= query.issue_count_by_group[group] %> 31 | <%= link_to_function("#{l(:button_collapse_all)}/#{l(:button_expand_all)}", 32 | 'toggleAllRowGroups(this)', class: 'toggle-all') %> 33 |
<%= check_box_tag('ids[]', issue.id, false, id: nil) %>#{column_content(column, issue)}
<%= text %>
51 |
52 | <% end -%> 53 | <% end -%> 54 |

55 | <%= query.pagination_links %> 56 |

57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Redmine My Page Queries 2 | 3 | [![Build Status](https://travis-ci.org/Restream/redmine_my_page_queries.svg?branch=master)](https://travis-ci.org/Restream/redmine_my_page_queries) 4 | [![Code Climate](https://codeclimate.com/github/Restream/redmine_my_page_queries.png)](https://codeclimate.com/github/Restream/redmine_my_page_queries) 5 | 6 | This plugin enhances **My page** screen in Redmine with the following blocks: 7 | 8 | * **My custom queries** block 9 | * Separate blocks for each query with either compact or extended view 10 | * **Restore default** button to reset the screen layout 11 | * **Text** block to add custom text 12 | 13 | The initial author is [ALVILA](https://github.com/alvila/redmine_my_page_queries). 14 | 15 | ## Compatibility 16 | 17 | This plugin version is compatible with Redmine 3.x. 18 | 19 | ## Installation 20 | 21 | 22 | 1. To install the plugin 23 | * Download the .ZIP archive, extract files and copy the plugin directory into #{REDMINE_ROOT}/plugins. 24 | 25 | Or 26 | 27 | * Change you current directory to your Redmine root directory: 28 | 29 | cd {REDMINE_ROOT} 30 | 31 | Copy the plugin from GitHub using the following commands: 32 | 33 | git clone https://github.com/Restream/redmine_my_page_queries.git plugins/redmine_my_page_queries 34 | 35 | 2. Update the Gemfile.lock file by running the following commands: 36 | 37 | rm Gemfile.lock 38 | bundle install 39 | 40 | 3. Restart Redmine. 41 | 42 | Now you should be able to see the plugin in **Administration > Plugins**. 43 | 44 | If you are upgrading Redmine from previous versions (prior to 2.0.0) and you want to save user settings related to custom queries on the **My page** screen, run the following command: 45 | 46 | rake my_page_queries:upgrade RAILS_ENV=production 47 | 48 | ## Usage 49 | 50 | This plugin enhances the **My page** screen with several useful blocks related to user's custom queries. 51 | 52 | To add a block that displays all your queries, click **Personalize this page**, select **My custom queries** in the drop-down list and then click **Add**. 53 | ![custom queries](doc/my_page_queries_1.png) 54 | 55 | To add a block with issues that match a particular query, select this query name in the **My custom queries** section of the drop-down list, and click **Add**. 56 | 57 | The **Queries from my projects** section in the drop-down list contains all public filters created by other users based on your projects. The **Queries from public projects** section shows other public filters from public projects. 58 | 59 | You can switch between a compact and extended view by clicking the corresponding links. 60 | ![compact and extended view](doc/my_page_queries_2.png) 61 | 62 | To reset the layout of the **My page** screen, click **Restore default**. 63 | 64 | ## Maintainers 65 | 66 | Danil Tashkinov, [github.com/nodecarter](https://github.com/nodecarter) 67 | -------------------------------------------------------------------------------- /app/helpers/my_page_queries_helper.rb: -------------------------------------------------------------------------------- 1 | module MyPageQueriesHelper 2 | def block_exists?(user, block) 3 | MyController::BLOCKS.keys.include?(block) || 4 | query_from_block(user, block) || 5 | text_block?(block) 6 | end 7 | 8 | def extract_query_id_from_block(block) 9 | $1.to_i if block =~ /\Aquery_(\d+)\z/ 10 | end 11 | 12 | def query_from_block(user, block) 13 | query_id = extract_query_id_from_block(block) 14 | user.detect_query(query_id) 15 | end 16 | 17 | def text_block?(block) 18 | block =~ /\Atext_(\d+)\z/ 19 | end 20 | 21 | def render_block(user, block_name) 22 | if (query = query_from_block(user, block_name)) 23 | query_presenter = QueryPresenter.new(query, self) 24 | render query_block_partial_name, 25 | user: user, 26 | query: query_presenter 27 | elsif text_block?(block_name) 28 | render 'my/text_block', 29 | user: user, 30 | block_name: block_name, 31 | text: user.my_page_text_block(block_name) 32 | else 33 | render "my/blocks/#{block_name}", user: user 34 | end 35 | end 36 | 37 | def query_block_partial_name 38 | 'my/query_block' 39 | end 40 | 41 | def link_to_query(query, html_options = {}) 42 | url_params = { controller: 'issues', action: 'index', query_id: query.id } 43 | url_params[:project_id] = query.project.identifier if query.project 44 | link_to query.name, url_params, html_options 45 | end 46 | 47 | def block_options_for_select(user = User.current) 48 | my_page_blocks = @block_options + [[l(:field_text), MyPageQueries::TEXT_BLOCK]] 49 | content_tag('option') + 50 | grouped_options_for_select(l(:label_my_page_block) => my_page_blocks) + 51 | grouped_options_for_select(my_queries(user)) + 52 | grouped_options_for_select(queries_from_my_projects(user)) + 53 | grouped_options_for_select(queries_from_public_projects(user)) 54 | end 55 | 56 | def my_queries(user) 57 | queries = reject_used_queries(user.my_visible_queries) 58 | queries.empty? ? {} : { l(:label_my_queries) => grouped_queries_for_select(queries) } 59 | end 60 | 61 | def queries_from_my_projects(user) 62 | queries = reject_used_queries(user.queries_from_my_projects) 63 | queries.empty? ? {} : { l(:label_queries_from_my_projects) => grouped_queries_for_select(queries) } 64 | end 65 | 66 | def queries_from_public_projects(user) 67 | queries = reject_used_queries(user.queries_from_public_projects) 68 | queries.empty? ? {} : { l(:label_queries_from_public_projects) => grouped_queries_for_select(queries) } 69 | end 70 | 71 | def grouped_queries_for_select(queries) 72 | result = [] 73 | 74 | by_project = queries.group_by { |q| q.project } 75 | global = by_project.delete(nil) || [] 76 | 77 | result += global.map { |q| [q.name, query_string_id(q)] } if global.any? 78 | 79 | by_project.each do |project, queries| 80 | result += queries.map { |q| ["#{project.name} - #{q.name}", query_string_id(q)] } 81 | end 82 | 83 | result 84 | end 85 | 86 | def reject_used_queries(queries) 87 | queries.reject { |q| @blocks.values.flatten.include? query_string_id(q) } 88 | end 89 | 90 | def query_string_id(query) 91 | "query_#{query.id}" 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /lib/my_page_queries/patches/my_controller_patch.rb: -------------------------------------------------------------------------------- 1 | require_dependency 'query' 2 | require_dependency 'issue_query' 3 | 4 | module MyPageQueries::Patches::MyControllerPatch 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | 9 | before_filter :apply_default_layout, 10 | only: [:add_block, :remove_block], 11 | if: proc { User.current.pref[:my_page_layout].nil? } 12 | 13 | before_filter :my_page_sort_init 14 | 15 | alias_method_chain :add_block, :query_and_text 16 | 17 | helper :sort 18 | include SortHelper 19 | helper :queries 20 | include QueriesHelper 21 | 22 | helper :my_page_queries 23 | include MyPageQueriesHelper 24 | 25 | helper_method :per_page_option 26 | end 27 | 28 | def default_layout 29 | @user = User.current 30 | # remove block in all groups 31 | @user.pref[:my_page_layout] = nil 32 | @user.pref.save 33 | redirect_to action: 'page_layout' 34 | end 35 | 36 | def add_block_with_query_and_text(user = User.current) 37 | if (block = detect_query_block_from_params) 38 | add_block_to_top(user, block) 39 | redirect_to action: 'page_layout' 40 | elsif (block = detect_new_text_block_from_params) 41 | user.update_my_page_text_block(block, l(:label_text)) if user.my_page_text_block(block).blank? 42 | add_block_to_top(user, block) 43 | redirect_to action: 'page_layout' 44 | else 45 | add_block_without_query_and_text 46 | end 47 | end 48 | 49 | def update_query_block 50 | @user = User.current 51 | query = @user.detect_query params[:query_id] 52 | if query 53 | @block_name = "query_#{query.id}" 54 | update_user_query_pref_from_param(@user) 55 | render 'query_block', layout: false 56 | else 57 | render_404 58 | end 59 | end 60 | 61 | def update_text_block 62 | @user = User.current 63 | text = params[:my_page_text_area] 64 | block_name = params[:block_name] 65 | @user.update_my_page_text_block(block_name, text) 66 | render 'update_text', 67 | layout: false, 68 | content_type: 'text/javascript', 69 | locals: { 70 | block_name: block_name, 71 | text: text 72 | } 73 | end 74 | 75 | private 76 | 77 | def apply_default_layout 78 | user = User.current 79 | # make a deep copy of default layout 80 | user.pref[:my_page_layout] = Marshal.load(Marshal.dump(MyController::DEFAULT_LAYOUT)) 81 | user.save 82 | end 83 | 84 | def my_page_sort_init 85 | sort_init('none') 86 | sort_update(['none']) 87 | end 88 | 89 | def add_block_to_top(user, block) 90 | layout = user.pref[:my_page_layout] || {} 91 | # remove if already present in a group 92 | %w(top left right).each { |f| (layout[f] ||= []).delete block } 93 | # add it on top 94 | layout['top'].unshift block 95 | user.pref[:my_page_layout] = layout 96 | user.pref.save 97 | end 98 | 99 | def detect_query_block_from_params 100 | block = params[:block].to_s.underscore 101 | block if extract_query_id_from_block(block) 102 | end 103 | 104 | def detect_new_text_block_from_params(user = User.current) 105 | block = params[:block].to_s.underscore 106 | return nil unless block == MyPageQueries::TEXT_BLOCK 107 | layout = user.pref[:my_page_layout] || {} 108 | block_id = 1 109 | while true 110 | block = "#{MyPageQueries::TEXT_BLOCK}_#{block_id}" 111 | return block unless %w(top left right).detect { |f| (layout[f] ||= []).include?(block) } 112 | block_id += 1 113 | end 114 | end 115 | 116 | def update_user_query_pref_from_param(user) 117 | return unless params[:query] 118 | query_key = "query_#{params[:query_id]}".to_sym 119 | opts = user.pref[query_key] || {} 120 | opts.merge! params[:query].symbolize_keys 121 | user.pref[query_key] = opts 122 | user.pref.save! 123 | end 124 | end 125 | 126 | unless MyController.included_modules.include?(MyPageQueries::Patches::MyControllerPatch) 127 | MyController.send :include, MyPageQueries::Patches::MyControllerPatch 128 | end 129 | -------------------------------------------------------------------------------- /app/models/query_presenter.rb: -------------------------------------------------------------------------------- 1 | class QueryPresenter < SimpleDelegator 2 | 3 | DEFAULT_LIMIT = 10 4 | 5 | include SortHelper 6 | 7 | def initialize(obj, view_context) 8 | super(obj) 9 | @view = view_context 10 | end 11 | 12 | def title 13 | if project.nil? 14 | "#{name} (#{issue_count})" 15 | else 16 | "#{project.name} - #{name} (#{issue_count})" 17 | end 18 | end 19 | 20 | def link(title) 21 | url_opts = { controller: 'issues', 22 | action: 'index', 23 | query_id: self[:id] } 24 | 25 | url_opts[:project_id] = project.id unless project.nil? 26 | @view.link_to title, url_opts 27 | end 28 | 29 | def issues(options = {}) 30 | options.merge!( 31 | include: [:assigned_to, :tracker, :priority, :category, :fixed_version], 32 | limit: limit, 33 | order: sort_criteria.to_sql 34 | ) 35 | super(options) 36 | end 37 | 38 | def limit 39 | optn = pref_options[:limit] 40 | optn && optn.to_i || DEFAULT_LIMIT 41 | end 42 | 43 | def pagination_links 44 | [ 45 | link(@view.l(:label_issue_view_all)), 46 | limit_links, 47 | view_format_links 48 | ].join(' | ').html_safe 49 | end 50 | 51 | def compact_view? 52 | pref_options[:compact_view].nil? || pref_options[:compact_view] == 'true' 53 | end 54 | 55 | def sort_criteria 56 | @sort_criteria ||= begin 57 | sort_criteria_attr = __getobj__.sort_criteria 58 | query_criteria = sort_criteria_attr.empty? ? [%w(id desc)] : sort_criteria_attr 59 | criteria = SortCriteria.new 60 | criteria.available_criteria = sortable_columns 61 | criteria.from_param(pref_options[:sort]) 62 | criteria.criteria = query_criteria if criteria.empty? 63 | criteria 64 | end 65 | end 66 | 67 | def column_header(column) 68 | column.sortable ? 69 | sort_header_tag(column.name.to_s, caption: column.caption, 70 | default_order: column.default_order) : 71 | @view.content_tag('th', column.caption) 72 | end 73 | 74 | private 75 | 76 | def sort_header_tag(column, options = {}) 77 | caption = options.delete(:caption) || column.to_s.humanize 78 | default_order = options.delete(:default_order) || 'asc' 79 | options[:title] = l(:label_sort_by, "\"#{caption}\"") unless options[:title] 80 | @view.content_tag('th', sort_link(column, caption, default_order), options) 81 | end 82 | 83 | def sort_link(column, caption, default_order) 84 | css, order = nil, default_order 85 | 86 | if column.to_s == sort_criteria.first_key 87 | if sort_criteria.first_asc? 88 | css = 'sort asc' 89 | order = 'desc' 90 | else 91 | css = 'sort desc' 92 | order = 'asc' 93 | end 94 | end 95 | caption = column.to_s.humanize unless caption 96 | 97 | sort_options = { sort: sort_criteria.add(column.to_s, order).to_param } 98 | 99 | url_options = @view.update_query_block_path(self[:id], query: sort_options) 100 | 101 | @view.link_to caption, 102 | url_options, 103 | method: 'put', 104 | remote: true, 105 | class: css 106 | end 107 | 108 | def available_limits 109 | (Setting.per_page_options_array + [1, 3, 5, 10]).sort.uniq 110 | end 111 | 112 | def limit_links 113 | limits = available_limits.map do |q_limit| 114 | @view.link_to q_limit, 115 | @view.update_query_block_path(self[:id], query: { limit: q_limit }), 116 | method: 'put', 117 | remote: true 118 | end.join(', ').html_safe 119 | @view.l(:my_page_query_limit, limits: limits).html_safe 120 | end 121 | 122 | def view_format_links 123 | links = [] 124 | if compact_view? 125 | links << @view.l(:my_page_query_compact) 126 | links << @view.link_to(@view.l(:my_page_query_full), 127 | @view.update_query_block_path( 128 | self[:id], 129 | query: { compact_view: false }), 130 | method: 'put', 131 | remote: true) 132 | else 133 | links << @view.link_to(l(:my_page_query_compact), 134 | @view.update_query_block_path( 135 | self[:id], 136 | query: { compact_view: true }), 137 | method: 'put', 138 | remote: true) 139 | links << @view.l(:my_page_query_full) 140 | end 141 | links.join('/').html_safe 142 | end 143 | 144 | def pref_options 145 | User.current.pref.others[pref_key] || {} 146 | end 147 | 148 | def pref_key 149 | "query_#{self[:id]}".to_sym 150 | end 151 | 152 | end 153 | -------------------------------------------------------------------------------- /test/functional/my_page_queries/my_controller_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../test_helper', __FILE__) 2 | require 'my_controller' 3 | 4 | # Re-raise errors caught by the controller. 5 | class MyController; 6 | def rescue_action(e) 7 | raise e 8 | end 9 | 10 | ; 11 | end 12 | 13 | class MyPageQueries::MyControllerTest < ActionController::TestCase 14 | fixtures :users, :user_preferences, :roles, :projects, :members, :member_roles, 15 | :issues, :issue_statuses, :trackers, :enumerations, :custom_fields, :auth_sources, 16 | :queries, :trackers, :enabled_modules 17 | 18 | def setup 19 | @controller = MyController.new 20 | @request = ActionController::TestRequest.new 21 | @request.session[:user_id] = 2 22 | @response = ActionController::TestResponse.new 23 | @user = User.find(2) 24 | end 25 | 26 | def test_index 27 | get :index 28 | assert_response :success 29 | end 30 | 31 | def test_page_layout_with_query_block 32 | query = Query.find(5) 33 | @user.pref[:my_page_layout] = { 'top' => ['query_5'] } 34 | @user.pref.save! 35 | get :page_layout 36 | assert_response :success 37 | assert_select 'h3', html: /#{query.name}/ 38 | end 39 | 40 | def test_page_layout_with_text_block 41 | @user.update_my_page_text_block('text_1', 'some-text-1') 42 | @user.pref[:my_page_layout] = { 'top' => ['text_1'] } 43 | @user.pref.save! 44 | get :page_layout 45 | assert_response :success 46 | assert_select 'div', html: /some-text-1/ 47 | end 48 | 49 | def test_page_layout_with_already_added_query 50 | query = Query.find(5) 51 | @user.pref[:my_page_layout] = { 'top' => ['query_5'] } 52 | @user.pref.save! 53 | get :page_layout 54 | assert_response :success 55 | assert_select 'option', { html: /#{query.name}/, count: 0 } 56 | end 57 | 58 | def test_textblock_available_even_with_already_added_block 59 | @user.update_my_page_text_block('text_1', 'sometext1') 60 | @user.pref[:my_page_layout] = { 'top' => ['text_1'] } 61 | @user.pref.save! 62 | get :page_layout 63 | assert_response :success 64 | assert_select 'option[value="text"]', true 65 | end 66 | 67 | def test_page_layout_with_missing_query 68 | @user.pref[:my_page_layout] = { 'top' => ['query_66'] } 69 | @user.pref.save! 70 | get :page_layout 71 | assert_response :success 72 | end 73 | 74 | def test_page_layout_with_missing_text 75 | @user.pref[:my_page_layout] = { 'top' => ['text_66'] } 76 | @user.pref.save! 77 | get :page_layout 78 | assert_response :success 79 | end 80 | 81 | def test_add_query_block 82 | post :add_block, block: 'query_5' 83 | assert_redirected_to '/my/page_layout' 84 | assert @user.pref[:my_page_layout] 85 | top_blocks = @user.pref[:my_page_layout]['top'] 86 | assert top_blocks 87 | assert top_blocks.include?('query_5') 88 | end 89 | 90 | def test_add_text_block 91 | post :add_block, block: 'text' 92 | assert_redirected_to '/my/page_layout' 93 | @user.pref.reload 94 | assert @user.pref[:my_page_layout] 95 | top_blocks = @user.pref[:my_page_layout]['top'] 96 | text_blocks = @user.pref[:my_page_text_blocks] 97 | assert top_blocks 98 | assert top_blocks.include?('text_1') 99 | assert text_blocks 100 | assert text_blocks.keys.include?('text_1') 101 | end 102 | 103 | def test_add_second_text_block 104 | @user.update_my_page_text_block('text_1', 'some-text-1') 105 | @user.pref[:my_page_layout] = { 'top' => ['text_1'] } 106 | @user.pref.save! 107 | post :add_block, block: 'text' 108 | assert_redirected_to '/my/page_layout' 109 | @user.pref.reload 110 | assert @user.pref[:my_page_layout] 111 | top_blocks = @user.pref[:my_page_layout]['top'] 112 | assert top_blocks 113 | assert top_blocks.include?('text_2') 114 | end 115 | 116 | def test_add_first_text_block 117 | @user.update_my_page_text_block('text_1', 'some-text-1') 118 | @user.update_my_page_text_block('text_2', 'some-text-2') 119 | @user.pref[:my_page_layout] = { 'top' => ['text_2'] } 120 | @user.pref.save! 121 | post :add_block, block: 'text' 122 | assert_redirected_to '/my/page_layout' 123 | @user.pref.reload 124 | assert @user.pref[:my_page_layout] 125 | top_blocks = @user.pref[:my_page_layout]['top'] 126 | assert top_blocks 127 | assert top_blocks.include?('text_1') 128 | end 129 | 130 | def test_show_query_block 131 | query = Query.find(5) 132 | @user.pref[:my_page_layout] = { 'top' => ['query_5'] } 133 | @user.pref.save! 134 | get :page 135 | assert_response :success 136 | assert_select 'h3', html: /#{query.name}/ 137 | end 138 | 139 | def test_show_text_block 140 | @user.update_my_page_text_block('text_1', 'some-show-text-1') 141 | @user.pref[:my_page_layout] = { 'top' => ['text_1'] } 142 | @user.pref.save! 143 | get :page 144 | assert_response :success 145 | assert_select 'div', html: /some-show-text-1/ 146 | end 147 | 148 | def test_add_query_block_to_default 149 | @user.pref[:my_page_layout] = nil 150 | post :add_block, block: 'query_5' 151 | assert_redirected_to '/my/page_layout' 152 | @user = User.current 153 | MyController::DEFAULT_LAYOUT.each do |position, block| 154 | assert @user.pref[:my_page_layout][position].include?(block.first) 155 | end 156 | end 157 | 158 | def test_add_text_block_to_default 159 | @user.pref[:my_page_layout] = nil 160 | @user.pref[:my_page_text_blocks] = nil 161 | @user.pref.save! 162 | post :add_block, block: 'text' 163 | assert_redirected_to '/my/page_layout' 164 | @user = User.current 165 | MyController::DEFAULT_LAYOUT.each do |position, block| 166 | assert @user.pref[:my_page_layout][position].include?(block.first) 167 | end 168 | top_blocks = @user.pref[:my_page_layout]['top'] 169 | text_blocks = @user.pref[:my_page_text_blocks] 170 | assert top_blocks 171 | assert top_blocks.include?('text_1') 172 | assert text_blocks 173 | assert text_blocks.keys.include?('text_1') 174 | end 175 | 176 | def test_remove_query_block 177 | @user.pref[:my_page_layout] = { 'top' => ['query_5'] } 178 | @user.pref.save! 179 | post :remove_block, block: 'query_5' 180 | assert_redirected_to '/my/page_layout' 181 | @user.pref.reload 182 | refute @user.pref[:my_page_layout].values.flatten.include?('query_5') 183 | end 184 | 185 | def test_remove_text_block 186 | @user.update_my_page_text_block('text_1', 'some-show-text-1') 187 | @user.pref[:my_page_layout] = { 'top' => ['text_1'] } 188 | @user.pref.save! 189 | post :remove_block, block: 'text_1' 190 | assert_redirected_to '/my/page_layout' 191 | @user.pref.reload 192 | refute @user.pref[:my_page_layout].values.flatten.include?('text_1') 193 | assert_equal 'some-show-text-1', @user.my_page_text_block('text_1'), 'Text content should stay persisted' 194 | end 195 | 196 | def test_order_blocks 197 | xhr :post, :order_blocks, group: 'left', 'blocks' => ['query_5', 'calendar', 'latestnews'] 198 | assert_response :success 199 | assert_equal ['query_5', 'calendar', 'latestnews'], @user.pref[:my_page_layout]['left'] 200 | end 201 | 202 | def test_layout_contains_users_queries 203 | get :page_layout 204 | assert_response :success 205 | assert_select 'option[value="query_4"]', true 206 | end 207 | 208 | def test_layout_contains_other_queries 209 | get :page_layout 210 | assert_response :success 211 | assert_select 'option[value="query_5"]', true 212 | assert_select 'option[value="query_6"]', true 213 | assert_select 'option[value="query_9"]', true 214 | end 215 | 216 | def test_update_query_limit 217 | @user.pref[:my_page_layout] = { 'top' => ['query_5'] } 218 | @user.pref[:query_5] = { limit: 20 } 219 | @user.pref.save! 220 | xhr :put, :update_query_block, query_id: 5, query: { limit: 3 } 221 | assert_response :success 222 | @user.pref.reload 223 | assert_equal 3, @user.pref[:query_5][:limit].to_i 224 | end 225 | 226 | end 227 | --------------------------------------------------------------------------------