Browse Source

Overviews with multi role support.

Martin Edenhofer 8 years ago
parent
commit
f0f4f278c3

+ 3 - 4
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

+ 86 - 77
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)
-
-    # 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 attributeConfig.callback
-      result = attributeConfig.callback(result, attributeConfig)
-
-    # text2html in textarea view
-    isHtmlEscape = false
-    if attributeConfig.tag is 'textarea'
-      isHtmlEscape = true
-      result       = App.Utils.text2html(result)
-
-    # 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]
-
-    # transform boolean
-    if attributeConfig.tag is 'boolean'
-      if result is true
-        result = 'yes'
-      else if result is false
-        result = 'no'
-
-    # translate content
-    if attributeConfig.translate || (isObject && item.translate && item.translate())
-      isHtmlEscape = true
-      result       = App.i18n.translateContent(result)
-
-    # transform date
-    if attributeConfig.tag is 'date'
-      isHtmlEscape = true
-      result       = App.i18n.translateDate(result)
-
-    # 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>"
-      else if attributeConfig.type is 'url'
-        result = App.Utils.linkify(result)
-      else
-        result = App.Utils.htmlEscape(result)
-      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    = "<time class=\"humanTimeFromNow #{cssClass}\" data-time=\"#{result}\" title=\"#{timestamp}\">#{humanTime}</time>"
-
-    if !isHtmlEscape && typeof result is 'string'
-      result = App.Utils.htmlEscape(result)
+    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
+          resultLocal = item.displayNameLong()
+        else if item.displayName
+          resultLocal = item.displayName()
+        else
+          resultLocal = item.name
+
+      # 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
+        resultLocal       = App.Utils.text2html(resultLocal)
+
+      # remember, html snippets are already escaped
+      else if attributeConfig.tag is 'richtext'
+        isHtmlEscape = true
+
+      # fillup options
+      if !_.isEmpty(attributeConfig.options)
+        if attributeConfig.options[resultLocal]
+          resultLocal = attributeConfig.options[resultLocal]
+
+      # 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
+        resultLocal       = App.i18n.translateContent(resultLocal)
+
+      # 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'
+          resultLocal = "<a href=\"#{App.Utils.phoneify(resultLocal)}\">#{App.Utils.htmlEscape(resultLocal)}</a>"
+        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(resultLocal)
+        escalation = false
+        cssClass = attributeConfig.class || ''
+        if cssClass.match 'escalation'
+          escalation = true
+        humanTime = App.PrettyDate.humanTime(resultLocal, escalation)
+        resultLocal    = "<time class=\"humanTimeFromNow #{cssClass}\" data-time=\"#{resultLocal}\" title=\"#{timestamp}\">#{humanTime}</time>"
+
+      if !isHtmlEscape && typeof resultLocal is 'string'
+        resultLocal = App.Utils.htmlEscape(resultLocal)
+
+      if !_.isEmpty(result)
+        result += ', '
+      result += resultLocal
 
     result
 

+ 3 - 3
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 = '''

+ 1 - 0
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

+ 4 - 3
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

+ 7 - 1
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

+ 23 - 0
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

+ 8 - 8
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' => {

+ 4 - 4
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',
         },

+ 2 - 2
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',
         },

Some files were not shown because too many files changed in this diff