From: Coleman Watts Date: Tue, 1 Nov 2022 02:10:33 +0000 (-0400) Subject: SearchKit - Improve icon handling X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=5f0a735dd66f1eed860235ae108a743657939465;p=civicrm-core.git SearchKit - Improve icon handling Gives the ability to have "fallback" icons, e.g. choosing the icon for contact_sub_type with a fallback to contact_type. --- diff --git a/Civi/Api4/Utils/FormattingUtil.php b/Civi/Api4/Utils/FormattingUtil.php index 8216a4c328..59c2f1e8df 100644 --- a/Civi/Api4/Utils/FormattingUtil.php +++ b/Civi/Api4/Utils/FormattingUtil.php @@ -243,6 +243,12 @@ class FormattingUtil { $fieldOptions = self::getPseudoconstantList($field, $fieldName, $result, $action); $dataType = NULL; } + // Store contact_type value before replacing pseudoconstant (e.g. transforming it to contact_type:label) + // Used by self::contactFieldsToRemove below + if ($value && isset($field['entity']) && $field['entity'] === 'Contact' && $field['name'] === 'contact_type') { + $prefix = strrpos($fieldName, '.'); + $contactTypePaths[$prefix ? substr($fieldName, 0, $prefix + 1) : ''] = $value; + } if ($fieldExpr->supportsExpansion) { if (!empty($field['serialize']) && is_string($value)) { $value = \CRM_Core_DAO::unSerializeField($value, $field['serialize']); @@ -251,11 +257,6 @@ class FormattingUtil { $value = self::replacePseudoconstant($fieldOptions, $value); } } - // Keep track of contact types for self::contactFieldsToRemove - if ($value && isset($field['entity']) && $field['entity'] === 'Contact' && $field['name'] === 'contact_type') { - $prefix = strrpos($fieldName, '.'); - $contactTypePaths[$prefix ? substr($fieldName, 0, $prefix + 1) : ''] = $value; - } $result[$key] = self::convertDataType($value, $dataType); } // Remove inapplicable contact fields diff --git a/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php b/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php index 25034eb1be..66cf56ba95 100644 --- a/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php +++ b/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php @@ -319,7 +319,11 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction { } /** - * Evaluates conditional style rules + * Add icons to a column + * + * Note: Only one icon is allowed per side (left/right). + * If more than one per side is given, latter icons are treated as fallbacks + * and only shown if prior ones are missing. * * @param array{icon: string, field: string, if: array, side: string}[] $icons * @param array $data @@ -327,20 +331,23 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction { */ protected function getColumnIcons(array $icons, array $data) { $result = []; - foreach ($icons as $icon) { + // Reverse order so latter icons become fallbacks and earlier ones take priority + foreach (array_reverse($icons) as $icon) { $iconClass = $icon['icon'] ?? NULL; - if (!$iconClass && !empty($icon['field'])) { - $iconClass = $data[$icon['field']] ?? NULL; + if (!$iconClass && !empty($icon['field']) && !empty($data[$icon['field']])) { + // Icon field may be multivalued e.g. contact_sub_type + $iconClass = \CRM_Utils_Array::first(array_filter((array) $data[$icon['field']])); } if ($iconClass) { $condition = $this->getRuleCondition($icon['if'] ?? []); if (!is_null($condition[0]) && !(self::filterCompare($data, $condition))) { continue; } - $result[] = ['class' => $iconClass, 'side' => $icon['side'] ?? 'left']; + $side = $icon['side'] ?? 'left'; + $result[$side] = ['class' => $iconClass, 'side' => $side]; } } - return $result; + return array_values($result); } /** diff --git a/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchRunTest.php b/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchRunTest.php index 50556cafac..9791a96a74 100644 --- a/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchRunTest.php +++ b/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchRunTest.php @@ -1066,7 +1066,7 @@ class SearchRunTest extends Api4TestBase implements TransactionalInterface { // Icon based on activity type $this->assertEquals([['class' => 'fa-slideshare', 'side' => 'left']], $result[0]['columns'][0]['icons']); // Activity type icon + conditional icon based on status - $this->assertEquals([['class' => 'fa-phone', 'side' => 'left'], ['class' => 'fa-star', 'side' => 'right']], $result[1]['columns'][0]['icons']); + $this->assertEquals([['class' => 'fa-star', 'side' => 'right'], ['class' => 'fa-phone', 'side' => 'left']], $result[1]['columns'][0]['icons']); } /** @@ -1452,4 +1452,86 @@ class SearchRunTest extends Api4TestBase implements TransactionalInterface { $this->assertEquals(3, $result[2]['columns'][1]['val']); } + public function testContactTypeIcons(): void { + $this->createTestRecord('ContactType', [ + 'label' => 'Star', + 'name' => 'Star', + 'parent_id:name' => 'Individual', + 'icon' => 'fa-star', + ]); + $this->createTestRecord('ContactType', [ + 'label' => 'None', + 'name' => 'None', + 'parent_id:name' => 'Individual', + 'icon' => NULL, + ]); + + $lastName = uniqid(__FUNCTION__); + $sampleData = [ + [ + 'first_name' => 'Starry', + 'contact_sub_type' => ['Star'], + ], + [ + 'first_name' => 'No icon', + 'contact_sub_type' => ['None'], + ], + [ + 'first_name' => 'Both', + 'contact_sub_type' => ['None', 'Star'], + ], + ]; + $records = $this->saveTestRecords('Contact', [ + 'records' => $sampleData, + 'defaults' => ['last_name' => $lastName], + ]); + + $params = [ + 'checkPermissions' => FALSE, + 'return' => 'page:1', + 'savedSearch' => [ + 'api_entity' => 'Contact', + 'api_params' => [ + 'version' => 4, + 'select' => ['first_name', 'last_name'], + ], + ], + 'display' => [ + 'type' => 'table', + 'label' => '', + 'settings' => [ + 'actions' => TRUE, + 'pager' => [], + 'columns' => [ + [ + 'key' => 'first_name', + 'label' => 'First', + 'dataType' => 'String', + 'type' => 'field', + 'icons' => [ + ['field' => 'contact_sub_type:icon'], + ['field' => 'contact_type:icon'], + ], + ], + ], + 'sort' => [ + ['sort_name', 'ASC'], + ], + ], + ], + 'filters' => ['last_name' => $lastName], + ]; + + $result = civicrm_api4('SearchDisplay', 'run', $params); + $this->assertCount(3, $result); + + // Contacts will be returned in order by sort_name + $this->assertEquals('Both', $result[0]['columns'][0]['val']); + $this->assertEquals('fa-star', $result[0]['columns'][0]['icons'][0]['class']); + $this->assertEquals('No icon', $result[1]['columns'][0]['val']); + $this->assertEquals('fa-user', $result[1]['columns'][0]['icons'][0]['class']); + $this->assertEquals('Starry', $result[2]['columns'][0]['val']); + $this->assertEquals('fa-star', $result[2]['columns'][0]['icons'][0]['class']); + } + }