Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Feature cfm150 autocomplete (#167)
* Autocomplete widget added for selecting a Group member (CFM-150) (#164)

* add primevue autocomplete library dependency

* Improve/cleanup backend block search functionality

* Decloupe where clause construction from join clause construction

* Improve filtering.Dynamically construct where clause using QueryExpressions.

* Introduce IndexQueryTrait

* fix broken mveaType query.Improve Changelog.

* first get request

* add CoSettings PeoplePicker Configuration

* fix condition

* Improve FieldHelper

* Properly align grouped html controls

* Add type to emails and identifiers

* Add loader.Improve fetch response handling

* Added loader. Fixed duplicates.

* typo fix

* add filtering with given and family name. Fetch only active users.

* refactor frontend error handling

* add subfield-col css rule

* Peoplepikcer enum

* Provide access to configurations locally and globally.Improve fetch implementation.

* fix url string

* fix provide/inject functionality

* Add a subcomponent for item-with-type and style things for easier visual parsing and disambiguation (CFM-150)

* highlight query string

* highlighting fix

* sort methods by name

* Add item-id to autocomplete label and update color file (CFM-150)

* Add pager link "show more" to autocomplete (CFM-150)

* Fix label id reference for autocomplete field (CFM-150)

* Implement pagination

* filter the active GroupMembers

* Refactor GroupMembersTable::isMember Query construction

* Update Group Member listing to expose add / edit / view in a modal window; refactor setLayout to better accommodate multiple actions; carry over person picker code in preparation for moving the person picker ot the index of the Group Member list. (CFM-150)

* Move the person picker to the index of the Group Member list and launch "Add" from the picker. (CFM-150)

* Add scrolling to the autocomplete people picker (CFM-150)

* removed trailing semi colon

* Change default modal title (CFM-150)

---------

Co-authored-by: Arlen Johnson <arlen@sphericalcowgroup.com>
Ioannis and arlen committed Mar 21, 2024
1 parent 4ebaba8 commit 46e4df3
Showing 48 changed files with 2,190 additions and 425 deletions.
5 changes: 4 additions & 1 deletion app/config/schema/schema.json
@@ -111,7 +111,10 @@
"required_fields_address": { "type": "string", "size": 160 },
"required_fields_name": { "type": "string", "size": 160 },
"search_global_limit": { "type": "integer" },
"search_global_limited_models": { "type": "boolean" }
"search_global_limited_models": { "type": "boolean" },
"person_picker_email_type": { "type": "integer" },
"person_picker_identifier_type": { "type": "integer" },
"person_picker_display_types": { "type": "boolean" }
},
"indexes": {
"co_settings_i1": { "columns": [ "co_id" ]},
15 changes: 15 additions & 0 deletions app/resources/locales/en_US/field.po
@@ -101,6 +101,9 @@ msgstr "Display Name"
msgid "edupersonaffiliation"
msgstr "eduPersonAffiliation"

msgid "email"
msgstr "Email"

msgid "ends_at"
msgstr "Ends at:"

@@ -125,6 +128,9 @@ msgstr "Given Name"
msgid "group_membership"
msgstr "{0} Membership in {1}"

msgid "hidden"
msgstr "Hidden"

msgid "honorific"
msgstr "Honorific"

@@ -342,6 +348,15 @@ msgstr "Name Permitted Fields"
msgid "CoSettings.permitted_fields_telephone_number"
msgstr "Telephone Number Permitted Fields"

msgid "CoSettings.person_picker_display_fields"
msgstr "Person Picker Display Fields"

msgid "CoSettings.person_picker_display_fields.desc"
msgstr "This determines what fields to display alongside the person name when using the autocomplete Person Picker (e.g. for selecting Group members)."

msgid "CoSettings.person_picker_display_types"
msgstr "Display field types in the picker"

msgid "CoSettings.required_fields_address"
msgstr "Address Required Fields"

18 changes: 18 additions & 0 deletions app/resources/locales/en_US/operation.po
@@ -33,6 +33,24 @@ msgstr "Add"
msgid "add.a"
msgstr "Add a New {0}"

msgid "add.bulk"
msgstr "Bulk add"

msgid "add.member"
msgstr "Add member: "

msgid "autocomplete.pager.show.more"
msgstr "show more"

msgid "autocomplete.people.desc"
msgstr "Begin typing to find a person (use at least {0} characters from a name, email address, or identifier)"

msgid "autocomplete.people.label"
msgstr "Search for a person"

msgid "autocomplete.people.placeholder"
msgstr "enter name, email address, or identifier"

msgid "api.key.generate"
msgstr "Generate API Key"

93 changes: 41 additions & 52 deletions app/src/Controller/ApiV2Controller.php
@@ -41,7 +41,8 @@

class ApiV2Controller extends AppController {
use \App\Lib\Traits\LabeledLogTrait;

use \App\Lib\Traits\IndexQueryTrait;

/**
* Perform Cake Controller initialization.
*
@@ -77,6 +78,8 @@ public function initialize(): void {
public function add() {
// $this->name = Models
$modelsName = $this->name;
// $table = the actual table object
$table = $this->$modelsName;
// $tableName = models
$tableName = $this->tableName;

@@ -144,15 +147,20 @@ public function beforeRender(\Cake\Event\EventInterface $event) {
public function delete($id) {
// $this->name = Models (ie: from ModelsTable)
$modelsName = $this->name;

// $table = the actual table object
$table = $this->$modelsName;
// $tableName = models
$tableName = $table->getTable();


// Make sure the requested object exists
try {
$obj = $this->$modelsName->findById($id)->firstOrFail();
$obj = $table->findById($id)->firstOrFail();

// XXX document AR-CO-1 when we implement hard delete/changelog
// note similar logic in StandardController
$this->$modelsName->deleteOrFail($obj);
$table->deleteOrFail($obj);

if(method_exists($obj, "isReadOnly") && $obj->isReadOnly()) {
throw new BadRequestException(__d('error', 'edit.readonly'));
}
@@ -184,10 +192,12 @@ public function delete($id) {
public function edit($id) {
// $this->name = Models (ie: from ModelsTable)
$modelsName = $this->name;
// $table = the actual table object
$table = $this->$modelsName;
// $tableName = models
$tableName = $this->$modelsName->getTable();
$tableName = $table->getTable();

$query = $this->$modelsName->findById($id);
$query = $table->findById($id);

try {
// Pull the current record
@@ -203,14 +213,14 @@ public function edit($id) {
throw new BadRequestException(__d('error', 'api.object', [$modelsName]));
}

$obj = $this->$modelsName->patchEntity($obj, $json[$modelsName]);
$obj = $table->patchEntity($obj, $json[$modelsName]);

$this->$modelsName->saveOrFail($obj);
$table->saveOrFail($obj);

// Trigger provisioning, letting errors bubble up (AR-GMR-5)
if(method_exists($this->$modelsName, "requestProvisioning")) {
if(method_exists($table, "requestProvisioning")) {
$this->llog('rule', "AR-GMR-5 Requesting provisioning for $modelsName " . $obj->id);
$this->$modelsName->requestProvisioning(id: $obj->id, context: ProvisioningContextEnum::Automatic);
$table->requestProvisioning(id: $obj->id, context: ProvisioningContextEnum::Automatic);
}

// Let the view render
@@ -224,7 +234,6 @@ public function edit($id) {
$err = $this->exceptionToError($e);

$this->llog('debug', $err);
$results[] = ['error' => $err];

throw new BadRequestException($this->exceptionToError($e));
}
@@ -293,48 +302,26 @@ public function generateApiKey(string $id) {
public function index() {
// $modelsName = Models
$modelsName = $this->name;

$query = $this->$modelsName->find();

// PrimaryLinkTrait
$link = $this->getPrimaryLink(true);

// We automatically allow API calls to be filtered on primary link
if(!empty($link->attr) && !empty($link->value)) {
$query = $query->where([$this->$modelsName->getAlias().'.'.$link->attr => $link->value]);
}
// $table = the actual table object
$table = $this->$modelsName;

// This will produce a nested object which is very useful for vue integration
if($this->request->getQuery('extended') !== null) {
$modelContain = [];
$associations = $this->$modelsName->associations();
foreach($associations->getByType(['BelongsTo']) as $a) {
$modelContain[] = $a->getClassName();
}

if(!empty($modelContain)) {
$query = $query->contain($modelContain);
$reqParameters = [];
$pickerMode = false;
if($this->request->is('ajax')) {
$reqParameters = [...$this->request->getQuery()];
if($this->request->getQuery('picker') !== null) {
$pickerMode = filter_var($this->request->getQuery('picker'), FILTER_VALIDATE_BOOLEAN);
}
}

if($modelsName == 'AuthenticationEvents') {
// Special case for filtering on authenticated identifier. There is a
// similar filter in AuthenticationEventsController::beforeFilter.
// If other special cases show up this should get refactored into a trait
// populated by the table (or something similar).

if($this->getRequest()->getQuery('authenticated_identifier')) {
$query = $query->where(['authenticated_identifier' => \App\Lib\Util\StringUtilities::urlbase64decode($this->getRequest()->getQuery('authenticated_identifier'))]);
} else {
// We only allow unfiltered queries for platform users

if(!$this->RegistryAuth->isPlatformAdmin()) {
throw new \InvalidArgumentException(__d('error', 'input.notprov', 'authenticated_identifier'));
}
}

// Construct the Query
$query = $this->getIndexQuery($pickerMode, $reqParameters);

if(method_exists($table, 'findIndexed')) {
$query = $table->findIndexed($query);
}

// This magically makes REST calls paginated... can use eg direction=,
// This magically makes REST calls paginated... can use eg direction=,
// sort=, limit=, page=
$this->set($this->tableName, $this->paginate($query));

@@ -351,14 +338,16 @@ public function index() {
public function view($id = null) {
// $this->name = Models
$modelsName = $this->name;
// $table = the actual table object
$table = $this->$modelsName;
// $tableName = models
$tableName = $this->$modelsName->getTable();
$tableName = $table->getTable();

if(empty($id)) {
throw new InvalidArgumentException(__d('error', 'notprov', ['id']));
}

$obj = $this->$modelsName->findById($id)->firstOrFail();
$obj = $table->findById($id)->firstOrFail();

$this->set($tableName, [$obj]);

32 changes: 29 additions & 3 deletions app/src/Controller/GroupMembersController.php
@@ -30,6 +30,8 @@
namespace App\Controller;

// XXX not doing anything with Log yet
use Cake\Event\EventInterface;
use Cake\Http\Response;
use Cake\Log\Log;

class GroupMembersController extends StandardController {
@@ -38,15 +40,17 @@ class GroupMembersController extends StandardController {
'People.primary_name.name' => 'asc'
]
];

/**
* Callback run prior to the request render.
*
* @param EventInterface $event Cake Event
*
* @return Response|void
* @since COmanage Registry v5.0.0
* @param EventInterface $event Cake Event
*/

public function beforeRender(\Cake\Event\EventInterface $event) {
public function beforeRender(EventInterface $event) {
// Pull the Group name for breadcrumb rendering

$link = $this->getPrimaryLink(true);
@@ -58,4 +62,26 @@ public function beforeRender(\Cake\Event\EventInterface $event) {

return parent::beforeRender($event);
}

/**
* Handle an add action for a Group Member.
*
* @since COmanage Registry v5.0.0
*/

public function add() {
// If we have a person_id in the request, the person has been pre-selected.
if(!empty($this->request->getQuery('person_id'))) {
$personId = $this->request->getQuery('person_id');
$Names = $this->getTableLocator()->get('Names');
$personName = $Names->primaryName((int)$personId)->full_name;
$selectedPerson = [
'id' => $personId,
'name' => $personName
];
$this->set('vv_selected_person', $selectedPerson);
}

return parent::add();
}
}

0 comments on commit 46e4df3

Please sign in to comment.