issues_controller.rb 24.2 KB
Newer Older
1 2
# Redmine - project management software
# Copyright (C) 2006-2008  Jean-Philippe Lang
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

class IssuesController < ApplicationController
19
  menu_item :new_issue, :only => :new
jplang's avatar
jplang committed
20
  default_search_scope :issues
21
  
22
  before_filter :find_issue, :only => [:show, :edit, :reply]
23
  before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
24 25 26
  before_filter :find_project, :only => [:new, :update_form, :preview]
  before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu]
  before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar]
27
  accept_key_auth :index, :show, :changes
28

jplang's avatar
jplang committed
29
  helper :journals
30 31
  helper :projects
  include ProjectsHelper   
32 33
  helper :custom_fields
  include CustomFieldsHelper
34 35
  helper :issue_relations
  include IssueRelationsHelper
36 37
  helper :watchers
  include WatchersHelper
38
  helper :attachments
39 40 41 42
  include AttachmentsHelper
  helper :queries
  helper :sort
  include SortHelper
43
  include IssuesHelper
44
  helper :timelog
jplang's avatar
jplang committed
45
  include Redmine::Export::PDF
jplang's avatar
jplang committed
46

47 48 49 50
  verify :method => :post,
         :only => :destroy,
         :render => { :nothing => true, :status => :method_not_allowed }
           
51 52
  def index
    retrieve_query
53
    sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
54
    sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.available_columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
55
    
56
    if @query.valid?
57 58 59
      limit = per_page_option
      respond_to do |format|
        format.html { }
60
        format.atom { limit = Setting.feeds_limit.to_i }
61 62 63
        format.csv  { limit = Setting.issues_export_limit.to_i }
        format.pdf  { limit = Setting.issues_export_limit.to_i }
      end
64 65
      @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
      @issue_pages = Paginator.new self, @issue_count, limit, params['page']
jplang's avatar
jplang committed
66
      @issues = Issue.find :all, :order => [@query.group_by_sort_order, sort_clause].compact.join(','),
67
                           :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ],
68
                           :conditions => @query.statement,
69 70 71
                           :limit  =>  limit,
                           :offset =>  @issue_pages.current.offset
      respond_to do |format|
jplang's avatar
jplang committed
72 73 74 75 76 77 78 79 80 81 82 83
        format.html { 
          if @query.grouped?
            # Retrieve the issue count by group
            @issue_count_by_group = begin
              Issue.count(:group => @query.group_by, :include => [:status, :project], :conditions => @query.statement)
            # Rails will raise an (unexpected) error if there's only a nil group value
            rescue ActiveRecord::RecordNotFound
              {nil => @issue_count}
            end
          end
          render :template => 'issues/index.rhtml', :layout => !request.xhr?
        }
84
        format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
85
        format.csv  { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') }
jplang's avatar
jplang committed
86
        format.pdf  { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
87 88 89 90
      end
    else
      # Send html if the query is not valid
      render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
91
    end
92 93
  rescue ActiveRecord::RecordNotFound
    render_404
94 95 96 97
  end
  
  def changes
    retrieve_query
98
    sort_init 'id', 'desc'
jplang's avatar
jplang committed
99
    sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.available_columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
100
    
101
    if @query.valid?
102
      @journals = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ],
103 104 105
                                     :conditions => @query.statement,
                                     :limit => 25,
                                     :order => "#{Journal.table_name}.created_on DESC"
106
    end
107 108
    @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
    render :layout => false, :content_type => 'application/atom+xml'
109 110
  rescue ActiveRecord::RecordNotFound
    render_404
111 112
  end
  
jplang's avatar
jplang committed
113
  def show
114
    @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
115 116
    @journals.each_with_index {|j,i| j.indice = i+1}
    @journals.reverse! if User.current.wants_comments_in_reverse_order?
117 118
    @changesets = @issue.changesets
    @changesets.reverse! if User.current.wants_comments_in_reverse_order?
119 120
    @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
    @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
121
    @priorities = IssuePriority.all
122
    @time_entry = TimeEntry.new
123 124
    respond_to do |format|
      format.html { render :template => 'issues/show.rhtml' }
125
      format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
jplang's avatar
jplang committed
126
      format.pdf  { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
127
    end
jplang's avatar
jplang committed
128
  end
jplang's avatar
jplang committed
129

130 131 132
  # Add a new issue
  # The new issue will be created from an existing one if copy_from parameter is given
  def new
133 134
    @issue = Issue.new
    @issue.copy_from(params[:copy_from]) if params[:copy_from]
135
    @issue.project = @project
136 137
    # Tracker must be set before custom field values
    @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
138
    if @issue.tracker.nil?
139
      render_error l(:error_no_tracker_in_project)
140 141
      return
    end
142 143 144 145
    if params[:issue].is_a?(Hash)
      @issue.attributes = params[:issue]
      @issue.watcher_user_ids = params[:issue]['watcher_user_ids'] if User.current.allowed_to?(:add_issue_watchers, @project)
    end
146
    @issue.author = User.current
147 148 149
    
    default_status = IssueStatus.default
    unless default_status
150
      render_error l(:error_no_default_issue_status)
151 152 153
      return
    end    
    @issue.status = default_status
154
    @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.roles_for_project(@project), @issue.tracker)).uniq
155 156 157 158 159 160 161 162 163 164
    
    if request.get? || request.xhr?
      @issue.start_date ||= Date.today
    else
      requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
      # Check that the user is allowed to apply the requested status
      @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
      if @issue.save
        attach_files(@issue, params[:attachments])
        flash[:notice] = l(:notice_successful_create)
165
        call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
166 167
        redirect_to(params[:continue] ? { :action => 'new', :tracker_id => @issue.tracker } :
                                        { :action => 'show', :id => @issue })
168 169 170
        return
      end		
    end	
171
    @priorities = IssuePriority.all
172 173 174
    render :layout => !request.xhr?
  end
  
175 176 177 178
  # Attributes that can be updated on workflow transition (without :edit permission)
  # TODO: make it configurable (at least per role)
  UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
  
179
  def edit
180
    @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
181
    @priorities = IssuePriority.all
182
    @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
183
    @time_entry = TimeEntry.new
184 185 186 187 188 189 190 191 192 193 194
    
    @notes = params[:notes]
    journal = @issue.init_journal(User.current, @notes)
    # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
    if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
      attrs = params[:issue].dup
      attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
      attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
      @issue.attributes = attrs
    end

195
    if request.post?
196 197
      @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
      @time_entry.attributes = params[:time_entry]
198 199
      attachments = attach_files(@issue, params[:attachments])
      attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
200 201 202
      
      call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})

203
      if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save
204
        # Log spend time
205
        if User.current.allowed_to?(:log_time, @project)
206 207 208 209
          @time_entry.save
        end
        if !journal.new_record?
          # Only send notification if something was actually changed
210 211
          flash[:notice] = l(:notice_successful_update)
        end
212
        call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
213
        redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
214
      end
215 216 217 218
    end
  rescue ActiveRecord::StaleObjectError
    # Optimistic locking exception
    flash.now[:error] = l(:notice_locking_conflict)
219 220
    # Remove the previously added attachments if issue was not updated
    attachments.each(&:destroy)
jplang's avatar
jplang committed
221 222
  end

223 224 225 226 227 228 229 230 231
  def reply
    journal = Journal.find(params[:journal_id]) if params[:journal_id]
    if journal
      user = journal.user
      text = journal.notes
    else
      user = @issue.author
      text = @issue.description
    end
232 233
    content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
    content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
234
    render(:update) { |page|
235
      page.<< "$('notes').value = \"#{content}\";"
236 237 238 239 240 241 242
      page.show 'update'
      page << "Form.Element.focus('notes');"
      page << "Element.scrollTo('update');"
      page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
    }
  end
  
243 244 245 246
  # Bulk edit a set of issues
  def bulk_edit
    if request.post?
      status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
247
      priority = params[:priority_id].blank? ? nil : IssuePriority.find_by_id(params[:priority_id])
248 249 250
      assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
      category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
      fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.versions.find_by_id(params[:fixed_version_id])
251
      custom_field_values = params[:custom_field_values] ? params[:custom_field_values].reject {|k,v| v.blank?} : nil
252 253 254 255 256 257
      
      unsaved_issue_ids = []      
      @issues.each do |issue|
        journal = issue.init_journal(User.current, params[:notes])
        issue.priority = priority if priority
        issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
258 259
        issue.category = category if category || params[:category_id] == 'none'
        issue.fixed_version = fixed_version if fixed_version || params[:fixed_version_id] == 'none'
260 261 262
        issue.start_date = params[:start_date] unless params[:start_date].blank?
        issue.due_date = params[:due_date] unless params[:due_date].blank?
        issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
263
        issue.custom_field_values = custom_field_values if custom_field_values && !custom_field_values.empty?
264
        call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
265
        # Don't save any change to the issue if the user is not authorized to apply the requested status
266
        unless (status.nil? || (issue.new_statuses_allowed_to(User.current).include?(status) && issue.status = status)) && issue.save
267 268 269 270 271 272 273
          # Keep unsaved issue ids to display them in flash error
          unsaved_issue_ids << issue.id
        end
      end
      if unsaved_issue_ids.empty?
        flash[:notice] = l(:notice_successful_update) unless @issues.empty?
      else
274 275 276
        flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
                                                         :total => @issues.size,
                                                         :ids => '#' + unsaved_issue_ids.join(', #'))
277
      end
278
      redirect_to(params[:back_to] || {:controller => 'issues', :action => 'index', :project_id => @project})
279 280 281 282
      return
    end
    # Find potential statuses the user could be allowed to switch issues to
    @available_statuses = Workflow.find(:all, :include => :new_status,
283
                                              :conditions => {:role_id => User.current.roles_for_project(@project).collect(&:id)}).collect(&:new_status).compact.uniq.sort
284
    @custom_fields = @project.issue_custom_fields.select {|f| f.field_format == 'list'}
285 286 287 288 289 290 291
  end

  def move
    @allowed_projects = []
    # find projects to which the user is allowed to move the issue
    if User.current.admin?
      # admin is allowed to move issues to any active (visible) project
292
      @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current))
293
    else
294
      User.current.memberships.each {|m| @allowed_projects << m.project if m.roles.detect {|r| r.allowed_to?(:move_issues)}}
295 296 297 298 299 300 301
    end
    @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
    @target_project ||= @project    
    @trackers = @target_project.trackers
    if request.post?
      new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
      unsaved_issue_ids = []
302
      moved_issues = []
303
      @issues.each do |issue|
304
        issue.init_journal(User.current)
305 306 307 308 309
        if r = issue.move_to(@target_project, new_tracker, params[:copy_options])
          moved_issues << r
        else
          unsaved_issue_ids << issue.id
        end
310 311 312 313
      end
      if unsaved_issue_ids.empty?
        flash[:notice] = l(:notice_successful_update) unless @issues.empty?
      else
314 315 316
        flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
                                                         :total => @issues.size,
                                                         :ids => '#' + unsaved_issue_ids.join(', #'))
317
      end
318 319 320 321 322 323 324 325 326
      if params[:follow]
        if @issues.size == 1 && moved_issues.size == 1
          redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
        else
          redirect_to :controller => 'issues', :action => 'index', :project_id => (@target_project || @project)
        end
      else
        redirect_to :controller => 'issues', :action => 'index', :project_id => @project
      end
327 328 329 330 331
      return
    end
    render :layout => false if request.xhr?
  end
  
jplang's avatar
jplang committed
332
  def destroy
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
    @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
    if @hours > 0
      case params[:todo]
      when 'destroy'
        # nothing to do
      when 'nullify'
        TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
      when 'reassign'
        reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
        if reassign_to.nil?
          flash.now[:error] = l(:error_issue_not_found_in_project)
          return
        else
          TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
        end
      else
        # display the destroy form
        return
      end
    end
353
    @issues.each(&:destroy)
354
    redirect_to :action => 'index', :project_id => @project
355
  end
356
  
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
  def gantt
    @gantt = Redmine::Helpers::Gantt.new(params)
    retrieve_query
    if @query.valid?
      events = []
      # Issues that have start and due dates
      events += Issue.find(:all, 
                           :order => "start_date, due_date",
                           :include => [:tracker, :status, :assigned_to, :priority, :project], 
                           :conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
                           )
      # Issues that don't have a due date but that are assigned to a version with a date
      events += Issue.find(:all, 
                           :order => "start_date, effective_date",
                           :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version], 
                           :conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
                           )
374 375 376 377
      # Versions
      events += Version.find(:all, :include => :project,
                                   :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
                                   
378 379 380
      @gantt.events = events
    end
    
381 382
    basename = (@project ? "#{@project.identifier}-" : '') + 'gantt'
    
383 384
    respond_to do |format|
      format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
385 386
      format.png  { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image')
      format.pdf  { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{basename}.pdf") }
387 388 389
    end
  end
  
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
  def calendar
    if params[:year] and params[:year].to_i > 1900
      @year = params[:year].to_i
      if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
        @month = params[:month].to_i
      end    
    end
    @year ||= Date.today.year
    @month ||= Date.today.month
    
    @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
    retrieve_query
    if @query.valid?
      events = []
      events += Issue.find(:all, 
                           :include => [:tracker, :status, :assigned_to, :priority, :project], 
                           :conditions => ["(#{@query.statement}) AND ((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
                           )
      events += Version.find(:all, :include => :project,
                                   :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
                                     
      @calendar.events = events
    end
    
    render :layout => false if request.xhr?
  end
  
417
  def context_menu
418 419 420 421 422 423 424 425 426
    @issues = Issue.find_all_by_id(params[:ids], :include => :project)
    if (@issues.size == 1)
      @issue = @issues.first
      @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
    end
    projects = @issues.collect(&:project).compact.uniq
    @project = projects.first if projects.size == 1

    @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
jplang's avatar
jplang committed
427
            :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
428
            :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
429 430 431 432
            :move => (@project && User.current.allowed_to?(:move_issues, @project)),
            :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
            :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
            }
433 434 435 436 437
    if @project
      @assignables = @project.assignable_users
      @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
    end
    
438
    @priorities = IssuePriority.all.reverse
439
    @statuses = IssueStatus.find(:all, :order => 'position')
440
    @back = params[:back_url] || request.env['HTTP_REFERER']
441
    
442 443
    render :layout => false
  end
444

445 446 447 448 449
  def update_form
    @issue = Issue.new(params[:issue])
    render :action => :new, :layout => false
  end
  
jplang's avatar
jplang committed
450
  def preview
451
    @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
452
    @attachements = @issue.attachments if @issue
453
    @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil)
jplang's avatar
jplang committed
454 455 456
    render :partial => 'common/preview'
  end
  
457
private
458
  def find_issue
459 460 461 462
    @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
    @project = @issue.project
  rescue ActiveRecord::RecordNotFound
    render_404
463 464
  end
  
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
  # Filter for bulk operations
  def find_issues
    @issues = Issue.find_all_by_id(params[:id] || params[:ids])
    raise ActiveRecord::RecordNotFound if @issues.empty?
    projects = @issues.collect(&:project).compact.uniq
    if projects.size == 1
      @project = projects.first
    else
      # TODO: let users bulk edit/move/destroy issues from different projects
      render_error 'Can not bulk edit/move/destroy issues from different projects' and return false
    end
  rescue ActiveRecord::RecordNotFound
    render_404
  end
  
480 481 482 483 484 485
  def find_project
    @project = Project.find(params[:project_id])
  rescue ActiveRecord::RecordNotFound
    render_404
  end
  
486
  def find_optional_project
487 488 489
    @project = Project.find(params[:project_id]) unless params[:project_id].blank?
    allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
    allowed ? true : deny_access
490 491 492 493
  rescue ActiveRecord::RecordNotFound
    render_404
  end
  
494 495
  # Retrieve query from session or build a new query
  def retrieve_query
jplang's avatar
jplang committed
496
    if !params[:query_id].blank?
497 498 499 500
      cond = "project_id IS NULL"
      cond << " OR project_id = #{@project.id}" if @project
      @query = Query.find(params[:query_id], :conditions => cond)
      @query.project = @project
501
      session[:query] = {:id => @query.id, :project_id => @query.project_id}
502
      sort_clear
503
    else
504
      if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
505
        # Give it a name, required to be valid
506
        @query = Query.new(:name => "_")
507 508 509 510 511 512 513 514 515
        @query.project = @project
        if params[:fields] and params[:fields].is_a? Array
          params[:fields].each do |field|
            @query.add_filter(field, params[:operators][field], params[:values][field])
          end
        else
          @query.available_filters.keys.each do |field|
            @query.add_short_filter(field, params[field]) if params[field]
          end
516
        end
jplang's avatar
jplang committed
517 518
        @query.group_by = params[:group_by]
        session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by}
519
      else
520
        @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
jplang's avatar
jplang committed
521
        @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by])
522
        @query.project = @project
523 524 525
      end
    end
  end
jplang's avatar
jplang committed
526
end