mirror of
https://github.com/alchemy-fr/Phraseanet.git
synced 2025-10-23 18:03:17 +00:00
Merge pull request #3301 from alchemy-fr/PHRAS-2879_facet-order_4.1
PHRAS-2879 #comment Final fusion of the Define facets order in the query result, this PR also includes the PR #3321
This commit is contained in:
@@ -28,7 +28,8 @@ main:
|
|||||||
port: 11211
|
port: 11211
|
||||||
search-engine:
|
search-engine:
|
||||||
type: phrasea
|
type: phrasea
|
||||||
options: []
|
options:
|
||||||
|
facets: []
|
||||||
task-manager:
|
task-manager:
|
||||||
status: started
|
status: started
|
||||||
enabled: true
|
enabled: true
|
||||||
|
@@ -13,9 +13,11 @@ namespace Alchemy\Phrasea\Controller\Admin;
|
|||||||
use Alchemy\Phrasea\Controller\Controller;
|
use Alchemy\Phrasea\Controller\Controller;
|
||||||
use Alchemy\Phrasea\SearchEngine\Elastic\ElasticsearchSettingsFormType;
|
use Alchemy\Phrasea\SearchEngine\Elastic\ElasticsearchSettingsFormType;
|
||||||
use Alchemy\Phrasea\SearchEngine\Elastic\ElasticsearchOptions;
|
use Alchemy\Phrasea\SearchEngine\Elastic\ElasticsearchOptions;
|
||||||
|
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\GlobalStructure;
|
||||||
use Symfony\Component\Form\FormInterface;
|
use Symfony\Component\Form\FormInterface;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use databox_descriptionStructure;
|
||||||
|
|
||||||
class SearchEngineController extends Controller
|
class SearchEngineController extends Controller
|
||||||
{
|
{
|
||||||
@@ -31,7 +33,19 @@ class SearchEngineController extends Controller
|
|||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
if ($form->isValid()) {
|
if ($form->isValid()) {
|
||||||
$this->saveElasticSearchOptions($form->getData());
|
/** @var ElasticsearchOptions $data */
|
||||||
|
$data = $form->getData();
|
||||||
|
// $q = $request->request->get('elasticsearch_settings');
|
||||||
|
$facetNames = []; // rebuild the data "_customValues/facets" list following the form order
|
||||||
|
foreach($request->request->get('elasticsearch_settings') as $name=>$value) {
|
||||||
|
$matches = null;
|
||||||
|
if(preg_match('/^facets:(.+):limit$/', $name, $matches) === 1) {
|
||||||
|
$facetNames[] = $matches[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$data->reorderAggregableFields($facetNames);
|
||||||
|
|
||||||
|
$this->saveElasticSearchOptions($data);
|
||||||
|
|
||||||
return $this->app->redirectPath('admin_searchengine_form');
|
return $this->app->redirectPath('admin_searchengine_form');
|
||||||
}
|
}
|
||||||
@@ -76,6 +90,16 @@ class SearchEngineController extends Controller
|
|||||||
*/
|
*/
|
||||||
private function saveElasticSearchOptions(ElasticsearchOptions $configuration)
|
private function saveElasticSearchOptions(ElasticsearchOptions $configuration)
|
||||||
{
|
{
|
||||||
|
// save to databoxes fields for backward compatibility (useless ?)
|
||||||
|
foreach($configuration->getAggregableFields() as $fname=>$aggregableField) {
|
||||||
|
foreach ($this->app->getDataboxes() as $databox) {
|
||||||
|
if(!is_null($f = $databox->get_meta_structure()->get_element_by_name($fname, databox_descriptionStructure::STRICT_COMPARE))) {
|
||||||
|
$f->set_aggregable($aggregableField['limit'])->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// save to conf
|
||||||
$this->getConf()->set(['main', 'search-engine', 'options'], $configuration->toArray());
|
$this->getConf()->set(['main', 'search-engine', 'options'], $configuration->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +109,10 @@ class SearchEngineController extends Controller
|
|||||||
*/
|
*/
|
||||||
private function getConfigurationForm(ElasticsearchOptions $options)
|
private function getConfigurationForm(ElasticsearchOptions $options)
|
||||||
{
|
{
|
||||||
return $this->app->form(new ElasticsearchSettingsFormType(), $options, [
|
/** @var GlobalStructure $g */
|
||||||
|
$g = $this->app['search_engine.structure'];
|
||||||
|
|
||||||
|
return $this->app->form(new ElasticsearchSettingsFormType($g, $options), $options, [
|
||||||
'action' => $this->app->url('admin_searchengine_form'),
|
'action' => $this->app->url('admin_searchengine_form'),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@@ -433,24 +433,15 @@ class QueryController extends Controller
|
|||||||
|
|
||||||
// populates facets (aggregates)
|
// populates facets (aggregates)
|
||||||
$facets = [];
|
$facets = [];
|
||||||
// $facetClauses = [];
|
|
||||||
foreach ($result->getFacets() as $facet) {
|
foreach ($result->getFacets() as $facet) {
|
||||||
$facetName = $facet['name'];
|
$facetName = $facet['name'];
|
||||||
|
|
||||||
if(array_key_exists($facetName, $fieldsInfosByName)) {
|
if(array_key_exists($facetName, $fieldsInfosByName)) {
|
||||||
|
|
||||||
$f = $fieldsInfosByName[$facetName];
|
$f = $fieldsInfosByName[$facetName];
|
||||||
|
|
||||||
$facet['label'] = $f['trans_label'];
|
$facet['label'] = $f['trans_label'];
|
||||||
$facet['labels'] = $f['labels'];
|
$facet['labels'] = $f['labels'];
|
||||||
$facet['type'] = strtoupper($f['type']) . "-AGGREGATE";
|
$facet['type'] = strtoupper($f['type']) . "-AGGREGATE";
|
||||||
$facets[] = $facet;
|
$facets[] = $facet;
|
||||||
|
|
||||||
// $facetClauses[] = [
|
|
||||||
// 'type' => strtoupper($f['type']) . "-AGGREGATE",
|
|
||||||
// 'field' => $f['field'],
|
|
||||||
// 'facet' => $facet
|
|
||||||
// ];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -97,7 +97,7 @@ class SearchEngineServiceProvider implements ServiceProviderInterface
|
|||||||
});
|
});
|
||||||
|
|
||||||
$app['elasticsearch.facets_response.factory'] = $app->protect(function (array $response) use ($app) {
|
$app['elasticsearch.facets_response.factory'] = $app->protect(function (array $response) use ($app) {
|
||||||
return new FacetsResponse(new Escaper(), $response, $app['search_engine.structure']);
|
return new FacetsResponse($app['elasticsearch.options'], new Escaper(), $response, $app['search_engine.structure']);
|
||||||
});
|
});
|
||||||
|
|
||||||
return $app;
|
return $app;
|
||||||
@@ -228,7 +228,8 @@ class SearchEngineServiceProvider implements ServiceProviderInterface
|
|||||||
});
|
});
|
||||||
|
|
||||||
$app['elasticsearch.options'] = $app->share(function ($app) {
|
$app['elasticsearch.options'] = $app->share(function ($app) {
|
||||||
$options = ElasticsearchOptions::fromArray($app['conf']->get(['main', 'search-engine', 'options'], []));
|
$conf = $app['conf']->get(['main', 'search-engine', 'options'], []);
|
||||||
|
$options = ElasticsearchOptions::fromArray($conf);
|
||||||
|
|
||||||
if (empty($options->getIndexName())) {
|
if (empty($options->getIndexName())) {
|
||||||
$options->setIndexName(strtolower(sprintf('phraseanet_%s', str_replace(
|
$options->setIndexName(strtolower(sprintf('phraseanet_%s', str_replace(
|
||||||
|
@@ -16,7 +16,7 @@ class Version
|
|||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
private $number = '4.1.0-alpha.20a';
|
private $number = '4.1.0-alpha.21a';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
|
@@ -668,17 +668,20 @@ class ElasticSearchEngine implements SearchEngineInterface
|
|||||||
}
|
}
|
||||||
// fields aggregates
|
// fields aggregates
|
||||||
$structure = $this->context_factory->getLimitedStructure($options);
|
$structure = $this->context_factory->getLimitedStructure($options);
|
||||||
foreach ($structure->getFacetFields() as $name => $field) {
|
foreach($structure->getAllFields() as $name => $field) {
|
||||||
// 2015-05-26 (mdarse) Removed databox filtering.
|
$size = $this->options->getAggregableFieldLimit($name);
|
||||||
// It was already done by the ACL filter in the query scope, so no
|
if ($size !== databox_field::FACET_DISABLED) {
|
||||||
// document that shouldn't be displayed can go this far.
|
if ($size === databox_field::FACET_NO_LIMIT) {
|
||||||
$agg = [
|
$size = ESField::FACET_NO_LIMIT;
|
||||||
'terms' => [
|
}
|
||||||
'field' => $field->getIndexField(true),
|
$agg = [
|
||||||
'size' => $field->getFacetValuesLimit()
|
'terms' => [
|
||||||
]
|
'field' => $field->getIndexField(true),
|
||||||
];
|
'size' => $size
|
||||||
$aggs[$name] = AggregationHelper::wrapPrivateFieldAggregation($field, $agg);
|
]
|
||||||
|
];
|
||||||
|
$aggs[$name] = AggregationHelper::wrapPrivateFieldAggregation($field, $agg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $aggs;
|
return $aggs;
|
||||||
|
@@ -9,6 +9,10 @@
|
|||||||
*/
|
*/
|
||||||
namespace Alchemy\Phrasea\SearchEngine\Elastic;
|
namespace Alchemy\Phrasea\SearchEngine\Elastic;
|
||||||
|
|
||||||
|
use databox_field;
|
||||||
|
use igorw;
|
||||||
|
|
||||||
|
|
||||||
class ElasticsearchOptions
|
class ElasticsearchOptions
|
||||||
{
|
{
|
||||||
const POPULATE_ORDER_RID = "RECORD_ID";
|
const POPULATE_ORDER_RID = "RECORD_ID";
|
||||||
@@ -35,7 +39,7 @@ class ElasticsearchOptions
|
|||||||
private $populateDirection;
|
private $populateDirection;
|
||||||
|
|
||||||
/** @var int[] */
|
/** @var int[] */
|
||||||
private $_customValues;
|
private $_customValues = [];
|
||||||
private $activeTab;
|
private $activeTab;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,14 +61,10 @@ class ElasticsearchOptions
|
|||||||
'populate_order' => self::POPULATE_ORDER_RID,
|
'populate_order' => self::POPULATE_ORDER_RID,
|
||||||
'populate_direction' => self::POPULATE_DIRECTION_DESC,
|
'populate_direction' => self::POPULATE_DIRECTION_DESC,
|
||||||
'activeTab' => null,
|
'activeTab' => null,
|
||||||
|
'facets' => []
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach(self::getAggregableTechnicalFields() as $k => $f) {
|
|
||||||
$defaultOptions[$k.'_limit'] = 0;
|
|
||||||
}
|
|
||||||
$options = array_replace($defaultOptions, $options);
|
$options = array_replace($defaultOptions, $options);
|
||||||
|
|
||||||
|
|
||||||
$self = new self();
|
$self = new self();
|
||||||
$self->setHost($options['host']);
|
$self->setHost($options['host']);
|
||||||
$self->setPort($options['port']);
|
$self->setPort($options['port']);
|
||||||
@@ -76,11 +76,10 @@ class ElasticsearchOptions
|
|||||||
$self->setPopulateOrder($options['populate_order']);
|
$self->setPopulateOrder($options['populate_order']);
|
||||||
$self->setPopulateDirection($options['populate_direction']);
|
$self->setPopulateDirection($options['populate_direction']);
|
||||||
$self->setActiveTab($options['activeTab']);
|
$self->setActiveTab($options['activeTab']);
|
||||||
foreach(self::getAggregableTechnicalFields() as $k => $f) {
|
foreach($options['facets'] as $fieldname=>$attributes) {
|
||||||
$self->setAggregableFieldLimit($k, $options[$k.'_limit']);
|
$self->setAggregableField($fieldname, $attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return $self;
|
return $self;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,10 +98,11 @@ class ElasticsearchOptions
|
|||||||
'highlight' => $this->highlight,
|
'highlight' => $this->highlight,
|
||||||
'populate_order' => $this->populateOrder,
|
'populate_order' => $this->populateOrder,
|
||||||
'populate_direction' => $this->populateDirection,
|
'populate_direction' => $this->populateDirection,
|
||||||
'activeTab' => $this->activeTab
|
'activeTab' => $this->activeTab,
|
||||||
|
'facets' => []
|
||||||
];
|
];
|
||||||
foreach(self::getAggregableTechnicalFields() as $k => $f) {
|
foreach($this->getAggregableFields() as $fieldname=>$attributes) {
|
||||||
$ret[$k.'_limit'] = $this->getAggregableFieldLimit($k);
|
$ret['facets'][$fieldname] = $attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $ret;
|
return $ret;
|
||||||
@@ -222,12 +222,51 @@ class ElasticsearchOptions
|
|||||||
|
|
||||||
public function setAggregableFieldLimit($key, $value)
|
public function setAggregableFieldLimit($key, $value)
|
||||||
{
|
{
|
||||||
$this->_customValues[$key.'_limit'] = $value;
|
if(is_null($this->getAggregableField($key))) {
|
||||||
|
$this->_customValues['facets'][$key] = [];
|
||||||
|
}
|
||||||
|
$this->_customValues['facets'][$key]['limit'] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAggregableField($key, $attributes)
|
||||||
|
{
|
||||||
|
$this->getAggregableFields(); // ensure facets exists
|
||||||
|
$this->_customValues['facets'][$key] = $attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAggregableFieldLimit($key)
|
public function getAggregableFieldLimit($key)
|
||||||
{
|
{
|
||||||
return $this->_customValues[$key.'_limit'];
|
$facet = $this->getAggregableField($key);
|
||||||
|
return (is_array($facet) && array_key_exists('limit', $facet)) ? $facet['limit'] : databox_field::FACET_DISABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAggregableField($key)
|
||||||
|
{
|
||||||
|
$facets = $this->getAggregableFields();
|
||||||
|
return array_key_exists($key, $facets) ? $facets[$key] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getAggregableFields()
|
||||||
|
{
|
||||||
|
if(!array_key_exists('facets', $this->_customValues) || !is_array($this->_customValues['facets'])) {
|
||||||
|
$this->_customValues['facets'] = [];
|
||||||
|
}
|
||||||
|
return $this->_customValues['facets'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// set to change the facets order during admin/form save
|
||||||
|
public function reorderAggregableFields($facetNames)
|
||||||
|
{
|
||||||
|
$newFacets = [];
|
||||||
|
foreach ($facetNames as $name) {
|
||||||
|
if(($facet = $this->getAggregableField($name)) !== null) {
|
||||||
|
$newFacets[$name] = $facet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->_customValues['facets'] = $newFacets;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getActiveTab()
|
public function getActiveTab()
|
||||||
@@ -241,56 +280,56 @@ class ElasticsearchOptions
|
|||||||
|
|
||||||
public function __get($key)
|
public function __get($key)
|
||||||
{
|
{
|
||||||
if(!array_key_exists($key, $this->_customValues)) {
|
$keys = explode(':', $key);
|
||||||
$this->_customValues[$key] = 0;
|
|
||||||
}
|
return igorw\get_in($this->_customValues, $keys);
|
||||||
return $this->_customValues[$key];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __set($key, $value)
|
public function __set($key, $value)
|
||||||
{
|
{
|
||||||
$this->_customValues[$key] = $value;
|
$keys = explode(':', $key);
|
||||||
|
$this->_customValues = igorw\assoc_in($this->_customValues, $keys, $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getAggregableTechnicalFields()
|
public static function getAggregableTechnicalFields()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'base_aggregate' => [
|
'_base' => [
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'label' => 'prod::facet:base_label',
|
'label' => 'prod::facet:base_label',
|
||||||
'field' => "database",
|
'field' => "database",
|
||||||
'esfield' => 'databox_name',
|
'esfield' => 'databox_name',
|
||||||
'query' => 'database:%s',
|
'query' => 'database:%s',
|
||||||
],
|
],
|
||||||
'collection_aggregate' => [
|
'_collection' => [
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'label' => 'prod::facet:collection_label',
|
'label' => 'prod::facet:collection_label',
|
||||||
'field' => "collection",
|
'field' => "collection",
|
||||||
'esfield' => 'collection_name',
|
'esfield' => 'collection_name',
|
||||||
'query' => 'collection:%s',
|
'query' => 'collection:%s',
|
||||||
],
|
],
|
||||||
'doctype_aggregate' => [
|
'_doctype' => [
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'label' => 'prod::facet:doctype_label',
|
'label' => 'prod::facet:doctype_label',
|
||||||
'field' => "type",
|
'field' => "type",
|
||||||
'esfield' => 'type',
|
'esfield' => 'type',
|
||||||
'query' => 'type:%s',
|
'query' => 'type:%s',
|
||||||
],
|
],
|
||||||
'camera_model_aggregate' => [
|
'_camera_model' => [
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'label' => 'Camera Model',
|
'label' => 'Camera Model',
|
||||||
'field' => "meta.CameraModel",
|
'field' => "meta.CameraModel",
|
||||||
'esfield' => 'metadata_tags.CameraModel',
|
'esfield' => 'metadata_tags.CameraModel',
|
||||||
'query' => 'meta.CameraModel:%s',
|
'query' => 'meta.CameraModel:%s',
|
||||||
],
|
],
|
||||||
'iso_aggregate' => [
|
'_iso' => [
|
||||||
'type' => 'number',
|
'type' => 'number',
|
||||||
'label' => 'ISO',
|
'label' => 'ISO',
|
||||||
'field' => "meta.ISO",
|
'field' => "meta.ISO",
|
||||||
'esfield' => 'metadata_tags.ISO',
|
'esfield' => 'metadata_tags.ISO',
|
||||||
'query' => 'meta.ISO=%s',
|
'query' => 'meta.ISO=%s',
|
||||||
],
|
],
|
||||||
'aperture_aggregate' => [
|
'_aperture' => [
|
||||||
'type' => 'number',
|
'type' => 'number',
|
||||||
'label' => 'Aperture',
|
'label' => 'Aperture',
|
||||||
'field' => "meta.Aperture",
|
'field' => "meta.Aperture",
|
||||||
@@ -300,7 +339,7 @@ class ElasticsearchOptions
|
|||||||
return round($value, 1);
|
return round($value, 1);
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'shutterspeed_aggregate' => [
|
'_shutterspeed' => [
|
||||||
'type' => 'number',
|
'type' => 'number',
|
||||||
'label' => 'Shutter speed',
|
'label' => 'Shutter speed',
|
||||||
'field' => "meta.ShutterSpeed",
|
'field' => "meta.ShutterSpeed",
|
||||||
@@ -313,7 +352,7 @@ class ElasticsearchOptions
|
|||||||
return $value . ' s.';
|
return $value . ' s.';
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'flashfired_aggregate' => [
|
'_flashfired' => [
|
||||||
'type' => 'boolean',
|
'type' => 'boolean',
|
||||||
'label' => 'FlashFired',
|
'label' => 'FlashFired',
|
||||||
'field' => "meta.FlashFired",
|
'field' => "meta.FlashFired",
|
||||||
@@ -327,49 +366,49 @@ class ElasticsearchOptions
|
|||||||
return array_key_exists($value, $map) ? $map[$value] : $value;
|
return array_key_exists($value, $map) ? $map[$value] : $value;
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'framerate_aggregate' => [
|
'_framerate' => [
|
||||||
'type' => 'number',
|
'type' => 'number',
|
||||||
'label' => 'FrameRate',
|
'label' => 'FrameRate',
|
||||||
'field' => "meta.FrameRate",
|
'field' => "meta.FrameRate",
|
||||||
'esfield' => 'metadata_tags.FrameRate',
|
'esfield' => 'metadata_tags.FrameRate',
|
||||||
'query' => 'meta.FrameRate=%s',
|
'query' => 'meta.FrameRate=%s',
|
||||||
],
|
],
|
||||||
'audiosamplerate_aggregate' => [
|
'_audiosamplerate' => [
|
||||||
'type' => 'number',
|
'type' => 'number',
|
||||||
'label' => 'Audio Samplerate',
|
'label' => 'Audio Samplerate',
|
||||||
'field' => "meta.AudioSamplerate",
|
'field' => "meta.AudioSamplerate",
|
||||||
'esfield' => 'metadata_tags.AudioSamplerate',
|
'esfield' => 'metadata_tags.AudioSamplerate',
|
||||||
'query' => 'meta.AudioSamplerate=%s',
|
'query' => 'meta.AudioSamplerate=%s',
|
||||||
],
|
],
|
||||||
'videocodec_aggregate' => [
|
'_videocodec' => [
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'label' => 'Video codec',
|
'label' => 'Video codec',
|
||||||
'field' => "meta.VideoCodec",
|
'field' => "meta.VideoCodec",
|
||||||
'esfield' => 'metadata_tags.VideoCodec',
|
'esfield' => 'metadata_tags.VideoCodec',
|
||||||
'query' => 'meta.VideoCodec:%s',
|
'query' => 'meta.VideoCodec:%s',
|
||||||
],
|
],
|
||||||
'audiocodec_aggregate' => [
|
'_audiocodec' => [
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'label' => 'Audio codec',
|
'label' => 'Audio codec',
|
||||||
'field' => "meta.AudioCodec",
|
'field' => "meta.AudioCodec",
|
||||||
'esfield' => 'metadata_tags.AudioCodec',
|
'esfield' => 'metadata_tags.AudioCodec',
|
||||||
'query' => 'meta.AudioCodec:%s',
|
'query' => 'meta.AudioCodec:%s',
|
||||||
],
|
],
|
||||||
'orientation_aggregate' => [
|
'_orientation' => [
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'label' => 'Orientation',
|
'label' => 'Orientation',
|
||||||
'field' => "meta.Orientation",
|
'field' => "meta.Orientation",
|
||||||
'esfield' => 'metadata_tags.Orientation',
|
'esfield' => 'metadata_tags.Orientation',
|
||||||
'query' => 'meta.Orientation=%s',
|
'query' => 'meta.Orientation=%s',
|
||||||
],
|
],
|
||||||
'colorspace_aggregate' => [
|
'_colorspace' => [
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'label' => 'Colorspace',
|
'label' => 'Colorspace',
|
||||||
'field' => "meta.ColorSpace",
|
'field' => "meta.ColorSpace",
|
||||||
'esfield' => 'metadata_tags.ColorSpace',
|
'esfield' => 'metadata_tags.ColorSpace',
|
||||||
'query' => 'meta.ColorSpace:%s',
|
'query' => 'meta.ColorSpace:%s',
|
||||||
],
|
],
|
||||||
'mimetype_aggregate' => [
|
'_mimetype' => [
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'label' => 'MimeType',
|
'label' => 'MimeType',
|
||||||
'field' => "meta.MimeType",
|
'field' => "meta.MimeType",
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
namespace Alchemy\Phrasea\SearchEngine\Elastic;
|
namespace Alchemy\Phrasea\SearchEngine\Elastic;
|
||||||
|
|
||||||
|
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\GlobalStructure;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
@@ -17,6 +18,18 @@ use Symfony\Component\Validator\Constraints\Range;
|
|||||||
|
|
||||||
class ElasticsearchSettingsFormType extends AbstractType
|
class ElasticsearchSettingsFormType extends AbstractType
|
||||||
{
|
{
|
||||||
|
/** @var GlobalStructure */
|
||||||
|
private $globalStructure;
|
||||||
|
|
||||||
|
/** @var ElasticsearchOptions */
|
||||||
|
private $esSettings;
|
||||||
|
|
||||||
|
public function __construct(GlobalStructure $g, ElasticsearchOptions $settings)
|
||||||
|
{
|
||||||
|
$this->globalStructure = $g;
|
||||||
|
$this->esSettings = $settings;
|
||||||
|
}
|
||||||
|
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
{
|
{
|
||||||
$builder
|
$builder
|
||||||
@@ -56,59 +69,89 @@ class ElasticsearchSettingsFormType extends AbstractType
|
|||||||
->add('minScore', 'integer', [
|
->add('minScore', 'integer', [
|
||||||
'label' => 'Thesaurus Min score',
|
'label' => 'Thesaurus Min score',
|
||||||
'constraints' => new Range(['min' => 0]),
|
'constraints' => new Range(['min' => 0]),
|
||||||
]);
|
])
|
||||||
|
->add('highlight', 'checkbox', [
|
||||||
|
'label' => 'Activate highlight',
|
||||||
|
'required' => false
|
||||||
|
])
|
||||||
|
// ->add('save', 'submit', [
|
||||||
|
// 'attr' => ['class' => 'btn btn-primary']
|
||||||
|
// ])
|
||||||
|
->add('esSettingFromIndex', 'button', [
|
||||||
|
'label' => 'Get setting form index',
|
||||||
|
'attr' => [
|
||||||
|
'onClick' => 'esSettingFromIndex()',
|
||||||
|
'class' => 'btn'
|
||||||
|
]
|
||||||
|
])
|
||||||
|
->add('dumpField', 'textarea', [
|
||||||
|
'label' => false,
|
||||||
|
'required' => false,
|
||||||
|
'mapped' => false,
|
||||||
|
'attr' => ['class' => 'dumpfield hide']
|
||||||
|
])
|
||||||
|
->add('activeTab', 'hidden');
|
||||||
|
|
||||||
foreach(ElasticsearchOptions::getAggregableTechnicalFields() as $k => $f) {
|
// keep aggregates in configuration order with this intermediate array
|
||||||
if(array_key_exists('choices', $f)) {
|
$aggs = [];
|
||||||
// choices[] : choice_key => choice_value
|
|
||||||
$choices = $f['choices'];
|
// helper fct to add aggregate to a tmp list
|
||||||
}
|
$addAgg = function($k, $label, $help, $disabled=false, $choices=null) use (&$aggs) {
|
||||||
else {
|
if(!$choices) {
|
||||||
$choices = [
|
$choices = [
|
||||||
"10 values" => 10,
|
"10 values" => 10,
|
||||||
"20 values" => 20,
|
"50 values" => 50,
|
||||||
"50 values" => 50,
|
"100 values" => 100,
|
||||||
"100 values" => 100,
|
"all values" => -1
|
||||||
"all values" => -1
|
];
|
||||||
];
|
|
||||||
}
|
|
||||||
// array_unshift($choices, "not aggregated"); // always as first choice
|
|
||||||
$choices = array_merge(["not aggregated" => 0], $choices);
|
|
||||||
$builder
|
|
||||||
->add($k.'_limit', ChoiceType::class, [
|
|
||||||
// 'label' => $f['label'],// . ' ' . 'aggregate limit',
|
|
||||||
'choices_as_values' => true,
|
|
||||||
'choices' => $choices,
|
|
||||||
'attr' => [
|
|
||||||
'class' => 'aggregate'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
$choices = array_merge(["not aggregated" => 0], $choices); // add this option always as first choice
|
||||||
|
$aggs[$k] = [ // default value will be replaced by hardcoded tech fields & all databoxes fields
|
||||||
|
'label' => $label,
|
||||||
|
'choices_as_values' => true,
|
||||||
|
'choices' => $choices,
|
||||||
|
'attr' => [
|
||||||
|
'class' => 'aggregate'
|
||||||
|
],
|
||||||
|
'disabled' => $disabled,
|
||||||
|
'help_message' => $help // todo : not displayed ?
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
$builder
|
// all fields fron conf
|
||||||
->add('highlight', 'checkbox', [
|
foreach($this->esSettings->getAggregableFields() as $k=>$f) {
|
||||||
'label' => 'Activate highlight',
|
// default value will be replaced by hardcoded tech fields & all databoxes fields
|
||||||
'required' => false
|
$addAgg($k, "/?\\ " . $k, "This field does not exists in current databoxes.", true);
|
||||||
])
|
}
|
||||||
// ->add('save', 'submit', [
|
|
||||||
// 'attr' => ['class' => 'btn btn-primary']
|
// add or replace hardcoded tech fields
|
||||||
// ])
|
foreach(ElasticsearchOptions::getAggregableTechnicalFields() as $k => $f) {
|
||||||
->add('esSettingFromIndex', 'button', [
|
$choices = array_key_exists('choices', $f) ? $f['choices'] : null; // a tech-field can publish it's own choices
|
||||||
'label' => 'Get setting form index',
|
$help = null;
|
||||||
'attr' => [
|
$label = '#' . $k;
|
||||||
'onClick' => 'esSettingFromIndex()',
|
if(!array_key_exists($k, $aggs)) {
|
||||||
'class' => 'btn'
|
$label = "/!\\ " . $label;
|
||||||
]
|
$help = "New field, please confirm setting.";
|
||||||
])
|
}
|
||||||
->add('dumpField', 'textarea', [
|
$addAgg($k, $label, $help, false, $choices);
|
||||||
'label' => false,
|
}
|
||||||
'required' => false,
|
|
||||||
'mapped' => false,
|
// add or replace all databoxes fields (nb: new db field - unknown in conf - will be a the end)
|
||||||
'attr' => ['class' => 'dumpfield hide']
|
foreach($this->globalStructure->getAllFields() as $field) {
|
||||||
])
|
$k = $label = $field->getName();
|
||||||
->add('activeTab', 'hidden');
|
$help = null;
|
||||||
|
if(!array_key_exists($field->getName(), $aggs)) {
|
||||||
|
$label = "/!\\ " . $label;
|
||||||
|
$help = "New field, please confirm setting.";
|
||||||
|
}
|
||||||
|
$addAgg($k, $label, $help); // default choices
|
||||||
|
}
|
||||||
|
|
||||||
|
// populate aggs to form
|
||||||
|
foreach($aggs as $k=>$agg) {
|
||||||
|
$builder->add('facets:' . $k . ':limit', ChoiceType::class, $agg);
|
||||||
|
}
|
||||||
|
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getName()
|
public function getName()
|
||||||
|
@@ -15,7 +15,7 @@ class FacetsResponse
|
|||||||
private $escaper;
|
private $escaper;
|
||||||
private $facets = array();
|
private $facets = array();
|
||||||
|
|
||||||
public function __construct(Escaper $escaper, array $response, GlobalStructure $structure)
|
public function __construct(ElasticsearchOptions $options, Escaper $escaper, array $response, GlobalStructure $structure)
|
||||||
{
|
{
|
||||||
$this->escaper = $escaper;
|
$this->escaper = $escaper;
|
||||||
|
|
||||||
@@ -25,7 +25,13 @@ class FacetsResponse
|
|||||||
|
|
||||||
$atf = ElasticsearchOptions::getAggregableTechnicalFields();
|
$atf = ElasticsearchOptions::getAggregableTechnicalFields();
|
||||||
|
|
||||||
foreach ($response['aggregations'] as $name => $aggregation) {
|
// sort facets respecting the order defined in options
|
||||||
|
foreach($options->getAggregableFields() as $name=>$foptions) {
|
||||||
|
if(!array_key_exists($name, $response['aggregations'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$aggregation = $response['aggregations'][$name];
|
||||||
|
|
||||||
$tf = null;
|
$tf = null;
|
||||||
$valueFormatter = function($v){ return $v; }; // default equality formatter
|
$valueFormatter = function($v){ return $v; }; // default equality formatter
|
||||||
|
|
||||||
@@ -78,6 +84,7 @@ class FacetsResponse
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -35,11 +35,6 @@ final class GlobalStructure implements Structure
|
|||||||
*/
|
*/
|
||||||
private $private = array();
|
private $private = array();
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Field[]
|
|
||||||
*/
|
|
||||||
private $facets = array();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Flag[]
|
* @var Flag[]
|
||||||
*/
|
*/
|
||||||
@@ -145,9 +140,11 @@ final class GlobalStructure implements Structure
|
|||||||
$this->private[$name] = $field;
|
$this->private[$name] = $field;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
if ($field->isFacet() && $field->isSearchable()) {
|
if ($field->isFacet() && $field->isSearchable()) {
|
||||||
$this->facets[$name] = $field;
|
$this->facets[$name] = $field;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
if ($field->hasConceptInference()) {
|
if ($field->hasConceptInference()) {
|
||||||
$this->thesaurus_fields[$name] = $field;
|
$this->thesaurus_fields[$name] = $field;
|
||||||
@@ -183,14 +180,6 @@ final class GlobalStructure implements Structure
|
|||||||
return $this->private;
|
return $this->private;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Field[]
|
|
||||||
*/
|
|
||||||
public function getFacetFields()
|
|
||||||
{
|
|
||||||
return $this->facets;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Field[]
|
* @return Field[]
|
||||||
*/
|
*/
|
||||||
|
@@ -47,14 +47,6 @@ final class LimitedStructure implements Structure
|
|||||||
return $this->limit($this->structure->getPrivateFields());
|
return $this->limit($this->structure->getPrivateFields());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Field[]
|
|
||||||
*/
|
|
||||||
public function getFacetFields()
|
|
||||||
{
|
|
||||||
return $this->limit($this->structure->getFacetFields());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getThesaurusEnabledFields()
|
public function getThesaurusEnabledFields()
|
||||||
{
|
{
|
||||||
return $this->limit($this->structure->getThesaurusEnabledFields());
|
return $this->limit($this->structure->getThesaurusEnabledFields());
|
||||||
|
@@ -33,11 +33,6 @@ interface Structure
|
|||||||
*/
|
*/
|
||||||
public function getPrivateFields();
|
public function getPrivateFields();
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Field[]
|
|
||||||
*/
|
|
||||||
public function getFacetFields();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Field[]
|
* @return Field[]
|
||||||
*/
|
*/
|
||||||
|
@@ -11,10 +11,10 @@
|
|||||||
|
|
||||||
use Alchemy\Phrasea\Application;
|
use Alchemy\Phrasea\Application;
|
||||||
|
|
||||||
class patch_410alpha20a implements patchInterface
|
class patch_410alpha21a implements patchInterface
|
||||||
{
|
{
|
||||||
/** @var string */
|
/** @var string */
|
||||||
private $release = '4.1.0-alpha.20a';
|
private $release = '4.1.0-alpha.21a';
|
||||||
|
|
||||||
/** @var array */
|
/** @var array */
|
||||||
private $concern = [base::DATA_BOX];
|
private $concern = [base::DATA_BOX];
|
@@ -238,14 +238,12 @@
|
|||||||
<label for="aggregable">{% trans %}Aggregation{% endtrans %}</label>
|
<label for="aggregable">{% trans %}Aggregation{% endtrans %}</label>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<select id="aggregable">
|
<%= field['aggregable'] == "0" ? '{% trans %}Not aggregated{% endtrans %}' : '' %>
|
||||||
<option <%= field['aggregable'] == "0" ? 'selected' : '' %> value='0'>{% trans %}Not aggregated{% endtrans %}</option>
|
<%= field['aggregable'] == "10" ? '10 values' : '' %>
|
||||||
<option <%= field['aggregable'] == "10" ? 'selected' : '' %> value='10'>10 values</option>
|
<%= field['aggregable'] == "20" ? '20 values' : '' %>
|
||||||
<option <%= field['aggregable'] == "20" ? 'selected' : '' %> value='20'>20 values</option>
|
<%= field['aggregable'] == "50" ? '50 values' : '' %>
|
||||||
<option <%= field['aggregable'] == "50" ? 'selected' : '' %> value='50'>50 values</option>
|
<%= field['aggregable'] == "100" ? '100 values' : '' %>
|
||||||
<option <%= field['aggregable'] == "100" ? 'selected' : '' %> value='100'>100 values</option>
|
<%= field['aggregable'] == "-1" ? 'All values' : '' %>
|
||||||
<option <%= field['aggregable'] == "-1" ? 'selected' : '' %> value='-1'>{% trans %}All values{% endtrans %}</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@@ -1,12 +1,37 @@
|
|||||||
<div class="general-aggregation-layout">
|
<div class="general-aggregation-layout">
|
||||||
|
<button type="submit" id="elasticsearch_settings_save_facets" name="elasticsearch_settings[save]"
|
||||||
|
class="btn btn-primary">{{ 'Save' | trans }}</button>
|
||||||
|
<div id="basket-filter" class="facet-filter unstyled" style="float: right; padding-top: 27px;">
|
||||||
|
{#<span class="filter-title">{{ 'See' | trans }} : </span>#}
|
||||||
|
<span class="filter-item">
|
||||||
|
<label id="aggregated-list" class="checkbox inline" for="basketFilter">
|
||||||
|
<input type="checkbox" class="checkbox " value="aggregated" checked
|
||||||
|
style="margin-top: 1px">
|
||||||
|
{{ 'Aggregated' | trans }}
|
||||||
|
</label>
|
||||||
|
</span>
|
||||||
|
<span class="filter-item" style="margin-left: 10px">
|
||||||
|
<label id="not-aggregated-list" class="checkbox inline" for="basketFilter">
|
||||||
|
<input type="checkbox" class="checkbox " value="not-aggregated" checked
|
||||||
|
style="margin-top: 1px">
|
||||||
|
{{ 'Not aggregated' | trans }}
|
||||||
|
</label>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
<ul class="unstyled aggregation-collection">
|
<ul class="unstyled aggregation-collection">
|
||||||
{% for formdata in form %}
|
{% for formdata in form %}
|
||||||
{% set attr = formdata.vars['attr']|join(',') %}
|
{% set attr = formdata.vars['attr']|join(',') %}
|
||||||
|
{% set label = formdata.vars['label']|join(',') %}
|
||||||
{% if attr == 'aggregate' %}
|
{% if attr == 'aggregate' %}
|
||||||
<li>
|
<li id="{{ label }}" class="field-row {% if label starts with '#_' %}lightblue{% endif %}">
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
|
<td class="handle">
|
||||||
|
<i class="fa fa-arrows" aria-hidden="true"></i>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ form_label(formdata, null, {
|
{{ form_label(formdata, null, {
|
||||||
'label_attr': {'class': 'label-aggregation'}
|
'label_attr': {'class': 'label-aggregation'}
|
||||||
@@ -22,6 +47,39 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
<button type="submit" id="elasticsearch_settings_save" name="elasticsearch_settings[save]"
|
<input type="hidden" id="listValues" name="facet_list_values"/>
|
||||||
class="btn btn-primary">{{ 'Save' | trans }}</button>
|
</div>
|
||||||
</div>
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
/*Add a sort for the list*/
|
||||||
|
function filterFacet() {
|
||||||
|
$.each($(".aggregate option:selected"), function () {
|
||||||
|
if ($(this).val() == 0) {
|
||||||
|
$(this).closest('.field-row').addClass('not-aggregated')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$(this).closest('.field-row').addClass('aggregated')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$('#aggregated-list input').click(function () {
|
||||||
|
$('.aggregated').toggleClass('hidden');
|
||||||
|
});
|
||||||
|
$('#not-aggregated-list input').click(function () {
|
||||||
|
$('.not-aggregated').toggleClass('hidden');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/*Get the order of new list*/
|
||||||
|
var sortEventHandler = function (event, ui) {
|
||||||
|
$('#listValues').val(JSON.stringify($('form').serializeArray()));
|
||||||
|
};
|
||||||
|
$(document).ready(function () {
|
||||||
|
$('.lightblue .label-aggregation').css('color','mediumblue');
|
||||||
|
$('.aggregation-collection').sortable({
|
||||||
|
handle: ".handle",
|
||||||
|
stop: sortEventHandler
|
||||||
|
});
|
||||||
|
// sortEventHandler(null, null); // enforce build list
|
||||||
|
filterFacet();
|
||||||
|
$("#elasticsearch_settings_save_facets").click(function(){sortEventHandler(null, null);});
|
||||||
|
});
|
||||||
|
</script>
|
@@ -20,7 +20,6 @@ class StructureTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEmpty($structure->getAllFields());
|
$this->assertEmpty($structure->getAllFields());
|
||||||
$this->assertEmpty($structure->getUnrestrictedFields());
|
$this->assertEmpty($structure->getUnrestrictedFields());
|
||||||
$this->assertEmpty($structure->getPrivateFields());
|
$this->assertEmpty($structure->getPrivateFields());
|
||||||
$this->assertEmpty($structure->getFacetFields());
|
|
||||||
$this->assertEmpty($structure->getThesaurusEnabledFields());
|
$this->assertEmpty($structure->getThesaurusEnabledFields());
|
||||||
$this->assertEmpty($structure->getDateFields());
|
$this->assertEmpty($structure->getDateFields());
|
||||||
}
|
}
|
||||||
@@ -95,19 +94,6 @@ class StructureTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertNotContains($unrestricted_field, $private_fields);
|
$this->assertNotContains($unrestricted_field, $private_fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetFacetFields()
|
|
||||||
{
|
|
||||||
$facet = new Field('foo', FieldMapping::TYPE_STRING, ['facet' => Field::FACET_NO_LIMIT]);
|
|
||||||
$not_facet = new Field('bar', FieldMapping::TYPE_STRING, ['facet' => Field::FACET_DISABLED]);
|
|
||||||
$structure = new Structure();
|
|
||||||
$structure->add($facet);
|
|
||||||
$this->assertContains($facet, $structure->getFacetFields());
|
|
||||||
$structure->add($not_facet);
|
|
||||||
$facet_fields = $structure->getFacetFields();
|
|
||||||
$this->assertContains($facet, $facet_fields);
|
|
||||||
$this->assertNotContains($not_facet, $facet_fields);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetDateFields()
|
public function testGetDateFields()
|
||||||
{
|
{
|
||||||
$string = new Field('foo', FieldMapping::TYPE_STRING);
|
$string = new Field('foo', FieldMapping::TYPE_STRING);
|
||||||
|
Reference in New Issue
Block a user