From 2fe33e6c64653c93325cb69f098f5905ed7520b0 Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Thu, 9 Sep 2021 09:11:28 -0400 Subject: [PATCH] SearchKit - Fix display of links in aggregated columns --- .../SearchDisplay/AbstractRunAction.php | 27 +++++++++++++++++++ .../crmSearchAdmin.component.js | 5 ++-- .../crmSearchAdminDisplay.component.js | 8 ++++++ .../crmSearchFunction.component.js | 2 +- .../displays/colType/field.html | 2 +- .../crmSearchAdminResultsTable.component.js | 2 +- .../api/v4/SearchDisplay/SearchAfformTest.php | 4 +-- 7 files changed, 43 insertions(+), 7 deletions(-) diff --git a/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php b/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php index cdd605f723..f1578097d2 100644 --- a/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php +++ b/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php @@ -364,9 +364,36 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction { preg_match_all('/\\[([^]]+)\\]/', $possibleTokens, $tokens); // Only add fields not already in SELECT clause $additions = array_diff(array_merge($additions, $tokens[1]), $existing); + // Tokens for aggregated columns start with 'GROUP_CONCAT_' + foreach ($additions as $index => $alias) { + if (strpos($alias, 'GROUP_CONCAT_') === 0) { + $additions[$index] = 'GROUP_CONCAT(' . $this->getJoinFromAlias(explode('_', $alias, 3)[2]) . ') AS ' . $alias; + } + } $apiParams['select'] = array_unique(array_merge($apiParams['select'], $additions)); } + /** + * Given an alias like Contact_Email_01_location_type_id + * this will return Contact_Email_01.location_type_id + * @param string $alias + * @return string + */ + protected function getJoinFromAlias(string $alias) { + $result = ''; + foreach ($this->savedSearch['api_params']['join'] ?? [] as $join) { + $joinName = explode(' AS ', $join[0])[1]; + if (strpos($alias, $joinName) === 0) { + $parsed = $joinName . '.' . substr($alias, strlen($joinName) + 1); + // Ensure we are using the longest match + if (strlen($parsed) > strlen($result)) { + $result = $parsed; + } + } + } + return $result; + } + /** * Checks if a filter contains a non-empty value * diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdmin.component.js b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdmin.component.js index b09294fb4a..036458122e 100644 --- a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdmin.component.js +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdmin.component.js @@ -300,7 +300,7 @@ if (ctrl.canAggregate(col)) { // Ensure all non-grouped columns are aggregated if using GROUP BY if (!info.fn || info.fn.category !== 'aggregate') { - ctrl.savedSearch.api_params.select[pos] = ctrl.DEFAULT_AGGREGATE_FN + '(DISTINCT ' + fieldExpr + ') AS ' + ctrl.DEFAULT_AGGREGATE_FN + '_DISTINCT_' + fieldExpr.replace(/[.:]/g, '_'); + ctrl.savedSearch.api_params.select[pos] = ctrl.DEFAULT_AGGREGATE_FN + '(DISTINCT ' + fieldExpr + ') AS ' + ctrl.DEFAULT_AGGREGATE_FN + '_' + fieldExpr.replace(/[.:]/g, '_'); } } else { // Remove aggregate functions when no grouping @@ -620,11 +620,12 @@ joinEntity = searchMeta.getEntity(join.entity), primaryKey = joinEntity.primary_key[0], isAggregate = ctrl.canAggregate(join.alias + '.' + primaryKey), + joinPrefix = (isAggregate ? 'GROUP_CONCAT_' : '') + join.alias + '.', bridgeEntity = _.isString(joinClause[2]) ? searchMeta.getEntity(joinClause[2]) : null; _.each(joinEntity.paths, function(path) { var link = _.cloneDeep(path); link.isAggregate = isAggregate; - link.path = link.path.replace(/\[/g, '[' + join.alias + '.'); + link.path = link.path.replace(/\[/g, '[' + joinPrefix).replace(/[.:]/g, '_'); link.join = join.alias; addTitle(link, join.label); links.push(link); diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminDisplay.component.js b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminDisplay.component.js index a8e6ebe14f..6ea6b50e8f 100644 --- a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminDisplay.component.js +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminDisplay.component.js @@ -181,6 +181,14 @@ return !col.image && !col.rewrite && !col.link && !info.fn && info.field && !info.field.readonly; }; + // Aggregate functions (COUNT, AVG, MAX) cannot display as links, except for GROUP_CONCAT + // which gets special treatment in APIv4 to convert it to an array. + this.canBeLink = function(col) { + var expr = ctrl.getExprFromSelect(col.key), + info = searchMeta.parseExpr(expr); + return !info.fn || info.fn.category !== 'aggregate' || info.fn.name === 'GROUP_CONCAT'; + }; + this.toggleLink = function(column) { if (column.link) { ctrl.onChangeLink(column, column.link.path, ''); diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchFunction.component.js b/ext/search_kit/ang/crmSearchAdmin/crmSearchFunction.component.js index 7ea57ecd4b..b1d31d5675 100644 --- a/ext/search_kit/ang/crmSearchAdmin/crmSearchFunction.component.js +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchFunction.component.js @@ -87,7 +87,7 @@ // Make a sql-friendly alias for this expression function makeAlias() { - return (ctrl.fn + '_' + (ctrl.modifier ? ctrl.modifier + '_' : '') + ctrl.path).replace(/[.:]/g, '_'); + return (ctrl.fn + '_' + ctrl.path).replace(/[.:]/g, '_'); } this.writeExpr = function() { diff --git a/ext/search_kit/ang/crmSearchAdmin/displays/colType/field.html b/ext/search_kit/ang/crmSearchAdmin/displays/colType/field.html index ddc0383657..278ed6ffee 100644 --- a/ext/search_kit/ang/crmSearchAdmin/displays/colType/field.html +++ b/ext/search_kit/ang/crmSearchAdmin/displays/colType/field.html @@ -1,4 +1,4 @@ -
+