Merge pull request #2278 from internetee/2275-fix-csv-view-for-historical-data

Admin: fix CSV structure for historical data
This commit is contained in:
Timo Võhmar 2022-04-05 14:04:07 +03:00 committed by GitHub
commit b80c53b44f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 291 additions and 130 deletions

View file

@ -35,7 +35,8 @@ module Admin
respond_to do |format|
format.html
format.csv do
send_data @q.result.to_csv, filename: "account_activities_#{Time.zone.now.to_formatted_s(:number)}.csv"
raw_csv = CsvGenerator.generate_csv(@q.result)
send_data raw_csv, filename: "account_activities_#{Time.zone.now.to_formatted_s(:number)}.csv"
end
end

View file

@ -26,7 +26,7 @@ module Admin
respond_to do |format|
format.html { render page }
format.csv do
raw_csv = @q.result.to_csv
raw_csv = CsvGenerator.generate_csv(@q.result)
send_data raw_csv,
filename: "#{filename}_#{Time.zone.now.to_formatted_s(:number)}.csv",
type: "#{Mime[:csv]}; charset=utf-8"

View file

@ -1,6 +1,6 @@
module Admin
class ContactVersionsController < BaseController
include ObjectVersionsHelper
include ApplicationHelper
load_and_authorize_resource class: Version::ContactVersion
@ -23,7 +23,7 @@ module Admin
end
versions = Version::ContactVersion.includes(:item).where(where_s).order(created_at: :desc, id: :desc)
@q = versions.ransack(params[:q])
@q = versions.ransack(fix_date_params)
@versions = @q.result.page(params[:page])
@versions = @versions.per(params[:results_per_page]) if params[:results_per_page].to_i.positive?
@ -56,5 +56,16 @@ module Admin
def create_where_string(key, value)
" AND object->>'#{key}' ~* '#{value}'"
end
private
def fix_date_params
params_copy = params[:q].deep_dup
if params_copy['created_at_lteq'].present?
params_copy['created_at_lteq'] = Date.parse(params_copy['created_at_lteq']) + 1.day
end
params_copy
end
end
end

View file

@ -1,7 +1,5 @@
module Admin
class DomainVersionsController < BaseController
include ObjectVersionsHelper
load_and_authorize_resource class: Version::DomainVersion
def index
@ -82,9 +80,8 @@ module Admin
def fix_date_params
params_copy = params[:q].deep_dup
if params_copy['created_at_lteq'].present?
params_copy['created_at_lteq'] = Date.parse(params_copy['created_at_lteq']) + 1.day
end
created_at = params_copy['created_at_lteq']
params_copy['created_at_lteq'] = Date.parse(created_at) + 1.day if created_at.present?
params_copy
end

View file

@ -20,7 +20,8 @@ class Registrar
respond_to do |format|
format.html { @account_activities = @q.result.page(params[:page]) }
format.csv do
send_data @q.result.to_csv, filename: "account_activities_#{Time.zone.now.to_formatted_s(:number)}.csv"
raw_csv = CsvGenerator.generate_csv(@q.result)
send_data raw_csv, filename: "account_activities_#{Time.zone.now.to_formatted_s(:number)}.csv"
end
end

View file

@ -40,7 +40,7 @@ class Registrar
@contacts = @contacts.per(contacts_per_page) if contacts_per_page.positive?
end
format.csv do
raw_csv = contacts.to_csv
raw_csv = CsvGenerator.generate_csv(contacts)
send_data raw_csv, filename: 'contacts.csv', type: "#{Mime[:csv]}; charset=utf-8"
end
format.pdf do

View file

@ -10,27 +10,11 @@ module ApplicationHelper
end
def ident_for(contact)
if contact.is_a? Hash
ident_country_code = contact[:ident_country_code]
ident_type = contact[:ident_type]
ident = contact[:ident]
else
ident_country_code = contact.ident_country_code
ident_type = contact.ident_type
ident = contact.ident
end
description = "[#{contact.ident_country_code} #{contact.ident_type}]"
description.prepend("#{ident} ") if ident.present?
case ident_type
when 'birthday'
"#{ident} [#{ident_country_code} #{ident_type}]"
else
if ident.present?
"#{ident} [#{ident_country_code} #{ident_type}]"
else
"[#{ident_country_code} #{ident_type}]"
end
end
description
end
def current_commit_link

View file

@ -1,19 +0,0 @@
module ObjectVersionsHelper
def attach_existing_fields(version, new_object)
version.object_changes.to_h.each do |key, value|
method_name = "#{key}=".to_sym
new_object.public_send(method_name, event_value(version, value)) if new_object.respond_to?(method_name)
end
end
def only_present_fields(version, model)
field_names = model.column_names
version.object.to_h.select { |key, _value| field_names.include?(key) }
end
private
def event_value(version, val)
version.event == 'destroy' ? val.first : val.last
end
end

View file

@ -1,10 +0,0 @@
module ToCsv
def to_csv
CSV.generate do |csv|
csv << column_names
all.find_each do |item|
csv << item.attributes.values_at(*column_names)
end
end
end
end

View file

@ -1,5 +1,4 @@
class Account < ApplicationRecord
extend ToCsv
include Versions
belongs_to :registrar, required: true
@ -12,4 +11,12 @@ class Account < ApplicationRecord
def activities
account_activities
end
def as_csv_row
[id, balance, currency, registrar]
end
def self.csv_header
['Id', 'Balance', 'Currency', 'Registrar']
end
end

View file

@ -11,6 +11,7 @@ class AccountActivity < ApplicationRecord
UPDATE_CREDIT = 'update_credit'.freeze
after_create :update_balance
def update_balance
account.balance += sum
account.save
@ -19,23 +20,17 @@ class AccountActivity < ApplicationRecord
save
end
def as_csv_row
[account.registrar.try(:code), description, I18n.t(activity_type), I18n.l(created_at), sum]
end
class << self
def types_for_select
[CREATE, RENEW, ADD_CREDIT, UPDATE_CREDIT].map { |x| [I18n.t(x), x] }
end
def to_csv
attributes = %w(description activity_type created_at sum)
CSV.generate(headers: true) do |csv|
csv << %w(registrar description activity_type receipt_date sum)
all.each do |x|
attrs = [x.account.registrar.try(:code)]
attrs += attributes.map { |attr| x.send(attr) }
csv << attrs
end
end
def csv_header
['Registrar', 'Description', 'Activity Type', 'Receipt Date', 'Sum']
end
end
end

View file

@ -1,5 +1,3 @@
module ApiLog
class EppLog < Db
extend ToCsv
end
class EppLog < Db; end
end

View file

@ -1,5 +1,3 @@
module ApiLog
class ReppLog < Db
extend ToCsv
end
class ReppLog < Db; end
end

View file

@ -1,6 +1,5 @@
class BlockedDomain < ApplicationRecord
include Versions
extend ToCsv
before_save :generate_data
after_destroy :remove_data

View file

@ -188,15 +188,6 @@ class Contact < ApplicationRecord
]
end
def to_csv
CSV.generate do |csv|
csv << column_names
all.each do |contact|
csv << contact.attributes.values_at(*column_names)
end
end
end
def pdf(html)
kit = PDFKit.new(html)
kit.to_pdf
@ -569,4 +560,27 @@ class Contact < ApplicationRecord
def deletable?
!linked?
end
def ident_human_description
description = "[#{ident_country_code} #{ident_type}]"
description.prepend("#{ident} ") if ident.present?
description
end
def as_csv_row
[
name,
code,
ident_human_description,
email,
created_at.to_formatted_s(:db),
registrar,
phone,
]
end
def self.csv_header
['Name', 'ID', 'Ident', 'E-mail', 'Created at', 'Registrar', 'Phone']
end
end

View file

@ -1,5 +1,4 @@
class Dispute < ApplicationRecord
extend ToCsv
include WhoisStatusPopulate
validates :domain_name, :password, :starts_at, :expires_at, presence: true
before_validation :fill_empty_passwords, :set_expiry_date

View file

@ -289,21 +289,6 @@ class Domain < ApplicationRecord
)
end
def to_csv
CSV.generate do |csv|
headers = column_names.dup
swap_elements(headers, [[0, 1], [1, 5]])
headers[0] = 'Domain'
headers[1] = headers[1].humanize
csv << headers
all.find_each do |item|
row = item.attributes.values_at(*column_names)
swap_elements(row, [[0, 1], [1, 5]])
csv << row
end
end
end
private
def registrant_user_domains_by_registrant(registrant_user)
@ -753,6 +738,38 @@ class Domain < ApplicationRecord
contacts.select(&:email_verification_failed?)&.map(&:email)&.uniq
end
def as_csv_row
[
name,
registrant_name,
valid_to.to_formatted_s(:db),
registrar,
created_at.to_formatted_s(:db),
statuses,
contacts.pluck(:code),
force_delete_date,
force_delete_data,
]
end
def registrant_name
return registrant.name if registrant
ver = Version::ContactVersion.where(item_id: registrant_id).last
contact = Contact.all_versions_for([registrant_id], created_at).first
contact = ObjectVersionsParser.new(ver).parse if contact.nil? && ver
contact.try(:name) || 'Deleted'
end
def self.csv_header
[
'Domain', 'Registrant', 'Valid to', 'Registrar', 'Created at',
'Statuses', 'Contacts code', 'Force delete date', 'Force delete data'
]
end
def self.pdf(html)
kit = PDFKit.new(html)
kit.to_pdf

View file

@ -3,7 +3,6 @@ class Invoice < ApplicationRecord
include Invoice::Cancellable
include Invoice::Payable
include Invoice::BookKeeping
extend ToCsv
belongs_to :buyer, class_name: 'Registrar'
has_one :account_activity
@ -117,6 +116,23 @@ class Invoice < ApplicationRecord
e_invoice_sent_at.present?
end
def as_csv_row
[
number,
buyer,
cancelled? ? I18n.t(:cancelled) : due_date,
receipt_date_status,
issue_date,
total,
currency,
seller_name,
]
end
def self.csv_header
['Number', 'Buyer', 'Due Date', 'Receipt Date', 'Issue Date', 'Total', 'Currency', 'Seller Name']
end
def self.create_from_transaction!(transaction)
registrar_user = Registrar.find_by(reference_no: transaction.parsed_ref_number)
return unless registrar_user
@ -128,6 +144,16 @@ class Invoice < ApplicationRecord
private
def receipt_date_status
if paid?
receipt_date
elsif cancelled?
I18n.t(:cancelled)
else
I18n.t(:unpaid)
end
end
def apply_default_buyer_vat_no
self.buyer_vat_no = buyer.vat_no
end

View file

@ -1,5 +1,4 @@
class ReservedDomain < ApplicationRecord
extend ToCsv
include Versions # version/reserved_domain_version.rb
include WhoisStatusPopulate
before_save :fill_empty_passwords

View file

@ -1,7 +1,23 @@
class Version::ContactVersion < PaperTrail::Version
extend ToCsv
include VersionSession
self.table_name = :log_contacts
self.sequence_name = :log_contacts_id_seq
def as_csv_row
contact = ObjectVersionsParser.new(self).parse
[
contact.name,
contact.code,
contact.ident_human_description,
contact.registrar,
event,
created_at.to_formatted_s(:db)
]
end
def self.csv_header
['Name', 'ID', 'Ident', 'Registrar', 'Action', 'Created at']
end
end

View file

@ -1,5 +1,4 @@
class Version::DomainVersion < PaperTrail::Version
extend ToCsv
include VersionSession
self.table_name = :log_domains
@ -7,6 +6,18 @@ class Version::DomainVersion < PaperTrail::Version
scope :deleted, -> { where(event: 'destroy') }
def as_csv_row
domain = ObjectVersionsParser.new(self).parse
[
domain.name,
domain.registrant_name,
domain.registrar,
event,
created_at.to_formatted_s(:db)
]
end
def self.was_contact_linked?(contact_id)
sql = <<-SQL
SELECT
@ -43,4 +54,8 @@ class Version::DomainVersion < PaperTrail::Version
count_by_sql(sql).nonzero?
end
def self.csv_header
['Name', 'Registrant', 'Registrar', 'Action', 'Created at']
end
end

View file

@ -0,0 +1,29 @@
class CsvGenerator
class << self
def generate_csv(objects)
class_name = objects.first.class
return default_generation(objects) unless custom_csv?(class_name)
CSV.generate do |csv|
csv << class_name.csv_header
objects.each { |object| csv << object.as_csv_row }
end
end
private
def default_generation(objects)
CSV.generate do |csv|
csv << objects.column_names
objects.all.find_each { |object| csv << object.attributes.values_at(*objects.column_names) }
end
end
def custom_csv?(class_name)
[
Version::DomainVersion, Version::ContactVersion, Domain,
Contact, Invoice, Account, AccountActivity
].include?(class_name)
end
end
end

View file

@ -0,0 +1,28 @@
class ObjectVersionsParser
def initialize(version)
@version = version
end
def parse
model = @version.item_type.constantize
attributes = only_present_fields(model)
history_object = model.new(attributes)
attach_existing_fields(history_object) unless @version.event == 'destroy'
history_object
end
private
def attach_existing_fields(history_object)
@version.object_changes.to_h.each do |key, value|
method_name = "#{key}=".to_sym
history_object.public_send(method_name, value.last) if history_object.respond_to?(method_name)
end
end
def only_present_fields(model)
field_names = model.column_names
@version.object.to_h.select { |key, _value| field_names.include?(key) }
end
end

View file

@ -64,9 +64,7 @@
%tbody
- @versions.each do |version|
- if version
- attributes = only_present_fields(version, Contact)
- contact = Contact.new(attributes)
- attach_existing_fields(version, contact)
- contact = ObjectVersionsParser.new(version).parse
%tr
%td= link_to(contact.name, admin_contact_version_path(version.id))

View file

@ -1,6 +1,5 @@
- attributes = only_present_fields(@version, Contact)
- contact = Contact.new(attributes)
- attach_existing_fields(@version, contact)
- contact = ObjectVersionsParser.new(@version).parse
= render 'shared/title', name: contact.name
.row

View file

@ -62,9 +62,7 @@
%tbody
- @versions.each do |version|
- if version
- attributes = only_present_fields(version, Domain)
- domain = Domain.new(attributes)
- attach_existing_fields(version, domain) unless version.event == 'destroy'
- domain = ObjectVersionsParser.new(version).parse
%tr
%td= link_to(domain.name, admin_domain_version_path(version.id))

View file

@ -1,6 +1,4 @@
- present_fields = only_present_fields(@version, Domain)
- domain = Domain.new(present_fields)
- attach_existing_fields(@version, domain) unless @version.event == 'destroy'
- domain = ObjectVersionsParser.new(@version).parse
- if @version
- children = HashWithIndifferentAccess.new(@version.children)

4
test/fixtures/files/accounts.csv vendored Normal file
View file

@ -0,0 +1,4 @@
Id,Balance,Currency,Registrar
112846265,100.0,EUR,Best Names
298486374,100.0,EUR,Good Names
597560588,0.0,EUR,Not in use
1 Id Balance Currency Registrar
2 112846265 100.0 EUR Best Names
3 298486374 100.0 EUR Good Names
4 597560588 0.0 EUR Not in use

View file

@ -0,0 +1,3 @@
Name,ID,Ident,Registrar,Action,Created at
,test_code,[ ],Best Names,update,2018-04-23 15:50:48
,,[ ],,update,2010-07-04 21:00:00
1 Name ID Ident Registrar Action Created at
2 test_code [ ] Best Names update 2018-04-23 15:50:48
3 [ ] update 2010-07-04 21:00:00

2
test/fixtures/files/contacts.csv vendored Normal file
View file

@ -0,0 +1,2 @@
Name,ID,Ident,E-mail,Created at,Registrar,Phone
Acme Ltd,acme-ltd-001,1234567 [US org],acme@outlook.test,2010-07-05 07:30:00,Best Names,+555.555
1 Name ID Ident E-mail Created at Registrar Phone
2 Acme Ltd acme-ltd-001 1234567 [US org] acme@outlook.test 2010-07-05 07:30:00 Best Names +555.555

View file

@ -0,0 +1,3 @@
Name,Registrant,Registrar,Action,Created at
,test_code,Best Names,update,2018-04-23 15:50:48
,John,,update,2010-07-04 21:00:00
1 Name Registrant Registrar Action Created at
2 test_code Best Names update 2018-04-23 15:50:48
3 John update 2010-07-04 21:00:00

2
test/fixtures/files/domains.csv vendored Normal file
View file

@ -0,0 +1,2 @@
Domain,Registrant,Valid to,Registrar,Created at,Statuses,Contacts code,Force delete date,Force delete data
metro.test,Jack,2010-07-05 00:00:00,Good Names,2010-07-05 07:30:00,[],"[""jack-001"", ""jack-001""]",,
1 Domain Registrant Valid to Registrar Created at Statuses Contacts code Force delete date Force delete data
2 metro.test Jack 2010-07-05 00:00:00 Good Names 2010-07-05 07:30:00 [] ["jack-001", "jack-001"]

3
test/fixtures/files/invoices.csv vendored Normal file
View file

@ -0,0 +1,3 @@
Number,Buyer,Due Date,Receipt Date,Issue Date,Total,Currency,Seller Name
2,Best Names,2010-07-06,Unpaid,2010-07-05,16.5,EUR,Seller Ltd
1,Best Names,2010-07-06,2010-07-05,2010-07-05,16.5,EUR,Seller Ltd
1 Number Buyer Due Date Receipt Date Issue Date Total Currency Seller Name
2 2 Best Names 2010-07-06 Unpaid 2010-07-05 16.5 EUR Seller Ltd
3 1 Best Names 2010-07-06 2010-07-05 2010-07-05 16.5 EUR Seller Ltd

View file

@ -29,4 +29,16 @@ class AdminAccountsSystemTest < ApplicationSystemTestCase
assert_text 'Account has been successfully updated'
assert_text '234'
end
def test_download_accounts_list_as_csv
travel_to Time.zone.parse('2010-07-05 10:30')
get admin_accounts_path(format: :csv)
assert_response :ok
assert_equal 'text/csv; charset=utf-8', response.headers['Content-Type']
assert_equal %(attachment; filename="accounts_#{Time.zone.now.to_formatted_s(:number)}.csv"; filename*=UTF-8''accounts_#{Time.zone.now.to_formatted_s(:number)}.csv),
response.headers['Content-Disposition']
assert_equal file_fixture('accounts.csv').read, response.body
end
end

View file

@ -62,11 +62,10 @@ class ContactVersionsTest < ApplicationSystemTestCase
travel_to now
get admin_contact_versions_path(format: :csv)
assert_response :ok
assert_equal 'text/csv; charset=utf-8', response.headers['Content-Type']
assert_equal %(attachment; filename="contact_history_#{Time.zone.now.to_formatted_s(:number)}.csv"; filename*=UTF-8''contact_history_#{Time.zone.now.to_formatted_s(:number)}.csv),
response.headers['Content-Disposition']
assert_not_empty response.body
assert_equal file_fixture('contact_versions.csv').read, response.body
end
end

View file

@ -0,0 +1,22 @@
require 'application_system_test_case'
class ContactsCsvTest < ApplicationSystemTestCase
setup do
sign_in users(:admin)
Domain.destroy_all
Contact.all.each { |contact| contact.destroy unless contact.name == 'Acme Ltd' }
end
def test_download_contacts_list_as_csv
travel_to Time.zone.parse('2010-07-05 10:30')
contact = Contact.first
contact.created_at = Time.zone.now
contact.save(validate: false)
visit admin_contacts_url
click_link('CSV')
assert_equal "attachment; filename=\"contacts_#{Time.zone.now.to_formatted_s(:number)}.csv\"; filename*=UTF-8''contacts_#{Time.zone.now.to_formatted_s(:number)}.csv", response_headers['Content-Disposition']
assert_equal file_fixture('contacts.csv').read, page.body
end
end

View file

@ -98,7 +98,7 @@ class DomainVersionsTest < ApplicationSystemTestCase
assert_equal 'text/csv; charset=utf-8', response.headers['Content-Type']
assert_equal %(attachment; filename="domain_history_#{Time.zone.now.to_formatted_s(:number)}.csv"; filename*=UTF-8''domain_history_#{Time.zone.now.to_formatted_s(:number)}.csv),
response.headers['Content-Disposition']
assert_not_empty response.body
assert_equal file_fixture('domain_versions.csv').read, response.body
end
def test_search_event_param

View file

@ -1,18 +1,21 @@
require 'application_system_test_case'
class AdminAreaCsvTest < ApplicationSystemTestCase
class DomainsCsvTest < ApplicationSystemTestCase
setup do
sign_in users(:admin)
Domain.all.each { |domain| domain.destroy unless domain.name == 'metro.test' }
end
def test_downloads_domain_list_as_csv
search_params = {"valid_to_lteq"=>nil}
expected_csv = Domain.includes(:registrar, :registrant).search(search_params).result.to_csv
def test_download_domains_list_as_csv
travel_to Time.zone.parse('2010-07-05 10:30')
domain = Domain.first
domain.created_at = Time.zone.now
domain.save(validate: false)
visit admin_domains_url
click_link('CSV')
assert_equal "attachment; filename=\"domains_#{Time.zone.now.to_formatted_s(:number)}.csv\"; filename*=UTF-8''domains_#{Time.zone.now.to_formatted_s(:number)}.csv", response_headers['Content-Disposition']
assert_equal expected_csv, page.body
assert_equal file_fixture('domains.csv').read, page.body
end
end

View file

@ -40,4 +40,14 @@ class AdminAreaInvoicesTest < ApplicationSystemTestCase
assert_current_path admin_invoice_path(@invoice)
assert_text 'Invoice has been sent'
end
def test_download_invoices_list_as_csv
travel_to Time.zone.parse('2010-07-05 10:30')
visit admin_invoices_url
click_link('CSV')
assert_equal "attachment; filename=\"invoices_#{Time.zone.now.to_formatted_s(:number)}.csv\"; filename*=UTF-8''invoices_#{Time.zone.now.to_formatted_s(:number)}.csv", response_headers['Content-Disposition']
assert_equal file_fixture('invoices.csv').read, page.body
end
end