FEATURE: export user list

This commit is contained in:
Arpit Jalan 2014-08-09 15:58:57 +05:30
parent 2850ce46b8
commit d0736a06b6
11 changed files with 222 additions and 1 deletions

View file

@ -0,0 +1,26 @@
/**
Data model for representing an export
@class ExportCsv
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.ExportCsv = Discourse.Model.extend({});
Discourse.ExportCsv.reopenClass({
/**
Exports user list
@method export_user_list
**/
exportUserList: function() {
return Discourse.ajax("/admin/export_csv/users.json").then(function(result) {
if (result.success) {
bootbox.alert(I18n.t("admin.export_csv.success"));
} else {
bootbox.alert(I18n.t("admin.export_csv.failed"));
}
});
}
});

View file

@ -9,6 +9,12 @@
Discourse.AdminUsersListRoute = Discourse.Route.extend({ Discourse.AdminUsersListRoute = Discourse.Route.extend({
renderTemplate: function() { renderTemplate: function() {
this.render('admin/templates/users_list', {into: 'admin/templates/admin'}); this.render('admin/templates/users_list', {into: 'admin/templates/admin'});
},
actions: {
exportUsers: function() {
Discourse.ExportCsv.exportUserList();
}
} }
}); });
@ -57,7 +63,7 @@ Discourse.AdminUsersListNewRoute = Discourse.Route.extend({
/** /**
Handles the route that lists pending users. Handles the route that lists pending users.
@class AdminUsersListNewRoute @class AdminUsersListPendingRoute
@extends Discourse.Route @extends Discourse.Route
@namespace Discourse @namespace Discourse
@module Discourse @module Discourse

View file

@ -15,6 +15,9 @@
<div class='username controls'> <div class='username controls'>
{{text-field value=username placeholderKey="search_hint"}} {{text-field value=username placeholderKey="search_hint"}}
</div> </div>
<div class="pull-right">
<button {{action exportUsers}} class="btn" title="{{i18n admin.export_csv.users.title}}"><i class="fa fa-download"></i>{{i18n admin.export_csv.users.text}}</button>
</div>
</div> </div>
<div class="admin-container"> <div class="admin-container">

View file

@ -0,0 +1,20 @@
class Admin::ExportCsvController < Admin::AdminController
skip_before_filter :check_xhr, only: [:download]
def export_user_list
# export csv file in a background thread
Jobs.enqueue(:export_csv_file, entity: 'user', user_id: current_user.id)
render json: success_json
end
def download
filename = params.fetch(:id)
if export_csv_path = ExportCsv.get_download_path(filename)
send_file export_csv_path
else
render nothing: true, status: 404
end
end
end

View file

@ -0,0 +1,75 @@
require 'csv'
require_dependency 'system_message'
module Jobs
class ExportCsvFile < Jobs::Base
sidekiq_options retry: false
attr_accessor :current_user
def initialize
@file_name = ""
end
def execute(args)
entity = args[:entity]
@current_user = User.find_by(id: args[:user_id])
raise Discourse::InvalidParameters.new(:entity) if entity.blank?
case entity
when 'user'
query = ::AdminUserIndexQuery.new
user_data = query.find_users_query.to_a
data = Hash.new do |hash, key|
hash[key] = {}
end
user_data.each do |user|
id = user['id']
email = user['email']
data[id] = email
end
data = data.to_a
end
if data && data.length > 0
set_file_path
write_csv_file(data)
end
notify_user
end
private
def set_file_path
@file_name = "export_#{SecureRandom.hex(4)}.csv"
# ensure directory exists
dir = File.dirname("#{ExportCsv.base_directory}/#{@file_name}")
FileUtils.mkdir_p(dir) unless Dir.exists?(dir)
end
def write_csv_file(data)
# write to CSV file
CSV.open(File.expand_path("#{ExportCsv.base_directory}/#{@file_name}", __FILE__), "w") do |csv|
data.each do |value|
csv << [value[1]]
end
end
end
def notify_user
if @current_user
if @file_name != "" && File.exists?("#{ExportCsv.base_directory}/#{@file_name}")
SystemMessage.create_from_system_user(@current_user, :csv_export_succeeded, download_link: "#{Discourse.base_url}/admin/export_csv/#{@file_name}/download", file_name: @file_name)
else
SystemMessage.create_from_system_user(@current_user, :csv_export_failed)
end
end
end
end
end

16
app/models/export_csv.rb Normal file
View file

@ -0,0 +1,16 @@
class ExportCsv
def self.get_download_path(filename)
path = File.join(ExportCsv.base_directory, filename)
if File.exists?(path)
return path
else
nil
end
end
def self.base_directory
File.join(Rails.root, "public", "uploads", "csv_exports", RailsMultisite::ConnectionManagement.current_db)
end
end

View file

@ -1603,6 +1603,13 @@ en:
title: "Rollback the database to previous working state" title: "Rollback the database to previous working state"
confirm: "Are your sure you want to rollback the database to the previous working state?" confirm: "Are your sure you want to rollback the database to the previous working state?"
export_csv:
users:
text: "Export Users"
title: "Export user list in a CSV file."
success: "Export has been initiated, you will be notified shortly with progress."
failed: "Export failed. Please check the logs."
customize: customize:
title: "Customize" title: "Customize"
long_title: "Site Customizations" long_title: "Site Customizations"

View file

@ -1387,6 +1387,17 @@ en:
%{logs} %{logs}
``` ```
csv_export_succeeded:
subject_template: "Data Export completed successfully"
text_body_template: |
The data export was successful.
Download CSV file: <a class="attachment" href="%{download_link}">%{file_name}</a>
csv_export_failed:
subject_template: "Export failed"
text_body_template: "The export has failed. Please check the logs."
email_reject_trust_level: email_reject_trust_level:
subject_template: "Email issue -- Insufficient Trust Level" subject_template: "Email issue -- Insufficient Trust Level"
text_body_template: | text_body_template: |

View file

@ -145,6 +145,15 @@ Discourse::Application.routes.draw do
end end
end end
resources :export_csv, constraints: AdminConstraint.new do
member do
get "download" => "export_csv#download", constraints: { id: /[^\/]+/ }
end
collection do
get "users" => "export_csv#export_user_list"
end
end
resources :badges, constraints: AdminConstraint.new do resources :badges, constraints: AdminConstraint.new do
collection do collection do
get "types" => "badges#badge_types" get "types" => "badges#badge_types"

View file

@ -0,0 +1,35 @@
require "spec_helper"
describe Admin::ExportCsvController do
it "is a subclass of AdminController" do
(Admin::ExportCsvController < Admin::AdminController).should be_true
end
let(:export_filename) { "export_b6a2bc87.csv" }
context "while logged in as an admin" do
before { @admin = log_in(:admin) }
describe ".download" do
it "uses send_file to transmit the export file" do
controller.stubs(:render)
export = ExportCsv.new()
ExportCsv.expects(:get_download_path).with(export_filename).returns(export)
subject.expects(:send_file).with(export)
get :download, id: export_filename
end
it "returns 404 when the export file does not exist" do
ExportCsv.expects(:get_download_path).returns(nil)
get :download, id: export_filename
response.should be_not_found
end
end
end
end

View file

@ -0,0 +1,13 @@
require 'spec_helper'
describe Jobs::ExportCsvFile do
context '.execute' do
it 'raises an error when the entity is missing' do
lambda { Jobs::ExportCsvFile.new.execute(user_id: "1") }.should raise_error(Discourse::InvalidParameters)
end
end
end