From 0fb4c1234e2a86eb03e3a82fee639ede756f9081 Mon Sep 17 00:00:00 2001 From: Bola Ahmed Buari Date: Wed, 21 Apr 2021 07:17:25 +0000 Subject: [PATCH] Fixes #3018: Times in ticket history are displayed in UTC --- .../generic_history.coffee | 31 ++++++- .../javascripts/app/lib/app_post/i18n.coffee | 12 +++ public/assets/tests/i18n.js | 3 + spec/system/ticket/history_spec.rb | 81 +++++++++++++++++++ spec/system/user/history_spec.rb | 53 ++++++++++++ 5 files changed, 177 insertions(+), 3 deletions(-) create mode 100644 spec/system/ticket/history_spec.rb create mode 100644 spec/system/user/history_spec.rb diff --git a/app/assets/javascripts/app/controllers/_application_controller/generic_history.coffee b/app/assets/javascripts/app/controllers/_application_controller/generic_history.coffee index 3ee970faa..29f29905a 100644 --- a/app/assets/javascripts/app/controllers/_application_controller/generic_history.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller/generic_history.coffee @@ -83,7 +83,7 @@ class App.GenericHistory extends App.ControllerModal else content = "#{ @T( item.type ) } #{ @T(item.object) } " if item.attribute - content += "#{ @T(item.attribute) }" + content += "#{ @translateItemAttribute(item) }" # convert time stamps if item.object is 'User' && item.attribute is 'last_login' @@ -95,12 +95,12 @@ class App.GenericHistory extends App.ControllerModal if item.value_from if item.value_to content += " #{ @T( 'from' ) }" - content += " '#{ App.Utils.htmlEscape(item.value_from) }'" + content += " '#{ @translateItemValue(item, item.value_from) }'" if item.value_to if item.value_from content += ' →' - content += " '#{ App.Utils.htmlEscape(item.value_to) }'" + content += " '#{ @translateItemValue(item, item.value_to) }'" else if item.value_from content += " → '-'" @@ -110,3 +110,28 @@ class App.GenericHistory extends App.ControllerModal newItems.push newItem newItems + + translateItemValue: ({object, attribute}, value) -> + localAttribute = @objectAttribute(object, attribute) + if localAttribute && localAttribute.tag is 'datetime' + return App.i18n.translateTimestamp(value) + + if /_(time|at)$/.test(attribute) + return App.i18n.translateTimestamp(value) + + if localAttribute && localAttribute.translate is true + return @T(value) + + App.Utils.htmlEscape(value) + + translateItemAttribute: ({object, attribute}) -> + localAttribute = @objectAttribute(object, attribute) + if localAttribute && localAttribute.display + return @T(localAttribute.display) + + @T(attribute) + + objectAttribute: (object, attribute) -> + return if !App[object] + return if !App[object].attributesGet() + App[object].attributesGet()["#{attribute}_id"] || App[object].attributesGet()[attribute] diff --git a/app/assets/javascripts/app/lib/app_post/i18n.coffee b/app/assets/javascripts/app/lib/app_post/i18n.coffee index d3328c086..61dcb8036 100644 --- a/app/assets/javascripts/app/lib/app_post/i18n.coffee +++ b/app/assets/javascripts/app/lib/app_post/i18n.coffee @@ -365,6 +365,11 @@ class _i18nSingleton extends Spine.Module return time if !time @convert(time, offset, @mapTime['timestamp'] || @timestampFormat) + convertUTC: (time) -> + timeArray = time.match(/\d+/g) + [y, m, d, H, M] = timeArray + new Date(Date.UTC(y, m - 1, d, H, M)) + formatNumber: (num, digits) -> while num.toString().length < digits num = '0' + num @@ -374,6 +379,13 @@ class _i18nSingleton extends Spine.Module timeObject = new Date(time) + # On firefox the Date constructor does not recongise date format that + # ends with UTC, instead it returns a NaN (Invalid Date Format) this + # block serves as polyfill to support time format that ends UTC in firefox + if isNaN(timeObject) + # works for only time string with this format: 2021-02-08 09:13:20 UTC + timeObject = @convertUTC(time) if time.match(/ UTC/) + # add timezone diff, needed for unit tests if offset timeObject = new Date(timeObject.getTime() + (timeObject.getTimezoneOffset() * 60000)) diff --git a/public/assets/tests/i18n.js b/public/assets/tests/i18n.js index 6fc3c10a4..0b994235f 100644 --- a/public/assets/tests/i18n.js +++ b/public/assets/tests/i18n.js @@ -114,6 +114,9 @@ test('i18n .detectBrowserLocale', function() { var timestamp = App.i18n.translateTimestamp('2012-11-06T21:07:24Z', offset); equal(timestamp, '06.11.2012 21:07', 'de-de - timestamp translated correctly') + var timestamp = App.i18n.translateTimestamp('2021-02-08 09:13:20 UTC', offset); + equal(timestamp, '08.02.2021 09:13', 'de-de - timestamp translated correctly with UTC format') + timestamp = App.i18n.translateTimestamp('', offset); equal(timestamp, '', 'de-de - timestamp translated correctly') diff --git a/spec/system/ticket/history_spec.rb b/spec/system/ticket/history_spec.rb new file mode 100644 index 000000000..a0267555b --- /dev/null +++ b/spec/system/ticket/history_spec.rb @@ -0,0 +1,81 @@ +require 'rails_helper' + +RSpec.describe 'Ticket history', type: :system, authenticated_as: true, time_zone: 'Europe/London' do + let(:group) { Group.find_by(name: 'Users') } + let(:ticket) { create(:ticket, group: group) } + let!(:session_user) { User.find_by(login: 'master@example.com') } + + before do + freeze_time + + travel_to DateTime.parse('2021-01-22 13:40:00 UTC') + current_time = Time.current + ticket.update(title: 'New Ticket Title') + ticket_article = create(:ticket_article, ticket: ticket, internal: true) + ticket.update! state: Ticket::State.lookup(name: 'open') + ticket.update! last_owner_update_at: current_time + ticket.update! priority: Ticket::Priority.lookup(name: '1 low') + ticket.update! last_contact_at: current_time + ticket.update! last_contact_customer_at: current_time + ticket.update! last_contact_agent_at: current_time + ticket_article.update! internal: false + + travel_to DateTime.parse('2021-04-06 23:30:00 UTC') + current_time = Time.current + ticket.update! state: Ticket::State.lookup(name: 'pending close') + ticket.update! priority: Ticket::Priority.lookup(name: '3 high') + ticket_article.update! internal: true + ticket.update! last_contact_at: current_time + ticket.update! last_contact_customer_at: current_time + ticket.update! last_contact_agent_at: current_time + ticket.update! pending_time: current_time + ticket.update! first_response_escalation_at: current_time + + travel_back + + session_user.preferences[:locale] = 'de-de' + session_user.save! + + refresh + + visit "#ticket/zoom/#{ticket.id}" + find('[data-tab="ticket"] .js-actions').click + click('[data-type="ticket-history"]') + end + + it "translates timestamp when attribute's tag is datetime" do + expect(page).to have_css('li', text: /22.01.2021 13:40/) + end + + it 'does not include time with UTC format' do + expect(page).to have_no_text(/ UTC/) + end + + it 'translates value when attribute is state' do + expect(page).to have_css('li', text: /Ticket Status von 'neu'/) + end + + it 'translates value when attribute is priority' do + expect(page).to have_css('li', text: /Ticket Priorität von '1 niedrig'/) + end + + it 'translates value when attribute is internal' do + expect(page).to have_css('li', text: /Artikel intern von 'true'/) + end + + it 'translates last_contact_at display attribute' do + expect(page).to have_css('li', text: /Ticket Letzter Kontakt von '22.01.2021 13:40' → '07.04.2021 00:30'/) + end + + it 'translates last_contact_customer_at display attribute' do + expect(page).to have_css('li', text: /Ticket Letzter Kontakt \(Kunde\) von '22.01.2021 13:40' → '07.04.2021 00:30'/) + end + + it 'translates last_contact_agent_at display attribute' do + expect(page).to have_css('li', text: /Ticket Letzter Kontakt \(Agent\) von '22.01.2021 13:40' → '07.04.2021 00:30'/) + end + + it 'translates pending_time display attribute' do + expect(page).to have_css('li', text: /Ticket Warten bis '07.04.2021 00:30'/) + end +end diff --git a/spec/system/user/history_spec.rb b/spec/system/user/history_spec.rb new file mode 100644 index 000000000..4aeb90074 --- /dev/null +++ b/spec/system/user/history_spec.rb @@ -0,0 +1,53 @@ +require 'rails_helper' + +RSpec.describe 'Ticket history', type: :system, authenticated_as: true, time_zone: 'Europe/London' do + let(:group) { Group.find_by(name: 'Users') } + let(:customer) { create(:customer) } + let!(:session_user) { User.find_by(login: 'master@example.com') } + + before do + freeze_time + + travel_to DateTime.parse('2021-01-22 13:40:00 UTC') + current_time = Time.current + customer.update! firstname: 'Customer' + customer.update! email: 'test@example.com' + customer.update! country: 'Germany' + customer.update! out_of_office_start_at: current_time + customer.update! last_login: current_time + + travel_to DateTime.parse('2021-04-06 23:30:00 UTC') + current_time = Time.current + customer.update! lastname: 'Example' + customer.update! mobile: '5757473827' + customer.update! out_of_office_end_at: current_time + customer.update! last_login: current_time + + travel_back + + session_user.preferences[:locale] = 'de-de' + session_user.save! + + refresh + + visit "#user/profile/#{customer.id}" + find('#userAction').click + click('[data-type="history"]') + end + + it "translates timestamp when attribute's tag is datetime" do + expect(page).to have_css('li', text: /'22.01.2021 00:00'/) + end + + it 'does not include time with UTC format' do + expect(page).to have_no_text(/ UTC/) + end + + it 'translates out_of_office_start_at value to time stamp' do + expect(page).to have_css('li', text: /Benutzer out_of_office_start_at '22.01.2021 00:00'/) + end + + it 'translates out_of_office_end_at value to time stamp' do + expect(page).to have_css('li', text: /Benutzer out_of_office_end_at '06.04.2021 01:00'/) + end +end