Merge branch 'master' into PHRAS-2812-hide-cterms-ckbox

This commit is contained in:
Nicolas Maillat
2020-02-18 14:27:56 +01:00
committed by GitHub
21 changed files with 308 additions and 176 deletions

View File

@@ -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

View File

@@ -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'),
]); ]);
} }

View File

@@ -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
// ];
} }
} }

View File

@@ -458,9 +458,9 @@ class AccountController extends Controller
->setZipCode($request->request->get("form_zip")) ->setZipCode($request->request->get("form_zip"))
->setPhone($request->request->get("form_phone")) ->setPhone($request->request->get("form_phone"))
->setFax($request->request->get("form_fax")) ->setFax($request->request->get("form_fax"))
->setJob($request->request->get("form_activity")) ->setJob($request->request->get("form_function"))
->setCompany($request->request->get("form_company")) ->setCompany($request->request->get("form_company"))
->setPosition($request->request->get("form_function")) ->setPosition($request->request->get("form_activity"))
->setNotifications((Boolean) $request->request->get("mail_notifications")); ->setNotifications((Boolean) $request->request->get("mail_notifications"));
$service->updateAccount($command); $service->updateAccount($command);

View File

@@ -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(

View File

@@ -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

View File

@@ -657,8 +657,8 @@ class Edit extends \Alchemy\Phrasea\Helper\Helper
->setEmail($parm['email']) ->setEmail($parm['email'])
->setAddress($parm['address']) ->setAddress($parm['address'])
->setZipCode($parm['zip']) ->setZipCode($parm['zip'])
->setActivity($parm['function']) ->setActivity($parm['activite'])
->setJob($parm['activite']) ->setJob($parm['function'])
->setCompany($parm['company']) ->setCompany($parm['company'])
->setPhone($parm['telephone']) ->setPhone($parm['telephone'])
->setFax($parm['fax']); ->setFax($parm['fax']);

View File

@@ -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;

View File

@@ -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",

View File

@@ -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()

View File

@@ -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
]; ];
} }
} }
} }

View File

@@ -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[]
*/ */

View File

@@ -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());

View File

@@ -33,11 +33,6 @@ interface Structure
*/ */
public function getPrivateFields(); public function getPrivateFields();
/**
* @return Field[]
*/
public function getFacetFields();
/** /**
* @return Field[] * @return Field[]
*/ */

View File

@@ -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];

View File

@@ -65,7 +65,7 @@
"normalize-css": "^2.1.0", "normalize-css": "^2.1.0",
"npm": "^6.0.0", "npm": "^6.0.0",
"npm-modernizr": "^2.8.3", "npm-modernizr": "^2.8.3",
"phraseanet-production-client": "0.34.128-d", "phraseanet-production-client": "0.34.135-d",
"requirejs": "^2.3.5", "requirejs": "^2.3.5",
"tinymce": "^4.0.28", "tinymce": "^4.0.28",
"underscore": "^1.8.3", "underscore": "^1.8.3",

View File

@@ -107,7 +107,7 @@
<div class="control-group"> <div class="control-group">
<label class="form_label control-label" for="form_function"><strong>{{ "admin::compte-utilisateur poste" | trans }}</strong></label> <label class="form_label control-label" for="form_function"><strong>{{ "admin::compte-utilisateur poste" | trans }}</strong></label>
<div class="controls"> <div class="controls">
<input class="input_element input-xlarge" type="text" name="form_function" id="form_function" value="{{ app["authentication"].getUser().getActivity() }}" /> <input class="input_element input-xlarge" type="text" name="form_function" id="form_function" value="{{ app["authentication"].getUser().getJob() }}" />
<p class="form_alert help-block"></p> <p class="form_alert help-block"></p>
</div> </div>
</div> </div>
@@ -121,7 +121,7 @@
<div class="control-group"> <div class="control-group">
<label class="form_label control-label" for="form_activity"><strong>{{ "admin::compte-utilisateur activite" | trans }}</strong></label> <label class="form_label control-label" for="form_activity"><strong>{{ "admin::compte-utilisateur activite" | trans }}</strong></label>
<div class="controls"> <div class="controls">
<input class="input_element input-xlarge" type="text" name="form_activity" id="form_activity" value="{{ app["authentication"].getUser().getJob() }}" /> <input class="input_element input-xlarge" type="text" name="form_activity" id="form_activity" value="{{ app["authentication"].getUser().getActivity() }}" />
<p class="form_alert help-block"></p> <p class="form_alert help-block"></p>
</div> </div>
</div> </div>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);

View File

@@ -7576,10 +7576,12 @@ phraseanet-common@^0.4.5-d:
jquery "^3.2.1" jquery "^3.2.1"
js-cookie "^2.1.0" js-cookie "^2.1.0"
pym.js "^1.3.1" pym.js "^1.3.1"
phraseanet-production-client@0.34.128-d:
version "0.34.128-d"
resolved "https://registry.yarnpkg.com/phraseanet-production-client/-/phraseanet-production-client-0.34.128-d.tgz#c24bebe16d99f67f45b22c7e62b783be135bf569" phraseanet-production-client@0.34.135-d:
integrity sha512-AZ6Mdx26yfjQ53Y2MSBtOoopsQ3+eHA5qiRLnoUgqm6CmDdYatUyFt7lzGRk1axmVYf+2yH7jNaoHKCxb/n9UA== version "0.34.135-d"
resolved "https://registry.yarnpkg.com/phraseanet-production-client/-/phraseanet-production-client-0.34.135-d.tgz#fafbeecc0bd7aac7271742596576d5601b35d93e"
integrity sha512-mIORoFSZ4ZQgT0/1PhXsbwExrpTtNskdq/37/tuTrh5s/6SasFgvutSkUCvqr7EDyE2LNHZDAfcTCFLqo9T7DQ==
dependencies: dependencies:
"@mapbox/mapbox-gl-language" "^0.9.2" "@mapbox/mapbox-gl-language" "^0.9.2"
"@turf/turf" "^5.1.6" "@turf/turf" "^5.1.6"