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:
Jean-Yves Gaulier
2020-01-22 12:26:34 +01:00
parent 579ba471c4
commit 2ccdcfed27
3 changed files with 135 additions and 85 deletions

View File

@@ -13,6 +13,7 @@ namespace Alchemy\Phrasea\Controller\Admin;
use Alchemy\Phrasea\Controller\Controller;
use Alchemy\Phrasea\SearchEngine\Elastic\ElasticsearchSettingsFormType;
use Alchemy\Phrasea\SearchEngine\Elastic\ElasticsearchOptions;
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\GlobalStructure;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -85,7 +86,10 @@ class SearchEngineController extends Controller
*/
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'),
]);
}

View File

@@ -9,6 +9,9 @@
*/
namespace Alchemy\Phrasea\SearchEngine\Elastic;
use igorw;
class ElasticsearchOptions
{
const POPULATE_ORDER_RID = "RECORD_ID";
@@ -58,13 +61,8 @@ class ElasticsearchOptions
'populate_direction' => self::POPULATE_DIRECTION_DESC,
'activeTab' => null,
];
foreach(self::getAggregableTechnicalFields() as $k => $f) {
$defaultOptions[$k.'_limit'] = 0;
}
$options = array_replace($defaultOptions, $options);
$self = new self();
$self->setHost($options['host']);
$self->setPort($options['port']);
@@ -76,11 +74,10 @@ class ElasticsearchOptions
$self->setPopulateOrder($options['populate_order']);
$self->setPopulateDirection($options['populate_direction']);
$self->setActiveTab($options['activeTab']);
foreach(self::getAggregableTechnicalFields() as $k => $f) {
$self->setAggregableFieldLimit($k, $options[$k.'_limit']);
foreach($options['aggregates'] as $fieldname=>$attributes) {
$self->setAggregableField($fieldname, $attributes);
}
return $self;
}
@@ -99,10 +96,11 @@ class ElasticsearchOptions
'highlight' => $this->highlight,
'populate_order' => $this->populateOrder,
'populate_direction' => $this->populateDirection,
'activeTab' => $this->activeTab
'activeTab' => $this->activeTab,
'aggregates' => []
];
foreach(self::getAggregableTechnicalFields() as $k => $f) {
$ret[$k.'_limit'] = $this->getAggregableFieldLimit($k);
foreach($this->_customValues['aggregates'] as $fieldname=>$attributes) {
$ret['aggregates'][$fieldname] = $attributes;
}
return $ret;
@@ -220,14 +218,19 @@ class ElasticsearchOptions
$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()
@@ -241,56 +244,56 @@ class ElasticsearchOptions
public function __get($key)
{
if(!array_key_exists($key, $this->_customValues)) {
$this->_customValues[$key] = 0;
}
return $this->_customValues[$key];
$keys = explode(':', $key);
return igorw\get_in($this->_customValues, $keys);
}
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()
{
return [
'base_aggregate' => [
'base' => [
'type' => 'string',
'label' => 'prod::facet:base_label',
'field' => "database",
'esfield' => 'databox_name',
'query' => 'database:%s',
],
'collection_aggregate' => [
'collection' => [
'type' => 'string',
'label' => 'prod::facet:collection_label',
'field' => "collection",
'esfield' => 'collection_name',
'query' => 'collection:%s',
],
'doctype_aggregate' => [
'doctype' => [
'type' => 'string',
'label' => 'prod::facet:doctype_label',
'field' => "type",
'esfield' => 'type',
'query' => 'type:%s',
],
'camera_model_aggregate' => [
'camera_model' => [
'type' => 'string',
'label' => 'Camera Model',
'field' => "meta.CameraModel",
'esfield' => 'metadata_tags.CameraModel',
'query' => 'meta.CameraModel:%s',
],
'iso_aggregate' => [
'iso' => [
'type' => 'number',
'label' => 'ISO',
'field' => "meta.ISO",
'esfield' => 'metadata_tags.ISO',
'query' => 'meta.ISO=%s',
],
'aperture_aggregate' => [
'aperture' => [
'type' => 'number',
'label' => 'Aperture',
'field' => "meta.Aperture",
@@ -300,7 +303,7 @@ class ElasticsearchOptions
return round($value, 1);
},
],
'shutterspeed_aggregate' => [
'shutterspeed' => [
'type' => 'number',
'label' => 'Shutter speed',
'field' => "meta.ShutterSpeed",
@@ -313,7 +316,7 @@ class ElasticsearchOptions
return $value . ' s.';
},
],
'flashfired_aggregate' => [
'flashfired' => [
'type' => 'boolean',
'label' => 'FlashFired',
'field' => "meta.FlashFired",
@@ -327,49 +330,49 @@ class ElasticsearchOptions
return array_key_exists($value, $map) ? $map[$value] : $value;
},
],
'framerate_aggregate' => [
'framerate' => [
'type' => 'number',
'label' => 'FrameRate',
'field' => "meta.FrameRate",
'esfield' => 'metadata_tags.FrameRate',
'query' => 'meta.FrameRate=%s',
],
'audiosamplerate_aggregate' => [
'audiosamplerate' => [
'type' => 'number',
'label' => 'Audio Samplerate',
'field' => "meta.AudioSamplerate",
'esfield' => 'metadata_tags.AudioSamplerate',
'query' => 'meta.AudioSamplerate=%s',
],
'videocodec_aggregate' => [
'videocodec' => [
'type' => 'string',
'label' => 'Video codec',
'field' => "meta.VideoCodec",
'esfield' => 'metadata_tags.VideoCodec',
'query' => 'meta.VideoCodec:%s',
],
'audiocodec_aggregate' => [
'audiocodec' => [
'type' => 'string',
'label' => 'Audio codec',
'field' => "meta.AudioCodec",
'esfield' => 'metadata_tags.AudioCodec',
'query' => 'meta.AudioCodec:%s',
],
'orientation_aggregate' => [
'orientation' => [
'type' => 'string',
'label' => 'Orientation',
'field' => "meta.Orientation",
'esfield' => 'metadata_tags.Orientation',
'query' => 'meta.Orientation=%s',
],
'colorspace_aggregate' => [
'colorspace' => [
'type' => 'string',
'label' => 'Colorspace',
'field' => "meta.ColorSpace",
'esfield' => 'metadata_tags.ColorSpace',
'query' => 'meta.ColorSpace:%s',
],
'mimetype_aggregate' => [
'mimetype' => [
'type' => 'string',
'label' => 'MimeType',
'field' => "meta.MimeType",

View File

@@ -9,6 +9,7 @@
*/
namespace Alchemy\Phrasea\SearchEngine\Elastic;
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\GlobalStructure;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
@@ -17,6 +18,18 @@ use Symfony\Component\Validator\Constraints\Range;
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)
{
$builder
@@ -56,43 +69,14 @@ class ElasticsearchSettingsFormType extends AbstractType
->add('minScore', 'integer', [
'label' => 'Thesaurus Min score',
'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', [
'label' => 'Activate highlight',
'required' => false
])
// ->add('save', 'submit', [
// 'attr' => ['class' => 'btn btn-primary']
// ])
// ->add('save', 'submit', [
// 'attr' => ['class' => 'btn btn-primary']
// ])
->add('esSettingFromIndex', 'button', [
'label' => 'Get setting form index',
'attr' => [
@@ -108,7 +92,66 @@ class ElasticsearchSettingsFormType extends AbstractType
])
->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()