Allow to filter parameters by their (numeric) value

This commit is contained in:
Jan Böhmer 2022-09-08 00:04:53 +02:00
parent b56a970d5b
commit dd400ae70c
8 changed files with 288 additions and 23 deletions

View file

@ -13,8 +13,14 @@ export default class extends Controller {
*/
update()
{
const two_element_values = [
"BETWEEN",
'RANGE_IN_RANGE',
'RANGE_INTERSECT_RANGE'
];
for (const thingToHide of this.thingsToHideTargets) {
thingToHide.classList.toggle("d-none", this.operatorTarget.value !== "BETWEEN");
thingToHide.classList.toggle("d-none", !two_element_values.includes(this.operatorTarget.value));
}
}
}

View file

@ -22,6 +22,9 @@ class ParameterConstraint extends AbstractConstraint
/** @var TextConstraint */
protected $value_text;
/** @var ParameterValueConstraint */
protected $value;
/** @var string The alias to use for the subquery */
protected $alias;
@ -33,6 +36,7 @@ class ParameterConstraint extends AbstractConstraint
$this->alias = uniqid('param_', false);
$this->value_text = new TextConstraint($this->alias . '.value_text');
$this->value = new ParameterValueConstraint($this->alias );
}
public function isEnabled(): bool
@ -71,6 +75,7 @@ class ParameterConstraint extends AbstractConstraint
//Apply all subfilters
$this->value_text->apply($subqb);
$this->value->apply($subqb);
//Copy all parameters from the subquery to the main query
//We can not use setParameters here, as this would override the exiting paramaters in queryBuilder
@ -144,14 +149,12 @@ class ParameterConstraint extends AbstractConstraint
}
/**
* DO NOT USE THIS SETTER!
* This is just a workaround for collectionType behavior
* @param $value
* @return $this
* @return ParameterValueConstraint
*/
/*public function setValueText($value): self
public function getValue(): ParameterValueConstraint
{
//Do not really set the value here, as we dont want to override the constraint created in the constructor
return $this;
}*/
return $this->value;
}
}

View file

@ -0,0 +1,130 @@
<?php
namespace App\DataTables\Filters\Constraints\Part;
use App\DataTables\Filters\Constraints\NumberConstraint;
use Doctrine\ORM\QueryBuilder;
class ParameterValueConstraint extends NumberConstraint
{
protected $alias;
public const ALLOWED_OPERATOR_VALUES = ['=', '!=', '<', '>', '<=', '>=', 'BETWEEN',
//Additional operators
'IN_RANGE', 'NOT_IN_RANGE', 'GREATER_THAN_RANGE', 'GREATER_EQUAL_RANGE', 'LESS_THAN_RANGE', 'LESS_EQUAL_RANGE', 'RANGE_IN_RANGE', 'RANGE_INTERSECT_RANGE'];
/**
* @param string $alias The alias which is used in the sub query of ParameterConstraint
*/
public function __construct(string $alias) {
$this->alias = $alias;
parent::__construct($alias . '.value_typical');
}
public function apply(QueryBuilder $queryBuilder): void
{
//Skip if not enabled
if(!$this->isEnabled()) {
return;
}
$paramName1 = $this->generateParameterIdentifier('value1');
$paramName2 = $this->generateParameterIdentifier('value2');
if ($this->operator === 'IN_RANGE') {
$queryBuilder->andWhere(
"({$this->alias}.value_min <= :{$paramName1} AND {$this->alias}.value_max >= :{$paramName1}) OR
({$this->alias}.value_typical = :{$paramName1})"
);
$queryBuilder->setParameter($paramName1, $this->value1);
return;
}
if ($this->operator === 'NOT_IN_RANGE') {
$queryBuilder->andWhere(
"({$this->alias}.value_min > :{$paramName1} OR {$this->alias}.value_max < :{$paramName1}) AND
({$this->alias}.value_typical IS NULL OR {$this->alias}.value_typical != :{$paramName1})"
);
$queryBuilder->setParameter($paramName1, $this->value1);
return;
}
if ($this->operator === 'GREATER_THAN_RANGE') {
$queryBuilder->andWhere(
"{$this->alias}.value_max < :{$paramName1} OR {$this->alias}.value_typical < :{$paramName1}"
);
$queryBuilder->setParameter($paramName1, $this->value1);
return;
}
if ($this->operator === 'GREATER_EQUAL_RANGE') {
$queryBuilder->andWhere(
"{$this->alias}.value_max <= :{$paramName1} OR {$this->alias}.value_typical <= :{$paramName1}"
);
$queryBuilder->setParameter($paramName1, $this->value1);
return;
}
if ($this->operator === 'LESS_THAN_RANGE') {
$queryBuilder->andWhere(
"{$this->alias}.value_min > :{$paramName1} OR {$this->alias}.value_typical > :{$paramName1}"
);
$queryBuilder->setParameter($paramName1, $this->value1);
return;
}
if ($this->operator === 'LESS_EQUAL_RANGE') {
$queryBuilder->andWhere(
"{$this->alias}.value_min >= :{$paramName1} OR {$this->alias}.value_typical >= :{$paramName1}"
);
$queryBuilder->setParameter($paramName1, $this->value1);
return;
}
// This operator means the constraint range must lie completely within the parameter value range
if ($this->operator === 'RANGE_IN_RANGE') {
$queryBuilder->andWhere(
"({$this->alias}.value_min <= :{$paramName1} AND {$this->alias}.value_max >= :{$paramName2}) OR
({$this->alias}.value_typical >= :{$paramName1} AND {$this->alias}.value_typical <= :{$paramName2})"
);
$queryBuilder->setParameter($paramName1, $this->value1);
$queryBuilder->setParameter($paramName2, $this->value2);
return;
}
if ($this->operator === 'RANGE_INTERSECT_RANGE') {
$queryBuilder->andWhere(
//The ORs are important here!!
"({$this->alias}.value_min <= :{$paramName1} OR {$this->alias}.value_min <= :{$paramName2}) OR
({$this->alias}.value_max >= :{$paramName1} OR {$this->alias}.value_max >= :{$paramName2}) OR
({$this->alias}.value_typical >= :{$paramName1} AND {$this->alias}.value_typical <= :{$paramName2})"
);
$queryBuilder->setParameter($paramName1, $this->value1);
$queryBuilder->setParameter($paramName2, $this->value2);
return;
}
//For all other cases use the default implementation
parent::apply($queryBuilder);
}
}

View file

@ -13,6 +13,17 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
class NumberConstraintType extends AbstractType
{
protected const CHOICES = [
'' => '',
'=' => '=',
'!=' => '!=',
'<' => '<',
'>' => '>',
'<=' => '<=',
'>=' => '>=',
'filter.number_constraint.value.operator.BETWEEN' => 'BETWEEN',
];
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
@ -31,17 +42,6 @@ class NumberConstraintType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$choices = [
'' => '',
'=' => '=',
'!=' => '!=',
'<' => '<',
'>' => '>',
'<=' => '<=',
'>=' => '>=',
'filter.number_constraint.value.operator.BETWEEN' => 'BETWEEN',
];
$builder->add('value1', NumberType::class, array_merge_recursive([
'label' => 'filter.number_constraint.value1',
'attr' => [
@ -68,7 +68,7 @@ class NumberConstraintType extends AbstractType
$builder->add('operator', ChoiceType::class, [
'label' => 'filter.number_constraint.operator',
'choices' => $choices,
'choices' => static::CHOICES,
'required' => false,
]);
}

View file

@ -41,6 +41,9 @@ class ParameterConstraintType extends AbstractType
//'required' => false,
] );
$builder->add('value', ParameterValueConstraintType::class, [
]);
/*
* I am not quite sure why this is needed, but somehow symfony tries to create a new instance of TextConstraint
* instead of using the existing one for the prototype (or the one from empty data). This fails as the constructor of TextConstraint requires

View file

@ -0,0 +1,33 @@
<?php
namespace App\Form\Filters\Constraints;
class ParameterValueConstraintType extends NumberConstraintType
{
protected const CHOICES = [
'' => '',
'filter.parameter_value_constraint.operator.=' => '=',
'filter.parameter_value_constraint.operator.!=' => '!=',
'filter.parameter_value_constraint.operator.<' => '<',
'filter.parameter_value_constraint.operator.>' => '>',
'filter.parameter_value_constraint.operator.<=' => '<=',
'filter.parameter_value_constraint.operator.>=' => '>=',
'filter.parameter_value_constraint.operator.BETWEEN' => 'BETWEEN',
//Extensions by ParameterValueConstraint
'filter.parameter_value_constraint.operator.IN_RANGE' => 'IN_RANGE',
'filter.parameter_value_constraint.operator.NOT_IN_RANGE' => 'NOT_IN_RANGE',
'filter.parameter_value_constraint.operator.GREATER_THAN_RANGE' => 'GREATER_THAN_RANGE',
'filter.parameter_value_constraint.operator.GREATER_EQUAL_RANGE' => 'GREATER_EQUAL_RANGE',
'filter.parameter_value_constraint.operator.LESS_THAN_RANGE' => 'LESS_THAN_RANGE',
'filter.parameter_value_constraint.operator.LESS_EQUAL_RANGE' => 'LESS_EQUAL_RANGE',
'filter.parameter_value_constraint.operator.RANGE_IN_RANGE' => 'RANGE_IN_RANGE',
'filter.parameter_value_constraint.operator.RANGE_INTERSECT_RANGE' => 'RANGE_INTERSECT_RANGE'
];
public function getParent(): string
{
return NumberConstraintType::class;
}
}

View file

@ -43,9 +43,9 @@
{% block parameter_constraint_widget %}
{% import 'components/collection_type.macro.html.twig' as collection %}
<tr {{ stimulus_controller('pages/parameters_autocomplete', {"url": url('typeahead_parameters', {"query": "__QUERY__", "type": "part"}) }) }} >
<td>{{ form_widget(form.name, {"attr": {"data-pages--parameters-autocomplete-target": "name"}}) }}</td>
<td class="col-sm-2">{{ form_widget(form.name, {"attr": {"data-pages--parameters-autocomplete-target": "name"}}) }}</td>
<td {{ stimulus_controller('pages/latex_preview') }}>{{ form_widget(form.symbol, {"attr": {"data-pages--parameters-autocomplete-target": "symbol", "data-pages--latex-preview-target": "input"}}) }}<span {{ stimulus_target('pages/latex_preview', 'preview') }}></span></td>
<td></td>
<td>{{ form_widget(form.value) }}</td>
<td {{ stimulus_controller('pages/latex_preview') }}>{{ form_widget(form.unit, {"attr": {"data-pages--parameters-autocomplete-target": "unit", "data-pages--latex-preview-target": "input"}}) }}<span {{ stimulus_target('pages/latex_preview', 'preview') }}></span></td>
<td>{{ form_widget(form.value_text) }}</td>
<td>

View file

@ -9543,5 +9543,95 @@ Element 3</target>
<target>Filter</target>
</segment>
</unit>
<unit id="kTizqBM" name="filter.parameter_value_constraint.operator.=">
<segment>
<source>filter.parameter_value_constraint.operator.=</source>
<target>Typ. Value =</target>
</segment>
</unit>
<unit id="2Nf2h2a" name="filter.parameter_value_constraint.operator.!=">
<segment>
<source>filter.parameter_value_constraint.operator.!=</source>
<target>Typ. Value !=</target>
</segment>
</unit>
<unit id="0fmepxx" name="filter.parameter_value_constraint.operator.&lt;">
<segment>
<source>filter.parameter_value_constraint.operator.&lt;</source>
<target><![CDATA[Typ. Value <]]></target>
</segment>
</unit>
<unit id="ubkcT6U" name="filter.parameter_value_constraint.operator.&gt;">
<segment>
<source>filter.parameter_value_constraint.operator.&gt;</source>
<target><![CDATA[Typ. Value >]]></target>
</segment>
</unit>
<unit id="O4x0opb" name="filter.parameter_value_constraint.operator.&lt;=">
<segment>
<source>filter.parameter_value_constraint.operator.&lt;=</source>
<target><![CDATA[Typ. Value <=]]></target>
</segment>
</unit>
<unit id="ituiTs9" name="filter.parameter_value_constraint.operator.&gt;=">
<segment>
<source>filter.parameter_value_constraint.operator.&gt;=</source>
<target><![CDATA[Typ. Value >=]]></target>
</segment>
</unit>
<unit id="Y8B3A9X" name="filter.parameter_value_constraint.operator.BETWEEN">
<segment>
<source>filter.parameter_value_constraint.operator.BETWEEN</source>
<target>Typ. Value is between</target>
</segment>
</unit>
<unit id="FqUoIEy" name="filter.parameter_value_constraint.operator.IN_RANGE">
<segment>
<source>filter.parameter_value_constraint.operator.IN_RANGE</source>
<target>In Value range</target>
</segment>
</unit>
<unit id="v_1AWPE" name="filter.parameter_value_constraint.operator.NOT_IN_RANGE">
<segment>
<source>filter.parameter_value_constraint.operator.NOT_IN_RANGE</source>
<target>Not in Value range</target>
</segment>
</unit>
<unit id="X4hI3kL" name="filter.parameter_value_constraint.operator.GREATER_THAN_RANGE">
<segment>
<source>filter.parameter_value_constraint.operator.GREATER_THAN_RANGE</source>
<target>Greater than Value range</target>
</segment>
</unit>
<unit id="yQO92R2" name="filter.parameter_value_constraint.operator.GREATER_EQUAL_RANGE">
<segment>
<source>filter.parameter_value_constraint.operator.GREATER_EQUAL_RANGE</source>
<target>Greater equal than Value range</target>
</segment>
</unit>
<unit id="h8zJtL5" name="filter.parameter_value_constraint.operator.LESS_THAN_RANGE">
<segment>
<source>filter.parameter_value_constraint.operator.LESS_THAN_RANGE</source>
<target>Less than Value range</target>
</segment>
</unit>
<unit id="sxSjUOg" name="filter.parameter_value_constraint.operator.LESS_EQUAL_RANGE">
<segment>
<source>filter.parameter_value_constraint.operator.LESS_EQUAL_RANGE</source>
<target>Less equal than Value range</target>
</segment>
</unit>
<unit id="fqRL5fp" name="filter.parameter_value_constraint.operator.RANGE_IN_RANGE">
<segment>
<source>filter.parameter_value_constraint.operator.RANGE_IN_RANGE</source>
<target>Range is completly in Value range</target>
</segment>
</unit>
<unit id="PJgHP5E" name="filter.parameter_value_constraint.operator.RANGE_INTERSECT_RANGE">
<segment>
<source>filter.parameter_value_constraint.operator.RANGE_INTERSECT_RANGE</source>
<target>Range intersects Value range</target>
</segment>
</unit>
</file>
</xliff>