From 3066e49eb192006d5b5f1842517fa3c913eb3513 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Mon, 2 Feb 2015 14:01:19 +0100 Subject: [PATCH 01/42] Fix for using id in loading seeds.rb - unit tests will follow asap. --- app/models/application_model.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/application_model.rb b/app/models/application_model.rb index 03c68a8f3..9594295bd 100644 --- a/app/models/application_model.rb +++ b/app/models/application_model.rb @@ -41,10 +41,10 @@ class ApplicationModel < ActiveRecord::Base attr_accessor :history_changes_last_done - @@import_class_list = ['Ticket', 'Ticket::Article', 'History', 'Ticket::State', 'Ticket::Priority', 'Group', 'User' ] + @@import_class_list = ['Ticket', 'Ticket::Article', 'History', 'Ticket::State', 'Ticket::StateType', 'Ticket::Priority', 'Group', 'User', 'Role' ] def check_attributes_protected - if Setting.get('import_mode') && @@import_class_list.include?( self.class.to_s ) + if ( !Setting.get('system_init_done') || Setting.get('import_mode')) && @@import_class_list.include?( self.class.to_s ) # do noting, use id as it is else self[:id] = nil From 53d13b20f383751e4cc0c1a254b3755a21f0ecf5 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Mon, 2 Feb 2015 14:44:37 +0100 Subject: [PATCH 02/42] Added unit tests for "id in seeds.rb failed" issue. --- db/seeds.rb | 14 ++++---- test/unit/db_auto_increment_test.rb | 56 +++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 test/unit/db_auto_increment_test.rb diff --git a/db/seeds.rb b/db/seeds.rb index 8ceb17f73..7f8e21d1f 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1335,13 +1335,13 @@ Link::Object.create_if_not_exists( :name => 'Question/Answer' ) Link::Object.create_if_not_exists( :name => 'Idea' ) Link::Object.create_if_not_exists( :name => 'Bug' ) -Ticket::StateType.create_if_not_exists( :id => 1, :name => 'new', :updated_by_id => 1 ) -Ticket::StateType.create_if_not_exists( :id => 2, :name => 'open', :updated_by_id => 1 ) -Ticket::StateType.create_if_not_exists( :id => 3, :name => 'pending reminder', :updated_by_id => 1 ) -Ticket::StateType.create_if_not_exists( :id => 4, :name => 'pending action', :updated_by_id => 1 ) -Ticket::StateType.create_if_not_exists( :id => 5, :name => 'closed', :updated_by_id => 1 ) -Ticket::StateType.create_if_not_exists( :id => 6, :name => 'merged', :updated_by_id => 1 ) -Ticket::StateType.create_if_not_exists( :id => 7, :name => 'removed', :updated_by_id => 1 ) +Ticket::StateType.create_if_not_exists( :id => 1, :name => 'new' ) +Ticket::StateType.create_if_not_exists( :id => 2, :name => 'open' ) +Ticket::StateType.create_if_not_exists( :id => 3, :name => 'pending reminder' ) +Ticket::StateType.create_if_not_exists( :id => 4, :name => 'pending action' ) +Ticket::StateType.create_if_not_exists( :id => 5, :name => 'closed' ) +Ticket::StateType.create_if_not_exists( :id => 6, :name => 'merged' ) +Ticket::StateType.create_if_not_exists( :id => 7, :name => 'removed' ) Ticket::State.create_if_not_exists( :id => 1, :name => 'new', :state_type_id => Ticket::StateType.where(:name => 'new').first.id ) Ticket::State.create_if_not_exists( :id => 2, :name => 'open', :state_type_id => Ticket::StateType.where(:name => 'open').first.id ) diff --git a/test/unit/db_auto_increment_test.rb b/test/unit/db_auto_increment_test.rb new file mode 100644 index 000000000..a83a1a6c3 --- /dev/null +++ b/test/unit/db_auto_increment_test.rb @@ -0,0 +1,56 @@ +# encoding: utf-8 +require 'test_helper' + +class DbAutoIncrementTest < ActiveSupport::TestCase + + test 'id overwrite' do + + setting_backup = Setting.get('system_init_done') + + Setting.set('system_init_done', false) + + Ticket::StateType.create_if_not_exists( :id => 200, :name => 'unit test 1', :updated_by_id => 1, :created_by_id => 1 ) + state_type = Ticket::StateType.where( :name => 'unit test 1' ).first + assert_equal( Ticket::StateType.to_s, state_type.class.to_s ) + assert_equal( 'unit test 1', state_type.name ) + + Ticket::StateType.create_if_not_exists( :id => 200, :name => 'unit test 1 _ should not be created', :updated_by_id => 1, :created_by_id => 1 ) + state_type = Ticket::StateType.where( :id => 200 ).first + assert_equal( Ticket::StateType.to_s, state_type.class.to_s ) + assert_equal( 'unit test 1', state_type.name ) + + Ticket::StateType.create_or_update( :id => 200, :name => 'unit test 1 _ should be updated', :updated_by_id => 1, :created_by_id => 1 ) + state_type = Ticket::StateType.where( :name => 'unit test 1 _ should be updated' ).first + assert_equal( Ticket::StateType.to_s, state_type.class.to_s ) + assert_equal( 'unit test 1 _ should be updated', state_type.name ) + + state_type = Ticket::StateType.where( :id => 200 ).first + assert_equal( Ticket::StateType.to_s, state_type.class.to_s ) + assert_equal( 'unit test 1 _ should be updated', state_type.name ) + + + Ticket::State.create_if_not_exists( :id => 210, :name => 'unit test 1', :state_type_id => Ticket::StateType.where(:name => 'unit test 1 _ should be updated').first.id, :updated_by_id => 1, :created_by_id => 1 ) + state = Ticket::State.where( :name => 'unit test 1' ).first + assert_equal( Ticket::State.to_s, state.class.to_s ) + assert_equal( 'unit test 1', state.name ) + + Ticket::State.create_if_not_exists( :id => 210, :name => 'unit test 1 _ should not be created', :state_type_id => Ticket::StateType.where(:name => 'unit test 1 _ should be updated').first.id, :updated_by_id => 1, :created_by_id => 1 ) + state = Ticket::State.where( :id => 210 ).first + assert_equal( Ticket::State.to_s, state.class.to_s ) + assert_equal( 'unit test 1', state.name ) + + Ticket::State.create_or_update( :id => 210, :name => 'unit test 1 _ should be updated', :state_type_id => Ticket::StateType.where(:name => 'unit test 1 _ should be updated').first.id, :updated_by_id => 1, :created_by_id => 1 ) + state = Ticket::State.where( :name => 'unit test 1 _ should be updated' ).first + assert_equal( Ticket::State.to_s, state.class.to_s ) + assert_equal( 'unit test 1 _ should be updated', state.name ) + + state = Ticket::State.where( :id => 210 ).first + assert_equal( Ticket::State.to_s, state.class.to_s ) + assert_equal( 'unit test 1 _ should be updated', state.name ) + + + + Setting.set('system_init_done', setting_backup) + + end +end \ No newline at end of file From e2d2890c6d50eca47ab7f1546c99df1cceb22ddc Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Wed, 4 Feb 2015 08:53:47 +0100 Subject: [PATCH 03/42] Improved network error handling if ticket is already open. --- .../app/controllers/ticket_zoom.js.coffee | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee b/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee index 5b9871a43..d6ce60104 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee @@ -124,17 +124,27 @@ class App.TicketZoom extends App.Controller @doNotLog = 1 @recentView( 'Ticket', ticket_id ) - error: (xhr, status, error) => + error: (xhr) => + statusText = xhr.statusText + status = xhr.status + detail = xhr.responseText + #console.log('error', status, statusText) - # do not close window if request is aborted - return if status is 'abort' + # ignore if request is aborted + if statusText is 'abort' + return + + # if ticket is already loaded, ignore status "0" - network issues e. g. temp. not connection + if @ticketUpdatedAtLastCall && status is 0 + console.log('network issues e. g. temp. not connection', status, statusText, detail) + return # show error message - if xhr.status is 401 || error is 'Unauthorized' + if status is 401 || statusText is 'Unauthorized' @taskHead = '» ' + App.i18n.translateInline('Unauthorized') + ' «' @taskIconClass = 'error' @html App.view('generic/error/unauthorized')( objectName: 'Ticket' ) - else if xhr.status is 404 || error is 'Not Found' + else if status is 404 || statusText is 'Not Found' @taskHead = '» ' + App.i18n.translateInline('Not Found') + ' «' @taskIconClass = 'error' @html App.view('generic/error/not_found')( objectName: 'Ticket' ) @@ -142,9 +152,7 @@ class App.TicketZoom extends App.Controller @taskHead = '» ' + App.i18n.translateInline('Error') + ' «' @taskIconClass = 'error' - status = xhr.status - detail = xhr.responseText - if !status && !detail + if !detail detail = 'General communication error, maybe internet is not available!' @html App.view('generic/error/generic')( status: status From 84f433f87c0979fe56d71b07a6679745f58b764f Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Wed, 4 Feb 2015 08:54:03 +0100 Subject: [PATCH 04/42] Moved to ff 32. --- app/assets/javascripts/app/lib/app_post/browser.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/app/lib/app_post/browser.coffee b/app/assets/javascripts/app/lib/app_post/browser.coffee index 1afa1b2c2..c6d0f9a06 100644 --- a/app/assets/javascripts/app/lib/app_post/browser.coffee +++ b/app/assets/javascripts/app/lib/app_post/browser.coffee @@ -30,7 +30,7 @@ class App.Browser # define min. required browser version map = Chrome2: 37 - Firefox: 28 + Firefox: 32 Explorer: 10 Safari: 6 Opera: 22 From d5102c87b1657866e64469b646213c8f7294ccc6 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Wed, 4 Feb 2015 08:54:32 +0100 Subject: [PATCH 05/42] Updated doc. --- app/models/history.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/history.rb b/app/models/history.rb index 68787a90b..cfa0a24c3 100644 --- a/app/models/history.rb +++ b/app/models/history.rb @@ -27,7 +27,7 @@ add a new history entry for an object :id_to => 3, :id_from => 2, :value_from => 'open', - :value_to => 'pending', + :value_to => 'pending reminder', :created_by_id => 1, :created_at => '2013-06-04 10:00:00', :updated_at => '2013-06-04 10:00:00' From 0843ea52bebfec70201df592e90cb9ac0600ad1d Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Thu, 5 Feb 2015 00:34:32 +0100 Subject: [PATCH 06/42] Fixed now() selector (was with - timezone). --- .../_application_controller_form.js.coffee | 12 +-- public/assets/tests/form-validation.js | 80 +++++++++++++++++++ 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee b/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee index 0f6752308..b7702955b 100644 --- a/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee @@ -320,12 +320,13 @@ class App.ControllerForm extends App.Controller number if !reset && (year isnt '' && month isnt '' && day isnt '') time = new Date( Date.parse( "#{year}-#{format(month)}-#{format(day)}T00:00:00Z" ) ) + time.setMinutes( time.getMinutes() + diff + time.getTimezoneOffset() ) else time = new Date() - #time.setMinutes( time.getMinutes() + diff + time.getTimezoneOffset() ) - item.closest('.form-group').find("[name=\"{date}#{name}___day\"]").val( time.getUTCDate() ) - item.closest('.form-group').find("[name=\"{date}#{name}___month\"]").val( time.getUTCMonth()+1 ) - item.closest('.form-group').find("[name=\"{date}#{name}___year\"]").val( time.getUTCFullYear() ) + time.setMinutes( time.getMinutes() + diff ) + item.closest('.form-group').find("[name=\"{date}#{name}___day\"]").val( time.getDate() ) + item.closest('.form-group').find("[name=\"{date}#{name}___month\"]").val( time.getMonth()+1 ) + item.closest('.form-group').find("[name=\"{date}#{name}___year\"]").val( time.getFullYear() ) item.find('.js-today').bind('click', (e) -> e.preventDefault() @@ -462,9 +463,10 @@ class App.ControllerForm extends App.Controller number if !reset && (year isnt '' && month isnt '' && day isnt '' && hour isnt '' && day isnt '') time = new Date( Date.parse( "#{year}-#{format(month)}-#{format(day)}T#{format(hour)}:#{format(minute)}:00Z" ) ) + time.setMinutes( time.getMinutes() + diff + time.getTimezoneOffset() ) else time = new Date() - time.setMinutes( time.getMinutes() + diff + time.getTimezoneOffset() ) + time.setMinutes( time.getMinutes() + diff ) #console.log('T', time, time.getHours(), time.getMinutes()) item.closest('.form-group').find("[name=\"{datetime}#{name}___day\"]").val( time.getDate() ) item.closest('.form-group').find("[name=\"{datetime}#{name}___month\"]").val( time.getMonth()+1 ) diff --git a/public/assets/tests/form-validation.js b/public/assets/tests/form-validation.js index 6c3b4bb7f..8a9dd6884 100644 --- a/public/assets/tests/form-validation.js +++ b/public/assets/tests/form-validation.js @@ -248,4 +248,84 @@ test( "date validation check", function() { equal( el.find('[data-name="date1"]').closest('.form-group').hasClass('has-error'), true, 'check date1 has-error') equal( el.find('[data-name="date1"]').closest('.form-group').find('.help-inline').text(), '', 'check date1 error message') +}); + +test( "datetime selector check", function() { + + $('#forms').append('

datetime selector check

') + + var el = $('#form4') + var defaults = {} + var form = new App.ControllerForm({ + el: el, + model: { + configure_attributes: [ + { name: 'datetime1', display: 'Datetime1', tag: 'datetime', null: false, default: defaults['datetime1'] }, + ], + }, + params: defaults, + }); + + // check params + params = App.ControllerForm.params( el ) + test_params = { + datetime1: undefined, + } + deepEqual( params, test_params, 'params check' ) + + el.find('.js-today').click() + + // check params + timeStamp = new Date() + currentTime = timeStamp.toISOString() + currentTime = currentTime.replace(/(\d\d\.\d\d\dZ)$/, '00.000Z') + params = App.ControllerForm.params( el ) + test_params = { + datetime1: currentTime, + } + deepEqual( params, test_params, 'params check' ) + +}); + +test( "date selector check", function() { + + $('#forms').append('

date selector check

') + + var el = $('#form5') + var defaults = {} + var form = new App.ControllerForm({ + el: el, + model: { + configure_attributes: [ + { name: 'date1', display: 'Datet1', tag: 'date', null: false, default: defaults['date1'] }, + ], + }, + params: defaults, + }); + + // check params + params = App.ControllerForm.params( el ) + test_params = { + date1: undefined, + } + deepEqual( params, test_params, 'params check' ) + + el.find('.js-today').click() + + // check params + format = function (number) { + if ( parseInt(number) < 10 ) { + number = '0' + number.toString() + } + return number + } + + timeStamp = new Date() + currentTime = timeStamp.getFullYear() + '-' + format(timeStamp.getMonth()+1) + '-' + format(timeStamp.getDate()) + params = App.ControllerForm.params( el ) + test_params = { + date1: currentTime, + } + deepEqual( params, test_params, 'params check' ) + }); \ No newline at end of file From 4928732bbaeef786b8bf2080a542e43baf615b66 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Thu, 5 Feb 2015 10:40:25 +0100 Subject: [PATCH 07/42] Added new useless with very long non default breakable filename. --- .../app/views/layout_ref/communication_overview.jst.eco | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/javascripts/app/views/layout_ref/communication_overview.jst.eco b/app/assets/javascripts/app/views/layout_ref/communication_overview.jst.eco index 4428daa32..6e06ac127 100644 --- a/app/assets/javascripts/app/views/layout_ref/communication_overview.jst.eco +++ b/app/assets/javascripts/app/views/layout_ref/communication_overview.jst.eco @@ -243,6 +243,10 @@ Oliver
something with very long line which is slonger as the container arount it if you can see it I wish you a very happy day.txt
17.1 kb
+
+
something-withverylonglinewhichisslongerasthecontainerarountitifyoucanseeitIwishyouaveryhappyday.txt
+
17.1 kb
+
From d46196b69edef2a1dab41d24004e2bbff30b44fd Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Thu, 5 Feb 2015 14:02:21 +0100 Subject: [PATCH 08/42] Moved back to FF31, current ESR version. --- app/assets/javascripts/app/lib/app_post/browser.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/app/lib/app_post/browser.coffee b/app/assets/javascripts/app/lib/app_post/browser.coffee index c6d0f9a06..d9dc3304b 100644 --- a/app/assets/javascripts/app/lib/app_post/browser.coffee +++ b/app/assets/javascripts/app/lib/app_post/browser.coffee @@ -30,7 +30,7 @@ class App.Browser # define min. required browser version map = Chrome2: 37 - Firefox: 32 + Firefox: 31 Explorer: 10 Safari: 6 Opera: 22 From 412f868d427feffd8fc77ca80c1f7c945b2b2218 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 6 Feb 2015 09:59:08 +0100 Subject: [PATCH 09/42] Improved rendering of profile pages. Added touch to customer and organization if ticket has changed. --- .../organization_profile.js.coffee | 58 ++++-- .../app/controllers/user_profile.js.coffee | 55 +++-- .../app/views/organization_profile.jst.eco | 50 ----- .../views/organization_profile/index.jst.eco | 6 + .../views/organization_profile/object.jst.eco | 39 ++++ .../app/views/user_profile.jst.eco | 37 ---- .../app/views/user_profile/index.jst.eco | 6 + .../app/views/user_profile/object.jst.eco | 27 +++ .../observer/ticket/ref_object_touch.rb | 30 +++ config/application.rb | 1 + .../agent_organization_profile_test.rb | 188 ++++++++++++++++++ test/browser/agent_user_profile_test.rb | 188 ++++++++++++++++++ test/unit/ticket_ref_object_update_test.rb | 92 +++++++++ 13 files changed, 655 insertions(+), 122 deletions(-) delete mode 100644 app/assets/javascripts/app/views/organization_profile.jst.eco create mode 100644 app/assets/javascripts/app/views/organization_profile/index.jst.eco create mode 100644 app/assets/javascripts/app/views/organization_profile/object.jst.eco delete mode 100644 app/assets/javascripts/app/views/user_profile.jst.eco create mode 100644 app/assets/javascripts/app/views/user_profile/index.jst.eco create mode 100644 app/assets/javascripts/app/views/user_profile/object.jst.eco create mode 100644 app/models/observer/ticket/ref_object_touch.rb create mode 100644 test/browser/agent_organization_profile_test.rb create mode 100644 test/browser/agent_user_profile_test.rb create mode 100644 test/unit/ticket_ref_object_update_test.rb diff --git a/app/assets/javascripts/app/controllers/organization_profile.js.coffee b/app/assets/javascripts/app/controllers/organization_profile.js.coffee index 53a45b807..f455e21ba 100644 --- a/app/assets/javascripts/app/controllers/organization_profile.js.coffee +++ b/app/assets/javascripts/app/controllers/organization_profile.js.coffee @@ -1,7 +1,4 @@ class App.OrganizationProfile extends App.Controller - events: - 'focusout [contenteditable]': 'update' - constructor: (params) -> super @@ -12,11 +9,8 @@ class App.OrganizationProfile extends App.Controller @navupdate '#' - # subscribe and reload data / fetch new data if triggered - @subscribeId = App.Organization.full( @organization_id, @render, false, true ) - - release: => - App.Organization.unsubscribe(@subscribeId) + # fetch new data if needed + App.Organization.full( @organization_id, @render ) meta: => meta = @@ -48,6 +42,39 @@ class App.OrganizationProfile extends App.Controller @doNotLog = 1 @recentView( 'Organization', @organization_id ) + @html App.view('organization_profile/index')( + organization: organization + ) + + new Object( + el: @$('.js-object-container') + organization: organization + ) + + new App.TicketStats( + el: @$('.js-ticket-stats') + organization: organization + ) + + new App.UpdateTastbar( + genericObject: organization + ) + +class Object extends App.Controller + events: + 'focusout [contenteditable]': 'update' + + constructor: (params) -> + super + + # subscribe and reload data / fetch new data if triggered + @subscribeId = App.Organization.full( @organization.id, @render, false, true ) + + release: => + App.Organization.unsubscribe(@subscribeId) + + render: (organization) => + # get display data organizationData = [] for attributeName, attributeConfig of App.Organization.attributesGet('view') @@ -65,7 +92,7 @@ class App.OrganizationProfile extends App.Controller if name isnt 'name' organizationData.push attributeConfig - @html App.view('organization_profile')( + @html App.view('organization_profile/object')( organization: organization organizationData: organizationData ) @@ -76,15 +103,6 @@ class App.OrganizationProfile extends App.Controller maxlength: 250 }) - new App.TicketStats( - el: @$('.js-ticket-stats') - organization: organization - ) - - new App.UpdateTastbar( - genericObject: organization - ) - # start action controller showHistory = => new App.OrganizationHistory( organization_id: organization.id ) @@ -97,6 +115,7 @@ class App.OrganizationProfile extends App.Controller title: 'Organizations' object: 'Organization' objects: 'Organizations' + container: @el.closest('.content') ) actions = [ @@ -120,13 +139,14 @@ class App.OrganizationProfile extends App.Controller update: (e) => name = $(e.target).attr('data-name') value = $(e.target).html() - org = App.Organization.find( @organization_id ) + org = App.Organization.find( @organization.id ) if org[name] isnt value data = {} data[name] = value org.updateAttributes( data ) @log 'notice', 'update', name, value, org + class Router extends App.ControllerPermanent constructor: (params) -> super diff --git a/app/assets/javascripts/app/controllers/user_profile.js.coffee b/app/assets/javascripts/app/controllers/user_profile.js.coffee index 28b1898b3..3db91782a 100644 --- a/app/assets/javascripts/app/controllers/user_profile.js.coffee +++ b/app/assets/javascripts/app/controllers/user_profile.js.coffee @@ -1,7 +1,4 @@ class App.UserProfile extends App.Controller - events: - 'focusout [contenteditable]': 'update' - constructor: (params) -> super @@ -12,8 +9,8 @@ class App.UserProfile extends App.Controller @navupdate '#' - # subscribe and reload data / fetch new data if triggered - @subscribeId = App.User.full( @user_id, @render, false, true ) + # fetch new data if needed + @subscribeId = App.User.full( @user_id, @render ) release: => App.User.unsubscribe(@subscribeId) @@ -47,6 +44,40 @@ class App.UserProfile extends App.Controller @doNotLog = 1 @recentView( 'User', @user_id ) + @html App.view('user_profile/index')( + user: user + ) + + new Object( + el: @$('.js-object-container') + user: user + ) + + new App.TicketStats( + el: @$('.js-ticket-stats') + user: user + ) + + new App.UpdateTastbar( + genericObject: user + ) + + +class Object extends App.Controller + events: + 'focusout [contenteditable]': 'update' + + constructor: (params) -> + super + + # subscribe and reload data / fetch new data if triggered + @subscribeId = App.User.full( @user.id, @render, false, true ) + + release: => + App.User.unsubscribe(@subscribeId) + + render: (user) => + # get display data userData = [] for attributeName, attributeConfig of App.User.attributesGet('view') @@ -64,7 +95,7 @@ class App.UserProfile extends App.Controller if name isnt 'firstname' && name isnt 'lastname' && name isnt 'organization' userData.push attributeConfig - @html App.view('user_profile')( + @html App.view('user_profile/object')( user: user userData: userData ) @@ -75,15 +106,6 @@ class App.UserProfile extends App.Controller maxlength: 250 }) - new App.TicketStats( - el: @$('.js-ticket-stats') - user: user - ) - - new App.UpdateTastbar( - genericObject: user - ) - # start action controller showHistory = => new App.UserHistory( user_id: user.id ) @@ -97,6 +119,7 @@ class App.UserProfile extends App.Controller title: 'Users' object: 'User' objects: 'Users' + container: @el.closest('.content') ) actions = [ @@ -120,7 +143,7 @@ class App.UserProfile extends App.Controller update: (e) => name = $(e.target).attr('data-name') value = $(e.target).html() - user = App.User.find( @user_id ) + user = App.User.find( @user.id ) if user[name] isnt value data = {} data[name] = value diff --git a/app/assets/javascripts/app/views/organization_profile.jst.eco b/app/assets/javascripts/app/views/organization_profile.jst.eco deleted file mode 100644 index bec092e1b..000000000 --- a/app/assets/javascripts/app/views/organization_profile.jst.eco +++ /dev/null @@ -1,50 +0,0 @@ -
- -
-
-
-
-
-
-

<%= @organization.displayName() %>

-
-
-
- <% for row in @organizationData: %> - <% if @organization[row.name]: %> - <% if row.tag is 'richtext': %> -
- -
<%- @organization[row.name] %>
-
- <% else: %> -
- - <%- @L( @P( @organization[row.name] ) ) %> -
- <% end %> - <% end %> - <% end %> -
-
- - <% if @organization.members: %> -
- -
- - <% for user in @organization.members: %> -
- <%- user.avatar("40") %> - <%= user.displayName() %> -
- <% end %> - -
-
- <% end %> - -
- -
-
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/organization_profile/index.jst.eco b/app/assets/javascripts/app/views/organization_profile/index.jst.eco new file mode 100644 index 000000000..97263221b --- /dev/null +++ b/app/assets/javascripts/app/views/organization_profile/index.jst.eco @@ -0,0 +1,6 @@ +
+
+
+
+
+
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/organization_profile/object.jst.eco b/app/assets/javascripts/app/views/organization_profile/object.jst.eco new file mode 100644 index 000000000..c8eded5a5 --- /dev/null +++ b/app/assets/javascripts/app/views/organization_profile/object.jst.eco @@ -0,0 +1,39 @@ +
+
+
+
+
+

<%= @organization.displayName() %>

+
+
+
+ <% for row in @organizationData: %> + <% if row.tag is 'richtext': %> +
+ +
<%- @organization[row.name] %>
+
+ <% else: %> + <% if @organization[row.name]: %> +
+ + <%- @L( @P( @organization[row.name] ) ) %> +
+ <% end %> + <% end %> + <% end %> +
+
+<% if @organization.members: %> +
+ +
+ <% for user in @organization.members: %> +
+ <%- user.avatar("40") %> + <%= user.displayName() %> +
+ <% end %> +
+
+<% end %> \ No newline at end of file diff --git a/app/assets/javascripts/app/views/user_profile.jst.eco b/app/assets/javascripts/app/views/user_profile.jst.eco deleted file mode 100644 index 839f1622d..000000000 --- a/app/assets/javascripts/app/views/user_profile.jst.eco +++ /dev/null @@ -1,37 +0,0 @@ -
- -
-
-
- <%- @user.avatar("80") %> -

<%= @user.displayName() %>

- <% if @user.organization: %> - - <% end %> -
- -
-
- <% for row in @userData: %> - <% if @user[row.name]: %> - <% if row.tag is 'richtext': %> -
- -
<%- @user[row.name] %>
-
- <% else: %> -
- - <%- @L( @P( @user[row.name] ) ) %> -
- <% end %> - <% end %> - <% end %> -
-
- -
- -
- -
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/user_profile/index.jst.eco b/app/assets/javascripts/app/views/user_profile/index.jst.eco new file mode 100644 index 000000000..97263221b --- /dev/null +++ b/app/assets/javascripts/app/views/user_profile/index.jst.eco @@ -0,0 +1,6 @@ +
+
+
+
+
+
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/user_profile/object.jst.eco b/app/assets/javascripts/app/views/user_profile/object.jst.eco new file mode 100644 index 000000000..6768083e2 --- /dev/null +++ b/app/assets/javascripts/app/views/user_profile/object.jst.eco @@ -0,0 +1,27 @@ +
+
+ <%- @user.avatar("80") %> +

<%= @user.displayName() %>

+ <% if @user.organization: %> + + <% end %> +
+
+
+ <% for row in @userData: %> + <% if @user[row.name]: %> + <% if row.tag is 'richtext': %> +
+ +
<%- @user[row.name] %>
+
+ <% else: %> +
+ + <%- @L( @P( @user[row.name] ) ) %> +
+ <% end %> + <% end %> + <% end %> +
+
\ No newline at end of file diff --git a/app/models/observer/ticket/ref_object_touch.rb b/app/models/observer/ticket/ref_object_touch.rb new file mode 100644 index 000000000..9f0861882 --- /dev/null +++ b/app/models/observer/ticket/ref_object_touch.rb @@ -0,0 +1,30 @@ +# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/ + +class Observer::Ticket::RefObjectTouch < ActiveRecord::Observer + observe 'ticket' + + def after_create(record) + ref_object_touch(record) + end + def after_update(record) + ref_object_touch(record) + end + def after_touch(record) + ref_object_touch(record) + end + def after_destroy(record) + ref_object_touch(record) + end + + def ref_object_touch(record) + + # return if we run import mode + return if Setting.get('import_mode') + if record.customer + record.customer.touch + end + if record.organization + record.organization.touch + end + end +end \ No newline at end of file diff --git a/config/application.rb b/config/application.rb index 5b3c3a15b..a30b02dc5 100644 --- a/config/application.rb +++ b/config/application.rb @@ -40,6 +40,7 @@ module Zammad 'observer::_ticket::_notification', 'observer::_ticket::_reset_new_state', 'observer::_ticket::_escalation_calculation', + 'observer::_ticket::_ref_object_touch', 'observer::_tag::_ticket_history', 'observer::_user::_geo' diff --git a/test/browser/agent_organization_profile_test.rb b/test/browser/agent_organization_profile_test.rb new file mode 100644 index 000000000..3142e52b2 --- /dev/null +++ b/test/browser/agent_organization_profile_test.rb @@ -0,0 +1,188 @@ +# encoding: utf-8 +require 'browser_test_helper' + +class AgentOrganizationProfileTest < TestCase + def test_search_and_edit_verify_in_second + message = 'comment 1 ' + rand(99999999999999999).to_s + tests = [ + { + :name => 'start', + :instance1 => browser_instance, + :instance2 => browser_instance, + :instance1_username => 'master@example.com', + :instance1_password => 'test', + :instance2_username => 'agent1@example.com', + :instance2_password => 'test', + :url => browser_url, + :action => [ + { + :where => :instance1, + :execute => 'close_all_tasks', + }, + { + :where => :instance2, + :execute => 'close_all_tasks', + }, + { + :where => :instance1, + :execute => 'search_organization', + :term => 'Zammad', + }, + { + :where => :instance2, + :execute => 'search_organization', + :term => 'Zammad', + }, + + # update note + { + :where => :instance1, + :execute => 'set', + :css => '.active [data-name="note"]', + :value => message, + }, + { + :where => :instance1, + :execute => 'click', + :css => '.active .profile', + }, + { + :where => :instance1, + :execute => 'wait', + :value => 3, + }, + + # verify + { + :where => :instance2, + :execute => 'match', + :css => '.active .profile-window', + :value => message, + :match_result => true, + }, + ], + }, + ] + browser_double_test(tests) + end + + def test_search_and_edit_in_one + message = '1 ' + rand(99999999).to_s + tests = [ + { + :name => 'search and edit', + :action => [ + { + :execute => 'close_all_tasks', + }, + + # search and open org + { + :execute => 'search_organization', + :term => 'Zammad', + }, + { + :execute => 'match', + :css => '.active .profile-window', + :value => 'note', + :match_result => true, + }, + { + :execute => 'match', + :css => '.active .profile-window', + :value => 'member', + :match_result => true, + + }, + + # update note + { + :execute => 'set', + :css => '.active [data-name="note"]', + :value => 'some note 123' + }, + { + :execute => 'click', + :css => '.active .profile', + }, + { + :execute => 'wait', + :value => 1, + }, + + # check and change note again in edit screen + { + :execute => 'click', + :css => '.active .js-action .select-arrow', + }, + { + :execute => 'click', + :css => '.active .js-action a[data-type="edit"]', + }, + { + :execute => 'wait', + :value => 1, + }, + { + :execute => 'match', + :css => '.active .modal', + :value => 'note', + :match_result => true, + }, + { + :execute => 'match', + :css => '.active .modal', + :value => 'some note 123', + :match_result => true, + }, + { + :execute => 'set', + :css => '.active .modal [data-name="note"]', + :value => 'some note abc' + }, + { + :execute => 'click', + :css => '.active .modal button.js-submit', + }, + { + :execute => 'wait', + :value => 4, + }, + { + :execute => 'match', + :css => '.active .profile-window', + :value => 'some note abc', + :match_result => true, + }, + + # create new ticket + { + :execute => 'create_ticket', + :group => 'Users', + :subject => 'org profile check ' + message, + :body => 'org profile check ' + message, + }, + { + :execute => 'wait', + :value => 4, + }, + + # switch to org tab, verify if ticket is shown + { + :execute => 'search_organization', + :term => 'Zammad', + }, + + { + :execute => 'match', + :css => '.active .profile-window', + :value => 'org profile check ' + message, + :match_result => true, + }, + + ], + }, + ] + browser_signle_test_with_login(tests, { :username => 'master@example.com' }) + end +end \ No newline at end of file diff --git a/test/browser/agent_user_profile_test.rb b/test/browser/agent_user_profile_test.rb new file mode 100644 index 000000000..1dc7b10bd --- /dev/null +++ b/test/browser/agent_user_profile_test.rb @@ -0,0 +1,188 @@ +# encoding: utf-8 +require 'browser_test_helper' + +class AgentUserProfileTest < TestCase + def test_search_and_edit_verify_in_second + message = 'comment 1 ' + rand(99999999999999999).to_s + tests = [ + { + :name => 'start', + :instance1 => browser_instance, + :instance2 => browser_instance, + :instance1_username => 'master@example.com', + :instance1_password => 'test', + :instance2_username => 'agent1@example.com', + :instance2_password => 'test', + :url => browser_url, + :action => [ + { + :where => :instance1, + :execute => 'close_all_tasks', + }, + { + :where => :instance2, + :execute => 'close_all_tasks', + }, + { + :where => :instance1, + :execute => 'search_user', + :term => 'Braun', + }, + { + :where => :instance2, + :execute => 'search_user', + :term => 'Braun', + }, + + # update note + { + :where => :instance1, + :execute => 'set', + :css => '.active [data-name="note"]', + :value => message, + }, + { + :where => :instance1, + :execute => 'click', + :css => '.active .profile', + }, + { + :where => :instance1, + :execute => 'wait', + :value => 3, + }, + + # verify + { + :where => :instance2, + :execute => 'match', + :css => '.active .profile-window', + :value => message, + :match_result => true, + }, + ], + }, + ] + browser_double_test(tests) + end + + def test_search_and_edit_in_one + message = '1 ' + rand(99999999).to_s + tests = [ + { + :name => 'search and edit', + :action => [ + { + :execute => 'close_all_tasks', + }, + + # search and open user + { + :execute => 'search_user', + :term => 'Braun', + }, + { + :execute => 'match', + :css => '.active .profile-window', + :value => 'note', + :match_result => true, + }, + { + :execute => 'match', + :css => '.active .profile-window', + :value => 'email', + :match_result => true, + + }, + + # update note + { + :execute => 'set', + :css => '.active [data-name="note"]', + :value => 'some note 123' + }, + { + :execute => 'click', + :css => '.active .profile', + }, + { + :execute => 'wait', + :value => 1, + }, + + # check and change note again in edit screen + { + :execute => 'click', + :css => '.active .js-action .select-arrow', + }, + { + :execute => 'click', + :css => '.active .js-action a[data-type="edit"]', + }, + { + :execute => 'wait', + :value => 1, + }, + { + :execute => 'match', + :css => '.active .modal', + :value => 'note', + :match_result => true, + }, + { + :execute => 'match', + :css => '.active .modal', + :value => 'some note 123', + :match_result => true, + }, + { + :execute => 'set', + :css => '.active .modal [data-name="note"]', + :value => 'some note abc' + }, + { + :execute => 'click', + :css => '.active .modal button.js-submit', + }, + { + :execute => 'wait', + :value => 4, + }, + { + :execute => 'match', + :css => '.active .profile-window', + :value => 'some note abc', + :match_result => true, + }, + + # create new ticket + { + :execute => 'create_ticket', + :group => 'Users', + :subject => 'user profile check ' + message, + :body => 'user profile check ' + message, + }, + { + :execute => 'wait', + :value => 4, + }, + + # switch to org tab, verify if ticket is shown + { + :execute => 'search_user', + :term => 'Braun', + }, + + { + :execute => 'match', + :css => '.active .profile-window', + :value => 'user profile check ' + message, + :match_result => true, + }, + + ], + }, + ] + browser_signle_test_with_login(tests, { :username => 'master@example.com' }) + end +end \ No newline at end of file diff --git a/test/unit/ticket_ref_object_update_test.rb b/test/unit/ticket_ref_object_update_test.rb new file mode 100644 index 000000000..cda20c4c7 --- /dev/null +++ b/test/unit/ticket_ref_object_update_test.rb @@ -0,0 +1,92 @@ +# encoding: utf-8 +require 'test_helper' + +class TicketRefObjectTouchTest < ActiveSupport::TestCase + + # create base + groups = Group.where( :name => 'Users' ) + roles = Role.where( :name => 'Agent' ) + agent1 = User.create_or_update( + :login => 'ticket-ref-object-update-agent1@example.com', + :firstname => 'Notification', + :lastname => 'Agent1', + :email => 'ticket-ref-object-update-agent1@example.com', + :password => 'agentpw', + :active => true, + :roles => roles, + :groups => groups, + :updated_at => '2015-02-05 16:37:00', + :updated_by_id => 1, + :created_by_id => 1, + ) + roles = Role.where( :name => 'Customer' ) + customer1 = User.create_or_update( + :login => 'ticket-ref-object-update-customer1@example.com', + :firstname => 'Notification', + :lastname => 'Agent1', + :email => 'ticket-ref-object-update-customer1@example.com', + :password => 'customerpw', + :active => true, + :roles => roles, + :updated_at => '2015-02-05 16:37:00', + :updated_by_id => 1, + :created_by_id => 1, + ) + organization1 = Organization.create_if_not_exists( + :name => 'Ref Object Update Org', + :updated_at => '2015-02-05 16:37:00', + :updated_by_id => 1, + :created_by_id => 1, + ) + + test 'check if customer and organization has been updated' do + + ticket = Ticket.create( + :title => "some title\n äöüß", + :group => Group.lookup( :name => 'Users'), + :customer_id => customer1.id, + :owner_id => agent1.id, + :state => Ticket::State.lookup( :name => 'new' ), + :priority => Ticket::Priority.lookup( :name => '2 normal' ), + :updated_by_id => 1, + :created_by_id => 1, + ) + assert( ticket, "ticket created" ) + + # check if customer and organization has been touched + customer1 = User.find(customer1.id) + if customer1.updated_at > 2.second.ago + assert( true, "customer1.updated_at has been updated" ) + else + assert( false, "customer1.updated_at has not been updated" ) + end + + organization1 = Organization.find(organization1.id) + if organization1.updated_at > 2.second.ago + assert( true, "organization1.updated_at has been updated" ) + else + assert( false, "organization1.updated_at has not been updated" ) + end + + sleep 5 + + delete = ticket.destroy + assert( delete, "ticket destroy" ) + + # check if customer and organization has been touched + customer1 = User.find(customer1.id) + if customer1.updated_at > 2.second.ago + assert( true, "customer1.updated_at has been updated" ) + else + assert( false, "customer1.updated_at has not been updated" ) + end + + organization1 = Organization.find(organization1.id) + if organization1.updated_at > 2.second.ago + assert( true, "organization1.updated_at has been updated" ) + else + assert( false, "organization1.updated_at has not been updated" ) + end + + end +end \ No newline at end of file From db294351e9a1aed929874f58b298fc2d3d0b3a6f Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 6 Feb 2015 10:21:21 +0100 Subject: [PATCH 10/42] Improved code layout. --- .../app/controllers/_application_controller.js.coffee | 8 ++++---- .../javascripts/app/controllers/ticket_overview.js.coffee | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/app/controllers/_application_controller.js.coffee b/app/assets/javascripts/app/controllers/_application_controller.js.coffee index 0ac61050b..feae627ef 100644 --- a/app/assets/javascripts/app/controllers/_application_controller.js.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller.js.coffee @@ -556,10 +556,10 @@ class App.ControllerModal extends App.Controller @el.addClass('modal--local') @el.modal - keyboard: @keyboard - show: true - backdrop: @backdrop - container: @container + keyboard: @keyboard + show: true + backdrop: @backdrop + container: @container .on 'show.bs.modal': @onShow 'shown.bs.modal': @onShown diff --git a/app/assets/javascripts/app/controllers/ticket_overview.js.coffee b/app/assets/javascripts/app/controllers/ticket_overview.js.coffee index 68247f680..0ec91a768 100644 --- a/app/assets/javascripts/app/controllers/ticket_overview.js.coffee +++ b/app/assets/javascripts/app/controllers/ticket_overview.js.coffee @@ -482,7 +482,7 @@ class Table extends App.ControllerContent new App.OverviewSettings( overview_id: @overview.id view_mode: @view_mode - container: @el + container: @el ) class App.OverviewSettings extends App.ControllerModal From 01e9ad534f31954f750ad3457dcf8bccf7156526 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 6 Feb 2015 10:22:46 +0100 Subject: [PATCH 11/42] Improvd tests. --- test/unit/ticket_ref_object_update_test.rb | 99 ++++++++++++++++++---- 1 file changed, 84 insertions(+), 15 deletions(-) diff --git a/test/unit/ticket_ref_object_update_test.rb b/test/unit/ticket_ref_object_update_test.rb index cda20c4c7..592b4957e 100644 --- a/test/unit/ticket_ref_object_update_test.rb +++ b/test/unit/ticket_ref_object_update_test.rb @@ -20,29 +20,43 @@ class TicketRefObjectTouchTest < ActiveSupport::TestCase :created_by_id => 1, ) roles = Role.where( :name => 'Customer' ) - customer1 = User.create_or_update( - :login => 'ticket-ref-object-update-customer1@example.com', - :firstname => 'Notification', - :lastname => 'Agent1', - :email => 'ticket-ref-object-update-customer1@example.com', - :password => 'customerpw', - :active => true, - :roles => roles, - :updated_at => '2015-02-05 16:37:00', - :updated_by_id => 1, - :created_by_id => 1, - ) organization1 = Organization.create_if_not_exists( :name => 'Ref Object Update Org', :updated_at => '2015-02-05 16:37:00', :updated_by_id => 1, :created_by_id => 1, ) + customer1 = User.create_or_update( + :login => 'ticket-ref-object-update-customer1@example.com', + :firstname => 'Notification', + :lastname => 'Agent1', + :email => 'ticket-ref-object-update-customer1@example.com', + :password => 'customerpw', + :active => true, + :organization_id => organization1.id, + :roles => roles, + :updated_at => '2015-02-05 16:37:00', + :updated_by_id => 1, + :created_by_id => 1, + ) + customer2 = User.create_or_update( + :login => 'ticket-ref-object-update-customer2@example.com', + :firstname => 'Notification', + :lastname => 'Agent2', + :email => 'ticket-ref-object-update-customer2@example.com', + :password => 'customerpw', + :active => true, + :organization_id => nil, + :roles => roles, + :updated_at => '2015-02-05 16:37:00', + :updated_by_id => 1, + :created_by_id => 1, + ) - test 'check if customer and organization has been updated' do + test 'a - check if customer and organization has been updated' do ticket = Ticket.create( - :title => "some title\n äöüß", + :title => "some title1\n äöüß", :group => Group.lookup( :name => 'Users'), :customer_id => customer1.id, :owner_id => agent1.id, @@ -52,6 +66,8 @@ class TicketRefObjectTouchTest < ActiveSupport::TestCase :created_by_id => 1, ) assert( ticket, "ticket created" ) + assert_equal( ticket.customer.id, customer1.id ) + assert_equal( ticket.organization.id, organization1.id ) # check if customer and organization has been touched customer1 = User.find(customer1.id) @@ -68,7 +84,7 @@ class TicketRefObjectTouchTest < ActiveSupport::TestCase assert( false, "organization1.updated_at has not been updated" ) end - sleep 5 + sleep 4 delete = ticket.destroy assert( delete, "ticket destroy" ) @@ -87,6 +103,59 @@ class TicketRefObjectTouchTest < ActiveSupport::TestCase else assert( false, "organization1.updated_at has not been updated" ) end + end + + test 'b - check if customer (not organization) has been updated' do + + sleep 3 + ticket = Ticket.create( + :title => "some title2\n äöüß", + :group => Group.lookup( :name => 'Users'), + :customer_id => customer2.id, + :owner_id => agent1.id, + :state => Ticket::State.lookup( :name => 'new' ), + :priority => Ticket::Priority.lookup( :name => '2 normal' ), + :updated_by_id => 1, + :created_by_id => 1, + ) + assert( ticket, "ticket created" ) + assert_equal( ticket.customer.id, customer2.id ) + assert_equal( ticket.organization, nil ) + + # check if customer and organization has been touched + customer2 = User.find(customer2.id) + if customer2.updated_at > 2.second.ago + assert( true, "customer2.updated_at has been updated" ) + else + assert( false, "customer2.updated_at has not been updated" ) + end + + organization1 = Organization.find(organization1.id) + if organization1.updated_at > 2.second.ago + assert( false, "organization1.updated_at has been updated" ) + else + assert( true, "organization1.updated_at has not been updated" ) + end + + sleep 3 + + delete = ticket.destroy + assert( delete, "ticket destroy" ) + + # check if customer and organization has been touched + customer2 = User.find(customer2.id) + if customer2.updated_at > 2.second.ago + assert( true, "customer2.updated_at has been updated" ) + else + assert( false, "customer2.updated_at has not been updated" ) + end + + organization1 = Organization.find(organization1.id) + if organization1.updated_at > 2.second.ago + assert( false, "organization1.updated_at has been updated" ) + else + assert( true, "organization1.updated_at has not been updated" ) + end end end \ No newline at end of file From fa7926eee18a2fdd8cf4f11c2f1493489adcf236 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 6 Feb 2015 13:44:55 +0100 Subject: [PATCH 12/42] Show inline editing always. --- .../app/views/user_profile/object.jst.eco | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/app/views/user_profile/object.jst.eco b/app/assets/javascripts/app/views/user_profile/object.jst.eco index 6768083e2..6d454e4c3 100644 --- a/app/assets/javascripts/app/views/user_profile/object.jst.eco +++ b/app/assets/javascripts/app/views/user_profile/object.jst.eco @@ -9,13 +9,13 @@
<% for row in @userData: %> - <% if @user[row.name]: %> - <% if row.tag is 'richtext': %> -
- -
<%- @user[row.name] %>
-
- <% else: %> + <% if row.tag is 'richtext': %> +
+ +
<%- @user[row.name] %>
+
+ <% else: %> + <% if @user[row.name]: %>
<%- @L( @P( @user[row.name] ) ) %> From ec43a02588262b624b57f79edfa8c6b42c66c760 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 6 Feb 2015 16:19:07 +0100 Subject: [PATCH 13/42] Added vip feature. --- app/assets/stylesheets/zammad.css.scss | 21 ++++++++- .../20141217000001_update_object_manager2.rb | 2 +- db/migrate/20150206000001_create_vip.rb | 44 +++++++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20150206000001_create_vip.rb diff --git a/app/assets/stylesheets/zammad.css.scss b/app/assets/stylesheets/zammad.css.scss index a59689aa8..675d0f431 100644 --- a/app/assets/stylesheets/zammad.css.scss +++ b/app/assets/stylesheets/zammad.css.scss @@ -2478,7 +2478,7 @@ footer { .user-menu .dropdown-menu > li > a:hover { color: #2594d4; - background: rgba(0,0,0,.05); + background: rgba(0,0,0,.05); } .avatar { @@ -2492,6 +2492,11 @@ footer { vertical-align: bottom; } + :not(.navigation) .avatar.vip { + border: 2px dotted; + border-color: #fff; + } + .avatar.size-50 { width: 50px; height: 50px; @@ -2515,16 +2520,30 @@ footer { cursor: default; } + :not(.navigation) .unique.avatar.vip { + border: 2px dotted; + line-height: 40px; + } + .unique.avatar.size-50 { font-size: 16px; line-height: 52px; } + :not(.navigation) .unique.avatar.size-50.vip { + line-height: 50px; + } + .unique.avatar.size-80 { font-size: 20px; line-height: 84px; } + :not(.navigation) .unique.avatar.size-80.vip { + font-size: 20px; + line-height: 82px; + } + .sidebar { width: 32%; max-width: 300px; diff --git a/db/migrate/20141217000001_update_object_manager2.rb b/db/migrate/20141217000001_update_object_manager2.rb index cdb654b59..7da5b5be5 100644 --- a/db/migrate/20141217000001_update_object_manager2.rb +++ b/db/migrate/20141217000001_update_object_manager2.rb @@ -463,6 +463,7 @@ class UpdateObjectManager2 < ActiveRecord::Migration :maxlength => 100, :null => true, :autocomplete => 'off', + :item_class => 'formGroup--halfSize', }, :editable => false, :active => true, @@ -593,7 +594,6 @@ class UpdateObjectManager2 < ActiveRecord::Migration :display => 'Active', :data_type => 'boolean', :data_option => { - :maxlength => 250, :null => true, :default => true, }, diff --git a/db/migrate/20150206000001_create_vip.rb b/db/migrate/20150206000001_create_vip.rb new file mode 100644 index 000000000..e7854c49f --- /dev/null +++ b/db/migrate/20150206000001_create_vip.rb @@ -0,0 +1,44 @@ +class CreateVip < ActiveRecord::Migration + def up + add_column :users, :vip, :boolean, :default => false + + ObjectManager::Attribute.add( + :object => 'User', + :name => 'vip', + :display => 'VIP', + :data_type => 'boolean', + :data_option => { + :null => false, + :default => false, + :item_class => 'formGroup--halfSize', + :options => { + :false => 'no', + :true => 'yes', + }, + :translate => true, + }, + :editable => false, + :active => true, + :screens => { + :edit => { + :Admin => { + :null => false, + }, + }, + :view => { + '-all-' => { + :shown => false, + }, + }, + }, + :pending_migration => false, + :position => 1490, + :created_by_id => 1, + :updated_by_id => 1, + ) + + end + + def down + end +end From afc1d6571d3c0c99d540c961e97f58ec86bf262e Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 6 Feb 2015 16:23:22 +0100 Subject: [PATCH 14/42] Removed broken avatar background until @mrflix has time to fix it. --- .../agent_organization_profile_test.rb | 8 +-- test/browser_test_helper.rb | 55 ++++++++++++++++++- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/test/browser/agent_organization_profile_test.rb b/test/browser/agent_organization_profile_test.rb index 3142e52b2..585888de6 100644 --- a/test/browser/agent_organization_profile_test.rb +++ b/test/browser/agent_organization_profile_test.rb @@ -26,12 +26,12 @@ class AgentOrganizationProfileTest < TestCase { :where => :instance1, :execute => 'search_organization', - :term => 'Zammad', + :term => 'Zammad Foundation', }, { :where => :instance2, :execute => 'search_organization', - :term => 'Zammad', + :term => 'Zammad Foundation', }, # update note @@ -79,7 +79,7 @@ class AgentOrganizationProfileTest < TestCase # search and open org { :execute => 'search_organization', - :term => 'Zammad', + :term => 'Zammad Foundation', }, { :execute => 'match', @@ -170,7 +170,7 @@ class AgentOrganizationProfileTest < TestCase # switch to org tab, verify if ticket is shown { :execute => 'search_organization', - :term => 'Zammad', + :term => 'Zammad Foundation', }, { diff --git a/test/browser_test_helper.rb b/test/browser_test_helper.rb index 4be4ad056..82522f43f 100644 --- a/test/browser_test_helper.rb +++ b/test/browser_test_helper.rb @@ -525,6 +525,55 @@ class TestCase < Test::Unit::TestCase } assert( false, "(#{test[:name]}) ticket creation failed, can't get zoom url" ) return + elsif action[:execute] == 'search_user' + element = instance.find_elements( { :css => '#global-search' } )[0] + element.click + element.clear + if @stack + action[:term].gsub! '###stack###', @stack + end + element.send_keys( action[:term] ) + sleep 3 + element = instance.find_element( { :partial_link_text => action[:term] } ).click + name = instance.find_elements( { :css => '.active h1' } )[0].text + if name !~ /#{action[:term]}/ + assert( false, "(#{test[:name]}) unable to search/find user #{action[:term]}!" ) + return + end + assert( true, "(#{test[:name]}) user #{action[:term]} found" ) + return + elsif action[:execute] == 'search_organization' + element = instance.find_elements( { :css => '#global-search' } )[0] + element.click + element.clear + if @stack + action[:term].gsub! '###stack###', @stack + end + element.send_keys( action[:term] ) + sleep 3 + instance.find_elements( { :css => '.search .empty-search' } )[0].click + sleep 0.5 + text = instance.find_elements( { :css => '#global-search' } )[0].attribute('value') + if !text + assert( false, "(#{test[:name]}) #global-search is not empty!" ) + return + end + element = instance.find_elements( { :css => '#global-search' } )[0] + element.click + element.clear + if @stack + action[:term].gsub! '###stack###', @stack + end + element.send_keys( action[:term] ) + sleep 2 + element = instance.find_element( { :partial_link_text => action[:term] } ).click + name = instance.find_elements( { :css => '.active h1' } )[0].text + if name !~ /#{action[:term]}/ + assert( false, "(#{test[:name]}) unable to search/find org #{action[:term]}!" ) + return + end + assert( true, "(#{test[:name]}) org #{action[:term]} found" ) + return elsif action[:execute] == 'search_ticket' element = instance.find_elements( { :css => '#global-search' } )[0] element.click @@ -544,7 +593,7 @@ class TestCase < Test::Unit::TestCase element.clear action[:number].gsub! '###stack###', @stack element.send_keys( action[:number] ) - sleep 3 + sleep 2 element = instance.find_element( { :partial_link_text => action[:number] } ).click number = instance.find_elements( { :css => '.active .page-header .ticket-number' } )[0].text if number !~ /#{action[:number]}/ @@ -684,14 +733,14 @@ class TestCase < Test::Unit::TestCase match = false if action[:no_quote] #puts "aaaa #{text}/#{action[:value]}" - if text =~ /#{action[:value]}/ + if text =~ /#{action[:value]}/i if $1 @stack = $1 end match = $1 || true end else - if text =~ /#{Regexp.quote(action[:value])}/ + if text =~ /#{Regexp.quote(action[:value])}/i match = true end end From c030efc5c374ca9d593ae7341ef0aa18d6d4db85 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 6 Feb 2015 16:24:11 +0100 Subject: [PATCH 15/42] Added vip feature. --- app/assets/javascripts/app/models/user.js.coffee | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/assets/javascripts/app/models/user.js.coffee b/app/assets/javascripts/app/models/user.js.coffee index dc5911e21..ddea8b142 100644 --- a/app/assets/javascripts/app/models/user.js.coffee +++ b/app/assets/javascripts/app/models/user.js.coffee @@ -61,6 +61,8 @@ class App.User extends App.Model if !@image || @image is 'none' return @uniqueAvatar(size, placement, cssClass) else + if @vip + cssClass += "#{cssClass} vip" "" uniqueAvatar: (size = 40, placement = '', cssClass = '', avatar) -> @@ -80,6 +82,9 @@ class App.User extends App.Model data = "data-id=\"#{@id}\"" else data = "data-avatar-id=\"#{avatar.id}\"" + + if @vip + cssClass += "#{cssClass} vip" "#{ @initials() }" @_fillUp: (data) -> From 8a3038c7b61221809624ce2ba2aa54e43d52d196 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 6 Feb 2015 16:24:40 +0100 Subject: [PATCH 16/42] Removed broken avatar background until @mrflix has time to fix it. --- app/assets/stylesheets/zammad.css.scss | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/app/assets/stylesheets/zammad.css.scss b/app/assets/stylesheets/zammad.css.scss index 675d0f431..f5b2057e6 100644 --- a/app/assets/stylesheets/zammad.css.scss +++ b/app/assets/stylesheets/zammad.css.scss @@ -4400,17 +4400,6 @@ footer { position: relative; } -.userInfo-avatar:after { - content: ""; - background: image_url("/assets/images/sprite.svg"); - background-position: -236px 0; - right: 0; - top: 0; - width: 97px; - height: 96px; - position: absolute; -} - .userList { list-style: none; padding: 0; From 4c2d3e50c332c9e88847c2c50ce54bdd7b02f1cc Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 6 Feb 2015 17:13:13 +0100 Subject: [PATCH 17/42] Prevent given external id at object creation. --- app/controllers/application_controller.rb | 2 +- app/controllers/users_controller.rb | 4 ++-- app/models/application_model.rb | 12 +++++++++++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index fe2789135..2a32b3e78 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -264,7 +264,7 @@ class ApplicationController < ActionController::Base begin # create object - generic_object = object.new( object.param_cleanup( params[object.to_app_model_url] ) ) + generic_object = object.new( object.param_cleanup( params[object.to_app_model_url], true ) ) # save object generic_object.save! diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 6637ffa71..84ab2adaf 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -64,7 +64,7 @@ class UsersController < ApplicationController # @response_message 200 [User] Created User record. # @response_message 401 Invalid session. def create - user = User.new( User.param_cleanup(params) ) + user = User.new( User.param_cleanup(params, true) ) begin # check if it's first user @@ -122,7 +122,7 @@ class UsersController < ApplicationController end end - user.save + user.save! # if first user was added, set system init done if count <= 2 diff --git a/app/models/application_model.rb b/app/models/application_model.rb index 9594295bd..41736ea18 100644 --- a/app/models/application_model.rb +++ b/app/models/application_model.rb @@ -57,18 +57,28 @@ remove all not used model attributes of params result = Model.param_cleanup(params) + for object creation, ignore id's + + result = Model.param_cleanup(params, true) + + returns result = params # params with valid attributes of model =end - def self.param_cleanup(params) + def self.param_cleanup(params, newObject = false) if params == nil raise "No params for #{self.to_s}!" end + # ignore id for new objects + if newObject && params[:id] + params[:id] = nil + end + # only use object attributes data = {} self.new.attributes.each {|item| From 76982a897609141ac7cf1708cca6bff1cfd0d22a Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 6 Feb 2015 22:51:52 +0100 Subject: [PATCH 18/42] Extended clone to also copy functions by using clone( some_data, true). --- app/assets/javascripts/application.js | 30 +++++++++++++++++---------- public/assets/tests/core.js | 29 ++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 55002f83d..61386a377 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -74,41 +74,49 @@ function difference(object1, object2) { } // clone, just data, no instances of objects -function clone(item) { - if (!item) { return item; } +function clone(item, full) { + if (!item) { return item } // ignore certain objects - var acceptedInstances = [ 'Object', 'Number', 'String', 'Boolean', 'Array' ]; + var acceptedInstances = [ 'Object', 'Number', 'String', 'Boolean', 'Array' ] + if (full) { + acceptedInstances.push( 'Function' ) + } if (item && item.constructor) { if (!_.contains(acceptedInstances, item.constructor.name)) { - return; + return } } - var result; // copy array + var result; if ( _.isArray(item) ) { - result = []; + result = [] item.forEach(function(child, index, array) { - result[index] = clone( child ); + result[index] = clone( child, full ) }); } + // copy function + else if ( _.isFunction(item) ) { + result = item.bind({}) + } + // copy object else if ( _.isObject(item) ) { - result = {}; + result = {} for(var key in item) { if (item.hasOwnProperty(key)) { - result[key] = clone(item[key]) + result[key] = clone( item[key], full ) } } } // copy others else { - result = item; + result = item } - return result; + return result } // taken from http://stackoverflow.com/questions/4459928/how-to-deep-clone-in-javascript diff --git a/public/assets/tests/core.js b/public/assets/tests/core.js index bca6ec2df..82cd9f380 100644 --- a/public/assets/tests/core.js +++ b/public/assets/tests/core.js @@ -486,10 +486,12 @@ test( "clone", function() { var source = [ { name: 'some name' }, { name: 'some name2' }, + { fn: function() { return 'test' } }, ] var reference = [ { name: 'some name' }, { name: 'some name2' }, + { fn: undefined }, ] var result = clone( source ) @@ -498,6 +500,33 @@ test( "clone", function() { deepEqual( result, reference, 'clone' ); + + // full test + var source = [ + { name: 'some name' }, + { name: 'some name2' }, + { fn: function a() { return 'test' } }, + ] + var reference = [ + { name: 'some name' }, + { name: 'some name2' }, + { fn: function a() { return 'test' } }, + ] + var result = clone( source, true ) + + // modify source later, should not have any result + source[0].name = 'some new name' + source[2].fn = 'some new name' + + deepEqual( result[0], reference[0], 'clone full' ); + deepEqual( result[1], reference[1], 'clone full' ); + + equal( typeof reference[2].fn, 'function') + equal( typeof result[2].fn, 'function') + + equal( reference[2].fn(), 'test') + equal( result[2].fn(), 'test') + }); // diff From 06f2e779928c957ad6cf522a56f1261b4840d5b0 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 6 Feb 2015 23:11:51 +0100 Subject: [PATCH 19/42] Moved to local modals. --- .../_application_controller_generic.js.coffee | 17 +++++---- .../_application_controller_table.js.coffee | 5 +-- .../controllers/agent_ticket_create.js.coffee | 25 +++++++------ .../app/controllers/groups.js.coffee | 26 +++++++------- .../app/controllers/object_manager.js.coffee | 25 ++++++------- .../organization_history.js.coffee | 6 ++-- .../app/controllers/organizations.js.coffee | 26 +++++++------- .../app/controllers/overview.js.coffee | 26 +++++++------- .../app/controllers/scheduler.js.coffee | 26 +++++++------- .../javascripts/app/controllers/sla.js.coffee | 26 +++++++------- .../app/controllers/text_module.js.coffee | 26 +++++++------- .../app/controllers/ticket_overview.js.coffee | 2 +- .../app/controllers/ticket_zoom.js.coffee | 35 +++++++++++-------- ...user_organization_autocompletion.js.coffee | 3 +- 14 files changed, 143 insertions(+), 131 deletions(-) diff --git a/app/assets/javascripts/app/controllers/_application_controller_generic.js.coffee b/app/assets/javascripts/app/controllers/_application_controller_generic.js.coffee index c49f3897b..68c88ac33 100644 --- a/app/assets/javascripts/app/controllers/_application_controller_generic.js.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller_generic.js.coffee @@ -2,15 +2,15 @@ class App.ControllerGenericNew extends App.ControllerModal constructor: (params) -> super - @head = App.i18n.translateContent( 'New' ) + ': ' + App.i18n.translateContent( @pageData.object ) + @head = App.i18n.translateContent( 'New' ) + ': ' + App.i18n.translateContent( @pageData.object ) @cancel = true @button = true controller = new App.ControllerForm( - model: App[ @genericObject ] - params: @item - screen: @screen || 'edit' - autofocus: true + model: App[ @genericObject ] + params: @item + screen: @screen || 'edit' + autofocus: true ) @content = controller.form @@ -98,8 +98,8 @@ class App.ControllerGenericEdit extends App.ControllerModal class App.ControllerGenericIndex extends App.Controller events: - 'click [data-type=edit]': 'edit' - 'click [data-type=new]': 'new' + 'click [data-type = edit]': 'edit' + 'click [data-type = new]': 'new' constructor: -> super @@ -160,6 +160,7 @@ class App.ControllerGenericIndex extends App.Controller bindRow: events: 'click': @edit + container: @container }, @pageData.tableExtend ) @@ -177,6 +178,7 @@ class App.ControllerGenericIndex extends App.Controller id: item.id pageData: @pageData genericObject: @genericObject + container: @container ) new: (e) -> @@ -184,6 +186,7 @@ class App.ControllerGenericIndex extends App.Controller new App.ControllerGenericNew( pageData: @pageData genericObject: @genericObject + container: @container ) class App.ControllerGenericDestroyConfirm extends App.ControllerModal diff --git a/app/assets/javascripts/app/controllers/_application_controller_table.js.coffee b/app/assets/javascripts/app/controllers/_application_controller_table.js.coffee index 5f7b632d9..a218b4a7c 100644 --- a/app/assets/javascripts/app/controllers/_application_controller_table.js.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller_table.js.coffee @@ -235,13 +235,14 @@ class App.ControllerTable extends App.Controller # bind on delete dialog if data.model && destroy - table.delegate('[data-type="destroy"]', 'click', (e) -> + table.delegate('[data-type="destroy"]', 'click', (e) => e.stopPropagation() e.preventDefault() itemId = $(e.target).parents('tr').data('id') item = data.model.find(itemId) new App.ControllerGenericDestroyConfirm( - item: item + item: item + container: @container ) ) diff --git a/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee b/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee index 387657240..4f88bc5f1 100644 --- a/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee +++ b/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee @@ -473,8 +473,8 @@ class Sidebar extends App.Controller ) new App.WidgetUser( - el: el - user_id: user.id + el: el + user_id: user.id ) editCustomer = (e, el) => @@ -483,9 +483,10 @@ class Sidebar extends App.Controller genericObject: 'User' screen: 'edit' pageData: - title: 'Users' - object: 'User' + title: 'Users' + object: 'User' objects: 'Users' + container: @el.closest('.content') ) items.push { head: 'Customer' @@ -493,9 +494,9 @@ class Sidebar extends App.Controller icon: 'person' actions: [ { - title: 'Edit Customer' - name: 'Edit Customer' - class: 'glyphicon glyphicon-edit' + title: 'Edit Customer' + name: 'Edit Customer' + class: 'glyphicon glyphicon-edit' callback: editCustomer }, ] @@ -508,14 +509,15 @@ class Sidebar extends App.Controller id: user.organization_id genericObject: 'Organization' pageData: - title: 'Organizations' - object: 'Organization' + title: 'Organizations' + object: 'Organization' objects: 'Organizations' + container: @el.closest('.content') ) showOrganization = (el) => new App.WidgetOrganization( - el: el - organization_id: user.organization_id + el: el + organization_id: user.organization_id ) items.push { head: 'Organization' @@ -523,6 +525,7 @@ class Sidebar extends App.Controller icon: 'group' actions: [ { + title: 'Edit Organization' name: 'Edit Organization' class: 'glyphicon glyphicon-edit' callback: editOrganization diff --git a/app/assets/javascripts/app/controllers/groups.js.coffee b/app/assets/javascripts/app/controllers/groups.js.coffee index 4bf2ad3e8..c3fd370ec 100644 --- a/app/assets/javascripts/app/controllers/groups.js.coffee +++ b/app/assets/javascripts/app/controllers/groups.js.coffee @@ -6,22 +6,22 @@ class Index extends App.ControllerContent return if !@authenticate() new App.ControllerGenericIndex( - el: @el, - id: @id, - genericObject: 'Group', - pageData: { - title: 'Groups', - home: 'groups', - object: 'Group', - objects: 'Groups', - navupdate: '#groups', - notes: [ + el: @el + id: @id + genericObject: 'Group' + pageData: + title: 'Groups' + home: 'groups' + object: 'Group' + objects: 'Groups' + navupdate: '#groups' + notes: [ 'Groups are ...' - ], + ] buttons: [ { name: 'New Group', 'data-type': 'new', class: 'btn--success' }, - ], - }, + ] + container: @el.closest('.content') ) App.Config.set( 'Group', { prio: 1500, name: 'Groups', parent: '#manage', target: '#manage/groups', controller: Index, role: ['Admin'] }, 'NavBarAdmin' ) \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/object_manager.js.coffee b/app/assets/javascripts/app/controllers/object_manager.js.coffee index 496ab320c..6d97db5c0 100644 --- a/app/assets/javascripts/app/controllers/object_manager.js.coffee +++ b/app/assets/javascripts/app/controllers/object_manager.js.coffee @@ -7,10 +7,10 @@ class Index extends App.ControllerTabs # get data @ajax( - id: 'object_manager_attributes_list', - type: 'GET', - url: @apiPath + '/object_manager_attributes_list', - processData: true, + id: 'object_manager_attributes_list' + type: 'GET' + url: @apiPath + '/object_manager_attributes_list' + processData: true success: (data, status, xhr) => @build(data.objects) ) @@ -19,9 +19,9 @@ class Index extends App.ControllerTabs @tabs = [] for object in objects item = - name: object, - target: "c-#{object}", - controller: Items, + name: object + target: "c-#{object}" + controller: Items params: object: object @tabs.push item @@ -120,18 +120,19 @@ class Items extends App.ControllerContent objects: 'ObjectManagerAttributes' navupdate: '#object_manager' genericObject: 'ObjectManagerAttribute' + container: @el.closest('.content') ) edit: (e) => e.preventDefault() id = $( e.target ).closest('tr').data('id') new Edit( - pageData: { + pageData: object: 'ObjectManagerAttribute' - }, genericObject: 'ObjectManagerAttribute' - callback: @render + callback: @render id: id + container: @el.closest('.content') ) destroy: (e) -> @@ -158,7 +159,6 @@ class Edit extends App.ControllerModal items: [] ) ) - item = App.ObjectManagerAttribute.find(@id) options = @@ -249,7 +249,6 @@ class Edit extends App.ControllerModal @content.find('[name=data_type]').trigger('change') - configureAttributesBottom = [ { name: 'active', display: 'Active', tag: 'boolean', 'default': true, 'null': false }, ] @@ -263,8 +262,6 @@ class Edit extends App.ControllerModal #@content = controller.form - - #@show(content) @show() diff --git a/app/assets/javascripts/app/controllers/organization_history.js.coffee b/app/assets/javascripts/app/controllers/organization_history.js.coffee index a88953a15..96770b540 100644 --- a/app/assets/javascripts/app/controllers/organization_history.js.coffee +++ b/app/assets/javascripts/app/controllers/organization_history.js.coffee @@ -7,9 +7,9 @@ class App.OrganizationHistory extends App.GenericHistory # get data @ajax( - id: 'organization_history', - type: 'GET', - url: @apiPath + '/organizations/history/' + @organization_id, + id: 'organization_history' + type: 'GET' + url: @apiPath + '/organizations/history/' + @organization_id success: (data, status, xhr) => # load assets diff --git a/app/assets/javascripts/app/controllers/organizations.js.coffee b/app/assets/javascripts/app/controllers/organizations.js.coffee index 11648b712..81975f653 100644 --- a/app/assets/javascripts/app/controllers/organizations.js.coffee +++ b/app/assets/javascripts/app/controllers/organizations.js.coffee @@ -6,22 +6,22 @@ class Index extends App.ControllerContent return if !@authenticate() new App.ControllerGenericIndex( - el: @el, - id: @id, - genericObject: 'Organization', - pageData: { - title: 'Organizations', - home: 'organizations', - object: 'Organization', - objects: 'Organizations', - navupdate: '#organizations', + el: @el + id: @id + genericObject: 'Organization' + pageData: + title: 'Organizations' + home: 'organizations' + object: 'Organization' + objects: 'Organizations' + navupdate: '#organizations' notes: [ 'Organizations are for any person in the system. Agents (Owners, Resposbiles, ...) and Customers.' - ], + ] buttons: [ - { name: 'New Organization', 'data-type': 'new', class: 'btn--success' }, - ], - }, + { name: 'New Organization', 'data-type': 'new', class: 'btn--success' } + ] + container: @el.closest('.content') ) App.Config.set( 'Organization', { prio: 2000, name: 'Organizations', parent: '#manage', target: '#manage/organizations', controller: Index, role: ['Admin'] }, 'NavBarAdmin' ) \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/overview.js.coffee b/app/assets/javascripts/app/controllers/overview.js.coffee index a4af2d849..f65589a26 100644 --- a/app/assets/javascripts/app/controllers/overview.js.coffee +++ b/app/assets/javascripts/app/controllers/overview.js.coffee @@ -6,22 +6,22 @@ class Index extends App.ControllerContent return if !@authenticate() new App.ControllerGenericIndex( - el: @el, - id: @id, - genericObject: 'Overview', - pageData: { - title: 'Overviews', - home: 'overviews', - object: 'Overview', - objects: 'Overviews', - navupdate: '#overviews', + el: @el + id: @id + genericObject: 'Overview' + pageData: + title: 'Overviews' + home: 'overviews' + object: 'Overview' + objects: 'Overviews' + navupdate: '#overviews' notes: [ 'Overview are ...' - ], + ] buttons: [ - { name: 'New Overview', 'data-type': 'new', class: 'btn--success' }, - ], - }, + { name: 'New Overview', 'data-type': 'new', class: 'btn--success' } + ] + container: @el.closest('.content') ) App.Config.set( 'Overview', { prio: 2300, name: 'Overviews', parent: '#manage', target: '#manage/overviews', controller: Index, role: ['Admin'] }, 'NavBarAdmin' ) \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/scheduler.js.coffee b/app/assets/javascripts/app/controllers/scheduler.js.coffee index 4b378f314..8ec53f30d 100644 --- a/app/assets/javascripts/app/controllers/scheduler.js.coffee +++ b/app/assets/javascripts/app/controllers/scheduler.js.coffee @@ -6,22 +6,22 @@ class Index extends App.ControllerContent return if !@authenticate() new App.ControllerGenericIndex( - el: @el, - id: @id, - genericObject: 'Job', - pageData: { - title: 'Schedulers', - home: 'schedulers', - object: 'Scheduler', - objects: 'Schedulers', - navupdate: '#schedulers', + el: @el + id: @id + genericObject: 'Job' + pageData: + title: 'Schedulers' + home: 'schedulers' + object: 'Scheduler' + objects: 'Schedulers' + navupdate: '#schedulers' notes: [ 'Scheduler are ...' - ], + ] buttons: [ - { name: 'New Scheduler', 'data-type': 'new', class: 'btn--success' }, - ], - }, + { name: 'New Scheduler', 'data-type': 'new', class: 'btn--success' } + ] + container: @el.closest('.content') ) App.Config.set( 'Scheduler', { prio: 3000, name: 'Schedulers', parent: '#manage', target: '#manage/schedulers', controller: Index, role: ['Admin'] }, 'NavBarAdmin' ) \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/sla.js.coffee b/app/assets/javascripts/app/controllers/sla.js.coffee index 8567b6e49..ca0ea6ba5 100644 --- a/app/assets/javascripts/app/controllers/sla.js.coffee +++ b/app/assets/javascripts/app/controllers/sla.js.coffee @@ -6,22 +6,22 @@ class Index extends App.ControllerContent return if !@authenticate() new App.ControllerGenericIndex( - el: @el, - id: @id, - genericObject: 'Sla', - pageData: { - title: 'SLA', - home: 'slas', - object: 'SLA', - objects: 'SLAs', - navupdate: '#slas', + el: @el + id: @id + genericObject: 'Sla' + pageData: + title: 'SLA' + home: 'slas' + object: 'SLA' + objects: 'SLAs' + navupdate: '#slas' notes: [ # 'SLA are ...' - ], + ] buttons: [ - { name: 'New SLA', 'data-type': 'new', class: 'btn--success' }, - ], - }, + { name: 'New SLA', 'data-type': 'new', class: 'btn--success' } + ] + container: @el.closest('.content') ) App.Config.set( 'Sla', { prio: 2900, name: 'SLAs', parent: '#manage', target: '#manage/slas', controller: Index, role: ['Admin'] }, 'NavBarAdmin' ) \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/text_module.js.coffee b/app/assets/javascripts/app/controllers/text_module.js.coffee index 687d8b4ac..22e37abb1 100644 --- a/app/assets/javascripts/app/controllers/text_module.js.coffee +++ b/app/assets/javascripts/app/controllers/text_module.js.coffee @@ -6,22 +6,22 @@ class Index extends App.ControllerContent return if !@authenticate() new App.ControllerGenericIndex( - el: @el, - id: @id, - genericObject: 'TextModule', - pageData: { - title: 'TextModules', - home: 'text_modules', - object: 'TextModule', - objects: 'TextModules', - navupdate: '#text_modules', + el: @el + id: @id + genericObject: 'TextModule' + pageData: + title: 'TextModules' + home: 'text_modules' + object: 'TextModule' + objects: 'TextModules' + navupdate: '#text_modules' notes: [ 'TextModules are ...' - ], + ] buttons: [ - { name: 'New TextModule', 'data-type': 'new', class: 'btn--success' }, - ], - }, + { name: 'New TextModule', 'data-type': 'new', class: 'btn--success' } + ] + container: @el.closest('.content') ) App.Config.set( 'TextModule', { prio: 2300, name: 'TextModules', parent: '#manage', target: '#manage/text_modules', controller: Index, role: ['Admin'] }, 'NavBarAdmin' ) \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/ticket_overview.js.coffee b/app/assets/javascripts/app/controllers/ticket_overview.js.coffee index 0ec91a768..32c7a256d 100644 --- a/app/assets/javascripts/app/controllers/ticket_overview.js.coffee +++ b/app/assets/javascripts/app/controllers/ticket_overview.js.coffee @@ -482,7 +482,7 @@ class Table extends App.ControllerContent new App.OverviewSettings( overview_id: @overview.id view_mode: @view_mode - container: @el + container: @el.closest('.content') ) class App.OverviewSettings extends App.ControllerModal diff --git a/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee b/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee index d6ce60104..7e65572d6 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee @@ -329,21 +329,26 @@ class App.TicketZoom extends App.Controller ) showTicketHistory = => - new App.TicketHistory( ticket_id: @ticket.id ) + new App.TicketHistory( + ticket_id: @ticket.id + container: @el.closest('.content') + ) showTicketMerge = => - new App.TicketMerge - ticket: @ticket - task_key: @task_key - container: @el + new App.TicketMerge( + ticket: @ticket + task_key: @task_key + container: @el.closest('.content') + ) changeCustomer = (e, el) => new App.TicketCustomer( - ticket: @ticket + ticket: @ticket + container: @el.closest('.content') ) items = [ { - head: 'Ticket' - name: 'ticket' - icon: 'message' + head: 'Ticket' + name: 'ticket' + icon: 'message' callback: editTicket } ] @@ -375,6 +380,7 @@ class App.TicketZoom extends App.Controller title: 'Users' object: 'User' objects: 'Users' + container: @el.closest('.content') ) showCustomer = (el) => new App.WidgetUser( @@ -382,9 +388,9 @@ class App.TicketZoom extends App.Controller user_id: @ticket.customer_id ) items.push { - head: 'Customer' - name: 'customer' - icon: 'person' + head: 'Customer' + name: 'customer' + icon: 'person' actions: [ { title: 'Change Customer' @@ -408,6 +414,7 @@ class App.TicketZoom extends App.Controller title: 'Organizations' object: 'Organization' objects: 'Organizations' + container: @el.closest('.content') ) showOrganization = (el) => new App.WidgetOrganization( @@ -429,8 +436,8 @@ class App.TicketZoom extends App.Controller } new App.Sidebar( - el: @el.find('.tabsSidebar') - items: items + el: @el.find('.tabsSidebar') + items: items ) # show article diff --git a/app/assets/javascripts/app/lib/app_post/user_organization_autocompletion.js.coffee b/app/assets/javascripts/app/lib/app_post/user_organization_autocompletion.js.coffee index a5c9a98b7..4c13dc901 100644 --- a/app/assets/javascripts/app/lib/app_post/user_organization_autocompletion.js.coffee +++ b/app/assets/javascripts/app/lib/app_post/user_organization_autocompletion.js.coffee @@ -276,7 +276,8 @@ class App.UserOrganizationAutocompletion extends App.Controller if e e.preventDefault() new UserNew( - parent: @ + parent: @ + container: @el.closest('.content') ) class UserNew extends App.ControllerModal From 98c1ba4fab6652e43e061d17eef55989b5327912 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 6 Feb 2015 23:44:01 +0100 Subject: [PATCH 20/42] Improved code layout. --- .../javascripts/app/controllers/maintenance.js.coffee | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/app/controllers/maintenance.js.coffee b/app/assets/javascripts/app/controllers/maintenance.js.coffee index 4dcedcfd8..f5804b1c2 100644 --- a/app/assets/javascripts/app/controllers/maintenance.js.coffee +++ b/app/assets/javascripts/app/controllers/maintenance.js.coffee @@ -15,11 +15,11 @@ class Index extends App.ControllerContent e.preventDefault() params = @formParam(e.target) App.Event.trigger( - 'ws:send' - action: 'broadcast' - event: 'session:maintenance' - spool: false - data: params + 'ws:send' + action: 'broadcast' + event: 'session:maintenance' + spool: false + data: params ) @notify type: 'success' From fb2269addfc72a6242bb4d5c271eef9b54da3c1e Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Sat, 7 Feb 2015 00:00:23 +0100 Subject: [PATCH 21/42] Set VIP to optional. --- db/migrate/20150206000001_create_vip.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/db/migrate/20150206000001_create_vip.rb b/db/migrate/20150206000001_create_vip.rb index e7854c49f..96b2a4973 100644 --- a/db/migrate/20150206000001_create_vip.rb +++ b/db/migrate/20150206000001_create_vip.rb @@ -8,10 +8,10 @@ class CreateVip < ActiveRecord::Migration :display => 'VIP', :data_type => 'boolean', :data_option => { - :null => false, - :default => false, + :null => true, + :default => false, :item_class => 'formGroup--halfSize', - :options => { + :options => { :false => 'no', :true => 'yes', }, @@ -20,9 +20,9 @@ class CreateVip < ActiveRecord::Migration :editable => false, :active => true, :screens => { - :edit => { + :edit => { :Admin => { - :null => false, + :null => true, }, }, :view => { From 0d216b3bc652c5b1b1353875c421ce86e4efa2d4 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Sat, 7 Feb 2015 09:15:50 +0100 Subject: [PATCH 22/42] Moved to local modals. --- .../app/controllers/_channel/email.js.coffee | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/app/controllers/_channel/email.js.coffee b/app/assets/javascripts/app/controllers/_channel/email.js.coffee index 4cd598dbc..b2c9e7c53 100644 --- a/app/assets/javascripts/app/controllers/_channel/email.js.coffee +++ b/app/assets/javascripts/app/controllers/_channel/email.js.coffee @@ -64,11 +64,16 @@ class App.ChannelEmailFilter extends App.Controller new: (e) => e.preventDefault() - new App.ChannelEmailFilterEdit( {} ) + new App.ChannelEmailFilterEdit( + container: @el.closest('.content') + ) edit: (id, e) => e.preventDefault() - new App.ChannelEmailFilterEdit( object: App.PostmasterFilter.find(id) ) + new App.ChannelEmailFilterEdit( + object: App.PostmasterFilter.find(id) + container: @el.closest('.content') + ) class App.ChannelEmailFilterEdit extends App.ControllerModal constructor: -> @@ -152,12 +157,17 @@ class App.ChannelEmailAddress extends App.Controller new: (e) => e.preventDefault() - new App.ChannelEmailAddressEdit( {} ) + new App.ChannelEmailAddressEdit( + container: @el.closest('.content') + ) edit: (id, e) => e.preventDefault() item = App.EmailAddress.find(id) - new App.ChannelEmailAddressEdit( object: item ) + new App.ChannelEmailAddressEdit( + object: item + container: @el.closest('.content') + ) class App.ChannelEmailAddressEdit extends App.ControllerModal constructor: -> @@ -238,12 +248,17 @@ class App.ChannelEmailSignature extends App.Controller new: (e) => e.preventDefault() - new App.ChannelEmailSignatureEdit( {} ) + new App.ChannelEmailSignatureEdit( + container: @el.closest('.content') + ) edit: (id, e) => e.preventDefault() item = App.Signature.find(id) - new App.ChannelEmailSignatureEdit( object: item ) + new App.ChannelEmailSignatureEdit( + object: item + container: @el.closest('.content') + ) class App.ChannelEmailSignatureEdit extends App.ControllerModal constructor: -> @@ -324,12 +339,17 @@ class App.ChannelEmailInbound extends App.Controller new: (e) => e.preventDefault() - new App.ChannelEmailInboundEdit( {} ) + new App.ChannelEmailInboundEdit( + container: @el.closest('.content') + ) edit: (id, e) => e.preventDefault() item = App.Channel.find(id) - new App.ChannelEmailInboundEdit( object: item ) + new App.ChannelEmailInboundEdit( + object: item + container: @el.closest('.content') + ) class App.ChannelEmailInboundEdit extends App.ControllerModal From 64794c37672329bf88b0953167ce6b74e96be967 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Sat, 7 Feb 2015 23:43:27 +0100 Subject: [PATCH 23/42] Introduced new @P() to print values in templates based on attribute definition. --- .../_application_controller.js.coffee | 27 ++-- .../_application_controller_table.js.coffee | 2 +- .../app/controllers/navigation.js.coffee | 3 - app/assets/javascripts/app/index.js.coffee | 120 +++++++++++++++--- .../app/models/email_address.js.coffee | 10 +- .../javascripts/app/models/group.js.coffee | 18 +-- .../javascripts/app/models/job.js.coffee | 10 +- .../javascripts/app/models/network.js.coffee | 8 +- .../models/object_manager_attribute.js.coffee | 10 +- .../app/models/organization.js.coffee | 10 +- .../javascripts/app/models/overview.js.coffee | 10 +- .../app/models/postmaster_filter.js.coffee | 14 +- .../javascripts/app/models/role.js.coffee | 14 +- .../app/models/signature.js.coffee | 16 +-- .../javascripts/app/models/sla.js.coffee | 10 +- .../app/models/text_module.js.coffee | 10 +- .../javascripts/app/models/ticket.js.coffee | 20 +-- .../app/models/ticket_article.js.coffee | 26 ++-- .../app/models/ticket_priority.js.coffee | 8 +- .../app/models/ticket_state.js.coffee | 8 +- .../javascripts/app/models/user.js.coffee | 38 +++--- .../views/agent_ticket_view/detail.jst.eco | 12 +- .../app/views/generic/table.jst.eco | 60 +++------ .../views/organization_profile/object.jst.eco | 2 +- .../app/views/popover/organization.jst.eco | 8 +- .../app/views/popover/ticket.jst.eco | 16 +-- .../app/views/popover/user.jst.eco | 6 +- .../views/popover/user_ticket_list.jst.eco | 4 +- .../app/views/ticket_zoom/title.jst.eco | 2 +- .../app/views/user_profile/object.jst.eco | 2 +- .../app/views/widget/organization.jst.eco | 2 +- .../javascripts/app/views/widget/user.jst.eco | 2 +- app/assets/stylesheets/zammad.css.scss | 16 ++- app/controllers/tickets_controller.rb | 4 +- app/models/ticket/screen_options.rb | 21 ++- config/routes/test.rb | 1 + public/assets/tests/model-ui.js | 79 ++++++++++++ public/assets/tests/table.js | 5 +- test/browser/aab_unit_test.rb | 22 ++++ 39 files changed, 409 insertions(+), 247 deletions(-) create mode 100644 public/assets/tests/model-ui.js diff --git a/app/assets/javascripts/app/controllers/_application_controller.js.coffee b/app/assets/javascripts/app/controllers/_application_controller.js.coffee index feae627ef..0bef0a08c 100644 --- a/app/assets/javascripts/app/controllers/_application_controller.js.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller.js.coffee @@ -369,7 +369,7 @@ class App.Controller extends Spine.Controller userTicketPopups: (params) -> - show = (data, tickets) => + show = (data, ticket_list) => if !data.position data.position = 'left' @@ -390,15 +390,14 @@ class App.Controller extends Spine.Controller content: -> type = $(@).filter('[data-type]').data('type') - data = tickets[type] || [] - - # set human time - for ticket in data - ticket.humanTime = controller.humanTime(ticket.created_at) + tickets = [] + if ticket_list[type] + for ticket_id in ticket_list[type] + tickets.push App.Ticket.fullLocal( ticket_id ) # insert data App.view('popover/user_ticket_list')( - tickets: data, + tickets: tickets, ) ) @@ -411,14 +410,18 @@ class App.Controller extends Spine.Controller } processData: true, success: (data, status, xhr) => - App.Store.write( "user-ticket-popover::#{params.user_id}", data.tickets ) - show( params, data.tickets ) + App.Store.write( "user-ticket-popover::#{params.user_id}", data ) + + # load assets + App.Collection.loadAssets( data.assets ) + + show( params, { open: data.ticket_ids_open, closed: data.ticket_ids_closed } ) ) # get data - tickets = App.Store.get( "user-ticket-popover::#{params.user_id}" ) - if tickets - show( params, tickets ) + data = App.Store.get( "user-ticket-popover::#{params.user_id}" ) + if data + show( params, { open: data.ticket_ids_open, closed: data.ticket_ids_closed } ) @delay( => fetch(params) diff --git a/app/assets/javascripts/app/controllers/_application_controller_table.js.coffee b/app/assets/javascripts/app/controllers/_application_controller_table.js.coffee index a218b4a7c..460e0fcf5 100644 --- a/app/assets/javascripts/app/controllers/_application_controller_table.js.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller_table.js.coffee @@ -82,7 +82,7 @@ class App.ControllerTable extends App.Controller el: element overview: ['time', 'area', 'level', 'browser', 'location', 'data'] attributes: [ - { name: 'time', display: 'Time', type: 'time' }, + { name: 'time', display: 'Time', tag: 'datetime' }, { name: 'area', display: 'Area', type: 'text' }, { name: 'level', display: 'Level', type: 'text' }, { name: 'browser', display: 'Browser', type: 'text' }, diff --git a/app/assets/javascripts/app/controllers/navigation.js.coffee b/app/assets/javascripts/app/controllers/navigation.js.coffee index 53ede62f5..58cb9c5f2 100644 --- a/app/assets/javascripts/app/controllers/navigation.js.coffee +++ b/app/assets/javascripts/app/controllers/navigation.js.coffee @@ -156,11 +156,8 @@ class App.Navigation extends App.Controller area.result = [] for id in area.ids ticket = App.Ticket.find( id ) - ticket.humanTime = @humanTime(ticket.created_at) data = display: "##{ticket.number} - #{ticket.title}" - createt_at: "#{ticket.created_at}" - humanTime: "#{ticket.humanTime}" id: ticket.id class: "task level-1 ticket-popover" url: ticket.uiUrl() diff --git a/app/assets/javascripts/app/index.js.coffee b/app/assets/javascripts/app/index.js.coffee index f628c4e61..3e9458097 100644 --- a/app/assets/javascripts/app/index.js.coffee +++ b/app/assets/javascripts/app/index.js.coffee @@ -10,29 +10,111 @@ #= require_tree ./lib/app_post class App extends Spine.Controller + @viewPrint: (object, attribute_name) -> + attributes = {} + if object.constructor.attributesGet + attributes = object.constructor.attributesGet() + attribute_config = attributes[attribute_name] + value = object[attribute_name] + valueRef = undefined + + # check if relation is requested + if !attribute_config + attribute_name_new = "#{attribute_name}_id" + attribute_config = attributes[attribute_name_new] + if attribute_config + attribute_name = attribute_name_new + if object[attribute_name] + valueRef = value + value = object[attribute_name] + + # in case of :: key, get the sub value + if !value + parts = attribute_name.split('::') + if parts[0] && parts[1] && object[ parts[0] ] + value = object[ parts[0] ][ parts[1] ] + + #console.log('Pa', attribute_name, object, attribute_config, object[attribute_name], valueRef, value) + + # if we have no config, get output this way + if !attribute_config + return @viewPrintItem( value ) + + # check if valueRef already exists, no lookup needed later + if !valueRef + attribute_name_without_ref = attribute_name.substr(attribute_name.length-3, attribute_name.length) + if attribute_name_without_ref is '_id' + attribute_name_without_ref = attribute_name.substr(0, attribute_name.length-3) + if object[attribute_name_without_ref] + valueRef = object[attribute_name_without_ref] + + return @viewPrintItem( value, attribute_config, valueRef ) + + # define print name helper + @viewPrintItem: ( item, attribute_config = {}, valueRef ) -> + return '-' if item is undefined + return '-' if item is '' + return item if !item + result = item + + # lookup relation + if attribute_config.relation || valueRef + if valueRef + item = valueRef + else + item = App[attribute_config.relation].find(item) + + # if date is a object, get name of the object + isObject = false + if typeof item is 'object' + isObject = true + if item.displayNameLong + result = item.displayNameLong() + else if item.displayName + result = item.displayName() + else + result = item.name + + # execute callback on content + if attribute_config.callback + result = attribute_config.callback( result, attribute_config ) + + # text2html in textarea view + if attribute_config.tag is 'textarea' + result = App.Utils.text2html( result ) + + # fillup options + if !_.isEmpty(attribute_config.options) + if attribute_config.options[result] + result = attribute_config.options[result] + + # translate content + isTranslated = false + if attribute_config.translate || ( isObject && item.translate && item.translate() ) + isTranslated = true + result = App.i18n.translateContent( result ) + + # transform date + if attribute_config.tag is 'date' + result = App.i18n.translateDate(result) + + # use pretty time for datetime + else if attribute_config.tag is 'datetime' + result = "?" + #result = App.i18n.translateTimestamp(result) + + else if !isTranslated + if typeof result is 'string' + result = App.Utils.htmlEscape(result) + + result + @view: (name) -> template = ( params = {} ) => # define print name helper - params.P = ( item, row = {} ) -> - return '-' if item is undefined - return '-' if item is '' - return item if !item - - # if date is a object, get name of the object - if typeof item is 'object' - if item.displayNameLong - return item.displayNameLong() - else if item.displayName - return item.displayName() - return item.name - - # execute callback on content - if row.callback - return row.callback( item, row ) - - # return raw data - item + params.P = ( object, attribute_name ) -> + App.viewPrint( object, attribute_name ) # define date format helper params.date = ( time ) -> diff --git a/app/assets/javascripts/app/models/email_address.js.coffee b/app/assets/javascripts/app/models/email_address.js.coffee index 0a85fa3d0..a2d045240 100644 --- a/app/assets/javascripts/app/models/email_address.js.coffee +++ b/app/assets/javascripts/app/models/email_address.js.coffee @@ -4,11 +4,11 @@ class App.EmailAddress extends App.Model @url: @apiPath + '/email_addresses' @configure_attributes = [ - { name: 'realname', display: 'Realname', tag: 'input', type: 'text', limit: 250, 'null': false, 'class': 'span4' }, - { name: 'email', display: 'Email', tag: 'input', type: 'text', limit: 250, 'null': false, 'class': 'span4' }, - { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, 'null': true, 'class': 'span4' }, - { name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, - { name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false, 'class': 'span4' }, + { name: 'realname', display: 'Realname', tag: 'input', type: 'text', limit: 250, null: false }, + { name: 'email', display: 'Email', tag: 'input', type: 'text', limit: 250, null: false }, + { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true }, + { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, + { name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', default: true, null: false }, ] @configure_overview = [ 'realname', 'email' diff --git a/app/assets/javascripts/app/models/group.js.coffee b/app/assets/javascripts/app/models/group.js.coffee index b07a43791..85f6716e2 100644 --- a/app/assets/javascripts/app/models/group.js.coffee +++ b/app/assets/javascripts/app/models/group.js.coffee @@ -4,15 +4,15 @@ class App.Group extends App.Model @url: @apiPath + '/groups' @configure_attributes = [ - { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, 'class': 'span4' }, - { name: 'assignment_timeout', display: 'Assignment Timeout', tag: 'input', note: 'Assignment timeout in minutes if assigned agent is not working on it. Ticket will be shown as unassigend.', type: 'text', limit: 100, 'null': true, 'class': 'span4' }, - { name: 'follow_up_possible', display: 'Follow up possible',tag: 'select', default: 'yes', options: { yes: 'yes', reject: 'reject follow up/do not reopen Ticket', 'new_ticket': 'do not reopen Ticket but create new Ticket' }, 'null': false, note: 'Follow up for closed ticket possible or not.', 'class': 'span4' }, - { name: 'follow_up_assignment', display: 'Assign Follow Ups', tag: 'select', default: 'yes', options: { true: 'yes', false: 'no' }, 'null': false, note: 'Assign follow up to latest agent again.', 'class': 'span4' }, - { name: 'email_address_id', display: 'Email', tag: 'select', multiple: false, null: true, relation: 'EmailAddress', nulloption: true, class: 'span4' }, - { name: 'signature_id', display: 'Signature', tag: 'select', multiple: false, null: true, relation: 'Signature', nulloption: true, class: 'span4' }, - { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, 'null': true, 'class': 'span4' }, - { name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, - { name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false, 'class': 'span4' }, + { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false }, + { name: 'assignment_timeout', display: 'Assignment Timeout', tag: 'input', note: 'Assignment timeout in minutes if assigned agent is not working on it. Ticket will be shown as unassigend.', type: 'text', limit: 100, null: true }, + { name: 'follow_up_possible', display: 'Follow up possible',tag: 'select', default: 'yes', options: { yes: 'yes', reject: 'reject follow up/do not reopen Ticket', 'new_ticket': 'do not reopen Ticket but create new Ticket' }, null: false, note: 'Follow up for closed ticket possible or not.' }, + { name: 'follow_up_assignment', display: 'Assign Follow Ups', tag: 'select', default: 'yes', options: { true: 'yes', false: 'no' }, 'null': false, note: 'Assign follow up to latest agent again.' }, + { name: 'email_address_id', display: 'Email', tag: 'select', multiple: false, null: true, relation: 'EmailAddress', nulloption: true }, + { name: 'signature_id', display: 'Signature', tag: 'select', multiple: false, null: true, relation: 'Signature', nulloption: true }, + { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true }, + { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, + { name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', default: true, null: false }, ] @configure_overview = [ 'name', diff --git a/app/assets/javascripts/app/models/job.js.coffee b/app/assets/javascripts/app/models/job.js.coffee index 95b2e1b0f..d73984319 100644 --- a/app/assets/javascripts/app/models/job.js.coffee +++ b/app/assets/javascripts/app/models/job.js.coffee @@ -8,15 +8,15 @@ class App.Job extends App.Model { name: 'condition', display: 'Conditions for matching objects.', tag: 'ticket_attribute_selection', null: true }, { name: 'execute', display: 'Execute changes on objects.', tag: 'ticket_attribute_set', null: true }, { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true }, - { name: 'active', display: 'Active', tag: 'boolean', note: 'boolean', 'default': true, null: false }, + { name: 'active', display: 'Active', tag: 'boolean', note: 'boolean', default: true, null: false }, { name: 'matching', display: 'Matching', readonly: 1 }, - { name: 'processed', display: 'Processed', readonly: 1 }, - { name: 'last_run_at', display: 'Last run', type: 'time', readonly: 1 }, + { name: 'processed', display: 'Processed', readonly: 1 }, + { name: 'last_run_at', display: 'Last run', tag: 'datetime', readonly: 1 }, { name: 'running', display: 'Running', tag: 'boolean', readonly: 1 }, { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, - { name: 'created_at', display: 'Created', type: 'time', readonly: 1 }, + { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 }, { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, - { name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, + { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, ] @configure_delete = true @configure_overview = [ diff --git a/app/assets/javascripts/app/models/network.js.coffee b/app/assets/javascripts/app/models/network.js.coffee index d33c60e15..f5871cd59 100644 --- a/app/assets/javascripts/app/models/network.js.coffee +++ b/app/assets/javascripts/app/models/network.js.coffee @@ -2,8 +2,8 @@ class App.Network extends App.Model @configure 'Network', 'name', 'note', 'active', 'updated_at' @extend Spine.Model.Ajax @configure_attributes = [ - { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, 'class': 'xlarge' }, - { name: 'note', display: 'Note', note: 'Notes are visible to agents only, never to customers.', tag: 'textarea', limit: 250, 'null': true, 'class': 'xlarge' }, - { name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, - { name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false, 'class': 'xlarge' }, + { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false }, + { name: 'note', display: 'Note', note: 'Notes are visible to agents only, never to customers.', tag: 'textarea', limit: 250, null: true }, + { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, + { name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', default: true, null: false }, ] diff --git a/app/assets/javascripts/app/models/object_manager_attribute.js.coffee b/app/assets/javascripts/app/models/object_manager_attribute.js.coffee index 0611d8c67..bddd641fa 100644 --- a/app/assets/javascripts/app/models/object_manager_attribute.js.coffee +++ b/app/assets/javascripts/app/models/object_manager_attribute.js.coffee @@ -3,13 +3,13 @@ class App.ObjectManagerAttribute extends App.Model @extend Spine.Model.Ajax @url: @apiPath + '/object_manager_attributes' @configure_attributes = [ - { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false }, - { name: 'display', display: 'Anzeige', tag: 'input', type: 'text', limit: 100, 'null': false }, + { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false }, + { name: 'display', display: 'Anzeige', tag: 'input', type: 'text', limit: 100, null: false }, { name: 'object', display: 'Object', tag: 'input', readonly: 1 }, { name: 'position', display: 'Position', tag: 'input', readonly: 1 }, - { name: 'active', display: 'Active', tag: 'boolean', 'default': true, 'null': false }, - { name: 'data_type', display: 'Format', tag: 'input', type: 'text', limit: 100, 'null': false }, - { name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, + { name: 'active', display: 'Active', tag: 'boolean', default: true, null: false }, + { name: 'data_type', display: 'Format', tag: 'input', type: 'text', limit: 100, null: false }, + { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, ] @configure_overview = [ #'name', diff --git a/app/assets/javascripts/app/models/organization.js.coffee b/app/assets/javascripts/app/models/organization.js.coffee index 9b6761673..fac2fb4b4 100644 --- a/app/assets/javascripts/app/models/organization.js.coffee +++ b/app/assets/javascripts/app/models/organization.js.coffee @@ -3,11 +3,11 @@ class App.Organization extends App.Model @extend Spine.Model.Ajax @url: @apiPath + '/organizations' @configure_attributes = [ - { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, info: true }, - { name: 'shared', display: 'Shared organization', tag: 'boolean', note: 'Customers in the organization can view each other items.', type: 'boolean', 'default': true, 'null': false, info: false }, - { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, 'null': true, info: true }, - { name: 'updated_at', display: 'Updated', type: 'time', readonly: 1, info: false }, - { name: 'active', display: 'Active', tag: 'boolean', 'default': true, 'null': false, info: false }, + { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false, info: true }, + { name: 'shared', display: 'Shared organization', tag: 'boolean', note: 'Customers in the organization can view each other items.', type: 'boolean', default: true, null: false, info: false }, + { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true, info: true }, + { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1, info: false }, + { name: 'active', display: 'Active', tag: 'boolean', default: true, null: false, info: false }, ] @configure_overview = [ 'name', diff --git a/app/assets/javascripts/app/models/overview.js.coffee b/app/assets/javascripts/app/models/overview.js.coffee index c82c09646..8a344b63a 100644 --- a/app/assets/javascripts/app/models/overview.js.coffee +++ b/app/assets/javascripts/app/models/overview.js.coffee @@ -131,11 +131,11 @@ class App.Overview extends App.Model owner: 'Owner' class: 'span4' }, - { name: 'active', display: 'Active', tag: 'boolean', note: 'boolean', 'default': true, 'null': false, 'class': 'span4' }, - { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, - { name: 'created_at', display: 'Created', type: 'time', readonly: 1 }, - { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, - { name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, + { name: 'active', display: 'Active', tag: 'boolean', note: 'boolean', default: true, null: false }, + { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, + { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 }, + { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, + { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, ] @configure_delete = true @configure_overview = [ diff --git a/app/assets/javascripts/app/models/postmaster_filter.js.coffee b/app/assets/javascripts/app/models/postmaster_filter.js.coffee index eea1540e7..d5439fbc2 100644 --- a/app/assets/javascripts/app/models/postmaster_filter.js.coffee +++ b/app/assets/javascripts/app/models/postmaster_filter.js.coffee @@ -8,13 +8,13 @@ class App.PostmasterFilter extends App.Model { name: 'channel', display: 'Channel', type: 'input', readonly: 1 }, { name: 'match', display: 'Match all of the following', tag: 'postmaster_match' }, { name: 'perform', display: 'Perform action of the following', tag: 'postmaster_set' }, - { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, 'null': true }, - { name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, - { name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false }, - { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, - { name: 'created_at', display: 'Created', type: 'time', readonly: 1 }, - { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, - { name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, + { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true }, + { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, + { name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', default: true, null: false }, + { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, + { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 }, + { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, + { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, ] @configure_delete = true @configure_overview = [ diff --git a/app/assets/javascripts/app/models/role.js.coffee b/app/assets/javascripts/app/models/role.js.coffee index fa733bb4e..660dce149 100644 --- a/app/assets/javascripts/app/models/role.js.coffee +++ b/app/assets/javascripts/app/models/role.js.coffee @@ -3,13 +3,13 @@ class App.Role extends App.Model @extend Spine.Model.Ajax @url: @apiPath + '/roles' @configure_attributes = [ - { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false }, - { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true }, - { name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, null: false }, - { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, - { name: 'created_at', display: 'Created', type: 'time', readonly: 1 }, - { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, - { name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, + { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false }, + { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true }, + { name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, null: false }, + { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, + { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 }, + { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, + { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, ] @configure_overview = [ 'name', diff --git a/app/assets/javascripts/app/models/signature.js.coffee b/app/assets/javascripts/app/models/signature.js.coffee index 582608372..68af43b08 100644 --- a/app/assets/javascripts/app/models/signature.js.coffee +++ b/app/assets/javascripts/app/models/signature.js.coffee @@ -4,14 +4,14 @@ class App.Signature extends App.Model @url: @apiPath + '/signatures' @configure_attributes = [ - { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, 'class': 'span4' }, - { name: 'body', display: 'Text', tag: 'textarea', limit: 250, 'null': true, 'class': 'span4', rows: 10 }, - { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, 'null': true, 'class': 'span4' }, - { name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false, 'class': 'span4' }, - { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, - { name: 'created_at', display: 'Created', type: 'time', readonly: 1 }, - { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, - { name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, + { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false }, + { name: 'body', display: 'Text', tag: 'textarea', limit: 250, 'null': true, rows: 10 }, + { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, 'null': true }, + { name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false }, + { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, + { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 }, + { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, + { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, ] @configure_overview = [ 'name', diff --git a/app/assets/javascripts/app/models/sla.js.coffee b/app/assets/javascripts/app/models/sla.js.coffee index e85682eaf..a89a31e52 100644 --- a/app/assets/javascripts/app/models/sla.js.coffee +++ b/app/assets/javascripts/app/models/sla.js.coffee @@ -31,11 +31,11 @@ class App.Sla extends App.Model group: 'Group' owner: 'Owner' }, - { name: 'active', display: 'Active', tag: 'boolean', note: 'boolean', 'default': true, 'null': false }, - { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, - { name: 'created_at', display: 'Created', type: 'time', readonly: 1 }, - { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, - { name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, + { name: 'active', display: 'Active', tag: 'boolean', note: 'boolean', default: true, null: false }, + { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, + { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 }, + { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, + { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, ] @configure_delete = true @configure_overview = [ diff --git a/app/assets/javascripts/app/models/text_module.js.coffee b/app/assets/javascripts/app/models/text_module.js.coffee index b35c9cfee..8d9425129 100644 --- a/app/assets/javascripts/app/models/text_module.js.coffee +++ b/app/assets/javascripts/app/models/text_module.js.coffee @@ -3,11 +3,11 @@ class App.TextModule extends App.Model @extend Spine.Model.Ajax @url: @apiPath + '/text_modules' @configure_attributes = [ - { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, 'class': 'span4' }, - { name: 'keywords', display: 'Keywords', tag: 'input', type: 'text', limit: 100, 'null': true, 'class': 'span4' }, - { name: 'content', display: 'Content', tag: 'textarea', limit: 250, 'null': false, 'class': 'span4' }, - { name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, - { name: 'active', display: 'Active', tag: 'boolean', note: 'boolean', 'default': true, 'null': false, 'class': 'span4' }, + { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, 'class': 'span4' }, + { name: 'keywords', display: 'Keywords', tag: 'input', type: 'text', limit: 100, 'null': true, 'class': 'span4' }, + { name: 'content', display: 'Content', tag: 'textarea', limit: 250, 'null': false, 'class': 'span4' }, + { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, + { name: 'active', display: 'Active', tag: 'boolean', note: 'boolean', 'default': true, 'null': false, 'class': 'span4' }, ] @configure_delete = true @configure_overview = [ diff --git a/app/assets/javascripts/app/models/ticket.js.coffee b/app/assets/javascripts/app/models/ticket.js.coffee index 25fcc3f81..8b28ee53c 100644 --- a/app/assets/javascripts/app/models/ticket.js.coffee +++ b/app/assets/javascripts/app/models/ticket.js.coffee @@ -11,18 +11,18 @@ class App.Ticket extends App.Model { name: 'title', display: 'Title', tag: 'input', type: 'text', limit: 100, null: false, parentClass: 'noTruncate' }, { name: 'state_id', display: 'State', tag: 'select', multiple: false, null: false, relation: 'TicketState', default: 'new', style: 'width: 12%', edit: true, customer: true, }, { name: 'priority_id', display: 'Priority', tag: 'select', multiple: false, null: false, relation: 'TicketPriority', default: '2 normal', style: 'width: 12%', edit: true, customer: true, }, - { name: 'last_contact', display: 'Last contact', type: 'time', null: true, style: 'width: 12%', parentClass: 'noTruncate' }, - { name: 'last_contact_agent', display: 'Last contact (Agent)', type: 'time', null: true, style: 'width: 12%', parentClass: 'noTruncate' }, - { name: 'last_contact_customer', display: 'Last contact (Customer)', type: 'time', null: true, style: 'width: 12%', parentClass: 'noTruncate' }, - { name: 'first_response', display: 'First response', type: 'time', null: true, style: 'width: 12%', parentClass: 'noTruncate' }, - { name: 'close_time', display: 'Close time', type: 'time', null: true, style: 'width: 12%', parentClass: 'noTruncate' }, - { name: 'pending_time', display: 'Pending Time', type: 'time', null: true, style: 'width: 12%', parentClass: 'noTruncate' }, - { name: 'escalation_time', display: 'Escalation', type: 'time', null: true, style: 'width: 12%', class: 'escalation', parentClass: 'noTruncate' }, - { name: 'article_count', display: 'Article#', style: 'width: 12%' }, + { name: 'last_contact', display: 'Last contact', tag: 'datetime', null: true, style: 'width: 12%', parentClass: 'noTruncate' }, + { name: 'last_contact_agent', display: 'Last contact (Agent)', tag: 'datetime', null: true, style: 'width: 12%', parentClass: 'noTruncate' }, + { name: 'last_contact_customer', display: 'Last contact (Customer)', tag: 'datetime', null: true, style: 'width: 12%', parentClass: 'noTruncate' }, + { name: 'first_response', display: 'First response', tag: 'datetime', null: true, style: 'width: 12%', parentClass: 'noTruncate' }, + { name: 'close_time', display: 'Close time', tag: 'datetime', null: true, style: 'width: 12%', parentClass: 'noTruncate' }, + { name: 'pending_time', display: 'Pending Time', tag: 'datetime', null: true, style: 'width: 12%', parentClass: 'noTruncate' }, + { name: 'escalation_time', display: 'Escalation', tag: 'datetime', null: true, style: 'width: 12%', class: 'escalation', parentClass: 'noTruncate' }, + { name: 'article_count', display: 'Article#', style: 'width: 12%' }, { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, - { name: 'created_at', display: 'Created', type: 'time', style: 'width: 120px', readonly: 1, parentClass: 'noTruncate' }, + { name: 'created_at', display: 'Created', tag: 'datetime', style: 'width: 120px', readonly: 1, parentClass: 'noTruncate' }, { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, - { name: 'updated_at', display: 'Updated', type: 'time', style: 'width: 120px', readonly: 1, parentClass: 'noTruncate' }, + { name: 'updated_at', display: 'Updated', tag: 'datetime', style: 'width: 120px', readonly: 1, parentClass: 'noTruncate' }, ] uiUrl: -> diff --git a/app/assets/javascripts/app/models/ticket_article.js.coffee b/app/assets/javascripts/app/models/ticket_article.js.coffee index 496d9a265..67a42cc76 100644 --- a/app/assets/javascripts/app/models/ticket_article.js.coffee +++ b/app/assets/javascripts/app/models/ticket_article.js.coffee @@ -3,19 +3,19 @@ class App.TicketArticle extends App.Model @extend Spine.Model.Ajax @url: @apiPath + '/ticket_articles' @configure_attributes = [ - { name: 'ticket_id', display: 'TicketID', null: false, readonly: 1, }, - { name: 'from', display: 'From', tag: 'input', type: 'text', limit: 100, null: false, }, - { name: 'to', display: 'To', tag: 'input', type: 'text', limit: 100, null: true, }, - { name: 'cc', display: 'Cc', tag: 'input', type: 'text', limit: 100, null: true, }, - { name: 'subject', display: 'Subject', tag: 'input', type: 'text', limit: 100, null: true, }, - { name: 'body', display: 'Text', tag: 'textarea', rows: 5, limit: 100, null: false, }, - { name: 'type_id', display: 'Type', tag: 'select', multiple: false, null: false, relation: 'TicketArticleType', default: '', class: 'medium' }, - { name: 'sender_id', display: 'Sender', tag: 'select', multiple: false, null: false, relation: 'TicketArticleSender', default: '', class: 'medium' }, - { name: 'internal', display: 'Visibility', tag: 'radio', default: false, null: true, options: { true: 'internal', false: 'public' }, class: 'medium' }, - { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, - { name: 'created_at', display: 'Created', type: 'time', readonly: 1 }, - { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, - { name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, + { name: 'ticket_id', display: 'TicketID', null: false, readonly: 1, }, + { name: 'from', display: 'From', tag: 'input', type: 'text', limit: 100, null: false }, + { name: 'to', display: 'To', tag: 'input', type: 'text', limit: 100, null: true }, + { name: 'cc', display: 'Cc', tag: 'input', type: 'text', limit: 100, null: true }, + { name: 'subject', display: 'Subject', tag: 'input', type: 'text', limit: 100, null: true }, + { name: 'body', display: 'Text', tag: 'textarea', rows: 5, limit: 100, null: false }, + { name: 'type_id', display: 'Type', tag: 'select', multiple: false, null: false, relation: 'TicketArticleType', default: '' }, + { name: 'sender_id', display: 'Sender', tag: 'select', multiple: false, null: false, relation: 'TicketArticleSender', default: '' }, + { name: 'internal', display: 'Visibility', tag: 'radio', default: false, null: true, options: { true: 'internal', false: 'public' } }, + { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, + { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 }, + { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, + { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, ] uiUrl: -> diff --git a/app/assets/javascripts/app/models/ticket_priority.js.coffee b/app/assets/javascripts/app/models/ticket_priority.js.coffee index 6139d808d..2051f025e 100644 --- a/app/assets/javascripts/app/models/ticket_priority.js.coffee +++ b/app/assets/javascripts/app/models/ticket_priority.js.coffee @@ -3,10 +3,10 @@ class App.TicketPriority extends App.Model @extend Spine.Model.Ajax @url: @apiPath + '/ticket_priorities' @configure_attributes = [ - { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, translate: true }, - { name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false }, - { name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, - { name: 'created_at', display: 'Created', type: 'time', readonly: 1 }, + { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false, translate: true }, + { name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', default: true, null: false }, + { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, + { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 }, ] @configure_translate = true @configure_overview = [ diff --git a/app/assets/javascripts/app/models/ticket_state.js.coffee b/app/assets/javascripts/app/models/ticket_state.js.coffee index c54cfb8c2..649e6dcdc 100644 --- a/app/assets/javascripts/app/models/ticket_state.js.coffee +++ b/app/assets/javascripts/app/models/ticket_state.js.coffee @@ -3,10 +3,10 @@ class App.TicketState extends App.Model @extend Spine.Model.Ajax @url: @apiPath + '/ticket_states' @configure_attributes = [ - { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, translate: true }, - { name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false }, - { name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, - { name: 'created_at', display: 'Created', type: 'time', readonly: 1 }, + { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false, translate: true }, + { name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', default: true, null: false }, + { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, + { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 }, ] @configure_translate = true @configure_overview = [ diff --git a/app/assets/javascripts/app/models/user.js.coffee b/app/assets/javascripts/app/models/user.js.coffee index ddea8b142..70a967f61 100644 --- a/app/assets/javascripts/app/models/user.js.coffee +++ b/app/assets/javascripts/app/models/user.js.coffee @@ -5,25 +5,25 @@ class App.User extends App.Model # @hasMany 'roles', 'App.Role' @configure_attributes = [ - { name: 'login', display: 'Login', tag: 'input', type: 'text', limit: 100, null: false, class: 'span4', autocapitalize: false, signup: false, quick: false }, - { name: 'firstname', display: 'Firstname', tag: 'input', type: 'text', limit: 100, null: false, class: 'span4', signup: true, info: true, invite_agent: true }, - { name: 'lastname', display: 'Lastname', tag: 'input', type: 'text', limit: 100, null: false, class: 'span4', signup: true, info: true, invite_agent: true }, - { name: 'email', display: 'Email', tag: 'input', type: 'email', limit: 100, null: false, class: 'span4', signup: true, info: true, invite_agent: true }, - { name: 'web', display: 'Web', tag: 'input', type: 'url', limit: 100, null: true, class: 'span4', signup: false, info: true }, - { name: 'phone', display: 'Phone', tag: 'input', type: 'phone', limit: 100, null: true, class: 'span4', signup: false, info: true }, - { name: 'mobile', display: 'Mobile', tag: 'input', type: 'phone', limit: 100, null: true, class: 'span4', signup: false, info: true }, - { name: 'fax', display: 'Fax', tag: 'input', type: 'phone', limit: 100, null: true, class: 'span4', signup: false, info: true }, - { name: 'organization_id', display: 'Organization', tag: 'select', multiple: false, nulloption: true, null: true, relation: 'Organization', class: 'span4', signup: false, info: true }, - { name: 'department', display: 'Department', tag: 'input', type: 'text', limit: 200, null: true, class: 'span4', signup: false, info: true }, - { name: 'street', display: 'Street', tag: 'input', type: 'text', limit: 100, null: true, class: 'span4', signup: false, info: true }, - { name: 'zip', display: 'Zip', tag: 'input', type: 'text', limit: 100, null: true, class: 'span4', signup: false, info: true }, - { name: 'city', display: 'City', tag: 'input', type: 'text', limit: 100, null: true, class: 'span4', signup: false, info: true }, - { name: 'password', display: 'Password', tag: 'input', type: 'password', limit: 50, null: true, autocomplete: 'off', class: 'span4', signup: true, }, - { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true, class: 'span4', info: true }, - { name: 'role_ids', display: 'Roles', tag: 'checkbox', multiple: true, null: false, relation: 'Role', class: 'span4' }, - { name: 'group_ids', display: 'Groups', tag: 'checkbox', multiple: true, null: true, relation: 'Group', class: 'span4', invite_agent: true }, - { name: 'active', display: 'Active', tag: 'boolean', default: true, null: true, class: 'span4' }, - { name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, + { name: 'login', display: 'Login', tag: 'input', type: 'text', limit: 100, null: false, autocapitalize: false, signup: false, quick: false }, + { name: 'firstname', display: 'Firstname', tag: 'input', type: 'text', limit: 100, null: false, signup: true, info: true, invite_agent: true }, + { name: 'lastname', display: 'Lastname', tag: 'input', type: 'text', limit: 100, null: false, signup: true, info: true, invite_agent: true }, + { name: 'email', display: 'Email', tag: 'input', type: 'email', limit: 100, null: false, signup: true, info: true, invite_agent: true }, + { name: 'web', display: 'Web', tag: 'input', type: 'url', limit: 100, null: true, signup: false, info: true }, + { name: 'phone', display: 'Phone', tag: 'input', type: 'phone', limit: 100, null: true, signup: false, info: true }, + { name: 'mobile', display: 'Mobile', tag: 'input', type: 'phone', limit: 100, null: true, signup: false, info: true }, + { name: 'fax', display: 'Fax', tag: 'input', type: 'phone', limit: 100, null: true, signup: false, info: true }, + { name: 'organization_id', display: 'Organization', tag: 'select', multiple: false, nulloption: true, null: true, relation: 'Organization', signup: false, info: true }, + { name: 'department', display: 'Department', tag: 'input', type: 'text', limit: 200, null: true, signup: false, info: true }, + { name: 'street', display: 'Street', tag: 'input', type: 'text', limit: 100, null: true, signup: false, info: true }, + { name: 'zip', display: 'Zip', tag: 'input', type: 'text', limit: 100, null: true, signup: false, info: true }, + { name: 'city', display: 'City', tag: 'input', type: 'text', limit: 100, null: true, signup: false, info: true }, + { name: 'password', display: 'Password', tag: 'input', type: 'password', limit: 50, null: true, autocomplete: 'off', signup: true, }, + { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true, info: true }, + { name: 'role_ids', display: 'Roles', tag: 'checkbox', multiple: true, null: false, relation: 'Role' }, + { name: 'group_ids', display: 'Groups', tag: 'checkbox', multiple: true, null: true, relation: 'Group', invite_agent: true }, + { name: 'active', display: 'Active', tag: 'boolean', default: true, null: true }, + { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, ] @configure_overview = [ # 'login', 'firstname', 'lastname', 'email', 'updated_at', diff --git a/app/assets/javascripts/app/views/agent_ticket_view/detail.jst.eco b/app/assets/javascripts/app/views/agent_ticket_view/detail.jst.eco index b370be5f8..a32548e19 100644 --- a/app/assets/javascripts/app/views/agent_ticket_view/detail.jst.eco +++ b/app/assets/javascripts/app/views/agent_ticket_view/detail.jst.eco @@ -17,24 +17,24 @@

<%= ticket.title %> <%= ticket.number %> ?

- <%- @T( 'State' ) %> <%- @T( ticket.state.name ) %> + <%- @T( 'State' ) %> <%- @P( ticket, 'state' ) %>
- <%- @T( 'Group' ) %> <%= ticket.group.name %> + <%- @T( 'Group' ) %> <%- @P( ticket, 'group' ) %>
- <%- @T( 'Customer' ) %> <%= ticket.customer.displayName() %> + <%- @T( 'Customer' ) %> <%- @P( ticket, 'customer' ) %>
- <%- @T( 'Priority' ) %> <%- @T( ticket.priority.name ) %> + <%- @T( 'Priority' ) %> <%- @P( ticket, 'priority' ) %>
- <%- @T( 'Owner' ) %> <%= ticket.owner.displayName() %> + <%- @T( 'Owner' ) %> <%- @P( ticket, 'owner' ) %>
- <%- @T( 'Organization' ) %> <%= @P( ticket.customer.organization ) %> + <%- @T( 'Organization' ) %> <%- @P( ticket, 'organization' ) %>
diff --git a/app/assets/javascripts/app/views/generic/table.jst.eco b/app/assets/javascripts/app/views/generic/table.jst.eco index 1a324f996..003c2318f 100644 --- a/app/assets/javascripts/app/views/generic/table.jst.eco +++ b/app/assets/javascripts/app/views/generic/table.jst.eco @@ -29,16 +29,9 @@ <% groupLast = '' %> <% for object in @objects: %> <% if @groupBy: %> - <% if object[@groupBy] && object[@groupBy].displayName: %> - <% groupByName = object[@groupBy].displayName() %> - <% if object[@groupBy].translate(): %> - <% groupByName = @T(groupByName) %> - <% end %> - <% else: %> - <% groupByName = object[@groupBy] || '-' %> - <% end %> + <% groupByName = @P( object, @groupBy ) %> <% if groupLast isnt groupByName: %> - <%- @P( groupByName ) %> + <%= groupByName %> <% groupLast = groupByName %> <% end %> <% end %> @@ -56,47 +49,26 @@ <% end %> <% for item in @header: %> - <% translation = false %> - <% value = object[item.name] %> - <% item_id = item.name.substr(item.name.length-3, item.name.length) %> - <% if item_id is '_id' && object[ item.name.substr(0, item.name.length-3) ]: %> - <% value = object[ item.name.substr(0, item.name.length-3) ] %> - <% refObject = object[ item.name.substr(0, item.name.length-3) ] %> - <% end %> - <% if !value: %> - <% parts = item.name.split '::' %> - <% if parts[0] && parts[1] && object[ parts[0] ]: %> - <% value = object[ parts[0] ][ parts[1] ] %> - <% end %> - <% end %> - <% if value && value.displayNameLong : %> - <% translation = true %> - <% value = value.displayNameLong() %> - <% else if value && value.displayName : %> - <% translation = true %> - <% value = value.displayName() %> - <% end %> - <% item_clone = item %> + <% value = @P( object, item.name ) %> <% if @callbacks: %> + <% if item.name.substr(item.name.length-3, item.name.length) is '_id' && object[ item.name.substr(0, item.name.length-3) ]: %> + <% refObject = object[ item.name.substr(0, item.name.length-3) ] %> + <% end %> <% for attribute, callbacksAll of @callbacks: %> - <% if attribute is item.name || attribute is item_id: %> + <% if attribute is item.name: %> <% for callback in callbacksAll: %> - <% value = callback( value, object, item_clone, @header, refObject ) %> + <% value = callback( value, object, item, @header, refObject ) %> <% end %> <% end %> <% end %> <% end %> - <% #console.log('HH', item_clone.name, item_clone.type, item_clone.translate, item_clone, object.translate(), refObject, translation) %> - class="<%= item_clone.parentClass %>"<% end %>> - <% if item_clone.link: %>target="<%= item_clone.target %>"<% end %>><% end %> - <% if item_clone.translate || ( translation && !refObject && object.translate && object.translate() ) || ( translation && refObject && refObject.translate && refObject.translate() ) : %> - class="<%= item_clone.class %>"<% end %>><%- @T( @P( value, item_clone ) ) %> - <% else if item_clone.type is 'time': %> - ? - <% else: %> - class="<%= item_clone.class %>"<% end %> <% if item_clone.title: %>title="<%= item_clone.title %>"<% end %> <% if item_clone.data: %><% for data_key, data_item of item_clone.data: %>data-<%- data_key %>="<%= data_item %>" <% end %><% end %>><%= @P(value) %> - <% end %> - <% if item_clone.link: %><% end %> + + class="<%= item.parentClass %>"<% end %>> + + <% if item.link: %>target="<%= item.target %>"<% end %>><% end %> + class="<%= item.class %>"<% end %> <% if item.title: %>title="<%= item.title %>"<% end %> <% if item.data: %><% for data_key, data_item of item.data: %>data-<%- data_key %>="<%= data_item %>" <% end %><% end %>><%- value %> + <% if item.link: %><% end %> + <% end %> <% if @destroy: %> @@ -105,4 +77,4 @@ <% end %> - + \ No newline at end of file diff --git a/app/assets/javascripts/app/views/organization_profile/object.jst.eco b/app/assets/javascripts/app/views/organization_profile/object.jst.eco index c8eded5a5..738410c25 100644 --- a/app/assets/javascripts/app/views/organization_profile/object.jst.eco +++ b/app/assets/javascripts/app/views/organization_profile/object.jst.eco @@ -17,7 +17,7 @@ <% if @organization[row.name]: %>
- <%- @L( @P( @organization[row.name] ) ) %> + <%- @P( @organization, row.name ) %>
<% end %> <% end %> diff --git a/app/assets/javascripts/app/views/popover/organization.jst.eco b/app/assets/javascripts/app/views/popover/organization.jst.eco index 6301dc391..c0a1f0fdc 100644 --- a/app/assets/javascripts/app/views/popover/organization.jst.eco +++ b/app/assets/javascripts/app/views/popover/organization.jst.eco @@ -4,11 +4,7 @@ <% if @organization[row.name]: %>

<%- @T( row.display ) %>

- <% if row.tag is 'richtext': %> -
<%- @organization[row.name] %>
- <% else: %> -
<%- @L( @P( @organization[row.name] ) ) %>
- <% end %> +
<%- @P( @organization, row.name ) %>
<% end %> <% end %> @@ -18,7 +14,7 @@

<%- @T('Members') %>

<% for user in @organization.members: %>
- <%= user.displayName() %> + <%= user.displayName() %>
<% end %> <% end %> \ No newline at end of file diff --git a/app/assets/javascripts/app/views/popover/ticket.jst.eco b/app/assets/javascripts/app/views/popover/ticket.jst.eco index e7a5808b3..7d78a0677 100644 --- a/app/assets/javascripts/app/views/popover/ticket.jst.eco +++ b/app/assets/javascripts/app/views/popover/ticket.jst.eco @@ -2,17 +2,17 @@ <%- @ticket.iconTitle() %>

-

<%- @P('Agent') %>

+

<%- @T('Agent') %>

<%= @ticket.owner.displayName() %> - <% if @ticket.owner.organization_id: %> + <% if @ticket.owner.organization: %> <%= @ticket.owner.organization.displayName() %> <% end %>
-

<%- @P('Customer') %>

+

<%- @T('Customer') %>

<%= @ticket.customer.displayName() %> - <% if @ticket.customer.organization_id: %> + <% if @ticket.customer.organization: %> <%= @ticket.customer.organization.displayName() %> <% end %>
@@ -20,18 +20,18 @@

#

-
<%- @P( @ticket.number ) %>
+
<%- @P( @ticket, 'number' ) %>

<%- @T( 'Priority' ) %>

-
<%- @T( @ticket.priority.name ) %>
+
<%- @P( @ticket, 'priority' ) %>

<%- @T( 'Created' ) %>

-
<%- @P( @ticket.humanTime ) %>
+
<%- @P( @ticket, 'created_at' ) %>

<%- @T( 'Group' ) %>

-
<%- @P( @ticket.group ) %>
+
<%- @P( @ticket, 'group' ) %>
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/popover/user.jst.eco b/app/assets/javascripts/app/views/popover/user.jst.eco index 48ba7d8b6..ef11cbba0 100644 --- a/app/assets/javascripts/app/views/popover/user.jst.eco +++ b/app/assets/javascripts/app/views/popover/user.jst.eco @@ -7,11 +7,7 @@ <% if @user[row.name]: %>

<%- @T( row.display ) %>

- <% if row.tag is 'richtext': %> -
<%- @user[row.name] %>
- <% else: %> -
<%- @L( @P( @user[row.name] ) ) %>
- <% end %> +
<%- @P( @user, row.name ) %>
<% end %> <% end %> diff --git a/app/assets/javascripts/app/views/popover/user_ticket_list.jst.eco b/app/assets/javascripts/app/views/popover/user_ticket_list.jst.eco index 2fdbba9d9..90e8e2938 100644 --- a/app/assets/javascripts/app/views/popover/user_ticket_list.jst.eco +++ b/app/assets/javascripts/app/views/popover/user_ticket_list.jst.eco @@ -1,3 +1,5 @@ +
    <% for ticket in @tickets: %> -
    T:<%= ticket.number %> <%= ticket.humanTime %>
    <%= ticket.title %>
    +
  • T:<%= ticket.number %> <%- @P( ticket, 'created_at') %>
    <%= ticket.title %>
  • <% end %> +
      \ No newline at end of file diff --git a/app/assets/javascripts/app/views/ticket_zoom/title.jst.eco b/app/assets/javascripts/app/views/ticket_zoom/title.jst.eco index a99521406..d28ebe8fa 100644 --- a/app/assets/javascripts/app/views/ticket_zoom/title.jst.eco +++ b/app/assets/javascripts/app/views/ticket_zoom/title.jst.eco @@ -1 +1 @@ -

      <%= @P( @ticket.title ) %>

      \ No newline at end of file +
      <%= @ticket.title %>
      \ No newline at end of file diff --git a/app/assets/javascripts/app/views/user_profile/object.jst.eco b/app/assets/javascripts/app/views/user_profile/object.jst.eco index 6d454e4c3..11dd720bd 100644 --- a/app/assets/javascripts/app/views/user_profile/object.jst.eco +++ b/app/assets/javascripts/app/views/user_profile/object.jst.eco @@ -18,7 +18,7 @@ <% if @user[row.name]: %>
      - <%- @L( @P( @user[row.name] ) ) %> + <%- @P( @user, row.name ) %>
      <% end %> <% end %> diff --git a/app/assets/javascripts/app/views/widget/organization.jst.eco b/app/assets/javascripts/app/views/widget/organization.jst.eco index 073832c20..d6ffa474c 100644 --- a/app/assets/javascripts/app/views/widget/organization.jst.eco +++ b/app/assets/javascripts/app/views/widget/organization.jst.eco @@ -9,7 +9,7 @@