Search kit: Add UI support for multiple, nested joins
authorColeman Watts <coleman@civicrm.org>
Tue, 8 Dec 2020 22:52:44 +0000 (17:52 -0500)
committerColeman Watts <coleman@civicrm.org>
Tue, 8 Dec 2020 22:52:44 +0000 (17:52 -0500)
ext/search/ang/crmSearchAdmin.module.js
ext/search/ang/crmSearchAdmin/crmSearchAdmin.component.js

index 90d6570598de620835447396d6ea8a082aee10e2..754a4de912169f296a1fc39269c7f7929d5bfa1f 100644 (file)
     })
 
     .factory('searchMeta', function() {
-      // JoinIndex lists each join by alias. It gets built once then cached.
-      function _getJoinIndex() {
-        if (!joinIndex) {
-          joinIndex = _.transform(CRM.crmSearchAdmin.joins, function(joinIndex, joins, base) {
-            _.each(joins, function(join) {
-              join.base = base;
-              joinIndex[join.alias] = join;
-            });
-          });
-        }
-        return joinIndex;
-      }
       function getEntity(entityName) {
         if (entityName) {
           return _.find(CRM.crmSearchAdmin.schema, {name: entityName});
         }
       }
+      // Get join metadata matching a given expression like "Email AS Contact_Email_contact_id_01"
       function getJoin(fullNameOrAlias) {
-        var joinIndex = _getJoinIndex(),
-          alias = _.last(fullNameOrAlias.split(' AS '));
-        return joinIndex[alias];
+        var alias = _.last(fullNameOrAlias.split(' AS ')),
+          path = alias,
+          baseEntity = searchEntity,
+          label = [],
+          join,
+          result;
+        while (path.length) {
+          /* jshint -W083 */
+          join = _.find(CRM.crmSearchAdmin.joins[baseEntity], function(join) {
+            return new RegExp('^' + join.alias + '_\\d\\d').test(path);
+          });
+          if (!join) {
+            console.warn( 'Join ' + fullNameOrAlias + ' not found.');
+            return;
+          }
+          path = path.replace(join.alias + '_', '');
+          var num = parseInt(path.substr(0, 2), 10);
+          baseEntity = join.entity;
+          label.push(join.label + (num > 1 ? ' ' + num : ''));
+          path = path.replace(/^\d\d_?/, '');
+        }
+        result = _.assign(_.cloneDeep(join), {label: label.join(' - '), alias: alias});
+        // Add the numbered suffix to the join conditions
+        // If this is a deep join, also add the base entity prefix
+        var prefix = alias.replace(new RegExp('_?' + join.alias + '_?\\d?\\d?$'), '');
+        _.each(result.conditions, function(condition) {
+          if (_.isArray(condition)) {
+            _.each(condition, function(ref, side) {
+              if (side !== 1 && _.includes(ref, '.')) {
+                condition[side] = ref.replace(join.alias + '.', alias + '.');
+              } else if (side !== 1 && prefix.length && !_.includes(ref, '"') && !_.includes(ref, "'")) {
+                condition[side] = prefix + '.' + ref;
+              }
+            });
+          }
+        });
+        return result;
       }
       function getField(fieldName, entityName) {
         var dotSplit = fieldName.split('.'),
index e3ff102b183c0ba1f4787756313fc5a1d04e18ac..a162ca4ed61d8f5fcbf67da8ee7e576e65defd02 100644 (file)
         }
       };
 
+      function addNum(name, num) {
+        return name + (num < 10 ? '_0' : '_') + num;
+      }
+
+      function getExistingJoins() {
+        return _.transform(ctrl.savedSearch.api_params.join || [], function(joins, join) {
+          joins[join[0].split(' AS ')[1]] = searchMeta.getJoin(join[0]);
+        }, {});
+      }
+
       $scope.getJoin = searchMeta.getJoin;
 
       $scope.getJoinEntities = function() {
-        var joinEntities = _.transform(CRM.crmSearchAdmin.joins[ctrl.savedSearch.api_entity], function(joinEntities, join) {
-          var entity = searchMeta.getEntity(join.entity);
-          if (entity) {
-            joinEntities.push({
-              id: join.entity + ' AS ' + join.alias,
+        var existingJoins = getExistingJoins();
+
+        function addEntityJoins(entity, stack, baseEntity) {
+          return _.transform(CRM.crmSearchAdmin.joins[entity], function(joinEntities, join) {
+            var num = 0;
+            // Add all joins that don't just point directly back to the original entity
+            if (!(baseEntity === join.entity && !join.multi)) {
+              do {
+                appendJoin(joinEntities, join, ++num, stack, entity);
+              } while (addNum((stack ? stack + '_' : '') + join.alias, num) in existingJoins);
+            }
+          }, []);
+        }
+
+        function appendJoin(collection, join, num, stack, baseEntity) {
+          var alias = addNum((stack ? stack + '_' : '') + join.alias, num),
+            opt = {
+              id: join.entity + ' AS ' + alias,
               description: join.description,
-              text: join.label,
-              icon: entity.icon
-            });
+              text: join.label + (num > 1 ? ' ' + num : ''),
+              icon: searchMeta.getEntity(join.entity).icon,
+              disabled: alias in existingJoins
+            };
+          if (alias in existingJoins) {
+            opt.children = addEntityJoins(join.entity, (stack ? stack + '_' : '') + alias, baseEntity);
           }
-        }, []);
-        return {results: joinEntities};
+          collection.push(opt);
+        }
+
+        return {results: addEntityJoins(ctrl.savedSearch.api_entity)};
       };
 
       $scope.addJoin = function() {