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 @@
  • \ 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 %> +
    + + <%= @object.parent().category().guaranteedTitle(@object.kb_locale_id) %> +
    <%= 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