Fixes #1589 - No localised time displayed in trigger (autoreply/slack/...) notifications.

This commit is contained in:
Martin Edenhofer 2019-02-10 12:01:38 +01:00
parent 487f36a5a5
commit 63214c9327
22 changed files with 696 additions and 217 deletions

View file

@ -0,0 +1,35 @@
class App.SettingsAreaItemDefaultTimezone extends App.SettingsAreaItem
result: {}
render: =>
@fetchTimezones()
localRender: (data) =>
options = {}
for timezone, offset of data.timezones
if !offset.toString().match(/(\+|\-)/)
offset = "+#{offset}"
options[timezone] = "#{timezone} (GMT#{offset})"
configure_attributes = [
{ name: 'timezone_default', display: '', tag: 'searchable_select', null: false, class: 'input', options: options, default: @setting.state_current.value },
]
@html App.view(@template)(
setting: @setting
)
new App.ControllerForm(
el: @el.find('.form-item')
model: { configure_attributes: configure_attributes, className: '' }
autofocus: false
)
fetchTimezones: =>
@ajax(
id: 'calendar_timezones'
type: 'GET'
url: "#{@apiPath}/calendars/timezones"
success: (data) =>
@result = data
@localRender(data)
)

View file

@ -340,6 +340,7 @@ class Base extends App.WizardFullScreen
@params = @formParam(e.target)
@params.logo = @logoPreview.attr('src')
@params.locale_default = App.i18n.detectBrowserLocale()
@params.timezone_default = App.i18n.detectBrowserTimezone()
store = (logoResizeDataUrl) =>
@params.logo_resize = logoResizeDataUrl

View file

@ -0,0 +1,36 @@
class DefaultTimezone extends App.Controller
constructor: ->
super
check = =>
timezone = App.i18n.detectBrowserTimezone()
return if !timezone
# check system timezone_default
if _.isEmpty(@Config('timezone_default')) && @permissionCheck('admin.system')
App.Setting.fetchFull(
=> @updateSetting(timezone)
force: false
)
# prepare user based timezone
# check current user timezone
#preferences = App.Session.get('preferences')
#return if !preferences
#return if !_.isEmpty(preferences.timezone)
#@ajax(
# id: "i18n-set-user-timezone"
# type: 'PUT'
# url: "#{App.Config.get('api_path')}/users/preferences"
# data: JSON.stringify(timezone: timezone)
# processData: true
#)
App.Event.bind('auth:login', (session) =>
@delay(check, 8500, 'default_timezone')
)
updateSetting: (timezone) ->
App.Setting.set('timezone_default', timezone)
App.Config.set('default_timezone', DefaultTimezone, 'Widgets')

View file

@ -103,6 +103,17 @@ class App.i18n
window.navigator.userLanguage || window.navigator.language || 'en-us'
@detectBrowserTimezone: ->
return if !window.Intl
return if !window.Intl.DateTimeFormat
DateTimeFormat = Intl.DateTimeFormat()
return if !DateTimeFormat
return if !DateTimeFormat.resolvedOptions
resolvedOptions = DateTimeFormat.resolvedOptions()
return if !resolvedOptions
return if !resolvedOptions.timeZone
resolvedOptions.timeZone
class _i18nSingleton extends Spine.Module
@include App.LogInclude

View file

@ -1,7 +1,8 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
class CalendarsController < ApplicationController
prepend_before_action { authentication_check(permission: 'admin.calendar') }
prepend_before_action -> { authentication_check(permission: 'admin.calendar') }, only: %i[init index show create update destroy]
prepend_before_action -> { authentication_check(permission: 'admin') }, only: %i[timezones]
def init
assets = {}
@ -41,4 +42,10 @@ class CalendarsController < ApplicationController
model_destroy_render(Calendar, params)
end
def timezones
render json: {
timezones: Calendar.timezones
}
end
end

View file

@ -148,6 +148,11 @@ curl http://localhost/api/v1/getting_started -v -u #{login}:#{password}
settings[:locale_default] = params[:locale_default]
end
# add timezone_default
if params[:timezone_default].present?
settings[:timezone_default] = params[:timezone_default]
end
if messages.present?
render json: {
result: 'invalid',

View file

@ -1543,10 +1543,13 @@ result
end
objects = build_notification_template_objects(article)
body = NotificationFactory::Renderer.new(objects, 'en-en', value['body'], false)
.render
.html2text
.tr(' ', ' ') # convert non-breaking space to simple space
body = NotificationFactory::Renderer.new(
objects: objects,
locale: 'en-en',
timezone: Setting.get('timezone_default'),
template: value['body'],
escape: false
).render.html2text.tr(' ', ' ') # convert non-breaking space to simple space
# attributes content_type is not needed for SMS
article = Ticket::Article.create(

View file

@ -242,7 +242,7 @@ class Transaction::Notification
# only show allowed attributes
attribute_list = ObjectManager::Attribute.by_object_as_hash('Ticket', user)
#puts "AL #{attribute_list.inspect}"
user_related_changes = {}
@item[:changes].each do |key, value|

View file

@ -84,7 +84,8 @@ class Transaction::Slack
result = NotificationFactory::Slack.template(
template: template,
locale: user[:preferences][:locale],
locale: user[:preferences][:locale] || Setting.get('locale_default'),
timezone: user[:preferences][:timezone] || Setting.get('timezone_default'),
objects: {
ticket: ticket,
article: article,

View file

@ -215,6 +215,90 @@ translate strings in ruby context, e. g. for notifications
=begin
translate timestampes in ruby context, e. g. for notifications
translated = Translation.timestamp('de-de', 'Europe/Berlin', '2018-10-10T10:00:00Z0')
or
translated = Translation.timestamp('de-de', 'Europe/Berlin', Time.zone.parse('2018-10-10T10:00:00Z0'))
=end
def self.timestamp(locale, timezone, timestamp)
if timestamp.class == String
begin
timestamp_parsed = Time.zone.parse(timestamp)
return timestamp.to_s if !timestamp_parsed
timestamp = timestamp_parsed
rescue
return timestamp.to_s
end
end
record = Translation.where(locale: locale, source: 'timestamp', format: 'time').pluck(:target).first
return timestamp.to_s if !record
begin
timestamp = timestamp.in_time_zone(timezone)
rescue
return timestamp.to_s
end
record.sub!('dd', format('%02d', timestamp.day))
record.sub!('d', timestamp.day.to_s)
record.sub!('mm', format('%02d', timestamp.month))
record.sub!('m', timestamp.month.to_s)
record.sub!('yyyy', timestamp.year.to_s)
record.sub!('yy', timestamp.year.to_s.last(2))
record.sub!('SS', format('%02d', timestamp.sec.to_s))
record.sub!('MM', format('%02d', timestamp.min.to_s))
record.sub!('HH', format('%02d', timestamp.hour.to_s))
"#{record} (#{timezone})"
end
=begin
translate date in ruby context, e. g. for notifications
translated = Translation.date('de-de', '2018-10-10')
or
translated = Translation.date('de-de', Date.parse('2018-10-10'))
=end
def self.date(locale, date)
if date.class == String
begin
date_parsed = Date.parse(date)
return date.to_s if !date_parsed
date = date_parsed
rescue
return date.to_s
end
end
return date.to_s if date.class != Date
record = Translation.where(locale: locale, source: 'date', format: 'time').pluck(:target).first
return date.to_s if !record
record.sub!('dd', format('%02d', date.day))
record.sub!('d', date.day.to_s)
record.sub!('mm', format('%02d', date.month))
record.sub!('m', date.month.to_s)
record.sub!('yyyy', date.year.to_s)
record.sub!('yy', date.year.to_s.last(2))
record
end
=begin
load translations from local
all:

View file

@ -2,11 +2,12 @@ Zammad::Application.routes.draw do
api_path = Rails.configuration.api_path
# calendars
match api_path + '/calendars_init', to: 'calendars#init', via: :get
match api_path + '/calendars', to: 'calendars#index', via: :get
match api_path + '/calendars/:id', to: 'calendars#show', via: :get
match api_path + '/calendars', to: 'calendars#create', via: :post
match api_path + '/calendars/:id', to: 'calendars#update', via: :put
match api_path + '/calendars/:id', to: 'calendars#destroy', via: :delete
match api_path + '/calendars_init', to: 'calendars#init', via: :get
match api_path + '/calendars/timezones', to: 'calendars#timezones', via: :get
match api_path + '/calendars', to: 'calendars#index', via: :get
match api_path + '/calendars/:id', to: 'calendars#show', via: :get
match api_path + '/calendars', to: 'calendars#create', via: :post
match api_path + '/calendars/:id', to: 'calendars#update', via: :put
match api_path + '/calendars/:id', to: 'calendars#destroy', via: :delete
end

View file

@ -0,0 +1,27 @@
class SettingTimezoneDefault < ActiveRecord::Migration[5.1]
def up
# return if it's a new setup
return if !Setting.find_by(name: 'system_init_done')
Setting.create_if_not_exists(
title: 'Timezone',
name: 'timezone_default',
area: 'System::Branding',
description: 'Defines the system default timezone.',
options: {
form: [
{
name: 'timezone_default',
}
],
},
state: '',
preferences: {
prio: 9,
controller: 'SettingsAreaItemDefaultTimezone',
permission: ['admin.system'],
},
frontend: true
)
end
end

View file

@ -176,6 +176,26 @@ Setting.create_if_not_exists(
},
frontend: true
)
Setting.create_if_not_exists(
title: 'Timezone',
name: 'timezone_default',
area: 'System::Branding',
description: 'Defines the system default timezone.',
options: {
form: [
{
name: 'timezone_default',
}
],
},
state: '',
preferences: {
prio: 9,
controller: 'SettingsAreaItemDefaultTimezone',
permission: ['admin.system'],
},
frontend: true
)
Setting.create_or_update(
title: 'Pretty Date',
name: 'pretty_date_format',

View file

@ -228,6 +228,7 @@ retunes
result = NotificationFactory::Mailer.template(
template: 'password_reset',
locale: 'en-us',
timezone: 'America/Santiago',
objects: {
recipient: User.find(2),
},
@ -236,6 +237,7 @@ retunes
result = NotificationFactory::Mailer.template(
templateInline: "Invitation to \#{config.product_name} at \#{config.fqdn}",
locale: 'en-us',
timezone: 'America/Santiago',
objects: {
recipient: User.find(2),
},
@ -247,6 +249,7 @@ only raw subject/body
result = NotificationFactory::Mailer.template(
template: 'password_reset',
locale: 'en-us',
timezone: 'America/Santiago',
objects: {
recipient: User.find(2),
},
@ -266,7 +269,13 @@ returns
def self.template(data)
if data[:templateInline]
return NotificationFactory::Renderer.new(data[:objects], data[:locale], data[:templateInline], data[:quote]).render
return NotificationFactory::Renderer.new(
objects: data[:objects],
locale: data[:locale],
timezone: data[:timezone],
template: data[:templateInline],
escape: data[:quote]
).render
end
template = NotificationFactory.template_read(
@ -276,8 +285,19 @@ returns
type: 'mailer',
)
message_subject = NotificationFactory::Renderer.new(data[:objects], data[:locale], template[:subject], false).render
message_body = NotificationFactory::Renderer.new(data[:objects], data[:locale], template[:body]).render
message_subject = NotificationFactory::Renderer.new(
objects: data[:objects],
locale: data[:locale],
timezone: data[:timezone],
template: template[:subject],
escape: false
).render
message_body = NotificationFactory::Renderer.new(
objects: data[:objects],
locale: data[:locale],
timezone: data[:timezone],
template: template[:body]
).render
if !data[:raw]
application_template = NotificationFactory.application_template_read(
@ -286,7 +306,12 @@ returns
)
data[:objects][:message] = message_body
data[:objects][:standalone] = data[:standalone]
message_body = NotificationFactory::Renderer.new(data[:objects], data[:locale], application_template).render
message_body = NotificationFactory::Renderer.new(
objects: data[:objects],
locale: data[:locale],
timezone: data[:timezone],
template: application_template
).render
end
{
subject: message_subject,

View file

@ -5,27 +5,30 @@ class NotificationFactory::Renderer
examples how to use
message_subject = NotificationFactory::Renderer.new(
{
objects: {
ticket: Ticket.first,
},
'de-de',
'some template <b>#{ticket.title}</b> {config.fqdn}',
false
locale: 'de-de',
timezone: 'America/Port-au-Prince',
template: 'some template <b>#{ticket.title}</b> {config.fqdn}',
escape: false
).render
message_body = NotificationFactory::Renderer.new(
{
objects: {
ticket: Ticket.first,
},
'de-de',
'some template <b>#{ticket.title}</b> #{config.fqdn}',
locale: 'de-de',
timezone: 'America/Port-au-Prince',
template: 'some template <b>#{ticket.title}</b> #{config.fqdn}',
).render
=end
def initialize(objects, locale, template, escape = true)
def initialize(objects:, locale: nil, timezone: nil, template:, escape: true)
@objects = objects
@locale = locale || Setting.get('locale_default') || 'en-us'
@timezone = timezone || Setting.get('timezone_default')
@template = NotificationFactory::Template.new(template, escape)
@escape = escape
end
@ -141,7 +144,8 @@ examples how to use
else
value
end
escaping(placeholder, escape)
escaping(convert_to_timezone(placeholder), escape)
end
# c - config
@ -159,15 +163,22 @@ examples how to use
end
# h - htmlEscape
# h('fqdn', htmlEscape)
def h(key)
return key if !key
# h(htmlEscape)
def h(value)
return value if !value
CGI.escapeHTML(key.to_s)
CGI.escapeHTML(convert_to_timezone(value).to_s)
end
private
def convert_to_timezone(value)
return Translation.timestamp(@locale, @timezone, value) if value.class == ActiveSupport::TimeWithZone
return Translation.date(@locale, value) if value.class == Date
value
end
def escaping(key, escape)
return key if escape == false
return key if escape.nil? && !@escape

View file

@ -5,6 +5,7 @@ class NotificationFactory::Slack
result = NotificationFactory::Slack.template(
template: 'ticket_update',
locale: 'en-us',
timezone: 'Europe/Berlin',
objects: {
recipient: User.find(2),
ticket: Ticket.find(1)
@ -23,7 +24,12 @@ returns
def self.template(data)
if data[:templateInline]
return NotificationFactory::Renderer.new(data[:objects], data[:locale], data[:templateInline]).render
return NotificationFactory::Renderer.new(
objects: data[:objects],
locale: data[:locale],
timezone: data[:timezone],
template: data[:templateInline]
).render
end
template = NotificationFactory.template_read(
@ -33,8 +39,20 @@ returns
type: 'slack',
)
message_subject = NotificationFactory::Renderer.new(data[:objects], data[:locale], template[:subject], false).render
message_body = NotificationFactory::Renderer.new(data[:objects], data[:locale], template[:body], false).render
message_subject = NotificationFactory::Renderer.new(
objects: data[:objects],
locale: data[:locale],
timezone: data[:timezone],
template: template[:subject],
escape: false
).render
message_body = NotificationFactory::Renderer.new(
objects: data[:objects],
locale: data[:locale],
timezone: data[:timezone],
template: template[:body],
escape: false
).render
if !data[:raw]
application_template = NotificationFactory.application_template_read(
@ -43,7 +61,13 @@ returns
)
data[:objects][:message] = message_body
data[:objects][:standalone] = data[:standalone]
message_body = NotificationFactory::Renderer.new(data[:objects], data[:locale], application_template, false).render
message_body = NotificationFactory::Renderer.new(
objects: data[:objects],
locale: data[:locale],
timezone: data[:timezone],
template: application_template,
escape: false
).render
end
{
subject: message_subject.strip!,

View file

@ -5,6 +5,6 @@ FactoryBot.define do
template ''
escape true
initialize_with { new(objects, locale, template, escape) }
initialize_with { new(objects: objects, locale: locale, template: template, escape: escape) }
end
end

View file

@ -7,7 +7,7 @@ RSpec.describe Translation do
Translation.sync('de-de')
end
context 'default translations' do
context 'default string translations' do
it 'en with existing word' do
expect(Translation.translate('en', 'New')).to eq('New')
@ -31,6 +31,82 @@ RSpec.describe Translation do
end
context 'default timestamp translations' do
it 'de-de with array' do
expect(Translation.timestamp('de-de', 'Europe/Berlin', ['some value'])).to eq('["some value"]')
end
it 'not_existing with timestamp as string' do
expect(Translation.timestamp('not_existing', 'Europe/Berlin', '2018-10-10T10:00:00Z0')).to eq('2018-10-10 10:00:00 UTC')
end
it 'not_existing with time object' do
expect(Translation.timestamp('not_existing', 'Europe/Berlin', Time.zone.parse('2018-10-10T10:00:00Z0'))).to eq('2018-10-10 10:00:00 UTC')
end
it 'not_existing with invalid timestamp string' do
expect(Translation.timestamp('not_existing', 'Europe/Berlin', 'something')).to eq('something')
end
it 'en-us with invalid time zone' do
expect(Translation.timestamp('en-us', 'Europe/Berlin', '2018-10-10T10:00:00Z0')).to eq('10/10/2018 12:00 (Europe/Berlin)')
end
it 'en-us with timestamp as string' do
expect(Translation.timestamp('en-us', 'Europe/Berlin', '2018-10-10T10:00:00Z0')).to eq('10/10/2018 12:00 (Europe/Berlin)')
end
it 'en-us with time object' do
expect(Translation.timestamp('en-us', 'Europe/Berlin', Time.zone.parse('2018-10-10T10:00:00Z0'))).to eq('10/10/2018 12:00 (Europe/Berlin)')
end
it 'de-de with timestamp as string' do
expect(Translation.timestamp('de-de', 'Europe/Berlin', '2018-10-10T10:00:00Z0')).to eq('10.10.2018 12:00 (Europe/Berlin)')
end
it 'de-de with time object' do
expect(Translation.timestamp('de-de', 'Europe/Berlin', Time.zone.parse('2018-10-10T10:00:00Z0'))).to eq('10.10.2018 12:00 (Europe/Berlin)')
end
end
context 'default date translations' do
it 'de-de with array' do
expect(Translation.date('de-de', ['some value'])).to eq('["some value"]')
end
it 'not_existing with date as string' do
expect(Translation.date('not_existing', '2018-10-10')).to eq('2018-10-10')
end
it 'not_existing with date object' do
expect(Translation.date('not_existing', Date.parse('2018-10-10'))).to eq('2018-10-10')
end
it 'not_existing with invalid data as string' do
expect(Translation.date('not_existing', 'something')).to eq('something')
end
it 'en-us with date as string' do
expect(Translation.date('en-us', '2018-10-10')).to eq('10/10/2018')
end
it 'en-us with date object' do
expect(Translation.date('en-us', Date.parse('2018-10-10'))).to eq('10/10/2018')
end
it 'de-de with date as string' do
expect(Translation.date('de-de', '2018-10-10')).to eq('10.10.2018')
end
it 'de-de with date object' do
expect(Translation.date('de-de', Date.parse('2018-10-10'))).to eq('10.10.2018')
end
end
context 'remote_translation_need_update? tests' do
it 'translation is still the same' do

View file

@ -20,6 +20,12 @@ RSpec.describe 'Calendars', type: :request do
expect(json_response).to be_a_kind_of(Hash)
expect(json_response['error']).to eq('authentication failed')
get '/api/v1/calendars/timezones', as: :json
expect(response).to have_http_status(401)
expect(json_response).to be_a_kind_of(Hash)
expect(json_response['error']).to eq('authentication failed')
end
it 'does calendar index with admin' do
@ -58,6 +64,13 @@ RSpec.describe 'Calendars', type: :request do
expect(json_response['timezones']['America/Sitka']).to be_between(-9, -8)
expect(json_response['timezones']['Europe/Berlin']).to be_between(1, 2)
expect(json_response['assets']).to be_truthy
# timezones
get '/api/v1/calendars/timezones', as: :json
expect(response).to have_http_status(200)
expect(json_response).to be_a_kind_of(Hash)
expect(json_response['timezones']).to be_a_kind_of(Hash)
expect(json_response['timezones']['America/New_York']).to be_truthy
end
end

View file

@ -39,149 +39,164 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase
template = "\#{ticket.title}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML(ticket.title), result)
template = "\#{ticket.created_at}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(ticket.created_at.to_s, result)
assert_equal('11/12/2016 13:00 (Europe/Berlin)', result)
template = "\#{ticket.created_by.firstname}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal('CurrentUser&lt;b&gt;xxx&lt;/b&gt;', result)
template = "\#{ticket.updated_at}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(ticket.updated_at.to_s, result)
assert_equal('11/12/2016 15:00 (Europe/Berlin)', result)
template = "\#{ticket.updated_by.firstname}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal('CurrentUser&lt;b&gt;xxx&lt;/b&gt;', result)
template = "\#{ticket.owner.firstname}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal('Owner&lt;b&gt;xxx&lt;/b&gt;', result)
template = "\#{ticket. title}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML(ticket.title), result)
template = "\#{ticket.\n title}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML(ticket.title), result)
template = "\#{ticket.\t title}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML(ticket.title), result)
template = "\#{ticket.\t\n title\t}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML(ticket.title), result)
template = "\#{ticket.\" title\t}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML(ticket.title), result)
template = "\#{<a href=\"/test123\">ticket.\" title</a>}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML(ticket.title), result)
template = "some test<br>\#{article.body}"
result = described_class.new(
{
objects: {
article: article_html1,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal('some test<br>&gt; test hello<br>&gt; some new line<br>', result)
result = described_class.new(
{
objects: {
article: article_plain1,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal('some test<br>&gt; test &lt;b&gt;hello&lt;/b&gt;<br>&gt; some new line<br>', result)
result = described_class.new(
{
objects: {
article: article_plain2,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal('some test<br>&gt; test &lt;b&gt;hello&lt;/b&gt;<br>&gt; some new line<br>', result)
@ -192,11 +207,12 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase
setting = 'fqdn'
template = "\#{config.#{setting}}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(Setting.get(setting), result)
@ -204,11 +220,12 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase
setting2 = 'product_name'
template = "some \#{config.#{setting1}} and \#{config.#{setting2}}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal("some #{Setting.get(setting1)} and #{Setting.get(setting2)}", result)
@ -216,11 +233,12 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase
setting2 = 'product_name'
template = "some \#{ config.#{setting1}} and \#{\tconfig.#{setting2}}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal("some #{Setting.get(setting1)} and #{Setting.get(setting2)}", result)
end
@ -230,41 +248,45 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase
#template = "<%= t 'new' %>"
template = "\#{t('new')}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'de-de',
template,
locale: 'de-de',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal('neu', result)
template = "some text \#{t('new')} and \#{t('open')}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'de-de',
template,
locale: 'de-de',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal('some text neu and offen', result)
template = "some text \#{t('new') } and \#{ t('open')}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'de-de',
template,
locale: 'de-de',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal('some text neu and offen', result)
template = "some text \#{\nt('new') } and \#{ t('open')\t}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'de-de',
template,
locale: 'de-de',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal('some text neu and offen', result)
@ -275,11 +297,12 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase
template = "\#{t(ticket.state.name)}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'de-de',
template,
locale: 'de-de',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal('neu', result)
@ -289,111 +312,122 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase
template = "\#{}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('#{no such object}'), result)
template = "\#{notexsiting.notexsiting}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('#{notexsiting / no such object}'), result)
template = "\#{ticket.notexsiting}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('#{ticket.notexsiting / no such method}'), result)
template = "\#{ticket.}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('#{ticket. / no such method}'), result)
template = "\#{ticket.title.notexsiting}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('#{ticket.title.notexsiting / no such method}'), result)
template = "\#{ticket.notexsiting.notexsiting}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('#{ticket.notexsiting / no such method}'), result)
template = "\#{notexsiting}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('#{notexsiting / no such object}'), result)
template = "\#{notexsiting.}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('#{notexsiting / no such object}'), result)
template = "\#{string}"
result = described_class.new(
{
objects: {
string: 'some string',
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('some string'), result)
template = "\#{fixum}"
result = described_class.new(
{
objects: {
fixum: 123,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('123'), result)
template = "\#{float}"
result = described_class.new(
{
objects: {
float: 123.99,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('123.99'), result)
@ -403,181 +437,199 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase
template = "\#{ticket.title `echo 1`}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('#{ticket.title`echo1` / not allowed}'), result)
template = "\#{ticket.destroy}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('#{ticket.destroy / not allowed}'), result)
template = "\#{ticket.save}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('#{ticket.save / not allowed}'), result)
template = "\#{ticket.update}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('#{ticket.update / not allowed}'), result)
template = "\#{ticket.create}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('#{ticket.create / not allowed}'), result)
template = "\#{ticket.delete}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('#{ticket.delete / not allowed}'), result)
template = "\#{ticket.remove}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('#{ticket.remove / not allowed}'), result)
template = "\#{ticket.drop}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('#{ticket.drop / not allowed}'), result)
template = "\#{ticket.create}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('#{ticket.create / not allowed}'), result)
template = "\#{ticket.new}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('#{ticket.new / not allowed}'), result)
template = "\#{ticket.update_att}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('#{ticket.update_att / not allowed}'), result)
template = "\#{ticket.all}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('#{ticket.all / not allowed}'), result)
template = "\#{ticket.find}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('#{ticket.find / not allowed}'), result)
template = "\#{ticket.where}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('#{ticket.where / not allowed}'), result)
template = "\#{ticket. destroy}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('#{ticket.destroy / not allowed}'), result)
template = "\#{ticket.\n destroy}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML("\#{ticket.destroy / not allowed}"), result)
template = "\#{ticket.\t destroy}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML("\#{ticket.destroy / not allowed}"), result)
template = "\#{ticket.\r destroy}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML("\#{ticket.destroy / not allowed}"), result)
@ -587,51 +639,56 @@ class NotificationFactoryRendererTest < ActiveSupport::TestCase
template = "\#{ticket.title.first(3)}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('<b>'), result)
template = "\#{ticket.title.last(4)}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML('</b>'), result)
template = "\#{ticket.title.slice(3, 4)}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal(CGI.escapeHTML("\#{ticket.title.slice(3,4) / invalid parameter: 3,4}"), result)
template = "\#{ticket.title.first('some invalid parameter')}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal("\#{ticket.title.first(someinvalidparameter) / invalid parameter: someinvalidparameter}", result)
template = "\#{ticket.title.chomp(`cat /etc/passwd`)}"
result = described_class.new(
{
objects: {
ticket: ticket,
},
'en-us',
template,
locale: 'en-us',
timezone: 'Europe/Berlin',
template: template,
).render
assert_equal("\#{ticket.title.chomp(`cat/etc/passwd`) / not allowed}", result)
end

View file

@ -66,7 +66,8 @@ class NotificationFactorySlackTemplateTest < ActiveSupport::TestCase
changes = {}
result = NotificationFactory::Slack.template(
template: 'ticket_create',
locale: 'es-us',
locale: 'en-us',
timezone: 'Europe/Berlin',
objects: {
ticket: ticket,
article: article,
@ -97,12 +98,14 @@ class NotificationFactorySlackTemplateTest < ActiveSupport::TestCase
created_by_id: 1,
)
changes = {
state: %w[aaa bbb],
group: %w[xxx yyy],
state: %w[aaa bbb],
group: %w[xxx yyy],
pending_time: [Time.zone.parse('2019-04-01T10:00:00Z0'), Time.zone.parse('2019-04-01T23:00:00Z0')],
}
result = NotificationFactory::Slack.template(
template: 'ticket_update',
locale: 'es-us',
locale: 'en-us',
timezone: 'Europe/Berlin',
objects: {
ticket: ticket,
article: article,
@ -111,14 +114,31 @@ class NotificationFactorySlackTemplateTest < ActiveSupport::TestCase
changes: changes,
},
)
assert_match('# Welcome to Zammad!', result[:subject])
assert_match('User<b>xxx</b>', result[:body])
assert_match('state: aaa -> bbb', result[:body])
assert_match('group: xxx -> yyy', result[:body])
assert_match('pending_time: 04/01/2019 12:00 (Europe/Berlin) -> 04/02/2019 01:00 (Europe/Berlin)', result[:body])
assert_no_match('Dein', result[:body])
assert_no_match('longname', result[:body])
assert_match('Current User', result[:body])
# en notification
ticket.escalation_at = Time.zone.parse('2019-04-01T10:00:00Z')
result = NotificationFactory::Slack.template(
template: 'ticket_escalation',
locale: 'en-us',
timezone: 'Europe/Berlin',
objects: {
ticket: ticket,
article: article,
recipient: agent1,
}
)
assert_match('# Welcome to Zammad!', result[:subject])
assert_match('is escalated since "04/01/2019 12:00 (Europe/Berlin)"!', result[:body])
end
end

View file

@ -2,6 +2,7 @@ require 'test_helper'
class TicketNotificationTest < ActiveSupport::TestCase
setup do
Setting.set('timezone_default', 'Europe/Berlin')
Trigger.create_or_update(
name: 'auto reply - new ticket',
condition: {
@ -78,7 +79,8 @@ class TicketNotificationTest < ActiveSupport::TestCase
roles: roles,
groups: groups,
preferences: {
locale: 'en-ca',
locale: 'en-us',
timezone: 'America/St_Lucia',
},
updated_by_id: 1,
created_by_id: 1,
@ -1152,6 +1154,7 @@ class TicketNotificationTest < ActiveSupport::TestCase
# en notification
result = NotificationFactory::Mailer.template(
locale: @agent2.preferences[:locale],
timezone: @agent2.preferences[:timezone],
template: 'ticket_update',
objects: {
ticket: ticket1,
@ -1165,7 +1168,7 @@ class TicketNotificationTest < ActiveSupport::TestCase
assert_match(/1 low/, result[:body])
assert_match(/2 normal/, result[:body])
assert_match(/Pending till/, result[:body])
assert_match(/2015-01-11 23:33:47 UTC/, result[:body])
assert_match('01/11/2015 19:33 (America/St_Lucia)', result[:body])
assert_match(/update/, result[:body])
assert_no_match(/pending_till/, result[:body])
assert_no_match(/i18n/, result[:body])
@ -1181,9 +1184,10 @@ class TicketNotificationTest < ActiveSupport::TestCase
assert_not(human_changes['pending_time'])
assert_not(human_changes['pending_till'])
# de notification
# de & Europe/Berlin notification
result = NotificationFactory::Mailer.template(
locale: @agent1.preferences[:locale],
timezone: @agent1.preferences[:timezone],
template: 'ticket_update',
objects: {
ticket: ticket1,
@ -1198,7 +1202,7 @@ class TicketNotificationTest < ActiveSupport::TestCase
assert_match(/1 niedrig/, result[:body])
assert_match(/2 normal/, result[:body])
assert_match(/Warten/, result[:body])
assert_match(/2015-01-11 23:33:47 UTC/, result[:body])
assert_match('12.01.2015 00:33 (Europe/Berlin)', result[:body])
assert_match(/aktualis/, result[:body])
assert_no_match(/pending_till/, result[:body])
assert_no_match(/i18n/, result[:body])
@ -1229,6 +1233,7 @@ class TicketNotificationTest < ActiveSupport::TestCase
# de notification
result = NotificationFactory::Mailer.template(
locale: @agent1.preferences[:locale],
timezone: @agent1.preferences[:timezone],
template: 'ticket_update',
objects: {
ticket: ticket1,
@ -1254,6 +1259,7 @@ class TicketNotificationTest < ActiveSupport::TestCase
# en notification
result = NotificationFactory::Mailer.template(
locale: @agent2.preferences[:locale],
timezone: @agent2.preferences[:timezone],
template: 'ticket_update',
objects: {
ticket: ticket1,
@ -1276,6 +1282,22 @@ class TicketNotificationTest < ActiveSupport::TestCase
assert_no_match(/pending_till/, result[:body])
assert_no_match(/i18n/, result[:body])
# en notification
ticket1.escalation_at = Time.zone.parse('2019-04-01T10:00:00Z')
result = NotificationFactory::Mailer.template(
locale: @agent2.preferences[:locale],
timezone: @agent2.preferences[:timezone],
template: 'ticket_escalation',
objects: {
ticket: ticket1,
article: article,
recipient: @agent2,
}
)
assert_match('Ticket is escalated (some notification template test 1 Bobs\'s resumé', result[:subject])
assert_match('is escalated since "04/01/2019 06:00 (America/St_Lucia)"!', result[:body])
end
end