require 'elastic/site-search/configuration'
require 'elastic/site-search/result_set'
require 'elastic/site-search/request'
module Elastic
module SiteSearch
# API client for the {Elastic Site Search API}[https://www.elastic.co/products/site-search/service].
class Client
DEFAULT_TIMEOUT = 15
include Elastic::SiteSearch::Request
# Create a new Elastic::SiteSearch::Client client
#
# @param options [Hash] a hash of configuration options that will override what is set on the Elastic::SiteSearch class.
# @option options [String] :api_key an API Key to use for this client
# @option options [String] :platform_access_token a user's access token, will be used instead of API key for authenticating requests
# @option options [Numeric] :overall_timeout overall timeout for requests in seconds (default: 15s)
# @option options [Numeric] :open_timeout the number of seconds Net::HTTP (default: 15s)
# @option options [String] :proxy url of proxy to use, ex: "http://localhost:8888"
# will wait while opening a connection before raising a Timeout::Error
def initialize(options={})
@options = options
end
def api_key
@options[:api_key] || Elastic::SiteSearch.api_key
end
def platform_access_token
@options[:platform_access_token]
end
def proxy
@options[:proxy]
end
def open_timeout
@options[:open_timeout] || DEFAULT_TIMEOUT
end
def overall_timeout
(@options[:overall_timeout] || DEFAULT_TIMEOUT).to_f
end
def wrap(element)
[element].flatten(1)
end
# Methods wrapping the Elastic::SiteSearch private search and API endpoints. Using these methods, you can perform full-text
# and prefix searches over the Documents in your Engine, in a specific DocumentType, or any subset of DocumentTypes.
# You can also filter results and get faceted counts for results.
#
# For more information, visit the {REST API documentation on searching}[https://swiftype.com/documentation/site-search/searching].
module Search
# Perform an autocomplete (prefix) search over all the DocumentTypes of the provided engine.
# This can be used to implement type-ahead autocompletion. However, if your data is not sensitive,
# you should consider using the {Site Search public JSONP API}[https://swiftype.com/documentation/site-search/searching#public_search]
# in the user's web browser for suggest queries.
#
# results = client.suggest("site-search-api-example", "gla")
# results['videos'] # => [{'external_id' => 'v1uyQZNg2vE', 'title' => 'How It Feels [through Glass]', ...}, ...]
#
# @param [String] engine_id the Engine slug or ID
# @param [String] query the search terms
# @param [Hash] options search options (see {the REST API docs}[https://swiftype.com/documentation/site-search/searching] for a complete list)
# @option options [Integer] :page page number of results to fetch (server defaults to 1)
# @option options [Integer] :per_page number of results per page (server defaults to 20)
# @option options [Array] :document_types an array of DocumentType slugs to search.
# The server defaults to searching all DocumentTypes in the engine. To search a single document type,
# the +suggest_document_type+ method is more convenient.
# @option options [Hash] :fetch_fields a Hash of DocumentType slug to array of the fields to return with results
# (example: {'videos' => ['title', 'channel_id']}
)
# @option options [Hash] :search_fields a Hash of DocumentType slug to array of the fields to search.
# May contain {field weight boosts}[https://swiftype.com/documentation/site-search/searching/field-weights]
# (example: {'videos' => ['title^5', 'tags^2', 'caption']}
).
# The server defaults to searching all +string+ fields for suggest queries.
# @option options [Hash] :filters a Hash of DocumentType slug to filter definition Hash.
# See {filters in the REST API documentation}[https://swiftype.com/documentation/site-search/searching/filtering] for more details
# (example: {'videos' => {'category_id' => ['23', '25']}}
)
# @option options [Hash] :functional_boosts a Hash of DocumentType slug to {functional boost}[https://swiftype.com/documentation/site-search/searching/boosting] definition
# (example: {'videos' => {'view_count' => 'logarithmic'}}
).
# @option options [Hash] :sort_field a Hash of DocumentType slug to field name to sort on
# (example: {'videos' => 'view_count'}
)
# @option options [Hash] :sort_direction a Hash of DocumentType slug to direction to sort
# (example: 'videos' => 'desc'
). Usually used with +:sort_field+.
#
# @return [Elastic::SiteSearch::ResultSet]
def suggest(engine_id, query, options={})
search_params = { :q => query }.merge(options)
response = post("engines/#{engine_id}/suggest.json", search_params)
ResultSet.new(response)
end
# Perform a full-text search over all the DocumentTypes of the provided engine.
#
# results = client.search("site-search-api-example", "glass")
# results['videos'] # => [{'external_id' => 'v1uyQZNg2vE', 'title' => 'How It Feels [through Glass]', ...}, ...]
#
# @param [String] engine_id the Engine slug or ID
# @param [String] query the search terms (may be nil)
# @param [Hash] options search options (see {the REST API docs}[https://swiftype.com/documentation/site-search/searching] for a complete list)
# @option options [Integer] :page page number of results to fetch (server defaults to 1)
# @option options [Integer] :per_page number of results per page (server defaults to 20)
# @option options [Array] :document_types an array of DocumentType slugs to search.
# The server defaults to searching all DocumentTypes in the engine. To search a single document type,
# the +search_document_type+ method is more convenient.
# @option options [Hash] :fetch_fields a Hash of DocumentType slug to array of the fields to return with results
# (example: {'videos' => ['title', 'channel_id']}
)
# @option options [Hash] :search_fields a Hash of DocumentType slug to array of the fields to search.
# May contain {field weight boosts}[https://swiftype.com/documentation/site-search/searching/field-weights]
# (example: {'videos' => ['title^5', 'tags^2', 'caption']}
).
# The server defaults to searching all +string+ and +text+ fields for search queries.
# @option options [Hash] :filters a Hash of DocumentType slug to filter definition Hash.
# See {filters in the REST API documentation}[https://swiftype.com/documentation/site-search/searching/filtering] for more details
# (example: {'videos' => {'category_id' => ['23', '25']}}
)
# @option options [Hash] :functional_boosts a Hash of DocumentType slug to {functional boost}[https://swiftype.com/documentation/site-search/searching/boosting] definition
# (example: {'videos' => {'view_count' => 'logarithmic'}}
).
# @option options [Hash] :facets a Hash of DocumentType slug to an Array of field names to provide facetted counts for
# (example: {'videos' => ['category_id', 'channel_id']}
)
# @option options [Hash] :sort_field a Hash of DocumentType slug to field name to sort on
# (example: {'videos' => 'view_count'}
)
# @option options [Hash] :sort_direction a Hash of DocumentType slug to direction to sort
# (example: 'videos' => 'desc'
). Usually used with +:sort_field+.
#
# @return [Elastic::SiteSearch::ResultSet]
def search(engine_id, query, options={})
search_params = { :q => query }.merge(options)
response = post("engines/#{engine_id}/search.json", search_params)
ResultSet.new(response)
end
# Perform an autocomplete (prefix) search over a single DocumentType in an Engine.
# This can be used to implement type-ahead autocompletion. However, if your data is not sensitive,
# you should consider using the {Site Search public JSONP API}[https://swiftype.com/documentation/site-search/searching#public_search]
# in the user's web browser for suggest queries.
#
# results = client.suggest_document_type("site-search-api-example", "videos", "gla")
# results['videos'] # => [{'external_id' => 'v1uyQZNg2vE', 'title' => 'How It Feels [through Glass]', ...}, ...]
#
# @param [String] engine_id the Engine slug or ID
# @param [String] query the search terms
# @param [Hash] options search options (see {the REST API docs}[https://swiftype.com/documentation/site-search/searching] for a complete list)
# @option options [Integer] :page page number of results to fetch (server defaults to 1)
# @option options [Integer] :per_page number of results per page (server defaults to 20)
# @option options [Array] :document_types an array of DocumentType slugs to search.
# The server defaults to searching all DocumentTypes in the engine. To search a single document type,
# the +suggest_document_type+ method is more convenient.
# @option options [Hash] :fetch_fields a Hash of DocumentType slug to array of the fields to return with results
# (example: {'videos' => ['title', 'channel_id']}
)
# @option options [Hash] :search_fields a Hash of DocumentType slug to array of the fields to search.
# May contain {field weight boosts}[https://swiftype.com/documentation/site-search/searching/field-weights]
# (example: {'videos' => ['title^5', 'tags^2', 'caption']}
).
# The server defaults to searching all +string+ fields for suggest queries.
# @option options [Hash] :filters a Hash of DocumentType slug to filter definition Hash.
# See {filters in the REST API documentation}[https://swiftype.com/documentation/site-search/searching/filtering] for more details
# (example: {'videos' => {'category_id' => ['23', '25']}}
)
# @option options [Hash] :functional_boosts a Hash of DocumentType slug to {functional boost}[https://swiftype.com/documentation/site-search/searching/boosting] definition
# (example: {'videos' => {'view_count' => 'logarithmic'}}
).
# @option options [Hash] :sort_field a Hash of DocumentType slug to field name to sort on
# (example: {'videos' => 'view_count'}
)
# @option options [Hash] :sort_direction a Hash of DocumentType slug to direction to sort
# (example: 'videos' => 'desc'
). Usually used with +:sort_field+.
#
# @return [Elastic::SiteSearch::ResultSet]
def suggest_document_type(engine_id, document_type_id, query, options={})
search_params = { :q => query }.merge(options)
response = post("engines/#{engine_id}/document_types/#{document_type_id}/suggest.json", search_params)
ResultSet.new(response)
end
# Perform a full-text search over a single DocumentType in an Engine.
#
# results = client.search_document_type("site-search-api-example", "videos", "glass")
# results['videos'] # => [{'external_id' => 'v1uyQZNg2vE', 'title' => 'How It Feels [through Glass]', ...}, ...]
#
# @param [String] engine_id the Engine slug or ID
# @param [String] document_type_id the DocumentType slug or ID
# @param [String] query the search terms (may be nil)
# @param [Hash] options search options (see {the REST API docs}[https://swiftype.com/documentation/site-search/searching] for a complete list)
# @option options [Integer] :page page number of results to fetch (server defaults to 1)
# @option options [Integer] :per_page number of results per page (server defaults to 20)
# @option options [Hash] :fetch_fields a Hash of DocumentType slug to array of the fields to return with results
# (example: {'videos' => ['title', 'channel_id']}
)
# @option options [Hash] :search_fields a Hash of DocumentType slug to array of the fields to search.
# May contain {field weight boosts}[https://swiftype.com/documentation/site-search/searching/field-weights]
# (example: {'videos' => ['title^5', 'tags^2', 'caption']}
).
# The server defaults to searching all +string+ and +text+ fields for search queries.
# @option options [Hash] :filters a Hash of DocumentType slug to filter definition Hash.
# See {filters in the REST API documentation}[https://swiftype.com/documentation/site-search/searching/filtering] for more details
# (example: {'videos' => {'category_id' => ['23', '25']}}
)
# @option options [Hash] :functional_boosts a Hash of DocumentType slug to {functional boost}[https://swiftype.com/documentation/site-search/searching/boosting] definition
# (example: {'videos' => {'view_count' => 'logarithmic'}}
).
# @option options [Hash] :facets a Hash of DocumentType slug to an Array of field names to provide facetted counts for
# (example: {'videos' => ['category_id', 'channel_id']}
)
# @option options [Hash] :sort_field a Hash of DocumentType slug to field name to sort on
# (example: {'videos' => 'view_count'}
)
# @option options [Hash] :sort_direction a Hash of DocumentType slug to direction to sort
# (example: 'videos' => 'desc'
). Usually used with +:sort_field+.
#
# @return [Elastic::SiteSearch::ResultSet]
def search_document_type(engine_id, document_type_id, query, options={})
search_params = { :q => query }.merge(options)
response = post("engines/#{engine_id}/document_types/#{document_type_id}/search.json", search_params)
ResultSet.new(response)
end
end
module User
# List users for the configured application.
#
# @param options [Hash]
# @option options [Integer] :page page number of users to fetch (server defaults to 1)
# @option options [Integer] :per_page users to return per page (server defaults to 50)
def users(options={})
params = {
:client_id => Elastic::SiteSearch.platform_client_id,
:client_secret => Elastic::SiteSearch.platform_client_secret
}
get("users.json", params.merge(options))
end
# Create a new user for the configured application.
def create_user
params = {
:client_id => Elastic::SiteSearch.platform_client_id,
:client_secret => Elastic::SiteSearch.platform_client_secret
}
post("users.json", params)
end
# Return a user created by the configured application.
#
# @param user_id [String] the Site Search User ID
def user(user_id)
params = {
:client_id => Elastic::SiteSearch.platform_client_id,
:client_secret => Elastic::SiteSearch.platform_client_secret
}
get("users/#{user_id}.json", params)
end
end
# An Engine is a search engine that lets you search and filter the Documents it contains.
# For more information, see the {REST API overview}[https://swiftype.com/documentation/site-search/overview].
module Engine
def engines
get("engines.json")
end
def engine(engine_id)
get("engines/#{engine_id}.json")
end
def create_engine(name)
post("engines.json", :engine => {:name => name})
end
def destroy_engine(engine_id)
delete("engines/#{engine_id}.json")
end
end
# Every Document must belong to a DocumentType. For more information, see the {REST API overview}[https://swiftype.com/documentation/site-search/overview].
module DocumentType
def document_types(engine_id)
get("engines/#{engine_id}/document_types.json")
end
def document_type(engine_id, document_type_id)
get("engines/#{engine_id}/document_types/#{document_type_id}.json")
end
def create_document_type(engine_id, name)
post("engines/#{engine_id}/document_types.json", :document_type => {:name => name})
end
def destroy_document_type(engine_id, document_type_id)
delete("engines/#{engine_id}/document_types/#{document_type_id}.json")
end
end
# Documents have fields that can be searched or filtered.
#
# For more information on indexing documents, see the {REST API indexing documentation}[https://swiftype.com/documentation/site-search/indexing].
module Document
def documents(engine_id, document_type_id, page=nil, per_page=nil)
options = {}
options[:page] = page if page
options[:per_page] = per_page if per_page
get("engines/#{engine_id}/document_types/#{document_type_id}/documents.json", options)
end
def document(engine_id, document_type_id, document_id)
get("engines/#{engine_id}/document_types/#{document_type_id}/documents/#{document_id}.json")
end
def create_document(engine_id, document_type_id, document={})
post("engines/#{engine_id}/document_types/#{document_type_id}/documents.json", :document => document)
end
def create_documents(engine_id, document_type_id, documents=[])
post("engines/#{engine_id}/document_types/#{document_type_id}/documents/bulk_create.json", :documents => documents)
end
def destroy_document(engine_id, document_type_id, document_id)
delete("engines/#{engine_id}/document_types/#{document_type_id}/documents/#{document_id}.json")
end
def destroy_documents(engine_id, document_type_id, document_ids=[])
post("engines/#{engine_id}/document_types/#{document_type_id}/documents/bulk_destroy.json", :documents => document_ids)
end
def create_or_update_document(engine_id, document_type_id, document={})
post("engines/#{engine_id}/document_types/#{document_type_id}/documents/create_or_update.json", :document => document)
end
def create_or_update_documents(engine_id, document_type_id, documents=[])
post("engines/#{engine_id}/document_types/#{document_type_id}/documents/bulk_create_or_update.json", :documents => documents)
end
def create_or_update_documents_verbose(engine_id, document_type_id, documents=[])
post("engines/#{engine_id}/document_types/#{document_type_id}/documents/bulk_create_or_update_verbose.json", :documents => documents)
end
def update_document(engine_id, document_type_id, document_id, fields)
put("engines/#{engine_id}/document_types/#{document_type_id}/documents/#{document_id}/update_fields.json", { :fields => fields })
end
def update_documents(engine_id, document_type_id, documents={})
put("engines/#{engine_id}/document_types/#{document_type_id}/documents/bulk_update.json", { :documents => documents })
end
def async_create_or_update_documents(engine_id, document_type_id, documents=[])
post("engines/#{engine_id}/document_types/#{document_type_id}/documents/async_bulk_create_or_update.json", :documents => documents)
end
# Retrieve Document Receipts from the API by ID
#
# @param [Array] receipt_ids an Array of Document Receipt IDs
#
# @return [Array] an Array of Document Receipt hashes
def document_receipts(receipt_ids)
post("document_receipts.json", :ids => receipt_ids)
end
# Index a batch of documents using the {asynchronous API}[https://swiftype.com/documentation/site-search/indexing#asynchronous].
# This is a good choice if you have a large number of documents.
#
# @param [String] engine_id the Engine slug or ID
# @param [String] document_type_id the Document Type slug or ID
# @param [Array] documents an Array of Document Hashes
# @param [Hash] options additional options
# @option options [Boolean] :async (false) When true, output is document receipts created. When false, poll until all receipts are no longer pending or timeout is reached.
# @option options [Numeric] :timeout (10) Number of seconds to wait before raising an exception
#
# @return [Array] an Array of newly-created Document Receipt hashes if used in :async => true mode
# @return [Array] an Array of processed Document Receipt hashes if used in :async => false mode
#
# @raise [Timeout::Error] when used in :async => false mode and the timeout expires
def index_documents(engine_id, document_type_id, documents = [], options = {})
documents = wrap(documents)
res = async_create_or_update_documents(engine_id, document_type_id, documents)
if options[:async]
res
else
receipt_ids = res["document_receipts"].map { |a| a["id"] }
poll(options) do
receipts = document_receipts(receipt_ids)
flag = receipts.all? { |a| a["status"] != "pending" }
flag ? receipts : false
end
end
end
end
# The analytics API provides a way to export analytics data similar to what is found in the Site Search Dashboard.
# See the {REST API Documentation}[https://swiftype.com/documentation/site-search/analytics] for details.
module Analytics
# Return the number of searches that occurred on each day in the time range for the provided Engine and optional DocumentType.
# The maximum time range between start and end dates is 30 days.
#
# @param [String] engine_id the Engine slug or ID
# @param [Hash] options
# @option options [String] :document_type_id the DocumentType slug or ID
# @option options [String] :start_date a date formatted like '2013-01-01'
# @option options [String] :end_date to a date formatted like '2013-01-01'
def analytics_searches(engine_id, options={})
document_type_id = options.delete(:document_type_id)
if document_type_id
get("engines/#{engine_id}/document_types/#{document_type_id}/analytics/searches.json", options)
else
get("engines/#{engine_id}/analytics/searches.json", options)
end
end
# Return the number of autoselects (when a user clicks a result from an autocomplete dropdown)
# that occurred on each day in the time range for the provided Engine and optional DocumentType.
# The maximum time range between start and end dates is 30 days.
#
# @param [String] engine_id the Engine slug or ID
# @param [Hash] options
# @option options [String] :document_type_id the DocumentType slug or ID
# @option options [String] :start_date a date formatted like '2013-01-01'
# @option options [String] :end_date to a date formatted like '2013-01-01'
def analytics_autoselects(engine_id, options={})
document_type_id = options.delete(:document_type_id)
if document_type_id
get("engines/#{engine_id}/document_types/#{document_type_id}/analytics/autoselects.json", options)
else
get("engines/#{engine_id}/analytics/autoselects.json", options)
end
end
# Return the number of clickthroughs (when a user clicks a result from a search results page)
# that occurred on each day in the time range for the provided Engine and optional DocumentType.
# The maximum time range between start and end dates is 30 days.
#
# @param [String] engine_id the Engine slug or ID
# @param [Hash] options
# @option options [String] :document_type_id the DocumentType slug or ID
# @option options [String] :start_date a date formatted like '2013-01-01'
# @option options [String] :end_date to a date formatted like '2013-01-01'
def analytics_clicks(engine_id, options={})
document_type_id = options.delete(:document_type_id)
if document_type_id
get("engines/#{engine_id}/document_types/#{document_type_id}/analytics/clicks.json", options)
else
get("engines/#{engine_id}/analytics/clicks.json", options)
end
end
# Return top queries for an engine.
#
# @param [String] engine_id the engine slug or ID
# @param [Hash] options
# @option options [String] :start_date a date formatted like '2013-01-01'
# @option options [String] :end_date a date formatted like '2013-01-01'
# @option options [Integer] :page page number. The server defaults to page 1 and the maximum is 50.
# @option options [Integer] :per_page number of results per page. The server defaults to 20 and the maximum is 100.
def analytics_top_queries(engine_id, options={})
get("engines/#{engine_id}/analytics/top_queries.json", options)
end
# Return top queries with no results for an engine.
#
# @param [String] engine_id the engine slug or ID
# @param [Hash] options
# @option options [String] :start_date a date formatted like '2013-01-01'
# @option options [String] :end_date a date formatted like '2013-01-01'
# @option options [Integer] :page page number. The server defaults to page 1 and the maximum is 50.
# @option options [Integer] :per_page number of results per page. The server defaults to 20 and the maximum is 100.
def analytics_top_no_result_queries(engine_id, options={})
get("engines/#{engine_id}/analytics/top_no_result_queries.json", options)
end
end
# A Domain represents a host in a crawler-based Engine. Domains
# are only relevant to crawler-base engines, but you can
# manipulate them through the REST API.
module Domain
def domains(engine_id)
get("engines/#{engine_id}/domains.json")
end
def domain(engine_id, domain_id)
get("engines/#{engine_id}/domains/#{domain_id}.json")
end
def create_domain(engine_id, url)
post("engines/#{engine_id}/domains.json", {:domain => {:submitted_url => url}})
end
def destroy_domain(engine_id, domain_id)
delete("engines/#{engine_id}/domains/#{domain_id}.json")
end
# Trigger a recrawl request for a Domain. Note that this will fail if you have exceeded your recrawl limit.
def recrawl_domain(engine_id, domain_id)
put("engines/#{engine_id}/domains/#{domain_id}/recrawl.json")
end
# Request to add or update a URL on a Domain. The host of the URL must match the host of the Domain.
#
# @param [String] engine_id the Engine slug or ID
# @param [String] domain_id the Domain ID
# @param [String] url the URL to crawl
def crawl_url(engine_id, domain_id, url)
put("engines/#{engine_id}/domains/#{domain_id}/crawl_url.json", {:url => url})
end
end
# A Clickthrough represents a user clicking on a full-text search result.
#
# If you are routing searches through your own server instead of
# executing them client-side with the Site Search JavaScript API, you
# will need to record clickthroughs yourself.
module Clickthrough
# Log a clickthrough for a Document.
#
# @param [String] engine_id the Engine slug or ID
# @param [String] document_type the DocumentType slug or ID
# @param [String] q the query that generated the search result
# @param [String] id the external_id or ID of the Document
def log_clickthrough(engine_id, document_type, q, id)
post(
"engines/#{engine_id}/document_types/#{document_type}/analytics/log_clickthrough.json",
{:q => q, :id => id}
)
end
end
include Elastic::SiteSearch::Client::User
include Elastic::SiteSearch::Client::Search
include Elastic::SiteSearch::Client::Engine
include Elastic::SiteSearch::Client::DocumentType
include Elastic::SiteSearch::Client::Document
include Elastic::SiteSearch::Client::Analytics
include Elastic::SiteSearch::Client::Domain
include Elastic::SiteSearch::Client::Clickthrough
end
end
end