From ba2afc0c163b45bd2509eb576494103133727d15 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Mon, 16 Sep 2019 22:36:03 +0200 Subject: [PATCH] Fixes #2699 - Excel export of report or time accounting drops additional fields in row if integer field contain `null/nil/undefined`. --- app/controllers/reports_controller.rb | 157 +------ .../time_accountings_controller.rb | 391 +++++------------- lib/excel_sheet.rb | 173 ++++++++ lib/excel_sheet/ticket.rb | 103 +++++ spec/lib/excel_sheet_spec.rb | 14 + spec/requests/report_spec.rb | 4 - spec/requests/time_accounting_spec.rb | 3 - 7 files changed, 391 insertions(+), 454 deletions(-) create mode 100644 lib/excel_sheet.rb create mode 100644 lib/excel_sheet/ticket.rb create mode 100644 spec/lib/excel_sheet_spec.rb diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 3fb71d9ef..62fd2350a 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -61,7 +61,7 @@ class ReportsController < ApplicationController # get data result = {} - content = nil + excel = nil filename = nil get_params[:metric][:backend].each do |backend| next if params[:downloadBackendSelected] != backend[:name] @@ -89,14 +89,19 @@ class ReportsController < ApplicationController # generate sheet if params[:sheet] - content = sheet(get_params[:profile], backend[:display], result) + excel = ExcelSheet::Ticket.new( + title: "#{get_params[:profile].name} (#{backend[:display]})", + ticket_ids: result[:ticket_ids], + timezone: params[:timezone], + locale: current_user.locale, + ) filename = "tickets-#{get_params[:profile].name}-#{backend[:display]}.xls" end break end - if content + if excel send_data( - content, + excel.content, filename: filename, type: 'application/vnd.ms-excel', disposition: 'attachment' @@ -176,148 +181,4 @@ class ReportsController < ApplicationController } end - def sheet(profile, title, result) - - params[:timezone] ||= Setting.get('timezone_default') - - # Create a new Excel workbook - temp_file = Tempfile.new('time_tracking.xls') - workbook = WriteExcel.new(temp_file) - - # Add a worksheet - worksheet = workbook.add_worksheet - worksheet.set_row(0, 18) - worksheet.set_column(0, 0, 10) - worksheet.set_column(1, 1, 34) - worksheet.set_column(2, 2, 10) - worksheet.set_column(3, 3, 10) - worksheet.set_column(4, 8, 20) - worksheet.set_column(11, 0, 20) - worksheet.set_column(12, 0, 20) - worksheet.set_column(13, 0, 20) - - # Add and define a format - format = workbook.add_format - format.set_bold - format.set_size(14) - format.set_color('black') - - # Write a formatted and unformatted string, row and column notation. - worksheet.write_string(0, 0, "Tickets: #{profile.name} (#{title})", format) - - format_header = workbook.add_format - format_header.set_italic - format_header.set_bg_color('gray') - format_header.set_color('white') - - format_time = workbook.add_format(num_format: 'yyyy-mm-dd hh:mm:ss') - format_date = workbook.add_format(num_format: 'yyyy-mm-dd') - - format_footer = workbook.add_format - format_footer.set_italic - format_footer.set_color('gray') - format_footer.set_size(8) - - worksheet.write_string(2, 0, '#', format_header) - worksheet.write_string(2, 1, 'Title', format_header) - worksheet.write_string(2, 2, 'State', format_header) - worksheet.write_string(2, 3, 'Priority', format_header) - worksheet.write_string(2, 4, 'Group', format_header) - worksheet.write_string(2, 5, 'Owner', format_header) - worksheet.write_string(2, 6, 'Customer', format_header) - worksheet.write_string(2, 7, 'Organization', format_header) - worksheet.write_string(2, 8, 'Create Channel', format_header) - worksheet.write_string(2, 9, 'Sender', format_header) - worksheet.write_string(2, 10, 'Tags', format_header) - worksheet.write_string(2, 11, 'Created at', format_header) - worksheet.write_string(2, 12, 'Updated at', format_header) - worksheet.write_string(2, 13, 'Closed at', format_header) - - # ObjectManager attributes - header_column = 14 - # needs to be skipped - objects = ObjectManager::Attribute.where(editable: true, - active: true, - to_create: false, - object_lookup_id: ObjectLookup.lookup(name: 'Ticket').id) - .pluck(:name, :display, :data_type, :data_option) - .map { |name, display, data_type, data_option| { name: name, display: display, data_type: data_type, data_option: data_option } } - objects.each do |object| - worksheet.set_column(header_column, 0, 16) - worksheet.write_string(2, header_column, object[:display].capitalize, format_header) - header_column += 1 - end - - row = 2 - result[:ticket_ids].each do |ticket_id| - - ticket = Ticket.lookup(id: ticket_id) - row += 1 - worksheet.write_string(row, 0, ticket.number) - worksheet.write_string(row, 1, ticket.title) - worksheet.write_string(row, 2, ticket.state.name) - worksheet.write_string(row, 3, ticket.priority.name) - worksheet.write_string(row, 4, ticket.group.name) - worksheet.write_string(row, 5, ticket.owner.fullname) - worksheet.write_string(row, 6, ticket.customer.fullname) - worksheet.write_string(row, 7, ticket.try(:organization).try(:name)) - worksheet.write_string(row, 8, ticket.create_article_type.name) - worksheet.write_string(row, 9, ticket.create_article_sender.name) - worksheet.write_string(row, 10, ticket.tag_list.join(',')) - - worksheet.write_date_time(row, 11, time_in_localtime_for_excel(ticket.created_at, params[:timezone]), format_time) - worksheet.write_date_time(row, 12, time_in_localtime_for_excel(ticket.updated_at, params[:timezone]), format_time) - worksheet.write_date_time(row, 13, time_in_localtime_for_excel(ticket.close_at, params[:timezone]), format_time) if ticket.close_at.present? - - # Object Manager attributes - column = 14 - # We already queried ObjectManager::Attributes, so we just use objects - objects.each do |object| - key = object[:name] - case object[:data_type] - when 'boolean', 'select' - value = ticket.send(key.to_sym) - if object[:data_option] && object[:data_option]['options'] && object[:data_option]['options'][ticket.send(key.to_sym)] - value = object[:data_option]['options'][ticket.send(key.to_sym)] - end - worksheet.write_string(row, column, value) - when 'datetime' - worksheet.write_date_time(row, column, time_in_localtime_for_excel(ticket.send(key.to_sym), params[:timezone]), format_time) if ticket.send(key.to_sym).present? - when 'date' - worksheet.write_date_time(row, column, ticket.send(key.to_sym).to_s, format_date) if ticket.send(key.to_sym).present? - when 'integer' - worksheet.write_number(row, column, ticket.send(key.to_sym)) - else - # for text, integer and tree select - worksheet.write_string(row, column, ticket.send(key.to_sym).to_s) - end - column += 1 - end - rescue => e - Rails.logger.error "SKIP: #{e.message}" - - end - - row += 2 - worksheet.write_string(row, 0, "#{Translation.translate(current_user.locale, 'Timezone')}: #{params[:timezone]}", format_footer) - - workbook.close - - # read file again - file = File.new(temp_file, 'r') - contents = file.read - file.close - contents - end - - def time_in_localtime_for_excel(time, timezone) - return if time.blank? - - if timezone.present? - offset = time.in_time_zone(timezone).utc_offset - time += offset - end - local_time = time.utc.iso8601.to_s.sub(/Z$/, '') - local_time.sub(/T/, ' ') - end end diff --git a/app/controllers/time_accountings_controller.rb b/app/controllers/time_accountings_controller.rb index 0860b0edd..f410c593b 100644 --- a/app/controllers/time_accountings_controller.rb +++ b/app/controllers/time_accountings_controller.rb @@ -21,216 +21,79 @@ class TimeAccountingsController < ApplicationController end time_unit[record[0]][:time_unit] += record[1] end - customers = {} - organizations = {} - agents = {} - results = [] - time_unit.each do |ticket_id, local_time_unit| - ticket = Ticket.lookup(id: ticket_id) - next if !ticket - if !customers[ticket.customer_id] - customers[ticket.customer_id] = '-' - if ticket.customer_id - customer_user = User.lookup(id: ticket.customer_id) - if customer_user - customers[ticket.customer_id] = customer_user.fullname - end - end - end - if !organizations[ticket.organization_id] - organizations[ticket.organization_id] = '-' - if ticket.organization_id - organization = Organization.lookup(id: ticket.organization_id) - if organization - organizations[ticket.organization_id] = organization.name - end - end - end - if !agents[local_time_unit[:agent_id]] - agent_user = User.lookup(id: local_time_unit[:agent_id]) - if agent_user - agents[local_time_unit[:agent_id]] = agent_user.fullname - end - end - result = { - ticket: ticket.attributes, - time_unit: local_time_unit[:time_unit], - customer: customers[ticket.customer_id], - organization: organizations[ticket.organization_id], - agent: agents[local_time_unit[:agent_id]], - } - results.push result - end + if !params[:download] + customers = {} + organizations = {} + agents = {} + results = [] + time_unit.each do |ticket_id, local_time_unit| + ticket = Ticket.lookup(id: ticket_id) + next if !ticket - if params[:download] - header = [ - { - name: 'Ticket#', - width: 15, - }, - { - name: 'Title', - width: 30, - }, - { - name: 'Customer', - width: 20, - }, - { - name: 'Organization', - width: 20, - }, - { - name: 'Agent', - width: 20, - }, - { - name: 'Time Units', - width: 10, - }, - { - name: 'Time Units Total', - width: 10, - }, - { - name: 'Created at', - width: 18, - }, - { - name: 'Closed at', - width: 18, - }, - { - name: 'Close Escalation At', - width: 18, - }, - { - name: 'Close In Min', - width: 10, - }, - { - name: 'Close Diff In Min', - width: 10, - }, - { - name: 'First Response At', - width: 18, - }, - { - name: 'First Response Escalation At', - width: 18, - }, - { - name: 'First Response In Min', - width: 10, - }, - { - name: 'First Response Diff In Min', - width: 10, - }, - { - name: 'Update Escalation At', - width: 18, - }, - { - name: 'Update In Min', - width: 10, - }, - { - name: 'Update Diff In Min', - width: 10, - }, - { - name: 'Last Contact At', - width: 18, - }, - { - name: 'Last Contact Agent At', - width: 18, - }, - { - name: 'Last Contact Customer At', - width: 18, - }, - { - name: 'Article Count', - width: 10, - }, - { - name: 'Escalation At', - width: 18, - }, - ] - objects = ObjectManager::Attribute.where(editable: true, - active: true, - to_create: false, - object_lookup_id: ObjectLookup.lookup(name: 'Ticket').id) - .pluck(:name, :display, :data_type, :data_option) - .map { |name, display, data_type, data_option| { name: name, display: display, data_type: data_type, data_option: data_option } } - objects.each do |object| - header.push({ name: object[:display], width: 18 }) - end - - result = [] - results.each do |row| - result_row = [ - row[:ticket]['number'], - row[:ticket]['title'], - row[:customer], - row[:organization], - row[:agent], - row[:time_unit], - row[:ticket]['time_unit'], - row[:ticket]['created_at'], - row[:ticket]['close_at'], - row[:ticket]['close_escalation_at'], - row[:ticket]['close_in_min'], - row[:ticket]['close_diff_in_min'], - row[:ticket]['first_response_at'], - row[:ticket]['first_response_escalation_at'], - row[:ticket]['first_response_in_min'], - row[:ticket]['first_response_diff_in_min'], - row[:ticket]['update_escalation_at'], - row[:ticket]['update_in_min'], - row[:ticket]['update_diff_in_min'], - row[:ticket]['last_contact_at'], - row[:ticket]['last_contact_agent_at'], - row[:ticket]['last_contact_customer_at'], - row[:ticket]['article_count'], - row[:ticket]['escalation_at'], - ] - - # Object Manager attributes - # We already queried ObjectManager::Attributes, so we just use objects - objects.each do |object| - key = object[:name] - case object[:data_type] - when 'boolean', 'select' - value = row[:ticket][key] - if object[:data_option] && object[:data_option]['options'] && object[:data_option]['options'][row[:ticket][key]] - value = object[:data_option]['options'][row[:ticket][key]] + if !customers[ticket.customer_id] + customers[ticket.customer_id] = '-' + if ticket.customer_id + customer_user = User.lookup(id: ticket.customer_id) + if customer_user + customers[ticket.customer_id] = customer_user.fullname end - value.present? ? result_row.push(value) : result_row.push('') - else - # for text, integer and tree select - row[:ticket][key].present? ? result_row.push(row[:ticket][key]) : result_row.push('') end end - - result.push result_row + if !organizations[ticket.organization_id] + organizations[ticket.organization_id] = '-' + if ticket.organization_id + organization = Organization.lookup(id: ticket.organization_id) + if organization + organizations[ticket.organization_id] = organization.name + end + end + end + if !agents[local_time_unit[:agent_id]] + agent_user = User.lookup(id: local_time_unit[:agent_id]) + if agent_user + agents[local_time_unit[:agent_id]] = agent_user.fullname + end + end + result = { + ticket: ticket.attributes, + time_unit: local_time_unit[:time_unit], + customer: customers[ticket.customer_id], + organization: organizations[ticket.organization_id], + agent: agents[local_time_unit[:agent_id]], + } + results.push result end - content = sheet("By Ticket #{year}-#{month}", header, result) - send_data( - content, - filename: "by_ticket-#{year}-#{month}.xls", - type: 'application/vnd.ms-excel', - disposition: 'attachment' - ) + render json: results return end - render json: results + ticket_ids = [] + additional_attributes = [] + additional_attributes_header = [{ display: 'Time Units', name: 'time_unit_for_range', width: 10, data_type: 'float' }] + time_unit.each do |ticket_id, local_time_unit| + ticket_ids.push ticket_id + additional_attribute = { + time_unit_for_range: local_time_unit[:time_unit], + } + additional_attributes.push additional_attribute + end + + excel = ExcelSheet::Ticket.new( + title: "Tickets: #{year}-#{month}", + ticket_ids: ticket_ids, + additional_attributes: additional_attributes, + additional_attributes_header: additional_attributes_header, + timezone: params[:timezone], + locale: current_user.locale, + ) + + send_data( + excel.content, + filename: "by_ticket-#{year}-#{month}.xls", + type: 'application/vnd.ms-excel', + disposition: 'attachment' + ) end def by_customer @@ -287,11 +150,12 @@ class TimeAccountingsController < ApplicationController width: 30, }, { - name: 'Time Units', - width: 10, + name: 'Time Units', + width: 10, + data_type: 'float' } ] - result = [] + records = [] results.each do |row| customer_name = User.find(row[:customer]['id']).fullname organization_name = '' @@ -299,11 +163,18 @@ class TimeAccountingsController < ApplicationController organization_name = row[:organization]['name'] end result_row = [customer_name, organization_name, row[:time_unit]] - result.push result_row + records.push result_row end - content = sheet("By Customer #{year}-#{month}", header, result) + + excel = ExcelSheet.new( + title: "By Customer #{year}-#{month}", + header: header, + records: records, + timezone: params[:timezone], + locale: current_user.locale, + ) send_data( - content, + excel.content, filename: "by_customer-#{year}-#{month}.xls", type: 'application/vnd.ms-excel', disposition: 'attachment' @@ -360,22 +231,30 @@ class TimeAccountingsController < ApplicationController width: 40, }, { - name: 'Time Units', - width: 20, + name: 'Time Units', + width: 20, + data_type: 'float', } ] - result = [] + records = [] results.each do |row| organization_name = '' if row[:organization].present? organization_name = row[:organization]['name'] end result_row = [organization_name, row[:time_unit]] - result.push result_row + records.push result_row end - content = sheet("By Organization #{year}-#{month}", header, result) + + excel = ExcelSheet.new( + title: "By Organization #{year}-#{month}", + header: header, + records: records, + timezone: params[:timezone], + locale: current_user.locale, + ) send_data( - content, + excel.content, filename: "by_organization-#{year}-#{month}.xls", type: 'application/vnd.ms-excel', disposition: 'attachment' @@ -385,90 +264,4 @@ class TimeAccountingsController < ApplicationController render json: results end - - private - - def sheet(title, header, result) - - params[:timezone] ||= Setting.get('timezone_default') - - # Create a new Excel workbook - temp_file = Tempfile.new('time_tracking.xls') - workbook = WriteExcel.new(temp_file) - - # Add a worksheet - worksheet = workbook.add_worksheet - - # Add and define a format - format = workbook.add_format # Add a format - format.set_bold - format.set_size(14) - format.set_color('black') - - format_time = workbook.add_format(num_format: 'yyyy-mm-dd hh:mm:ss') - format_date = workbook.add_format(num_format: 'yyyy-mm-dd') - - format_footer = workbook.add_format - format_footer.set_italic - format_footer.set_color('gray') - format_footer.set_size(8) - - worksheet.set_row(0, 18, header.count) - - # Write a formatted and unformatted string, row and column notation. - worksheet.write_string(0, 0, title, format) - - format_header = workbook.add_format # Add a format - format_header.set_italic - format_header.set_bg_color('gray') - format_header.set_color('white') - count = 0 - header.each do |item| - if item[:width] - worksheet.set_column(count, count, item[:width]) - end - worksheet.write_string(2, count, item[:name], format_header) - count += 1 - end - - row_count = 2 - result.each do |row| - row_count += 1 - row_item_count = 0 - row.each do |item| - if item.acts_like?(:time) - worksheet.write_date_time(row_count, row_item_count, time_in_localtime_for_excel(item, params[:timezone]), format_time) if item.present? - elsif item.acts_like?(:date) - worksheet.write_date_time(row_count, row_item_count, item.to_s, format_date) if item.present? - elsif item.is_a?(Integer) || item.is_a?(Float) - worksheet.write_number(row_count, row_item_count, item) - else - worksheet.write_string(row_count, row_item_count, item.to_s) - end - row_item_count += 1 - end - end - - row_count += 2 - worksheet.write_string(row_count, 0, "#{Translation.translate(current_user.locale, 'Timezone')}: #{params[:timezone]}", format_footer) - - workbook.close - - # read file again - file = File.new(temp_file, 'r') - contents = file.read - file.close - contents - end - - def time_in_localtime_for_excel(time, timezone) - return if time.blank? - - if timezone.present? - offset = time.in_time_zone(timezone).utc_offset - time += offset - end - local_time = time.utc.iso8601.to_s.sub(/Z$/, '') - local_time.sub(/T/, ' ') - end end diff --git a/lib/excel_sheet.rb b/lib/excel_sheet.rb new file mode 100644 index 000000000..6de5b33db --- /dev/null +++ b/lib/excel_sheet.rb @@ -0,0 +1,173 @@ +class ExcelSheet + + def initialize(title:, header:, records:, timezone: nil, locale:) + @title = title + @header = header + @records = records + @timezone = timezone.presence || Setting.get('timezone_default') + @timezone_offset = @timezone.present? ? Time.now.in_time_zone(@timezone).utc_offset : 0 + @locale = locale || 'en-en' + @tempfile = Tempfile.new('excel-export.xls') + @workbook = WriteExcel.new(@tempfile) + @worksheet = @workbook.add_worksheet + @contents = nil + @current_row = 0 + @current_column = 0 + + @lookup_cache = {} + + @format_time = @workbook.add_format(num_format: 'yyyy-mm-dd hh:mm:ss') + @format_date = @workbook.add_format(num_format: 'yyyy-mm-dd') + + @format_headline = @workbook.add_format + @format_headline.set_bold + @format_headline.set_size(14) + @format_headline.set_color('black') + + @format_header = @workbook.add_format + @format_header.set_italic + @format_header.set_bg_color('gray') + @format_header.set_color('white') + + @format_footer = @workbook.add_format + @format_footer.set_italic + @format_footer.set_color('gray') + @format_footer.set_size(8) + end + + def contents + file = File.new(@tempfile, 'r') + contents = file.read + file.close + contents + end + + def content + gen_header + gen_rows + gen_footer + contents + end + + def gen_header + @worksheet.write_string(@current_row, @current_column, @title, @format_headline) + @worksheet.set_row(0, 18) + + @current_row += 2 + @current_column = 0 + @header.each do |header| + if header[:width] + @worksheet.set_column(@current_column, @current_column, header[:width]) + end + @worksheet.write_string(@current_row, @current_column, header[:display] || header[:name], @format_header) + @current_column += 1 + end + end + + def gen_rows + @records.each do |record| + gen_row_by_array(record) + end + end + + def gen_row_by_array(record) + @current_row += 1 + @current_column = 0 + record.each do |item| + begin + if item.acts_like?(:time) + value_convert(item, nil, { data_type: 'datetime' }) + elsif item.acts_like?(:date) + value_convert(item, nil, { data_type: 'datetime' }) + elsif item.is_a?(Integer) || item.is_a?(Float) + value_convert(item, nil, { data_type: 'integer' }) + else + value_convert(item, nil, { data_type: 'string' }) + end + rescue => e + Rails.logger.error e + end + @current_column += 1 + end + end + + def gen_row_by_header(record, additional = {}) + @current_row += 1 + @current_column = 0 + @header.each do |header| + begin + value_convert(record, header[:name], header, additional) + rescue => e + Rails.logger.error e + end + @current_column += 1 + end + end + + def gen_footer + @current_row += 2 + @worksheet.write_string(@current_row, 0, "#{Translation.translate(@locale, 'Timezone')}: #{@timezone}", @format_footer) + @workbook.close + end + + def timestamp_in_localtime(time) + return if time.blank? + + (time + @timezone_offset).utc.strftime('%F %T') # "2019-08-19 16:21:52" + end + + def value_lookup(record, attribute, additional) + value = record[attribute.to_sym] + if attribute[-3, 3] == '_id' + ref = attribute[0, attribute.length - 3] + if record.respond_to?(ref.to_sym) + @lookup_cache[attribute] ||= {} + return @lookup_cache[attribute][value] if @lookup_cache[attribute][value] + + ref_object = record.send(ref.to_sym) + ref_name = value + if ref_object.respond_to?(:fullname) + ref_name = ref_object.fullname + elsif ref_object.respond_to?(:name) + ref_name = ref_object.name + end + @lookup_cache[attribute][value] = ref_name + return ref_name + end + end + value = record.try(attribute) + + # if no value exists, check additional values + if !value && additional && additional[attribute.to_sym] + value = additional[attribute.to_sym] + end + if value.is_a?(Array) + value = value.join(',') + end + value + end + + def value_convert(record, attribute, object, additional = {}) + value = if attribute + value_lookup(record, attribute, additional) + else + record + end + case object[:data_type] + when 'boolean', 'select' + if object[:data_option] && object[:data_option]['options'] && object[:data_option]['options'][value] + value = object[:data_option]['options'][value] + end + @worksheet.write_string(@current_row, @current_column, value) if value.present? + when 'datetime' + @worksheet.write_date_time(@current_row, @current_column, timestamp_in_localtime(value), @format_time) if value.present? + when 'date' + @worksheet.write_date_time(@current_row, @current_column, value.to_s, @format_date) if value.present? + when 'integer' + @worksheet.write_number(@current_row, @current_column, value) if value.present? + else + @worksheet.write_string(@current_row, @current_column, value.to_s) if value.present? + end + end + +end diff --git a/lib/excel_sheet/ticket.rb b/lib/excel_sheet/ticket.rb new file mode 100644 index 000000000..5f9c6a111 --- /dev/null +++ b/lib/excel_sheet/ticket.rb @@ -0,0 +1,103 @@ +class ExcelSheet::Ticket < ExcelSheet + +=begin + + excel = ExcelSheet::Ticket.new( + title: "#{year}-#{month}", + ticket_ids: ticket_ids, + additional_attributes: additional_attributes, + additional_attributes_header: additional_attributes_header, + timezone: params[:timezone], + locale: current_user.locale, + ) + + excel.content + +=end + + def initialize(params) + @ticket_ids = params[:ticket_ids] || [] + @additional_attributes = params[:additional_attributes] || [] + @additional_attributes_header = params[:additional_attributes_header] || [] + + super( + title: params[:title], + header: ticket_header, + records: [], + timezone: params[:timezone], + locale: params[:locale] + ) + end + + def ticket_header + header = [ + { display: '#', name: 'number', width: 18, data_type: 'string' }, + { display: 'Title', name: 'title', width: 34, data_type: 'string' }, + { display: 'State', name: 'state_id', width: 14, data_type: 'string' }, + { display: 'Priority', name: 'priority_id', width: 14, data_type: 'string' }, + { display: 'Group', name: 'group_id', width: 20, data_type: 'string' }, + { display: 'Owner', name: 'owner_id', width: 20, data_type: 'string' }, + { display: 'Customer', name: 'customer_id', width: 20, data_type: 'string' }, + { display: 'Organization', name: 'organization_id', width: 20, data_type: 'string' }, + { display: 'Create Channel', name: 'create_article_type_id', width: 10, data_type: 'string' }, + { display: 'Sender', name: 'create_article_sender_id', width: 14, data_type: 'string' }, + { display: 'Tags', name: 'tag_list', width: 20, data_type: 'string' }, + { display: 'Time Units Total', name: 'time_unit', width: 10, data_type: 'float' }, + ] + + header = header.concat(@additional_attributes_header) if @additional_attributes_header + + # ObjectManager attributes + objects = ObjectManager::Attribute.where(active: true, + to_create: false, + object_lookup_id: ObjectLookup.lookup(name: 'Ticket').id) + .pluck(:name, :display, :data_type, :data_option) + .map { |name, display, data_type, data_option| { name: name, display: display, data_type: data_type, data_option: data_option, width: 20 } } + objects.each do |object| + already_exists = false + header.each do |local_header| + next if local_header[:name] != object[:name] + + already_exists = true + break + end + next if already_exists + + header.push object + end + + header = header.concat([ + { display: 'Created At', name: 'created_at', width: 18, data_type: 'datetime' }, + { display: 'Updated At', name: 'updated_at', width: 18, data_type: 'datetime' }, + { display: 'Closed At', name: 'close_at', width: 18, data_type: 'datetime' }, + { display: 'Close Escalation At', name: 'close_escalation_at', width: 18, data_type: 'datetime' }, + { display: 'Close In Min', name: 'close_in_min', width: 10, data_type: 'integer' }, + { display: 'Close Diff In Min', name: 'close_diff_in_min', width: 10, data_type: 'integer' }, + { display: 'First Response At', name: 'first_response_at', width: 18, data_type: 'datetime' }, + { display: 'First Response Escalation At', name: 'first_response_escalation_at', width: 18, data_type: 'datetime' }, + { display: 'First Response In Min', name: 'first_response_in_min', width: 10, data_type: 'integer' }, + { display: 'First Response Diff In Min', name: 'first_response_diff_in_min', width: 10, data_type: 'integer' }, + { display: 'Update Escalation At', name: 'update_escalation_at', width: 18, data_type: 'datetime' }, + { display: 'Update In Min', name: 'update_in_min', width: 10, data_type: 'integer' }, + { display: 'Update Diff In Min', name: 'update_diff_in_min', width: 10, data_type: 'integer' }, + { display: 'Last Contact At', name: 'last_contact_at', width: 18, data_type: 'datetime' }, + { display: 'Last Contact Agent At', name: 'last_contact_agent_at', width: 18, data_type: 'datetime' }, + { display: 'Last Contact Customer At', name: 'last_contact_customer_at', width: 18, data_type: 'datetime' }, + { display: 'Article Count', name: 'article_count', width: 10, data_type: 'integer' }, + { display: 'Escalation At', name: 'escalation_at', width: 18, data_type: 'datetime' }, + ]) + header + end + + def gen_rows + @ticket_ids.each_with_index do |ticket_id, index| + ticket = ::Ticket.lookup(id: ticket_id) + raise "Can't find Ticket with ID #{ticket_id} for '#{@title}' #{self.class.name} generation" if !ticket + + gen_row_by_header(ticket, @additional_attributes[index]) + rescue => e + Rails.logger.error e + end + end + +end diff --git a/spec/lib/excel_sheet_spec.rb b/spec/lib/excel_sheet_spec.rb new file mode 100644 index 000000000..26e192cb1 --- /dev/null +++ b/spec/lib/excel_sheet_spec.rb @@ -0,0 +1,14 @@ +require 'rails_helper' + +RSpec.describe ExcelSheet do + + describe '.timestamp_in_localtime' do + + let(:document) { described_class.new(title: 'some title', header: [], records: [], timezone: 'Europe/Berlin', locale: 'de-de') } + + it 'does convert UTC timestamp to local system based timestamp' do + expect(document.timestamp_in_localtime(Time.parse('2019-08-08T01:00:05Z').in_time_zone)).to eq('2019-08-08 03:00:05') + end + + end +end diff --git a/spec/requests/report_spec.rb b/spec/requests/report_spec.rb index 9f893812a..ae3532e7a 100644 --- a/spec/requests/report_spec.rb +++ b/spec/requests/report_spec.rb @@ -81,10 +81,6 @@ RSpec.describe 'Report', type: :request, searchindex: true do expect(@response['Content-Type']).to eq('application/vnd.ms-excel') end - it 'does convert UTC timestamp to local system based timestamp' do - expect(ReportsController.new.time_in_localtime_for_excel(Time.parse('2019-08-08T01:00:05Z').in_time_zone, 'Europe/Berlin')).to eq('2019-08-08 03:00:05') - end - it 'does report example - deliver result' do skip('No ES configured') if !SearchIndexBackend.enabled? diff --git a/spec/requests/time_accounting_spec.rb b/spec/requests/time_accounting_spec.rb index 87d69297a..42b21b1eb 100644 --- a/spec/requests/time_accounting_spec.rb +++ b/spec/requests/time_accounting_spec.rb @@ -62,8 +62,5 @@ RSpec.describe 'Time Accounting API endpoints', type: :request do end end - it 'does convert UTC timestamp to local system based timestamp' do - expect(TimeAccountingsController.new.instance_eval { time_in_localtime_for_excel(Time.parse('2019-08-08T01:00:05Z').in_time_zone, 'Europe/Berlin') }).to eq('2019-08-08 03:00:05') - end end end