Merge branch 'develop' of github.com:martini/zammad into develop
This commit is contained in:
commit
f57e8a5ca7
9 changed files with 188 additions and 115 deletions
|
@ -1435,7 +1435,18 @@ class InputsRef extends App.ControllerContent
|
||||||
id: 'project-name-123'
|
id: 'project-name-123'
|
||||||
placeholder: 'Enter Project Name'
|
placeholder: 'Enter Project Name'
|
||||||
options: [{"value":0,"name":"Apple"},{"value":1,"name":"Microsoft","selected":true},{"value":2,"name":"Google"},{"value":3,"name":"Deutsche Bahn"},{"value":4,"name":"Sparkasse"},{"value":5,"name":"Deutsche Post"},{"value":6,"name":"Mitfahrzentrale"},{"value":7,"name":"Starbucks"},{"value":8,"name":"Mac Donalds"},{"value":9,"name":"Flixbus"},{"value":10,"name":"Betahaus"},{"value":11,"name":"Bruno Banani"},{"value":12,"name":"Alpina"},{"value":13,"name":"Samsung"},{"value":14,"name":"ChariTea"},{"value":15,"name":"fritz-kola"},{"value":16,"name":"Vitamin Water"},{"value":17,"name":"Znuny"},{"value":18,"name":"Max & Moritz"}]
|
options: [{"value":0,"name":"Apple"},{"value":1,"name":"Microsoft","selected":true},{"value":2,"name":"Google"},{"value":3,"name":"Deutsche Bahn"},{"value":4,"name":"Sparkasse"},{"value":5,"name":"Deutsche Post"},{"value":6,"name":"Mitfahrzentrale"},{"value":7,"name":"Starbucks"},{"value":8,"name":"Mac Donalds"},{"value":9,"name":"Flixbus"},{"value":10,"name":"Betahaus"},{"value":11,"name":"Bruno Banani"},{"value":12,"name":"Alpina"},{"value":13,"name":"Samsung"},{"value":14,"name":"ChariTea"},{"value":15,"name":"fritz-kola"},{"value":16,"name":"Vitamin Water"},{"value":17,"name":"Znuny"},{"value":18,"name":"Max & Moritz"}]
|
||||||
@$('.searchableSelectPlaceholder').replaceWith( searchableSelectObject.el )
|
@$('.searchableSelectPlaceholder').replaceWith( searchableSelectObject.element() )
|
||||||
|
|
||||||
|
# selectable search
|
||||||
|
searchableAjaxSelectObject = new App.SearchableAjaxSelect
|
||||||
|
attribute:
|
||||||
|
name: 'user'
|
||||||
|
id: 'user-123'
|
||||||
|
placeholder: 'Enter User'
|
||||||
|
limt: 10
|
||||||
|
object: 'User'
|
||||||
|
|
||||||
|
@$('.searchableAjaxSelectPlaceholder').replaceWith( searchableAjaxSelectObject.element() )
|
||||||
|
|
||||||
# time and timeframe
|
# time and timeframe
|
||||||
@$('.time').timepicker()
|
@$('.time').timepicker()
|
||||||
|
|
|
@ -26,7 +26,6 @@ class App.SearchableSelect extends Spine.Controller
|
||||||
@render()
|
@render()
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
console.log "options", @options
|
|
||||||
firstSelected = _.find @options.attribute.options, (option) -> option.selected
|
firstSelected = _.find @options.attribute.options, (option) -> option.selected
|
||||||
|
|
||||||
if firstSelected
|
if firstSelected
|
||||||
|
|
|
@ -14,7 +14,7 @@ class App.UserOrganizationAutocompletion extends App.Controller
|
||||||
@key = Math.floor( Math.random() * 999999 ).toString()
|
@key = Math.floor( Math.random() * 999999 ).toString()
|
||||||
|
|
||||||
if !@attribute.source
|
if !@attribute.source
|
||||||
@attribute.source = @apiPath + '/search_user_org'
|
@attribute.source = @apiPath + '/search/user-organization'
|
||||||
@build()
|
@build()
|
||||||
|
|
||||||
element: =>
|
element: =>
|
||||||
|
|
|
@ -3,12 +3,81 @@ class App.SearchableAjaxSelect extends App.SearchableSelect
|
||||||
onInput: (event) =>
|
onInput: (event) =>
|
||||||
super
|
super
|
||||||
|
|
||||||
# send ajax request @query
|
# convert requested object
|
||||||
|
# e.g. Ticket to ticket or AnotherObject to another_object
|
||||||
|
objectString = underscored( @options.attribute.object )
|
||||||
|
|
||||||
onAjaxResponse: (data) =>
|
# create common accessors
|
||||||
|
@apiPath = App.Config.get('api_path')
|
||||||
|
|
||||||
|
# create cache and cache key
|
||||||
|
@searchResultCache = @searchResultCache || {}
|
||||||
|
|
||||||
|
@cacheKey = "#{objectString}+#{@query}"
|
||||||
|
|
||||||
|
# use cache for search result
|
||||||
|
if @searchResultCache[@cacheKey]
|
||||||
|
return @onAjaxResponse( @searchResultCache[@cacheKey] )
|
||||||
|
|
||||||
|
# add timout for loader icon
|
||||||
|
clearTimeout @loaderTimeoutId
|
||||||
|
@loaderTimeoutId = setTimeout @showLoader, 1000
|
||||||
|
|
||||||
|
# start search request and update options
|
||||||
|
App.Ajax.request(
|
||||||
|
id: @options.attribute.id
|
||||||
|
type: 'GET'
|
||||||
|
url: "#{@apiPath}/search/#{objectString}"
|
||||||
|
data:
|
||||||
|
query: @query
|
||||||
|
limit: @options.attribute.limit
|
||||||
|
processData: true
|
||||||
|
success: @onAjaxResponse
|
||||||
|
)
|
||||||
|
|
||||||
|
onAjaxResponse: (data, status, xhr) =>
|
||||||
|
|
||||||
|
# clear timout and remove loader icon
|
||||||
|
clearTimeout @loaderTimeoutId
|
||||||
|
@el.removeClass('is-loading')
|
||||||
|
|
||||||
|
# cache search result
|
||||||
|
@searchResultCache[@cacheKey] = data
|
||||||
|
|
||||||
|
# load assets
|
||||||
|
App.Collection.loadAssets( data.assets )
|
||||||
|
|
||||||
|
# get options from search result
|
||||||
|
options = []
|
||||||
|
for object in data.result
|
||||||
|
if object.type is 'Ticket'
|
||||||
|
ticket = App.Ticket.find( object.id )
|
||||||
|
data =
|
||||||
|
name: "##{ticket.number} - #{ticket.title}"
|
||||||
|
value: ticket.id
|
||||||
|
options.push data
|
||||||
|
else if object.type is 'User'
|
||||||
|
user = App.User.find( object.id )
|
||||||
|
data =
|
||||||
|
name: "#{user.displayName()}"
|
||||||
|
value: user.id
|
||||||
|
options.push data
|
||||||
|
else if object.type is 'Organization'
|
||||||
|
organization = App.Organization.find( object.id )
|
||||||
|
data =
|
||||||
|
name: "#{organization.displayName()}"
|
||||||
|
value: organization.id
|
||||||
|
options.push data
|
||||||
|
|
||||||
|
# fill template with gathered options
|
||||||
@optionsList.html App.view('generic/searchable_select_options')
|
@optionsList.html App.view('generic/searchable_select_options')
|
||||||
options: data
|
options: options
|
||||||
|
|
||||||
|
# refresh elements
|
||||||
@refreshElements()
|
@refreshElements()
|
||||||
|
|
||||||
|
# execute filter
|
||||||
@filterByQuery @query
|
@filterByQuery @query
|
||||||
|
|
||||||
|
showLoader: =>
|
||||||
|
@el.addClass('is-loading')
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
<% for option in @options: %>
|
<% if @options: %>
|
||||||
|
<% for option in @options: %>
|
||||||
<li role="presentation" class="js-option" data-value="<%= option.value %>"><%= option.name %>
|
<li role="presentation" class="js-option" data-value="<%= option.value %>"><%= option.name %>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
|
@ -66,6 +66,10 @@
|
||||||
<div class="searchableSelectPlaceholder"></div>
|
<div class="searchableSelectPlaceholder"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="select form-group">
|
||||||
|
<label for="b">Users (searchable ajax)</label>
|
||||||
|
<div class="searchableAjaxSelectPlaceholder"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h2>Checkbox</h2>
|
<h2>Checkbox</h2>
|
||||||
<div class="checkbox form-group">
|
<div class="checkbox form-group">
|
||||||
|
|
|
@ -203,6 +203,11 @@ function clone2(item) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// taken from https://github.com/epeli/underscore.string/blob/master/underscored.js
|
||||||
|
function underscored (str) {
|
||||||
|
return str.trim().replace(/([a-z\d])([A-Z]+)/g, '$1_$2').replace(/[-\s]+/g, '_').toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
jQuery.event.special.remove = {
|
jQuery.event.special.remove = {
|
||||||
remove: function(e) {
|
remove: function(e) {
|
||||||
if (e.handler) e.handler();
|
if (e.handler) e.handler();
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
class SearchController < ApplicationController
|
class SearchController < ApplicationController
|
||||||
before_action :authentication_check
|
before_action :authentication_check
|
||||||
|
|
||||||
# GET /api/v1/search_user_org
|
# GET|POST /api/v1/search/:objects
|
||||||
def search_user_org
|
|
||||||
|
def search_generic
|
||||||
|
|
||||||
# enable search only for agents and admins
|
# enable search only for agents and admins
|
||||||
if !current_user.role?(Z_ROLENAME_AGENT) && !current_user.role?(Z_ROLENAME_ADMIN)
|
if !current_user.role?(Z_ROLENAME_AGENT) && !current_user.role?(Z_ROLENAME_ADMIN)
|
||||||
|
@ -16,11 +17,15 @@ class SearchController < ApplicationController
|
||||||
query = params[:query]
|
query = params[:query]
|
||||||
limit = params[:limit] || 10
|
limit = params[:limit] || 10
|
||||||
|
|
||||||
|
# convert objects string into array of class names
|
||||||
|
# e.g. user-ticket-another_object = %w( User Ticket AnotherObject )
|
||||||
|
objects = params[:objects].split('-').map(&:camelize)
|
||||||
|
|
||||||
# try search index backend
|
# try search index backend
|
||||||
assets = {}
|
assets = {}
|
||||||
result = []
|
result = []
|
||||||
if SearchIndexBackend.enabled?
|
if SearchIndexBackend.enabled?
|
||||||
items = SearchIndexBackend.search( query, limit, %w(User Organization) )
|
items = SearchIndexBackend.search( query, limit, objects )
|
||||||
items.each { |item|
|
items.each { |item|
|
||||||
require item[:type].to_filename
|
require item[:type].to_filename
|
||||||
record = Kernel.const_get( item[:type] ).find( item[:id] )
|
record = Kernel.const_get( item[:type] ).find( item[:id] )
|
||||||
|
@ -29,34 +34,23 @@ class SearchController < ApplicationController
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
# do query
|
# do query
|
||||||
users = User.search(
|
objects.each { |object|
|
||||||
query: query,
|
|
||||||
limit: limit,
|
|
||||||
current_user: current_user,
|
|
||||||
)
|
|
||||||
users.each do |user|
|
|
||||||
item = {
|
|
||||||
id: user.id,
|
|
||||||
type: user.class.to_s
|
|
||||||
}
|
|
||||||
result.push item
|
|
||||||
assets = user.assets(assets)
|
|
||||||
end
|
|
||||||
|
|
||||||
organizations = Organization.search(
|
found_objects = object.constantize.search(
|
||||||
query: query,
|
query: query,
|
||||||
limit: limit,
|
limit: limit,
|
||||||
current_user: current_user,
|
current_user: current_user,
|
||||||
)
|
)
|
||||||
|
|
||||||
organizations.each do |organization|
|
found_objects.each do |found_object|
|
||||||
item = {
|
item = {
|
||||||
id: organization.id,
|
id: found_object.id,
|
||||||
type: organization.class.to_s
|
type: found_object.class.to_s
|
||||||
}
|
}
|
||||||
result.push item
|
result.push item
|
||||||
assets = organization.assets(assets)
|
assets = found_object.assets(assets)
|
||||||
end
|
end
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: {
|
render json: {
|
||||||
|
@ -68,64 +62,54 @@ class SearchController < ApplicationController
|
||||||
# GET /api/v1/search
|
# GET /api/v1/search
|
||||||
def search
|
def search
|
||||||
|
|
||||||
# build result list
|
|
||||||
tickets = Ticket.search(
|
|
||||||
limit: params[:limit],
|
|
||||||
query: params[:term],
|
|
||||||
current_user: current_user,
|
|
||||||
)
|
|
||||||
assets = {}
|
assets = {}
|
||||||
ticket_result = []
|
|
||||||
tickets.each do |ticket|
|
|
||||||
assets = ticket.assets(assets)
|
|
||||||
ticket_result.push ticket.id
|
|
||||||
end
|
|
||||||
|
|
||||||
# do query
|
|
||||||
users = User.search(
|
|
||||||
query: params[:term],
|
|
||||||
limit: params[:limit],
|
|
||||||
current_user: current_user,
|
|
||||||
)
|
|
||||||
user_result = []
|
|
||||||
users.each do |user|
|
|
||||||
user_result.push user.id
|
|
||||||
assets = user.assets(assets)
|
|
||||||
end
|
|
||||||
|
|
||||||
organizations = Organization.search(
|
|
||||||
query: params[:term],
|
|
||||||
limit: params[:limit],
|
|
||||||
current_user: current_user,
|
|
||||||
)
|
|
||||||
|
|
||||||
organization_result = []
|
|
||||||
organizations.each do |organization|
|
|
||||||
organization_result.push organization.id
|
|
||||||
assets = organization.assets(assets)
|
|
||||||
end
|
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
if ticket_result[0]
|
objects = %w( Ticket User Organization )
|
||||||
|
if SearchIndexBackend.enabled?
|
||||||
|
|
||||||
|
found_objects = {}
|
||||||
|
items = SearchIndexBackend.search( params[:term], params[:limit], objects )
|
||||||
|
items.each { |item|
|
||||||
|
require item[:type].to_filename
|
||||||
|
record = Kernel.const_get( item[:type] ).find( item[:id] )
|
||||||
|
assets = record.assets(assets)
|
||||||
|
|
||||||
|
found_objects[ item[:type] ] ||= []
|
||||||
|
found_objects[ item[:type] ].push item[:id]
|
||||||
|
}
|
||||||
|
|
||||||
|
found_objects.each { |object, object_ids|
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
name: 'Ticket',
|
name: object,
|
||||||
ids: ticket_result,
|
ids: object_ids,
|
||||||
}
|
}
|
||||||
result.push data
|
result.push data
|
||||||
|
}
|
||||||
|
else
|
||||||
|
|
||||||
|
objects.each { |object|
|
||||||
|
|
||||||
|
found_objects = object.constantize.search(
|
||||||
|
query: params[:term],
|
||||||
|
limit: params[:limit],
|
||||||
|
current_user: current_user,
|
||||||
|
)
|
||||||
|
|
||||||
|
object_ids = []
|
||||||
|
found_objects.each do |found_object|
|
||||||
|
object_ids.push found_object.id
|
||||||
|
assets = found_object.assets(assets)
|
||||||
end
|
end
|
||||||
if user_result[0]
|
|
||||||
|
next if object_ids.empty?
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
name: 'User',
|
name: object,
|
||||||
ids: user_result,
|
ids: object_ids,
|
||||||
}
|
}
|
||||||
result.push data
|
result.push data
|
||||||
end
|
|
||||||
if organization_result[0]
|
|
||||||
data = {
|
|
||||||
name: 'Organization',
|
|
||||||
ids: organization_result,
|
|
||||||
}
|
}
|
||||||
result.push data
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# return result
|
# return result
|
||||||
|
|
|
@ -4,7 +4,6 @@ Zammad::Application.routes.draw do
|
||||||
# search
|
# search
|
||||||
match api_path + '/search', to: 'search#search', via: [:get, :post]
|
match api_path + '/search', to: 'search#search', via: [:get, :post]
|
||||||
|
|
||||||
# search_user_org
|
# search_generic
|
||||||
match api_path + '/search_user_org', to: 'search#search_user_org', via: [:get, :post]
|
match api_path + '/search/:objects', to: 'search#search_generic', via: [:get, :post]
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue