diff --git a/Gemfile b/Gemfile index 9a69086..e69de29 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +0,0 @@ -group :test do - gem 'webrat' -end diff --git a/Rakefile b/Rakefile old mode 100755 new mode 100644 diff --git a/app/controllers/questions_controller.rb b/app/controllers/issuequestions_controller.rb similarity index 55% rename from app/controllers/questions_controller.rb rename to app/controllers/issuequestions_controller.rb index 16e9c4e..0ec70ce 100644 --- a/app/controllers/questions_controller.rb +++ b/app/controllers/issuequestions_controller.rb @@ -1,4 +1,4 @@ -class QuestionsController < ApplicationController +class IssuequestionsController < ApplicationController unloadable layout 'base' @@ -12,18 +12,31 @@ def user_issue_filter new_filter_for_questions_assigned_to(params[:user_id]) redirect_to :controller => 'issues', :action => 'index', :project_id => params[:project] end + + def hide + @question = Question.find(params[:id]) + @question.hidden = true + if @question.save + redirect_to :back + end + + end def autocomplete_for_user_login - if params[:user] - @users = User.active.all(:conditions => ["LOWER(login) LIKE :user OR LOWER(firstname) LIKE :user OR LOWER(lastname) LIKE :user", {:user => params[:user]+"%" }], - :limit => 10, - :order => 'login ASC') + if params[:issue_id] && Setting.plugin_question_plugin[:only_members] == 1 + @issue = Issue.find(params[:issue_id]) + base = @issue.project.users + else + base = User + end + q = (params[:q] || params[:term] || params[:user]).to_s.strip.downcase + if q.present? + @users = base.active.where(["LOWER(login) LIKE :user OR LOWER(firstname) LIKE :user OR LOWER(lastname) LIKE :user", {:user => q + "%" }]). + limit(10). + order('login ASC') end @users ||=[] - if params[:issue_id] - @issue = Issue.find_by_id(params[:issue_id]) - end render :layout => false end @@ -32,7 +45,7 @@ def autocomplete_for_user_login def new_filter_for_questions_assigned_to(user_id) @project = Project.find(params[:project]) unless params[:project].nil? - @query = Query.new(:name => "_", + @query = (ActiveSupport::Dependencies::search_for_file('issue_query') ? IssueQuery : Query).new(:name => "_", :filters => {'status_id' => {:operator => '*', :values => [""]}} ) @query.project = @project unless params[:project].nil? diff --git a/app/models/question.rb b/app/models/question.rb index 1834499..54ddf23 100644 --- a/app/models/question.rb +++ b/app/models/question.rb @@ -5,18 +5,22 @@ class Question < ActiveRecord::Base belongs_to :assigned_to, :class_name => "User", :foreign_key => "assigned_to_id" belongs_to :author, :class_name => "User", :foreign_key => "author_id" belongs_to :issue - belongs_to :journal + belongs_to :journal, :class_name => "Journal", :foreign_key => "journal_id" validates_presence_of :author validates_presence_of :issue validates_presence_of :journal + + attr_protected :journal + + scope :opened, lambda { where(:opened => true) } + scope :not_hidden, lambda { where(:hidden => false) } - named_scope :opened, :conditions => {:opened => true} - named_scope :for_user, lambda {|user| - { :conditions => {:assigned_to_id => user.id }} + scope :for_user, lambda {|user| + where(:assigned_to_id => user.id) } - named_scope :by_user, lambda {|user| - { :conditions => {:author_id => user.id }} + scope :by_user, lambda {|user| + where(:author_id => user.id) } delegate :notes, :to => :journal, :allow_nil => true @@ -29,22 +33,20 @@ def close!(closing_journal=nil) if self.opened self.opened = false if self.save && closing_journal - QuestionMailer.deliver_answered_question(self, closing_journal) + QuestionMailer.answered_question(self, closing_journal).deliver end end end - + # TODO: refactor to named_scope def self.count_of_open_for_user(user) - Question.count(:conditions => {:assigned_to_id => user.id, :opened => true}) + Question.where(:assigned_to_id => user.id, :opened => true).count end # TODO: refactor to named_scope def self.count_of_open_for_user_on_project(user, project) - Question.count(:conditions => ["#{Question.table_name}.assigned_to_id = ? AND #{Project.table_name}.id = ? AND #{Question.table_name}.opened = ?", - user.id, - project.id, - true], - :include => [:issue => [:project]]) + Question.where(["(#{Question.table_name}.assigned_to_id = ?) AND #{project.project_condition(Setting.display_subprojects_issues?)} AND (#{Question.table_name}.opened = ?)", + user.id, true]). + joins(:issue => :project).preload(:project).count end end diff --git a/app/models/question_mailer.rb b/app/models/question_mailer.rb index 3ce6175..b5496ff 100644 --- a/app/models/question_mailer.rb +++ b/app/models/question_mailer.rb @@ -1,69 +1,90 @@ +# encoding: utf-8 class QuestionMailer < Mailer unloadable def asked_question(journal) question = journal.question - subject "[Question #{question.issue.project.name} ##{question.issue.id}] #{question.issue.subject}" - recipients question.assigned_to.mail unless question.assigned_to.nil? - @from = "#{question.author.name} (Redmine) <#{Setting.mail_from}>" unless question.author.nil? + + if Setting.plugin_question_plugin[:obfuscate_author] == "1" + # Obfuscate author infos + from = "#{Setting.app_title} <#{Setting.mail_from}>" + else + # Clear author infos + from = question.author ? "#{question.author.name} (#{l(:field_system_name)} - #{I18n.t(:text_question)}) <#{Setting.mail_from}>" : nil + end + to = question.assigned_to ? question.assigned_to.mail : nil + subject = "[#{question.issue.project.name} ##{question.issue.id}] #{question.issue.subject}" + + @from = from + @question = question + @issue = question.issue + @journal = journal + @users = [question.assigned_to] + @issue_url = url_for(:controller => 'issues', :action => 'show', :id => question.issue) + @users = [question.assigned_to] + redmine_headers 'Issue-Id' => question.issue.id redmine_headers 'Question-Asked' => question.author.login if question.author.present? redmine_headers 'Question-Assigned-To' => question.assigned_to.login if question.assigned_to.present? - body({ - :question => question, - :issue => question.issue, - :journal => journal, - :issue_url => url_for(:controller => 'issues', :action => 'show', :id => question.issue) - }) - - RAILS_DEFAULT_LOGGER.debug 'Sending QuestionMailer#asked_question' - render_multipart('asked_question', body) + Rails.logger.debug 'Sending QuestionMailer#asked_question' + mail(:from => from, :to => to, :subject => subject) end def answered_question(question, closing_journal) - subject "[Answered #{question.issue.project.name} ##{question.issue.id}] #{question.issue.subject}" + if Setting.plugin_question_plugin[:obfuscate_author] == "1" + # Obfuscate author infos + from = "#{Setting.app_title} <#{Setting.mail_from}>" + @from = from + else + # Clear author infos + from = question.assigned_to ? "#{question.assigned_to.name} (#{l(:field_system_name)} - #{I18n.t(:text_answer)}) <#{Setting.mail_from}>" : nil + @from = "#{question.assigned_to.name} (#{l(:field_system_name)} - #{I18n.t(:text_answer)}) <#{Setting.mail_from}>" unless question.assigned_to.nil? + end + to = question.author ? question.author.mail : nil + subject = "[#{question.issue.project.name} ##{question.issue.id}] #{question.issue.subject}" + + @question = question + @issue = question.issue + @journal = closing_journal + @users = [question.author] + @issue_url = url_for(:controller => 'issues', :action => 'show', :id => question.issue) + @users = [question.author] - recipients question.author.mail unless question.author.nil? - @from = "#{question.assigned_to.name} (Redmine) <#{Setting.mail_from}>" unless question.assigned_to.nil? redmine_headers 'Issue-Id' => question.issue.id redmine_headers 'Question-Answer' => "#{question.issue.id}-#{closing_journal.id}" - body({ - :question => question, - :issue => question.issue, - :journal => closing_journal, - :issue_url => url_for(:controller => 'issues', :action => 'show', :id => question.issue) - }) - - RAILS_DEFAULT_LOGGER.debug 'Sending QuestionMailer#answered_question' - render_multipart('answered_question', body) + Rails.logger.debug 'Sending QuestionMailer#answered_question' + mail(:from => from, :to => to, :subject => subject) end # Creates an email with a list of issues that have open questions # assigned to the user def question_reminder(user, issues) - redmine_headers 'Type' => "Question" set_language_if_valid user.language - recipients user.mail - subject l(:question_reminder_subject, :count => issues.size) - body :issues => issues, - :issues_url => url_for(:controller => 'questions', :action => 'my_issue_filter') - render_multipart('question_reminder', body) + + to = user.mail + subject = l(:question_reminder_subject, :count => issues.size) + + @issues = issues + @issues_url = url_for(:controller => 'issuequestions', :action => 'my_issue_filter') + + redmine_headers 'Type' => 'Question' + + mail(:to => to, :subject => subject) end # Send email reminders to users who have open questions. def self.question_reminders - - open_questions_by_assignee = Question.opened.all(:order => 'id desc').group_by(&:assigned_to) + open_questions_by_assignee = Question.opened.order('id desc').all.group_by(&:assigned_to) open_questions_by_assignee.each do |assignee, questions| - next unless assignee.present? + next unless assignee.present? and not assignee.locked? - issues = questions.collect {|q| q.issue.visible?(assignee) ? q.issue : nil }.compact.uniq + issues = questions.reject {|q| not q.issue.visible?(assignee) }.collect {|q| q.issue }.uniq next if issues.count == 0 - deliver_question_reminder(assignee, issues) + question_reminder(assignee, issues).deliver end end end diff --git a/app/views/issuequestions/autocomplete_for_user_login.html.erb b/app/views/issuequestions/autocomplete_for_user_login.html.erb new file mode 100644 index 0000000..14c0ccd --- /dev/null +++ b/app/views/issuequestions/autocomplete_for_user_login.html.erb @@ -0,0 +1,7 @@ +<%= raw @users.map {|user| { + 'id' => user.login, + 'label' => "#{user.login}: #{user.name(:lastname_coma_firstname)}", + 'value' => user.login + } + }.to_json +%> \ No newline at end of file diff --git a/app/views/issuequestions/autocomplete_for_user_login.json.erb b/app/views/issuequestions/autocomplete_for_user_login.json.erb new file mode 100644 index 0000000..8c1bf6d --- /dev/null +++ b/app/views/issuequestions/autocomplete_for_user_login.json.erb @@ -0,0 +1,7 @@ +<%= raw @users.map {|user| { + 'id' => user.login, + 'label' => "#{user.login}: #{user.name(:lastname_coma_firstname)}", + 'value' => user.login + } + }.to_json +%> \ No newline at end of file diff --git a/app/views/layouts/_questions.html.erb b/app/views/layouts/_questions.html.erb new file mode 100644 index 0000000..4da7ee5 --- /dev/null +++ b/app/views/layouts/_questions.html.erb @@ -0,0 +1,18 @@ +<% questions = Question.opened.not_hidden.for_user(User.current) %> +<% if questions && questions.any? %> +
+

<%= l(:text_questions_for_me)%> (<%= h(questions.length) %>)

+
    + <% questions.each do |question| %> +
  1. + <%= link_to_issue(question.issue, :project => true, :subject => false) %>: + <%= truncate(question.notes, :length => 120) %> + <%= link_to l(:label_question_plugin_hide), hide_path(question) %> +
  2. + <% end %> +
+
+<% end %> + + + diff --git a/app/views/question_mailer/answered_question.html.erb b/app/views/question_mailer/answered_question.html.erb new file mode 100644 index 0000000..b9a7b01 --- /dev/null +++ b/app/views/question_mailer/answered_question.html.erb @@ -0,0 +1,20 @@ +<% if Setting.plugin_question_plugin[:obfuscate_content] == "1" %> +<%= textilizable(l(:mail_body_answer_link)) %> +<% else %> +

<%= l(:text_answer) %>

+<%= textilizable(@journal, :notes, :only_path => false) %> +<% end %> + +

<%= l(:text_question_for) %> <%= @question.assigned_to %>

+<%= textilizable(@question.journal, :notes, :only_path => false) %> + +<% if Setting.plugin_question_plugin[:obfuscate_content] == "0" %> + + +
+<% end %> +<%= render :partial => "mailer/issue", :locals => { :issue => @issue, :issue_url => @issue_url, :users => @users } %> diff --git a/app/views/question_mailer/answered_question.text.erb b/app/views/question_mailer/answered_question.text.erb new file mode 100644 index 0000000..ae1118c --- /dev/null +++ b/app/views/question_mailer/answered_question.text.erb @@ -0,0 +1,20 @@ +<% if Setting.plugin_question_plugin[:obfuscate_content] == "1" %> +<%= l(:mail_body_answer_link) %> +<% else %> +

<%= l(:text_answer) %>

+<%= textilizable(@journal, :notes, :only_path => false) %> +<% end %> + +

<%= l(:text_question_for) %> <%= @question.assigned_to %>

+<%= textilizable(@question.journal, :notes, :only_path => false) %> + +<% if Setting.plugin_question_plugin[:obfuscate_content] == "0" %> + + +
+<% end %> +<%= render :partial => "mailer/issue", :locals => { :issue => @issue, :issue_url => @issue_url, :users => @users } %> diff --git a/app/views/question_mailer/answered_question.text.html.rhtml b/app/views/question_mailer/answered_question.text.html.rhtml deleted file mode 100644 index ceeafe4..0000000 --- a/app/views/question_mailer/answered_question.text.html.rhtml +++ /dev/null @@ -1,14 +0,0 @@ -

<%= l(:text_answer) %>

-<%= textilizable(@journal, :notes, :only_path => false) %> - -

<%= l(:text_question) %>

-<%= textilizable(@question.journal, :notes, :only_path => false) %> - - - -
-<%= render :partial => "mailer/issue_text_html", :locals => { :issue => @issue, :issue_url => @issue_url } %> diff --git a/app/views/question_mailer/answered_question.text.plain.rhtml b/app/views/question_mailer/answered_question.text.plain.rhtml deleted file mode 100644 index d01eb36..0000000 --- a/app/views/question_mailer/answered_question.text.plain.rhtml +++ /dev/null @@ -1,14 +0,0 @@ -<%= l(:text_answer) %> - -<%= @journal.notes if @journal.notes? %> - -<%= l(:text_question) %> - -<%= @question.journal.notes if @question.journal && @question.journal.notes? %> - -<% for detail in @journal.details -%> -<%= @journal.render_detail(detail, true) %> -<% end -%> - ----------------------------------------- -<%= render :partial => "mailer/issue_text_plain", :locals => { :issue => @issue, :issue_url => @issue_url } %> diff --git a/app/views/question_mailer/asked_question.html.erb b/app/views/question_mailer/asked_question.html.erb new file mode 100644 index 0000000..6749a00 --- /dev/null +++ b/app/views/question_mailer/asked_question.html.erb @@ -0,0 +1,16 @@ +

<%= l(:mail_body_title, name: @question.assigned_to, issue: "##{@issue.id}", author: @question.author) %>

+ +<% if Setting.plugin_question_plugin[:obfuscate_content] == "1" %> +<%= textilizable(l(:mail_body_question_link)) %> +<% else %> +<%= textilizable(@journal, :notes, :only_path => false) %> + + + +
+<% end %> +<%= render :partial => "mailer/issue", :locals => { :issue => @issue, :issue_url => @issue_url, :users => @users } %> diff --git a/app/views/question_mailer/asked_question.text.erb b/app/views/question_mailer/asked_question.text.erb new file mode 100644 index 0000000..8dc8bf3 --- /dev/null +++ b/app/views/question_mailer/asked_question.text.erb @@ -0,0 +1,14 @@ +<%= l(:mail_body_title, name: @question.assigned_to, issue: "##{@issue.id}", author: @question.author) %> + +<% if Setting.plugin_question_plugin[:obfuscate_content] == "1" %> +<%= l(:mail_body_question_link) %> +<% else %> +<%= @journal.notes if @journal.notes? %> + +<% for detail in @journal.details -%> +<%= show_detail(detail, true) %> +<% end -%> + +---------------------------------------- +<% end %> +<%= render :partial => "mailer/issue", :locals => { :issue => @issue, :issue_url => @issue_url, :users => @users } %> diff --git a/app/views/question_mailer/asked_question.text.html.rhtml b/app/views/question_mailer/asked_question.text.html.rhtml deleted file mode 100644 index 36ca1ff..0000000 --- a/app/views/question_mailer/asked_question.text.html.rhtml +++ /dev/null @@ -1,12 +0,0 @@ -<%= l(:text_question_asked) %> <%= h "##{@issue.id}" %> <%= @question.author %> - -<%= textilizable(@journal, :notes, :only_path => false) %> - - - -
-<%= render :partial => "mailer/issue_text_html", :locals => { :issue => @issue, :issue_url => @issue_url } %> diff --git a/app/views/question_mailer/asked_question.text.plain.rhtml b/app/views/question_mailer/asked_question.text.plain.rhtml deleted file mode 100644 index 658618b..0000000 --- a/app/views/question_mailer/asked_question.text.plain.rhtml +++ /dev/null @@ -1,10 +0,0 @@ -<%= l(:text_question_asked) %> <%= h "##{@issue.id}" %> <%= @question.author %> - -<%= @journal.notes if @journal.notes? %> - -<% for detail in @journal.details -%> -<%= @journal.render_detail(detail, true) %> -<% end -%> - ----------------------------------------- -<%= render :partial => "mailer/issue_text_plain", :locals => { :issue => @issue, :issue_url => @issue_url } %> diff --git a/app/views/question_mailer/question_reminder.text.html.rhtml b/app/views/question_mailer/question_reminder.html.erb similarity index 100% rename from app/views/question_mailer/question_reminder.text.html.rhtml rename to app/views/question_mailer/question_reminder.html.erb diff --git a/app/views/question_mailer/question_reminder.text.plain.rhtml b/app/views/question_mailer/question_reminder.text.erb similarity index 100% rename from app/views/question_mailer/question_reminder.text.plain.rhtml rename to app/views/question_mailer/question_reminder.text.erb diff --git a/app/views/questions/autocomplete_for_user_login.html.erb b/app/views/questions/autocomplete_for_user_login.html.erb deleted file mode 100644 index c6057b4..0000000 --- a/app/views/questions/autocomplete_for_user_login.html.erb +++ /dev/null @@ -1,22 +0,0 @@ - diff --git a/app/views/settings/_question_plugin.html.erb b/app/views/settings/_question_plugin.html.erb new file mode 100644 index 0000000..28117c6 --- /dev/null +++ b/app/views/settings/_question_plugin.html.erb @@ -0,0 +1,28 @@ +

+ <%= label_tag "settings[only_members]", l(:label_question_plugin_only_members) %> + <%= select_tag 'settings[only_members]', options_for_select([ + [l(:text_question_all), 0], + [l(:text_question_project_member), 1], + ], Setting.plugin_question_plugin[:only_members]) %> +

+

+ <%= label_tag "settings[close_all_questions]", l(:label_question_plugin_close_settings) %> + <%= select_tag 'settings[close_all_questions]', options_for_select([ + [l(:text_question_close_all), 1], + [l(:text_question_close_selected), 0], + ], Setting.plugin_question_plugin[:close_all_questions]) %> + +

+

+ <%= label_tag "settings[obfuscate_author]", l(:label_question_plugin_notifications_obfuscate_author_settings) %> + <%= check_box_tag 'settings[obfuscate_author]', 1, (Setting.plugin_question_plugin[:obfuscate_author] ? true : false) %> + +

+

+ <%= label_tag "settings[obfuscate_content]", l(:label_question_plugin_notifications_obfuscate_content_settings) %> + <%= check_box_tag 'settings[obfuscate_content]', 1, (Setting.plugin_question_plugin[:obfuscate_content] ? true : false) %> +

+

+ <%= label_tag "settings[show_banner]", l(:label_question_plugin_show_banner_settings) %> + <%= check_box_tag 'settings[show_banner]', 1, (Setting.plugin_question_plugin[:show_banner] ? true : false) %> +

diff --git a/config/locales/cs.yml b/config/locales/cs.yml new file mode 100644 index 0000000..71d150b --- /dev/null +++ b/config/locales/cs.yml @@ -0,0 +1,36 @@ +cs: + field_question_assign_to: Položit otázku na + field_question_assigned_to: Otázka pro + field_question_asked_by: Otázka od + field_formatted_questions: Otázky + text_question: Otázka + text_question_answered: Otázka zodpovězena + text_answer: Odpověď + text_anyone: Kdokoliv + text_question_for: Otázka pro + text_question_for_anyone: Otázka pro kohokoliv + text_questions_for_me: Otázky pro mě + text_question_asked: Položené otázky + mail_body_title: "Máte novou otázku od %{author}" + mail_body_question_link: Otázku zobrazíte kliknutím na následující odkaz + mail_body_answer_link: "Vaše otázka byla zodpovězena: klikněte na odkaz níže k jejímu zobrazení" + text_question_remove: "<< Odstranit Otázku >>" + question_text_asked_by: "Otázka od" + question_text_assigned_to: "Otázka pro" + question_text_created_on: "Vytvořeno" + question_text_ratio_questions_answered: "{{ratio}} zodpovězených otázek" + field_journal: "Žurnál" + text_questions_asked_by_me: "Otázky ode mne" + question_reminder_subject: "%{count} nezodpovězených otázek" + question_reminder_body: "%{count} nezodpovězených otázek, které jsou položeny na vás:" + label_question_plugin_only_members: Otázku lze položit na + text_question_all: Jakéhokoliv uživatele + text_question_project_member: Pouze členy projektu + label_question_plugin_close_settings: Zodpovědět při přidání komentáře + text_question_close_all: všechny nezodpovězené otázky v úkolu + text_question_close_selected: pouze vybranou otázku + label_question_plugin_notifications_obfuscate_author_settings: Skrýt jméno a e-mail dotazujícího/dotazovaného v notifikacích + label_question_plugin_notifications_obfuscate_content_settings: Skrýt otázku/odpověď v notifikacích + field_question_to_answer: Otázka k zodpovězení + label_question_plugin_show_banner_settings: Zobrazit nezodpovězenou otázku na každé stránce + label_question_plugin_hide: Skrýt diff --git a/config/locales/de.yml b/config/locales/de.yml index 190785a..31b1577 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1,17 +1,33 @@ de: - field_question_assign_to: Frage zuweisen an - field_question_assigned_to: Frage ist gestellt an - field_question_asked_by: Frage wurde gestellt durch - field_formatted_questions: Fragen - text_question: Frage - text_question_answered: Frage beantwortet - text_answer: Antwort - text_anyone: Irgendwer - text_question_for: Frage für - text_question_for_anyone: Frage für irgendjemanden - text_questions_for_me: Fragen für mich - text_question_asked: Frage gestellt + field_question_assign_to: "Frage zuweisen an" + field_question_assigned_to: "Frage ist gestellt an" + field_question_asked_by: "Frage wurde gestellt durch" + field_formatted_questions: "Fragen" + text_question: "Frage" + text_question_answered: "Frage beantwortet" + text_answer: "Antwort" + text_anyone: "Irgendwer" + text_question_for: "Frage für" + text_question_for_anyone: "Frage für irgendjemanden" + text_questions_for_me: "Fragen für mich" + text_question_asked: "Frage gestellt" text_question_remove: "<>" question_text_asked_by: "gefragt von" question_text_assigned_to: "Zugewiesen an" - question_text_created_on: "Erstellt am" + question_text_created_on: "erstellt am" + question_text_ratio_questions_answered: "{{ratio}} beantwortete Fragen" + field_journal: "Journal" + text_questions_asked_by_me: "meine gestellten Fragen" + question_reminder_subject: "%{count} offene Frage(n)" + question_reminder_body: "%{count} offene dir zugewiesene Frage(n)" + label_question_plugin_only_members: Die Frage kann diesem Benutzer zugewiesen werden + text_question_all: jedem Benutzer + text_question_project_member: Projektmitarbeitern + label_question_plugin_close_settings: Frage des Tickets abschließend beantworten + text_question_close_all: alle offene Fragen + text_question_close_selected: ausschließlich aktuell ausgewählte Frage + label_question_plugin_notifications_obfuscate_author_settings: Name und Email des Fragestellers in Email Benachrichtigungen verbergen + label_question_plugin_notifications_obfuscate_content_settings: Frage- und Anworttext in Email Benachrichtigungen verbergen + field_question_to_answer: Frage beantworten + mail_body_question_link: Zeige Sie die Frage für sich an, indem sie auf den nachfolgenden Link für das Ticket klicken + mail_body_answer_link: "Ihre Frage wurde beantwortet: Klicken Sie auf den nachfolgenden Link für das Ticket um die Antwort zu sehen" diff --git a/config/locales/en.yml b/config/locales/en.yml index 8098bf2..a1afce1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -11,13 +11,26 @@ en: text_question_for_anyone: Question for anyone text_questions_for_me: Questions for me text_question_asked: Question asked + mail_body_title: "You have new question from %{author}" + mail_body_question_link: Display question by clicking on the issue link below + mail_body_answer_link: "Your question has received an answer: click on the issue link below to display it" text_question_remove: "<< Remove Question>>" question_text_asked_by: "Asked By" - question_text_assigned_to: "Assigned To" + question_text_assigned_to: "Question assigned To" question_text_created_on: "Created On" question_text_ratio_questions_answered: "{{ratio}} questions answered" field_journal: "Journal" text_questions_asked_by_me: "Questions asked by me" question_reminder_subject: "%{count} open question(s)" question_reminder_body: "%{count} open questions(s) that are assigned to you:" - + label_question_plugin_only_members: Question can be assigned to + text_question_all: Any user + text_question_project_member: Project members only + label_question_plugin_close_settings: When add comment close + text_question_close_all: all opened questions in issue + text_question_close_selected: only selected question + label_question_plugin_notifications_obfuscate_author_settings: Obfuscate asker/answerer name and e-mail address in notifications + label_question_plugin_notifications_obfuscate_content_settings: Obfuscate question/answer text in notifications + field_question_to_answer: Answer question + label_question_plugin_show_banner_settings: Show unanswered questions on every page + label_question_plugin_hide: Hide diff --git a/config/locales/es.yml b/config/locales/es.yml index 7d6384d..b30e402 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1,19 +1,19 @@ es: - field_question_assign_to: Asigne la pregunta a - field_question_assigned_to: Pregunata se asigna a - field_question_asked_by: Pregunta fue hecha cerca - field_formatted_questions: Preguntas - text_question: Pregunta - text_question_answered: Pregunta contest - text_answer: Respuesta - text_anyone: Cualquier persona - text_question_for: Pregunta para - text_question_for_anyone: Pregunta para cualquier persona - text_questions_for_me: Preguntas para m - text_question_asked: Pregunta pidi + field_question_assign_to: "Asigne la pregunta a" + field_question_assigned_to: "Pregunata se asigna a" + field_question_asked_by: "Pregunta fue hecha cerca" + field_formatted_questions: "Preguntas" + text_question: "Pregunta" + text_question_answered: "Pregunta contestó" + text_answer: "Respuesta" + text_anyone: "Cualquier persona" + text_question_for: "Pregunta para" + text_question_for_anyone: "Pregunta para cualquier persona" + text_questions_for_me: "Preguntas para mí" + text_question_asked: "Pregunta pidió" text_question_remove: "<< Quite la pregunta >>" question_text_asked_by: "Pedido Cerca" question_text_assigned_to: "Asignado A" question_text_created_on: "Creada hace" question_text_ratio_questions_answered: "{{ratio}} Preguntas contestaron" - field_journal: "Diario" \ No newline at end of file + field_journal: "Diario" diff --git a/config/locales/hu.yml b/config/locales/hu.yml index ffe7d21..4ecc704 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -1,16 +1,16 @@ hu: - field_question_assign_to: Kérdés hozzá - field_question_assigned_to: Kérdés hozzá - field_question_asked_by: Kérdést feltette - field_formatted_questions: Kérdések - text_question: Kérdés - text_question_answered: Kérdés megválaszolva - text_answer: Kérdés - text_anyone: - bárkihez - - text_question_for: Kérdés hozzá - text_question_for_anyone: Kérdés bárkinek - text_questions_for_me: Kérdések hozzám - text_question_asked: Feltett kérdések + field_question_assign_to: "Kérdés hozzá" + field_question_assigned_to: "Kérdés hozzá" + field_question_asked_by: "Kérdést feltette" + field_formatted_questions: "Kérdések" + text_question: "Kérdés" + text_question_answered: "Kérdés megválaszolva" + text_answer: "Kérdés" + text_anyone: "- bárkihez -" + text_question_for: "Kérdés hozzá" + text_question_for_anyone: "Kérdés bárkinek" + text_questions_for_me: "Kérdések hozzám" + text_question_asked: "Feltett kérdések" text_question_remove: "kérdés törlése" question_text_asked_by: "Kérdezte" question_text_assigned_to: "Hozzárendelve" diff --git a/config/locales/ru.yml b/config/locales/ru.yml new file mode 100644 index 0000000..dd3adb5 --- /dev/null +++ b/config/locales/ru.yml @@ -0,0 +1,35 @@ +ru: + field_question_assign_to: Спросить у + field_question_assigned_to: Вопрос к + field_question_asked_by: Вопрос задал + field_formatted_questions: Вопросы + text_question: Вопрос + text_question_answered: Вопрос отвечен + text_answer: Ответ + text_anyone: Все + text_question_for: Вопрос к + text_question_for_anyone: Вопрос ко всем + text_questions_for_me: Вопросы ко мне + mail_body_question_link: Для просмотра вопроса нажмите на нижеследующую ссылку на задачу + mail_body_answer_link: "Ваш вопрос получил ответ: Для его просмотра нажмите на нижеследующую ссылку на задачу" + text_question_asked: Вопрос задан + text_question_remove: "<< Удалить вопрос >>" + question_text_asked_by: "Автор вопроса" + question_text_assigned_to: "Вопрос назначен" + question_text_created_on: "Создан" + question_text_ratio_questions_answered: "{{ratio}} вопросов отвечено" + field_journal: "Journal" + text_questions_asked_by_me: "Вопросы заданные мной" + question_reminder_subject: "%{count} не отвеченных вопросов" + question_reminder_body: "%{count} не отвеченных вопросов заданных Вам:" + label_question_plugin_only_members: Вопрос может быть задан + text_question_all: Любому пользователю + text_question_project_member: Только участникам проекта + label_question_plugin_close_settings: При добавлении комментария закрывать + text_question_close_all: все неотвеченные вопросы в задаче + text_question_close_selected: только выбранный вопрос + label_question_plugin_notifications_obfuscate_author_settings: Прятать имя и e-mail спрашивающего/отвечающего в оповещениях + label_question_plugin_notifications_obfuscate_content_settings: Прятать текст вопроса/ответа в оповещениях + field_question_to_answer: Ответ на вопрос + label_question_plugin_show_banner_settings: Показывать неотвеченные вопросы на каждой странице + label_question_plugin_hide: Скрыть diff --git a/config/locales/tr.yml b/config/locales/tr.yml new file mode 100644 index 0000000..a8f157c --- /dev/null +++ b/config/locales/tr.yml @@ -0,0 +1,29 @@ +tr: + field_question_assign_to: Soruyu şu kişiye ata + field_question_assigned_to: Soru şu kişiye atanmış + field_question_asked_by: Soruyu soran + field_formatted_questions: Sorular + text_question: Soru + text_question_answered: Soru Cevaplandı + text_answer: Cevap + text_anyone: Herhangi biri + text_question_for: Şu kişiye sor + text_question_for_anyone: Herhangi birine sor + text_questions_for_me: Bana sorulmuş sorular + text_question_asked: Sorulmuş sorular + text_question_remove: "<< Soruyu Sil >>" + question_text_asked_by: "Şu kişi sormuş" + question_text_assigned_to: "Soru şu kişiye atanmış" + question_text_created_on: "Oluşturulma" + question_text_ratio_questions_answered: "{{ratio}} soru cevaplandı" + field_journal: "Günlük" + text_questions_asked_by_me: "Benim sorduğum sorular" + question_reminder_subject: "%{count} açık soru" + question_reminder_body: "Bana atanmış %{count} açık soru:" + label_question_plugin_only_members: Soru şu kişilere atanabilir + text_question_all: Herhangi bir kullanıcı + text_question_project_member: Sadece birim üyeleri + label_question_plugin_close_settings: Not ekleyince kapat + text_question_close_all: İş içindeki tüm sorular + text_question_close_selected: sadece seçilmiş soru + field_question_to_answer: Soruyu cevapla diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..811bff0 --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,6 @@ +RedmineApp::Application.routes.draw do + match 'issuequestions/autocomplete_for_user_login/project/:id/issue/:issue_id' => 'issuequestions#autocomplete_for_user_login', :format => false, :as => 'issuequestions_autocomplete_for_user_login', :via => [:get, :post] + match 'issuequestions/my_issue_filter(/project/:project)' => 'issuequestions#my_issue_filter', :format => false, :as => 'questions_my_issue_filter', :via => [:get, :post] + match 'issuequestions/user_issue_filter/user/:user_id' => 'issuequestions#user_issue_filter', :format => false, :as => 'questions_user_issue_filter', :via => [:get, :post] + match 'issuequestions/hide/:id' => 'issuequestions#hide', :as => 'hide', :via => [:get, :post] +end diff --git a/db/migrate/006_add_hidden_to_questions.rb b/db/migrate/006_add_hidden_to_questions.rb new file mode 100644 index 0000000..55d7cdd --- /dev/null +++ b/db/migrate/006_add_hidden_to_questions.rb @@ -0,0 +1,9 @@ +class AddHiddenToQuestions < ActiveRecord::Migration + def self.up + add_column :questions, :hidden, :boolean, :default => false + end + + def self.down + remove_column :questions, :hidden + end +end diff --git a/init.rb b/init.rb index d1e0648..5a476df 100644 --- a/init.rb +++ b/init.rb @@ -1,31 +1,38 @@ require 'redmine' -require 'question_issue_hooks' -require 'question_kanban_hooks' -require 'question_layout_hooks' -require 'question_journal_hooks' +require 'question_plugin/hooks/issue_hooks' +require 'question_plugin/hooks/kanban_hooks' +require 'question_plugin/hooks/layout_hooks' +require 'question_plugin/hooks/journal_hooks' -# Patches to the Redmine core. -require 'dispatcher' - -Dispatcher.to_prepare :question_plugin do - - require_dependency 'journal_observer' - JournalObserver.send(:include, QuestionPlugin::Patches::JournalObserverPatch) +Rails.configuration.to_prepare do require_dependency 'issue' - Issue.send(:include, QuestionIssuePatch) unless Issue.included_modules.include? QuestionIssuePatch + Issue.send(:include, QuestionPlugin::Patches::IssuePatch) unless Issue.included_modules.include? QuestionPlugin::Patches::IssuePatch require_dependency 'journal' - Journal.send(:include, QuestionJournalPatch) unless Journal.included_modules.include? QuestionJournalPatch - - require_dependency 'queries_helper' - QueriesHelper.send(:include, QuestionQueriesHelperPatch) unless QueriesHelper.included_modules.include? QuestionQueriesHelperPatch - - require_dependency "query" - Query.send(:include, QuestionQueryPatch) unless Query.included_modules.include? QuestionQueryPatch + Journal.send(:include, QuestionPlugin::Patches::JournalPatch) unless Journal.included_modules.include? QuestionPlugin::Patches::JournalPatch + + require_dependency 'application_helper' + ApplicationHelper.send(:include, QuestionPlugin::Patches::ApplicationHelperPatch) unless ApplicationHelper.included_modules.include? QuestionPlugin::Patches::ApplicationHelperPatch + + if ActiveSupport::Dependencies::search_for_file('issue_queries_helper') + require_dependency 'issue_queries_helper' + IssueQueriesHelper.send(:include, QuestionPlugin::Patches::QueriesHelperPatch) unless IssueQueriesHelper.included_modules.include? QuestionPlugin::Patches::QueriesHelperPatch + else + require_dependency 'queries_helper' + QueriesHelper.send(:include, QuestionPlugin::Patches::QueriesHelperPatch) unless QueriesHelper.included_modules.include? QuestionPlugin::Patches::QueriesHelperPatch + end + + if ActiveSupport::Dependencies::search_for_file('issue_query') + require_dependency 'issue_query' + IssueQuery.send(:include, QuestionPlugin::Patches::QueryPatch) unless Query.included_modules.include? QuestionPlugin::Patches::QueryPatch + else + require_dependency 'query' + Query.send(:include, QuestionPlugin::Patches::QueryPatch) unless Query.included_modules.include? QuestionPlugin::Patches::QueryPatch + end end -Redmine::Plugin.register :question_plugin do +p = Redmine::Plugin.register :question_plugin do name 'Redmine Question plugin' author 'Eric Davis' url "https://projects.littlestreamsoftware.com/projects/redmine-questions" if respond_to?(:url) @@ -33,8 +40,21 @@ description 'This is a plugin for Redmine that will allow users to ask questions to each other in issue notes' version '0.3.0' - requires_redmine :version_or_higher => '0.8.0' + requires_redmine :version_or_higher => '3.0.0' + + settings :default => { + :only_members => 1, + :close_all_questions => 1, + :obfuscate_author => 0, + :obfuscate_content => 0, + }, :partial => 'settings/question_plugin' + +end +# Ensure ActionMailer knows where to find the views for the question plugin +view_path = File.join(p.directory, 'app', 'views') +if File.directory?(view_path) + ActionMailer::Base.prepend_view_path(view_path) end require 'question_plugin/hooks/view_user_kanbans_show_contextual_top_hook' diff --git a/lang/de.yml b/lang/de.yml deleted file mode 100644 index f390902..0000000 --- a/lang/de.yml +++ /dev/null @@ -1,16 +0,0 @@ -field_question_assign_to: Frage zuweisen an -field_question_assigned_to: Frage ist gestellt an -field_question_asked_by: Frage wurde gestellt durch -field_formatted_questions: Fragen -text_question: Frage -text_question_answered: Frage beantwortet -text_answer: Antwort -text_anyone: Irgendwer -text_question_for: Frage für -text_question_for_anyone: Frage für irgendjemanden -text_questions_for_me: Fragen für mich -text_question_asked: Frage gestellt -text_question_remove: "<>" -question_text_asked_by: "gefragt von" -question_text_assigned_to: "Zugewiesen an" -question_text_created_on: "Erstellt am" diff --git a/lang/en.yml b/lang/en.yml deleted file mode 100644 index 360c3f0..0000000 --- a/lang/en.yml +++ /dev/null @@ -1,16 +0,0 @@ -field_question_assign_to: Assign question to -field_question_assigned_to: Question is assigned to -field_question_asked_by: Question was asked by -field_formatted_questions: Questions -text_question: Question -text_question_answered: Question Answered -text_answer: Answer -text_anyone: Anyone -text_question_for: Question for -text_question_for_anyone: Question for anyone -text_questions_for_me: Questions for me -text_question_asked: Question asked -text_question_remove: "<< Remove Question>>" -question_text_asked_by: "Asked By" -question_text_assigned_to: "Assigned To" -question_text_created_on: "Created On" diff --git a/lib/question_hooks_base.rb b/lib/question_hooks_base.rb deleted file mode 100644 index 5aedfcf..0000000 --- a/lib/question_hooks_base.rb +++ /dev/null @@ -1,24 +0,0 @@ -class QuestionHooksBase < Redmine::Hook::ViewListener - # Have to inclue Gravatars because ApplicationHelper will not get it - include Gravatarify::Helper - - protected - - def assigned_question_html(question) - html = "" - html << " " - html << "#{l(:text_question_for)} #{question.assigned_to.to_s}" - html << " " - html << "#{avatar(question.assigned_to, { :size => 16, :class => '' })} " if question.assigned_to && question.assigned_to.mail - html - end - - def unassigned_question_html(question) - html = "" - html << " " - html << l(:text_question_for_anyone) - html << " " - html << "" - html - end -end diff --git a/lib/question_issue_hooks.rb b/lib/question_issue_hooks.rb deleted file mode 100644 index 175a0b1..0000000 --- a/lib/question_issue_hooks.rb +++ /dev/null @@ -1,88 +0,0 @@ -class QuestionIssueHooks < QuestionHooksBase - # Applies the question class to each journal div if they are questions - def view_issues_history_journal_bottom(context = { }) - o = '' - if context[:journal] && context[:journal].question && context[:journal].question.opened? - question = context[:journal].question - - if question.assigned_to - html = assigned_question_html(question) - else - html = unassigned_question_html(question) - end - - o += < - $('change-#{context[:journal].id}').addClassName('question'); - $$('#change-#{context[:journal].id} h4 div').each(function(ele) { ele.insert({ top: ' #{html} ' }) }); - -JS - - end - return o - end - - def view_issues_edit_notes_bottom(context = { }) - f = context[:form] - @issue = context[:issue] - o = '' - o << content_tag(:p, - " " + - text_field_tag('note[question_assigned_to]', nil, :size => "40")) - - o << content_tag(:div,'', :id => "note_question_assigned_to_choices", :class => "autocomplete") - o << javascript_tag("new Ajax.Autocompleter('note_question_assigned_to', 'note_question_assigned_to_choices', '#{ url_for(:controller => 'questions', :action => 'autocomplete_for_user_login', :id => @issue.project, :issue_id => @issue) }', { minChars: 1, frequency: 0.5, paramName: 'user', select: 'field' });") - - return o - end - - def controller_issues_edit_before_save(context = { }) - params = context[:params] - journal = context[:journal] - issue = context[:issue] - if params[:note] && !params[:note][:question_assigned_to].blank? - unless journal.question # Update handled by Journal hooks - # New - issue.extra_journal_attributes = { - :question => Question.new( - :author => User.current, - :issue => journal.issue, - :assigned_to => User.find_by_login(params[:note][:question_assigned_to]) - ) - } - end - end - - return '' - end - - def view_issues_sidebar_issues_bottom(context = { }) - project = context[:project] - if project - question_count = Question.count_of_open_for_user_on_project(User.current, project) - else - question_count = Question.count_of_open_for_user(User.current) - end - - if question_count > 0 - return link_to(l(:text_questions_for_me) + " (#{question_count})", - { - :controller => 'questions', - :action => 'my_issue_filter', - :project => project, - :only_path => true - }, - { :class => 'question-link' } - ) + '
' - else - return '' - end - - end - - private - - def assign_question_to_user(journal, user) - journal.question.assigned_to = user - end -end diff --git a/lib/question_issue_patch.rb b/lib/question_issue_patch.rb deleted file mode 100644 index 88ed551..0000000 --- a/lib/question_issue_patch.rb +++ /dev/null @@ -1,101 +0,0 @@ -module QuestionIssuePatch - def self.included(base) # :nodoc: - base.extend(ClassMethods) - - base.send(:include, InstanceMethods) - - base.class_eval do - unloadable # Send unloadable so it will not be unloaded in development - has_many :questions - has_many :open_questions, :class_name => 'Question', :conditions => { :opened => true } - - include ActionView::Helpers::TextHelper # for truncate - - class << self - # I dislike alias method chain, it's not the most readable backtraces - alias_method :default_find, :find - alias_method :find, :find_with_questions_added_to_the_includes - - alias_method :default_count, :count - alias_method :count, :count_with_questions_added_to_the_includes - - alias_method :default_sum, :sum - alias_method :sum, :sum_with_questions_added_to_the_includes - end - end - - end - - module ClassMethods - def find_with_questions_added_to_the_includes(*args) - scan_for_options_hash_and_add_includes_if_needed(args) - default_find(*args) - end - - def count_with_questions_added_to_the_includes(*args) - scan_for_options_hash_and_add_includes_if_needed(args) - default_count(*args) - end - - def sum_with_questions_added_to_the_includes(*args) - scan_for_options_hash_and_add_includes_if_needed(args) - default_sum(*args) - end - - private - - # Finds the options hash. If question is part of the conditions then - # add questions to the includes - def scan_for_options_hash_and_add_includes_if_needed(args) - args.each do |arg| - if arg.is_a?(Hash) && arg[:conditions] - if arg[:conditions].is_a?(String) && arg[:conditions].include?('question') - # String conditions - add_questions_to_the_includes(arg) - elsif arg[:conditions].is_a?(Array) && arg[:conditions][0].include?('question') - # Array conditions - add_questions_to_the_includes(arg) - end - end - end - end - - def add_questions_to_the_includes(arg) - if arg[:include] - # Has includes - if arg[:include].is_a?(Hash) - # Hash includes - arg[:include] << :questions - else - # single includes - arg[:include] = [ arg[:include] , :questions ] - end - else - # No includes - arg[:include] = :questions - end - end - end - - module InstanceMethods - def pending_question?(user) - self.open_questions.find(:all).each do |question| - return true if question.assigned_to == user || question.for_anyone? - end - return false - end - - def close_pending_questions(user, closing_journal) - self.open_questions.find(:all).each do |question| - question.close!(closing_journal) if question.assigned_to == user || question.for_anyone? - end - end - - def formatted_questions - open_questions.collect do |question| - truncate(question.journal.notes, Question::TruncateTo) - end.join(", ") - end - end -end - diff --git a/lib/question_journal_hooks.rb b/lib/question_journal_hooks.rb deleted file mode 100644 index 66ff203..0000000 --- a/lib/question_journal_hooks.rb +++ /dev/null @@ -1,96 +0,0 @@ -class QuestionJournalHooks < QuestionHooksBase - def view_journals_notes_form_after_notes(context = { }) - @journal = context[:journal] - if @journal.question && @journal.question.opened && @journal.question.assigned_to - assigned_to = @journal.question.assigned_to.login - else - assigned_to = '' - end - - o = '' - o << content_tag(:p, - " " + - text_field_tag('question[assigned_to]', assigned_to, :size => "40")) - - o << content_tag(:div,'', :id => "question_assigned_to_choices", :class => "autocomplete") - o << javascript_tag("new Ajax.Autocompleter('question_assigned_to', 'question_assigned_to_choices', '#{ url_for(:controller => 'questions', :action => 'autocomplete_for_user_login', :id => @journal.project, :issue_id => @journal.issue) }', { minChars: 1, frequency: 0.5, paramName: 'user', select: 'field' });") - - return o - end - - def controller_journals_edit_post(context = { }) - journal = context[:journal] - params = context[:params] - - # Handle destroying journals through the 'edit' action (done by clearing notes) - return '' if journal.destroyed? - - if params[:question] && params[:question][:assigned_to] - if journal.question && params[:question][:assigned_to].blank? - # Wants to remove the question - journal.question.destroy - elsif journal.question && journal.question.opened - # Reassignment - if params[:question][:assigned_to].downcase == 'anyone' - journal.question.update_attributes(:assigned_to => nil) - else - journal.question.update_attributes(:assigned_to => User.find_by_login(params[:question][:assigned_to])) - end - elsif journal.question && !journal.question.opened - # Existing question, destry it first and then add a new question - journal.question.destroy - add_new_question(journal, User.find_by_login(params[:question][:assigned_to])) - else - if params[:question][:assigned_to].downcase == 'anyone' - add_new_question(journal) - elsif !params[:question][:assigned_to].blank? - add_new_question(journal, User.find_by_login(params[:question][:assigned_to])) - else - # No question - end - end - - end - - return '' - end - - def view_journals_update_rjs_bottom(context = { }) - @journal = context[:journal] - page = context[:page] - unless @journal.frozen? - @journal.reload - if @journal && @journal.question && @journal.question.opened? - question = @journal.question - - if question.assigned_to - html = assigned_question_html(question) - else - html = unassigned_question_html(question) - end - - page << "$('change-#{@journal.id}').addClassName('question');" - page << "$$('#change-#{@journal.id} h4 div span.question-line').each(function(ele) {ele.remove()});" - page << "$$('#change-#{@journal.id} h4 div').each(function(ele) { ele.insert({ top: ' #{html} ' }) });" - - elsif @journal && @journal.question.nil? - # No question found, make sure the UI reflects this - page << "$('change-#{@journal.id}').removeClassName('question');" - page << "$$('#change-#{@journal.id} h4 div span.question-line').each(function(ele) {ele.remove()});" - end - end - return '' - end - - private - - def add_new_question(journal, assigned_to=nil) - journal.question = Question.new( - :author => User.current, - :issue => journal.issue, - :assigned_to => assigned_to - ) - journal.question.save! - journal.save - end -end diff --git a/lib/question_journal_patch.rb b/lib/question_journal_patch.rb deleted file mode 100644 index 5070005..0000000 --- a/lib/question_journal_patch.rb +++ /dev/null @@ -1,22 +0,0 @@ -module QuestionJournalPatch - def self.included(base) # :nodoc: - base.extend(ClassMethods) - - base.send(:include, InstanceMethods) - - base.class_eval do - unloadable # Send unloadable so it will not be unloaded in development - has_one :question, :dependent => :destroy - end - - end - - module ClassMethods - end - - module InstanceMethods - def question_assigned_to - # TODO: pull out the assigned user on edits - end - end -end diff --git a/lib/question_kanban_hooks.rb b/lib/question_kanban_hooks.rb deleted file mode 100644 index f308378..0000000 --- a/lib/question_kanban_hooks.rb +++ /dev/null @@ -1,59 +0,0 @@ -class QuestionKanbanHooks < QuestionHooksBase - def view_kanbans_issue_details(context = {}) - # GREY when there are no questions - # RED when there are open questions - # BLACK if all questions are answered - issue = context[:issue] - - return '' unless issue - - if issue.questions.count == 0 - return question_icon(:gray, issue) - end - - if issue.open_questions.count > 0 - return question_icon(:red, issue) - end - - if issue.questions.count > 0 && issue.open_questions.count == 0 - return question_icon(:black, issue) - end - - return '' - end - - # * :user - def view_kanbans_user_name(context = {}) - user = context[:user] - if user - count = Question.count_of_open_for_user(user) - - if count > 0 - return content_tag(:p, link_to(l(:field_formatted_questions) + " (#{count})", - { - :controller => 'questions', - :action => 'user_issue_filter', - :user_id => user.id, - :only_path => true - }, - { :class => 'question-link' })) - end - end - - return '' - - end - - protected - - def question_icon(color, issue) - total_questions = issue.questions.count - open_questions = issue.open_questions.count - answered_questions = total_questions - open_questions - - title = l(:question_text_ratio_questions_answered, :ratio => "#{answered_questions}/#{total_questions}") - link_to(image_tag("question-#{color}.png", :plugin => 'question_plugin', :title => title, :class => "kanban-question #{color}"), - { :controller => 'issues', :action => 'show', :id => issue }, - :class => "issue-show-popup issue-id-#{h(issue.id)}") - end -end diff --git a/lib/question_layout_hooks.rb b/lib/question_layout_hooks.rb deleted file mode 100644 index 2870471..0000000 --- a/lib/question_layout_hooks.rb +++ /dev/null @@ -1,18 +0,0 @@ -class QuestionLayoutHooks < Redmine::Hook::ViewListener - - # Add a question CSS class - def view_layouts_base_html_head(context = { }) - o = < -.question { background-color:#FFEBC1; border:2px solid #FDBD3B; margin-bottom:12px; padding:0px 4px 8px 4px; } -td.formatted_questions { text-align: left; white-space: normal} -td.formatted_questions ol { margin-top: 0px; margin-bottom: 0px; } - -.kanban-question { background:#FFFFFF none repeat scroll 0 0; border:1px solid #D5D5D5; padding:2px; font-size: 0.8em } -.question-link {font-weight: bold; } /* Kanban Menu item */ - - -CSS - return o - end -end diff --git a/lib/question_plugin/hooks/hooks_base.rb b/lib/question_plugin/hooks/hooks_base.rb new file mode 100644 index 0000000..aace9dc --- /dev/null +++ b/lib/question_plugin/hooks/hooks_base.rb @@ -0,0 +1,25 @@ +module QuestionPlugin + module Hooks + class HooksBase < Redmine::Hook::ViewListener + # Have to inclue Gravatars because ApplicationHelper will not get it + include GravatarHelper::PublicMethods + + protected + + def assigned_question_html(question) + html = "" + html << " #{l(:text_question_for)} " + html << link_to_user(question.assigned_to) + html << " " if question.assigned_to && question.assigned_to.mail + html + end + + def unassigned_question_html(question) + html = "" + html << l(:text_question_for_anyone) + html << " " + html + end + end + end +end diff --git a/lib/question_plugin/hooks/issue_hooks.rb b/lib/question_plugin/hooks/issue_hooks.rb new file mode 100644 index 0000000..6817cb1 --- /dev/null +++ b/lib/question_plugin/hooks/issue_hooks.rb @@ -0,0 +1,112 @@ +module QuestionPlugin + module Hooks + class IssueHooks < HooksBase + # Applies the question class to each journal div if they are questions + def view_issues_history_journal_bottom(context = { }) + o = '' + if context[:journal] && context[:journal].question + question = context[:journal].question + + if question.assigned_to + html = assigned_question_html(question) + else + html = unassigned_question_html(question) + end + + className = question.opened ? 'question' : 'question-closed' + o += < + $('#change-#{context[:journal].id}').addClass('#{className}'); + $('#change-#{context[:journal].id} h4 .journal-link').after(' #{html} '); + +JS + end + return o + end + + def view_issues_edit_notes_bottom(context = { }) + f = context[:form] + @issue = context[:issue] + o = '' + + if @issue.pending_question?(User.current) && Setting.plugin_question_plugin[:close_all_questions] != "1" + questions = @issue.pending_questions(User.current) + o << content_tag(:p, + " ".html_safe + + select_tag('question_to_answer', options_for_select([[]] + questions.collect {|q| [truncate(q.journal.notes, :length => Question::TruncateTo), q.id]})) + ) + end + o << content_tag(:p, + " ".html_safe + + text_field_tag('note[question_assigned_to]', nil, :size => "40")) + o << javascript_tag("observeAutocompleteField('note_question_assigned_to', '#{escape_javascript issuequestions_autocomplete_for_user_login_path(@issue.project, @issue)}')") + + return o + end + + def controller_issues_edit_before_save(context = { }) + params = context[:params] + journal = context[:journal] + issue = context[:issue] + if params[:note] && !params[:note][:question_assigned_to].blank? + unless journal.question # Update handled by Journal hooks + # New + journal.question = Question.new( + :author => User.current, + :issue => journal.issue + ) + if params[:note][:question_assigned_to].downcase != 'anyone' + # Assigned to a specific user + assign_question_to_user(journal, User.find_by(:login => params[:note][:question_assigned_to])) + end + end + end + + if Setting.plugin_question_plugin[:close_all_questions] == "1" + # Close any open questions + if journal.issue.present? && journal.issue.pending_question?(journal.user) + journal.issue.close_pending_questions(journal.user, journal) + end + else + # Close specific question + if params[:question_to_answer] and !params[:question_to_answer].empty? + question = Question.find(params[:question_to_answer]) + question.close!(journal) + end + end + + return '' + end + + def view_issues_sidebar_issues_bottom(context = { }) + project = context[:project] + if project + question_count = Question.count_of_open_for_user_on_project(User.current, project) + else + question_count = Question.count_of_open_for_user(User.current) + end + + if question_count > 0 + return link_to(l(:text_questions_for_me) + " (#{question_count})", + { + :controller => 'issuequestions', + :action => 'my_issue_filter', + :project => project, + :only_path => true + }, + { :class => 'question-link' } + ) + '
'.html_safe + else + return '' + end + + end + + private + + def assign_question_to_user(journal, user) + journal.question.assigned_to = user + end + end + end +end diff --git a/lib/question_plugin/hooks/journal_hooks.rb b/lib/question_plugin/hooks/journal_hooks.rb new file mode 100644 index 0000000..dca1e9e --- /dev/null +++ b/lib/question_plugin/hooks/journal_hooks.rb @@ -0,0 +1,100 @@ +module QuestionPlugin + module Hooks + class JournalHooks < HooksBase + def view_journals_notes_form_after_notes(context = { }) + @journal = context[:journal] + if @journal.question && @journal.question.opened && @journal.question.assigned_to + assigned_to = @journal.question.assigned_to.login + else + assigned_to = '' + end + + o = '' + o << content_tag(:p, + " ".html_safe + + text_field_tag('question[assigned_to]', assigned_to, :size => "40")) + + o << javascript_tag("observeAutocompleteField('question_assigned_to', '#{escape_javascript issuequestions_autocomplete_for_user_login_path(@journal.issue.project, @journal.issue)}')") + + return o + end + + def controller_journals_edit_post(context = { }) + journal = context[:journal] + params = context[:params] + + # Handle destroying journals through the 'edit' action (done by clearing notes) + return '' if journal.destroyed? + + if params[:question] && params[:question][:assigned_to] + if journal.question && params[:question][:assigned_to].blank? + # Wants to remove the question + journal.question.destroy + elsif journal.question && journal.question.opened + # Reassignment + if params[:question][:assigned_to].downcase == 'anyone' + journal.question.update_attributes(:assigned_to => nil) + else + journal.question.update_attributes(:assigned_to => User.find_by(:login => params[:question][:assigned_to])) + end + elsif journal.question && !journal.question.opened + # Existing question, destry it first and then add a new question + journal.question.destroy + add_new_question(journal, User.find_by(:login => params[:question][:assigned_to])) + else + if params[:question][:assigned_to].downcase == 'anyone' + add_new_question(journal) + elsif !params[:question][:assigned_to].blank? + add_new_question(journal, User.find_by(:login => params[:question][:assigned_to])) + else + # No question + end + end + + end + + return '' + end + + def view_journals_update_js_bottom(context = { }) + @journal = context[:journal] + page = context[:page] + page = "" + unless @journal.frozen? + @journal.reload + if @journal && @journal.question && @journal.question.opened? + question = @journal.question + + if question.assigned_to + html = assigned_question_html(question) + else + html = unassigned_question_html(question) + end + + page << "$('#change-#{@journal.id}').addClass('question');" + page << "$('#change-#{@journal.id} h4 span.question-line').remove();" + page << "$('#change-#{@journal.id} h4 .journal-link').after(' #{html} ') ;" + + elsif @journal && @journal.question.nil? + # No question found, make sure the UI reflects this + page << "$('#change-#{@journal.id}').removeClass('question');" + page << "$('#change-#{@journal.id} h4 span.question-line').remove();" + end + end + return page + end + + private + + def add_new_question(journal, assigned_to=nil) + journal.question = Question.new( + :author => User.current, + :issue => journal.issue, + :assigned_to => assigned_to + ) + journal.question.save! + journal.save + end + end + end +end diff --git a/lib/question_plugin/hooks/kanban_hooks.rb b/lib/question_plugin/hooks/kanban_hooks.rb new file mode 100644 index 0000000..e775f22 --- /dev/null +++ b/lib/question_plugin/hooks/kanban_hooks.rb @@ -0,0 +1,63 @@ +module QuestionPlugin + module Hooks + class KanbanHooks < HooksBase + def view_kanbans_issue_details(context = {}) + # GREY when there are no questions + # RED when there are open questions + # BLACK if all questions are answered + issue = context[:issue] + + return '' unless issue + + if issue.questions.count == 0 + return question_icon(:gray, issue) + end + + if issue.open_questions.count > 0 + return question_icon(:red, issue) + end + + if issue.questions.count > 0 && issue.open_questions.count == 0 + return question_icon(:black, issue) + end + + return '' + end + + # * :user + def view_kanbans_user_name(context = {}) + user = context[:user] + if user + count = Question.count_of_open_for_user(user) + + if count > 0 + return content_tag(:p, link_to(l(:field_formatted_questions) + " (#{count})", + { + :controller => 'issuequestions', + :action => 'user_issue_filter', + :user_id => user.id, + :only_path => true + }, + { :class => 'question-link' })) + end + end + + return '' + + end + + protected + + def question_icon(color, issue) + total_questions = issue.questions.count + open_questions = issue.open_questions.count + answered_questions = total_questions - open_questions + + title = l(:question_text_ratio_questions_answered, :ratio => "#{answered_questions}/#{total_questions}") + link_to(image_tag("question-#{color}.png", :plugin => 'question_plugin', :title => title, :class => "kanban-question #{color}"), + { :controller => 'issues', :action => 'show', :id => issue }, + :class => "issue-show-popup issue-id-#{h(issue.id)}") + end + end + end +end diff --git a/lib/question_plugin/hooks/layout_hooks.rb b/lib/question_plugin/hooks/layout_hooks.rb new file mode 100644 index 0000000..47f0055 --- /dev/null +++ b/lib/question_plugin/hooks/layout_hooks.rb @@ -0,0 +1,24 @@ +module QuestionPlugin + module Hooks + class LayoutHooks < Redmine::Hook::ViewListener + + # Add a question CSS class + def view_layouts_base_html_head(context = { }) + o = < + +.question, div.flash.question { background-color:#FFEBC1; border:2px solid; margin-bottom:12px; padding:0px 4px 8px 4px; border-color: #fdbd2b} +.question-line { float: right; } +td.formatted_questions { text-align: left; white-space: normal} +td.formatted_questions ol { margin-top: 0px; margin-bottom: 0px; } + +.kanban-question { background:#FFFFFF none repeat scroll 0 0; border:1px solid #D5D5D5; padding:2px; font-size: 0.8em } +.question-link {font-weight: bold; } /* Kanban Menu item */ + + +CSS + return o + end + end + end +end diff --git a/lib/question_plugin/hooks/view_user_kanbans_show_contextual_top_hook.rb b/lib/question_plugin/hooks/view_user_kanbans_show_contextual_top_hook.rb index db4e995..1c23230 100644 --- a/lib/question_plugin/hooks/view_user_kanbans_show_contextual_top_hook.rb +++ b/lib/question_plugin/hooks/view_user_kanbans_show_contextual_top_hook.rb @@ -10,7 +10,7 @@ def view_user_kanbans_show_contextual_top(context={}) if count > 0 return link_to(l(:field_formatted_questions) + " (#{count})", { - :controller => 'questions', + :controller => 'issuequestions', :action => 'user_issue_filter', :user_id => user.id, :only_path => true diff --git a/lib/question_plugin/patches/application_helper_patch.rb b/lib/question_plugin/patches/application_helper_patch.rb new file mode 100644 index 0000000..920c8c0 --- /dev/null +++ b/lib/question_plugin/patches/application_helper_patch.rb @@ -0,0 +1,28 @@ +module QuestionPlugin + module Patches + module ApplicationHelperPatch + def self.included(base) # :nodoc: + base.extend(ClassMethods) + + base.send(:include, InstanceMethods) + + base.class_eval do + unloadable # Send unloadable so it will not be unloaded in development + alias_method_chain :render_flash_messages, :question + end + end + + module ClassMethods + end + + module InstanceMethods + def render_flash_messages_with_question + s = "" + s = render :partial => 'layouts/questions', :layout => false unless Setting.plugin_question_plugin[:show_banner] != "1" + s << render_flash_messages_without_question + s.html_safe + end + end + end + end +end diff --git a/lib/question_plugin/patches/issue_patch.rb b/lib/question_plugin/patches/issue_patch.rb new file mode 100644 index 0000000..47e84dd --- /dev/null +++ b/lib/question_plugin/patches/issue_patch.rb @@ -0,0 +1,51 @@ +module QuestionPlugin + module Patches + module IssuePatch + def self.included(base) # :nodoc: + base.extend(ClassMethods) + + base.send(:include, InstanceMethods) + + base.class_eval do + unloadable # Send unloadable so it will not be unloaded in development + has_many :questions + has_many :open_questions, lambda { Question.opened }, :class_name => 'Question' + + include ActionView::Helpers::TextHelper # for truncate + end + end + + module ClassMethods + end + + module InstanceMethods + def pending_question?(user) + self.open_questions.all.each do |question| + return true if question.assigned_to == user || question.for_anyone? + end + return false + end + + def pending_questions(user) + q = [] + self.open_questions.all.each do |question| + q << question if question.assigned_to == user || question.for_anyone? + end + return q + end + + def close_pending_questions(user, closing_journal) + self.pending_questions(user).each do |question| + question.close!(closing_journal) + end + end + + def formatted_questions + open_questions.collect do |question| + truncate(question.journal.notes, :length => Question::TruncateTo) + end.join(", ") + end + end + end + end +end diff --git a/lib/question_plugin/patches/journal_observer_patch.rb b/lib/question_plugin/patches/journal_observer_patch.rb deleted file mode 100644 index 9a64d52..0000000 --- a/lib/question_plugin/patches/journal_observer_patch.rb +++ /dev/null @@ -1,40 +0,0 @@ -module QuestionPlugin - module Patches - module JournalObserverPatch - def self.included(base) - base.extend(ClassMethods) - - base.send(:include, InstanceMethods) - base.class_eval do - unloadable - - alias_method_chain :after_create, :question - end - end - - module ClassMethods - end - - module InstanceMethods - def after_create_with_question(journal) - after_create_without_question(journal) - - if journal.is_a?(IssueJournal) - if journal.question - journal.question.save - QuestionMailer.deliver_asked_question(journal) - end - - # Close any open questions - if journal.journaled.present? && journal.journaled.pending_question?(journal.user) - journal.journaled.close_pending_questions(journal.user, journal) - end - end - - - end - - end - end - end -end diff --git a/lib/question_plugin/patches/journal_patch.rb b/lib/question_plugin/patches/journal_patch.rb new file mode 100644 index 0000000..774201a --- /dev/null +++ b/lib/question_plugin/patches/journal_patch.rb @@ -0,0 +1,45 @@ +module QuestionPlugin + module Patches + module JournalPatch + def self.included(base) + + base.extend(ClassMethods) + + base.send(:include, InstanceMethods) + base.class_eval do + unloadable + has_one :question, :dependent => :destroy + + + class << self + alias_method_chain :preload_journals_details_custom_fields, :question + end + + # + # used for redmine >= 2.3.2 + # + alias_method_chain :send_notification, :question unless ActiveSupport::Dependencies::search_for_file('journal_observer') + end + end + + module ClassMethods + def preload_journals_details_custom_fields_with_question(journals) + + # preload questions for all journal entries for faster display + ActiveRecord::Associations::Preloader.new.preload(journals, :question) + + preload_journals_details_custom_fields_without_question(journals) + end + end + + module InstanceMethods + def send_notification_with_question + send_notification_without_question + if question + QuestionMailer.asked_question(self).deliver + end + end + end + end + end +end diff --git a/lib/question_plugin/patches/queries_helper_patch.rb b/lib/question_plugin/patches/queries_helper_patch.rb new file mode 100644 index 0000000..4dd776e --- /dev/null +++ b/lib/question_plugin/patches/queries_helper_patch.rb @@ -0,0 +1,58 @@ +module QuestionPlugin + module Patches + module QueriesHelperPatch + def self.included(base) # :nodoc: + base.extend(ClassMethods) + + base.send(:include, InstanceMethods) + + base.class_eval do + unloadable + alias_method_chain :column_content, :question + end + end + + module ClassMethods + end + + module InstanceMethods + + + # removed format_questions for compatibility with redmine >= 2.3 + # see http://www.redmine.org/boards/3/topics/37345, + # http://www.redmine.org/issues/13753 + def column_content_with_question(column, issue) + if column.name == :formatted_questions + return '' if issue.open_questions.empty? + html = '
    ' + issue.open_questions.each do |question| + html << "
  1. " + html << "
    " + html << " " + html << link_to(h(truncate(question.journal.notes, :length => Question::TruncateTo)), + :controller => 'issues', + :action => 'show', + :id => question.issue, + :anchor => "question-#{question.id}") + html << " " + html << " " + html << link_to_issue(question.issue) + html << ": #{h(question.journal.notes)}

    " + html << "#{l(:question_text_asked_by)}: #{question.author.to_s}
    " + html << "#{l(:question_text_assigned_to)}: #{question.assigned_to.to_s}
    " + html << "#{l(:question_text_created_on)}: #{format_date(question.journal.created_on)}" + html << "
    " + html << "
    " + html << "
  2. " + end + html << '
' + return html + else + column_content_without_question(column, issue) + end + end + + end + end + end +end diff --git a/lib/question_plugin/patches/query_patch.rb b/lib/question_plugin/patches/query_patch.rb new file mode 100644 index 0000000..287f592 --- /dev/null +++ b/lib/question_plugin/patches/query_patch.rb @@ -0,0 +1,94 @@ +module QuestionPlugin + module Patches + module QueryPatch + def self.included(base) # :nodoc: + base.extend(ClassMethods) + + base.send(:include, InstanceMethods) + + # Same as typing in the class + base.class_eval do + unloadable # Send unloadable so it will not be unloaded in development + + base.add_available_column(QueryColumn.new(:formatted_questions)) + + alias_method_chain :available_filters, :question + alias_method_chain :sql_for_field, :question + + end + end + + module ClassMethods + unless Query.respond_to?(:available_columns=) + # Setter for +available_columns+ that isn't provided by the core. + def available_columns=(v) + self.available_columns = (v) + end + end + + unless Query.respond_to?(:add_available_column) + # Method to add a column to the +available_columns+ that isn't provided by the core. + def add_available_column(column) + self.available_columns << (column) + end + end + end + + module InstanceMethods + # Wrapper around the +available_filters+ to add a new Question filter + def available_filters_with_question + return @available_filters if @available_filters + available_filters_without_question + + if @available_filters["assigned_to_id"] + user_values = @available_filters["assigned_to_id"][:values] + + @available_filters["question_assigned_to_id"] = { :name => l("question_text_assigned_to"), :type => :list_optional, :order => 16, :values => user_values } + @available_filters["question_asked_by_id"] = { :name => l("question_text_asked_by"), :type => :list_optional, :order => 16, :values => user_values } + end + + @available_filters + end + + # Wrapper for +sql_for_field+ so Questions can use a different table than Issues + def sql_for_field_with_question(field, operator, v, db_table, db_field, is_custom_filter=false) + if field == "question_assigned_to_id" || field == "question_asked_by_id" + v = values_for(field).clone + + db_table = Question.table_name + if field == "question_assigned_to_id" + db_field = 'assigned_to_id' + else + db_field = 'author_id' + end + + # "me" value subsitution + v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me") + where_sql = [] + case operator + when "=" + where_sql << "#{db_table}.#{db_field} in (?)" + when "!" + where_sql << "#{db_table}.#{db_field} not in (?)" + when "!*" + where_sql << "#{db_table}.#{db_field} is null" +# when "*" + end + + where_sql << "#{db_table}.opened = true" + + subselect_sql = Question.select("#{Journal.table_name}.journalized_id") + .joins(:journal) + .where(where_sql.join(' and '),[v.join(",")]).to_sql; + + sql = "#{Issue.table_name}.id in (#{subselect_sql})" + + return sql + else + return sql_for_field_without_question(field, operator, v, db_table, db_field, is_custom_filter) + end + end + end + end + end +end diff --git a/lib/question_queries_helper_patch.rb b/lib/question_queries_helper_patch.rb deleted file mode 100644 index 35a3bac..0000000 --- a/lib/question_queries_helper_patch.rb +++ /dev/null @@ -1,54 +0,0 @@ -module QuestionQueriesHelperPatch - def self.included(base) # :nodoc: - base.extend(ClassMethods) - - base.send(:include, InstanceMethods) - - base.class_eval do - alias_method :default_column_content, :column_content - alias_method :column_content, :question_column_content - end - end - - module ClassMethods - end - - module InstanceMethods - def question_column_content(column, issue) - if column.name == :formatted_questions - return format_questions(issue.open_questions) - else - default_column_content(column, issue) - end - end - - def format_questions(questions) - return '' if questions.empty? - html = '
    ' - questions.each do |question| - html << "
  1. " - html << "
    " - html << " " - html << link_to(h(truncate(question.journal.notes, Question::TruncateTo)), - :controller => 'issues', - :action => 'show', - :id => question.issue, - :anchor => "question-#{question.id}") - html << " " - html << " " - html << link_to_issue(question.issue) - html << ": #{h(question.journal.notes)}

    " - html << "#{l(:question_text_asked_by)}: #{question.author.to_s}
    " - html << "#{l(:question_text_assigned_to)}: #{question.assigned_to.to_s}
    " - html << "#{l(:question_text_created_on)}: #{format_date(question.journal.created_on)}" - html << "
    " - html << "
    " - html << "
  2. " - end - html << '
' - return html - end - end -end - - diff --git a/lib/question_query_patch.rb b/lib/question_query_patch.rb deleted file mode 100644 index 20ff601..0000000 --- a/lib/question_query_patch.rb +++ /dev/null @@ -1,93 +0,0 @@ -module QuestionQueryPatch - def self.included(base) # :nodoc: - base.extend(ClassMethods) - - base.send(:include, InstanceMethods) - - # Same as typing in the class - base.class_eval do - unloadable # Send unloadable so it will not be unloaded in development - base.add_available_column(QueryColumn.new(:formatted_questions)) - - - alias_method :available_filters_before_question, :available_filters - alias_method :available_filters, :question_available_filters - - alias_method :sql_for_field_before_question, :sql_for_field - alias_method :sql_for_field, :question_sql_for_field - end - - end - - module ClassMethods - unless Query.respond_to?(:available_columns=) - # Setter for +available_columns+ that isn't provided by the core. - def available_columns=(v) - self.available_columns = (v) - end - end - - unless Query.respond_to?(:add_available_column) - # Method to add a column to the +available_columns+ that isn't provided by the core. - def add_available_column(column) - self.available_columns << (column) - end - end - end - - module InstanceMethods - - # Wrapper around the +available_filters+ to add a new Question filter - def question_available_filters - @available_filters = available_filters_before_question - - user_values = [] - user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? - if project - user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] } - else - user_values += User.current.projects.collect(&:users).flatten.uniq.sort.collect{|s| [s.name, s.id.to_s] } - end - - question_filters = { - "question_assigned_to_id" => { :type => :list, :order => 14, :values => user_values }, - "question_asked_by_id" => { :type => :list, :order => 14, :values => user_values } - } - - return @available_filters.merge(question_filters) - end - - # Wrapper for +sql_for_field+ so Questions can use a different table than Issues - def question_sql_for_field(field, operator, v, db_table, db_field, is_custom_filter=false) - if field == "question_assigned_to_id" || field == "question_asked_by_id" - v = values_for(field).clone - - db_table = Question.table_name - if field == "question_assigned_to_id" - db_field = 'assigned_to_id' - else - db_field = 'author_id' - end - - - # "me" value subsitution - v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me") - - case operator - when "=" - sql = "#{db_table}.#{db_field} IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ") AND #{db_table}.opened = true" - when "!" - sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")) AND #{db_table}.opened = true" - end - - return sql - - else - return sql_for_field_before_question(field, operator, v, db_table, db_field, is_custom_filter) - end - - end - - end -end - diff --git a/question_plugin.gemspec b/question_plugin.gemspec index 7c034b5..bde82ae 100644 --- a/question_plugin.gemspec +++ b/question_plugin.gemspec @@ -22,7 +22,7 @@ Gem::Specification.new do |s| "README.rdoc", "Rakefile", "VERSION", - "app/controllers/questions_controller.rb", + "app/controllers/issuequestions_controller.rb", "app/models/journal_questions_observer.rb", "app/models/question.rb", "app/models/question_mailer.rb", diff --git a/rails/init.rb b/rails/init.rb deleted file mode 100644 index 48087f0..0000000 --- a/rails/init.rb +++ /dev/null @@ -1 +0,0 @@ -require File.dirname(__FILE__) + "/../init" diff --git a/test/integration/answering_question_test.rb b/test/integration/answering_question_test.rb index 38feded..f8b81ef 100644 --- a/test/integration/answering_question_test.rb +++ b/test/integration/answering_question_test.rb @@ -10,9 +10,7 @@ class AnsweringQuestionTest < ActionController::IntegrationTest @question = Question.new(:assigned_to => @author, :author => @author, :issue => @issue) @issue.journal_notes = "Question" - @issue.extra_journal_attributes = { - :question => @question - } + @issue.question = @question assert @issue.save ActionMailer::Base.deliveries.clear end diff --git a/test/integration/asking_question_test.rb b/test/integration/asking_question_test.rb index 8f7ab2e..d56b42e 100644 --- a/test/integration/asking_question_test.rb +++ b/test/integration/asking_question_test.rb @@ -10,9 +10,7 @@ class AskingQuestionTest < ActionController::IntegrationTest @issue = Issue.generate_for_project!(@project) @issue.journal_notes = "Question" - @issue.extra_journal_attributes = { - :question => Question.new(:assigned_to => @author, :author => @author, :issue => @issue) - } + @issue.question = Question.new(:assigned_to => @author, :author => @author, :issue => @issue) assert_difference("Question.count") do assert @issue.save diff --git a/test/integration/mail_handler_test.rb b/test/integration/mail_handler_test.rb index 18cf653..9a03940 100644 --- a/test/integration/mail_handler_test.rb +++ b/test/integration/mail_handler_test.rb @@ -12,7 +12,7 @@ class MailHandlerTest < ActionController::IntegrationTest @issue = Issue.generate_for_project!(@project) @question = Question.new(:issue => @issue, :author => @asker, :assigned_to => @responder) @issue.journal_notes = "Test" - @issue.extra_journal_attributes = { :question => @question } + @issue.question = @question assert @issue.save @question_journal = @issue.journals.last diff --git a/test/integration/my_page_blocks_test.rb b/test/integration/my_page_blocks_test.rb index 3631c5a..47e2877 100644 --- a/test/integration/my_page_blocks_test.rb +++ b/test/integration/my_page_blocks_test.rb @@ -9,7 +9,7 @@ def setup @question = Question.new(:issue => @issue, :author => @me, :assigned_to => @me) @issue.journal_notes = "Test" @issue.journal_user = @me - @issue.extra_journal_attributes = { :question => @question } + @issue.question = @question assert @issue.save @question_journal = @issue.journals.last end diff --git a/test/integration/question_plugin/hooks/question_issue_hooks_test.rb b/test/integration/question_plugin/hooks/question_issue_hooks_test.rb index b070571..5980624 100644 --- a/test/integration/question_plugin/hooks/question_issue_hooks_test.rb +++ b/test/integration/question_plugin/hooks/question_issue_hooks_test.rb @@ -103,7 +103,7 @@ def call_hook(context) @issue = Issue.generate_for_project!(@project) @question = Question.new(:author => @user1, :assigned_to => @user1, :opened => true, :issue => @issue) @issue.journal_notes = "A note" - @issue.extra_journal_attributes = { :question => @question } + @issue.question = @question assert @issue.save @journal = @issue.journals.last User.add_to_project(@user1, @project, Role.generate!(:permissions => [:view_issues, :add_issues, :edit_issues])) diff --git a/test/test_helper.rb b/test/test_helper.rb index e4c3ddb..087988f 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,9 +1,5 @@ # Load the normal Rails helper -require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper') - -# Ensure that we are using the temporary fixture path -Engines::Testing.set_fixture_path - +require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper') require "webrat" diff --git a/test/unit/lib/question_plugin/patches/queries_helper_patch_test.rb b/test/unit/lib/question_plugin/patches/queries_helper_patch_test.rb index f8bb367..665e252 100644 --- a/test/unit/lib/question_plugin/patches/queries_helper_patch_test.rb +++ b/test/unit/lib/question_plugin/patches/queries_helper_patch_test.rb @@ -34,7 +34,7 @@ def for_assert_select(response_text) @assignee = User.generate! @question = Question.new(:issue => @issue, :author => @author, :assigned_to => @assignee) @issue.journal_notes = @content - @issue.extra_journal_attributes = { :question => @question } + @issue.question = @question assert @issue.save @questions = [@question] end @@ -82,12 +82,12 @@ def for_assert_select(response_text) @question_two = Question.new(:issue => @issue, :author => @author, :assigned_to => @assignee) @issue.journal_notes = @content_one - @issue.extra_journal_attributes = { :question => @question } + @issue.question = @question assert @issue.save && @issue.reload @journal_one = @issue.journals.last @issue.journal_notes = @content_two - @issue.extra_journal_attributes = { :question => @question_two } + @issue.question = @question_two assert @issue.save && @issue.reload @journal_two = @issue.journals.last @@ -116,7 +116,7 @@ def for_assert_select(response_text) @assignee = User.generate! @question = Question.new(:issue => @issue, :author => @author, :assigned_to => @assignee) @issue.journal_notes = "A question" - @issue.extra_journal_attributes = { :question => @question } + @issue.question = @question assert @issue.save end diff --git a/test/unit/question_mailer_test.rb b/test/unit/question_mailer_test.rb index 03252b3..f0f7a12 100644 --- a/test/unit/question_mailer_test.rb +++ b/test/unit/question_mailer_test.rb @@ -13,7 +13,7 @@ class QuestionMailerTest < ActiveSupport::TestCase @issue = Issue.generate_for_project!(@project, :subject => "Add new stuff") @question = Question.new(:assigned_to => @user, :author => @author, :issue => @issue) @issue.journal_notes = "This is the question for the user" - @issue.extra_journal_attributes = { :question => @question } + @issue.question = @question assert @issue.save @journal = @issue.journals.last @@ -81,7 +81,7 @@ class QuestionMailerTest < ActiveSupport::TestCase @question = Question.new(:assigned_to => nil, :author => @author, :issue => @issue) @issue.journal_notes = "This is the question for the user" - @issue.extra_journal_attributes = { :question => @question } + @issue.question = @question assert @issue.save @journal = @issue.journals.last @@ -101,7 +101,7 @@ class QuestionMailerTest < ActiveSupport::TestCase @question = Question.new(:assigned_to => @user, :author => @author, :issue => @issue) @issue.journal_notes = "This is the question for the user" - @issue.extra_journal_attributes = { :question => @question } + @issue.question = @question assert @issue.save @journal_with_question = @issue.journals.last