From 9446fbaadbafc2724ac4b504d6e0f65db369a783 Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Wed, 27 Oct 2021 14:38:18 -0400 Subject: [PATCH] SearchKit - Set links in admin UI using entity+action+join instead of path Now that SearchDisplay.Run understands this new link syntax, the admin UI can use it too. The old links set in existing search displays will still work, but in order for ACLs to be checked they need to use the new syntax. --- ext/search_kit/Civi/Search/Admin.php | 15 +++---- .../crmSearchAdmin.component.js | 45 +++---------------- .../crmSearchAdminDisplay.component.js | 28 +++++++----- .../crmSearchAdminLinkGroup.component.js | 43 +++++++++--------- .../crmSearchAdminLinkGroup.html | 8 ++-- .../crmSearchAdminLinkSelect.component.js | 12 ++--- .../crmSearchAdminLinkSelect.html | 22 ++++----- .../displays/colType/field.html | 2 +- 8 files changed, 71 insertions(+), 104 deletions(-) diff --git a/ext/search_kit/Civi/Search/Admin.php b/ext/search_kit/Civi/Search/Admin.php index 6e94549377..96ecf41db4 100644 --- a/ext/search_kit/Civi/Search/Admin.php +++ b/ext/search_kit/Civi/Search/Admin.php @@ -96,7 +96,7 @@ class Admin { public static function getSchema() { $schema = []; $entities = \Civi\Api4\Entity::get() - ->addSelect('name', 'title', 'title_plural', 'bridge_title', 'type', 'primary_key', 'description', 'label_field', 'icon', 'paths', 'dao', 'bridge', 'ui_join_filters', 'searchable') + ->addSelect('name', 'title', 'title_plural', 'bridge_title', 'type', 'primary_key', 'description', 'label_field', 'icon', 'dao', 'bridge', 'ui_join_filters', 'searchable') ->addWhere('searchable', '!=', 'none') ->addOrderBy('title_plural') ->setChain([ @@ -105,15 +105,10 @@ class Admin { foreach ($entities as $entity) { // Skip if entity doesn't have a 'get' action or the user doesn't have permission to use get if ($entity['get']) { - // Add paths (but only RUD actions) with translated titles - foreach ($entity['paths'] as $action => $path) { - unset($entity['paths'][$action]); - if (in_array($action, ['view', 'update', 'delete'], TRUE)) { - $entity['paths'][] = [ - 'path' => $path, - 'action' => $action, - ]; - } + // Add links with translatable titles + $links = Display::getEntityLinks($entity['name']); + if ($links) { + $entity['links'] = array_values($links); } $getFields = civicrm_api4($entity['name'], 'getFields', [ 'select' => ['name', 'title', 'label', 'description', 'type', 'options', 'input_type', 'input_attrs', 'data_type', 'serialize', 'entity', 'fk_entity', 'readonly', 'operators'], diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdmin.component.js b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdmin.component.js index 1100fe36f6..d072d93611 100644 --- a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdmin.component.js +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdmin.component.js @@ -627,33 +627,15 @@ } // Build a list of all possible links to main entity & join entities + // @return {Array} this.buildLinks = function() { function addTitle(link, entityName) { - switch (link.action) { - case 'view': - link.title = ts('View %1', {1: entityName}); - link.icon = 'fa-external-link'; - link.style = 'default'; - break; - - case 'update': - link.title = ts('Edit %1', {1: entityName}); - link.icon = 'fa-pencil'; - link.style = 'default'; - break; - - case 'delete': - link.title = ts('Delete %1', {1: entityName}); - link.icon = 'fa-trash'; - link.style = 'danger'; - break; - } + link.text = link.text.replace('%1', entityName); } // Links to main entity - // @return {Array} var mainEntity = searchMeta.getEntity(ctrl.savedSearch.api_entity), - links = _.cloneDeep(mainEntity.paths || []); + links = _.cloneDeep(mainEntity.links || []); _.each(links, function(link) { link.join = ''; addTitle(link, mainEntity.title); @@ -662,24 +644,13 @@ _.each(ctrl.savedSearch.api_params.join, function(joinClause) { var join = searchMeta.getJoin(joinClause[0]), joinEntity = searchMeta.getEntity(join.entity), - primaryKey = joinEntity.primary_key[0], - // Links for aggregate columns get aggregated using GROUP_CONCAT - isAggregate = ctrl.canAggregate(join.alias + '.' + primaryKey), - joinPrefix = (isAggregate ? ctrl.DEFAULT_AGGREGATE_FN + '_' : '') + join.alias + '.', bridgeEntity = _.isString(joinClause[2]) ? searchMeta.getEntity(joinClause[2]) : null; - _.each(joinEntity.paths, function(path) { - var link = _.cloneDeep(path); - link.path = link.path.replace(/\[/g, '[' + joinPrefix); - if (isAggregate) { - link.path = link.path.replace(/[.:]/g, '_'); - } + _.each(_.cloneDeep(joinEntity.links), function(link) { link.join = join.alias; addTitle(link, join.label); links.push(link); }); - _.each(bridgeEntity && bridgeEntity.paths, function(path) { - var link = _.cloneDeep(path); - link.path = link.path.replace(/\[/g, '[' + join.alias + '.'); + _.each(_.cloneDeep(bridgeEntity && bridgeEntity.links), function(link) { link.join = join.alias; addTitle(link, join.label + (bridgeEntity.bridge_title ? ' ' + bridgeEntity.bridge_title : '')); links.push(link); @@ -695,9 +666,7 @@ if (!ctrl.canAggregate(idFieldName)) { var joinEntity = searchMeta.getEntity(idField.fk_entity), label = (idField.join ? idField.join.label + ': ' : '') + (idField.input_attrs && idField.input_attrs.label || idField.label); - _.each((joinEntity || {}).paths, function(path) { - var link = _.cloneDeep(path); - link.path = link.path.replace(/\[id/g, '[' + idFieldName); + _.each(_.cloneDeep(joinEntity && joinEntity.links), function(link) { link.join = idFieldName; addTitle(link, label); links.push(link); @@ -706,7 +675,7 @@ } } }); - return _.uniq(links, 'path'); + return links; }; function loadAfforms() { diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminDisplay.component.js b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminDisplay.component.js index 9aea30e3d9..c7b2548e53 100644 --- a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminDisplay.component.js +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminDisplay.component.js @@ -184,30 +184,36 @@ return !info.fn || info.fn.category !== 'aggregate' || info.fn.name === 'GROUP_CONCAT'; }; + var linkProps = ['path', 'entity', 'action', 'join', 'target']; + this.toggleLink = function(column) { if (column.link) { - ctrl.onChangeLink(column, column.link.path, ''); + ctrl.onChangeLink(column, {}); } 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); + ctrl.onChangeLink(column, defaultLink || {path: 'civicrm/'}); } }; - this.onChangeLink = function(column, before, after) { - var beforeLink = before && _.findWhere(ctrl.getLinks(), {path: before}), - afterLink = after && _.findWhere(ctrl.getLinks(), {path: after}); - if (!after) { - if (beforeLink && column.title === beforeLink.title) { + this.onChangeLink = function(column, afterLink) { + column.link = column.link || {}; + var beforeLink = column.link.action && _.findWhere(ctrl.getLinks(column.key), {action: column.link.action}); + if (!afterLink.action && !afterLink.path) { + if (beforeLink && beforeLink.text === column.title) { delete column.title; } delete column.link; - } else if (afterLink && ((!column.title && !before) || (beforeLink && beforeLink.title === column.title))) { - column.title = afterLink.title; - } else if (!afterLink && (beforeLink && beforeLink.title === column.title)) { + return; + } + if (afterLink.text && ((!column.title && !beforeLink) || (beforeLink && beforeLink.text === column.title))) { + column.title = afterLink.text; + } else if (!afterLink.text && (beforeLink && beforeLink.text === column.title)) { delete column.title; } + _.each(linkProps, function(prop) { + column.link[prop] = afterLink[prop] || ''; + }); }; this.getLinks = function(columnKey) { diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.component.js b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.component.js index 5053c6ba17..6623f2933d 100644 --- a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.component.js +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.component.js @@ -11,7 +11,8 @@ templateUrl: '~/crmSearchAdmin/crmSearchAdminLinkGroup.html', controller: function ($scope, $element, $timeout, searchMeta) { var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'), - ctrl = this; + ctrl = this, + linkProps = ['path', 'entity', 'action', 'join', 'target', 'icon', 'text', 'style']; this.styles = CRM.crmSearchAdmin.styles; @@ -37,49 +38,47 @@ }); }; - this.addItem = function(path) { - var link = ctrl.getLink(path); - ctrl.group.push({ - path: path, - style: link && link.style || 'default', - text: link ? link.title : ts('Link'), - icon: link && link.icon || 'fa-external-link' - }); + this.addItem = function(item) { + ctrl.group.push(_.pick(item, linkProps)); }; - this.onChangeLink = function(item, before, after) { - var beforeLink = before && ctrl.getLink(before), - beforeTitle = beforeLink ? beforeLink.title : ts('Link'), - afterLink = after && ctrl.getLink(after); - if (afterLink && (!item.text || beforeTitle === item.text)) { - item.text = afterLink.title; + this.onChangeLink = function(item, newValue) { + if (newValue.path === 'civicrm/') { + newValue = JSON.parse(this.default); } + _.each(linkProps, function(prop) { + item[prop] = newValue[prop] || ''; + }); }; + this.serialize = JSON.stringify; + this.$onInit = function() { + this.default = this.serialize({ + style: 'default', + text: ts('Link'), + icon: 'fa-external-link', + path: 'civicrm/' + }); var defaultLinks = _.filter(ctrl.links, function(link) { return !link.join; }); if (!ctrl.group.length) { if (defaultLinks.length) { - _.each(_.pluck(defaultLinks, 'path'), ctrl.addItem); + _.each(defaultLinks, ctrl.addItem); } else { - ctrl.addItem('civicrm/'); + ctrl.addItem(JSON.parse(this.default)); } } $element.on('change', 'select.crm-search-admin-add-link', function() { var $select = $(this); $scope.$apply(function() { - ctrl.addItem($select.val()); + ctrl.addItem(JSON.parse($select.val())); $select.val(''); }); }); }; - this.getLink = function(path) { - return _.findWhere(ctrl.links, {path: path}); - }; - } }); diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.html b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.html index c4459e5c9f..fc67712617 100644 --- a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.html +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.html @@ -32,7 +32,7 @@ - +
@@ -60,10 +60,10 @@ - - diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkSelect.component.js b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkSelect.component.js index 16f4528bb2..bf7c68d4ba 100644 --- a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkSelect.component.js +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkSelect.component.js @@ -15,20 +15,16 @@ ctrl = this; this.setValue = function(val) { - ctrl.link = ctrl.link || {}; - var link = ctrl.getLink(val), - oldVal = ctrl.link.path; - ctrl.link.path = val; - if (!link) { + if (val.path) { $timeout(function () { $('input[type=text]', $element).focus(); }); } - ctrl.onChange({before: oldVal, after: val}); + ctrl.onChange({newLink: val}); }; - this.getLink = function(path) { - return _.findWhere(ctrl.links, {path: path}); + this.getLink = function() { + return _.findWhere(ctrl.links, {action: ctrl.link.action, join: ctrl.link.join, entity: ctrl.link.entity}); }; } diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkSelect.html b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkSelect.html index cd5b1f1f91..cd4f85f2b7 100644 --- a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkSelect.html +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkSelect.html @@ -1,20 +1,22 @@
- -
- -
- + diff --git a/ext/search_kit/ang/crmSearchAdmin/displays/colType/field.html b/ext/search_kit/ang/crmSearchAdmin/displays/colType/field.html index 414eba6b30..6d3b75b03a 100644 --- a/ext/search_kit/ang/crmSearchAdmin/displays/colType/field.html +++ b/ext/search_kit/ang/crmSearchAdmin/displays/colType/field.html @@ -8,7 +8,7 @@ - +
-- 2.25.1