chg: [CRUD] Improved metaFields filtering capabilities
parent
5d1106e82a
commit
d4001fab18
|
@ -30,9 +30,10 @@ class CRUDComponent extends Component
|
|||
}
|
||||
$options['filters'][] = 'quickFilter';
|
||||
}
|
||||
$options['filters'][] = 'filteringLabel';
|
||||
$params = $this->Controller->ParamHandler->harvestParams(empty($options['filters']) ? [] : $options['filters']);
|
||||
$query = $this->Table->find();
|
||||
$query = $this->setFilters($params, $query);
|
||||
$query = $this->setFilters($params, $query, $options);
|
||||
$query = $this->setQuickFilters($params, $query, empty($options['quickFilters']) ? [] : $options['quickFilters']);
|
||||
if (!empty($options['contain'])) {
|
||||
$query->contain($options['contain']);
|
||||
|
@ -243,22 +244,35 @@ class CRUDComponent extends Component
|
|||
if (empty($this->Table->metaFields)) {
|
||||
return $data;
|
||||
}
|
||||
$query = $this->MetaFields->MetaTemplates->find();
|
||||
$metaFields = $this->Table->metaFields;
|
||||
$query->contain('MetaTemplateFields', function ($q) use ($id, $metaFields) {
|
||||
return $q->innerJoinWith('MetaFields')
|
||||
->where(['MetaFields.scope' => $metaFields, 'MetaFields.parent_id' => $id]);
|
||||
});
|
||||
$query->innerJoinWith('MetaTemplateFields', function ($q) {
|
||||
return $q->contain('MetaFields')->innerJoinWith('MetaFields');
|
||||
});
|
||||
$query->group(['MetaTemplates.id', 'MetaTemplates.scope', 'MetaTemplates.name', 'MetaTemplates.namespace', 'MetaTemplates.description', 'MetaTemplates.version', 'MetaTemplates.uuid', 'MetaTemplates.source', 'MetaTemplates.enabled', 'MetaTemplates.is_default'])
|
||||
->order(['MetaTemplates.is_default' => 'DESC']);
|
||||
$metaTemplates = $query->all();
|
||||
$metaFieldScope = $this->Table->metaFields;
|
||||
$query = $this->MetaTemplates->find()->where(['MetaTemplates.scope' => $metaFieldScope]);
|
||||
$query->contain(['MetaTemplateFields.MetaFields' => function ($q) use ($id, $metaFieldScope) {
|
||||
return $q->where(['MetaFields.scope' => $metaFieldScope, 'MetaFields.parent_id' => $id]);
|
||||
}]);
|
||||
$query
|
||||
->order(['MetaTemplates.is_default' => 'DESC'])
|
||||
->order(['MetaTemplates.name' => 'ASC']);
|
||||
$metaTemplates = $query->all()->toArray();
|
||||
$metaTemplates = $this->pruneEmptyMetaTemplates($metaTemplates);
|
||||
$data['metaTemplates'] = $metaTemplates;
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function pruneEmptyMetaTemplates($metaTemplates)
|
||||
{
|
||||
foreach ($metaTemplates as $i => $metaTemplate) {
|
||||
foreach ($metaTemplate['meta_template_fields'] as $j => $metaTemplateField) {
|
||||
if (empty($metaTemplateField['meta_fields'])) {
|
||||
unset($metaTemplates[$i]['meta_template_fields'][$j]);
|
||||
}
|
||||
}
|
||||
if (empty($metaTemplates[$i]['meta_template_fields'])) {
|
||||
unset($metaTemplates[$i]);
|
||||
}
|
||||
}
|
||||
return $metaTemplates;
|
||||
}
|
||||
|
||||
public function getMetaFields($id, $data)
|
||||
{
|
||||
if (empty($this->Table->metaFields)) {
|
||||
|
@ -361,28 +375,51 @@ class CRUDComponent extends Component
|
|||
return $query;
|
||||
}
|
||||
|
||||
protected function setFilters($params, \Cake\ORM\Query $query): \Cake\ORM\Query
|
||||
protected function setFilters($params, \Cake\ORM\Query $query, array $options): \Cake\ORM\Query
|
||||
{
|
||||
$params = $this->massageFilters($params);
|
||||
$conditions = array();
|
||||
if (!empty($params['simpleFilters'])) {
|
||||
foreach ($params['simpleFilters'] as $filter => $filterValue) {
|
||||
if ($filter === 'quickFilter') {
|
||||
continue;
|
||||
}
|
||||
if (is_array($filterValue)) {
|
||||
$query->where([($filter . ' IN') => $filterValue]);
|
||||
} else {
|
||||
$query = $this->setValueCondition($query, $filter, $filterValue);
|
||||
$filteringLabel = !empty($params['filteringLabel']) ? $params['filteringLabel'] : '';
|
||||
unset($params['filteringLabel']);
|
||||
$customFilteringFunction = '';
|
||||
$chosenFilter = '';
|
||||
if (!empty($options['contextFilters']['custom'])) {
|
||||
foreach ($options['contextFilters']['custom'] as $filter) {
|
||||
if ($filter['label'] == $filteringLabel) {
|
||||
$customFilteringFunction = $filter;
|
||||
$chosenFilter = $filter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($params['relatedFilters'])) {
|
||||
foreach ($params['relatedFilters'] as $filter => $filterValue) {
|
||||
$filterParts = explode('.', $filter);
|
||||
$query = $this->setNestedRelatedCondition($query, $filterParts, $filterValue);
|
||||
|
||||
if (!empty($customFilteringFunction['filterConditionFunction'])) {
|
||||
$query = $customFilteringFunction['filterConditionFunction']($query);
|
||||
} else {
|
||||
if (!empty($chosenFilter)) {
|
||||
$params = $this->massageFilters($chosenFilter['filterCondition']);
|
||||
} else {
|
||||
$params = $this->massageFilters($params);
|
||||
}
|
||||
$conditions = array();
|
||||
if (!empty($params['simpleFilters'])) {
|
||||
foreach ($params['simpleFilters'] as $filter => $filterValue) {
|
||||
if ($filter === 'quickFilter') {
|
||||
continue;
|
||||
}
|
||||
if (is_array($filterValue)) {
|
||||
$query->where([($filter . ' IN') => $filterValue]);
|
||||
} else {
|
||||
$query = $this->setValueCondition($query, $filter, $filterValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($params['relatedFilters'])) {
|
||||
foreach ($params['relatedFilters'] as $filter => $filterValue) {
|
||||
$filterParts = explode('.', $filter);
|
||||
$query = $this->setNestedRelatedCondition($query, $filterParts, $filterValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
@ -447,6 +484,57 @@ class CRUDComponent extends Component
|
|||
$this->Controller->set('filteringContexts', $filteringContexts);
|
||||
}
|
||||
|
||||
public function getParentsForMetaFields($query, array $metaConditions)
|
||||
{
|
||||
$metaTemplates = $this->MetaFields->MetaTemplates->find('list', [
|
||||
'keyField' => 'name',
|
||||
'valueField' => 'id'
|
||||
])->where(['name IN' => array_keys($metaConditions)])->all()->toArray();
|
||||
$fieldsConditions = [];
|
||||
foreach ($metaConditions as $templateName => $templateConditions) {
|
||||
$metaTemplateID = isset($metaTemplates[$templateName]) ? $metaTemplates[$templateName] : -1;
|
||||
foreach ($templateConditions as $conditions) {
|
||||
$conditions['meta_template_id'] = $metaTemplateID;
|
||||
$fieldsConditions[] = $conditions;
|
||||
}
|
||||
}
|
||||
$matchingMetaQuery = $this->getParentIDQueryForMetaANDConditions($fieldsConditions);
|
||||
return $query->where(['id IN' => $matchingMetaQuery]);
|
||||
}
|
||||
|
||||
private function getParentIDQueryForMetaANDConditions(array $metaANDConditions)
|
||||
{
|
||||
if (empty($metaANDConditions)) {
|
||||
throw new Exception('Invalid passed conditions');
|
||||
}
|
||||
foreach ($metaANDConditions as $i => $conditions) {
|
||||
$metaANDConditions[$i]['scope'] = $this->Table->metaFields;
|
||||
}
|
||||
$firstCondition = $this->prefixConditions('MetaFields', $metaANDConditions[0]);
|
||||
$conditionsToJoin = array_slice($metaANDConditions, 1);
|
||||
$query = $this->MetaFields->find()
|
||||
->select('parent_id')
|
||||
->where($firstCondition);
|
||||
foreach ($conditionsToJoin as $i => $conditions) {
|
||||
$joinedConditions = $this->prefixConditions("m{$i}", $conditions);
|
||||
$joinedConditions[] = "m{$i}.parent_id = MetaFields.parent_id";
|
||||
$query->rightJoin(
|
||||
["m{$i}" => 'meta_fields'],
|
||||
$joinedConditions
|
||||
);
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
private function prefixConditions(string $prefix, array $conditions)
|
||||
{
|
||||
$prefixedConditions = [];
|
||||
foreach ($conditions as $condField => $condValue) {
|
||||
$prefixedConditions["${prefix}.${condField}"] = $condValue;
|
||||
}
|
||||
return $prefixedConditions;
|
||||
}
|
||||
|
||||
public function toggle(int $id, string $fieldName = 'enabled', array $params = []): void
|
||||
{
|
||||
if (empty($id)) {
|
||||
|
|
|
@ -15,8 +15,45 @@ class OrganisationsController extends AppController
|
|||
public function index()
|
||||
{
|
||||
$this->CRUD->index([
|
||||
'filters' => ['name', 'uuid', 'nationality', 'sector', 'type', 'url', 'Alignments.id'],
|
||||
'quickFilters' => ['name', 'uuid', 'nationality', 'sector', 'type', 'url'],
|
||||
'filters' => ['name', 'uuid', 'nationality', 'sector', 'type', 'url', 'Alignments.id', 'MetaFields.field', 'MetaFields.value', 'MetaFields.MetaTemplates.name'],
|
||||
'quickFilters' => [['name' => true], 'uuid', 'nationality', 'sector', 'type', 'url'],
|
||||
'contextFilters' => [
|
||||
'custom' => [
|
||||
[
|
||||
'label' => __('ENISA Accredited'),
|
||||
'filterCondition' => [
|
||||
'MetaFields.field' => 'enisa-tistatus',
|
||||
'MetaFields.value' => 'Accredited',
|
||||
'MetaFields.MetaTemplates.name' => 'ENISA CSIRT Network'
|
||||
]
|
||||
],
|
||||
[
|
||||
'label' => __('ENISA not-Accredited'),
|
||||
'filterCondition' => [
|
||||
'MetaFields.field' => 'enisa-tistatus',
|
||||
'MetaFields.value !=' => 'Accredited',
|
||||
'MetaFields.MetaTemplates.name' => 'ENISA CSIRT Network'
|
||||
]
|
||||
],
|
||||
[
|
||||
'label' => __('ENISA CSIRT Network (GOV)'),
|
||||
'filterConditionFunction' => function($query) {
|
||||
return $this->CRUD->getParentsForMetaFields($query, [
|
||||
'ENISA CSIRT Network' => [
|
||||
[
|
||||
'field' => 'constituency',
|
||||
'value LIKE' => '%Government%',
|
||||
],
|
||||
[
|
||||
'field' => 'csirt-network-status',
|
||||
'value' => 'Member',
|
||||
],
|
||||
]
|
||||
]);
|
||||
}
|
||||
]
|
||||
],
|
||||
],
|
||||
'contain' => ['Alignments' => 'Individuals']
|
||||
]);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
|
|
|
@ -27,6 +27,8 @@ class MetaFieldsTable extends AppTable
|
|||
->notEmptyString('meta_template_id')
|
||||
->notEmptyString('meta_template_field_id')
|
||||
->requirePresence(['scope', 'field', 'value', 'uuid', 'meta_template_id', 'meta_template_field_id'], 'create');
|
||||
|
||||
// add validation regex
|
||||
return $validator;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ class OrganisationsTable extends AppTable
|
|||
[
|
||||
'dependent' => true,
|
||||
'foreignKey' => 'parent_id',
|
||||
'conditions' => ['scope' => 'organisation']
|
||||
'conditions' => ['MetaFields.scope' => 'organisation']
|
||||
]
|
||||
);
|
||||
$this->setDisplayField('name');
|
||||
|
|
|
@ -15,6 +15,10 @@ echo $this->element('genericElements/IndexTable/index_table', [
|
|||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
'type' => 'context_filters',
|
||||
'context_filters' => $filteringContexts
|
||||
],
|
||||
[
|
||||
'type' => 'search',
|
||||
'button' => __('Filter'),
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
$urlParams = [
|
||||
'controller' => $this->request->getParam('controller'),
|
||||
'action' => 'index',
|
||||
'?' => $filteringContext['filterCondition']
|
||||
'?' => array_merge($filteringContext['filterCondition'], ['filteringLabel' => $filteringContext['label']])
|
||||
];
|
||||
$currentQuery = $this->request->getQuery();
|
||||
unset($currentQuery['page'], $currentQuery['limit'], $currentQuery['sort']);
|
||||
$filteringLabel = !empty($currentQuery['filteringLabel']) ? $currentQuery['filteringLabel'] : '';
|
||||
unset($currentQuery['page'], $currentQuery['limit'], $currentQuery['sort'], $currentQuery['filteringLabel']);
|
||||
if (!empty($filteringContext['filterCondition'])) { // PHP replaces `.` by `_` when fetching the request parameter
|
||||
$currentFilteringContext = [];
|
||||
foreach ($filteringContext['filterCondition'] as $currentFilteringContextKey => $value) {
|
||||
|
@ -18,7 +19,14 @@
|
|||
$currentFilteringContext = $filteringContext['filterCondition'];
|
||||
}
|
||||
$contextArray[] = [
|
||||
'active' => $currentQuery == $currentFilteringContext,
|
||||
'active' => (
|
||||
(
|
||||
$currentQuery == $currentFilteringContext && // query conditions match
|
||||
!isset($filteringContext['filterConditionFunction']) && // not a custom filtering
|
||||
empty($filteringLabel) // do not check `All` by default
|
||||
) ||
|
||||
$filteringContext['label'] == $filteringLabel // labels should not be duplicated
|
||||
),
|
||||
'isFilter' => true,
|
||||
'onClick' => 'changeIndexContext',
|
||||
'onClickParams' => [
|
||||
|
|
Loading…
Reference in New Issue