Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Afform - SearchKit support for calculated fields #19612

Merged
merged 1 commit into from
Feb 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Civi/Api4/Query/Api4SelectQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ public function __construct($apiGet) {
// Add ACLs first to avoid redundant subclauses
$baoName = CoreUtil::getBAOFromApiName($this->getEntity());
$this->query->where($this->getAclClause(self::MAIN_TABLE_ALIAS, $baoName));

// Add explicit joins. Other joins implied by dot notation may be added later
$this->addExplicitJoins();
}

/**
Expand All @@ -113,8 +116,6 @@ public function __construct($apiGet) {
* @throws \CRM_Core_Exception
*/
public function getSql() {
// Add explicit joins. Other joins implied by dot notation may be added later
$this->addExplicitJoins();
$this->buildSelectClause();
$this->buildWhereClause();
$this->buildOrderBy();
Expand Down Expand Up @@ -152,7 +153,6 @@ public function run() {
* @throws \API_Exception
*/
public function getCount() {
$this->addExplicitJoins();
$this->buildWhereClause();
// If no having or groupBy, we only need to select count
if (!$this->getHaving() && !$this->getGroupBy()) {
Expand Down
48 changes: 48 additions & 0 deletions ext/afform/admin/Civi/Api4/Action/Afform/LoadAdminData.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

use Civi\AfformAdmin\AfformAdminMeta;
use Civi\Api4\Afform;
use Civi\Api4\Entity;
use Civi\Api4\Query\SqlExpression;

/**
* This action is used by the Afform Admin extension to load metadata for the Admin GUI.
Expand Down Expand Up @@ -176,6 +178,7 @@ public function _run(\Civi\Api4\Generic\Result $result) {
->addWhere('saved_search.name', '=', $displayTag['search-name'])
->addSelect('*', 'type:name', 'type:icon', 'saved_search.name', 'saved_search.api_entity', 'saved_search.api_params')
->execute()->first();
$display['calc_fields'] = $this->getCalcFields($display['saved_search.api_entity'], $display['saved_search.api_params']);
$info['search_displays'][] = $display;
if ($newForm) {
$info['definition']['layout'][0]['#children'][] = $displayTag + ['#tag' => $display['type:name']];
Expand Down Expand Up @@ -240,6 +243,51 @@ private function loadAvailableBlocks($entities, &$info, $where = []) {
}
}

/**
* @param string $apiEntity
* @param array $apiParams
* @return array
*/
private function getCalcFields($apiEntity, $apiParams) {
$calcFields = [];
$api = \Civi\API\Request::create($apiEntity, 'get', $apiParams);
$selectQuery = new \Civi\Api4\Query\Api4SelectQuery($api);
$joinMap = $joinCount = [];
foreach ($apiParams['join'] ?? [] as $join) {
[$entityName, $alias] = explode(' AS ', $join[0]);
$num = '';
if (!empty($joinCount[$entityName])) {
$num = ' ' . (++$joinCount[$entityName]);
}
else {
$joinCount[$entityName] = 1;
}
$label = Entity::get(FALSE)
->addWhere('name', '=', $entityName)
->addSelect('title')
->execute()->first()['title'];
$joinMap[$alias] = $label . $num;
}

foreach ($apiParams['select'] ?? [] as $select) {
if (strstr($select, ' AS ')) {
$expr = SqlExpression::convert($select, TRUE);
$field = $expr->getFields() ? $selectQuery->getField($expr->getFields()[0]) : NULL;
$joinName = explode('.', $expr->getFields()[0] ?? '')[0];
$label = $expr::getTitle() . ': ' . (isset($joinMap[$joinName]) ? $joinMap[$joinName] . ' ' : '') . $field['title'];
$calcFields[] = [
'#tag' => 'af-field',
'name' => $expr->getAlias(),
'defn' => [
'label' => $label,
'input_type' => 'Text',
],
];
}
}
return $calcFields;
}

public function fields() {
return [
[
Expand Down
11 changes: 11 additions & 0 deletions ext/afform/admin/ang/afGuiEditor/afGuiSearch.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
var ctrl = this;
$scope.controls = {};
$scope.fieldList = [];
$scope.calcFieldList = [];
$scope.blockList = [];
$scope.blockTitles = [];
$scope.elementList = [];
Expand All @@ -22,11 +23,21 @@

this.buildPaletteLists = function() {
var search = $scope.controls.fieldSearch ? $scope.controls.fieldSearch.toLowerCase() : null;
buildCalcFieldList(search);
buildFieldList(search);
buildBlockList(search);
buildElementList(search);
};

function buildCalcFieldList(search) {
$scope.calcFieldList.length = 0;
_.each(_.cloneDeep(ctrl.display.calc_fields), function(field) {
if (!search || _.contains(field.defn.label.toLowerCase(), search)) {
$scope.calcFieldList.push(field);
}
});
}

function buildBlockList(search) {
$scope.blockList.length = 0;
$scope.blockTitles.length = 0;
Expand Down
8 changes: 8 additions & 0 deletions ext/afform/admin/ang/afGuiEditor/afGuiSearch.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@
</div>
</div>
</div>
<div ng-if="calcFieldList.length">
<label>{{:: ts('Calculated Fields') }}</label>
<div ui-sortable="{update: buildPaletteLists, items: '&gt; div:not(.disabled)', connectWith: '[ui-sortable]', placeholder: 'af-gui-dropzone'}" ui-sortable-update="$ctrl.editor.onDrop" ng-model="calcFieldList">
<div ng-repeat="field in calcFieldList" ng-class="{disabled: fieldInUse(field.name)}">
{{:: field.defn.label }}
</div>
</div>
</div>
<div ng-repeat="fieldGroup in fieldList">
<div ng-if="fieldGroup.fields.length">
<label>{{:: fieldGroup.label }}</label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@
$scope.meta = afGui.meta;
};

// $scope.getEntity = function() {
// return ctrl.editor ? ctrl.editor.getEntity(ctrl.container.getEntityName()) : {};
// };

// Returns the original field definition from metadata
this.getDefn = function() {
return ctrl.editor ? afGui.getField(ctrl.container.getFieldEntityType(ctrl.node.name), ctrl.node.name) : {};
var defn = afGui.getField(ctrl.container.getFieldEntityType(ctrl.node.name), ctrl.node.name);
return defn || {
label: ts('Untitled'),
requred: false,
input_attrs: []
};
};

$scope.getOriginalLabel = function() {
Expand Down
20 changes: 19 additions & 1 deletion ext/search/Civi/Api4/Action/SearchDisplay/Run.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,26 @@ private function applyFilters() {
foreach ($this->filters as $fieldName => $value) {
if ($value) {
$field = $this->getField($fieldName) ?? [];
$dataType = $field['data_type'] ?? NULL;

// If the field doesn't exist, it could be an aggregated column
if (!$field) {
// Not a real field but in the SELECT clause. It must be an aggregated column. Add to HAVING clause.
if (in_array($fieldName, $this->getSelectAliases())) {
if ($prefixWithWildcard) {
$this->savedSearch['api_params']['having'][] = [$fieldName, 'CONTAINS', $value];
}
else {
$this->savedSearch['api_params']['having'][] = [$fieldName, 'LIKE', $value . '%'];
}
}
// Error - field doesn't exist and isn't a column alias
else {
// Maybe throw an exception? Or just log a warning?
}
continue;
}

$dataType = $field['data_type'];
if (!empty($field['serialize'])) {
$this->savedSearch['api_params']['where'][] = [$fieldName, 'CONTAINS', $value];
}
Expand Down