From: Coleman Watts Date: Sun, 19 Sep 2021 03:32:04 +0000 (-0400) Subject: Add CONCAT_WS fn to APIv4 and UI support in SearchKit X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=2929fd34b84a1b3d5ebbba8855f01b9f73363dd0;p=civicrm-core.git Add CONCAT_WS fn to APIv4 and UI support in SearchKit --- diff --git a/Civi/Api4/Query/SqlFunctionCOALESCE.php b/Civi/Api4/Query/SqlFunctionCOALESCE.php index fc5765f6df..5264e04ef1 100644 --- a/Civi/Api4/Query/SqlFunctionCOALESCE.php +++ b/Civi/Api4/Query/SqlFunctionCOALESCE.php @@ -25,6 +25,10 @@ class SqlFunctionCOALESCE extends SqlFunction { [ 'max_expr' => 99, 'optional' => FALSE, + 'ui_defaults' => [ + ['type' => 'SqlField', 'placeholder' => ts('If')], + ['type' => 'SqlField', 'placeholder' => ts('Else')], + ], ], ]; } diff --git a/Civi/Api4/Query/SqlFunctionCONCAT.php b/Civi/Api4/Query/SqlFunctionCONCAT.php index 4d6437eb4a..3659fa2d13 100644 --- a/Civi/Api4/Query/SqlFunctionCONCAT.php +++ b/Civi/Api4/Query/SqlFunctionCONCAT.php @@ -26,6 +26,9 @@ class SqlFunctionCONCAT extends SqlFunction { 'max_expr' => 99, 'optional' => FALSE, 'must_be' => ['SqlField', 'SqlString'], + 'ui_defaults' => [ + ['placeholder' => ts('Plus')], + ], ], ]; } @@ -34,14 +37,14 @@ class SqlFunctionCONCAT extends SqlFunction { * @return string */ public static function getTitle(): string { - return ts('Combine text'); + return ts('Combine if'); } /** * @return string */ public static function getDescription(): string { - return ts('Multiple values concatenated into a single string.'); + return ts('Joined text, only if all values are not null.'); } } diff --git a/Civi/Api4/Query/SqlFunctionCONCAT_WS.php b/Civi/Api4/Query/SqlFunctionCONCAT_WS.php new file mode 100644 index 0000000000..d381baf701 --- /dev/null +++ b/Civi/Api4/Query/SqlFunctionCONCAT_WS.php @@ -0,0 +1,51 @@ + 99, + 'optional' => FALSE, + 'must_be' => ['SqlField', 'SqlString'], + 'ui_defaults' => [ + ['type' => 'SqlString', 'placeholder' => ts('Separator')], + ['type' => 'SqlField', 'placeholder' => ts('Plus')], + ], + ], + ]; + } + + /** + * @return string + */ + public static function getTitle(): string { + return ts('Combine text'); + } + + /** + * @return string + */ + public static function getDescription(): string { + return ts('Every non-null value joined by a separator.'); + } + +} diff --git a/Civi/Api4/Query/SqlFunctionGREATEST.php b/Civi/Api4/Query/SqlFunctionGREATEST.php index 6a61daad32..a76e35b14f 100644 --- a/Civi/Api4/Query/SqlFunctionGREATEST.php +++ b/Civi/Api4/Query/SqlFunctionGREATEST.php @@ -26,6 +26,10 @@ class SqlFunctionGREATEST extends SqlFunction { 'max_expr' => 99, 'min_expr' => 2, 'optional' => FALSE, + 'ui_defaults' => [ + ['type' => 'SqlField', 'placeholder' => ts('If')], + ['type' => 'SqlField', 'placeholder' => ts('Else')], + ], ], ]; } diff --git a/Civi/Api4/Query/SqlFunctionIF.php b/Civi/Api4/Query/SqlFunctionIF.php index 66916a46f7..24e2faa65a 100644 --- a/Civi/Api4/Query/SqlFunctionIF.php +++ b/Civi/Api4/Query/SqlFunctionIF.php @@ -26,6 +26,11 @@ class SqlFunctionIF extends SqlFunction { 'min_expr' => 3, 'max_expr' => 3, 'optional' => FALSE, + 'ui_defaults' => [ + ['type' => 'SqlField', 'placeholder' => ts('If')], + ['type' => 'SqlField', 'placeholder' => ts('Then')], + ['type' => 'SqlField', 'placeholder' => ts('Else')], + ], ], ]; } diff --git a/Civi/Api4/Query/SqlFunctionLEAST.php b/Civi/Api4/Query/SqlFunctionLEAST.php index 4b9d4e66e5..a79ee4a8a2 100644 --- a/Civi/Api4/Query/SqlFunctionLEAST.php +++ b/Civi/Api4/Query/SqlFunctionLEAST.php @@ -26,6 +26,10 @@ class SqlFunctionLEAST extends SqlFunction { 'max_expr' => 99, 'min_expr' => 2, 'optional' => FALSE, + 'ui_defaults' => [ + ['type' => 'SqlField', 'placeholder' => ts('If')], + ['type' => 'SqlField', 'placeholder' => ts('Else')], + ], ], ]; } diff --git a/Civi/Api4/Query/SqlFunctionNULLIF.php b/Civi/Api4/Query/SqlFunctionNULLIF.php index 6cb6b2fb78..bda6a8e2e8 100644 --- a/Civi/Api4/Query/SqlFunctionNULLIF.php +++ b/Civi/Api4/Query/SqlFunctionNULLIF.php @@ -26,6 +26,10 @@ class SqlFunctionNULLIF extends SqlFunction { 'min_expr' => 2, 'max_expr' => 2, 'optional' => FALSE, + 'ui_defaults' => [ + ['type' => 'SqlField', 'placeholder' => ts('Preferred')], + ['type' => 'SqlField', 'placeholder' => ts('Alternate')], + ], ], ]; } diff --git a/Civi/Api4/Query/SqlFunctionREPLACE.php b/Civi/Api4/Query/SqlFunctionREPLACE.php index 916678db0f..f5a1cfbbea 100644 --- a/Civi/Api4/Query/SqlFunctionREPLACE.php +++ b/Civi/Api4/Query/SqlFunctionREPLACE.php @@ -25,6 +25,11 @@ class SqlFunctionREPLACE extends SqlFunction { 'max_expr' => 3, 'optional' => FALSE, 'must_be' => ['SqlString', 'SqlField'], + 'ui_defaults' => [ + ['type' => 'SqlField', 'placeholder' => ts('Source')], + ['type' => 'SqlString', 'placeholder' => ts('Find')], + ['type' => 'SqlString', 'placeholder' => ts('Replace')], + ], ], ]; } diff --git a/Civi/Api4/Query/SqlFunctionROUND.php b/Civi/Api4/Query/SqlFunctionROUND.php index 285f43c621..6e09945ecd 100644 --- a/Civi/Api4/Query/SqlFunctionROUND.php +++ b/Civi/Api4/Query/SqlFunctionROUND.php @@ -25,6 +25,10 @@ class SqlFunctionROUND extends SqlFunction { 'min_expr' => 1, 'max_expr' => 2, 'must_be' => ['SqlNumber', 'SqlField'], + 'ui_defaults' => [ + ['type' => 'SqlField', 'placeholder' => ts('Number')], + ['type' => 'SqlNumber', 'placeholder' => ts('Decimals')], + ], ], ]; } diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchFunction.component.js b/ext/search_kit/ang/crmSearchAdmin/crmSearchFunction.component.js index cebe91534a..25e7528269 100644 --- a/ext/search_kit/ang/crmSearchAdmin/crmSearchFunction.component.js +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchFunction.component.js @@ -13,6 +13,8 @@ var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'), ctrl = this; + var defaultUiDefaults = {type: 'SqlField', placeholder: ts('Select')}; + var allTypes = { aggregate: ts('Aggregate'), comparison: ts('Comparison'), @@ -29,6 +31,7 @@ this.$onInit = function() { var info = searchMeta.parseExpr(ctrl.expr); + ctrl.fieldArg = _.findWhere(info.args, {type: 'field'}); ctrl.args = info.args; ctrl.fn = info.fn; ctrl.fnName = !info.fn ? '' : info.fn.name; @@ -36,7 +39,7 @@ }; this.addArg = function(exprType) { - exprType = exprType || ctrl.fn.params[0].must_be[0]; + exprType = exprType || ctrl.getUiDefault(ctrl.args.length).type; ctrl.args.push({ type: ctrl.exprTypes[exprType].type, value: exprType === 'SqlNumber' ? 0 : '' @@ -55,23 +58,32 @@ ctrl.modifierName = null; ctrl.modifier = null; } + // Push args to reach the minimum while (ctrl.args.length < ctrl.fn.params[0].min_expr) { ctrl.addArg(); } } + this.getUiDefault = function(index) { + if (ctrl.fn.params[0].ui_defaults) { + return ctrl.fn.params[0].ui_defaults[index] || _.last(ctrl.fn.params[0].ui_defaults); + } + defaultUiDefaults.type = ctrl.fn.params[0].must_be[0]; + return defaultUiDefaults; + }; + // On-demand options for dropdown function selector this.getFunctions = function() { var allowedTypes = [], functions = []; - if (ctrl.expr && ctrl.args[0] && ctrl.args[0].field) { + if (ctrl.expr && ctrl.fieldArg) { if (ctrl.crmSearchAdmin.canAggregate(ctrl.expr)) { allowedTypes.push('aggregate'); } else { allowedTypes.push('comparison', 'string'); - if (_.includes(['Integer', 'Float', 'Date', 'Timestamp'], ctrl.args[0].field.data_type)) { + if (_.includes(['Integer', 'Float', 'Date', 'Timestamp'], ctrl.fieldArg.field.data_type)) { allowedTypes.push('math'); } - if (_.includes(['Date', 'Timestamp'], ctrl.args[0].field.data_type)) { + if (_.includes(['Date', 'Timestamp'], ctrl.fieldArg.field.data_type)) { allowedTypes.push('date'); } } @@ -99,8 +111,21 @@ this.selectFunction = function() { ctrl.fn = _.find(CRM.crmSearchAdmin.functions, {name: ctrl.fnName}); - ctrl.args.length = 1; - initFunction(); + ctrl.args = [ctrl.fieldArg]; + if (ctrl.fn) { + var exprType, pos = 0, + uiDefaults = ctrl.fn.params[0].ui_defaults || []; + // Add non-field args to the beginning if needed + while (uiDefaults[pos] && uiDefaults[pos].type && uiDefaults[pos].type !== 'SqlField') { + exprType = uiDefaults[pos].type; + ctrl.args.splice(pos, 0, { + type: ctrl.exprTypes[exprType].type, + value: exprType === 'SqlNumber' ? 0 : '' + }); + ++pos; + } + initFunction(); + } ctrl.writeExpr(); }; @@ -112,7 +137,7 @@ this.changeArg = function(index) { var val = ctrl.args[index].value; // Delete empty value - if (!val && ctrl.args.length > ctrl.fn.params[0].min_expr) { + if (index && !val && ctrl.args.length > ctrl.fn.params[0].min_expr) { ctrl.args.splice(index, 1); } ctrl.writeExpr(); diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchFunction.html b/ext/search_kit/ang/crmSearchAdmin/crmSearchFunction.html index 5ba166840a..67c9b6c980 100644 --- a/ext/search_kit/ang/crmSearchAdmin/crmSearchFunction.html +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchFunction.html @@ -1,15 +1,15 @@
- + -
+
- - - + + +
diff --git a/tests/phpunit/api/v4/Action/SqlFunctionTest.php b/tests/phpunit/api/v4/Action/SqlFunctionTest.php index ebac8d5d30..9101fad3e4 100644 --- a/tests/phpunit/api/v4/Action/SqlFunctionTest.php +++ b/tests/phpunit/api/v4/Action/SqlFunctionTest.php @@ -191,6 +191,22 @@ class SqlFunctionTest extends UnitTestCase { $this->assertEquals(FALSE, $result[$aids[2]]['duration_isnull']); } + public function testStringFunctions() { + $sampleData = [ + ['first_name' => 'abc', 'middle_name' => 'q', 'last_name' => 'tester1', 'source' => '123'], + ]; + $cid = Contact::save(FALSE) + ->setRecords($sampleData) + ->execute()->first()['id']; + + $result = Contact::get(FALSE) + ->addWhere('id', '=', $cid) + ->addSelect('CONCAT_WS("|", first_name, middle_name, last_name) AS concat_ws') + ->execute()->first(); + + $this->assertEquals('abc|q|tester1', $result['concat_ws']); + } + public function testIncorrectNumberOfArguments() { try { Activity::get(FALSE)