mirror of
https://github.com/alchemy-fr/Phraseanet.git
synced 2025-10-24 02:13:15 +00:00
Refactor facets handling
This commit is contained in:
@@ -183,9 +183,9 @@ class Query implements ControllerProviderInterface
|
||||
|
||||
/** Debug */
|
||||
$json['parsed_query'] = $result->getQuery();
|
||||
$json['aggregations'] = $result->getAggregations();
|
||||
/** End debug */
|
||||
|
||||
$json['facets'] = $result->getFacets();
|
||||
$json['phrasea_props'] = $proposals;
|
||||
$json['total_answers'] = (int) $result->getAvailable();
|
||||
$json['next_page'] = ($page < $npages && $result->getAvailable() > 0) ? ($page + 1) : false;
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Alchemy\Phrasea\SearchEngine\Elastic;
|
||||
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\RecordIndexer;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\TermIndexer;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Search\FacetsResponse;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
||||
use Alchemy\Phrasea\SearchEngine\SearchEngineInterface;
|
||||
use Alchemy\Phrasea\SearchEngine\SearchEngineOptions;
|
||||
@@ -291,6 +292,8 @@ class ElasticSearchEngine implements SearchEngineInterface
|
||||
$results[] = ElasticsearchRecordHydrator::hydrate($hit['_source'], $n++);
|
||||
}
|
||||
|
||||
$facets = new FacetsResponse($res);
|
||||
|
||||
$query['ast'] = $this->app['query_parser']->parse($string)->dump();
|
||||
$query['query_main'] = $recordQuery;
|
||||
$query['query'] = $params['body'];
|
||||
@@ -298,7 +301,7 @@ class ElasticSearchEngine implements SearchEngineInterface
|
||||
|
||||
return new SearchEngineResult($results, json_encode($query), $res['took'], $offset,
|
||||
$res['hits']['total'], $res['hits']['total'], null, null, $suggestions, [],
|
||||
$this->indexName, isset($res['aggregations']) ? $res['aggregations'] : []);
|
||||
$this->indexName, $facets);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -419,9 +422,13 @@ class ElasticSearchEngine implements SearchEngineInterface
|
||||
// filter aggregation to allowed databoxes
|
||||
// declare aggregation on current field
|
||||
$agg = array();
|
||||
// array_values is needed to ensure array serialization
|
||||
$agg['filter']['terms']['databox_id'] = array_values($databoxes);
|
||||
$agg['aggs']['distinct_occurrence']['terms']['field'] =
|
||||
// TODO (mdarse) Remove databox filtering. It's already done by the
|
||||
// ACL filter in the query scope, so no document that shouldn't be
|
||||
// displayed can go this far.
|
||||
// // array_values is needed to ensure array serialization
|
||||
// $agg['filter']['terms']['databox_id'] = array_values($databoxes);
|
||||
// $agg['aggs']['distinct_occurrence']['terms']['field'] =
|
||||
$agg['terms']['field'] =
|
||||
sprintf('%s.%s.raw', $prefix, $field_name);
|
||||
|
||||
$aggs[$field_name] = $agg;
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Phrasea\SearchEngine\Elastic\Search;
|
||||
|
||||
use Alchemy\Phrasea\Exception\RuntimeException;
|
||||
use JsonSerializable;
|
||||
|
||||
class FacetsResponse implements JsonSerializable
|
||||
{
|
||||
private $facets = array();
|
||||
|
||||
public function __construct(array $response)
|
||||
{
|
||||
if (!isset($response['aggregations'])) {
|
||||
return;
|
||||
}
|
||||
foreach ($response['aggregations'] as $name => $aggregation) {
|
||||
if (!isset($aggregation['buckets'])) {
|
||||
$this->throwAggregationResponseError();
|
||||
}
|
||||
$values = $this->buildBucketsValues($name, $aggregation['buckets']);
|
||||
if ($values) {
|
||||
$this->facets[] = array(
|
||||
'name' => $name,
|
||||
'values' => $values,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function buildBucketsValues($name, $buckets)
|
||||
{
|
||||
$values = array();
|
||||
foreach ($buckets as $bucket) {
|
||||
if (!isset($bucket['key']) || !isset($bucket['doc_count'])) {
|
||||
$this->throwAggregationResponseError();
|
||||
}
|
||||
$values[] = array(
|
||||
'value' => $bucket['key'],
|
||||
'count' => $bucket['doc_count'],
|
||||
'query' => $this->buildQuery($name, $bucket['key']),
|
||||
);
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
private function buildQuery($name, $value)
|
||||
{
|
||||
// Strip double quotes from values to prevent broken queries
|
||||
$value = str_replace('/"/u', ' ', $value);
|
||||
// TODO escape value when escaping is supported in query parser
|
||||
return ($name === 'Collection') ?
|
||||
sprintf('collection:"%s"', $value) :
|
||||
sprintf('"%s" IN %s', $value, $name);
|
||||
}
|
||||
|
||||
private function throwAggregationResponseError()
|
||||
{
|
||||
throw new RuntimeException('Invalid aggregation response');
|
||||
}
|
||||
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->facets;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace Alchemy\Phrasea\SearchEngine;
|
||||
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Search\FacetsResponse;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
class SearchEngineResult
|
||||
@@ -26,10 +27,10 @@ class SearchEngineResult
|
||||
protected $suggestions;
|
||||
protected $propositions;
|
||||
protected $indexes;
|
||||
protected $aggregations;
|
||||
protected $facets;
|
||||
|
||||
public function __construct(ArrayCollection $results, $query, $duration, $offsetStart, $available, $total, $error,
|
||||
$warning, ArrayCollection $suggestions, $propositions, $indexes, $aggregations = array())
|
||||
$warning, ArrayCollection $suggestions, $propositions, $indexes, FacetsResponse $facets = null)
|
||||
{
|
||||
$this->results = $results;
|
||||
$this->query = $query;
|
||||
@@ -42,7 +43,7 @@ class SearchEngineResult
|
||||
$this->suggestions = $suggestions;
|
||||
$this->propositions = $propositions;
|
||||
$this->indexes = $indexes;
|
||||
$this->aggregations = $aggregations;
|
||||
$this->facets = $facets;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -179,8 +180,8 @@ class SearchEngineResult
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAggregations()
|
||||
public function getFacets()
|
||||
{
|
||||
return $this->aggregations;
|
||||
return $this->facets;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,100 +464,12 @@ function initAnswerForm() {
|
||||
success: function (datas) {
|
||||
|
||||
// DEBUG QUERY PARSER
|
||||
var query = datas.parsed_query;
|
||||
try {
|
||||
query = JSON.parse(query);
|
||||
}
|
||||
catch (e) {}
|
||||
console.info(query);
|
||||
console.info(JSON.parse(datas.parsed_query));
|
||||
|
||||
var aggs = datas.aggregations;
|
||||
try {
|
||||
aggs = JSON.parse(aggs);
|
||||
}
|
||||
catch (e) {}
|
||||
|
||||
console.debug('Aggregations:');
|
||||
var toDisplay = [];
|
||||
_.each(aggs, function(field_aggs, key) {
|
||||
_.each(field_aggs, function(value) {
|
||||
_.each(value.buckets, function(bucket, keyBis) {
|
||||
if (!toDisplay[keyBis]) { toDisplay[keyBis] = {}; }
|
||||
toDisplay[keyBis][key] = bucket.key + ' ('+ bucket.doc_count + ')';
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
console.table(toDisplay);
|
||||
|
||||
var treeData = [];
|
||||
_.each(aggs, function(field_aggs, key) {
|
||||
var entry = {
|
||||
"title" : key,
|
||||
"key": key,
|
||||
"folder": true,
|
||||
"children" : []
|
||||
};
|
||||
|
||||
if(field_aggs.hasOwnProperty('buckets')){
|
||||
_.each(field_aggs.buckets, function(bucket) {
|
||||
entry.children.push({
|
||||
"title": bucket.key + ' ('+ bucket.doc_count + ')',
|
||||
"key": bucket.key,
|
||||
"query": '"'+ bucket.key + '" IN ' + key
|
||||
});
|
||||
});
|
||||
} else {
|
||||
_.each(field_aggs, function (agg) {
|
||||
_.each(agg.buckets, function(bucket) {
|
||||
entry.children.push({
|
||||
"title": bucket.key + ' ('+ bucket.doc_count + ')',
|
||||
"key": bucket.key,
|
||||
"query": '"'+ bucket.key + '" IN ' + key
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
treeData.push(entry);
|
||||
});
|
||||
|
||||
$('#answers').empty().append(datas.results).removeClass('loading');
|
||||
|
||||
var $tree = $("#proposals");
|
||||
|
||||
if ($tree.data("ui-fancytree")) {
|
||||
$tree.fancytree("destroy");
|
||||
}
|
||||
|
||||
if (treeData.length > 0) {
|
||||
$tree.fancytree({
|
||||
source: treeData,
|
||||
activate: function(event, data){
|
||||
var node = data.node;
|
||||
if (typeof node.data.query === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
var $input = $('form[name="phrasea_query"] input[name="qry"]');
|
||||
var current_query = $input.val();
|
||||
var query = node.data.query;
|
||||
|
||||
if (current_query != '') {
|
||||
query = '('+current_query+') AND ('+query+')';
|
||||
}
|
||||
$input.val(query);
|
||||
|
||||
checkFilters();
|
||||
newSearch();
|
||||
$('searchForm').trigger('submit');
|
||||
}
|
||||
});
|
||||
|
||||
$tree.fancytree("getRootNode").visit(function(node){
|
||||
node.setExpanded(true);
|
||||
});
|
||||
}
|
||||
loadFacets(datas.facets);
|
||||
|
||||
$("#answers img.lazyload").lazyload({
|
||||
container: $('#answers')
|
||||
@@ -600,6 +512,63 @@ function initAnswerForm() {
|
||||
searchForm.removeClass('triggerAfterInit').trigger('submit');
|
||||
}
|
||||
}
|
||||
|
||||
function loadFacets(facets) {
|
||||
// Convert facets data to fancytree source format
|
||||
var treeSource = _.map(facets, function(facet) {
|
||||
// Values
|
||||
var values = _.map(facet.values, function(value) {
|
||||
return {
|
||||
title: value.value + ' (' + value.count + ')',
|
||||
query: value.query
|
||||
}
|
||||
});
|
||||
// Facet
|
||||
return {
|
||||
title: facet.name,
|
||||
folder: true,
|
||||
children: values,
|
||||
expanded: true
|
||||
};
|
||||
});
|
||||
return getFacetsTree().reload(treeSource);
|
||||
}
|
||||
|
||||
function getFacetsTree() {
|
||||
var $facetsTree = $('#proposals');
|
||||
if (!$facetsTree.data('ui-fancytree')) {
|
||||
$facetsTree.fancytree({
|
||||
source: [],
|
||||
activate: function(event, data){
|
||||
var query = data.node.data.query;
|
||||
if (!query) return;
|
||||
facetCombinedSearch(query);
|
||||
}
|
||||
});
|
||||
}
|
||||
return $facetsTree.fancytree('getTree');
|
||||
}
|
||||
|
||||
var $searchForm;
|
||||
var $searchInput;
|
||||
var $facetsBackButton;
|
||||
|
||||
function facetSearch(query) {
|
||||
var currentQuery = $searchInput.val();
|
||||
if (currentQuery) {
|
||||
query = '(' + currentQuery + ') AND (' + query + ')';
|
||||
}
|
||||
checkFilters();
|
||||
newSearch();
|
||||
$searchInput.val(query);
|
||||
$searchForm.trigger('submit');
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$searchForm = $('#searchForm');
|
||||
$searchInput = $searchForm.find('input[name="qry"]');
|
||||
});
|
||||
|
||||
function answerSizer() {
|
||||
var el = $('#idFrameC').outerWidth();
|
||||
if (!$.support.cssFloat) {
|
||||
|
||||
Reference in New Issue
Block a user