├── Gemfile ├── test ├── database.travis.yml ├── factories │ ├── member.rb │ ├── default_query.rb │ ├── enabled_module.rb │ ├── project.rb │ ├── user.rb │ ├── issue_query.rb │ └── role.rb ├── test_helper.rb └── integration │ ├── manage_default_query_test.rb │ └── issues_with_default_query_test.rb ├── config ├── routes.rb └── locales │ ├── zh.yml │ ├── ja.yml │ ├── en.yml │ ├── tr.yml │ ├── fr.yml │ └── ru.yml ├── app ├── hooks │ └── issues_controller_hook.rb ├── patches │ ├── models │ │ ├── query_patch.rb │ │ ├── issue_query_patch.rb │ │ └── project_patch.rb │ ├── helpers │ │ └── projects_helper_patch.rb │ └── controllers │ │ └── issues_controller_patch.rb ├── controllers │ └── default_custom_query_setting_controller.rb ├── helpers │ └── default_custom_query_helper.rb ├── views │ ├── default_custom_query_setting │ │ └── _form.html.erb │ └── issues │ │ └── _sidebar_issues_bottom.html.erb └── models │ └── projects_default_query.rb ├── docker-compose.yml ├── Dockerfile ├── db └── migrate │ └── 001_create_projects_default_queries.rb ├── init.rb ├── lib └── default_custom_query.rb ├── .travis.yml ├── CHANGELOG.md ├── MIT-LICENSE └── README.md /Gemfile: -------------------------------------------------------------------------------- 1 | group :test do 2 | gem 'factory_girl' 3 | gem 'rails-controller-testing' 4 | end 5 | -------------------------------------------------------------------------------- /test/database.travis.yml: -------------------------------------------------------------------------------- 1 | test: 2 | adapter: sqlite3 3 | database: db/test.sqlite3 4 | timeout: 500 5 | -------------------------------------------------------------------------------- /test/factories/member.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :member do 3 | mail_notification 0 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/factories/default_query.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :default_query, class: ProjectsDefaultQuery do 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /test/factories/enabled_module.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :enabled_module do 3 | factory :default_custom_query_module do 4 | name 'default_custom_query' 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | RedmineApp::Application.routes.draw do 2 | put ':project_id/default_custom_query/update', :controller => 'default_custom_query_setting', action: 'update', as: 'default_custom_query_setting_update' 3 | end 4 | -------------------------------------------------------------------------------- /app/hooks/issues_controller_hook.rb: -------------------------------------------------------------------------------- 1 | module DefaultCustomQuery 2 | class IssuesControllerViewHooks < Redmine::Hook::ViewListener 3 | render_on :view_issues_sidebar_issues_bottom, partial: 'issues/sidebar_issues_bottom' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | services: 3 | app: 4 | build: 5 | context: . 6 | ports: 7 | - "3000:3000" 8 | command: bash -i -c 'bundle exec rails server -p 3000 -b 0.0.0.0' 9 | container_name: redmine 10 | restart: always 11 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM hidakatsuya/redmine-base:3.1 2 | 3 | COPY . plugins/redmine_default_custom_query 4 | 5 | RUN bundle install 6 | RUN bundle exec rake generate_secret_token 7 | RUN bundle exec rake db:migrate 8 | RUN bundle exec rake redmine:plugins:migrate 9 | 10 | RUN REDMINE_LANG=en bundle exec rake redmine:load_default_data 11 | -------------------------------------------------------------------------------- /test/factories/project.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :project do 3 | sequence(:name) {|n| "Project#{n}"} 4 | sequence(:identifier) {|n| "project-#{n}"} 5 | 6 | trait :with_default_custom_query do 7 | after(:create) do |project| 8 | create :default_custom_query_module, project: project 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/patches/models/query_patch.rb: -------------------------------------------------------------------------------- 1 | require_dependency 'query' 2 | 3 | module DefaultCustomQuery 4 | module QueryPatch 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | scope :only_public, -> { where(visibility: Query::VISIBILITY_PUBLIC) } 9 | end 10 | end 11 | end 12 | 13 | DefaultCustomQuery::QueryPatch.tap do |mod| 14 | Query.send :include, mod unless Query.include?(mod) 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/001_create_projects_default_queries.rb: -------------------------------------------------------------------------------- 1 | class CreateProjectsDefaultQueries < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :projects_default_queries do |t| 4 | t.belongs_to :project 5 | t.belongs_to :query 6 | 7 | t.timestamps null: false 8 | end 9 | add_index :projects_default_queries, :project_id, unique: true 10 | add_index :projects_default_queries, :query_id 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/factories/user.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :user do 3 | sequence(:login, '000') {|n| "user#{n}"} 4 | sequence(:lastname, '0000') 5 | 6 | firstname 'User' 7 | status 1 8 | language 'ja' 9 | mail {|u| "#{u.login}@example.co.jp" } 10 | mail_notification 'only_my_events' 11 | password '12345678' 12 | password_confirmation {|u| u.password } 13 | admin false 14 | type 'User' 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /config/locales/zh.yml: -------------------------------------------------------------------------------- 1 | zh: 2 | project_module_default_custom_query: 默认自定义查询 3 | permission_manage_default_query: 管理默认查询 4 | 5 | # Labels for projects_default_queries column 6 | field_query: 默认自定义查询 7 | 8 | default_custom_query: 9 | label_setting: 默认自定义查询 10 | label_view_default_issues: 查看默认问题列表 11 | label_queries_for_all_projects: 适用于所有项目 12 | label_queries_for_current_project: 适用于当前项目 13 | 14 | text_allowed_queries: 公共查询(仅供选择) -------------------------------------------------------------------------------- /config/locales/ja.yml: -------------------------------------------------------------------------------- 1 | ja: 2 | project_module_default_custom_query: デフォルトカスタムクエリ 3 | permission_manage_default_query: デフォルトカスタムクエリの管理 4 | 5 | # Labels for projects_default_queries column 6 | field_query: デフォルトクエリ 7 | 8 | default_custom_query: 9 | label_setting: デフォルトカスタムクエリ 10 | label_view_default_issues: デフォルトチケット一覧を見る 11 | label_queries_for_all_projects: 全プロジェクト向け 12 | label_queries_for_current_project: 現在プロジェクト 13 | 14 | text_allowed_queries: 公開クエリ(すべてのユーザ)のみ選択可能 -------------------------------------------------------------------------------- /app/controllers/default_custom_query_setting_controller.rb: -------------------------------------------------------------------------------- 1 | class DefaultCustomQuerySettingController < ApplicationController 2 | 3 | before_action :find_project_by_project_id 4 | before_action :authorize 5 | 6 | def update 7 | settings = params[:settings] 8 | 9 | @default_query = ProjectsDefaultQuery.initialize_for(@project.id) 10 | @default_query.query_id = settings[:query_id] 11 | 12 | if @default_query.save 13 | session[:query] = nil 14 | end 15 | 16 | render partial: 'form' 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/patches/models/issue_query_patch.rb: -------------------------------------------------------------------------------- 1 | require_dependency 'issue_query' 2 | 3 | module DefaultCustomQuery 4 | module IssueQueryPatch 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | has_many :projects_default_queries, dependent: :nullify, foreign_key: :query_id 9 | end 10 | 11 | def public_visibility? 12 | visibility == Query::VISIBILITY_PUBLIC 13 | end 14 | end 15 | end 16 | 17 | DefaultCustomQuery::IssueQueryPatch.tap do |mod| 18 | IssueQuery.send :include, mod unless IssueQuery.include?(mod) 19 | end 20 | -------------------------------------------------------------------------------- /test/factories/issue_query.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :issue_query do 3 | sequence(:name) {|n| "IssueQuery#{n}"} 4 | 5 | user_id { User.current.id } 6 | 7 | trait :private do 8 | visibility ::Query::VISIBILITY_PRIVATE 9 | end 10 | 11 | trait :roles do 12 | visibility ::Query::VISIBILITY_ROLES 13 | 14 | before(:create) do |query| 15 | query.roles << create(:role) 16 | end 17 | end 18 | 19 | trait :public do 20 | visibility ::Query::VISIBILITY_PUBLIC 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | project_module_default_custom_query: Default custom query 3 | permission_manage_default_query: Manage default query 4 | 5 | # Labels for projects_default_queries column 6 | field_query: Default custom query 7 | 8 | default_custom_query: 9 | label_setting: Default custom query 10 | label_view_default_issues: View default issues 11 | label_queries_for_all_projects: For all projects 12 | label_queries_for_current_project: For current project 13 | 14 | text_allowed_queries: Public (to any users) queries only selectable -------------------------------------------------------------------------------- /config/locales/tr.yml: -------------------------------------------------------------------------------- 1 | tr: 2 | project_module_default_custom_query: Öntanımlı özel sorgu 3 | permission_manage_default_query: Öntanımlı sorguyu yönet 4 | 5 | # Labels for projects_default_queries column 6 | field_query: Öntanımlı özel sorgu 7 | 8 | default_custom_query: 9 | label_setting: Öntanımlı özel sorgu 10 | label_view_default_issues: Öntanımlı işleri gör 11 | label_queries_for_all_projects: Tüm birimler için 12 | label_queries_for_current_project: Simdiki birim için 13 | 14 | text_allowed_queries: Herkese açık (tüm kullanıcılar) sorgular sadece seçilebilir 15 | -------------------------------------------------------------------------------- /config/locales/fr.yml: -------------------------------------------------------------------------------- 1 | fr: 2 | project_module_default_custom_query: Rapport par défaut 3 | permission_manage_default_query: Gérer le rapport par défaut 4 | 5 | # Labels for projects_default_queries column 6 | field_query: Rapport par défaut 7 | 8 | default_custom_query: 9 | label_setting: Rapport par défaut 10 | label_view_default_issues: Voir les demandes par défaut 11 | label_queries_for_all_projects: Pour tous les projets 12 | label_queries_for_current_project: Pour le projet en cours 13 | 14 | text_allowed_queries: Seuls les rapports publics (pour tous les utilisateurs) sont sélectionnables 15 | -------------------------------------------------------------------------------- /config/locales/ru.yml: -------------------------------------------------------------------------------- 1 | ru: 2 | project_module_default_custom_query: Выбор запроса по умолчанию (фильтр задач) 3 | permission_manage_default_query: Manage default query 4 | 5 | # Labels for projects_default_queries column 6 | field_query: Выбор запроса по умолчанию 7 | 8 | default_custom_query: 9 | label_setting: Выбор запроса по умолчанию 10 | label_view_default_issues: Просматривать запросы по умолчанию 11 | label_queries_for_all_projects: Для всех проектов 12 | label_queries_for_current_project: Для текущего прооекта 13 | 14 | text_allowed_queries: Для выбора доступны только публичные запросы (фильтры задач) -------------------------------------------------------------------------------- /init.rb: -------------------------------------------------------------------------------- 1 | Redmine::Plugin.register :redmine_default_custom_query do 2 | name 'Redmine Default Custom Query' 3 | author 'Katsuya Hidaka' 4 | description 'Redmine plugin for setting default custom query of Issues for each project' 5 | version '1.5.0' 6 | requires_redmine '4.0' 7 | url 'https://github.com/hidakatsuya/redmine_default_custom_query' 8 | author_url 'https://twitter.com/hidakatsuya' 9 | 10 | project_module :default_custom_query do 11 | permission :manage_default_query, { default_custom_query_setting: [ :update ] }, require: :member 12 | end 13 | end 14 | 15 | require_relative 'lib/default_custom_query' 16 | -------------------------------------------------------------------------------- /app/patches/models/project_patch.rb: -------------------------------------------------------------------------------- 1 | require_dependency 'project' 2 | 3 | module DefaultCustomQuery 4 | module ProjectPatch 5 | 6 | extend ActiveSupport::Concern 7 | 8 | included do 9 | has_many :default_queries, dependent: :delete_all, class_name: 'ProjectsDefaultQuery' 10 | end 11 | 12 | def default_query 13 | default_queries.first.try :query 14 | end 15 | 16 | def init_default_query 17 | default_queries.first || default_queries.new 18 | end 19 | end 20 | end 21 | 22 | DefaultCustomQuery::ProjectPatch.tap do |mod| 23 | Project.send :include, mod unless Project.include?(mod) 24 | end 25 | -------------------------------------------------------------------------------- /app/helpers/default_custom_query_helper.rb: -------------------------------------------------------------------------------- 1 | module DefaultCustomQueryHelper 2 | def options_for_selectable_queries(project) 3 | options_groups = [] 4 | 5 | queries = IssueQuery.only_public.where(project_id: nil) 6 | if queries.any? 7 | options_groups << [l('default_custom_query.label_queries_for_all_projects'), queries] 8 | end 9 | 10 | queries = project.queries.only_public 11 | if queries.any? 12 | options_groups << [l('default_custom_query.label_queries_for_current_project'), queries] 13 | end 14 | 15 | options_groups.map do |group, options| 16 | [group, options.collect {|o| [o.name, o.id] }] 17 | end 18 | end 19 | end -------------------------------------------------------------------------------- /lib/default_custom_query.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | 3 | module DefaultCustomQuery 4 | def self.root 5 | @root ||= Pathname.new File.expand_path('..', File.dirname(__FILE__)) 6 | end 7 | end 8 | 9 | Rails.configuration.to_prepare do 10 | # Load patches for Redmine 11 | Dir[DefaultCustomQuery.root.join('app/patches/**/*_patch.rb')].each {|f| require_dependency f } 12 | 13 | # Load application helper 14 | ::DefaultCustomQueryHelper.tap do |mod| 15 | ActionView::Base.send :include, mod unless ActionView::Base.include?(mod) 16 | end 17 | end 18 | 19 | # Load hooks 20 | Dir[DefaultCustomQuery.root.join('app/hooks/*_hook.rb')].each {|f| require_dependency f } 21 | -------------------------------------------------------------------------------- /test/factories/role.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :role, aliases: [:role_with_manage_default_query] do 3 | sequence(:name) {|n| "Role#{n}"} 4 | sequence(:position) 5 | assignable 1 6 | builtin 0 7 | issues_visibility 'all' 8 | permissions { 9 | perms = Redmine::AccessControl.permissions - 10 | Redmine::AccessControl.public_permissions 11 | perms.map &:name 12 | } 13 | 14 | factory :role_without_manage_default_query do 15 | permissions { 16 | perms = Redmine::AccessControl.permissions - 17 | Redmine::AccessControl.public_permissions 18 | perms.map(&:name) - [:manage_default_query] 19 | } 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/patches/helpers/projects_helper_patch.rb: -------------------------------------------------------------------------------- 1 | require_dependency 'projects_helper' 2 | 3 | module DefaultCustomQuery 4 | module ProjectsHelperPatch 5 | 6 | def project_settings_tabs 7 | tabs = super 8 | if User.current.allowed_to?(:manage_default_query, @project) && 9 | @project.module_enabled?(:default_custom_query) 10 | tabs << { 11 | name: 'default_custom_query', 12 | action: :manage_default_query, 13 | partial: 'default_custom_query_setting/form', 14 | label: :'default_custom_query.label_setting' 15 | } 16 | end 17 | tabs 18 | end 19 | end 20 | end 21 | 22 | ProjectsController.send :helper, DefaultCustomQuery::ProjectsHelperPatch 23 | -------------------------------------------------------------------------------- /app/views/default_custom_query_setting/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= labelled_form_for @default_query ||= @project.init_default_query, 2 | as: :settings, 3 | url: default_custom_query_setting_update_path(@project), 4 | remote: true, method: :put, html: { id: 'default-query-setting' } do |f| %> 5 | 6 | <%= error_messages_for @default_query %> 7 | 8 |
9 |

10 | <%= f.select :query_id, options_for_selectable_queries(@project), include_blank: true %>
11 | <%=l 'default_custom_query.text_allowed_queries' %> 12 |

13 |
14 | <%= submit_tag l(:button_save) %> 15 | <% end %> 16 | 17 | 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | 3 | rvm: 4 | - 2.2 5 | - 2.3 6 | - 2.4 7 | - 2.5 8 | - 2.6 9 | 10 | env: 11 | - REDMINE_REPOS=branches/4.0-stable 12 | 13 | before_install: 14 | - export PLUGIN_NAME=redmine_default_custom_query 15 | - export DB=sqlite 16 | - export NOKOGIRI_USE_SYSTEM_LIBRARIES=true 17 | - export REDMINE_PATH=$HOME/redmine 18 | - export BUNDLE_GEMFILE=$REDMINE_PATH/Gemfile 19 | - svn co http://svn.redmine.org/redmine/$REDMINE_REPOS $REDMINE_PATH 20 | - ln -s $TRAVIS_BUILD_DIR $REDMINE_PATH/plugins/$PLUGIN_NAME 21 | - cp test/database.travis.yml $REDMINE_PATH/config/database.yml 22 | - cd $REDMINE_PATH 23 | - bundle update 24 | 25 | before_script: 26 | - bundle exec rake db:migrate 27 | - bundle exec rake redmine:plugins:migrate 28 | 29 | script: 30 | - bundle exec rake redmine:plugins:test NAME=$PLUGIN_NAME 31 | 32 | branches: 33 | only: 34 | - master 35 | -------------------------------------------------------------------------------- /app/views/issues/_sidebar_issues_bottom.html.erb: -------------------------------------------------------------------------------- 1 | <% if @project && @project.module_enabled?(:default_custom_query) && @project.default_query.present? %> 2 | <%= javascript_tag id: 'add-default-issues-button' do %> 3 | (function() { 4 | var allIssuesPath = '<%=j project_issues_path(@project, set_filter: 1) %>'; 5 | 6 | var showIssuesButton = $('#sidebar a[href="' + allIssuesPath + '"]'); 7 | if (showIssuesButton.size() > 0) { 8 | showIssuesButton.attr('href', allIssuesPath + '&without_default=1'); 9 | } 10 | 11 | var defaultIssuePath = '<%=j project_issues_path(@project, query_id: @project.default_query.id) %>'; 12 | 13 | var showDefaultIssuesButton = $('').attr('href', defaultIssuePath) 14 | .text('<%=j l('default_custom_query.label_view_default_issues') %>'); 15 | showIssuesButton.before(showDefaultIssuesButton).before('
'); 16 | })(); 17 | <% end %> 18 | <% end %> 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.4.0 2 | 3 | - Support only Redmine 4.0 (@marius-balteanu). For older Redmine versions, please use version 1.3.0. 4 | - Support Ruby 2.5 and 2.6 (@marius-balteanu) 5 | 6 | ## 1.3.0 7 | 8 | - Support Redmine 3.4 (@maeda-m, @pboese) 9 | - Support Ruby 2.4 (@maeda-m) 10 | 11 | 12 | ## 1.2.0 13 | 14 | - Bug: Fixed CSV export when custom query is used #19 (@sdwolf) 15 | - Feature: Support Chinese translation #24 (@archonwang) 16 | - Drop support Redmine 2.6 and supports 3.1,3.2 #23 (@hidakatsuya) 17 | - Drop support Ruby 1.9.3 and supports 2.3 #22 (@hidakatsuya) 18 | 19 | ## 1.1.2 20 | 21 | - Feature: Support French locale #14 22 | 23 | ## 1.1.1 24 | 25 | - Feature: Russian translation #13 (@insspb) 26 | 27 | ## 1.1.0 28 | 29 | - Feature: Support v3.0 and drop v2.5 or less support #8 (@hidakatsuya) 30 | 31 | ## 1.0.1 32 | 33 | - Bug: First view don't show selected query #7 (@AntonJa, @hidakatsuya) 34 | 35 | ## 1.0.0 36 | 37 | - Allow select a global query #5 [Katsuya Hidaka] 38 | - Test code and Improvements by testing #1 [Katsuya Hidaka] 39 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Katsuya Hidaka. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /app/models/projects_default_query.rb: -------------------------------------------------------------------------------- 1 | class ProjectsDefaultQuery < ActiveRecord::Base 2 | 3 | belongs_to :project 4 | belongs_to :query, class_name: 'IssueQuery' 5 | 6 | validates :project_id, :query_id, numericality: { allow_nil: true } 7 | validates :project_id, uniqueness: true, presence: true 8 | validate :query_must_be_selectable 9 | 10 | def self.initialize_for(project_id) 11 | default_query = where(project_id: project_id).first 12 | 13 | unless default_query 14 | default_query = self.new 15 | default_query.project_id = project_id 16 | end 17 | default_query 18 | end 19 | 20 | def query 21 | return unless super 22 | 23 | unless new_record? || selectable_query?(super) 24 | update_attribute :query_id, nil 25 | end 26 | super 27 | end 28 | 29 | private 30 | 31 | def query_must_be_selectable 32 | return if errors.any? || query_id.blank? || !query_id_changed? 33 | 34 | issue_query = IssueQuery.where(id: query_id).first 35 | 36 | unless selectable_query?(issue_query) 37 | errors.add :query_id, :invalid 38 | end 39 | end 40 | 41 | def selectable_query?(query) 42 | query && query.public_visibility? && 43 | (query.project.nil? || query.project == project) 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper') 2 | 3 | Dir[DefaultCustomQuery.root.join('test/factories/*.rb')].each {|f| require f } 4 | 5 | module DefaultCustomQuery 6 | module TestHelper 7 | include FactoryGirl::Syntax::Methods 8 | include Redmine::I18n 9 | 10 | def create_member(project, user, role) 11 | create(:member, project: project, user: user, roles: [role]) 12 | end 13 | alias_method :add_member, :create_member 14 | 15 | def create_project_and_member_with_default_query 16 | user = create(:user) 17 | project = create(:project, :with_default_custom_query) 18 | add_member project, user, create(:role_with_manage_default_query) 19 | 20 | [project, user] 21 | end 22 | 23 | def set_default_query(project, query) 24 | create(:default_query, project: project, query: query) 25 | end 26 | 27 | def logged_in(user) 28 | log_user user.login, attributes_for(:user)[:password] 29 | end 30 | 31 | def assert_apply_query(query) 32 | assert_select 'h2', text: query.name 33 | assert_select 'ul.queries a.selected', text: query.name 34 | end 35 | 36 | def assert_not_applied_query 37 | assert_select 'h2', text: l(:label_issue_plural) 38 | assert_select 'ul.queries a.selected', false 39 | end 40 | end 41 | end 42 | 43 | Redmine::IntegrationTest.send :include, DefaultCustomQuery::TestHelper 44 | -------------------------------------------------------------------------------- /app/patches/controllers/issues_controller_patch.rb: -------------------------------------------------------------------------------- 1 | require_dependency 'issues_controller' 2 | 3 | module DefaultCustomQuery 4 | module IssuesControllerPatch 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | before_action :with_default_query, only: [:index], if: :default_query_module_enabled? 9 | 10 | alias_method :retrieve_query_from_session_without_default_custom_query, :retrieve_query_from_session 11 | alias_method :retrieve_query_from_session, :retrieve_query_from_session_with_default_custom_query 12 | end 13 | 14 | def with_default_query 15 | case 16 | when params[:query_id].present? 17 | # Nothing to do 18 | when api_request? || csv_request? 19 | # Nothing to do 20 | when show_all_issues? 21 | params[:set_filter] = 1 22 | when filter_applied? 23 | # Nothing to do 24 | when filter_cleared? 25 | apply_default_query! 26 | when session[:query] 27 | query_id, project_id = session[:query].values_at(:id, :project_id) 28 | unless query_id && (project_id == @project.id) && available_query?(query_id) 29 | apply_default_query! 30 | end 31 | else 32 | apply_default_query! 33 | end 34 | end 35 | 36 | def retrieve_query_from_session_with_default_custom_query 37 | if default_query_module_enabled? 38 | if session[:query] 39 | retrieve_query_from_session_without_default_custom_query 40 | else 41 | @query = find_default_query 42 | end 43 | else 44 | retrieve_query_from_session_without_default_custom_query 45 | end 46 | end 47 | 48 | private 49 | 50 | def find_default_query 51 | @project.default_query 52 | end 53 | 54 | def apply_default_query! 55 | default_query = find_default_query 56 | if default_query 57 | params[:query_id] = default_query.id 58 | end 59 | end 60 | 61 | def filter_applied? 62 | params[:set_filter] 63 | end 64 | 65 | def filter_cleared? 66 | params[:set_filter] && [:op, :f].all? {|k| !params.key?(k) } 67 | end 68 | 69 | def show_all_issues? 70 | params[:without_default] 71 | end 72 | 73 | def default_query_module_enabled? 74 | @project && @project.module_enabled?(:default_custom_query) 75 | end 76 | 77 | def available_query?(query_id) 78 | IssueQuery.only_public 79 | .where('project_id is null or project_id = ?', @project.id) 80 | .where(id: query_id).exists? 81 | end 82 | 83 | def csv_request? 84 | params[:format] == 'csv' 85 | end 86 | end 87 | end 88 | 89 | DefaultCustomQuery::IssuesControllerPatch.tap do |mod| 90 | IssuesController.send :include, mod unless IssuesController.include?(mod) 91 | end 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Redmine Default Custom Query [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hidakatsuya/redmine_default_custom_query?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 2 | 3 | 4 | [![Build Status](http://img.shields.io/travis/hidakatsuya/redmine_default_custom_query.svg?style=flat)](https://travis-ci.org/hidakatsuya/redmine_default_custom_query) 5 | [![Code Climate](http://img.shields.io/codeclimate/github/hidakatsuya/redmine_default_custom_query.svg?style=flat)](https://codeclimate.com/github/hidakatsuya/redmine_default_custom_query) 6 | 7 | Redmine plugin for setting the default custom query of Issues for each project. 8 | 9 | ![Configure the default query per projects](https://raw.githubusercontent.com/wiki/hidakatsuya/redmine_default_custom_query/images/select-default-query-per-projects.png) 10 | 11 | ![Apply the default query](https://raw.githubusercontent.com/wiki/hidakatsuya/redmine_default_custom_query/images/issues-with-default-query.png) 12 | 13 | ## Usage 14 | 15 | 1. Enable the Default Custom Query module in your project 16 | 2. Select a custom query to set to default in setting for your project 17 | 18 | ## Supported versions 19 | 20 | * Redmine 4.0 21 | * Ruby 2.2, 2.3, 2.4, 2.5, 2.6 22 | 23 | ## Install 24 | 25 | `git clone` or copy an unarchived plugin(archived file is [here](https://github.com/hidakatsuya/redmine_default_custom_query/releases)) to `plugins/redmine_default_custom_query` on your Redmine path. 26 | 27 | ``` 28 | $ git clone https://github.com/hidakatsuya/redmine_default_custom_query.git /path/to/your-redmine/plugins/redmine_default_custom_query 29 | ``` 30 | 31 | Install dependencies: 32 | 33 | ``` 34 | $ bundle install 35 | ``` 36 | 37 | Then, migrate: 38 | 39 | ``` 40 | $ cd /path/to/your-redmine 41 | $ rake redmine:plugins:migrate NAME=redmine_default_custom_query RAILS_ENV=production 42 | ``` 43 | 44 | That's all. 45 | 46 | ## Uninstall 47 | 48 | At first, rollback schema: 49 | 50 | ``` 51 | $ cd /path/to/your-redmine 52 | $ rake redmine:plugins:migrate NAME=redmine_default_custom_query VERSION=0 RAILS_ENV=production 53 | ``` 54 | 55 | Then, remove `plugins/redmine_default_custom_query` directory. 56 | 57 | ## Contribute 58 | 59 | ### How to test 60 | 61 | ``` 62 | $ cd /path/to/redmine 63 | $ bundle install 64 | $ bundle exec rake redmine:plugins:test NAME=redmine_default_custom_query 65 | ``` 66 | 67 | ### Pull Request 68 | 69 | 1. Fork it 70 | 2. Create your feature branch: `git checkout -b new-feature` 71 | 3. Commit your changes: `git commit -am 'add some new feature'` 72 | 4. Push to the branch: `git push origin new-feature` 73 | 5. Create new Pull Request 74 | 75 | ### Report bugs 76 | 77 | Please report from [here](https://github.com/hidakatsuya/redmine_default_custom_query/issues/new). 78 | 79 | ## Copyright 80 | 81 | © Katsuya Hidaka. See MIT-LICENSE for further details. 82 | -------------------------------------------------------------------------------- /test/integration/manage_default_query_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | class ManageDefaultQueryTest < Redmine::IntegrationTest 4 | def setup 5 | @user = create(:user) 6 | @project = create(:project, :with_default_custom_query) 7 | logged_in @user 8 | end 9 | 10 | def test_logged_user_has_not_manage_default_query_permission 11 | add_member @project, @user, create(:role_without_manage_default_query) 12 | 13 | # show 14 | get settings_project_path(@project) 15 | 16 | assert_response :success 17 | assert_select '#tab-default_custom_query', false 18 | 19 | # update 20 | put default_custom_query_setting_update_path(@project) 21 | 22 | assert_response :forbidden 23 | end 24 | 25 | def test_logged_user_has_manage_default_query_permission 26 | add_member @project, @user, create(:role_with_manage_default_query) 27 | 28 | @queries = [ 29 | create(:issue_query, :public, project: @project), 30 | # Global query 31 | create(:issue_query, :public) 32 | ] 33 | @unselectable_queries = [ 34 | create(:issue_query, :private, project: @project), 35 | create(:issue_query, :roles, project: @project) 36 | ] 37 | 38 | get settings_project_path(@project, tab: 'default_custom_query') 39 | 40 | # Render 41 | assert_response :success 42 | assert_template partial: 'default_custom_query_setting/_form' 43 | 44 | assert_select 'select#settings_query_id optgroup option', count: @queries.count 45 | assert_select 'select#settings_query_id' do 46 | @queries.each do |query| 47 | assert_select 'option', text: query.name 48 | end 49 | end 50 | 51 | # New setting 52 | assert_difference -> { @project.default_queries.count }, 1 do 53 | put default_custom_query_setting_update_path(@project), params: { 54 | settings: { query_id: @queries.first.id }, 55 | xhr: true 56 | } 57 | end 58 | 59 | assert_equal @project.default_query, @queries.first 60 | assert_response :success 61 | assert_template partial: 'default_custom_query_setting/_form' 62 | 63 | # Update 64 | assert_no_difference -> { @project.default_queries.count } do 65 | put default_custom_query_setting_update_path(@project), params: { 66 | settings: { query_id: @queries.last.id }, 67 | xhr: true 68 | } 69 | end 70 | 71 | assert_equal @project.default_query, @queries.last 72 | assert_response :success 73 | assert_template partial: 'default_custom_query_setting/_form' 74 | 75 | # Clear 76 | assert_no_difference -> { @project.default_queries.count } do 77 | put default_custom_query_setting_update_path(@project), params: { 78 | settings: { query_id: '' }, 79 | xhr: true 80 | } 81 | end 82 | 83 | assert_nil @project.default_query 84 | assert_response :success 85 | assert_template partial: 'default_custom_query_setting/_form' 86 | 87 | # Update to unselectable query 88 | put default_custom_query_setting_update_path(@project), params: { 89 | settings: { query_id: @unselectable_queries.first.id }, 90 | xhr: true 91 | } 92 | 93 | assert_response :success 94 | assert_template partial: 'default_custom_query_setting/_form' 95 | assert_nil @project.default_query 96 | 97 | # should be rendered the error message 98 | assert_select '#errorExplanation li', 1 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /test/integration/issues_with_default_query_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | class IssuesWithDefaultQueryTest < Redmine::IntegrationTest 4 | def setup 5 | @project, @user = create_project_and_member_with_default_query 6 | logged_in @user 7 | 8 | @default_query, @query = create_list(:issue_query, 2, :public, project: @project) 9 | end 10 | 11 | def test_basic_features_when_default_query_is_not_set_in_project 12 | get project_issues_path(@project) 13 | 14 | assert_response :success 15 | assert_select 'script#add-default-issues-button', false 16 | 17 | get project_issues_path(@project) 18 | 19 | assert_response :success 20 | assert_not_applied_query 21 | end 22 | 23 | def test_basic_features_when_default_query_is_set_in_project 24 | set_default_query @project, @default_query 25 | 26 | get project_issues_path(@project) 27 | 28 | # should be rendered the "Show default issues" button 29 | assert_response :success 30 | assert_select 'script#add-default-issues-button' 31 | 32 | get project_issues_path(@project) 33 | 34 | assert_response :success 35 | assert_apply_query @default_query 36 | end 37 | 38 | def test_click_view_all_issues_button 39 | set_default_query @project, @default_query 40 | 41 | # click "View all issues" button 42 | get project_issues_path(@project, set_filter: 1, without_default: 1) 43 | 44 | assert_response :success 45 | assert_not_applied_query 46 | end 47 | 48 | def test_select_other_query 49 | set_default_query @project, @default_query 50 | 51 | get project_issues_path(@project, query_id: @query.id) 52 | 53 | assert_response :success 54 | assert_apply_query @query 55 | end 56 | 57 | def test_default_query_has_been_deleted 58 | set_default_query @project, @default_query 59 | 60 | delete query_path(@default_query) 61 | 62 | assert_nil @project.default_query 63 | assert_response :redirect 64 | end 65 | 66 | def test_visibility_of_default_query_has_been_changed_to_PRIVATE 67 | set_default_query @project, @default_query 68 | 69 | @default_query.update_attribute :visibility, Query::VISIBILITY_PRIVATE 70 | 71 | get project_issues_path(@project) 72 | 73 | assert_response :success 74 | assert_nil @project.default_query 75 | end 76 | 77 | def test_visibility_of_default_query_has_been_changed_to_ROLES 78 | set_default_query @project, @default_query 79 | 80 | @default_query.visibility = Query::VISIBILITY_ROLES 81 | @default_query.roles << create(:role) 82 | @default_query.save! 83 | 84 | get project_issues_path(@project) 85 | 86 | assert_response :success 87 | assert_nil @project.default_query 88 | end 89 | 90 | def test_initial_default_query 91 | set_default_query @project, @default_query 92 | 93 | @other_project = create(:project, :with_default_custom_query) 94 | add_member @other_project, @user, create(:role_with_manage_default_query) 95 | 96 | # @other_project's global query 97 | @global_query = create(:issue_query, :public, project: nil) 98 | 99 | # select @global_query in @other_project 100 | get project_issues_path(@other_project, query_id: @global_query.id) 101 | get project_issues_path(@project) 102 | 103 | assert_response :success 104 | assert_apply_query @default_query 105 | end 106 | 107 | def test_select_project_overview_query 108 | set_default_query @project, @default_query 109 | 110 | get project_issues_path(@project, set_filter: 1, tracker_id: 1) 111 | 112 | assert_response :success 113 | assert_not_applied_query 114 | end 115 | 116 | def test_select_multiple_issues_query 117 | set_default_query @project, @default_query 118 | 119 | get issues_path(set_filter: 1, issue_id: '1,2,3') 120 | 121 | assert_response :success 122 | assert_not_applied_query 123 | end 124 | 125 | def test_select_only_columns_query 126 | set_default_query @project, @default_query 127 | 128 | get project_issues_path(@project, set_filter: 1, 129 | f: [], c: [ 'subject', 'assigned_to', 'due_date' ]) 130 | 131 | assert_response :success 132 | assert_not_applied_query 133 | end 134 | end 135 | --------------------------------------------------------------------------------