mirror of
https://github.com/alchemy-fr/Phraseanet.git
synced 2025-10-15 14:03:27 +00:00
PHRAS-2879_facet-order_4.1
save all facet settings in conf, order is preserved in admin/search-engine-settings. todo : ux to re-order facets todo : return facets into query results in this setting order. todo : migration from dbox settings (field struct) to conf ; remove field setting from admin
This commit is contained in:
@@ -13,6 +13,7 @@ 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;
|
||||||
@@ -85,7 +86,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'),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,9 @@
|
|||||||
*/
|
*/
|
||||||
namespace Alchemy\Phrasea\SearchEngine\Elastic;
|
namespace Alchemy\Phrasea\SearchEngine\Elastic;
|
||||||
|
|
||||||
|
use igorw;
|
||||||
|
|
||||||
|
|
||||||
class ElasticsearchOptions
|
class ElasticsearchOptions
|
||||||
{
|
{
|
||||||
const POPULATE_ORDER_RID = "RECORD_ID";
|
const POPULATE_ORDER_RID = "RECORD_ID";
|
||||||
@@ -58,13 +61,8 @@ class ElasticsearchOptions
|
|||||||
'populate_direction' => self::POPULATE_DIRECTION_DESC,
|
'populate_direction' => self::POPULATE_DIRECTION_DESC,
|
||||||
'activeTab' => null,
|
'activeTab' => null,
|
||||||
];
|
];
|
||||||
|
|
||||||
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 +74,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['aggregates'] as $fieldname=>$attributes) {
|
||||||
$self->setAggregableFieldLimit($k, $options[$k.'_limit']);
|
$self->setAggregableField($fieldname, $attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return $self;
|
return $self;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,10 +96,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,
|
||||||
|
'aggregates' => []
|
||||||
];
|
];
|
||||||
foreach(self::getAggregableTechnicalFields() as $k => $f) {
|
foreach($this->_customValues['aggregates'] as $fieldname=>$attributes) {
|
||||||
$ret[$k.'_limit'] = $this->getAggregableFieldLimit($k);
|
$ret['aggregates'][$fieldname] = $attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $ret;
|
return $ret;
|
||||||
@@ -220,14 +218,19 @@ class ElasticsearchOptions
|
|||||||
$this->highlight = $highlight;
|
$this->highlight = $highlight;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setAggregableFieldLimit($key, $value)
|
public function setAggregableField($key, $attributes)
|
||||||
{
|
{
|
||||||
$this->_customValues[$key.'_limit'] = $value;
|
$this->_customValues['aggregates'][$key] = $attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAggregableFieldLimit($key)
|
public function getAggregableField($key)
|
||||||
{
|
{
|
||||||
return $this->_customValues[$key.'_limit'];
|
return $this->_customValues['aggregates'][$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAggregableFields()
|
||||||
|
{
|
||||||
|
return $this->_customValues['aggregates'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getActiveTab()
|
public function getActiveTab()
|
||||||
@@ -241,56 +244,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 +303,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 +316,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 +330,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,43 +69,14 @@ 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]),
|
||||||
]);
|
])
|
||||||
|
|
||||||
foreach(ElasticsearchOptions::getAggregableTechnicalFields() as $k => $f) {
|
|
||||||
if(array_key_exists('choices', $f)) {
|
|
||||||
// choices[] : choice_key => choice_value
|
|
||||||
$choices = $f['choices'];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$choices = [
|
|
||||||
"10 values" => 10,
|
|
||||||
"20 values" => 20,
|
|
||||||
"50 values" => 50,
|
|
||||||
"100 values" => 100,
|
|
||||||
"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'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$builder
|
|
||||||
->add('highlight', 'checkbox', [
|
->add('highlight', 'checkbox', [
|
||||||
'label' => 'Activate highlight',
|
'label' => 'Activate highlight',
|
||||||
'required' => false
|
'required' => false
|
||||||
])
|
])
|
||||||
// ->add('save', 'submit', [
|
// ->add('save', 'submit', [
|
||||||
// 'attr' => ['class' => 'btn btn-primary']
|
// 'attr' => ['class' => 'btn btn-primary']
|
||||||
// ])
|
// ])
|
||||||
->add('esSettingFromIndex', 'button', [
|
->add('esSettingFromIndex', 'button', [
|
||||||
'label' => 'Get setting form index',
|
'label' => 'Get setting form index',
|
||||||
'attr' => [
|
'attr' => [
|
||||||
@@ -108,7 +92,66 @@ class ElasticsearchSettingsFormType extends AbstractType
|
|||||||
])
|
])
|
||||||
->add('activeTab', 'hidden');
|
->add('activeTab', 'hidden');
|
||||||
|
|
||||||
;
|
// keep aggregates in configuration order with this intermediate array
|
||||||
|
$aggs = [];
|
||||||
|
|
||||||
|
// helper fct to add aggregate to a tmp list
|
||||||
|
$addAgg = function($k, $label, $help, $disabled=false, $choices=null) use (&$aggs) {
|
||||||
|
if(!$choices) {
|
||||||
|
$choices = [
|
||||||
|
"10 values" => 10,
|
||||||
|
"50 values" => 50,
|
||||||
|
"100 values" => 100,
|
||||||
|
"all values" => -1
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$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 ?
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
// all fields fron conf
|
||||||
|
foreach($this->esSettings->getAggregableFields() as $k=>$f) {
|
||||||
|
// default value will be replaced by hardcoded tech fields & all databoxes fields
|
||||||
|
$addAgg($k, "/?\\ " . $k, "This field does not exists in current databoxes.", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add or replace hardcoded tech fields
|
||||||
|
foreach(ElasticsearchOptions::getAggregableTechnicalFields() as $k => $f) {
|
||||||
|
$choices = array_key_exists('choices', $f) ? $f['choices'] : null; // a tech-field can publish it's own choices
|
||||||
|
$help = null;
|
||||||
|
$label = '#' . $k;
|
||||||
|
if(!array_key_exists('_'.$k, $aggs)) {
|
||||||
|
$label = "/!\\ " . $label;
|
||||||
|
$help = "New field, please confirm setting.";
|
||||||
|
}
|
||||||
|
$addAgg('_'.$k, $label, $help, false, $choices);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add or replace all databoxes fields (nb: new db field - unknown in conf - will be a the end)
|
||||||
|
foreach($this->globalStructure->getAllFields() as $field) {
|
||||||
|
$k = $label = $field->getName();
|
||||||
|
$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('aggregates:' . $k . ':limit', ChoiceType::class, $agg);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getName()
|
public function getName()
|
||||||
|
Reference in New Issue
Block a user