Overviews with multi role support.
This commit is contained in:
parent
8aed86bbf0
commit
f0f4f278c3
13 changed files with 171 additions and 121 deletions
|
@ -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
|
||||
|
|
|
@ -179,9 +179,14 @@ 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
|
||||
for item in items
|
||||
resultLocal = item
|
||||
if attributeConfig.relation || valueRef
|
||||
if valueRef
|
||||
item = valueRef
|
||||
|
@ -193,21 +198,21 @@ class App extends Spine.Controller
|
|||
if typeof item is 'object'
|
||||
isObject = true
|
||||
if item.displayNameLong
|
||||
result = item.displayNameLong()
|
||||
resultLocal = item.displayNameLong()
|
||||
else if item.displayName
|
||||
result = item.displayName()
|
||||
resultLocal = item.displayName()
|
||||
else
|
||||
result = item.name
|
||||
resultLocal = item.name
|
||||
|
||||
# execute callback on content
|
||||
if attributeConfig.callback
|
||||
result = attributeConfig.callback(result, attributeConfig)
|
||||
resultLocal = attributeConfig.callback(resultLocal, attributeConfig)
|
||||
|
||||
# text2html in textarea view
|
||||
isHtmlEscape = false
|
||||
if attributeConfig.tag is 'textarea'
|
||||
isHtmlEscape = true
|
||||
result = App.Utils.text2html(result)
|
||||
resultLocal = App.Utils.text2html(resultLocal)
|
||||
|
||||
# remember, html snippets are already escaped
|
||||
else if attributeConfig.tag is 'richtext'
|
||||
|
@ -215,49 +220,53 @@ class App extends Spine.Controller
|
|||
|
||||
# fillup options
|
||||
if !_.isEmpty(attributeConfig.options)
|
||||
if attributeConfig.options[result]
|
||||
result = attributeConfig.options[result]
|
||||
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'
|
||||
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)
|
||||
resultLocal = App.i18n.translateContent(resultLocal)
|
||||
|
||||
# transform date
|
||||
if attributeConfig.tag is 'date'
|
||||
isHtmlEscape = true
|
||||
result = App.i18n.translateDate(result)
|
||||
resultLocal = App.i18n.translateDate(resultLocal)
|
||||
|
||||
# transform input tel|url to make it clickable
|
||||
if attributeConfig.tag is 'input'
|
||||
if attributeConfig.type is 'tel'
|
||||
result = "<a href=\"#{App.Utils.phoneify(result)}\">#{App.Utils.htmlEscape(result)}</a>"
|
||||
resultLocal = "<a href=\"#{App.Utils.phoneify(resultLocal)}\">#{App.Utils.htmlEscape(resultLocal)}</a>"
|
||||
else if attributeConfig.type is 'url'
|
||||
result = App.Utils.linkify(result)
|
||||
resultLocal = App.Utils.linkify(resultLocal)
|
||||
else
|
||||
result = App.Utils.htmlEscape(result)
|
||||
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)
|
||||
timestamp = App.i18n.translateTimestamp(resultLocal)
|
||||
escalation = false
|
||||
cssClass = attributeConfig.class || ''
|
||||
if cssClass.match 'escalation'
|
||||
escalation = true
|
||||
humanTime = App.PrettyDate.humanTime(result, escalation)
|
||||
result = "<time class=\"humanTimeFromNow #{cssClass}\" data-time=\"#{result}\" title=\"#{timestamp}\">#{humanTime}</time>"
|
||||
humanTime = App.PrettyDate.humanTime(resultLocal, escalation)
|
||||
resultLocal = "<time class=\"humanTimeFromNow #{cssClass}\" data-time=\"#{resultLocal}\" title=\"#{timestamp}\">#{humanTime}</time>"
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -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 = '''
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
23
db/migrate/20170419000002_overview_role_ids.rb
Normal file
23
db/migrate/20170419000002_overview_role_ids.rb
Normal file
|
@ -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
|
16
db/seeds.rb
16
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' => {
|
||||
|
|
|
@ -17,7 +17,7 @@ class AdminOverviewTest < TestCase
|
|||
overview_create(
|
||||
data: {
|
||||
name: name,
|
||||
role: 'Agent',
|
||||
roles: ['Agent'],
|
||||
selector: {
|
||||
'Priority' => '1 low',
|
||||
},
|
||||
|
@ -29,7 +29,7 @@ class AdminOverviewTest < TestCase
|
|||
overview_update(
|
||||
data: {
|
||||
name: name,
|
||||
role: 'Agent',
|
||||
roles: ['Agent'],
|
||||
selector: {
|
||||
'State' => 'new',
|
||||
},
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
|
|
|
@ -1575,7 +1575,7 @@ wait untill text in selector disabppears
|
|||
browser: browser1,
|
||||
data: {
|
||||
name: name,
|
||||
role: 'Agent',
|
||||
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]
|
||||
|
@ -1678,7 +1684,7 @@ wait untill text in selector disabppears
|
|||
browser: browser1,
|
||||
data: {
|
||||
name: name,
|
||||
role: 'Agent',
|
||||
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]
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
Loading…
Reference in a new issue