Implemented issue #2073 - Allow sort_by and order_by for tickets, users and organisations search REST API.
This commit is contained in:
parent
e64c2f3c9d
commit
597cd01b5a
12 changed files with 622 additions and 66 deletions
|
@ -246,6 +246,8 @@ curl http://localhost/api/v1/organization/{id} -v -u #{login}:#{password} -H "Co
|
||||||
query: query,
|
query: query,
|
||||||
limit: per_page,
|
limit: per_page,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
|
sort_by: params[:sort_by],
|
||||||
|
order_by: params[:order_by],
|
||||||
current_user: current_user,
|
current_user: current_user,
|
||||||
}
|
}
|
||||||
if params[:role_ids].present?
|
if params[:role_ids].present?
|
||||||
|
|
|
@ -445,6 +445,8 @@ class TicketsController < ApplicationController
|
||||||
condition: params[:condition].to_h,
|
condition: params[:condition].to_h,
|
||||||
limit: per_page,
|
limit: per_page,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
|
order_by: params[:order_by],
|
||||||
|
sort_by: params[:sort_by],
|
||||||
current_user: current_user,
|
current_user: current_user,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -405,6 +405,8 @@ class UsersController < ApplicationController
|
||||||
query: query,
|
query: query,
|
||||||
limit: per_page,
|
limit: per_page,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
|
sort_by: params[:sort_by],
|
||||||
|
order_by: params[:order_by],
|
||||||
current_user: current_user,
|
current_user: current_user,
|
||||||
}
|
}
|
||||||
%i[role_ids permissions].each do |key|
|
%i[role_ids permissions].each do |key|
|
||||||
|
|
157
app/models/concerns/has_search_sortable.rb
Normal file
157
app/models/concerns/has_search_sortable.rb
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
|
||||||
|
module HasSearchSortable
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
# methods defined here are going to extend the class, not the instance of it
|
||||||
|
class_methods do
|
||||||
|
|
||||||
|
=begin
|
||||||
|
|
||||||
|
This function will check the params for the "sort_by" attribute
|
||||||
|
and validate its values.
|
||||||
|
|
||||||
|
sort_by = search_get_sort_by(params, default)
|
||||||
|
|
||||||
|
returns
|
||||||
|
|
||||||
|
sort_by = [
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
]
|
||||||
|
|
||||||
|
=end
|
||||||
|
|
||||||
|
def search_get_sort_by(params, default)
|
||||||
|
sort_by = []
|
||||||
|
if params[:sort_by].present? && params[:sort_by].is_a?(String)
|
||||||
|
params[:sort_by] = [params[:sort_by]]
|
||||||
|
elsif params[:sort_by].blank?
|
||||||
|
params[:sort_by] = []
|
||||||
|
end
|
||||||
|
|
||||||
|
# check order
|
||||||
|
params[:sort_by].each do |value|
|
||||||
|
|
||||||
|
# only accept values which are set for the db schema
|
||||||
|
raise "Found invalid column '#{value}' for sorting." if columns_hash[value].blank?
|
||||||
|
|
||||||
|
sort_by.push(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
if sort_by.blank?
|
||||||
|
sort_by.push(default)
|
||||||
|
end
|
||||||
|
|
||||||
|
sort_by
|
||||||
|
end
|
||||||
|
|
||||||
|
=begin
|
||||||
|
|
||||||
|
This function will check the params for the "order_by" attribute
|
||||||
|
and validate its values.
|
||||||
|
|
||||||
|
order_by = search_get_order_by(params, default)
|
||||||
|
|
||||||
|
returns
|
||||||
|
|
||||||
|
order_by = [
|
||||||
|
'asc',
|
||||||
|
'desc',
|
||||||
|
]
|
||||||
|
|
||||||
|
=end
|
||||||
|
|
||||||
|
def search_get_order_by(params, default)
|
||||||
|
order_by = []
|
||||||
|
if params[:order_by].present? && params[:order_by].is_a?(String)
|
||||||
|
params[:order_by] = [ params[:order_by] ]
|
||||||
|
elsif params[:order_by].blank?
|
||||||
|
params[:order_by] = []
|
||||||
|
end
|
||||||
|
|
||||||
|
# check order
|
||||||
|
params[:order_by].each do |value|
|
||||||
|
raise "Found invalid order by value #{value}. Please use 'asc' or 'desc'." if value !~ /\A(asc|desc)\z/i
|
||||||
|
order_by.push(value.downcase)
|
||||||
|
end
|
||||||
|
|
||||||
|
if order_by.blank?
|
||||||
|
order_by.push(default)
|
||||||
|
end
|
||||||
|
|
||||||
|
order_by
|
||||||
|
end
|
||||||
|
|
||||||
|
=begin
|
||||||
|
|
||||||
|
This function will use the evaluated values for sort_by and
|
||||||
|
order_by to generate the ORDER-SELECT sql statement for the sorting
|
||||||
|
of the result.
|
||||||
|
|
||||||
|
sort_by = [ 'created_at', 'updated_at' ]
|
||||||
|
order_by = [ 'asc', 'desc' ]
|
||||||
|
default = 'tickets.created_at'
|
||||||
|
|
||||||
|
sql = search_get_order_select_sql(sort_by, order_by, default)
|
||||||
|
|
||||||
|
returns
|
||||||
|
|
||||||
|
sql = 'tickets.created_at, tickets.updated_at'
|
||||||
|
|
||||||
|
=end
|
||||||
|
|
||||||
|
def search_get_order_select_sql(sort_by, order_by, default)
|
||||||
|
sql = []
|
||||||
|
|
||||||
|
sort_by.each_with_index do |value, index|
|
||||||
|
next if value.blank?
|
||||||
|
next if order_by[index].blank?
|
||||||
|
|
||||||
|
sql.push( "#{ActiveRecord::Base.connection.quote_table_name(table_name)}.#{ActiveRecord::Base.connection.quote_column_name(value)}" )
|
||||||
|
end
|
||||||
|
|
||||||
|
if sql.blank?
|
||||||
|
sql.push("#{ActiveRecord::Base.connection.quote_table_name(table_name)}.#{ActiveRecord::Base.connection.quote_column_name(default)}")
|
||||||
|
end
|
||||||
|
|
||||||
|
sql.join(', ')
|
||||||
|
end
|
||||||
|
|
||||||
|
=begin
|
||||||
|
|
||||||
|
This function will use the evaluated values for sort_by and
|
||||||
|
order_by to generate the ORDER- sql statement for the sorting
|
||||||
|
of the result.
|
||||||
|
|
||||||
|
sort_by = [ 'created_at', 'updated_at' ]
|
||||||
|
order_by = [ 'asc', 'desc' ]
|
||||||
|
default = 'tickets.created_at DESC'
|
||||||
|
|
||||||
|
sql = search_get_order_sql(sort_by, order_by, default)
|
||||||
|
|
||||||
|
returns
|
||||||
|
|
||||||
|
sql = 'tickets.created_at ASC, tickets.updated_at DESC'
|
||||||
|
|
||||||
|
=end
|
||||||
|
|
||||||
|
def search_get_order_sql(sort_by, order_by, default)
|
||||||
|
sql = []
|
||||||
|
|
||||||
|
sort_by.each_with_index do |value, index|
|
||||||
|
next if value.blank?
|
||||||
|
next if order_by[index].blank?
|
||||||
|
|
||||||
|
sql.push( "#{ActiveRecord::Base.connection.quote_table_name(table_name)}.#{ActiveRecord::Base.connection.quote_column_name(value)} #{order_by[index]}" )
|
||||||
|
end
|
||||||
|
|
||||||
|
if sql.blank?
|
||||||
|
sql.push("#{ActiveRecord::Base.connection.quote_table_name(table_name)}.#{ActiveRecord::Base.connection.quote_column_name(default)}")
|
||||||
|
end
|
||||||
|
|
||||||
|
sql.join(', ')
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -4,6 +4,10 @@ class Organization
|
||||||
module Search
|
module Search
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
include HasSearchSortable
|
||||||
|
end
|
||||||
|
|
||||||
# methods defined here are going to extend the class, not the instance of it
|
# methods defined here are going to extend the class, not the instance of it
|
||||||
class_methods do
|
class_methods do
|
||||||
|
|
||||||
|
@ -43,6 +47,14 @@ search organizations
|
||||||
query: 'search something',
|
query: 'search something',
|
||||||
limit: 15,
|
limit: 15,
|
||||||
offset: 100,
|
offset: 100,
|
||||||
|
|
||||||
|
# sort single column
|
||||||
|
sort_by: 'created_at',
|
||||||
|
order_by: 'asc',
|
||||||
|
|
||||||
|
# sort multiple columns
|
||||||
|
sort_by: [ 'created_at', 'updated_at' ],
|
||||||
|
order_by: [ 'asc', 'desc' ],
|
||||||
)
|
)
|
||||||
|
|
||||||
returns
|
returns
|
||||||
|
@ -59,12 +71,18 @@ returns
|
||||||
offset = params[:offset] || 0
|
offset = params[:offset] || 0
|
||||||
current_user = params[:current_user]
|
current_user = params[:current_user]
|
||||||
|
|
||||||
|
# check sort
|
||||||
|
sort_by = search_get_sort_by(params, 'name')
|
||||||
|
|
||||||
|
# check order
|
||||||
|
order_by = search_get_order_by(params, 'asc')
|
||||||
|
|
||||||
# enable search only for agents and admins
|
# enable search only for agents and admins
|
||||||
return [] if !search_preferences(current_user)
|
return [] if !search_preferences(current_user)
|
||||||
|
|
||||||
# try search index backend
|
# try search index backend
|
||||||
if SearchIndexBackend.enabled?
|
if SearchIndexBackend.enabled?
|
||||||
items = SearchIndexBackend.search(query, limit, 'Organization', {}, offset)
|
items = SearchIndexBackend.search(query, limit, 'Organization', {}, offset, sort_by, order_by)
|
||||||
organizations = []
|
organizations = []
|
||||||
items.each do |item|
|
items.each do |item|
|
||||||
organization = Organization.lookup(id: item[:id])
|
organization = Organization.lookup(id: item[:id])
|
||||||
|
@ -74,11 +92,14 @@ returns
|
||||||
return organizations
|
return organizations
|
||||||
end
|
end
|
||||||
|
|
||||||
|
order_select_sql = search_get_order_select_sql(sort_by, order_by, 'organizations.name')
|
||||||
|
order_sql = search_get_order_sql(sort_by, order_by, 'organizations.name ASC')
|
||||||
|
|
||||||
# fallback do sql query
|
# fallback do sql query
|
||||||
# - stip out * we already search for *query* -
|
# - stip out * we already search for *query* -
|
||||||
query.delete! '*'
|
query.delete! '*'
|
||||||
organizations = Organization.where_or_cis(%i[name note], "%#{query}%")
|
organizations = Organization.where_or_cis(%i[name note], "%#{query}%")
|
||||||
.order('name')
|
.order(order_sql)
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.to_a
|
.to_a
|
||||||
|
@ -89,10 +110,10 @@ returns
|
||||||
return organizations if organizations.length > 3
|
return organizations if organizations.length > 3
|
||||||
|
|
||||||
# if only a few organizations are found, search for names of users
|
# if only a few organizations are found, search for names of users
|
||||||
organizations_by_user = Organization.select('DISTINCT(organizations.id), organizations.name')
|
organizations_by_user = Organization.select('DISTINCT(organizations.id), ' + order_select_sql)
|
||||||
.joins('LEFT OUTER JOIN users ON users.organization_id = organizations.id')
|
.joins('LEFT OUTER JOIN users ON users.organization_id = organizations.id')
|
||||||
.where(User.or_cis(%i[firstname lastname email], "%#{query}%"))
|
.where(User.or_cis(%i[firstname lastname email], "%#{query}%"))
|
||||||
.order('organizations.name')
|
.order(order_sql)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
|
|
||||||
organizations_by_user.each do |organization_by_user|
|
organizations_by_user.each do |organization_by_user|
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
module Ticket::Search
|
module Ticket::Search
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
include HasSearchSortable
|
||||||
|
end
|
||||||
|
|
||||||
# methods defined here are going to extend the class, not the instance of it
|
# methods defined here are going to extend the class, not the instance of it
|
||||||
class_methods do
|
class_methods do
|
||||||
|
|
||||||
|
@ -84,6 +88,15 @@ search tickets via database
|
||||||
},
|
},
|
||||||
limit: 15,
|
limit: 15,
|
||||||
offset: 100,
|
offset: 100,
|
||||||
|
|
||||||
|
# sort single column
|
||||||
|
sort_by: 'created_at',
|
||||||
|
order_by: 'asc',
|
||||||
|
|
||||||
|
# sort multiple columns
|
||||||
|
sort_by: [ 'created_at', 'updated_at' ],
|
||||||
|
order_by: [ 'asc', 'desc' ],
|
||||||
|
|
||||||
full: false,
|
full: false,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -106,6 +119,12 @@ returns
|
||||||
full = true
|
full = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# check sort
|
||||||
|
sort_by = search_get_sort_by(params, 'created_at')
|
||||||
|
|
||||||
|
# check order
|
||||||
|
order_by = search_get_order_by(params, 'desc')
|
||||||
|
|
||||||
# try search index backend
|
# try search index backend
|
||||||
if condition.blank? && SearchIndexBackend.enabled?
|
if condition.blank? && SearchIndexBackend.enabled?
|
||||||
query_extention = {}
|
query_extention = {}
|
||||||
|
@ -135,7 +154,7 @@ returns
|
||||||
|
|
||||||
query_extention['bool']['must'].push access_condition
|
query_extention['bool']['must'].push access_condition
|
||||||
|
|
||||||
items = SearchIndexBackend.search(query, limit, 'Ticket', query_extention, offset)
|
items = SearchIndexBackend.search(query, limit, 'Ticket', query_extention, offset, sort_by, order_by)
|
||||||
if !full
|
if !full
|
||||||
ids = []
|
ids = []
|
||||||
items.each do |item|
|
items.each do |item|
|
||||||
|
@ -157,22 +176,25 @@ returns
|
||||||
|
|
||||||
# do query
|
# do query
|
||||||
# - stip out * we already search for *query* -
|
# - stip out * we already search for *query* -
|
||||||
|
|
||||||
|
order_select_sql = search_get_order_select_sql(sort_by, order_by, 'tickets.created_at')
|
||||||
|
order_sql = search_get_order_sql(sort_by, order_by, 'tickets.created_at DESC')
|
||||||
if query
|
if query
|
||||||
query.delete! '*'
|
query.delete! '*'
|
||||||
tickets_all = Ticket.select('DISTINCT(tickets.id), tickets.created_at')
|
tickets_all = Ticket.select('DISTINCT(tickets.id), ' + order_select_sql)
|
||||||
.where(access_condition)
|
.where(access_condition)
|
||||||
.where('(tickets.title LIKE ? OR tickets.number LIKE ? OR ticket_articles.body LIKE ? OR ticket_articles.from LIKE ? OR ticket_articles.to LIKE ? OR ticket_articles.subject LIKE ?)', "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%" )
|
.where('(tickets.title LIKE ? OR tickets.number LIKE ? OR ticket_articles.body LIKE ? OR ticket_articles.from LIKE ? OR ticket_articles.to LIKE ? OR ticket_articles.subject LIKE ?)', "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%" )
|
||||||
.joins(:articles)
|
.joins(:articles)
|
||||||
.order('tickets.created_at DESC')
|
.order(order_sql)
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
else
|
else
|
||||||
query_condition, bind_condition, tables = selector2sql(condition)
|
query_condition, bind_condition, tables = selector2sql(condition)
|
||||||
tickets_all = Ticket.select('DISTINCT(tickets.id), tickets.created_at')
|
tickets_all = Ticket.select('DISTINCT(tickets.id), ' + order_select_sql)
|
||||||
.joins(tables)
|
.joins(tables)
|
||||||
.where(access_condition)
|
.where(access_condition)
|
||||||
.where(query_condition, *bind_condition)
|
.where(query_condition, *bind_condition)
|
||||||
.order('tickets.created_at DESC')
|
.order(order_sql)
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
end
|
end
|
||||||
|
@ -193,4 +215,5 @@ returns
|
||||||
tickets
|
tickets
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,6 +4,10 @@ class User
|
||||||
module Search
|
module Search
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
include HasSearchSortable
|
||||||
|
end
|
||||||
|
|
||||||
# methods defined here are going to extend the class, not the instance of it
|
# methods defined here are going to extend the class, not the instance of it
|
||||||
class_methods do
|
class_methods do
|
||||||
|
|
||||||
|
@ -54,6 +58,14 @@ or with certain role_ids | permissions
|
||||||
current_user: user_model,
|
current_user: user_model,
|
||||||
role_ids: [1,2,3],
|
role_ids: [1,2,3],
|
||||||
permissions: ['ticket.agent']
|
permissions: ['ticket.agent']
|
||||||
|
|
||||||
|
# sort single column
|
||||||
|
sort_by: 'created_at',
|
||||||
|
order_by: 'asc',
|
||||||
|
|
||||||
|
# sort multiple columns
|
||||||
|
sort_by: [ 'created_at', 'updated_at' ],
|
||||||
|
order_by: [ 'asc', 'desc' ],
|
||||||
)
|
)
|
||||||
|
|
||||||
returns
|
returns
|
||||||
|
@ -70,6 +82,12 @@ returns
|
||||||
offset = params[:offset] || 0
|
offset = params[:offset] || 0
|
||||||
current_user = params[:current_user]
|
current_user = params[:current_user]
|
||||||
|
|
||||||
|
# check sort
|
||||||
|
sort_by = search_get_sort_by(params, 'updated_at')
|
||||||
|
|
||||||
|
# check order
|
||||||
|
order_by = search_get_order_by(params, 'desc')
|
||||||
|
|
||||||
# enable search only for agents and admins
|
# enable search only for agents and admins
|
||||||
return [] if !search_preferences(current_user)
|
return [] if !search_preferences(current_user)
|
||||||
|
|
||||||
|
@ -94,7 +112,7 @@ returns
|
||||||
}
|
}
|
||||||
query_extention['bool']['must'].push access_condition
|
query_extention['bool']['must'].push access_condition
|
||||||
end
|
end
|
||||||
items = SearchIndexBackend.search(query, limit, 'User', query_extention, offset)
|
items = SearchIndexBackend.search(query, limit, 'User', query_extention, offset, sort_by, order_by)
|
||||||
users = []
|
users = []
|
||||||
items.each do |item|
|
items.each do |item|
|
||||||
user = User.lookup(id: item[:id])
|
user = User.lookup(id: item[:id])
|
||||||
|
@ -104,17 +122,19 @@ returns
|
||||||
return users
|
return users
|
||||||
end
|
end
|
||||||
|
|
||||||
|
order_sql = search_get_order_sql(sort_by, order_by, 'users.updated_at DESC')
|
||||||
|
|
||||||
# fallback do sql query
|
# fallback do sql query
|
||||||
# - stip out * we already search for *query* -
|
# - stip out * we already search for *query* -
|
||||||
query.delete! '*'
|
query.delete! '*'
|
||||||
users = if params[:role_ids]
|
users = if params[:role_ids]
|
||||||
User.joins(:roles).where('roles.id' => params[:role_ids]).where(
|
User.joins(:roles).where('roles.id' => params[:role_ids]).where(
|
||||||
'(users.firstname LIKE ? OR users.lastname LIKE ? OR users.email LIKE ? OR users.login LIKE ?) AND users.id != 1', "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%"
|
'(users.firstname LIKE ? OR users.lastname LIKE ? OR users.email LIKE ? OR users.login LIKE ?) AND users.id != 1', "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%"
|
||||||
).order('updated_at DESC').offset(offset).limit(limit)
|
).order(order_sql).offset(offset).limit(limit)
|
||||||
else
|
else
|
||||||
User.where(
|
User.where(
|
||||||
'(firstname LIKE ? OR lastname LIKE ? OR email LIKE ? OR login LIKE ?) AND id != 1', "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%"
|
'(firstname LIKE ? OR lastname LIKE ? OR email LIKE ? OR login LIKE ?) AND id != 1', "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%"
|
||||||
).order('updated_at DESC').offset(offset).limit(limit)
|
).order(order_sql).offset(offset).limit(limit)
|
||||||
end
|
end
|
||||||
users
|
users
|
||||||
end
|
end
|
||||||
|
|
|
@ -277,6 +277,8 @@ return search result
|
||||||
|
|
||||||
result = SearchIndexBackend.search('search query', limit, 'User')
|
result = SearchIndexBackend.search('search query', limit, 'User')
|
||||||
|
|
||||||
|
result = SearchIndexBackend.search('search query', limit, 'User', ['updated_at'], ['desc'])
|
||||||
|
|
||||||
result = [
|
result = [
|
||||||
{
|
{
|
||||||
:id => 123,
|
:id => 123,
|
||||||
|
@ -294,20 +296,24 @@ return search result
|
||||||
|
|
||||||
=end
|
=end
|
||||||
|
|
||||||
def self.search(query, limit = 10, index = nil, query_extention = {}, from = 0)
|
# rubocop:disable Metrics/ParameterLists
|
||||||
|
def self.search(query, limit = 10, index = nil, query_extention = {}, from = 0, sort_by = [], order_by = [])
|
||||||
|
# rubocop:enable Metrics/ParameterLists
|
||||||
return [] if query.blank?
|
return [] if query.blank?
|
||||||
if index.class == Array
|
if index.class == Array
|
||||||
ids = []
|
ids = []
|
||||||
index.each do |local_index|
|
index.each do |local_index|
|
||||||
local_ids = search_by_index(query, limit, local_index, query_extention, from)
|
local_ids = search_by_index(query, limit, local_index, query_extention, from, sort_by, order_by )
|
||||||
ids = ids.concat(local_ids)
|
ids = ids.concat(local_ids)
|
||||||
end
|
end
|
||||||
return ids
|
return ids
|
||||||
end
|
end
|
||||||
search_by_index(query, limit, index, query_extention, from)
|
search_by_index(query, limit, index, query_extention, from, sort_by, order_by)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.search_by_index(query, limit = 10, index = nil, query_extention = {}, from)
|
# rubocop:disable Metrics/ParameterLists
|
||||||
|
def self.search_by_index(query, limit = 10, index = nil, query_extention = {}, from = 0, sort_by = [], order_by = [])
|
||||||
|
# rubocop:enable Metrics/ParameterLists
|
||||||
return [] if query.blank?
|
return [] if query.blank?
|
||||||
|
|
||||||
url = build_url
|
url = build_url
|
||||||
|
@ -324,15 +330,8 @@ return search result
|
||||||
data = {}
|
data = {}
|
||||||
data['from'] = from
|
data['from'] = from
|
||||||
data['size'] = limit
|
data['size'] = limit
|
||||||
data['sort'] =
|
|
||||||
[
|
data['sort'] = search_by_index_sort(sort_by, order_by)
|
||||||
{
|
|
||||||
updated_at: {
|
|
||||||
order: 'desc'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'_score'
|
|
||||||
]
|
|
||||||
|
|
||||||
data['query'] = query_extention || {}
|
data['query'] = query_extention || {}
|
||||||
data['query']['bool'] ||= {}
|
data['query']['bool'] ||= {}
|
||||||
|
@ -389,6 +388,36 @@ return search result
|
||||||
ids
|
ids
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.search_by_index_sort(sort_by = [], order_by = [])
|
||||||
|
result = []
|
||||||
|
|
||||||
|
sort_by.each_with_index do |value, index|
|
||||||
|
next if value.blank?
|
||||||
|
next if order_by[index].blank?
|
||||||
|
|
||||||
|
if value !~ /\./ && value !~ /_(time|date|till|id|ids|at)$/
|
||||||
|
value += '.raw'
|
||||||
|
end
|
||||||
|
result.push(
|
||||||
|
value => {
|
||||||
|
order: order_by[index],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
if result.blank?
|
||||||
|
result.push(
|
||||||
|
created_at: {
|
||||||
|
order: 'desc',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
result.push('_score')
|
||||||
|
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
=begin
|
=begin
|
||||||
|
|
||||||
get count of tickets and tickets which match on selector
|
get count of tickets and tickets which match on selector
|
||||||
|
|
|
@ -23,50 +23,26 @@ namespace :searchindex do
|
||||||
if info.present?
|
if info.present?
|
||||||
number = info['version']['number'].to_s
|
number = info['version']['number'].to_s
|
||||||
end
|
end
|
||||||
if number.blank? || number =~ /^[2-4]\./ || number =~ /^5\.[0-5]\./
|
|
||||||
|
|
||||||
# create indexes
|
mapping = {}
|
||||||
SearchIndexBackend.index(
|
Models.searchable.each do |local_object|
|
||||||
action: 'create',
|
mapping.merge!(get_mapping_properties_object(local_object))
|
||||||
data: {
|
|
||||||
mappings: {
|
|
||||||
Ticket: {
|
|
||||||
_source: { excludes: [ 'article.attachment' ] },
|
|
||||||
properties: {
|
|
||||||
article: {
|
|
||||||
type: 'nested',
|
|
||||||
include_in_parent: true,
|
|
||||||
properties: {
|
|
||||||
attachment: {
|
|
||||||
type: 'attachment',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
puts 'done'
|
|
||||||
Setting.set('es_pipeline', '')
|
|
||||||
|
|
||||||
# es with ingest-attachment plugin
|
|
||||||
else
|
|
||||||
|
|
||||||
# create indexes
|
|
||||||
SearchIndexBackend.index(
|
|
||||||
action: 'create',
|
|
||||||
data: {
|
|
||||||
mappings: {
|
|
||||||
Ticket: {
|
|
||||||
_source: { excludes: [ 'article.attachment' ] },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
puts 'done'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# create indexes
|
||||||
|
SearchIndexBackend.index(
|
||||||
|
action: 'create',
|
||||||
|
data: {
|
||||||
|
mappings: mapping
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if number.blank? || number =~ /^[2-4]\./ || number =~ /^5\.[0-5]\./
|
||||||
|
Setting.set('es_pipeline', '')
|
||||||
|
end
|
||||||
|
|
||||||
|
puts 'done'
|
||||||
|
|
||||||
Rake::Task['searchindex:create_pipeline'].execute
|
Rake::Task['searchindex:create_pipeline'].execute
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -168,3 +144,107 @@ namespace :searchindex do
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
=begin
|
||||||
|
|
||||||
|
This function will return a index mapping based on the
|
||||||
|
attributes of the database table of the existing object.
|
||||||
|
|
||||||
|
mapping = get_mapping_properties_object(Ticket)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
mapping = {
|
||||||
|
User: {
|
||||||
|
properties: {
|
||||||
|
firstname: {
|
||||||
|
type: 'keyword',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
=end
|
||||||
|
|
||||||
|
def get_mapping_properties_object(object)
|
||||||
|
result = {
|
||||||
|
object.name => {
|
||||||
|
properties: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
store_columns = %w[preferences data]
|
||||||
|
|
||||||
|
object.columns_hash.each do |key, value|
|
||||||
|
if value.type == :string && value.limit && value.limit <= 5000 && store_columns.exclude?(key)
|
||||||
|
result[object.name][:properties][key] = {
|
||||||
|
type: 'text',
|
||||||
|
fields: {
|
||||||
|
raw: { 'type': 'string', 'index': 'not_analyzed' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elsif value.type == :integer
|
||||||
|
result[object.name][:properties][key] = {
|
||||||
|
type: 'integer',
|
||||||
|
}
|
||||||
|
elsif value.type == :datetime
|
||||||
|
result[object.name][:properties][key] = {
|
||||||
|
type: 'date',
|
||||||
|
}
|
||||||
|
elsif value.type == :boolean
|
||||||
|
result[object.name][:properties][key] = {
|
||||||
|
type: 'boolean',
|
||||||
|
fields: {
|
||||||
|
raw: { 'type': 'boolean', 'index': 'not_analyzed' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elsif value.type == :binary
|
||||||
|
result[object.name][:properties][key] = {
|
||||||
|
type: 'binary',
|
||||||
|
}
|
||||||
|
elsif value.type == :bigint
|
||||||
|
result[object.name][:properties][key] = {
|
||||||
|
type: 'long',
|
||||||
|
}
|
||||||
|
elsif value.type == :decimal
|
||||||
|
result[object.name][:properties][key] = {
|
||||||
|
type: 'float',
|
||||||
|
}
|
||||||
|
elsif value.type == :date
|
||||||
|
result[object.name][:properties][key] = {
|
||||||
|
type: 'date',
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# es with mapper-attachments plugin
|
||||||
|
info = SearchIndexBackend.info
|
||||||
|
number = nil
|
||||||
|
if info.present?
|
||||||
|
number = info['version']['number'].to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
if object.name == 'Ticket'
|
||||||
|
|
||||||
|
result[object.name][:_source] = {
|
||||||
|
excludes: ['article.attachment']
|
||||||
|
}
|
||||||
|
|
||||||
|
if number.blank? || number =~ /^[2-4]\./ || number =~ /^5\.[0-5]\./
|
||||||
|
result[object.name][:_source] = {
|
||||||
|
excludes: ['article.attachment']
|
||||||
|
}
|
||||||
|
result[object.name][:properties][:article] = {
|
||||||
|
type: 'nested',
|
||||||
|
include_in_parent: true,
|
||||||
|
properties: {
|
||||||
|
attachment: {
|
||||||
|
type: 'attachment',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
|
@ -2126,4 +2126,93 @@ AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
|
||||||
assert_equal(ticket2.id, result['master_ticket']['id'])
|
assert_equal(ticket2.id, result['master_ticket']['id'])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test '08.01 ticket search sorted' do
|
||||||
|
title = "ticket pagination #{rand(999_999_999)}"
|
||||||
|
tickets = []
|
||||||
|
|
||||||
|
ticket1 = Ticket.create!(
|
||||||
|
title: "#{title} A",
|
||||||
|
group: Group.lookup(name: 'Users'),
|
||||||
|
customer_id: @customer_without_org.id,
|
||||||
|
state: Ticket::State.lookup(name: 'new'),
|
||||||
|
priority: Ticket::Priority.lookup(name: '2 normal'),
|
||||||
|
updated_by_id: 1,
|
||||||
|
created_by_id: 1,
|
||||||
|
)
|
||||||
|
Ticket::Article.create!(
|
||||||
|
type: Ticket::Article::Type.lookup(name: 'note'),
|
||||||
|
sender: Ticket::Article::Sender.lookup(name: 'Customer'),
|
||||||
|
from: 'sender',
|
||||||
|
subject: 'subject',
|
||||||
|
body: 'some body',
|
||||||
|
ticket_id: ticket1.id,
|
||||||
|
updated_by_id: 1,
|
||||||
|
created_by_id: 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
travel 2.seconds
|
||||||
|
|
||||||
|
ticket2 = Ticket.create!(
|
||||||
|
title: "#{title} B",
|
||||||
|
group: Group.lookup(name: 'Users'),
|
||||||
|
customer_id: @customer_without_org.id,
|
||||||
|
state: Ticket::State.lookup(name: 'new'),
|
||||||
|
priority: Ticket::Priority.lookup(name: '3 hoch'),
|
||||||
|
updated_by_id: 1,
|
||||||
|
created_by_id: 1,
|
||||||
|
)
|
||||||
|
Ticket::Article.create!(
|
||||||
|
type: Ticket::Article::Type.lookup(name: 'note'),
|
||||||
|
sender: Ticket::Article::Sender.lookup(name: 'Customer'),
|
||||||
|
from: 'sender',
|
||||||
|
subject: 'subject',
|
||||||
|
body: 'some body',
|
||||||
|
ticket_id: ticket2.id,
|
||||||
|
updated_by_id: 1,
|
||||||
|
created_by_id: 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('tickets-admin', 'adminpw')
|
||||||
|
get "/api/v1/tickets/search?query=#{CGI.escape(title)}&limit=40", params: {}, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(200)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
assert_equal(Hash, result.class)
|
||||||
|
assert_equal([ticket2.id, ticket1.id], result['tickets'])
|
||||||
|
|
||||||
|
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('tickets-admin', 'adminpw')
|
||||||
|
get "/api/v1/tickets/search?query=#{CGI.escape(title)}&limit=40", params: { sort_by: 'created_at', order_by: 'asc' }, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(200)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
assert_equal(Hash, result.class)
|
||||||
|
assert_equal([ticket1.id, ticket2.id], result['tickets'])
|
||||||
|
|
||||||
|
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('tickets-admin', 'adminpw')
|
||||||
|
get "/api/v1/tickets/search?query=#{CGI.escape(title)}&limit=40", params: { sort_by: 'title', order_by: 'asc' }, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(200)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
assert_equal(Hash, result.class)
|
||||||
|
assert_equal([ticket1.id, ticket2.id], result['tickets'])
|
||||||
|
|
||||||
|
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('tickets-admin', 'adminpw')
|
||||||
|
get "/api/v1/tickets/search?query=#{CGI.escape(title)}&limit=40", params: { sort_by: 'title', order_by: 'desc' }, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(200)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
assert_equal(Hash, result.class)
|
||||||
|
assert_equal([ticket2.id, ticket1.id], result['tickets'])
|
||||||
|
|
||||||
|
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('tickets-admin', 'adminpw')
|
||||||
|
get "/api/v1/tickets/search?query=#{CGI.escape(title)}&limit=40", params: { sort_by: %w[created_at updated_at], order_by: %w[asc asc] }, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(200)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
assert_equal(Hash, result.class)
|
||||||
|
assert_equal([ticket1.id, ticket2.id], result['tickets'])
|
||||||
|
|
||||||
|
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('tickets-admin', 'adminpw')
|
||||||
|
get "/api/v1/tickets/search?query=#{CGI.escape(title)}&limit=40", params: { sort_by: %w[created_at updated_at], order_by: %w[desc asc] }, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(200)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
assert_equal(Hash, result.class)
|
||||||
|
assert_equal([ticket2.id, ticket1.id], result['tickets'])
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1038,4 +1038,100 @@ class UserControllerTest < ActionDispatch::IntegrationTest
|
||||||
user2.destroy!
|
user2.destroy!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test 'user search sortable' do
|
||||||
|
firstname = "user_search_sortable #{rand(999_999_999)}"
|
||||||
|
|
||||||
|
roles = Role.where(name: 'Customer')
|
||||||
|
user1 = User.create_or_update(
|
||||||
|
login: 'rest-user_search_sortableA@example.com',
|
||||||
|
firstname: "#{firstname} A",
|
||||||
|
lastname: 'user_search_sortableA',
|
||||||
|
email: 'rest-user_search_sortableA@example.com',
|
||||||
|
password: 'user_search_sortableA',
|
||||||
|
active: true,
|
||||||
|
roles: roles,
|
||||||
|
organization_id: @organization.id,
|
||||||
|
out_of_office: false,
|
||||||
|
created_at: '2016-02-05 17:42:00',
|
||||||
|
updated_by_id: 1,
|
||||||
|
created_by_id: 1,
|
||||||
|
)
|
||||||
|
user2 = User.create_or_update(
|
||||||
|
login: 'rest-user_search_sortableB@example.com',
|
||||||
|
firstname: "#{firstname} B",
|
||||||
|
lastname: 'user_search_sortableB',
|
||||||
|
email: 'rest-user_search_sortableB@example.com',
|
||||||
|
password: 'user_search_sortableB',
|
||||||
|
active: true,
|
||||||
|
roles: roles,
|
||||||
|
organization_id: @organization.id,
|
||||||
|
out_of_office_start_at: '2016-02-06 19:42:00',
|
||||||
|
out_of_office_end_at: '2016-02-07 19:42:00',
|
||||||
|
out_of_office_replacement_id: 1,
|
||||||
|
out_of_office: true,
|
||||||
|
created_at: '2016-02-05 19:42:00',
|
||||||
|
updated_by_id: 1,
|
||||||
|
created_by_id: 1,
|
||||||
|
)
|
||||||
|
Scheduler.worker(true)
|
||||||
|
sleep 2 # let es time to come ready
|
||||||
|
|
||||||
|
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('rest-admin@example.com', 'adminpw')
|
||||||
|
get "/api/v1/users/search?query=#{CGI.escape(firstname)}", params: { sort_by: 'created_at', order_by: 'asc' }, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(200)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
assert_equal(Array, result.class)
|
||||||
|
result.collect! { |v| v['id'] }
|
||||||
|
assert_equal([user1.id, user2.id], result)
|
||||||
|
|
||||||
|
get "/api/v1/users/search?query=#{CGI.escape(firstname)}", params: { sort_by: 'firstname', order_by: 'asc' }, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(200)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
assert_equal(Array, result.class)
|
||||||
|
result.collect! { |v| v['id'] }
|
||||||
|
assert_equal([user1.id, user2.id], result)
|
||||||
|
|
||||||
|
get "/api/v1/users/search?query=#{CGI.escape(firstname)}", params: { sort_by: 'firstname', order_by: 'desc' }, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(200)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
assert_equal(Array, result.class)
|
||||||
|
result.collect! { |v| v['id'] }
|
||||||
|
assert_equal([user2.id, user1.id], result)
|
||||||
|
|
||||||
|
get "/api/v1/users/search?query=#{CGI.escape(firstname)}", params: { sort_by: %w[firstname created_at], order_by: %w[desc asc] }, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(200)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
assert_equal(Array, result.class)
|
||||||
|
result.collect! { |v| v['id'] }
|
||||||
|
assert_equal([user2.id, user1.id], result)
|
||||||
|
|
||||||
|
get "/api/v1/users/search?query=#{CGI.escape(firstname)}", params: { sort_by: %w[firstname created_at], order_by: %w[desc asc] }, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(200)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
assert_equal(Array, result.class)
|
||||||
|
result.collect! { |v| v['id'] }
|
||||||
|
assert_equal([user2.id, user1.id], result)
|
||||||
|
|
||||||
|
get "/api/v1/users/search?query=#{CGI.escape(firstname)}", params: { sort_by: 'out_of_office', order_by: 'asc' }, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(200)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
assert_equal(Array, result.class)
|
||||||
|
result.collect! { |v| v['id'] }
|
||||||
|
assert_equal([user1.id, user2.id], result)
|
||||||
|
|
||||||
|
get "/api/v1/users/search?query=#{CGI.escape(firstname)}", params: { sort_by: 'out_of_office', order_by: 'desc' }, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(200)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
assert_equal(Array, result.class)
|
||||||
|
result.collect! { |v| v['id'] }
|
||||||
|
assert_equal([user2.id, user1.id], result)
|
||||||
|
|
||||||
|
get "/api/v1/users/search?query=#{CGI.escape(firstname)}", params: { sort_by: %w[created_by_id created_at], order_by: %w[asc asc] }, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(200)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
assert_equal(Array, result.class)
|
||||||
|
result.collect! { |v| v['id'] }
|
||||||
|
assert_equal([user1.id, user2.id], result)
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -64,12 +64,15 @@ class UserOrganizationControllerTest < ActionDispatch::IntegrationTest
|
||||||
# create orgs
|
# create orgs
|
||||||
@organization = Organization.create!(
|
@organization = Organization.create!(
|
||||||
name: 'Rest Org',
|
name: 'Rest Org',
|
||||||
|
note: 'Rest Org A',
|
||||||
)
|
)
|
||||||
@organization2 = Organization.create!(
|
@organization2 = Organization.create!(
|
||||||
name: 'Rest Org #2',
|
name: 'Rest Org #2',
|
||||||
|
note: 'Rest Org B',
|
||||||
)
|
)
|
||||||
@organization3 = Organization.create!(
|
@organization3 = Organization.create!(
|
||||||
name: 'Rest Org #3',
|
name: 'Rest Org #3',
|
||||||
|
note: 'Rest Org C',
|
||||||
)
|
)
|
||||||
|
|
||||||
# create customer with org
|
# create customer with org
|
||||||
|
@ -767,4 +770,36 @@ class UserOrganizationControllerTest < ActionDispatch::IntegrationTest
|
||||||
assert_response(401)
|
assert_response(401)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test 'organization search sortable' do
|
||||||
|
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('rest-admin', 'adminpw')
|
||||||
|
|
||||||
|
get "/api/v1/organizations/search?query=#{CGI.escape('Rest Org')}", params: {}, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(200)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
result.collect! { |v| v['id'] }
|
||||||
|
assert_equal(Array, result.class)
|
||||||
|
assert_equal([ @organization.id, @organization2.id, @organization3.id ], result)
|
||||||
|
|
||||||
|
get "/api/v1/organizations/search?query=#{CGI.escape('Rest Org')}", params: { sort_by: 'note', order_by: 'asc' }, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(200)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
result.collect! { |v| v['id'] }
|
||||||
|
assert_equal(Array, result.class)
|
||||||
|
assert_equal([ @organization.id, @organization2.id, @organization3.id ], result)
|
||||||
|
|
||||||
|
get "/api/v1/organizations/search?query=#{CGI.escape('Rest Org')}", params: { sort_by: 'note', order_by: 'desc' }, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(200)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
result.collect! { |v| v['id'] }
|
||||||
|
assert_equal(Array, result.class)
|
||||||
|
assert_equal([ @organization3.id, @organization2.id, @organization.id ], result)
|
||||||
|
|
||||||
|
get "/api/v1/organizations/search?query=#{CGI.escape('Rest Org')}", params: { sort_by: %w[note created_at], order_by: %w[desc asc] }, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(200)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
result.collect! { |v| v['id'] }
|
||||||
|
assert_equal(Array, result.class)
|
||||||
|
assert_equal([ @organization3.id, @organization2.id, @organization.id ], result)
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue