diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index e2cc1505e..65d8e8ff7 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -25,6 +25,7 @@ class IssuesController < ApplicationController before_action :authorize, :except => [:index, :new, :create] before_action :find_optional_project, :only => [:index, :new, :create] before_action :build_new_issue_from_params, :only => [:new, :create] + before_action :with_default_query, only: [:index] accept_rss_auth :index, :show accept_api_auth :index, :show, :create, :update, :destroy @@ -468,6 +469,28 @@ class IssuesController < ApplicationController private + def with_default_query + return if params[:query_id].present? + return if api_request? + return if params[:set_filter] && params.key?(:op) && params.key?(:f) + params[:set_filter] = 1 and return if params[:without_default].present? + apply_default_query! and return if params[:set_filter] && [:op, :f].all? { |k| !params.key?(k) } + if session[:issue_query] + query_id, project_id = session[:issue_query].values_at(:id, :project_id) + unless query_id && (@project.is_a?(Project) && project_id == @project.id) && IssueQuery.available_query?(@project.id, query_id, User.current.id) + apply_default_query! + end + else + apply_default_query! + end + end + + def apply_default_query! + if default_query = find_default_query + params[:query_id] = default_query.id + end + end + def retrieve_previous_and_next_issue_ids if params[:prev_issue_id].present? || params[:next_issue_id].present? @prev_issue_id = params[:prev_issue_id].presence.try(:to_i) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 785f84291..010e1b8e3 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -110,6 +110,17 @@ module ProjectsHelper principals_options_for_select(assignable_users, project.default_assigned_to) end + def project_default_query_options(project) + grouped = Hash.new {|h,k| h[k] = []} + IssueQuery.only_public.where(project_id: nil).each do |query| + grouped[l('label_default_queries.for_all_projects')] << [query.name, query.id] + end + IssueQuery.only_public.where(project: project).each do |query| + grouped[l('label_default_queries.for_current_project')] << [query.name, query.id] + end + grouped_options_for_select(grouped, project.default_query_id) + end + def format_version_sharing(sharing) sharing = 'none' unless Version::VERSION_SHARINGS.include?(sharing) l("label_version_sharing_#{sharing}") diff --git a/app/helpers/queries_helper.rb b/app/helpers/queries_helper.rb index a9d8cff6f..bee523471 100644 --- a/app/helpers/queries_helper.rb +++ b/app/helpers/queries_helper.rb @@ -347,9 +347,29 @@ module QueriesHelper @query.project = @project end @query + else + @query = find_default_query end end + private + + def find_default_query + query = nil + if User.current.pref.default_user_project_query != nil + query = IssueQuery.find_by_id(User.current.pref.default_user_project_query) + end + + unless query.is_a?(IssueQuery) + if @project.is_a?(Project) and @project.default_query.is_a?(IssueQuery) + query = @project.default_query + elsif Setting.default_project_query != nil + query = IssueQuery.find_by_id(Setting.default_project_query) + end + end + query + end + # Returns the query definition as hidden field tags def query_as_hidden_field_tags(query) tags = hidden_field_tag("set_filter", "1", :id => nil) @@ -408,9 +428,16 @@ module QueriesHelper queries.collect {|query| css = +'query' clear_link = +'' + clear_link_param = {:set_filter => 1, :sort => '', :project_id => @project} + + if query == find_default_query + css << ' default' + clear_link_param[:without_default] = 1 + end + if query == @query css << ' selected' - clear_link += link_to_clear_query + clear_link += link_to_clear_query(clear_link_param) end content_tag('li', link_to(query.name, @@ -422,10 +449,10 @@ module QueriesHelper ) + "\n" end - def link_to_clear_query + def link_to_clear_query(params = {:set_filter => 1, :sort => '', :project_id => @project}) link_to( l(:button_clear), - {:set_filter => 1, :sort => '', :project_id => @project}, + params, :class => 'icon-only icon-clear-query', :title => l(:button_clear) ) diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index 8d8d98d72..18b2768b3 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -165,6 +165,16 @@ module SettingsHelper options.map {|label, value| [l(label), value.to_s]} end + def default_global_project_query_options + options = [ + [l(:label_none), ''] + ] + IssueQuery.only_public.where(project_id: nil).each do |query| + options.push([query.name, query.id]) + end + options + end + def cross_project_subtasks_options options = [ [:label_disabled, ''], diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 1f5841199..ccbd14760 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -29,6 +29,17 @@ module UsersHelper user.valid_notification_options.collect {|o| [l(o.last), o.first]} end + def default_user_project_query_options(user) + grouped = Hash.new {|h,k| h[k] = []} + IssueQuery.only_public.for_all_projects.each do |query| + grouped[l('label_default_queries.for_all_users')] << [query.name, query.id] + end + IssueQuery.for_all_projects.where(user_id: user.id).each do |query| + grouped[l('label_default_queries.for_current_user')] << [query.name, query.id] + end + grouped_options_for_select(grouped, user.pref.default_user_project_query) + end + def textarea_font_options [[l(:label_font_default), '']] + UserPreference::TEXTAREA_FONT_OPTIONS.map {|o| [l("label_font_#{o}"), o]} end diff --git a/app/models/issue_query.rb b/app/models/issue_query.rb index 79905b11a..0d4317680 100644 --- a/app/models/issue_query.rb +++ b/app/models/issue_query.rb @@ -55,11 +55,31 @@ class IssueQuery < Query QueryColumn.new(:last_notes, :caption => :label_last_notes, :inline => false) ] + has_many :projects, :foreign_key => 'default_query_id' + after_update { self.projects.clear unless self.public_visibility? } + after_destroy { self.projects.clear } + scope :only_public, -> { + where(:visibility => VISIBILITY_PUBLIC) + } + scope :for_all_projects, -> { + where(project_id: nil) + } + + def self.available_query?(project_id, query_id, user_id) + self.where('project_id is null or project_id = ?', project_id) + .where('user_id = ? or visibility = ?', user_id, VISIBILITY_PUBLIC) + .where(id: query_id).exists? + end + def initialize(attributes=nil, *args) super attributes self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} } end + def public_visibility? + visibility == VISIBILITY_PUBLIC + end + def draw_relations r = options[:draw_relations] r.nil? || r == '1' diff --git a/app/models/project.rb b/app/models/project.rb index 0119b1228..7920e62dd 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -58,6 +58,8 @@ class Project < ActiveRecord::Base :class_name => 'IssueCustomField', :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", :association_foreign_key => 'custom_field_id' + # Default Custom Query + belongs_to :default_query, :class_name => 'IssueQuery' acts_as_attachable :view_permission => :view_files, :edit_permission => :manage_files, @@ -767,7 +769,8 @@ class Project < ActiveRecord::Base 'issue_custom_field_ids', 'parent_id', 'default_version_id', - 'default_assigned_to_id') + 'default_assigned_to_id', + 'default_query_id') safe_attributes( 'enabled_module_names', diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb index 2a0efb7f9..5ec6e0c91 100644 --- a/app/models/user_preference.rb +++ b/app/models/user_preference.rb @@ -37,6 +37,7 @@ class UserPreference < ActiveRecord::Base 'textarea_font', 'recently_used_projects', 'history_default_tab', + 'default_user_project_query', 'toolbar_language_options') TEXTAREA_FONT_OPTIONS = ['monospace', 'proportional'] @@ -113,6 +114,9 @@ class UserPreference < ActiveRecord::Base self[:toolbar_language_options] = languages.join(',') end + def default_user_project_query; self[:default_user_project_query] end + def default_user_project_query=(value); self[:default_user_project_query]=value; end + # Returns the names of groups that are displayed on user's page # Example: # preferences.my_page_groups diff --git a/app/views/projects/copy.html.erb b/app/views/projects/copy.html.erb index 8a7805ef0..d1a7c9d9b 100644 --- a/app/views/projects/copy.html.erb +++ b/app/views/projects/copy.html.erb @@ -28,3 +28,13 @@ <%= submit_tag l(:button_copy) %> <% end %> + +<%= javascript_tag do %> + $('input[value="queries"]').change(function() { + if ($(this).prop('checked')){ + $('select#project_default_query_id optgroup[label="For current project"] option').prop("disabled", false); + }else{ + $('select#project_default_query_id optgroup[label="For current project"] option').prop("disabled", true); + } + }); +<% end %> \ No newline at end of file diff --git a/app/views/projects/settings/_issues.html.erb b/app/views/projects/settings/_issues.html.erb index 5b2db2937..04d8e3ec4 100644 --- a/app/views/projects/settings/_issues.html.erb +++ b/app/views/projects/settings/_issues.html.erb @@ -41,6 +41,13 @@ <% if @project.safe_attribute?('default_assigned_to_id') %>

<%= f.select :default_assigned_to_id, project_default_assigned_to_options(@project), include_blank: l(:label_none) %>

<% end %> + + <% if @project.safe_attribute?('default_query_id') && project_default_query_options(@project).present? %> +

+ <%= f.select :default_query_id, project_default_query_options(@project), include_blank: l(:label_none) %> + <%=l 'text_allowed_queries_to_select' %> +

+ <% end %>

<%= submit_tag l(:button_save) %>

diff --git a/app/views/settings/_issues.html.erb b/app/views/settings/_issues.html.erb index b4e50d8e3..6d4c2a512 100644 --- a/app/views/settings/_issues.html.erb +++ b/app/views/settings/_issues.html.erb @@ -24,6 +24,8 @@

<%= setting_text_field :gantt_items_limit, :size => 6 %>

<%= setting_text_field :gantt_months_limit, :size => 6 %>

+ +

<%= setting_select :default_project_query, default_global_project_query_options %>

diff --git a/app/views/users/_preferences.html.erb b/app/views/users/_preferences.html.erb index 3734c3064..acf92438b 100644 --- a/app/views/users/_preferences.html.erb +++ b/app/views/users/_preferences.html.erb @@ -7,4 +7,5 @@

<%= pref_fields.text_field :recently_used_projects, :size => 2 %>

<%= pref_fields.select :history_default_tab, history_default_tab_options %>

<%= pref_fields.text_area :toolbar_language_options, :rows => 4 %>

+

<%= pref_fields.select :default_user_project_query, default_user_project_query_options(@user), include_blank: l(:label_none) %>

<% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index c3f4925cd..5ae4e01b0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -384,6 +384,7 @@ en: field_time_entries_visibility: Time logs visibility field_total_estimated_hours: Total estimated time field_default_version: Default version + field_default_query: Default Query field_remote_ip: IP address field_textarea_font: Font used for text areas field_updated_by: Updated by @@ -494,6 +495,7 @@ en: setting_timelog_accept_future_dates: Accept time logs on future dates setting_show_status_changes_in_mail_subject: Show status changes in issue mail notifications subject setting_project_list_defaults: Projects list defaults + setting_default_project_query: Default Query permission_add_project: Create project permission_add_subprojects: Create subprojects @@ -1070,6 +1072,11 @@ en: label_optgroup_others: Other projects label_optgroup_recents: Recently used label_last_notes: Last notes + label_default_queries: + for_all_projects: For all projects + for_current_project: For current project + for_all_users: For all users + for_current_user: For current user label_nothing_to_preview: Nothing to preview label_inherited_from_parent_project: "Inherited from parent project" label_inherited_from_group: "Inherited from group %{name}" @@ -1290,6 +1297,7 @@ en: description_issue_category_reassign: Choose issue category description_wiki_subpages_reassign: Choose new parent page text_repository_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier cannot be changed.' + text_allowed_queries_to_select: Public (to any users) queries only selectable text_login_required_html: When not requiring authentication, public projects and their contents are openly available on the network. You can edit the applicable permissions. label_login_required_yes: "Yes" label_login_required_no: "No, allow anonymous access to public projects" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 19534a2c6..b20654485 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -385,6 +385,7 @@ fr: field_time_entries_visibility: Visibilité du temps passé field_total_estimated_hours: Temps estimé total field_default_version: Version par défaut + field_default_query: Rapport par défaut field_textarea_font: Police utilisée pour les champs texte field_updated_by: Mise à jour par field_last_updated_by: Dernière mise à jour par @@ -481,6 +482,7 @@ fr: setting_time_entry_list_defaults: Affichage par défaut de la liste des temps passés setting_timelog_accept_0_hours: Autoriser la saisie de temps avec 0 heure setting_timelog_max_hours_per_day: Maximum d'heures pouvant être saisies par un utilisateur sur un jour + setting_default_project_query: Rapport par défaut permission_add_project: Créer un projet permission_add_subprojects: Créer des sous-projets @@ -1026,6 +1028,11 @@ fr: label_font_monospace: Police non proportionnelle label_font_proportional: Police proportionnelle label_last_notes: Dernières notes + label_default_queries: + for_all_projects: Pour tous les projets + for_current_project: Pour le projet en cours + for_all_users: Pour tous les utilisateurs + for_current_user: Pour l'utilisateur courrant label_trackers_description: Description des trackers label_open_trackers_description: Afficher la description des trackers @@ -1218,6 +1225,7 @@ fr: description_issue_category_reassign: Choisir une catégorie description_wiki_subpages_reassign: Choisir une nouvelle page parent text_repository_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres, tirets et tirets bas sont autorisés.
Un fois sauvegardé, l''identifiant ne pourra plus être modifié.' + text_allowed_queries_to_select: Seuls les rapports publics (pour tous les utilisateurs) sont sélectionnables label_parent_task_attributes_derived: Calculé à partir des sous-tâches label_parent_task_attributes_independent: Indépendent des sous-tâches mail_subject_security_notification: Notification de sécurité diff --git a/config/settings.yml b/config/settings.yml index 7c0912232..1895e0eea 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -119,6 +119,8 @@ gantt_items_limit: gantt_months_limit: format: int default: 24 +default_project_query: + default: '' # Maximum size of files that can be displayed # inline through the file viewer (in KB) file_max_size_displayed: diff --git a/db/migrate/20200806105730_add_projects_default_query_id.rb b/db/migrate/20200806105730_add_projects_default_query_id.rb new file mode 100644 index 000000000..f14f4be58 --- /dev/null +++ b/db/migrate/20200806105730_add_projects_default_query_id.rb @@ -0,0 +1,10 @@ +class AddProjectsDefaultQueryId < ActiveRecord::Migration[4.2] + def self.up + unless column_exists?(:projects, :default_query_id, :integer) + add_column :projects, :default_query_id, :integer, :default => nil + end + end + def self.down + remove_column :projects, :default_query_id + end +end \ No newline at end of file diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index 282a4adf6..44317f893 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -145,6 +145,7 @@ a.user.locked, a.user.locked:link, a.user.locked:visited {color: #999;} #sidebar a.selected {line-height:1.7em; padding:1px 3px 2px 2px; margin-left:-2px; background-color:#9DB9D5; color:#fff; border-radius:2px;} #sidebar a.selected:hover {text-decoration:none;} +#sidebar .query.default {font-weight: bold;} #admin-menu a {line-height:1.7em;} #admin-menu a.selected {padding-left: 20px !important; background-position: 2px 40%;}