Skip to content

CFM-291_Groups_Cous_extra_filtering_options #185

Merged
6 changes: 6 additions & 0 deletions app/resources/locales/en_US/field.po
Expand Up @@ -107,6 +107,9 @@ msgstr "Email"
msgid "ends_at"
msgstr "Ends at:"

msgid "ends_at.tz"
msgstr "Ends at ({0})"

msgid "extension"
msgstr "Extension"

Expand Down Expand Up @@ -243,6 +246,9 @@ msgstr "Sponsor"
msgid "starts_at"
msgstr "Starts at:"

msgid "starts_at.tz"
msgstr "Starts at ({0})"

msgid "state"
msgstr "State"

Expand Down
5 changes: 4 additions & 1 deletion app/resources/locales/en_US/information.po
Expand Up @@ -106,4 +106,7 @@ msgid "plugin.active.only"
msgstr "Active, Cannot Be Disabled"

msgid "plugin.inactive"
msgstr "Inactive"
msgstr "Inactive"

msgid "table.list"
msgstr "{0} List"
6 changes: 6 additions & 0 deletions app/resources/locales/en_US/operation.po
Expand Up @@ -63,6 +63,9 @@ msgstr "Apply Database Schema"
msgid "assign"
msgstr "Assign"

msgid "any"
msgstr "Any"

msgid "cancel"
msgstr "Cancel"

Expand Down Expand Up @@ -144,6 +147,9 @@ msgstr "Logout"
msgid "next"
msgstr "Next"

msgid "none"
msgstr "None"

msgid "page.display"
msgstr "Display records"

Expand Down
6 changes: 4 additions & 2 deletions app/src/Lib/Traits/IndexQueryTrait.php
Expand Up @@ -136,7 +136,7 @@ public function getIndexQuery(bool $pickerMode = false, array $requestParams = [
$link = $this->getPrimaryLink(true);
// Initialize the Query Object
$query = $table->find();
// Get a pointer to my expressions list
// Get a pointer to my expression list
$newexp = $query->newExpr();
// The searchable attributes can have an AND or an OR conjunction. The first one is used from the filtering block
// while the second one from the picker vue module.
Expand All @@ -159,6 +159,8 @@ public function getIndexQuery(bool $pickerMode = false, array $requestParams = [
$this->viewBuilder()
->getVar('vv_tz'));

// Pass any additional field filter confiration in the view
$this->set('vv_searchable_attributes_extras', $table->getSearchFiltersExtras());
if(!empty($searchableAttributes)) {
$this->set('vv_searchable_attributes', $searchableAttributes);

Expand Down Expand Up @@ -195,7 +197,7 @@ public function getIndexQuery(bool $pickerMode = false, array $requestParams = [
// Specific expressions per view
$query = match($requestParams['for'] ?? '') {
// GroupMembers Add view: We need to filter the active members
'GroupMembers' => $query->leftJoinWith('GroupMembers', fn($q) => $q->where(['GroupMembers.group_id' => (int)$requestParams['groupid'] ?? -1]))
'GroupMembers' => $query->leftJoinWith('GroupMembers', fn($q) => $q->where(['GroupMembers.group_id' => (int)($requestParams['groupid'] ?? -1)]))
->where($this->getTableLocator()->get('GroupMembers')->checkValidity($query))
->where(fn(QueryExpression $exp, Query $query) => $exp->isNull('GroupMembers.' . StringUtilities::classNameToForeignKey($table->getAlias()))),
// Just return the query
Expand Down
82 changes: 63 additions & 19 deletions app/src/Lib/Traits/SearchFilterTrait.php
Expand Up @@ -29,6 +29,7 @@

namespace App\Lib\Traits;

use App\Lib\Util\StringUtilities;
use Bake\Utility\Model\AssociationFilter;
use Cake\Database\Expression\QueryExpression;
use Cake\Http\ServerRequest;
Expand All @@ -37,9 +38,25 @@
use Cake\I18n\FrozenTime;

trait SearchFilterTrait {
// Array (and configuration) of permitted search filters
/**
* Array (and configuration) of permitted search filters
*
* @var array
*/
private array $searchFilters = [];
// Optional filter configuration that dictates display state and allows for related models

/**
* Extra Configurations for each filter
*
* @var array
*/
private array $searchFiltersExtras = [];

/**
* Optional filter configuration that dictates display state and allows for related models
*
* @var array
*/
private array $filterConfig = [];

/**
Expand Down Expand Up @@ -167,21 +184,30 @@ public function expressionsConstructor(Query $query, QueryExpression $exp, strin

$attributeWithModelPrefix = $modelPrefix . '.' . $attribute;


$search = $q;
// Use the `lower` function to apply uniformity for the search
$lower = $query->func()->lower([$attributeWithModelPrefix => 'identifier']);

// Handle special expression functions here
if(\in_array($search, ['isnull', 'isnotnull'])) {
return match($search) {
'isnull' => $exp->isNull($attributeWithModelPrefix),
'isnotnull' => $exp->isNotNull($attributeWithModelPrefix)
};
}


// XXX Strings and Enums are not treated the same. Enums require an exact match but strings
// are partially/non-case sensitive matched
// are partially/non-case sensitive matched
return match ($this->searchFilters[$attribute]['type']) {
'string' => $exp->like($lower, strtolower('%' . $search . '%')),
// Use the `lower` function to apply uniformity for the search
'string' => $exp->like($query->func()->lower([$attributeWithModelPrefix => 'identifier']),
strtolower('%' . $search . '%')),
'integer',
'boolean',
'parent' => $exp->add([$attributeWithModelPrefix => $search]),
'date' => $exp->add([$attributeWithModelPrefix => FrozenTime::parseDate($search, 'y-M-d')]),
'timestamp' => $this->constructDateComparisonClause($exp, $attributeWithModelPrefix, $search),
default => $exp->eq($lower, strtolower($search))
default => $exp->eq($query->func()->lower([$attributeWithModelPrefix => 'identifier']),
strtolower($search))
};
}

Expand Down Expand Up @@ -209,16 +235,24 @@ public function getSearchableAttributes(string $controller, string $vv_tz=null):
// Gather up related models defined in the $filterConfig
// XXX For now, we'll list these first - but we should probably provide a better way to order these.
foreach ($filterConfig as $field => $f) {
if($f['type'] == 'relatedModel') {
$fieldName = Inflector::classify(Inflector::underscore($field));
$this->searchFilters[$field] = [
'type' => 'string', // XXX for now - this needs to be looked up.
'label' => \App\Lib\Util\StringUtilities::columnKey($fieldName, $field, $vv_tz, true),
'active' => $f['active'] ?? true,
'model' => $f['model'],
'order' => $f['order']
];
$fieldName = Inflector::classify(Inflector::underscore($field));

if(isset($f['extras'])) {
$this->searchFiltersExtras[$field] = $f['extras'];
continue;
}

$filterType = $f['type'] ?? 'string';
if(\in_array($f['type'], ['isNull', 'isNotNull'])) {
$filterType = 'boolean';
}
$this->searchFilters[$field] = [
'type' => $filterType,
'label' => $f['label'] ?? StringUtilities::columnKey($fieldName, $field, $vv_tz, true),
'active' => $f['active'] ?? true,
'model' => $f['model'],
'order' => $f['order']
];
}

foreach ($this->filterMetadataFields() as $column => $type) {
Expand All @@ -239,7 +273,7 @@ public function getSearchableAttributes(string $controller, string $vv_tz=null):

$attribute = [
'type' => $type,
'label' => \App\Lib\Util\StringUtilities::columnKey($modelname, $column, $vv_tz, true),
'label' => StringUtilities::columnKey($modelname, $column, $vv_tz, true),
'active' => $fieldIsActive,
'order' => 99 // this is the default
];
Expand Down Expand Up @@ -270,7 +304,7 @@ public function getSearchableAttributes(string $controller, string $vv_tz=null):
}

/**
* Set explicilty defined filter configuration defined in the table class.
* Set explicit defined filter configuration defined in the table class.
*
* @since COmanage Registry v5.0.0
*/
Expand All @@ -279,4 +313,14 @@ public function setFilterConfig(array $filterConfig): void {
$this->filterConfig = $filterConfig;
}

/**
* Get field extra configurations calculated in getSearchableAttributes
*
* @since COmanage Registry v5.0.0
*/
public function getSearchFiltersExtras(): array
{
return $this->searchFiltersExtras;
}

}
2 changes: 1 addition & 1 deletion app/src/Lib/Util/FunctionUtilities.php
Expand Up @@ -43,7 +43,7 @@ class FunctionUtilities {
* // Chain of methods
* 'getRequest',
* 'getQuery' => [
* // parameter name => parameter value, We are taking advantage the named parameters feature
* // parameter name => parameter value, We are taking advantage of the named parameters feature
* 'name' =>'group_id'
* ],
* ]
Expand Down
23 changes: 23 additions & 0 deletions app/src/Model/Table/CousTable.php
Expand Up @@ -113,6 +113,29 @@ public function initialize(array $config): void {
'index' => ['platformAdmin', 'coAdmin']
]
]);

$this->setFilterConfig([
'identifier' => [
'type' => 'string',
'model' => 'Identifiers',
'active' => true,
'order' => 4
],
'parent_id' => [
// We want to keep the default column configuration and add extra functionality.
// Here the extra functionality is additional to select options since the parent_id
// is of type select
// XXX If the extras key is present, no other provided key will be evaluated. The rest
// of the configuration will be expected from the TableMetaTrait::filterMetadataFields()
'extras' => [
'options' => [
'isnotnull' => __d('operation','any'),
'isnull' => __d('operation','none'),
__d('information','table.list', 'COUs') => '@DATA@',
]
]
]
]);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions app/src/Model/Table/GroupMembersTable.php
Expand Up @@ -131,13 +131,13 @@ public function initialize(array $config): void {

$this->setFilterConfig([
'family' => [
'type' => 'relatedModel',
'type' => 'string',
'model' => 'People.Names',
'active' => true,
'order' => 2
],
'given' => [
'type' => 'relatedModel',
'type' => 'string',
'model' => 'People.Names',
'active' => true,
'order' => 1
Expand Down
38 changes: 20 additions & 18 deletions app/src/Model/Table/GroupNestingsTable.php
Expand Up @@ -96,24 +96,26 @@ public function initialize(array $config): void {
]
]);

$this->setAutoViewVars([
'groupMembers' => [
'type' => 'auxiliary',
'model' => 'GroupMembers',
'whereEval' => [
// Where Clause column name
'GroupMembers.group_id' => [
// Chain of methods that will construct the whereClause condition value
// Method that accepts no parameters
'getRequest',
// Method that accepts only one parameter
// getQuery(name: 'group_id')
'getQuery' => [
'name' =>'group_id'
]
]
]
]]);
// XXX Keeping for functionality reference
// $this->setAutoViewVars([
// 'groupMembers' => [
// 'type' => 'auxiliary',
// 'model' => 'GroupMembers',
// 'whereEval' => [
// // Where Clause column name
// 'GroupMembers.group_id' => [
// // Chain of methods that will construct the whereClause condition value
// // Method that accepts no parameters
// 'getRequest',
// // Method that accepts only one parameter
// // getQuery(name: 'group_id')
// 'getQuery' => [
// 'name' =>'group_id'
// ]
// ]
// ]
// ]]);

}

/**
Expand Down
2 changes: 1 addition & 1 deletion app/src/Model/Table/GroupsTable.php
Expand Up @@ -150,7 +150,7 @@ public function initialize(array $config): void {

$this->setFilterConfig([
'identifier' => [
'type' => 'relatedModel',
'type' => 'string',
'model' => 'Identifiers',
'active' => true,
'order' => 4
Expand Down
50 changes: 35 additions & 15 deletions app/src/Model/Table/IdentifiersTable.php
Expand Up @@ -30,6 +30,7 @@
namespace App\Model\Table;

use Cake\Event\EventInterface;
use Cake\ORM\Query;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use \App\Lib\Enum\SuspendableStatusEnum;
Expand Down Expand Up @@ -191,24 +192,43 @@ public function localAfterSave(\Cake\Event\EventInterface $event, \Cake\Datasour
/**
* Look up a Person ID from an identifier and identifier type ID.
* Only active Identifiers can be used for lookups.
*
* @since COmanage Registry v5.0.0
* @param int $typeId Identifier Type ID
* @param string $identifier Identifier
*
* @param int $typeId Identifier Type ID
* @param string $identifier Identifier
* @param int|null $coId CO Id
* @param bool $login The identifier is login enabled
*
* @return int Person ID
* @throws Cake\Datasource\Exception\RecordNotFoundException
* @since COmanage Registry v5.0.0
*/

public function lookupPerson(int $typeId, string $identifier): int {
$id = $this->find()
->where([
'identifier' => $identifier,
'type_id' => $typeId,
'status' => SuspendableStatusEnum::Active,
'person_id IS NOT NULL'
])
->firstOrFail();

public function lookupPerson(int $typeId, string $identifier, ?int $coId, bool $login=false): int {
$whereClause = [
'identifier' => $identifier,
'status' => SuspendableStatusEnum::Active,
'person_id IS NOT NULL'
];

if($typeId) {
$whereClause['type_id'] = $typeId;
}

if($login) {
$whereClause['login'] = true;
}

$query = $this->find()
->where($whereClause);

if($coId) {
$query->matching(
'People',
fn(QueryExpression $exp, Query $query) => $query->where(['People.co_id' => $coId])
);
}

$id = $query->firstOrFail();

return $id->person_id;
}

Expand Down