issues_controller.rb 23.6 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 20
  menu_item :new_issue, :only => :new
  
21
  before_filter :find_issue, :only => [:show, :edit, :reply]
22
  before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
23 24 25
  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]
26
  accept_key_auth :index, :changes
27

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

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

125 126 127
  # Add a new issue
  # The new issue will be created from an existing one if copy_from parameter is given
  def new
128 129
    @issue = Issue.new
    @issue.copy_from(params[:copy_from]) if params[:copy_from]
130
    @issue.project = @project
131 132
    # Tracker must be set before custom field values
    @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
133
    if @issue.tracker.nil?
134
      render_error 'No tracker is associated to this project. Please check the Project settings.'
135 136
      return
    end
137 138 139 140
    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
141
    @issue.author = User.current
142 143 144
    
    default_status = IssueStatus.default
    unless default_status
145
      render_error 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
146 147 148
      return
    end    
    @issue.status = default_status
149
    @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)).uniq
150 151 152 153 154 155 156 157 158 159
    
    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)
160
        call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
161 162
        redirect_to(params[:continue] ? { :action => 'new', :tracker_id => @issue.tracker } :
                                        { :action => 'show', :id => @issue })
163 164 165
        return
      end		
    end	
166
    @priorities = Enumeration.priorities
167 168 169
    render :layout => !request.xhr?
  end
  
170 171 172 173
  # 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)
  
174
  def edit
175
    @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
176
    @priorities = Enumeration.priorities
177
    @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
178
    @time_entry = TimeEntry.new
179 180 181 182 183 184 185 186 187 188 189
    
    @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

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

198
      if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save
199
        # Log spend time
200
        if User.current.allowed_to?(:log_time, @project)
201 202 203 204
          @time_entry.save
        end
        if !journal.new_record?
          # Only send notification if something was actually changed
205 206
          flash[:notice] = l(:notice_successful_update)
        end
207
        call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
208
        redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
209
      end
210 211 212 213
    end
  rescue ActiveRecord::StaleObjectError
    # Optimistic locking exception
    flash.now[:error] = l(:notice_locking_conflict)
jplang's avatar
jplang committed
214 215
  end

216 217 218 219 220 221 222 223 224
  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
225 226
    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"
227
    render(:update) { |page|
228
      page.<< "$('notes').value = \"#{content}\";"
229 230 231 232 233 234 235
      page.show 'update'
      page << "Form.Element.focus('notes');"
      page << "Element.scrollTo('update');"
      page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
    }
  end
  
236 237 238 239 240
  # 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])
      priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
241 242 243
      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])
244
      custom_field_values = params[:custom_field_values] ? params[:custom_field_values].reject {|k,v| v.blank?} : nil
245 246 247 248 249 250
      
      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'
251 252
        issue.category = category if category || params[:category_id] == 'none'
        issue.fixed_version = fixed_version if fixed_version || params[:fixed_version_id] == 'none'
253 254 255
        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?
256
        issue.custom_field_values = custom_field_values if custom_field_values && !custom_field_values.empty?
257
        call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
258
        # Don't save any change to the issue if the user is not authorized to apply the requested status
259
        unless (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
260 261 262 263 264 265 266
          # 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
267 268 269
        flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
                                                         :total => @issues.size,
                                                         :ids => '#' + unsaved_issue_ids.join(', #'))
270
      end
271
      redirect_to(params[:back_to] || {:controller => 'issues', :action => 'index', :project_id => @project})
272 273 274 275
      return
    end
    # Find potential statuses the user could be allowed to switch issues to
    @available_statuses = Workflow.find(:all, :include => :new_status,
276
                                              :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq.sort
277
    @custom_fields = @project.issue_custom_fields.select {|f| f.field_format == 'list'}
278 279 280 281 282 283 284
  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
285
      @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current))
286 287 288 289 290 291 292 293 294 295
    else
      User.current.memberships.each {|m| @allowed_projects << m.project if m.role.allowed_to?(:move_issues)}
    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 = []
      @issues.each do |issue|
296
        issue.init_journal(User.current)
297
        unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker, params[:copy_options])
298 299 300 301
      end
      if unsaved_issue_ids.empty?
        flash[:notice] = l(:notice_successful_update) unless @issues.empty?
      else
302 303 304
        flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
                                                         :total => @issues.size,
                                                         :ids => '#' + unsaved_issue_ids.join(', #'))
305 306 307 308 309 310 311
      end
      redirect_to :controller => 'issues', :action => 'index', :project_id => @project
      return
    end
    render :layout => false if request.xhr?
  end
  
jplang's avatar
jplang committed
312
  def destroy
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
    @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
333
    @issues.each(&:destroy)
334
    redirect_to :action => 'index', :project_id => @project
335
  end
336
  
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
  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]
                           )
354 355 356 357
      # Versions
      events += Version.find(:all, :include => :project,
                                   :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
                                   
358 359 360
      @gantt.events = events
    end
    
361 362
    basename = (@project ? "#{@project.identifier}-" : '') + 'gantt'
    
363 364
    respond_to do |format|
      format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
365 366
      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") }
367 368 369
    end
  end
  
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
  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
  
397
  def context_menu
398 399 400 401 402 403 404 405 406
    @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
407
            :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
408
            :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
409 410 411 412
            :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))
            }
413 414 415 416 417
    if @project
      @assignables = @project.assignable_users
      @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
    end
    
418
    @priorities = Enumeration.priorities.reverse
419
    @statuses = IssueStatus.find(:all, :order => 'position')
420 421
    @back = request.env['HTTP_REFERER']
    
422 423
    render :layout => false
  end
424

425 426 427 428 429
  def update_form
    @issue = Issue.new(params[:issue])
    render :action => :new, :layout => false
  end
  
jplang's avatar
jplang committed
430
  def preview
431
    @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
432
    @attachements = @issue.attachments if @issue
433
    @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil)
jplang's avatar
jplang committed
434 435 436
    render :partial => 'common/preview'
  end
  
437
private
438
  def find_issue
439 440 441 442
    @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
    @project = @issue.project
  rescue ActiveRecord::RecordNotFound
    render_404
443 444
  end
  
445 446 447 448 449 450 451 452 453 454 455 456 457 458 459
  # 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
  
460 461 462 463 464 465
  def find_project
    @project = Project.find(params[:project_id])
  rescue ActiveRecord::RecordNotFound
    render_404
  end
  
466
  def find_optional_project
467 468 469
    @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
470 471 472 473
  rescue ActiveRecord::RecordNotFound
    render_404
  end
  
474 475
  # Retrieve query from session or build a new query
  def retrieve_query
jplang's avatar
jplang committed
476
    if !params[:query_id].blank?
477 478 479 480
      cond = "project_id IS NULL"
      cond << " OR project_id = #{@project.id}" if @project
      @query = Query.find(params[:query_id], :conditions => cond)
      @query.project = @project
481
      session[:query] = {:id => @query.id, :project_id => @query.project_id}
482
      sort_clear
483
    else
484
      if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
485
        # Give it a name, required to be valid
486
        @query = Query.new(:name => "_")
487 488 489 490 491 492 493 494 495
        @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
496
        end
jplang's avatar
jplang committed
497 498
        @query.group_by = params[:group_by]
        session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by}
499
      else
500
        @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
jplang's avatar
jplang committed
501
        @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by])
502
        @query.project = @project
503 504 505
      end
    end
  end
jplang's avatar
jplang committed
506
end