diff --git a/app/assets/javascripts/app/lib/app_post/searchable_select.coffee b/app/assets/javascripts/app/lib/app_post/searchable_select.coffee
index 3a2e28acb..31646f8b3 100644
--- a/app/assets/javascripts/app/lib/app_post/searchable_select.coffee
+++ b/app/assets/javascripts/app/lib/app_post/searchable_select.coffee
@@ -90,9 +90,17 @@ class App.SearchableSelect extends Spine.Controller
renderOptions: (options) ->
html = ''
for option in options
+ classes = 'u-textTruncate'
+ if option.children
+ classes += ' js-enter'
+ else
+ classes += ' js-option'
+ if option.category
+ classes += ' with-category'
+
html += App.view('generic/searchable_select_option')
option: option
- class: if option.children then 'js-enter' else 'js-option'
+ class: classes
html
renderAllOptions: (parentName, options, level) ->
diff --git a/app/assets/javascripts/app/lib/app_post/z_searchable_ajax_select.coffee b/app/assets/javascripts/app/lib/app_post/z_searchable_ajax_select.coffee
index 4f2067d0c..b8c078407 100644
--- a/app/assets/javascripts/app/lib/app_post/z_searchable_ajax_select.coffee
+++ b/app/assets/javascripts/app/lib/app_post/z_searchable_ajax_select.coffee
@@ -78,10 +78,15 @@ class App.SearchableAjaxSelect extends App.SearchableSelect
renderResponseItemAjax: (elem, data) ->
result = _.find(data.details, (detailElem) -> detailElem.type == elem.type and detailElem.id == elem.id)
+ category = undefined
+ if result.type is 'KnowledgeBase::Answer::Translation' && result.subtitle
+ category = result.subtitle
+
if result
{
- name: result.title
- value: elem.id
+ category: category
+ name: result.title
+ value: elem.id
}
renderResponseItem: (elem) ->
diff --git a/app/assets/javascripts/app/lib/base/jquery.textmodule.js b/app/assets/javascripts/app/lib/base/jquery.textmodule.js
index 2da57c5de..718fe7c5d 100644
--- a/app/assets/javascripts/app/lib/base/jquery.textmodule.js
+++ b/app/assets/javascripts/app/lib/base/jquery.textmodule.js
@@ -577,7 +577,8 @@
'flavor': 'agent',
'index': 'KnowledgeBase::Answer::Translation',
'url_type': 'agent',
- 'highlight_enabled': false
+ 'highlight_enabled': false,
+ 'include_locale': true,
}),
processData: true,
success: function(data, status, xhr) {
@@ -585,12 +586,13 @@
var items = data
.result
- .map(function(elem){
+ .map(function(elem) {
if(result = _.find(data.details, function(detailElem) { return detailElem.type == elem.type && detailElem.id == elem.id })) {
return {
- 'name': result.title,
- 'value': elem.id,
- 'url': result.url
+ 'category': result.subtitle,
+ 'name': result.title,
+ 'value': elem.id,
+ 'url': result.url
}
}
})
@@ -599,8 +601,11 @@
var element = $('
')
.attr('data-id', elem.value)
.attr('data-url', elem.url)
- .text(elem.name)
- .addClass('u-clickable u-textTruncate')
+ .addClass('u-clickable u-textTruncate with-category')
+
+ element.append($('').text(elem.category))
+ element.append('
')
+ element.append($('').text(elem.name))
if (index == array.length-1) {
element.addClass('is-active')
diff --git a/app/assets/javascripts/app/models/knowledge_base_category.coffee b/app/assets/javascripts/app/models/knowledge_base_category.coffee
index 4a0fb6b1e..fab6921e4 100644
--- a/app/assets/javascripts/app/models/knowledge_base_category.coffee
+++ b/app/assets/javascripts/app/models/knowledge_base_category.coffee
@@ -38,6 +38,22 @@ class App.KnowledgeBaseCategory extends App.Model
memo.concat elem.categoriesForDropdown(nested: options.nested + 1, kb_locale: options.kb_locale)
, initial
+ categoriesForSearch: (options = {}) ->
+ result = [@guaranteedTitle(options.kb_locale.id)]
+
+ check = @
+ while check.parent()
+ result.push(check.parent().guaranteedTitle(options.kb_locale.id))
+ check = check.parent()
+
+ if options.full || result.length <= 2
+ result = result.reverse().join(' > ')
+ else
+ result = result.reverse()
+ result = "#{result[0]} > .. > #{result[result.length - 1]}"
+
+ result
+
configure_attributes: (kb_locale = undefined) ->
[
{
diff --git a/app/assets/javascripts/app/views/generic/searchable_select_option.jst.eco b/app/assets/javascripts/app/views/generic/searchable_select_option.jst.eco
index bbefe2890..8ec9a7e09 100644
--- a/app/assets/javascripts/app/views/generic/searchable_select_option.jst.eco
+++ b/app/assets/javascripts/app/views/generic/searchable_select_option.jst.eco
@@ -1,8 +1,9 @@
+ <% if @option.category: %><%= @option.category %>
<% end %>
<%= @option.name %><% if @detail: %><%= @detail %><% end %>
<% if @option.children: %>
<%- @Icon('arrow-right', 'recipientList-arrow') %>
<% end %>
-
\ No newline at end of file
+
diff --git a/app/assets/javascripts/app/views/popover/kb_generic.jst.eco b/app/assets/javascripts/app/views/popover/kb_generic.jst.eco
index a2a033367..3300ca6ac 100644
--- a/app/assets/javascripts/app/views/popover/kb_generic.jst.eco
+++ b/app/assets/javascripts/app/views/popover/kb_generic.jst.eco
@@ -27,6 +27,10 @@
-
<% end %>
+
<%= App.KnowledgeBaseLocale.localeFor(@object).systemLocale().name %>
diff --git a/app/assets/stylesheets/zammad.scss b/app/assets/stylesheets/zammad.scss
index de3b84546..f3c66b427 100644
--- a/app/assets/stylesheets/zammad.scss
+++ b/app/assets/stylesheets/zammad.scss
@@ -8371,6 +8371,16 @@ footer {
}
}
+ .dropdown li.with-category, .dropdown.dropdown--actions li.with-category {
+ line-height: 19.5px;
+ }
+
+ .dropdown.dropdown--actions li.with-category {
+ height: 39px;
+ padding: 0px 15px;
+ display: list-item;
+ }
+
.dropdown li:not(:first-child) {
box-shadow: 0 1px rgba(255,255,255,.13) inset;
}
@@ -13023,4 +13033,3 @@ span.is-disabled {
.text-modules-box {
max-height: 40vh;
}
-
diff --git a/app/controllers/knowledge_base/search_controller.rb b/app/controllers/knowledge_base/search_controller.rb
index d68f99071..0b86a6bbe 100644
--- a/app/controllers/knowledge_base/search_controller.rb
+++ b/app/controllers/knowledge_base/search_controller.rb
@@ -75,7 +75,13 @@ class KnowledgeBase::SearchController < ApplicationController
def public_item_details_answer(meta, object)
category_translation = object.answer.category.translation_preferred(object.kb_locale)
- path = help_answer_path(category_translation, object, locale: object.kb_locale.system_locale.locale)
+ path = help_answer_path(category_translation, object, locale: object.kb_locale.system_locale.locale)
+ subtitle = object.answer.category.self_with_parents.map { |c| strip_tags(c.translation_preferred(object.kb_locale).title) }.reverse
+ subtitle = if subtitle.count <= 2
+ subtitle.join(' > ')
+ else
+ subtitle.values_at(0, -1).join(' > .. > ')
+ end
url = case url_type
when :public
@@ -91,7 +97,7 @@ class KnowledgeBase::SearchController < ApplicationController
date: object.updated_at,
url: url,
title: meta.dig(:highlight, 'title')&.first || object.title,
- subtitle: strip_tags(category_translation.title),
+ subtitle: subtitle,
body: meta.dig(:highlight, 'content.body')&.first || strip_tags(object.content.body).truncate(100)
}
end
diff --git a/app/models/knowledge_base/category.rb b/app/models/knowledge_base/category.rb
index 665fea491..27e12e9d3 100644
--- a/app/models/knowledge_base/category.rb
+++ b/app/models/knowledge_base/category.rb
@@ -61,6 +61,18 @@ class KnowledgeBase::Category < ApplicationModel
[self] + children.map(&:self_with_children).flatten
end
+ def self_with_parents
+ result = [self]
+
+ check = self
+ while check.parent.present?
+ result << check.parent
+ check = check.parent
+ end
+
+ result
+ end
+
def self_with_children_answers
KnowledgeBase::Answer.where(category_id: self_with_children_ids)
end
diff --git a/spec/models/knowledge_base/category_spec.rb b/spec/models/knowledge_base/category_spec.rb
index 58543b498..98bb8a4b0 100644
--- a/spec/models/knowledge_base/category_spec.rb
+++ b/spec/models/knowledge_base/category_spec.rb
@@ -40,6 +40,10 @@ RSpec.describe KnowledgeBase::Category, type: :model, current_user_id: 1 do
expect(kb_category_with_tree.self_with_children.count).to eq 7
end
+ it 'fetches all parents' do
+ expect(grandchild_category.self_with_parents.count).to eq 3
+ end
+
it 'root category has no parent' do
expect(kb_category_with_tree.parent).to be_blank
end
diff --git a/spec/requests/knowledge_base/search_with_details_spec.rb b/spec/requests/knowledge_base/search_with_details_spec.rb
index 221a00519..1804a21e6 100644
--- a/spec/requests/knowledge_base/search_with_details_spec.rb
+++ b/spec/requests/knowledge_base/search_with_details_spec.rb
@@ -61,4 +61,28 @@ RSpec.describe 'Knowledge Base search with details', type: :request, searchindex
expect(json_response['details'][0]['subtitle']).to eq category.translation_to(primary_locale).title
end
end
+
+ context 'when answer tree is long' do
+ let(:category1) { create('knowledge_base/category') }
+ let(:category2) { create('knowledge_base/category', parent: category1) }
+ let(:category3) { create('knowledge_base/category', parent: category2) }
+ let(:answer_cut_tree) { create(:knowledge_base_answer, :published, :with_attachment, category: category3) }
+ let(:category4) { create('knowledge_base/category') }
+ let(:category5) { create('knowledge_base/category', parent: category4) }
+ let(:answer_full_tree) { create(:knowledge_base_answer, :published, :with_attachment, category: category5) }
+
+ before do
+ answer_cut_tree && answer_full_tree && rebuild_searchindex
+ end
+
+ it 'returns category with cut tree', authenticated_as: -> { create(:admin) } do
+ post endpoint, params: { query: answer_cut_tree.translations.first.title }
+ expect(json_response['details'][0]['subtitle']).to eq("#{category1.translations.first.title} > .. > #{category3.translations.first.title}")
+ end
+
+ it 'returns category with full tree', authenticated_as: -> { create(:admin) } do
+ post endpoint, params: { query: answer_full_tree.translations.first.title }
+ expect(json_response['details'][0]['subtitle']).to eq("#{category4.translations.first.title} > #{category5.translations.first.title}")
+ end
+ end
end