FEATURE: Export any type of report supporting table mode. (#7662)

FEATURE: Export any type of report supporting table mode. (#7662)

diff --git a/app/assets/javascripts/admin/components/admin-report.js.es6 b/app/assets/javascripts/admin/components/admin-report.js.es6
index d539776..a3ed369 100644
--- a/app/assets/javascripts/admin/components/admin-report.js.es6
+++ b/app/assets/javascripts/admin/components/admin-report.js.es6
@@ -55,7 +55,7 @@ export default Ember.Component.extend({
   showTitle: true,
   showFilteringUI: false,
   showDatesOptions: Ember.computed.alias("model.dates_filtering"),
-  showExport: Ember.computed.not("model.onlyTable"),
+  showExport: Ember.computed.not("model.isTable"),
   showRefresh: Ember.computed.or(
     "showDatesOptions",
     "model.available_filters.length"
@@ -170,8 +170,9 @@ export default Ember.Component.extend({
       "[:prev_period]",
       this.get("reportOptions.table.limit"),
       customFilters
-        ? JSON.stringify(customFilters, (key, value) =>
-            isNumeric(value) ? value.toString() : value
+        ? JSON.stringify(
+            customFilters,
+            (key, value) => (isNumeric(value) ? value.toString() : value)
           )
         : null,
       SCHEMA_VERSION
diff --git a/app/assets/javascripts/admin/models/report.js.es6 b/app/assets/javascripts/admin/models/report.js.es6
index 019f19a..831fe8e 100644
--- a/app/assets/javascripts/admin/models/report.js.es6
+++ b/app/assets/javascripts/admin/models/report.js.es6
@@ -16,8 +16,8 @@ const Report = Discourse.Model.extend({
   higher_is_better: true,
 
   @computed("modes")
-  onlyTable(modes) {
-    return modes.length === 1 && modes[0] === "table";
+  isTable(modes) {
+    return modes.some(mode => mode === "table");
   },
 
   @computed("type", "start_date", "end_date")
diff --git a/app/jobs/regular/export_csv_file.rb b/app/jobs/regular/export_csv_file.rb
index 8101b10..e1b5739 100644
--- a/app/jobs/regular/export_csv_file.rb
+++ b/app/jobs/regular/export_csv_file.rb
@@ -55,7 +55,7 @@ module Jobs
 
       # write to CSV file
       CSV.open(absolute_path, "w") do |csv|
-        csv << get_header
+        csv << get_header if @entity != "report"
         public_send(export_method).each { |d| csv << d }
       end
 
@@ -186,14 +186,23 @@ module Jobs
       @extra[:category_id] = @extra[:category_id].present? ? @extra[:category_id].to_i : nil
       @extra[:group_id] = @extra[:group_id].present? ? @extra[:group_id].to_i : nil
 
-      report_hash = {}
-      Report.find(@extra[:name], @extra).data.each do |row|
-        report_hash[row[:x].to_s] = row[:y].to_s
-      end
+      report = Report.find(@extra[:name], @extra)
+
+      header = []
+      titles = {}
 
-      (@extra[:start_date].to_date..@extra[:end_date].to_date).each do |date|
-        yield [date.to_s(:db), report_hash.fetch(date.to_s, 0)]
+      report.labels.each do |label|
+        if label[:type] == :user
+          titles[label[:properties][:username]] = label[:title]
+          header << label[:properties][:username]
+        else
+          titles[label[:property]] = label[:title]
+          header << label[:property]
+        end
       end
+
+      yield header.map { |k| titles[k] || k }
+      report.data.each { |row| yield row.values_at(*header).map(&:to_s) }
     end
 
     def get_header
diff --git a/app/models/report.rb b/app/models/report.rb
index f4f023b..b16be16 100644
--- a/app/models/report.rb
+++ b/app/models/report.rb
@@ -17,6 +17,21 @@ class Report
     30
   end
 
+  def self.default_labels
+    [
+      {
+        type: :date,
+        property: :x,
+        title: I18n.t("reports.default.labels.day")
+      },
+      {
+        type: :number,
+        property: :y,
+        title: I18n.t("reports.default.labels.count")
+      },
+    ]
+  end
+
   def initialize(type)
     @type = type
     @start_date ||= Report.default_days.days.ago.utc.beginning_of_day
@@ -102,18 +117,7 @@ class Report
       primary_color: self.primary_color,
       secondary_color: self.secondary_color,
       available_filters: self.available_filters.map { |k, v| { id: k }.merge(v) },
-      labels: labels || [
-        {
-          type: :date,
-          property: :x,
-          title: I18n.t("reports.default.labels.day")
-        },
-        {
-          type: :number,
-          property: :y,
-          title: I18n.t("reports.default.labels.count")
-        },
-      ],
+      labels: labels || Report.default_labels,
       average: self.average,
       percent: self.percent,
       higher_is_better: self.higher_is_better,
diff --git a/spec/jobs/export_csv_file_spec.rb b/spec/jobs/export_csv_file_spec.rb
index 61b8b10..8862906 100644
--- a/spec/jobs/export_csv_file_spec.rb
+++ b/spec/jobs/export_csv_file_spec.rb
@@ -42,6 +42,43 @@ describe Jobs::ExportCsvFile do
     end
   end
 
+  context '.report_export' do
+
+    let(:user) { Fabricate(:admin) }
+
+    let(:exporter) do
+      exporter = Jobs::ExportCsvFile.new
+      exporter.instance_variable_set(:@entity, 'report')
+      exporter.instance_variable_set(:@extra, HashWithIndifferentAccess.new(start_date: '2010-01-01', end_date: '2011-01-01'))
+      exporter.instance_variable_set(:@current_user, User.find_by(id: user.id))
+      exporter
+    end
+
+    it 'works with single-column reports' do
+      user.user_visits.create!(visited_at: '2010-01-01', posts_read: 42)
+      Fabricate(:user).user_visits.create!(visited_at: '2010-01-03', posts_read: 420)
+
+      exporter.instance_variable_get(:@extra)['name'] = 'dau_by_mau'
+      report = exporter.report_export.to_a
+
+      expect(report.first).to contain_exactly("Day", "Percent")
+      expect(report.second).to contain_exactly("2010-01-01", "100.0")
+      expect(report.third).to contain_exactly("2010-01-03", "50.0")
+    end
+
+    it 'works with multi-columns reports' do
+      DiscourseIpInfo.stubs(:get).with("1.1.1.1").returns(location: "Earth")
+      user.user_auth_token_logs.create!(action: "login", client_ip: "1.1.1.1", created_at: '2010-01-01')
+
+      exporter.instance_variable_get(:@extra)['name'] = 'staff_logins'
+      report = exporter.report_export.to_a
+
+      expect(report.first).to contain_exactly("User", "Location", "Login at")
+      expect(report.second).to contain_exactly(user.username, "Earth", "2010-01-01 00:00:00 UTC")
+    end
+
+  end
+
   let(:user_list_header) {
     %w{
       id name username email title created_at last_seen_at last_posted_at

GitHub sha: b2eb0f4a

1 Like