From c0fcc6405f539f4cddb380797d924767e403b448 Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Sat, 23 Oct 2021 18:02:44 -0400 Subject: [PATCH] SearchKit - Use server-side preprocessing for editable fields --- Civi/Api4/Generic/AbstractAction.php | 3 - Civi/Api4/Query/Api4SelectQuery.php | 10 ++- .../SearchDisplay/AbstractRunAction.php | 65 +++++++++++++++++-- ext/search_kit/Civi/Search/Admin.php | 1 - .../crmSearchAdminDisplay.component.js | 32 ++------- .../displays/colType/field.html | 4 +- .../ang/crmSearchDisplay/colType/field.html | 4 +- .../crmSearchDisplayEditable.component.js | 28 ++++---- 8 files changed, 92 insertions(+), 55 deletions(-) diff --git a/Civi/Api4/Generic/AbstractAction.php b/Civi/Api4/Generic/AbstractAction.php index 08daf23954..a531cddc72 100644 --- a/Civi/Api4/Generic/AbstractAction.php +++ b/Civi/Api4/Generic/AbstractAction.php @@ -430,9 +430,6 @@ abstract class AbstractAction implements \ArrayAccess { public function entityFields() { if (!$this->_entityFields) { $allowedTypes = ['Field', 'Filter', 'Extra']; - if (method_exists($this, 'getCustomGroup')) { - $allowedTypes[] = 'Custom'; - } $getFields = \Civi\API\Request::create($this->getEntityName(), 'getFields', [ 'version' => 4, 'checkPermissions' => FALSE, diff --git a/Civi/Api4/Query/Api4SelectQuery.php b/Civi/Api4/Query/Api4SelectQuery.php index ff1590c32e..aee952cc6b 100644 --- a/Civi/Api4/Query/Api4SelectQuery.php +++ b/Civi/Api4/Query/Api4SelectQuery.php @@ -722,12 +722,14 @@ class Api4SelectQuery { $joinEntityFields = $joinEntityGet->entityFields(); foreach ($joinEntityFields as $field) { $field['sql_name'] = '`' . $alias . '`.`' . $field['column_name'] . '`'; + $field['explicit_join'] = $alias; $this->addSpecField($alias . '.' . $field['name'], $field); } $tableName = CoreUtil::getTableName($entity); // Save join info to be retrieved by $this->getExplicitJoin() $this->explicitJoins[$alias] = [ 'entity' => $entity, + 'alias' => $alias, 'table' => $tableName, 'bridge' => NULL, ]; @@ -938,6 +940,7 @@ class Api4SelectQuery { // For INNER joins, these fields get a sql alias pointing to the bridge entity, // but an api alias pretending they belong to the join entity. $field['sql_name'] = '`' . ($side === 'LEFT' ? $alias : $bridgeAlias) . '`.`' . $field['column_name'] . '`'; + $field['explicit_join'] = $alias; $this->addSpecField($alias . '.' . $name, $field); if ($field['type'] === 'Field') { $fakeFields[$field['column_name']] = '`' . $bridgeAlias . '`.`' . $field['column_name'] . '`'; @@ -1094,6 +1097,8 @@ class Api4SelectQuery { else { $fieldArray['sql_name'] = '`' . $baseTableAlias . '`.`' . $link->getBaseColumn() . '`'; } + $fieldArray['implicit_join'] = $link->getBaseColumn(); + $fieldArray['explicit_join'] = $explicitJoin ? $explicitJoin['alias'] : NULL; // Custom fields will already have the group name prefixed $fieldName = $isCustom ? explode('.', $fieldArray['name'])[1] : $fieldArray['name']; $this->addSpecField($joinTreeNode[$joinName]['#path'] . $fieldName, $fieldArray); @@ -1252,7 +1257,10 @@ class Api4SelectQuery { $this->apiFieldSpec[$path] = FALSE; return; } - $this->apiFieldSpec[$path] = $field; + $this->apiFieldSpec[$path] = $field + [ + 'implicit_join' => NULL, + 'explicit_join' => NULL, + ]; } /** diff --git a/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php b/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php index a9e48d58c1..d807e3447d 100644 --- a/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php +++ b/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php @@ -275,10 +275,64 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction { return \CRM_Utils_System::url($path, NULL, $absolute, NULL, FALSE); } + /** + * @param $column + * @param $data + * @return array{entity: string, input_type: string, data_type: string, options: bool, serialize: bool, fk_entity: string, value_key: string, record: array, value: mixed}|null + */ private function formatEditableColumn($column, $data) { + $editable = $this->getEditableInfo($column['key']); + if (!empty($data[$editable['id_path']])) { + $editable['record'] = [ + $editable['id_key'] => $data[$editable['id_path']], + ]; + $editable['value'] = $data[$editable['value_path']]; + \CRM_Utils_Array::remove($editable, 'id_key', 'id_path', 'value_path'); + return $editable; + } + return NULL; + } + /** + * @param $key + * @return array{entity: string, input_type: string, data_type: string, options: bool, serialize: bool, fk_entity: string, value_key: string, value_path: string, id_key: string, id_path: string}|null + */ + private function getEditableInfo($key) { + [$key] = explode(':', $key); + $field = $this->getField($key); + // If field is an implicit join, use the original fk field + if (!empty($field['implicit_join'])) { + return $this->getEditableInfo(substr($key, 0, -1 - strlen($field['name']))); + } + if ($field) { + $idKey = CoreUtil::getIdFieldName($field['entity']); + $idPath = ($field['explicit_join'] ? $field['explicit_join'] . '.' : '') . $idKey; + // Hack to support editing relationships + if ($field['entity'] === 'RelationshipCache') { + $field['entity'] = 'Relationship'; + $idPath = ($field['explicit_join'] ? $field['explicit_join'] . '.' : '') . 'relationship_id'; + } + return [ + 'entity' => $field['entity'], + 'input_type' => $field['input_type'], + 'data_type' => $field['data_type'], + 'options' => !empty($field['options']), + 'serialize' => !empty($field['serialize']), + 'fk_entity' => $field['fk_entity'], + 'value_key' => $field['name'], + 'value_path' => $key, + 'id_key' => $idKey, + 'id_path' => $idPath, + ]; + } + return NULL; } + /** + * @param $column + * @param $data + * @return array{url: string, width: int, height: int} + */ private function formatImage($column, $data) { $tokenExpr = $column['rewrite'] ?: '[' . $column['key'] . ']'; return [ @@ -575,10 +629,13 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction { $possibleTokens .= implode('', array_column($column['links'], 'text')); } - // Select value fields for in-place editing - if (isset($column['editable']['value'])) { - $additions[] = $column['editable']['value']; - $additions[] = $column['editable']['id']; + // Select id & value for in-place editing + if (!empty($column['editable'])) { + $editable = $this->getEditableInfo($column['key']); + if ($editable) { + $additions[] = $editable['value_path']; + $additions[] = $editable['id_path']; + } } } // Add fields referenced via token diff --git a/ext/search_kit/Civi/Search/Admin.php b/ext/search_kit/Civi/Search/Admin.php index 1e05fa1a57..5a912e3c7d 100644 --- a/ext/search_kit/Civi/Search/Admin.php +++ b/ext/search_kit/Civi/Search/Admin.php @@ -122,7 +122,6 @@ class Admin { $field['fieldName'] = $field['name']; // Hack for RelationshipCache to make Relationship fields editable if ($entity['name'] === 'RelationshipCache') { - $entity['primary_key'] = ['relationship_id']; if (in_array($field['name'], ['is_active', 'start_date', 'end_date'])) { $field['readonly'] = FALSE; } diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminDisplay.component.js b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminDisplay.component.js index 22219d6672..9aea30e3d9 100644 --- a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminDisplay.component.js +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminDisplay.component.js @@ -156,37 +156,12 @@ this.toggleEditable = function(col) { if (col.editable) { delete col.editable; - return; - } - - var info = searchMeta.parseExpr(col.key), - arg = _.findWhere(info.args, {type: 'field'}) || {}, - value = col.key.split(':')[0]; - if (!arg.field || info.fn) { - delete col.editable; - return; - } - // If field is an implicit join, use the original fk field - if (arg.field.name !== arg.field.fieldName) { - value = value.substr(0, value.lastIndexOf('.')); - info = searchMeta.parseExpr(value); - arg = info.args[0]; + } else { + col.editable = true; } - col.editable = { - // Hack to support editing relationships - entity: arg.field.entity.replace('RelationshipCache', 'Relationship'), - input_type: arg.field.input_type, - data_type: arg.field.data_type, - options: !!arg.field.options, - serialize: !!arg.field.serialize, - fk_entity: arg.field.fk_entity, - id: arg.prefix + searchMeta.getEntity(arg.field.entity).primary_key[0], - name: arg.field.name, - value: value - }; }; - this.isEditable = function(col) { + this.canBeEditable = function(col) { var expr = ctrl.getExprFromSelect(col.key), info = searchMeta.parseExpr(expr); return !col.image && !col.rewrite && !col.link && !info.fn && info.args[0] && info.args[0].field && !info.args[0].field.readonly; @@ -213,6 +188,7 @@ if (column.link) { ctrl.onChangeLink(column, column.link.path, ''); } else { + delete column.editable; var defaultLink = ctrl.getLinks(column.key)[0]; column.link = {path: defaultLink ? defaultLink.path : 'civicrm/'}; ctrl.onChangeLink(column, null, column.link.path); diff --git a/ext/search_kit/ang/crmSearchAdmin/displays/colType/field.html b/ext/search_kit/ang/crmSearchAdmin/displays/colType/field.html index 801c3d3108..414eba6b30 100644 --- a/ext/search_kit/ang/crmSearchAdmin/displays/colType/field.html +++ b/ext/search_kit/ang/crmSearchAdmin/displays/colType/field.html @@ -43,11 +43,11 @@
-