Browse Source

Fixes #4790 - Improve groups to support a more complex hierarchy.

Fixes #4570 - Group assignment overleaps outside of dialog content in the invite agent dialog.

Co-authored-by: Martin Gruner <mg@zammad.com>
Co-authored-by: Dominik Klein <dk@zammad.com>
Co-authored-by: Florian Liebe <fl@zammad.com>
Co-authored-by: Mantas Masalskis <mm@zammad.com>
Co-authored-by: Rolf Schmidt <rolf.schmidt@zammad.com>
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
Dusan Vuckovic 1 year ago
parent
commit
5b6ad994b2

+ 2 - 0
app/assets/javascripts/app/controllers/_application_controller/_generic_index.coffee

@@ -184,6 +184,7 @@ class App.ControllerGenericIndex extends App.Controller
       veryLarge:        @veryLarge
       handlers:         @handlers
       validateOnSubmit: @validateOnSubmit
+      screen:           @editScreen
     )
 
   newControllerClass: ->
@@ -203,6 +204,7 @@ class App.ControllerGenericIndex extends App.Controller
       veryLarge:        @veryLarge
       handlers:         @handlers
       validateOnSubmit: @validateOnSubmit
+      screen:           @createScreen
     )
 
   clone: (item) =>

+ 2 - 2
app/assets/javascripts/app/controllers/_channel/email.coffee

@@ -259,7 +259,7 @@ class ChannelEmailEdit extends App.ControllerModal
 
   content: =>
     configureAttributesBase = [
-      { name: 'group_id', display: __('Destination Group'), tag: 'select', null: false, relation: 'Group', nulloption: true, filter: { active: true } },
+      { name: 'group_id', display: __('Destination Group'), tag: 'tree_select', null: false, relation: 'Group', nulloption: true, filter: { active: true } },
     ]
     @form = new App.ControllerForm(
       model:
@@ -381,7 +381,7 @@ class ChannelEmailAccountWizard extends App.ControllerWizardModal
       { name: 'realname', display: __('Organization & Department Name'), tag: 'input',  type: 'text', limit: 160, null: false, placeholder: __('Organization Support'), autocomplete: 'off' },
       { name: 'email',    display: __('Email'),    tag: 'input',  type: 'email', limit: 120, null: false, placeholder: 'support@example.com', autocapitalize: false, autocomplete: 'off' },
       { name: 'password', display: __('Password'), tag: 'input',  type: 'password', limit: 120, null: false, autocapitalize: false, autocomplete: 'new-password', single: true },
-      { name: 'group_id', display: __('Destination Group'), tag: 'select', null: false, relation: 'Group', nulloption: true },
+      { name: 'group_id', display: __('Destination Group'), tag: 'tree_select', null: false, relation: 'Group', nulloption: true },
     ]
     @formMeta = new App.ControllerForm(
       el:    @$('.base-settings'),

+ 1 - 1
app/assets/javascripts/app/controllers/_channel/facebook.coffee

@@ -199,7 +199,7 @@ class AccountEdit extends App.ControllerModal
     content = $( App.view('facebook/account_edit')(channel: @channel) )
 
     groupSelection = (selected_id, el, prefix) ->
-      selection = App.UiElement.select.render(
+      selection = App.UiElement.tree_select.render(
         name: "#{prefix}::group_id"
         multiple: false
         limit: 100

+ 1 - 1
app/assets/javascripts/app/controllers/_channel/form.coffee

@@ -29,7 +29,7 @@ class ChannelForm extends App.ControllerSubContent
     ))
 
     group_id = App.Setting.get('form_ticket_create_group_id')
-    selection = App.UiElement.select.render(
+    selection = App.UiElement.tree_select.render(
       name: 'group_id'
       multiple: false
       null: false

+ 1 - 1
app/assets/javascripts/app/controllers/_channel/google.coffee

@@ -336,7 +336,7 @@ class ChannelGroupEdit extends App.ControllerModal
 
   content: =>
     configureAttributesBase = [
-      { name: 'group_id', display: __('Destination Group'), tag: 'select', null: false, relation: 'Group', nulloption: true, filter: { active: true } },
+      { name: 'group_id', display: __('Destination Group'), tag: 'tree_select', null: false, relation: 'Group', nulloption: true, filter: { active: true } },
     ]
     @form = new App.ControllerForm(
       model:

+ 1 - 1
app/assets/javascripts/app/controllers/_channel/microsoft365.coffee

@@ -348,7 +348,7 @@ class ChannelGroupEdit extends App.ControllerModal
 
   content: =>
     configureAttributesBase = [
-      { name: 'group_id', display: __('Destination Group'), tag: 'select', null: false, relation: 'Group', nulloption: true, filter: { active: true } },
+      { name: 'group_id', display: __('Destination Group'), tag: 'tree_select', null: false, relation: 'Group', nulloption: true, filter: { active: true } },
     ]
     @form = new App.ControllerForm(
       model:

+ 2 - 2
app/assets/javascripts/app/controllers/_channel/telegram.coffee

@@ -114,7 +114,7 @@ class BotAdd extends App.ControllerModal
   content: ->
     content = $(App.view('telegram/bot_add')())
     createGroupSelection = (selected_id) ->
-      return App.UiElement.select.render(
+      return App.UiElement.tree_select.render(
         name:       'group_id'
         multiple:   false
         limit:      100
@@ -163,7 +163,7 @@ class BotEdit extends App.ControllerModal
     content = $(App.view('telegram/bot_edit')(channel: @channel))
 
     createGroupSelection = (selected_id) ->
-      return App.UiElement.select.render(
+      return App.UiElement.tree_select.render(
         name:       'group_id'
         multiple:   false
         limit:      100

+ 1 - 1
app/assets/javascripts/app/controllers/_channel/twitter.coffee

@@ -202,7 +202,7 @@ class AccountEdit extends App.ControllerModal
     content = $( App.view('twitter/account_edit')(channel: @channel) )
 
     createGroupSelection = (selected_id, prefix) ->
-      return App.UiElement.select.render(
+      return App.UiElement.tree_select.render(
         name: "#{prefix}::group_id"
         multiple: false
         limit: 100

+ 46 - 31
app/assets/javascripts/app/controllers/_ui_element/_application/_ui_element.coffee

@@ -131,8 +131,7 @@ class App.UiElement.ApplicationUiElement
               list.push record
 
       # data based filter
-      else if attribute.filter && _.isArray attribute.filter
-
+      else if attribute.filter && _.isArray(attribute.filter) && attribute.tag is 'select'
         App.Log.debug 'ControllerForm', '_getRelationOptionList:filter-array', attribute.filter
 
         filter = _.clone(attribute.filter)
@@ -166,42 +165,53 @@ class App.UiElement.ApplicationUiElement
     # build options list
     @buildOptionList(list, attribute)
 
-  # build options list
-  @buildOptionList: (list, attribute) ->
+  @buildOptionListRow: (attribute, item) ->
 
-    for item in list
+    # check if element is selected, show it anyway - ignore active state
+    activeSupport = ('active' of item)
+    isSelected = false
+    if activeSupport && !item.active
+      isSelected = @_selectedOptionsIsSelected(attribute.value, {name: item.name || '', value: item.id})
 
-      # check if element is selected, show it anyway - ignore active state
-      activeSupport = ('active' of item)
-      isSelected = false
-      if activeSupport && !item.active
-        isSelected = @_selectedOptionsIsSelected(attribute.value, {name: item.name || '', value: item.id})
+    hasActiveChildren = false
+    if @isTreeRelation(attribute)
+      hasActiveChildren = _.some(item.all_children(), (obj) -> obj.active)
 
-      # if active or if active doesn't exist
-      if item.active || !activeSupport || isSelected
-        nameNew = '?'
-        if item.displayName
-          nameNew = item.displayName()
-        else if item.name
-          nameNew = item.name
+    # if active or if active doesn't exist
+    return if !item.active && activeSupport && !isSelected && !hasActiveChildren
 
-        if attribute.translate
-          nameNew = App.i18n.translatePlain(nameNew)
+    nameNew = '?'
+    if @isTreeRelation(attribute)
+      nameNew = item.name_last
+    else if item.displayName
+      nameNew = item.displayName()
+    else if item.name
+      nameNew = item.name
+
+    if attribute.translate
+      nameNew = App.i18n.translatePlain(nameNew)
 
-        row =
-          value: item.id,
-          note:  item.note,
-          name:  nameNew,
-          title: if item.email then item.email else nameNew
+    row =
+      value: item.id,
+      note:  item.note,
+      name:  nameNew,
+      title: if item.email then item.email else nameNew
 
-        if item.graphic
-          row.graphic = item.graphic
+    if item.graphic
+      row.graphic = item.graphic
 
-          # only used for graphics
-          if item.aspect_ratio
-            row.aspect_ratio = item.aspect_ratio
+      # only used for graphics
+      if item.aspect_ratio
+        row.aspect_ratio = item.aspect_ratio
 
-        attribute.options.push row
+    row
+
+  # build options list
+  @buildOptionList: (list, attribute) ->
+    for item in list
+      row = @buildOptionListRow(attribute, item)
+      continue if !row
+      attribute.options.push row
 
     attribute.sortBy = null
 
@@ -213,7 +223,7 @@ class App.UiElement.ApplicationUiElement
     if typeof attribute.filter is 'function'
       App.Log.debug 'ControllerForm', '_filterOption:filter-function'
       attribute.options = attribute.filter(attribute.options, attribute)
-    else if !attribute.relation && attribute.filter && _.isArray(attribute.filter)
+    else if (@isTreeRelation(attribute) || !attribute.relation) && attribute.filter && _.isArray(attribute.filter)
       @filterOptionArray(attribute)
 
   @filterOptionArray: (attribute) ->
@@ -323,3 +333,8 @@ class App.UiElement.ApplicationUiElement
         continue if !value
         continue if attribute.options[value]
         attribute.options[value] = attribute.historical_options[value] || value
+
+  @isTreeRelation: (attribute) ->
+    return false if !attribute.relation
+    return false if !_.find(App[attribute.relation].configure_attributes, (attr) -> attr.name is 'parent_id')
+    return true

+ 63 - 37
app/assets/javascripts/app/controllers/_ui_element/_application_tree_select.coffee

@@ -1,42 +1,5 @@
 # coffeelint: disable=camel_case_classes
 class App.UiElement.ApplicationTreeSelect extends App.UiElement.ApplicationUiElement
-  @optionsSelect: (children, value) ->
-    return if !children
-    for child in children
-      if child.value is value
-        child.selected = true
-      if child.children
-        @optionsSelect(child.children, value)
-
-  @filterTreeOptions: (values, valueDepth, options, nullExists) ->
-    newOptions = []
-    nullFound = false
-    for option, index in options
-      enabled = _.contains(values, option.value)
-      if nullExists && !option.value && !nullFound
-        nullFound = true
-        enabled   = true
-
-      activeChildren = false
-      if option.value && option.children && option.children.length > 0
-        for value in values
-          if value && value.startsWith(option.value + '::')
-            activeChildren = true
-
-      if activeChildren
-        option.inactive = !enabled
-        option.children = @filterTreeOptions(values, valueDepth + 1, option.children, nullExists)
-      else
-        option.children = undefined
-        continue if !enabled
-
-      newOptions.push(option)
-
-    return newOptions
-
-  @filterOptionArray: (attribute) ->
-    attribute.options = @filterTreeOptions(attribute.filter, 0, attribute.options, attribute.null)
-
   @render: (attributeConfig, params) ->
     attribute = $.extend(true, {}, attributeConfig)
 
@@ -82,3 +45,66 @@ class App.UiElement.ApplicationTreeSelect extends App.UiElement.ApplicationUiEle
     @filterOption(attribute, params)
 
     new App.SearchableSelect(attribute: attribute).element()
+
+  @optionsSelect: (children, value) ->
+    return if !children
+    for child in children
+      if child.value?.toString() is value?.toString()
+        child.selected = true
+      if child.children
+        @optionsSelect(child.children, value)
+
+  @filterTreeOptions: (attribute, valueDepth, options) ->
+    newOptions = []
+    nullFound = false
+    for option, index in options
+      enabled = _.contains(attribute.filter, option.value.toString())
+      if attribute.null && !option.value && !nullFound
+        nullFound = true
+        enabled   = true
+
+      activeChildren = false
+      if option.value && option.children && option.children.length > 0
+        if @isTreeRelation(attribute)
+          all_children_ids = _.map(App[attribute.relation].find(option.value).all_children(), (group) -> group.id.toString())
+          if _.intersection(attribute.filter, all_children_ids).length > 0
+            activeChildren = true
+        else
+          for value in attribute.filter
+            if value && value.startsWith(option.value + '::')
+              activeChildren = true
+
+      if activeChildren
+        option.inactive = !enabled
+        option.children = @filterTreeOptions(attribute, valueDepth + 1, option.children)
+      else
+        option.children = undefined
+        continue if !enabled
+
+      newOptions.push(option)
+
+    return newOptions
+
+  @filterOptionArray: (attribute) ->
+    attribute.options = @filterTreeOptions(attribute, 0, attribute.options)
+
+  @buildOptionList: (list, attribute) ->
+    return super if !@isTreeRelation(attribute)
+
+    parents           = list.filter((obj) -> !obj.parent_id)
+    attribute.options = @buildOptionListTreeRelation(list, attribute, parents)
+    attribute.sortBy  = null
+
+  @buildOptionListTreeRelation: (list, attribute, entries) ->
+    result = []
+    for item in entries
+      row = @buildOptionListRow(attribute, item)
+      continue if !row
+
+      children = _.filter(list, (obj) -> obj.parent_id is item.id)
+      children = @buildOptionListTreeRelation(list, attribute, children)
+      if children.length > 0
+        row.children = children
+
+      result.push row
+    result

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