Fixes #2612 - Tag KB Answers

This commit is contained in:
Mantas Masalskis 2021-08-16 10:20:07 +02:00 committed by Martin Gruner
parent 467bc03224
commit dde8046470
43 changed files with 522 additions and 58 deletions

View File

@ -244,6 +244,11 @@
"url": "",
"license": "MIT"
},
"hashtag.svg": {
"author": "Felix Niklas",
"url": "",
"license": "MIT"
},
"help.svg": {
"author": "Felix Niklas",
"url": "",

View File

@ -2,13 +2,17 @@ class App.KnowledgeBaseReaderController extends App.Controller
@extend App.PopoverProvidable
@registerPopovers 'Ticket'
events:
'click .js-tag': 'searchTag'
elements:
'.js-answer-title': 'answerTitle'
'.js-answer-body': 'answerBody'
'.js-answer-pagination': 'answerPagination'
'.js-answer-attachments': 'answerAttachments'
'.js-answer-title': 'answerTitle'
'.js-answer-body': 'answerBody'
'.js-answer-pagination': 'answerPagination'
'.js-answer-attachments': 'answerAttachments'
'.js-answer-tags': 'answerTags'
'.js-answer-linked-tickets': 'answerLinkedTickets'
'.js-answer-meta': 'answerMeta'
'.js-answer-meta': 'answerMeta'
constructor: ->
super
@ -60,6 +64,7 @@ class App.KnowledgeBaseReaderController extends App.Controller
return
@renderAttachments(answer.attachments)
@renderTags(answer.tags)
@renderLinkedTickets(answer.translation(kb_locale.id)?.linked_tickets())
paginator = new App.KnowledgeBaseReaderPagination(object: @object, kb_locale: kb_locale)
@ -129,6 +134,11 @@ class App.KnowledgeBaseReaderController extends App.Controller
attachments: attachments
)
renderTags: (tags) ->
@answerTags.html App.view('knowledge_base/_reader_tags')(
tags: tags
)
renderLinkedTickets: (linked_tickets) ->
@answerLinkedTickets.html App.view('knowledge_base/_reader_linked_tickets')(
tickets: linked_tickets
@ -154,3 +164,8 @@ class App.KnowledgeBaseReaderController extends App.Controller
return
decodeURIComponent @parentController.lastParams.arguments
searchTag: (e) ->
e.preventDefault()
item = $(e.currentTarget).text()
App.GlobalSearchWidget.search(item, 'tags')

View File

@ -15,7 +15,9 @@ class App.KnowledgeBaseSidebar extends App.Controller
true
rerender: ->
@show(@savedParams, @savedAction)
@delay( =>
@show(@savedParams, @savedAction)
, 300, 'rerender')
contentActionClicked: (e) ->
# coffeelint: disable=indentation
@ -59,5 +61,6 @@ class App.KnowledgeBaseSidebar extends App.Controller
if object instanceof App.KnowledgeBaseAnswer
output.push App.KnowledgeBaseSidebarLinkedTickets
output.push App.KnowledgeBaseSidebarAttachments
output.push App.KnowledgeBaseSidebarTags
output

View File

@ -0,0 +1,13 @@
class App.KnowledgeBaseSidebarTags extends App.Controller
className: 'sidebar-block'
constructor: ->
super
@widget = new App.WidgetTag(
el: @el
templateName: 'knowledge_base/sidebar/tags'
object_type: 'KnowledgeBaseAnswer'
object: @object
tags: @object.tags
)

View File

@ -2,6 +2,7 @@ class App.WidgetTag extends App.Controller
editMode: false
pendingRefresh: false
possibleTags: {}
templateName:'widget/tag'
elements:
'.js-newTagLabel': 'newTagLabel'
'.js-newTagInput': 'newTagInput'
@ -50,7 +51,7 @@ class App.WidgetTag extends App.Controller
render: =>
return if @lastLocalTags && _.isEqual(@lastLocalTags, @localTags)
@lastLocalTags = _.clone(@localTags)
@html App.view('widget/tag')(
@html App.view(@templateName)(
tags: @localTags || [],
)
source = "#{App.Config.get('api_path')}/tag_search"

View File

@ -45,6 +45,7 @@ InstanceMethods =
if @ instanceof App.KnowledgeBaseAnswer
attrs.icon = 'knowledge-base-answer'
attrs.state = @can_be_published_state()
attrs.tags = @tags
if options.isEditor
attrs.editorOnly = !@is_internally_published(kb_locale)

View File

@ -0,0 +1,14 @@
<% if @tags?.length: %>
<div class="attachments attachments--list">
<%- @Icon('hashtag') %>
<div class="attachments-title">
<%- @T('Tags') %>
</div>
<div class="knowledge-base-article-tags--container">
<% for tag, i in @tags: %>
<a href="#" class="tag js-tag"><%= tag %></a>
<% end %>
</div>
</div>
<% end %>

View File

@ -16,6 +16,7 @@
<div class="knowledge-base-article-content richtext-content js-answer-body"></div>
<div class="knowledge-base-article-attachments js-answer-attachments"></div>
<div class="knowledge-base-article-linked-tickets js-answer-linked-tickets"></div>
<div class="knowledge-base-article-tags js-answer-tags"></div>
</div>
<div class="js-answer-pagination"></div>

View File

@ -4,15 +4,12 @@
<div class="dropContainer">
<% if @attachments.length > 0: %>
<ol class="tasks tasks--standalone">
<ol class="list list--sidebar">
<% for attachment in @attachments: %>
<li class="task">
<div class="task-text">
<a class="name" target=_blank href="<%= attachment.url %>">
<%= attachment.filename %>
</a>
</div>
<li class="list-item">
<a class="list-item-name" target=_blank href="<%= attachment.url %>">
<%= attachment.filename %>
</a>
<a class="list-item-delete js-delete" data-object-id="<%= attachment.id %>" href="#">
<%- @Icon('diagonal-cross') %>
</a>

View File

@ -0,0 +1,24 @@
<div class="sidebar-block-header">
<h2><%- @T('Tags') %></h2>
</div>
<% if @tags.length > 0: %>
<ol class="list list--sidebar">
<% for elem in @tags: %>
<li class="list-item" data-id="<%= elem %>">
<a class="list-item-name js-tag"><%= elem %></a>
<div class="list-item-delete js-delete" data-type="remove">
<%- @Icon('diagonal-cross') %>
</div>
</li>
<% end %>
</ol>
<% end %>
<a class="sidebar-block-button text-muted js-newTagLabel" href="#">
+ <%- @T('Add Tag') %>
</a>
<form class="ui-front">
<input type="text" name="new_tag" class="hide js-newTagInput" autocomplete="off">
</form>

View File

@ -35,6 +35,13 @@
<label><%- @T('Language') %></label>
<%= App.KnowledgeBaseLocale.localeFor(@object).systemLocale().name %>
</div>
<% if !_.isEmpty(@object.parent().tags): %>
<div class="column">
<label><%- @T('Tags') %></label>
<%= @object.parent().tags.map((elem) -> "##{elem}").join(' ') %>
</div>
<% end %>
</div>
</div>
<% end %>

View File

@ -74,15 +74,17 @@
this.el.classList.add('result')
this.el.innerHTML = this.constructor.template
this.setTitle(data.title)
this.setTitle(data.title, data.tags)
this.setSubtitle(data.subtitle)
this.setPreview(data.body)
this.setURL(data.url)
this.setIcon(data.icon, data.type)
}
this.setTitle = function(text) {
this.el.querySelector('.result-title').innerHTML = text || ''
this.setTitle = function(text, tags) {
var title = text || ''
this.el.querySelector('.result-title').innerHTML = title
}
this.setSubtitle = function(text) {

View File

@ -455,6 +455,12 @@ b {
.main--categories {
h1 {
color: $dark-color;
.icon-hashtag {
fill: hsl(208,13%,81%);
width: .7em;
height: .7em;
}
}
}
@ -566,6 +572,11 @@ b {
width: 16px;
height: 16px;
&-hashtag {
width: 14px;
height: 14px;
}
&-knowledge-base {
width: 20px;
height: 20px;
@ -627,6 +638,8 @@ b {
}
.sections--list {
padding: 0 !important;
&:first-child {
margin-top: 20px;
}
@ -948,9 +961,13 @@ b {
}
}
.attachments {
.article-tags {
margin-top: 1rem;
}
.article-accessories {
padding: 2rem 0 2rem 4rem;
margin: 2rem 0 !important;
margin: 2rem 0;
list-style: none;
border-top: 1px solid $border;
position: relative;
@ -959,7 +976,7 @@ b {
padding-left: 2.8rem;
}
.icon-paperclip {
.icon {
position: absolute;
left: 1.2rem;
top: 2rem;
@ -979,6 +996,30 @@ b {
color: $light-color;
padding: 0 .8rem .2rem;
}
&:not(:last-child) {
padding-bottom: 0;
margin-bottom: 0;
}
}
.tags-content {
display: flex;
flex-wrap: wrap;
padding: .2rem .6rem 0;
.tag {
margin: .2rem;
}
}
.tag {
font-size: .8em;
display: inline-block;
color: white;
border-radius: 999px;
padding: 2px 12px 1px;
text-decoration: none;
}
.attachment {

View File

@ -48,6 +48,7 @@
.icon-gitlab-logo { width: 24px; height: 24px; }
.icon-google-button { width: 29px; height: 24px; }
.icon-group { width: 24px; height: 24px; }
.icon-hashtag { width: 28px; height: 28px; }
.icon-help { width: 16px; height: 16px; }
.icon-horizontal-rule { width: 12px; height: 12px; }
.icon-important { width: 16px; height: 16px; }

View File

@ -5168,6 +5168,12 @@ footer {
.btn-list {
margin-bottom: 5px;
}
&-button {
.sidebar-block-header + &.text-muted {
margin-top: 3px;
}
}
}
.sidebar-git-issue-delete {
@ -6608,8 +6614,7 @@ footer {
}
}
.attachments .icon-paperclip,
.attachments .icon-overviews {
.attachments > .icon:first-child {
position: absolute;
left: 33px;
top: 27px;
@ -7610,6 +7615,18 @@ footer {
margin-top: 10px;
}
.tag {
display: inline-block;
background: #0F94D6;
color: white;
border-radius: 999px;
padding: 2px 12px 1px;
&:hover {
background: hsl(200deg, 87%, 34%);
}
}
.userNotifications label + .btn {
margin-top: 1px;
}
@ -12929,6 +12946,7 @@ span.is-disabled {
}
&-attachments,
&-tags,
&-linked-tickets {
margin: 0 -30px;
@ -12941,6 +12959,16 @@ span.is-disabled {
@include bidi-style(margin-left, 7px, margin-right, 0);
}
&-tags--container {
padding: 6px 5px 11px;
display: flex;
flex-wrap: wrap;
.tag {
margin: 2px;
}
}
&-nav {
display: flex;

View File

@ -0,0 +1,13 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
class KnowledgeBase::Public::TagsController < KnowledgeBase::Public::BaseController
def show
@object = [:tag, params[:tag]]
all_tagged = KnowledgeBase::Answer.tag_objects(params[:tag])
@answers = policy_scope(all_tagged)
.localed(system_locale_via_uri)
.sorted
end
end

View File

@ -100,7 +100,8 @@ class KnowledgeBase::SearchController < ApplicationController
url: url,
title: meta.dig(:highlight, 'title')&.first || object.title,
subtitle: subtitle,
body: meta.dig(:highlight, 'content.body')&.first || strip_tags(object.content.body).truncate(100)
body: meta.dig(:highlight, 'content.body')&.first || strip_tags(object.content.body).truncate(100),
tags: object.answer.tag_list
}
end

View File

@ -2,7 +2,11 @@
module KnowledgeBaseBreadcrumbHelper
def render_breadcrumb_if_needed(knowledge_base, object, alternative)
objects = calculate_breadcrumb_path(object, alternative)
objects = if object.is_a? Array
calculate_breadcrumb_nonpath(object)
else
calculate_breadcrumb_path(object, alternative)
end
return if objects.empty?
@ -25,6 +29,10 @@ module KnowledgeBaseBreadcrumbHelper
objects + [last].compact
end
def calculate_breadcrumb_nonpath(object)
[object]
end
def calculate_breadcrumb_to_category(category)
return [] if category.blank?
@ -53,6 +61,8 @@ module KnowledgeBaseBreadcrumbHelper
case object
when HasTranslations
object.translation.title
when Array
object[1]
else
object
end

View File

@ -73,7 +73,7 @@ module KnowledgeBaseHelper
def kb_public_system_path(*objects)
objects
.compact
.map { |elem| elem.translation.to_param }
.map { |elem| elem.is_a?(HasTranslations) ? elem.translation.to_param : elem }
.unshift(help_root_path)
.join('/')
end

View File

@ -9,6 +9,8 @@ module KnowledgeBaseIconHelper
icon 'knowledge-base-answer'
when KnowledgeBase
icon 'knowledge-base'
when Array
icon 'hashtag' # object[0] override while tag icon is available
end
end

View File

@ -9,15 +9,22 @@ module KnowledgeBasePublicPageTitleHelper
end
def kb_public_page_title_suffix(item, exception)
return item&.translation&.title if exception.blank?
case item
when HasTranslations
return item&.translation&.title if exception.blank?
suffix = case exception
when :not_found
'Not Found'
when :alternatives
'Alternative Translations'
end
zt kb_public_page_title_suffix_exception(exception)
when String
item
end
end
zt(suffix)
def kb_public_page_title_suffix_exception(exception)
case exception
when :not_found
'Not Found'
when :alternatives
'Alternative Translations'
end
end
end

View File

@ -34,4 +34,14 @@ module KnowledgeBaseTopBarHelper
'Published'
end
end
def render_top_bar_if_needed(object, knowledge_base)
return if !policy(:knowledge_base).edit?
editable = object || knowledge_base
return if !editable.is_a? HasTranslations
render 'knowledge_base/public/top_banner', object: editable
end
end

View File

@ -3,6 +3,7 @@
class KnowledgeBase::Answer < ApplicationModel
include HasTranslations
include HasAgentAllowedParams
include HasTags
include CanBePublished
include ChecksKbClientNotification
include CanCloneAttachments
@ -33,6 +34,7 @@ class KnowledgeBase::Answer < ApplicationModel
attrs = super
attrs[:attachments] = attachments_sorted.map { |elem| self.class.attachment_to_hash(elem) }
attrs[:tags] = tag_list
Cache.write(key, attrs)
@ -112,6 +114,11 @@ class KnowledgeBase::Answer < ApplicationModel
end
before_save :reordering_callback
def touch_translations
translations.each(&:touch) # move to #touch_all when migrationg to Rails 6
end
after_touch :touch_translations
class << self
def attachment_to_hash(attachment)
url = Rails.application.routes.url_helpers.attachment_path(attachment.id)

View File

@ -56,12 +56,13 @@ class KnowledgeBase::Answer::Translation < ApplicationModel
def search_index_attribute_lookup(include_references: true)
attrs = super
attrs['title'] = ActionController::Base.helpers.strip_tags attrs['title']
attrs['content'] = content.search_index_attribute_lookup if content
attrs['scope_id'] = answer.category_id
attrs['attachment'] = answer.attachments_for_search_index_attribute_lookup
attrs
attrs.merge({
title: ActionController::Base.helpers.strip_tags(attrs['title']),
content: content&.search_index_attribute_lookup,
scope_id: answer.category_id,
attachment: answer.attachments_for_search_index_attribute_lookup,
tags: answer.tag_list
})
end
def linked_references

View File

@ -21,6 +21,10 @@
color: <%= knowledge_base.color_highlight %>;
}
.tag {
background: <%= knowledge_base.color_highlight %>;
}
.header {
background-color: <%= knowledge_base.color_header %>;
}

View File

@ -12,9 +12,9 @@ data-available-locales='<%= @object_locales.map(&:locale).join(',') %>'>
</div>
<% if (attachments = @object.attachments_sorted) && attachments.present? %>
<div class="attachments">
<div class="attachments article-accessories">
<%= icon 'paperclip' %>
<div class="attachments-title"><%= zt('Attached Files') %></div>
<div class="article-accessories-title"><%= zt('Attached Files') %></div>
<% attachments.each do |attachment| %>
<%= link_to custom_path_if_needed(attachment_path(attachment), @knowledge_base), class: 'attachment', download: true do %>
<span class="attachment-name u-highlight"><%= attachment.filename %></span>
@ -23,6 +23,18 @@ data-available-locales='<%= @object_locales.map(&:locale).join(',') %>'>
<% end %>
</div>
<% end %>
<% if (tags = @object.tag_list) && tags.present? %>
<div class="tags article-accessories">
<%= icon 'hashtag' %>
<div class="article-accessories-title"><%= zt('Tags') %></div>
<div class="tags-content">
<% tags.each do |tag| %>
<%= link_to tag, custom_path_if_needed(help_tag_path(tag, locale: params[:locale]), @knowledge_base), class: 'tag' %>
<% end %>
</div>
</div>
<% end %>
</div>
</article>
</main>

View File

@ -1,6 +1,9 @@
<%= link_to custom_path_if_needed help_answer_path(answer.category.translation, answer.translation, locale: params[:locale]), @knowledge_base do %>
<span class="section-inner">
<%= icon('knowledge-base-answer') %>
<%= answer.translation.title %> <%= visibility_note(answer) %>
</span>
<% end %>
<li class="section <%= visibility_class_name(answer) %>">
<%= link_to custom_path_if_needed help_answer_path(answer.category.translation, answer.translation, locale: params[:locale]), @knowledge_base do %>
<span class="section-inner">
<%= icon('knowledge-base-answer') %>
<%= answer.translation.title %>
<%= visibility_note(answer) %>
</span>
<% end %>
</li>

View File

@ -27,15 +27,13 @@
<% end %>
<% if @categories&.present? && @answers&.present? %>
<hr>
<hr>
<% end %>
<% if @answers&.present? %>
<ul class="sections sections--list" data-less-than-four="<%= @answers.length < 4 %>">
<% @answers.each do |translation| %>
<li class="section <%= visibility_class_name(translation) %>">
<%= render 'answer', { answer: translation } %>
</li>
<%= render 'answer', { answer: translation } %>
<% end %>
</ul>
<% end %>

View File

@ -0,0 +1,19 @@
<main class="main main--categories">
<div class="container">
<h1>
<%= icon 'hashtag' %> <%= @object[1] %>
</h1>
<% if @answers&.present? %>
<ul class="sections sections--list" data-less-than-four="<%= @answers.length < 4 %>">
<% @answers.each do |translation| %>
<%= render 'knowledge_base/public/categories/answer', { answer: translation } %>
<% end %>
</ul>
<% else %>
<div class="sections-empty">
<%= zt('No content to show') %>
</div>
<% end %>
</div>
</main>

View File

@ -16,7 +16,7 @@
<%= canonical_link_tag @knowledge_base, @category, @object %>
<div class="wrapper js-wrapper">
<%= render 'knowledge_base/public/top_banner', object: @object || @knowledge_base if policy(:knowledge_base).edit? %>
<%= render_top_bar_if_needed @object, @knowledge_base %>
<header class="header js-header">
<div class="container">

View File

@ -68,6 +68,8 @@ Zammad::Application.routes.draw do
get '', to: 'knowledge_base/public/categories#forward_root', as: :help_no_locale
get ':locale', to: 'knowledge_base/public/categories#index', as: :help_root
get ':locale/tag/:tag', to: 'knowledge_base/public/tags#show', as: :help_tag
get ':locale/:category', to: 'knowledge_base/public/categories#show', as: :help_category
get ':locale/:category/:answer', to: 'knowledge_base/public/answers#show', as: :help_answer
end

View File

@ -173,7 +173,7 @@ class SearchKnowledgeBaseBackend
if @params.fetch(:highlight_enabled, true)
output[:highlight_fields_by_indexes] = {
'KnowledgeBase::Answer::Translation': %w[title content.body attachment.content],
'KnowledgeBase::Answer::Translation': %w[title content.body attachment.content tags],
'KnowledgeBase::Category::Translation': %w[title],
'KnowledgeBase::Translation': %w[title]
}

View File

@ -350,6 +350,11 @@
group
</title>
<path d="M5.221 5.43c1.453 0 2.63-1.215 2.63-2.715C7.852 1.215 6.675 0 5.222 0c-1.453 0-2.63 1.216-2.63 2.715 0 1.5 1.177 2.715 2.63 2.715zm6.98 0c1.453 0 2.63-1.215 2.63-2.715 0-1.5-1.177-2.715-2.63-2.715C10.748 0 9.57 1.216 9.57 2.715c0 1.5 1.178 2.716 2.63 2.716zm5.982 0c1.453 0 2.631-1.215 2.631-2.715 0-1.5-1.178-2.715-2.63-2.715-1.454 0-2.632 1.216-2.632 2.715 0 1.5 1.178 2.716 2.631 2.716zm-2.408.863H8.273c-.641 0-1.161.536-1.161 1.197v6.846c0 .662.52 1.197 1.16 1.197h.358V24h6.65v-8.467h.495c.64 0 1.16-.535 1.16-1.197V7.49c0-.66-.52-1.197-1.16-1.197zm-10.3 8.043V7.49c0-.428.097-.83.26-1.197H1.86C1.22 6.293.7 6.829.7 7.49v6.846c0 .662.52 1.198 1.16 1.198h.358V24h4.775v-7.097a2.902 2.902 0 0 1-1.518-2.567zM22.29 6.293h-3.977c.163.366.26.77.26 1.197v6.846c0 1.172-.68 2.183-1.655 2.635V24h4.878v-8.467h.493c.641 0 1.161-.535 1.161-1.197V7.49c0-.66-.52-1.197-1.16-1.197z" fill-rule="evenodd"/>
</symbol><symbol id="icon-hashtag" viewBox="0 0 28 28">
<title>
hashtag
</title>
<path d="M12.5 1l-.78 6.5h5.5L18 1h3.5l-.78 6.5H26V11h-5.7l-.66 5.5H25V20h-5.78l-.72 6H15l.72-6h-5.5l-.72 6H6l.72-6H2v-3.5h5.14L7.8 11H3V7.5h5.22L9 1h3.5zm4.3 10h-5.5l-.66 5.5h5.5l.66-5.5z" fill-rule="evenodd"/>
</symbol><symbol id="icon-help" viewBox="0 0 16 16">
<title>
help

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="28px" height="28px" viewBox="0 0 28 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 63.1 (92452) - https://sketch.com -->
<title>hashtag</title>
<desc>Created with Sketch.</desc>
<g id="hashtag" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M12.5,1 L11.72,7.5 L17.22,7.5 L18,1 L21.5,1 L20.72,7.5 L26,7.5 L26,11 L20.3,11 L19.64,16.5 L25,16.5 L25,20 L19.22,20 L18.5,26 L15,26 L15.72,20 L10.22,20 L9.5,26 L6,26 L6.72,20 L2,20 L2,16.5 L7.14,16.5 L7.8,11 L3,11 L3,7.5 L8.22,7.5 L9,1 L12.5,1 Z M16.8,11 L11.3,11 L10.64,16.5 L16.14,16.5 L16.8,11 Z" id="Combined-Shape" fill="#50E3C2"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 774 B

View File

@ -3,10 +3,10 @@
FactoryBot.define do
factory 'knowledge_base/answer', aliases: %i[knowledge_base_answer] do
transient do
add_translation { true }
translation_traits { [] }
add_translation { true }
translation_traits { [] }
translation_attributes { {} }
knowledge_base { nil }
knowledge_base { nil }
end
category { create(:knowledge_base_category, { knowledge_base: knowledge_base }.compact) }
@ -43,6 +43,16 @@ FactoryBot.define do
end
end
trait :with_tag do
transient do
tag_names { %w[example_kb_tag] }
end
after(:create) do |answer, context|
context.tag_names.each { |tag| answer.tag_add tag }
end
end
trait :with_attachment do
transient do
attachment { File.open('spec/fixtures/upload/hello_world.txt') }

View File

@ -2,11 +2,14 @@
require 'rails_helper'
require 'models/concerns/checks_kb_client_notification_examples'
require 'models/concerns/has_tags_examples'
require 'models/contexts/factory_context'
RSpec.describe KnowledgeBase::Answer, type: :model, current_user_id: 1 do
subject!(:kb_answer) { create(:knowledge_base_answer) }
it_behaves_like 'HasTags'
include_context 'factory'
it_behaves_like 'ChecksKbClientNotification'

View File

@ -167,7 +167,7 @@ RSpec.describe Tag, type: :model do
let(:object_1) { create(:ticket) }
let(:object_2) { create(:knowledge_base_answer) }
let(:tag) { 'foo' }
let(:tag) { 'foo' }
it 'returns references' do
expect(described_class.tag_references(tag: tag)).to match_array [

View File

@ -9,6 +9,10 @@ RSpec.shared_context 'basic Knowledge Base', current_user_id: 1 do # rubocop:dis
knowledge_base.translation_primary.kb_locale
end
let :locale_name do
primary_locale.system_locale.locale
end
let :alternative_locale do
create(:knowledge_base_locale, knowledge_base: knowledge_base, system_locale: Locale.find_by(locale: 'lt'))
end
@ -29,6 +33,14 @@ RSpec.shared_context 'basic Knowledge Base', current_user_id: 1 do # rubocop:dis
create(:knowledge_base_answer, :published, :with_video, category: category)
end
let :published_answer_with_tag do
create(:knowledge_base_answer, :published, :with_tag, tag_names: [published_answer_tag_name], category: category)
end
let(:published_answer_tag_name) do
'example_kb_tag'
end
let :internal_answer do
create(:knowledge_base_answer, :internal, category: category)
end

View File

@ -87,4 +87,64 @@ RSpec.describe 'Knowledge Base Locale Answer Edit', type: :system do
expect(iframe['src']).to start_with('https://www.youtube.com/embed/')
end
end
context 'tags' do
before do
visit "#knowledge_base/#{knowledge_base.id}/locale/#{locale_name}/answer/#{published_answer_with_tag.id}/edit"
end
let(:new_tag_name) { 'capybara_kb_tag' }
it 'adds a new tag' do
within :active_content do
click '.js-newTagLabel'
elem = find('.js-newTagInput')
elem.fill_in with: new_tag_name
elem.send_keys :return
expect(page).to have_css('a.js-tag', text: new_tag_name)
end
end
it 'saves new tag to the database' do
within :active_content do
click '.js-newTagLabel'
elem = find('.js-newTagInput')
elem.fill_in with: new_tag_name
elem.send_keys :return
wait.until_exists { published_answer_with_tag.reload.tag_list.include? new_tag_name }
end
end
it 'shows an existing tag' do
within :active_content do
expect(page).to have_css('a.js-tag', text: published_answer_tag_name)
end
end
it 'deletes a tag' do
within :active_content do
click '.js-newTagLabel'
find('.list-item', text: published_answer_tag_name)
.find('.js-delete').click
expect(page).to have_no_css('a.js-tag', text: published_answer_tag_name)
end
end
it 'deletes the tag from the database' do
within :active_content do
click '.js-newTagLabel'
find('.list-item', text: published_answer_tag_name)
.find('.js-delete').click
wait.until_exists { published_answer_with_tag.reload.tag_list.exclude? published_answer_tag_name }
end
end
end
end

View File

@ -0,0 +1,51 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
require 'rails_helper'
RSpec.describe 'Knowledge Base Locale Answer Read', type: :system, authenticated_as: true do
include_context 'basic Knowledge Base'
describe 'tags' do
context 'when answer has tags' do
before do
visit "#knowledge_base/#{knowledge_base.id}/locale/#{locale_name}/answer/#{published_answer_with_tag.id}"
end
it 'has tags container' do
within :active_content do
expect(page).to have_css('.knowledge-base-article-tags--container')
end
end
it 'shows tag' do
within :active_content do
within '.knowledge-base-article-tags--container' do
expect(page).to have_css('a', text: published_answer_tag_name)
end
end
end
it 'opens search on clicking' do
within :active_content do
find('.knowledge-base-article-tags--container a', text: published_answer_tag_name).click
end
search_bar = find '#global-search'
expect(search_bar.value).to eq "tags:#{published_answer_tag_name}"
end
end
context 'when answer has no tags' do
before do
visit "#knowledge_base/#{knowledge_base.id}/locale/#{locale_name}/answer/#{published_answer.id}"
end
it 'has no tags container' do
within :active_content do
expect(page).to have_no_css('.knowledge-base-article-tags--container')
end
end
end
end
end

View File

@ -18,4 +18,20 @@ RSpec.describe 'Public Knowledge Base answer', type: :system, authenticated_as:
expect(iframe['src']).to start_with('https://www.youtube.com/embed/')
end
end
context 'tags' do
before do
visit help_answer_path(locale_name, category, published_answer_with_tag)
end
it 'shows an associated tag' do
expect(page).to have_css('.tags a', text: published_answer_tag_name)
end
it 'links to tag page' do
click '.tags a'
expect(current_url).to end_with help_tag_path(locale_name, published_answer_tag_name)
end
end
end

View File

@ -31,6 +31,11 @@ RSpec.describe 'Public Knowledge Base canonical link', type: :system, current_us
visit help_answer_path(locale, published_answer.category, published_answer)
expect(page).to have_canonical_url("#{prefix}/#{locale}/#{category_slug}/#{answer_slug}")
end
it 'includes canonical link on tag page' do
visit help_tag_path(locale, published_answer_tag_name)
expect(page).to have_canonical_url("#{prefix}/#{locale}/tag/#{published_answer_tag_name}")
end
end
shared_examples 'core locations' do

View File

@ -0,0 +1,51 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
require 'rails_helper'
RSpec.describe 'Public Knowledge Base tag', type: :system, authenticated_as: false do
include_context 'basic Knowledge Base'
context 'when answer with the tag exists' do
before do
published_answer && published_answer_with_tag
visit help_tag_path(locale_name, published_answer_tag_name)
end
it 'displays tag name' do
expect(page).to have_css('h1', text: published_answer_tag_name)
end
it 'lists an answer with the tag' do
expect(page).to have_css('a', text: published_answer_with_tag.translations.first.title)
end
it 'does not list another answer' do
expect(page).to have_no_css('a', text: published_answer.translations.first.title)
end
it 'does not show empty placeholder' do
expect(page).to have_no_css('.sections-empty')
end
end
context 'when no answers with the tag exists' do
before do
published_answer
visit help_tag_path(locale_name, published_answer_tag_name)
end
it 'shows empty placeholder' do
expect(page).to have_css('.sections-empty')
end
it 'shows no links' do
expect(page).to have_no_css('.main a')
end
it 'displays tag name' do
expect(page).to have_css('h1', text: published_answer_tag_name)
end
end
end