diff --git a/app/assets/javascripts/admin/components/admin-report.js.es6 b/app/assets/javascripts/admin/components/admin-report.js.es6 index d539776804a..a3ed3697527 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 019f19a8805..831fe8e7657 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 8101b10ef36..e1b57398c1a 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 + report = Report.find(@extra[:name], @extra) + + header = [] + titles = {} + + 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 - (@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)] - 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 f4f023bc3b0..b16be1632ec 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 61b8b10f572..88629062bf4 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