diff --git a/app/assets/javascripts/app/controllers/_application_controller_table.coffee b/app/assets/javascripts/app/controllers/_application_controller_table.coffee
index 32dbefa06..950dcddb3 100644
--- a/app/assets/javascripts/app/controllers/_application_controller_table.coffee
+++ b/app/assets/javascripts/app/controllers/_application_controller_table.coffee
@@ -190,9 +190,8 @@ class App.ControllerTable extends App.Controller
attribute.displayWidth = value
@headers.push attribute
else
- # e.g. column: owner_id
- rowWithoutId = item + '_id'
- if attributeName is rowWithoutId
+ # e.g. column: owner_id or owner_ids
+ if attributeName is "#{item}_id" || attributeName is "#{item}_ids"
headerFound = true
if @headerWidth[attribute.name]
attribute.displayWidth = @headerWidth[attribute.name] * @availableWidth
@@ -350,7 +349,7 @@ class App.ControllerTable extends App.Controller
for headerName in @headers
if !hit
position += 1
- if headerName.name is name || headerName.name is "#{name}_id"
+ if headerName.name is name || headerName.name is "#{name}_id" || headerName.name is "#{name}_ids"
hit = true
if hit
diff --git a/app/assets/javascripts/app/index.coffee b/app/assets/javascripts/app/index.coffee
index 953beb12c..933e01c90 100644
--- a/app/assets/javascripts/app/index.coffee
+++ b/app/assets/javascripts/app/index.coffee
@@ -179,85 +179,94 @@ class App extends Spine.Controller
return '-' if item is undefined
return '-' if item is ''
return item if item is null
- result = item
+ result = ''
+ items = [item]
+ if _.isArray(item)
+ items = item
# lookup relation
- if attributeConfig.relation || valueRef
- if valueRef
- item = valueRef
- else
- item = App[attributeConfig.relation].find(item)
+ for item in items
+ resultLocal = item
+ if attributeConfig.relation || valueRef
+ if valueRef
+ item = valueRef
+ else
+ item = App[attributeConfig.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
+ # if date is a object, get name of the object
+ isObject = false
+ if typeof item is 'object'
+ isObject = true
+ if item.displayNameLong
+ resultLocal = item.displayNameLong()
+ else if item.displayName
+ resultLocal = item.displayName()
+ else
+ resultLocal = item.name
- # execute callback on content
- if attributeConfig.callback
- result = attributeConfig.callback(result, attributeConfig)
+ # execute callback on content
+ if attributeConfig.callback
+ resultLocal = attributeConfig.callback(resultLocal, attributeConfig)
- # text2html in textarea view
- isHtmlEscape = false
- if attributeConfig.tag is 'textarea'
- isHtmlEscape = true
- result = App.Utils.text2html(result)
+ # text2html in textarea view
+ isHtmlEscape = false
+ if attributeConfig.tag is 'textarea'
+ isHtmlEscape = true
+ resultLocal = App.Utils.text2html(resultLocal)
- # remember, html snippets are already escaped
- else if attributeConfig.tag is 'richtext'
- isHtmlEscape = true
+ # remember, html snippets are already escaped
+ else if attributeConfig.tag is 'richtext'
+ isHtmlEscape = true
- # fillup options
- if !_.isEmpty(attributeConfig.options)
- if attributeConfig.options[result]
- result = attributeConfig.options[result]
+ # fillup options
+ if !_.isEmpty(attributeConfig.options)
+ if attributeConfig.options[resultLocal]
+ resultLocal = attributeConfig.options[resultLocal]
- # transform boolean
- if attributeConfig.tag is 'boolean'
- if result is true
- result = 'yes'
- else if result is false
- result = 'no'
+ # transform boolean
+ if attributeConfig.tag is 'boolean'
+ if resultLocal is true
+ resultLocal = 'yes'
+ else if resultLocal is false
+ resultLocal = 'no'
- # translate content
- if attributeConfig.translate || (isObject && item.translate && item.translate())
- isHtmlEscape = true
- result = App.i18n.translateContent(result)
+ # translate content
+ if attributeConfig.translate || (isObject && item.translate && item.translate())
+ isHtmlEscape = true
+ resultLocal = App.i18n.translateContent(resultLocal)
- # transform date
- if attributeConfig.tag is 'date'
- isHtmlEscape = true
- result = App.i18n.translateDate(result)
+ # transform date
+ if attributeConfig.tag is 'date'
+ isHtmlEscape = true
+ resultLocal = App.i18n.translateDate(resultLocal)
- # transform input tel|url to make it clickable
- if attributeConfig.tag is 'input'
- if attributeConfig.type is 'tel'
- result = "#{App.Utils.htmlEscape(result)}"
- else if attributeConfig.type is 'url'
- result = App.Utils.linkify(result)
- else
- result = App.Utils.htmlEscape(result)
- isHtmlEscape = true
+ # transform input tel|url to make it clickable
+ if attributeConfig.tag is 'input'
+ if attributeConfig.type is 'tel'
+ resultLocal = "#{App.Utils.htmlEscape(resultLocal)}"
+ else if attributeConfig.type is 'url'
+ resultLocal = App.Utils.linkify(resultLocal)
+ else
+ resultLocal = App.Utils.htmlEscape(resultLocal)
+ isHtmlEscape = true
- # use pretty time for datetime
- else if attributeConfig.tag is 'datetime'
- isHtmlEscape = true
- timestamp = App.i18n.translateTimestamp(result)
- escalation = false
- cssClass = attributeConfig.class || ''
- if cssClass.match 'escalation'
- escalation = true
- humanTime = App.PrettyDate.humanTime(result, escalation)
- result = ""
+ # use pretty time for datetime
+ else if attributeConfig.tag is 'datetime'
+ isHtmlEscape = true
+ timestamp = App.i18n.translateTimestamp(resultLocal)
+ escalation = false
+ cssClass = attributeConfig.class || ''
+ if cssClass.match 'escalation'
+ escalation = true
+ humanTime = App.PrettyDate.humanTime(resultLocal, escalation)
+ resultLocal = ""
- if !isHtmlEscape && typeof result is 'string'
- result = App.Utils.htmlEscape(result)
+ if !isHtmlEscape && typeof resultLocal is 'string'
+ resultLocal = App.Utils.htmlEscape(resultLocal)
+
+ if !_.isEmpty(result)
+ result += ', '
+ result += resultLocal
result
diff --git a/app/assets/javascripts/app/models/overview.coffee b/app/assets/javascripts/app/models/overview.coffee
index 029470430..df0f7af1b 100644
--- a/app/assets/javascripts/app/models/overview.coffee
+++ b/app/assets/javascripts/app/models/overview.coffee
@@ -1,11 +1,11 @@
class App.Overview extends App.Model
- @configure 'Overview', 'name', 'prio', 'condition', 'order', 'group_by', 'view', 'user_ids', 'organization_shared', 'role_id', 'order', 'group_by', 'active', 'updated_at'
+ @configure 'Overview', 'name', 'prio', 'condition', 'order', 'group_by', 'view', 'user_ids', 'organization_shared', 'role_ids', 'order', 'group_by', 'active', 'updated_at'
@extend Spine.Model.Ajax
@url: @apiPath + '/overviews'
@configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false },
{ name: 'link', display: 'Link', readonly: 1 },
- { name: 'role_id', display: 'Available for Role', tag: 'select', multiple: false, nulloption: true, null: false, relation: 'Role', translate: true },
+ { name: 'role_ids', display: 'Available for Role', tag: 'column_select', multiple: true, nulloption: true, null: false, relation: 'Role', translate: true },
{ name: 'user_ids', display: 'Available for User', tag: 'column_select', multiple: true, nulloption: false, null: true, relation: 'User', sortBy: 'firstname' },
{ name: 'organization_shared', display: 'Only available for Users with shared Organization', tag: 'select', options: { true: 'yes', false: 'no' }, default: false, null: true },
{ name: 'condition', display: 'Conditions for shown Tickets', tag: 'ticket_selector', null: false },
@@ -62,7 +62,7 @@ class App.Overview extends App.Model
@configure_overview = [
'name',
'link',
- 'role',
+ 'role_ids',
]
@description = '''
diff --git a/app/models/overview.rb b/app/models/overview.rb
index f631aa13a..8b4c284ce 100644
--- a/app/models/overview.rb
+++ b/app/models/overview.rb
@@ -7,6 +7,7 @@ class Overview < ApplicationModel
load 'overview/assets.rb'
include Overview::Assets
+ has_and_belongs_to_many :roles, after_add: :cache_update, after_remove: :cache_update, class_name: 'Role'
has_and_belongs_to_many :users, after_add: :cache_update, after_remove: :cache_update
store :condition
store :order
diff --git a/app/models/ticket/overviews.rb b/app/models/ticket/overviews.rb
index 1c9074aa0..6f9e0b4fd 100644
--- a/app/models/ticket/overviews.rb
+++ b/app/models/ticket/overviews.rb
@@ -19,11 +19,12 @@ returns
current_user = data[:current_user]
# get customer overviews
+ role_ids = User.joins(:roles).where(users: { id: current_user.id, active: true }, roles: { active: true }).pluck('roles.id')
if current_user.permissions?('ticket.customer')
overviews = if current_user.organization_id && current_user.organization.shared
- Overview.where(role_id: current_user.role_ids, active: true).order(:prio)
+ Overview.joins(:roles).where(overviews_roles: { role_id: role_ids }, overviews: { active: true }).distinct('overview.id').order(:prio)
else
- Overview.where(role_id: current_user.role_ids, organization_shared: false, active: true).order(:prio)
+ Overview.joins(:roles).where(overviews_roles: { role_id: role_ids }, overviews: { active: true, organization_shared: false }).distinct('overview.id').order(:prio)
end
overviews_list = []
overviews.each { |overview|
@@ -36,7 +37,7 @@ returns
# get agent overviews
return [] if !current_user.permissions?('ticket.agent')
- overviews = Overview.where(role_id: current_user.role_ids, active: true).order(:prio)
+ overviews = Overview.joins(:roles).where(overviews_roles: { role_id: role_ids }, overviews: { active: true }).distinct('overview.id').order(:prio)
overviews_list = []
overviews.each { |overview|
user_ids = overview.user_ids
diff --git a/db/migrate/20120101000010_create_ticket.rb b/db/migrate/20120101000010_create_ticket.rb
index 35f134fc9..e8c68dd0f 100644
--- a/db/migrate/20120101000010_create_ticket.rb
+++ b/db/migrate/20120101000010_create_ticket.rb
@@ -196,7 +196,6 @@ class CreateTicket < ActiveRecord::Migration
add_index :ticket_counters, [:generator], unique: true
create_table :overviews do |t|
- t.references :role, null: false
t.column :name, :string, limit: 250, null: false
t.column :link, :string, limit: 250, null: false
t.column :prio, :integer, null: false
@@ -212,6 +211,13 @@ class CreateTicket < ActiveRecord::Migration
end
add_index :overviews, [:name]
+ create_table :overviews_roles, id: false do |t|
+ t.integer :overview_id
+ t.integer :role_id
+ end
+ add_index :overviews_roles, [:overview_id]
+ add_index :overviews_roles, [:role_id]
+
create_table :overviews_users, id: false do |t|
t.integer :overview_id
t.integer :user_id
diff --git a/db/migrate/20170419000002_overview_role_ids.rb b/db/migrate/20170419000002_overview_role_ids.rb
new file mode 100644
index 000000000..255db825d
--- /dev/null
+++ b/db/migrate/20170419000002_overview_role_ids.rb
@@ -0,0 +1,23 @@
+class OverviewRoleIds < ActiveRecord::Migration
+ def up
+
+ # return if it's a new setup
+ return if !Setting.find_by(name: 'system_init_done')
+
+ create_table :overviews_roles, id: false do |t|
+ t.integer :overview_id
+ t.integer :role_id
+ end
+ add_index :overviews_roles, [:overview_id]
+ add_index :overviews_roles, [:role_id]
+ Overview.connection.schema_cache.clear!
+ Overview.reset_column_information
+ Overview.all.each { |overview|
+ next if overview.role_id.blank?
+ overview.role_ids = [overview.role_id]
+ overview.save!
+ }
+ remove_column :overviews, :role_id
+ end
+
+end
diff --git a/db/seeds.rb b/db/seeds.rb
index 8257e7378..f762725ef 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -3367,7 +3367,7 @@ Overview.create_if_not_exists(
name: 'My assigned Tickets',
link: 'my_assigned',
prio: 1000,
- role_id: overview_role.id,
+ role_ids: [overview_role.id],
condition: {
'ticket.state_id' => {
operator: 'is',
@@ -3394,7 +3394,7 @@ Overview.create_if_not_exists(
name: 'Unassigned & Open',
link: 'all_unassigned',
prio: 1010,
- role_id: overview_role.id,
+ role_ids: [overview_role.id],
condition: {
'ticket.state_id' => {
operator: 'is',
@@ -3421,7 +3421,7 @@ Overview.create_if_not_exists(
name: 'My pending reached Tickets',
link: 'my_pending_reached',
prio: 1020,
- role_id: overview_role.id,
+ role_ids: [overview_role.id],
condition: {
'ticket.state_id' => {
operator: 'is',
@@ -3453,7 +3453,7 @@ Overview.create_if_not_exists(
name: 'Open',
link: 'all_open',
prio: 1030,
- role_id: overview_role.id,
+ role_ids: [overview_role.id],
condition: {
'ticket.state_id' => {
operator: 'is',
@@ -3476,7 +3476,7 @@ Overview.create_if_not_exists(
name: 'Pending reached',
link: 'all_pending_reached',
prio: 1040,
- role_id: overview_role.id,
+ role_ids: [overview_role.id],
condition: {
'ticket.state_id' => {
operator: 'is',
@@ -3504,7 +3504,7 @@ Overview.create_if_not_exists(
name: 'Escalated',
link: 'all_escalated',
prio: 1050,
- role_id: overview_role.id,
+ role_ids: [overview_role.id],
condition: {
'ticket.escalation_at' => {
operator: 'within next (relative)',
@@ -3529,7 +3529,7 @@ Overview.create_if_not_exists(
name: 'My Tickets',
link: 'my_tickets',
prio: 1100,
- role_id: overview_role.id,
+ role_ids: [overview_role.id],
condition: {
'ticket.state_id' => {
operator: 'is',
@@ -3555,7 +3555,7 @@ Overview.create_if_not_exists(
name: 'My Organization Tickets',
link: 'my_organization_tickets',
prio: 1200,
- role_id: overview_role.id,
+ role_ids: [overview_role.id],
organization_shared: true,
condition: {
'ticket.state_id' => {
diff --git a/test/browser/admin_overview_test.rb b/test/browser/admin_overview_test.rb
index d04081298..94a577ea7 100644
--- a/test/browser/admin_overview_test.rb
+++ b/test/browser/admin_overview_test.rb
@@ -16,8 +16,8 @@ class AdminOverviewTest < TestCase
# add new overview
overview_create(
data: {
- name: name,
- role: 'Agent',
+ name: name,
+ roles: ['Agent'],
selector: {
'Priority' => '1 low',
},
@@ -28,8 +28,8 @@ class AdminOverviewTest < TestCase
# edit overview
overview_update(
data: {
- name: name,
- role: 'Agent',
+ name: name,
+ roles: ['Agent'],
selector: {
'State' => 'new',
},
diff --git a/test/browser/agent_ticket_overview_level1_test.rb b/test/browser/agent_ticket_overview_level1_test.rb
index 12a87c22d..4200c37a3 100644
--- a/test/browser/agent_ticket_overview_level1_test.rb
+++ b/test/browser/agent_ticket_overview_level1_test.rb
@@ -29,7 +29,7 @@ class AgentTicketOverviewLevel1Test < TestCase
browser: browser1,
data: {
name: name1,
- role: 'Agent',
+ roles: ['Agent'],
selector: {
'Priority' => '1 low',
},
@@ -40,7 +40,7 @@ class AgentTicketOverviewLevel1Test < TestCase
browser: browser1,
data: {
name: name2,
- role: 'Agent',
+ roles: ['Agent'],
selector: {
'Priority' => '3 high',
},
diff --git a/test/browser_test_helper.rb b/test/browser_test_helper.rb
index 2c1022d69..6af218976 100644
--- a/test/browser_test_helper.rb
+++ b/test/browser_test_helper.rb
@@ -1574,8 +1574,8 @@ wait untill text in selector disabppears
overview_create(
browser: browser1,
data: {
- name: name,
- role: 'Agent',
+ name: name,
+ roles: ['Agent'],
selector: {
'Priority': '1 low',
},
@@ -1616,13 +1616,19 @@ wait untill text in selector disabppears
mute_log: true,
)
end
- if data[:role]
- select(
- browser: instance,
- css: '.modal select[name="role_id"]',
- value: data[:role],
- mute_log: true,
- )
+
+ if data[:roles]
+ 99.times do
+ begin
+ element = instance.find_elements(css: '.modal .js-selected[data-name=role_ids] .js-option:not(.is-hidden)')[0]
+ break if !element
+ element.click
+ sleep 0.1
+ end
+ end
+ data[:roles].each { |role|
+ instance.execute_script("$(\".modal [data-name=role_ids] .js-pool .js-option:not(.is-hidden):contains('#{role}')\").first().click()")
+ }
end
if data[:selector]
@@ -1677,8 +1683,8 @@ wait untill text in selector disabppears
overview_update(
browser: browser1,
data: {
- name: name,
- role: 'Agent',
+ name: name,
+ roles: ['Agent'],
selector: {
'Priority': '1 low',
},
@@ -1717,13 +1723,18 @@ wait untill text in selector disabppears
mute_log: true,
)
end
- if data[:role]
- select(
- browser: instance,
- css: '.modal select[name="role_id"]',
- value: data[:role],
- mute_log: true,
- )
+ if data[:roles]
+ 99.times do
+ begin
+ element = instance.find_elements(css: '.modal .js-selected[data-name=role_ids] .js-option:not(.is-hidden)')[0]
+ break if !element
+ element.click
+ sleep 0.1
+ end
+ end
+ data[:roles].each { |role|
+ instance.execute_script("$(\".modal [data-name=role_ids] .js-pool .js-option:not(.is-hidden):contains('#{role}')\").first().click()")
+ }
end
if data[:selector]
diff --git a/test/unit/assets_test.rb b/test/unit/assets_test.rb
index 1c621ba07..6f042ab9e 100644
--- a/test/unit/assets_test.rb
+++ b/test/unit/assets_test.rb
@@ -351,8 +351,8 @@ class AssetsTest < ActiveSupport::TestCase
name: 'my asset test',
link: 'my_asset_test',
prio: 1000,
- role_id: overview_role.id,
- user_ids: [ user4.id, user5.id ],
+ role_ids: [overview_role.id],
+ user_ids: [user4.id, user5.id],
condition: {
'ticket.state_id' => {
operator: 'is',
@@ -389,8 +389,8 @@ class AssetsTest < ActiveSupport::TestCase
name: 'my asset test',
link: 'my_asset_test',
prio: 1000,
- role_id: overview_role.id,
- user_ids: [ user4.id ],
+ role_ids: [overview_role.id],
+ user_ids: [user4.id],
condition: {
'ticket.state_id' => {
operator: 'is',
diff --git a/test/unit/ticket_overview_test.rb b/test/unit/ticket_overview_test.rb
index 8530c9db6..555ebf023 100644
--- a/test/unit/ticket_overview_test.rb
+++ b/test/unit/ticket_overview_test.rb
@@ -105,7 +105,7 @@ class TicketOverviewTest < ActiveSupport::TestCase
name: 'My assigned Tickets',
link: 'my_assigned',
prio: 1000,
- role_id: overview_role.id,
+ role_ids: [overview_role.id],
condition: {
'ticket.state_id' => {
operator: 'is',
@@ -132,7 +132,7 @@ class TicketOverviewTest < ActiveSupport::TestCase
name: 'Unassigned & Open',
link: 'all_unassigned',
prio: 1010,
- role_id: overview_role.id,
+ role_ids: [overview_role.id],
condition: {
'ticket.state_id' => {
operator: 'is',
@@ -158,7 +158,7 @@ class TicketOverviewTest < ActiveSupport::TestCase
name: 'My Tickets 2',
link: 'my_tickets_2',
prio: 1020,
- role_id: overview_role.id,
+ role_ids: [overview_role.id],
user_ids: [agent2.id],
condition: {
'ticket.state_id' => {
@@ -185,7 +185,7 @@ class TicketOverviewTest < ActiveSupport::TestCase
name: 'My Tickets only with Note',
link: 'my_tickets_onyl_with_note',
prio: 1030,
- role_id: overview_role.id,
+ role_ids: [overview_role.id],
user_ids: [agent1.id],
condition: {
'article.type_id' => {
@@ -214,7 +214,7 @@ class TicketOverviewTest < ActiveSupport::TestCase
name: 'My Tickets',
link: 'my_tickets',
prio: 1100,
- role_id: overview_role.id,
+ role_ids: [overview_role.id],
condition: {
'ticket.state_id' => {
operator: 'is',
@@ -240,7 +240,7 @@ class TicketOverviewTest < ActiveSupport::TestCase
name: 'My Organization Tickets',
link: 'my_organization_tickets',
prio: 1200,
- role_id: overview_role.id,
+ role_ids: [overview_role.id],
organization_shared: true,
condition: {
'ticket.state_id' => {
@@ -267,7 +267,7 @@ class TicketOverviewTest < ActiveSupport::TestCase
name: 'My Organization Tickets (open)',
link: 'my_organization_tickets_open',
prio: 1200,
- role_id: overview_role.id,
+ role_ids: [overview_role.id],
user_ids: [customer2.id],
organization_shared: true,
condition: {
@@ -297,7 +297,7 @@ class TicketOverviewTest < ActiveSupport::TestCase
name: 'Not Shown Admin',
link: 'not_shown_admin',
prio: 9900,
- role_id: overview_role.id,
+ role_ids: [overview_role.id],
condition: {
'ticket.state_id' => {
operator: 'is',