]> source.dussan.org Git - redmine.git/commitdiff
CSV export of issues report (#37362).
authorGo MAEDA <maeda@farend.jp>
Thu, 21 Jul 2022 04:13:37 +0000 (04:13 +0000)
committerGo MAEDA <maeda@farend.jp>
Thu, 21 Jul 2022 04:13:37 +0000 (04:13 +0000)
Patch by Mizuki ISHIKAWA.

git-svn-id: https://svn.redmine.org/redmine/trunk@21732 e93f8b46-1217-0410-a6f0-8f06a7374b81

app/controllers/reports_controller.rb
app/helpers/reports_helper.rb
app/views/reports/_details.html.erb
test/functional/reports_controller_test.rb

index d818e0b08581ef2aa09314fba631b703c480d213..1cac99655969227f66d4ddb36f3878b53c8073d2 100644 (file)
@@ -21,6 +21,7 @@ class ReportsController < ApplicationController
   menu_item :issues
   before_action :find_project, :authorize, :find_issue_statuses
 
+  include ReportsHelper
   def issue_report
     with_subprojects = Setting.display_subprojects_issues?
     @trackers = @project.rolled_up_trackers(with_subprojects).visible
@@ -82,6 +83,14 @@ class ReportsController < ApplicationController
     else
       render_404
     end
+    respond_to do |format|
+      format.html
+      format.csv do
+        send_data(issue_report_details_to_csv(@field, @statuses, @rows, @data),
+                  :type => 'text/csv; header=present',
+                  :filename => "report-#{params[:detail]}.csv")
+      end
+    end
   end
 
   private
index a01abf3a351c0550a380bcef3796d311bcf49554..36ee6ddce9da17cd5cc71eac78a32ef18fa557bf 100644 (file)
@@ -44,4 +44,22 @@ module ReportsHelper
     parameters = {:set_filter => 1, :subproject_id => '!*', field => (row.id || '!*')}.merge(options)
     project_issues_path(row.is_a?(Project) ? row : project, parameters)
   end
+
+  def issue_report_details_to_csv(field_name, statuses, rows, data)
+    Redmine::Export::CSV.generate(:encoding => params[:encoding]) do |csv|
+      # csv headers
+      headers = [''] + statuses.map(&:name) + [l(:label_open_issues_plural), l(:label_closed_issues_plural), l(:label_total)]
+      csv << headers
+
+      # csv lines
+      rows.each do |row|
+        csv <<
+          [row.name] +
+          statuses.map{|s| aggregate(data, { field_name => row.id, 'status_id' => s.id })} +
+          [aggregate(data, { field_name => row.id, 'closed' => 0 })] +
+          [aggregate(data, { field_name => row.id, 'closed' => 1 })] +
+          [aggregate(data, { field_name => row.id })]
+      end
+    end
+  end
 end
index 3361117fcd3e5081d9010aeb7c5a6a8088beab43..3c647274341a4d8e2bc3a7a990f89010a03cb46f 100644 (file)
 <% end %>
 </tbody>
 </table>
+<% other_formats_links do |f| %>
+  <%= f.link_to_with_query_parameters 'CSV', {}, :onclick => "showModal('csv-export-options', '330px'); return false;" %>
+<% end %>
+<div id="csv-export-options" style="display: none;">
+  <h3 class="title"><%= l(:label_export_options, :export_format => 'CSV') %></h3>
+  <%= form_tag(project_issues_report_details_path(@project, :detail => params[:detail], :format => 'csv'), :method => :get, :id => 'csv-export-form') do %>
+  <%= export_csv_encoding_select_tag %>
+  <p class="buttons">
+    <%= submit_tag l(:button_export), :name => nil, :onclick => 'hideModal(this);', :data => {:disable_with => false} %>
+    <%= link_to_function l(:button_cancel), 'hideModal(this);' %>
+  </p>
+  <% end %>
+</div>
+
 <div class="issue-report-graph hide-when-print">
   <canvas id="issues_by_<%= params[:detail] %>"></canvas>
 </div>
index 37536f114eb6f8c5c181281b7caad670e3d3a477..e2bbe7123be8d185dbccc0e0dd70c47cb49452f2 100644 (file)
@@ -242,4 +242,91 @@ class ReportsControllerTest < Redmine::ControllerTest
     )
     assert_response 404
   end
+
+  def test_issue_report_details_should_csv_export
+    %w(tracker version priority category assigned_to author subproject).each do |detail|
+      get(
+        :issue_report_details,
+        params: {
+          id: 1,
+          detail: detail,
+          format: 'csv'
+        }
+      )
+      assert_response :success
+      assert_equal 'text/csv; header=present', response.media_type
+    end
+  end
+
+  def test_issue_report_details_with_tracker_detail_should_csv_export
+    project = Project.find(1)
+    tracker = project.trackers.find_by(:name => 'Support request')
+    project.trackers.delete(tracker)
+
+    with_settings :display_subprojects_issues => '1' do
+      get(
+        :issue_report_details,
+        params: {
+          id: 1,
+          detail: 'tracker',
+          format: 'csv'
+        }
+      )
+      assert_response :success
+
+      assert_equal 'text/csv; header=present', response.media_type
+      lines = response.body.chomp.split("\n")
+      # Number of lines
+      rows = Project.find(1).rolled_up_trackers(true).visible
+      assert_equal rows.size + 1, lines.size
+      # Header
+      assert_equal '"",New,Assigned,Resolved,Feedback,Closed,Rejected,open,closed,Total', lines.first
+      # Details
+      to_test = [
+        'Bug,5,0,0,0,3,0,5,3,8',
+        'Feature request,0,1,0,0,0,0,1,0,1',
+        'Support request,0,0,0,0,0,0,0,0,0'
+      ]
+      to_test.each do |expected|
+        assert_includes lines, expected
+      end
+    end
+  end
+
+  def test_issue_report_details_with_assigned_to_detail_should_csv_export
+    Issue.delete_all
+    Issue.generate!
+    Issue.generate!
+    Issue.generate!(:status_id => 5)
+    Issue.generate!(:assigned_to_id => 2)
+
+    with_settings :issue_group_assignment => '1' do
+      get(
+        :issue_report_details,
+        params: {
+          id: 1,
+          detail: 'assigned_to',
+          format: 'csv'
+        }
+      )
+      assert_response :success
+
+      assert_equal 'text/csv; header=present', response.media_type
+      lines = response.body.chomp.split("\n")
+      # Number of lines
+      rows = Project.find(1).principals.sorted + [I18n.t(:label_none)]
+      assert_equal rows.size + 1, lines.size
+      # Header
+      assert_equal '"",New,Assigned,Resolved,Feedback,Closed,Rejected,open,closed,Total', lines.first
+      # Details
+      to_test = [
+        'Dave Lopper,0,0,0,0,0,0,0,0,0',
+        'John Smith,1,0,0,0,0,0,1,0,1',
+        '[none] ,2,0,0,0,1,0,2,1,3'
+      ]
+      to_test.each do |expected|
+        assert_includes lines, expected
+      end
+    end
+  end
 end