Fixes #2867 - KB links are in the header and footer of the public KB, Fixes #2834 - unclear meaning of "Public Menu" tab in KB admin
This commit is contained in:
parent
a69aebcd0c
commit
cc2bc4f188
24 changed files with 607 additions and 125 deletions
|
@ -91,6 +91,7 @@ RSpec/FilePath:
|
|||
- 'spec/db/migrate/issue_2460_fix_corrupted_twitter_ids_spec.rb'
|
||||
- 'spec/db/migrate/issue_2715_fix_broken_twitter_urls_spec.rb'
|
||||
- 'spec/jobs/issue_2715_fix_broken_twitter_urls_job_spec.rb'
|
||||
- 'spec/db/migrate/issue_2867_footer_header_public_link_spec.rb'
|
||||
- 'spec/lib/import/base_factory_spec.rb'
|
||||
|
||||
# Offense count: 60
|
||||
|
|
|
@ -109,7 +109,7 @@ class App.ManageKnowledgeBase extends App.ControllerTabs
|
|||
},{
|
||||
name: 'Public Menu'
|
||||
target: 'public_menu'
|
||||
controller: App.KnowledgeBasePublicMenuForm
|
||||
controller: App.KnowledgeBasePublicMenuManager
|
||||
params: _.extend({}, params, { screen: 'public_menu' })
|
||||
},{
|
||||
name: 'Delete'
|
||||
|
|
|
@ -1,17 +1,69 @@
|
|||
class App.KnowledgeBasePublicMenuForm extends App.Controller
|
||||
events:
|
||||
'show.bs.tab': 'willShow'
|
||||
class App.KnowledgeBasePublicMenuForm extends App.ControllerModal
|
||||
autoFocusOnFirstInput: false
|
||||
includeForm: true
|
||||
|
||||
willShow: ->
|
||||
@el.empty()
|
||||
constructor: (params) ->
|
||||
@formItems = []
|
||||
@head = params.location.headline
|
||||
super
|
||||
|
||||
for kb_locale in App.KnowledgeBase.find(@knowledge_base_id).kb_locales()
|
||||
menu_items = App.KnowledgeBaseMenuItem.using_kb_locale(kb_locale)
|
||||
formParams: =>
|
||||
@formItems.map (elem) -> elem.buildData()
|
||||
|
||||
form_item = new App.KnowledgeBasePublicMenuFormItem(
|
||||
knowledge_base_id: @knowledge_base_id,
|
||||
kb_locale: kb_locale,
|
||||
menu_items: menu_items
|
||||
)
|
||||
content: ->
|
||||
@formItems = App.KnowledgeBase
|
||||
.find(@knowledge_base_id)
|
||||
.kb_locales()
|
||||
.map (kb_locale) =>
|
||||
menu_items = App.KnowledgeBaseMenuItem.using_kb_locale_location(kb_locale, @location.identifier)
|
||||
|
||||
@el.append form_item.el
|
||||
new App.KnowledgeBasePublicMenuFormItem(
|
||||
parent: @,
|
||||
knowledge_base_id: @knowledge_base_id,
|
||||
location: @location.identifier,
|
||||
kb_locale: kb_locale,
|
||||
menu_items: menu_items
|
||||
)
|
||||
|
||||
@formItems.map (elem) -> elem.el
|
||||
|
||||
hasError: ->
|
||||
@formItems
|
||||
.map (elem) -> elem.hasError()
|
||||
.filter((elem) -> elem)
|
||||
.pop()
|
||||
|
||||
onSubmit: (e) ->
|
||||
@preventDefaultAndStopPropagation(e)
|
||||
|
||||
if error = @hasError()
|
||||
@showAlert(error)
|
||||
return
|
||||
|
||||
@clearAlerts()
|
||||
@formItems.forEach (elem) -> elem.toggleUserInteraction(false)
|
||||
|
||||
kb = App.KnowledgeBase.find(@knowledge_base_id)
|
||||
|
||||
@ajax(
|
||||
id: 'update_menu_items'
|
||||
type: 'PATCH'
|
||||
url: kb.manageUrl('update_menu_items')
|
||||
data: JSON.stringify(menu_items_sets: @formParams())
|
||||
processData: true
|
||||
success: @onSuccess
|
||||
error: @onError
|
||||
)
|
||||
|
||||
onSuccess: (data, status, xhr) =>
|
||||
for formItem in @formItems
|
||||
for menuItem in App.KnowledgeBaseMenuItem.using_kb_locale_location(formItem.kb_locale, formItem.location)
|
||||
menuItem.remove(clear: true)
|
||||
|
||||
App.Collection.loadAssets(data.assets)
|
||||
App.KnowledgeBaseMenuItem.trigger('kb_data_change_loaded')
|
||||
@close()
|
||||
|
||||
onError: (xhr) =>
|
||||
@showAlert(xhr.responseJSON?.error_human || 'Couldn\'t save changes')
|
||||
@formItems.forEach (elem) -> elem.toggleUserInteraction(true)
|
||||
|
|
|
@ -3,7 +3,6 @@ class App.KnowledgeBasePublicMenuFormItem extends App.Controller
|
|||
'click .js-add': 'add'
|
||||
'click .js-remove': 'remove'
|
||||
'input input': 'input'
|
||||
'submit form': 'submit'
|
||||
|
||||
elements:
|
||||
'.js-alert': 'alert'
|
||||
|
@ -14,30 +13,29 @@ class App.KnowledgeBasePublicMenuFormItem extends App.Controller
|
|||
|
||||
render: ->
|
||||
@html App.view('knowledge_base/public_menu_form_item')(
|
||||
kb_locale_id: @kb_locale.id
|
||||
rows: @menu_items
|
||||
title: @kb_locale.systemLocale().name
|
||||
rows: @menu_items
|
||||
title: @kb_locale.systemLocale().name
|
||||
)
|
||||
|
||||
@applySortable()
|
||||
|
||||
applySortable: ->
|
||||
dndOptions =
|
||||
tolerance: 'pointer'
|
||||
distance: 15
|
||||
opacity: 0.6
|
||||
items: 'tr.sortable'
|
||||
start: (e, ui) ->
|
||||
tolerance: 'pointer'
|
||||
distance: 15
|
||||
opacity: 0.6
|
||||
items: 'tr.sortable'
|
||||
start: (e, ui) ->
|
||||
ui.placeholder.height( ui.item.height() )
|
||||
helper: (e, tr) ->
|
||||
helper: (e, tr) ->
|
||||
originals = tr.children()
|
||||
helper = tr
|
||||
helper.children().each (index, el) ->
|
||||
# Set helper cell sizes to match the original sizes
|
||||
$(@).width( originals.eq(index).width() )
|
||||
return helper
|
||||
update: @dndCallback
|
||||
stop: (e, ui) ->
|
||||
update: @dndCallback
|
||||
stop: (e, ui) ->
|
||||
ui.item.children().each (index, element) ->
|
||||
element.style.width = ''
|
||||
|
||||
|
@ -66,13 +64,14 @@ class App.KnowledgeBasePublicMenuFormItem extends App.Controller
|
|||
}
|
||||
|
||||
{
|
||||
kb_locale_id: @$('form').data('kb-locale-id'),
|
||||
menu_items: items
|
||||
kb_locale_id: @kb_locale.id,
|
||||
location: @location,
|
||||
menu_items: items
|
||||
}
|
||||
|
||||
input: ->
|
||||
if @validateForm(false)
|
||||
@hideAlert()
|
||||
if !@hasError()
|
||||
@parent.clearAlerts()
|
||||
|
||||
add: ->
|
||||
el = App.view('knowledge_base/public_menu_form_item_row')()
|
||||
|
@ -88,57 +87,14 @@ class App.KnowledgeBasePublicMenuFormItem extends App.Controller
|
|||
else
|
||||
row.remove()
|
||||
|
||||
showAlert: (message) ->
|
||||
translated = App.i18n.translatePlain(message)
|
||||
|
||||
@alert
|
||||
.text(translated)
|
||||
.removeClass('hidden')
|
||||
|
||||
hideAlert: ->
|
||||
@alert.addClass('hidden')
|
||||
|
||||
emptyFields: ->
|
||||
findEmptyFields: ->
|
||||
@$('tr.sortable:not(.js-deleted)')
|
||||
.find('input[data-name]')
|
||||
.toArray()
|
||||
.filter (elem) -> $(elem).val().length == 0
|
||||
|
||||
validateForm: (showAlert = true) ->
|
||||
if @emptyFields().length == 0
|
||||
return true
|
||||
hasError: ->
|
||||
if @findEmptyFields().length == 0
|
||||
return false
|
||||
|
||||
if showAlert
|
||||
@showAlert('Please fill in all fields')
|
||||
|
||||
false
|
||||
|
||||
submit: (e) ->
|
||||
@preventDefaultAndStopPropagation(e)
|
||||
|
||||
if !@validateForm()
|
||||
return
|
||||
|
||||
@hideAlert()
|
||||
@toggleUserInteraction(false)
|
||||
|
||||
kb = App.KnowledgeBase.find(@knowledge_base_id)
|
||||
|
||||
@ajax(
|
||||
id: 'update_menu_items'
|
||||
type: 'PATCH'
|
||||
url: kb.manageUrl('update_menu_items')
|
||||
data: JSON.stringify(@buildData())
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
for menu_item in App.KnowledgeBaseMenuItem.using_kb_locale(@kb_locale)
|
||||
menu_item.remove(clear: true)
|
||||
|
||||
App.Collection.loadAssets(data.assets)
|
||||
|
||||
@menu_items = App.KnowledgeBaseMenuItem.using_kb_locale(@kb_locale)
|
||||
@render()
|
||||
error: (xhr) =>
|
||||
@showAlert(xhr.responseJSON?.error_human || 'Couldn\'t save changes')
|
||||
@toggleUserInteraction(true)
|
||||
)
|
||||
'Please fill in all fields'
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
class App.KnowledgeBasePublicMenuManager extends App.Controller
|
||||
events:
|
||||
'show.bs.tab': 'willShow'
|
||||
'click .js-edit': 'edit'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
|
||||
@listenTo App.KnowledgeBaseMenuItem, 'kb_data_change_loaded', =>
|
||||
@render()
|
||||
|
||||
willShow: ->
|
||||
@render()
|
||||
|
||||
render: ->
|
||||
kb = App.KnowledgeBase.find(@knowledge_base_id)
|
||||
|
||||
@html App.view('knowledge_base/public_menu_manager')(
|
||||
locations: @locations(),
|
||||
locales: kb.kb_locales()
|
||||
)
|
||||
|
||||
locations: ->
|
||||
kb = App.KnowledgeBase.find(@knowledge_base_id)
|
||||
|
||||
[
|
||||
{
|
||||
headline: 'Header menu',
|
||||
identifier: 'header',
|
||||
color: kb.color_header
|
||||
},
|
||||
{
|
||||
headline: 'Footer menu',
|
||||
identifier: 'footer'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
edit: (e) =>
|
||||
@preventDefaultAndStopPropagation(e)
|
||||
|
||||
identifier = $(e.target).data('target-location')
|
||||
location = _.find @locations(), (elem) -> elem.identifier == identifier
|
||||
|
||||
new App.KnowledgeBasePublicMenuForm(
|
||||
location: location,
|
||||
knowledge_base_id: @knowledge_base_id
|
||||
container: @el.closest('.main')
|
||||
)
|
|
@ -5,3 +5,8 @@ class App.KnowledgeBaseMenuItem extends App.Model
|
|||
items = @findAllByAttribute('kb_locale_id', kb_locale.id)
|
||||
items.sort( (a, b) -> if a.position < b.position then -1 else 1)
|
||||
items
|
||||
|
||||
@using_kb_locale_location: (kb_locale, location) ->
|
||||
items = @all().filter (elem) -> elem.kb_locale_id is kb_locale.id and elem.location is location
|
||||
items.sort( (a, b) -> if a.position < b.position then -1 else 1)
|
||||
items
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<form data-kb-locale-id="<%= @kb_locale_id %>" class="settings-entry">
|
||||
<div data-kb-locale-id="<%= @kb_locale_id %>" class="settings-entry">
|
||||
<h2><%= @title %></h2>
|
||||
|
||||
<div class="js-alert alert alert--danger hidden"></div>
|
||||
|
@ -28,8 +28,4 @@
|
|||
</a>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="horizontal justify-end">
|
||||
<button type="submit" class="btn btn--primary"><%- @T('Submit') %></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
<h2>
|
||||
<%- @T 'Public Menu' %>
|
||||
</h2>
|
||||
|
||||
<p class="help-text">
|
||||
<%- @T 'Here you can add further links to your public FAQ page, which will be displayed either in the header or footer.' %>
|
||||
</p>
|
||||
|
||||
<% for location in @locations: %>
|
||||
<div class="settings-entry kb-menu-settings-entry">
|
||||
<h3><%= @T(location.headline) %></h3>
|
||||
|
||||
<% for kb_locale in @locales: %>
|
||||
<div class="kb-menu-preview">
|
||||
<div class="label"><%= kb_locale.systemLocale().name %></div>
|
||||
|
||||
<div class="kb-menu-preview-container kb-menu-preview-container--<%= location.identifier %>" style="background-color: <%= location.color %>">
|
||||
<% menu_items = App.KnowledgeBaseMenuItem.using_kb_locale_location(kb_locale, location.identifier) %>
|
||||
|
||||
<% if menu_items.length == 0: %>
|
||||
<span class="text-muted"><%= @T 'Empty' %></span>
|
||||
<% else: %>
|
||||
<% for item in menu_items: %>
|
||||
<a href="<%= item.url %>" target="_blank"><%= item.title %></a>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<a
|
||||
class="btn btn--primary js-edit btn-manage-public-menu-edit"
|
||||
href="#"
|
||||
data-target-location="<%= location.identifier %>"
|
||||
data-target-locale="<%= kb_locale.id %>">
|
||||
|
||||
<%= @T 'Edit' %>
|
||||
</a>
|
||||
</div>
|
||||
<% end %>
|
|
@ -2472,6 +2472,36 @@ input.has-error {
|
|||
}
|
||||
}
|
||||
|
||||
.kb-menu-preview {
|
||||
margin-bottom: 1em;
|
||||
|
||||
&-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
border: 1px solid hsl(213,14%,91%);
|
||||
|
||||
&--footer {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
a, span {
|
||||
font-size: 14px;
|
||||
padding: .5em 1em;
|
||||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
line-height: 2em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: hsl(206,8%,50%);
|
||||
}
|
||||
|
||||
.label {
|
||||
text-transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.modified-icon {
|
||||
position: relative;
|
||||
line-height: 1;
|
||||
|
@ -11684,3 +11714,11 @@ span.is-disabled {
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.btn-manage-public-menu-edit {
|
||||
margin-top: 0
|
||||
}
|
||||
|
||||
.kb-menu-settings-entry {
|
||||
margin-bottom: 12px
|
||||
}
|
||||
|
|
|
@ -31,13 +31,10 @@ class KnowledgeBase::ManageController < KnowledgeBase::BaseController
|
|||
|
||||
def update_menu_items
|
||||
kb = KnowledgeBase.find params[:id]
|
||||
kb_locale = kb.kb_locales.find params[:kb_locale_id]
|
||||
|
||||
KnowledgeBase::MenuItemUpdateAction
|
||||
.new(kb_locale, params[:menu_items])
|
||||
.perform!
|
||||
affected_items = KnowledgeBase::MenuItemUpdateAction.update_using_params! kb, params_for_permission[:menu_items_sets]
|
||||
|
||||
render json: { assets: ApplicationModel::CanAssets.reduce(kb_locale.menu_items.reload, {}) }
|
||||
render json: { assets: ApplicationModel::CanAssets.reduce(affected_items || [], {}) }
|
||||
end
|
||||
|
||||
def destroy
|
||||
|
|
|
@ -24,9 +24,7 @@ class KnowledgeBase::Public::BaseController < ApplicationController
|
|||
end
|
||||
|
||||
def menu_items
|
||||
@menu_items ||= KnowledgeBase::MenuItem
|
||||
.sorted
|
||||
.using_locale(guess_locale_via_uri || filter_primary_kb_locale)
|
||||
@menu_items ||= KnowledgeBase::MenuItem.using_locale(guess_locale_via_uri || filter_primary_kb_locale)
|
||||
end
|
||||
|
||||
def system_locale_via_uri
|
||||
|
|
|
@ -1,17 +1,24 @@
|
|||
class KnowledgeBase::MenuItem < ApplicationModel
|
||||
belongs_to :kb_locale, class_name: 'KnowledgeBase::Locale', inverse_of: :menu_items, touch: true
|
||||
|
||||
validates :title, presence: true, length: { maximum: 100 }
|
||||
validates :url, presence: true, length: { maximum: 100 }
|
||||
validates :title, presence: true, length: { maximum: 100 }
|
||||
validates :url, presence: true, length: { maximum: 500 }
|
||||
validates :location, presence: true, inclusion: { in: %w[header footer] }
|
||||
|
||||
acts_as_list scope: :kb_locale, top_of_list: 0
|
||||
acts_as_list scope: %i[kb_locale_id location], top_of_list: 0
|
||||
|
||||
scope :sorted, -> { order(position: :asc) }
|
||||
scope :using_locale, ->(locale) { locale.present? ? joins(:kb_locale).where(knowledge_base_locales: { system_locale_id: locale.id } ) : none }
|
||||
scope :sorted, -> { order(position: :asc) }
|
||||
scope :using_locale, ->(locale) { locale.present? ? joins(:kb_locale).where(knowledge_base_locales: { system_locale_id: locale.id } ) : none }
|
||||
scope :location, ->(location) { sorted.where(location: location) }
|
||||
|
||||
scope :location_header, -> { location(:header) }
|
||||
scope :location_footer, -> { location(:footer) }
|
||||
|
||||
private
|
||||
|
||||
def add_protocol_prefix
|
||||
return if url.blank?
|
||||
|
||||
url.strip!
|
||||
|
||||
return if url.match? %r{^\S+\:\/\/}
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<% end %>
|
||||
</h1>
|
||||
<nav class="menu">
|
||||
<% menu_items.each do |menu_item| %>
|
||||
<% menu_items.location_header.each do |menu_item| %>
|
||||
<%= link_to menu_item.title, menu_item.url, class: 'menu-item', target: menu_item.new_tab ? '_blank' : nil %>
|
||||
<% end %>
|
||||
</nav>
|
||||
|
@ -51,6 +51,13 @@
|
|||
<div class="copyright">
|
||||
<%= @knowledge_base.translation.footer_note %>
|
||||
</div>
|
||||
|
||||
<nav class="menu">
|
||||
<% menu_items.location_footer.each do |menu_item| %>
|
||||
<%= link_to menu_item.title, menu_item.url, class: 'menu-item', target: menu_item.new_tab ? '_blank' : nil %>
|
||||
<% end %>
|
||||
</nav>
|
||||
|
||||
<div class="language-picker">
|
||||
<a class="btn btn--action" href="#" data-toggle="dropdown" aria-expanded="false">
|
||||
<%= system_locale_via_uri.name %>
|
||||
|
|
|
@ -92,6 +92,7 @@ class InitializeKnowledgeBase < ActiveRecord::Migration[5.0]
|
|||
|
||||
create_table :knowledge_base_menu_items do |t|
|
||||
t.references :kb_locale, null: false, foreign_key: { to_table: :knowledge_base_locales, on_delete: :cascade }
|
||||
t.string :location, null: false, index: true
|
||||
t.integer :position, null: false, index: true
|
||||
t.string :title, null: false, limit: 100
|
||||
t.string :url, null: false, limit: 500
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
class Issue2867FooterHeaderPublicLink < ActiveRecord::Migration[5.2]
|
||||
def up
|
||||
# return if it's a new setup
|
||||
return if !Setting.find_by(name: 'system_init_done')
|
||||
|
||||
add_column :knowledge_base_menu_items, :location, :string, null: false, default: 'header'
|
||||
add_index :knowledge_base_menu_items, :location
|
||||
change_column_default :knowledge_base_menu_items, :location, nil
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :knowledge_base_menu_items, :location
|
||||
end
|
||||
end
|
|
@ -1,10 +1,15 @@
|
|||
class KnowledgeBase
|
||||
class MenuItemUpdateAction
|
||||
def initialize(kb_locale, menu_items_data)
|
||||
def initialize(kb_locale, location, menu_items_data)
|
||||
@kb_locale = kb_locale
|
||||
@location = location
|
||||
@menu_items_data = menu_items_data
|
||||
end
|
||||
|
||||
def scope
|
||||
@kb_locale.menu_items.location(@location)
|
||||
end
|
||||
|
||||
def perform!
|
||||
raise_unprocessable unless all_ids_present?
|
||||
|
||||
|
@ -16,15 +21,49 @@ class KnowledgeBase
|
|||
end
|
||||
end
|
||||
|
||||
# Mass-update KB menu items
|
||||
#
|
||||
# @param [KnowledgeBase] knowledge_base
|
||||
# @param [[<Hash>]] params @see .update_location_params!
|
||||
#
|
||||
# @return [<KnowledgeBase::MenuItem>]
|
||||
def self.update_using_params!(knowledge_base, params)
|
||||
return if params.blank?
|
||||
|
||||
params
|
||||
.map { |location_params| update_location_using_params! knowledge_base, location_params }
|
||||
.map(&:reload)
|
||||
.reduce(:+)
|
||||
end
|
||||
|
||||
# Mass-update KB menu items in a given location
|
||||
#
|
||||
# @param [KnowledgeBase] knowledge_base
|
||||
# @param [Hash] location_params
|
||||
#
|
||||
# @option location_params [Integer] :kb_locale_id
|
||||
# @option location_params [String] :location header or footer
|
||||
# @option location_params [[<Hash>]] :menu_items @see #update_order
|
||||
def self.update_location_using_params!(knowledge_base, location_params)
|
||||
action = new(
|
||||
knowledge_base.kb_locales.find(location_params[:kb_locale_id]),
|
||||
location_params[:location],
|
||||
location_params[:menu_items]
|
||||
)
|
||||
|
||||
action.perform!
|
||||
action.scope
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_order
|
||||
old_items = @kb_locale.menu_items.to_a
|
||||
old_items = scope.to_a
|
||||
|
||||
@menu_items_data
|
||||
.reject { |elem| elem[:_destroy] }
|
||||
.each_with_index do |data_elem, index|
|
||||
item = old_items.find { |record| record.id == data_elem[:id] } || @kb_locale.menu_items.build
|
||||
item = old_items.find { |record| record.id == data_elem[:id] } || scope.build
|
||||
|
||||
item.position = index
|
||||
item.title = data_elem[:title]
|
||||
|
@ -43,7 +82,7 @@ class KnowledgeBase
|
|||
end
|
||||
|
||||
def all_ids_present?
|
||||
old_ids = @kb_locale.menu_items.pluck(:id)
|
||||
old_ids = scope.pluck(:id)
|
||||
new_ids = @menu_items_data.map { |elem| elem[:id]&.to_i }.compact
|
||||
|
||||
old_ids.sort == new_ids.sort
|
||||
|
|
39
spec/db/migrate/issue_2867_footer_header_public_link_spec.rb
Normal file
39
spec/db/migrate/issue_2867_footer_header_public_link_spec.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Issue2867FooterHeaderPublicLink, type: :db_migration do
|
||||
self.use_transactional_tests = false # see comments on #without_index method
|
||||
|
||||
before { without_column(table, column: column) }
|
||||
|
||||
let(:table) { :knowledge_base_menu_items }
|
||||
let(:column) { :location }
|
||||
|
||||
it 'adds an index' do
|
||||
expect { migrate }.to change { index_exists?(table, column) }.to(true)
|
||||
end
|
||||
|
||||
it 'sets no default' do
|
||||
expect { migrate }
|
||||
.not_to change {
|
||||
KnowledgeBase::MenuItem.reset_column_information
|
||||
KnowledgeBase::MenuItem.column_defaults['location']
|
||||
}.from(nil)
|
||||
end
|
||||
|
||||
it 'sets location for existing items' do
|
||||
# create menu item without touching location column
|
||||
menu_item = KnowledgeBase::MenuItem.acts_as_list_no_update do
|
||||
attrs = attributes_for(:knowledge_base_menu_item)
|
||||
attrs.delete :location
|
||||
|
||||
item = KnowledgeBase::MenuItem.new(attrs)
|
||||
item.position = 0
|
||||
item.kb_locale = create(:knowledge_base).kb_locales.first
|
||||
item.save(validate: false)
|
||||
|
||||
item
|
||||
end
|
||||
|
||||
expect { migrate }.to change { menu_item.reload.attributes['location'] }.from(nil).to('header')
|
||||
end
|
||||
end
|
|
@ -1,8 +1,10 @@
|
|||
FactoryBot.define do
|
||||
factory 'knowledge_base/menu_item', aliases: %i[knowledge_base_menu_item] do
|
||||
kb_locale { nil }
|
||||
title { Faker::Kpop.iii_groups }
|
||||
url { Faker::Internet.url }
|
||||
kb_locale { nil }
|
||||
sequence(:title) { |n| "menu_#{n}" }
|
||||
url { Faker::Internet.url }
|
||||
|
||||
for_header
|
||||
|
||||
before :create do |menu_item|
|
||||
if menu_item.kb_locale.blank?
|
||||
|
@ -10,5 +12,13 @@ FactoryBot.define do
|
|||
menu_item.kb_locale = kb.kb_locales.first
|
||||
end
|
||||
end
|
||||
|
||||
trait :for_footer do
|
||||
location { 'footer' }
|
||||
end
|
||||
|
||||
trait :for_header do
|
||||
location { 'header' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,39 +6,64 @@ RSpec.describe KnowledgeBase::MenuItem, type: :model do
|
|||
|
||||
include_context 'factory'
|
||||
|
||||
context 'when url without prefix is added' do
|
||||
before { kb_menu_item.update(url: Faker::Internet.domain_name) }
|
||||
context 'item' do
|
||||
it { is_expected.to validate_presence_of :title }
|
||||
it { is_expected.to validate_presence_of :url }
|
||||
it { is_expected.to validate_presence_of :location }
|
||||
it { is_expected.to validate_inclusion_of(:location).in_array(%w[header footer]) }
|
||||
end
|
||||
|
||||
it 'is saved' do
|
||||
expect(kb_menu_item).not_to be_changed
|
||||
context 'has scopes for' do
|
||||
let(:kb_locale) { kb_menu_item.kb_locale }
|
||||
let(:scope) { described_class.where(kb_locale: kb_locale) }
|
||||
|
||||
let!(:header) { create(:knowledge_base_menu_item, :for_header, kb_locale: kb_locale) }
|
||||
let!(:footer) { create(:knowledge_base_menu_item, :for_footer, kb_locale: kb_locale) }
|
||||
|
||||
it 'header' do
|
||||
expect(scope.location_header).to match [kb_menu_item, header]
|
||||
end
|
||||
|
||||
it 'prefix is added to hostname' do
|
||||
expect(kb_menu_item.url).to start_with 'http://'
|
||||
it 'footer' do
|
||||
expect(scope.location_footer).to match [footer]
|
||||
end
|
||||
end
|
||||
|
||||
context 'when url with custom prefix is added' do
|
||||
before { kb_menu_item.update(url: "scheme://#{Faker::Internet.domain_name}") }
|
||||
context 'when url' do
|
||||
context 'without prefix is added' do
|
||||
before { kb_menu_item.update(url: Faker::Internet.domain_name) }
|
||||
|
||||
it 'is saved' do
|
||||
expect(kb_menu_item).not_to be_changed
|
||||
it 'is saved' do
|
||||
expect(kb_menu_item).not_to be_changed
|
||||
end
|
||||
|
||||
it 'prefix is added to hostname' do
|
||||
expect(kb_menu_item.url).to start_with 'http://'
|
||||
end
|
||||
end
|
||||
|
||||
it 'given scheme is not touched' do
|
||||
expect(kb_menu_item.url).to start_with 'scheme://'
|
||||
end
|
||||
end
|
||||
context 'with custom prefix is added' do
|
||||
before { kb_menu_item.update(url: "scheme://#{Faker::Internet.domain_name}") }
|
||||
|
||||
context 'protocol prefix is not added to relative url' do
|
||||
before { kb_menu_item.update(url: '/loremipsum') }
|
||||
it 'is saved' do
|
||||
expect(kb_menu_item).not_to be_changed
|
||||
end
|
||||
|
||||
it 'is saved' do
|
||||
expect(kb_menu_item).not_to be_changed
|
||||
it 'given scheme is not touched' do
|
||||
expect(kb_menu_item.url).to start_with 'scheme://'
|
||||
end
|
||||
end
|
||||
|
||||
it 'path is not modified' do
|
||||
expect(kb_menu_item.url).not_to start_with 'http://'
|
||||
context 'is relative and protocol prefix is not added' do
|
||||
before { kb_menu_item.update(url: '/loremipsum') }
|
||||
|
||||
it 'is saved' do
|
||||
expect(kb_menu_item).not_to be_changed
|
||||
end
|
||||
|
||||
it 'path is not modified' do
|
||||
expect(kb_menu_item.url).not_to start_with 'http://'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
67
spec/requests/admin/knowledge_base/public_menu_spec.rb
Normal file
67
spec/requests/admin/knowledge_base/public_menu_spec.rb
Normal file
|
@ -0,0 +1,67 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Admin Knowledge Base Public Menu', type: :request, authenticated_as: :admin_user do
|
||||
let(:url) { "/api/v1/knowledge_bases/manage/#{knowledge_base.id}/update_menu_items" }
|
||||
let(:params) do
|
||||
{
|
||||
menu_items_sets: [{
|
||||
"kb_locale_id": kb_locale.id,
|
||||
"location": location,
|
||||
"menu_items": menu_items
|
||||
}]
|
||||
}
|
||||
end
|
||||
|
||||
let(:menu_item) { create(:knowledge_base_menu_item) }
|
||||
let(:kb_locale) { menu_item.kb_locale }
|
||||
let(:knowledge_base) { kb_locale.knowledge_base }
|
||||
let(:location) { 'header' }
|
||||
|
||||
it 'edit title' do
|
||||
attrs = to_params(menu_item)
|
||||
attrs[:title] = 'new title'
|
||||
|
||||
params = build_params([attrs])
|
||||
|
||||
expect { make_request(params) }.to change { menu_item.reload.title }.to 'new title'
|
||||
end
|
||||
|
||||
it 'delete item' do
|
||||
attrs = to_params(menu_item)
|
||||
attrs[:_destroy] = true
|
||||
|
||||
params = build_params([attrs])
|
||||
|
||||
expect { make_request(params) }.to change { KnowledgeBase::MenuItem.count }.by(-1)
|
||||
end
|
||||
|
||||
it 'add item' do
|
||||
new_item = {
|
||||
title: 'new item',
|
||||
new_tab: false,
|
||||
url: '/new_url'
|
||||
}
|
||||
|
||||
params = build_params([to_params(menu_item), new_item])
|
||||
|
||||
expect { make_request(params) }.to change { KnowledgeBase::MenuItem.count }.by(1)
|
||||
end
|
||||
|
||||
def to_params(item)
|
||||
item.slice :id, :title, :url, :new_tab
|
||||
end
|
||||
|
||||
def make_request(params)
|
||||
patch url, params: params, as: :json
|
||||
end
|
||||
|
||||
def build_params(menu_items)
|
||||
{
|
||||
menu_items_sets: [{
|
||||
"kb_locale_id": kb_locale.id,
|
||||
"location": location,
|
||||
"menu_items": menu_items
|
||||
}]
|
||||
}
|
||||
end
|
||||
end
|
|
@ -53,6 +53,27 @@ module DbMigrationHelper
|
|||
end
|
||||
end
|
||||
|
||||
# Helper method for setting up specs on DB migrations that add columns.
|
||||
# Make sure to define type: :db_migration in your RSpec.describe call
|
||||
# and add `self.use_transactional_tests = false` to your context.
|
||||
#
|
||||
# @param [Symbol] from_table the name of the table with the indexed column
|
||||
# @param [Symbol] name(s) of indexed column(s)
|
||||
#
|
||||
# @example
|
||||
# without_column(:online_notifications, column: :user_id)
|
||||
#
|
||||
# @return [nil]
|
||||
def without_column(from_table, column:)
|
||||
suppress_messages do
|
||||
Array(column).each do |elem|
|
||||
next unless column_exists?(from_table, elem)
|
||||
|
||||
remove_column(from_table, elem)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Helper method for setting up specs on DB migrations that add indices.
|
||||
# Make sure to define type: :db_migration in your RSpec.describe call
|
||||
# and add `self.use_transactional_tests = false` to your context.
|
||||
|
|
|
@ -35,3 +35,11 @@ RSpec.shared_context 'basic Knowledge Base', current_user_id: 1 do
|
|||
create(:knowledge_base_answer, category: category, archived_at: 1.week.ago)
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_context 'Knowledge Base menu items', current_user_id: 1 do
|
||||
let!(:menu_item_1) { create(:knowledge_base_menu_item, :for_header, kb_locale: primary_locale) }
|
||||
let!(:menu_item_2) { create(:knowledge_base_menu_item, :for_header, kb_locale: primary_locale) }
|
||||
let!(:menu_item_3) { create(:knowledge_base_menu_item, :for_footer, kb_locale: primary_locale) }
|
||||
let!(:menu_item_4) { create(:knowledge_base_menu_item, :for_footer, kb_locale: alternative_locale) }
|
||||
let!(:menu_item_5) { create(:knowledge_base_menu_item, :for_footer, kb_locale: alternative_locale) }
|
||||
end
|
||||
|
|
74
spec/system/admin/knowledge_base/public_menu_spec.rb
Normal file
74
spec/system/admin/knowledge_base/public_menu_spec.rb
Normal file
|
@ -0,0 +1,74 @@
|
|||
require 'rails_helper'
|
||||
|
||||
# https://github.com/zammad/zammad/issues/266
|
||||
RSpec.describe 'Admin Panel > Knowledge Base > Public Menu', type: :system, authenticated: true do
|
||||
include_context 'basic Knowledge Base'
|
||||
include_context 'Knowledge Base menu items'
|
||||
|
||||
before do
|
||||
visit '/#manage/knowledge_base'
|
||||
find('a', text: 'Public Menu').click
|
||||
end
|
||||
|
||||
context 'lists menu items' do
|
||||
it { expect(find_locale('Footer menu', alternative_locale).text).to include menu_item_4.title }
|
||||
it { expect(find_locale('Header menu', primary_locale).text).to include menu_item_1.title }
|
||||
it { expect(find_locale('Header menu', alternative_locale).text).not_to include menu_item_2.title }
|
||||
it { expect(find_locale('Header menu', primary_locale).text).to include menu_item_2.title }
|
||||
end
|
||||
|
||||
context 'edit menu items' do
|
||||
before do
|
||||
find_location('Header menu').find('a', text: 'Edit').click
|
||||
|
||||
modal_ready
|
||||
end
|
||||
|
||||
it 'edit menu item' do
|
||||
find('input') { |elem| elem.value == menu_item_1.title }.fill_in with: 'test menu'
|
||||
find('button', text: 'Submit').click
|
||||
|
||||
modal_disappear
|
||||
|
||||
expect(find_locale('Header menu', primary_locale).text).to include 'test menu'
|
||||
end
|
||||
|
||||
it 'adds menu item' do
|
||||
container = find(:css, '.modal-body h2', text: alternative_locale.system_locale.name).find(:xpath, '..')
|
||||
container.find('a', text: 'Add').click
|
||||
|
||||
container.find('input') { |elem| elem['data-name'] == 'title' }.fill_in with: 'new item'
|
||||
container.find('input') { |elem| elem['data-name'] == 'url' }.fill_in with: '/new_item'
|
||||
|
||||
find('button', text: 'Submit').click
|
||||
|
||||
modal_disappear
|
||||
|
||||
expect(find_locale('Header menu', alternative_locale).text).to include 'new item'
|
||||
end
|
||||
|
||||
it 'deletes menu item' do
|
||||
find(:css, '.modal-body')
|
||||
.find('input') { |elem| elem.value == menu_item_1.title }
|
||||
.ancestor('tr')
|
||||
.find('.js-remove')
|
||||
.click
|
||||
|
||||
find('button', text: 'Submit').click
|
||||
|
||||
modal_disappear
|
||||
|
||||
expect(find_locale('Header menu', alternative_locale).text).not_to include menu_item_1.title
|
||||
end
|
||||
end
|
||||
|
||||
def find_locale(location, locale)
|
||||
find_location(location)
|
||||
.find('.label', text: /#{Regexp.escape locale.system_locale.name}/i)
|
||||
.ancestor('.kb-menu-preview')
|
||||
end
|
||||
|
||||
def find_location(location)
|
||||
find('h3', text: location).ancestor('.settings-entry')
|
||||
end
|
||||
end
|
38
spec/system/knowledge_base_public/menu_items_spec.rb
Normal file
38
spec/system/knowledge_base_public/menu_items_spec.rb
Normal file
|
@ -0,0 +1,38 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Public Knowledge Base menu items', type: :system, authenticated: false do
|
||||
include_context 'basic Knowledge Base'
|
||||
include_context 'Knowledge Base menu items'
|
||||
|
||||
before do
|
||||
published_answer
|
||||
|
||||
visit help_no_locale_path
|
||||
end
|
||||
|
||||
it 'shows header public link' do
|
||||
expect(page).to have_css('header .menu-item', text: menu_item_1.title)
|
||||
end
|
||||
|
||||
it 'shows another header public link' do
|
||||
expect(page).to have_css('header .menu-item', text: menu_item_2.title)
|
||||
end
|
||||
|
||||
it "doesn't show footer link in header" do
|
||||
expect(page).not_to have_css('header .menu-item', text: menu_item_3.title)
|
||||
end
|
||||
|
||||
it 'shows footer public link' do
|
||||
expect(page).to have_css('footer .menu-item', text: menu_item_3.title)
|
||||
end
|
||||
|
||||
it "doesn't show footer link of another locale" do
|
||||
expect(page).not_to have_css('footer .menu-item', text: menu_item_4.title)
|
||||
end
|
||||
|
||||
it 'shows public links in given order' do
|
||||
index_1 = page.body.index menu_item_1.title
|
||||
index_2 = page.body.index menu_item_2.title
|
||||
expect(index_1).to be < index_2
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue