User export improvements 2 (#10560)

* FEATURE: Use predictable filenames inside the user archive export

* FEATURE: Include badges in user archive export

* FEATURE: Add user_visits table to the user archive export
This commit is contained in:
Kane York 2020-08-31 15:26:51 -07:00 committed by GitHub
parent 43ffd4d28f
commit 5ec5fbd7ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 104 additions and 4 deletions

View File

@ -13,13 +13,17 @@ module Jobs
COMPONENTS ||= %w( COMPONENTS ||= %w(
user_archive user_archive
user_archive_profile user_archive_profile
badges
category_preferences category_preferences
visits
) )
HEADER_ATTRS_FOR ||= HashWithIndifferentAccess.new( HEADER_ATTRS_FOR ||= HashWithIndifferentAccess.new(
user_archive: ['topic_title', 'categories', 'is_pm', 'post', 'like_count', 'reply_count', 'url', 'created_at'], user_archive: ['topic_title', 'categories', 'is_pm', 'post', 'like_count', 'reply_count', 'url', 'created_at'],
user_archive_profile: ['location', 'website', 'bio', 'views'], user_archive_profile: ['location', 'website', 'bio', 'views'],
badges: ['badge_id', 'badge_name', 'granted_at', 'post_id', 'seq', 'granted_manually', 'notification_id', 'featured_rank'],
category_preferences: ['category_id', 'category_names', 'notification_level', 'dismiss_new_timestamp'], category_preferences: ['category_id', 'category_names', 'notification_level', 'dismiss_new_timestamp'],
visits: ['visited_at', 'posts_read', 'mobile', 'time_read'],
) )
def execute(args) def execute(args)
@ -36,13 +40,13 @@ module Jobs
if respond_to? filename_method if respond_to? filename_method
h[:filename] = public_send(filename_method) h[:filename] = public_send(filename_method)
else else
h[:filename] = "#{name}-#{@current_user.username}-#{@timestamp}" h[:filename] = name
end end
components.push(h) components.push(h)
end end
export_title = 'user_archive'.titleize export_title = 'user_archive'.titleize
filename = components.first[:filename] filename = "user_archive-#{@current_user.username}-#{@timestamp}"
user_export = UserExport.create(file_name: filename, user_id: @current_user.id) user_export = UserExport.create(file_name: filename, user_id: @current_user.id)
filename = "#{filename}-#{user_export.id}" filename = "#{filename}-#{user_export.id}"
@ -126,6 +130,29 @@ module Jobs
end end
end end
def badges_export
return enum_for(:badges_export) unless block_given?
UserBadge
.where(user_id: @current_user.id)
.joins(:badge)
.select(:badge_id, :granted_at, :post_id, :seq, :granted_by_id, :notification_id, :featured_rank)
.order(:granted_at)
.each do |ub|
yield [
ub.badge_id,
ub.badge.display_name,
ub.granted_at,
ub.post_id,
ub.seq,
# Hide the admin's identity, simply indicate human or system
User.human_user_id?(ub.granted_by_id),
ub.notification_id,
ub.featured_rank,
]
end
end
def category_preferences_export def category_preferences_export
return enum_for(:category_preferences_export) unless block_given? return enum_for(:category_preferences_export) unless block_given?
@ -142,6 +169,22 @@ module Jobs
end end
end end
def visits_export
return enum_for(:visits_export) unless block_given?
UserVisit
.where(user_id: @current_user.id)
.order(visited_at: :asc)
.each do |uv|
yield [
uv.visited_at,
uv.posts_read,
uv.mobile,
uv.time_read,
]
end
end
def get_header(entity) def get_header(entity)
if entity == 'user_list' if entity == 'user_list'
header_array = HEADER_ATTRS_FOR['user_list'] + HEADER_ATTRS_FOR['user_stats'] + HEADER_ATTRS_FOR['user_profile'] header_array = HEADER_ATTRS_FOR['user_list'] + HEADER_ATTRS_FOR['user_stats'] + HEADER_ATTRS_FOR['user_profile']

View File

@ -73,8 +73,8 @@ describe Jobs::ExportUserArchive do
end end
expect(files.size).to eq(Jobs::ExportUserArchive::COMPONENTS.length) expect(files.size).to eq(Jobs::ExportUserArchive::COMPONENTS.length)
expect(files.find { |f| f.match 'user_archive-john_doe-' }).to_not be_nil expect(files.find { |f| f == 'user_archive.csv' }).to_not be_nil
expect(files.find { |f| f.match 'user_archive_profile-john_doe-' }).to_not be_nil expect(files.find { |f| f == 'category_preferences.csv' }).to_not be_nil
end end
end end
@ -149,6 +149,37 @@ describe Jobs::ExportUserArchive do
end end
end end
context 'badges' do
let(:component) { 'badges' }
let(:admin) { Fabricate(:admin) }
let(:badge1) { Fabricate(:badge) }
let(:badge2) { Fabricate(:badge, multiple_grant: true) }
let(:badge3) { Fabricate(:badge, multiple_grant: true) }
let(:day_ago) { 1.day.ago }
it 'properly includes badge records' do
grant_start = Time.now.utc
BadgeGranter.grant(badge1, user)
BadgeGranter.grant(badge2, user)
BadgeGranter.grant(badge2, user, granted_by: admin)
BadgeGranter.grant(badge3, user, post_id: Fabricate(:post).id)
BadgeGranter.grant(badge3, user, post_id: Fabricate(:post).id)
BadgeGranter.grant(badge3, user, post_id: Fabricate(:post).id)
data, csv_out = make_component_csv
expect(data.length).to eq(6)
expect(data[0]['badge_id']).to eq(badge1.id.to_s)
expect(data[0]['badge_name']).to eq(badge1.display_name)
expect(data[0]['featured_rank']).to_not eq('')
expect(DateTime.parse(data[0]['granted_at'])).to be >= DateTime.parse(grant_start.to_s)
expect(data[2]['granted_manually']).to eq('true')
expect(Post.find(data[3]['post_id'])).to_not be_nil
end
end
context 'category_preferences' do context 'category_preferences' do
let(:component) { 'category_preferences' } let(:component) { 'category_preferences' }
@ -201,4 +232,30 @@ describe Jobs::ExportUserArchive do
end end
end end
context 'visits' do
let(:component) { 'visits' }
let(:user2) { Fabricate(:user) }
it 'correctly exports the UserVisit table' do
freeze_time '2017-03-01 12:00'
UserVisit.create(user_id: user.id, visited_at: 1.minute.ago, posts_read: 1, mobile: false, time_read: 10)
UserVisit.create(user_id: user.id, visited_at: 2.days.ago, posts_read: 2, mobile: false, time_read: 20)
UserVisit.create(user_id: user.id, visited_at: 1.week.ago, posts_read: 3, mobile: true, time_read: 30)
UserVisit.create(user_id: user.id, visited_at: 1.year.ago, posts_read: 4, mobile: false, time_read: 40)
UserVisit.create(user_id: user2.id, visited_at: 1.minute.ago, posts_read: 1, mobile: false, time_read: 50)
data, csv_out = make_component_csv
# user2's data is not mixed in
expect(data.length).to eq(4)
expect(data.find { |r| r['time_read'] == 50 }).to be_nil
expect(data[0]['visited_at']).to eq('2016-03-01')
expect(data[0]['posts_read']).to eq('4')
expect(data[0]['time_read']).to eq('40')
expect(data[1]['mobile']).to eq('true')
expect(data[3]['visited_at']).to eq('2017-03-01')
end
end
end end