SearchKit - Make UI less confusing by not allowing joins to be switched
[civicrm-core.git] / ext / search / ang / crmSearchAdmin / crmSearchAdmin.component.js
index 1b29fe45c6d23affacaeabd1ef8a4f5de9d8815d..353851435ff2b3e1a3f255bfa4f87cb6a1dab10f 100644 (file)
@@ -29,7 +29,7 @@
       $scope.groupOptions = CRM.crmSearchActions.groupOptions;
       // Try to create a sensible list of entities one might want to search for,
       // excluding those whos primary purpose is to provide joins or option lists to other entities
-      var primaryEntities = _.filter(CRM.vars.search.schema, function(entity) {
+      var primaryEntities = _.filter(CRM.crmSearchAdmin.schema, function(entity) {
         return !_.includes(entity.type, 'EntityBridge') && !_.includes(entity.type, 'OptionList');
       });
       $scope.entities = formatForSelect2(primaryEntities, 'name', 'title_plural', ['description', 'icon']);
         }
       };
 
+      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.vars.search.links[ctrl.savedSearch.api_entity], function(joinEntities, link) {
-          var entity = searchMeta.getEntity(link.entity);
-          if (entity) {
-            joinEntities.push({
-              id: link.entity + ' AS ' + link.alias,
-              text: entity.title_plural,
-              description: '(' + link.alias + ')',
-              icon: entity.icon
-            });
+        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 + (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() {
         $timeout(function() {
           if ($scope.controls.join) {
             ctrl.savedSearch.api_params.join = ctrl.savedSearch.api_params.join || [];
-            ctrl.savedSearch.api_params.join.push([$scope.controls.join, false]);
+            var join = searchMeta.getJoin($scope.controls.join),
+              params = [$scope.controls.join, false];
+            _.each(_.cloneDeep(join.conditions), function(condition) {
+              params.push(condition);
+            });
+            _.each(_.cloneDeep(join.defaults), function(condition) {
+              params.push(condition);
+            });
+            ctrl.savedSearch.api_params.join.push(params);
             loadFieldOptions();
           }
           $scope.controls.join = '';
         });
       };
 
-      $scope.changeJoin = function(idx) {
-        if (ctrl.savedSearch.api_params.join[idx][0]) {
-          ctrl.savedSearch.api_params.join[idx].length = 2;
-          loadFieldOptions();
-        } else {
-          ctrl.clearParam('join', idx);
-        }
-      };
-
       $scope.changeGroupBy = function(idx) {
         if (!ctrl.savedSearch.api_params.groupBy[idx]) {
           ctrl.clearParam('groupBy', idx);
         if (ctrl.savedSearch.api_params.groupBy.length) {
           _.each(ctrl.savedSearch.api_params.select, function(col, pos) {
             if (!_.contains(col, '(') && ctrl.canAggregate(col)) {
-              ctrl.savedSearch.api_params.select[pos] = ctrl.DEFAULT_AGGREGATE_FN + '(' + col + ')';
+              ctrl.savedSearch.api_params.select[pos] = ctrl.DEFAULT_AGGREGATE_FN + '(DISTINCT ' + col + ')';
             }
           });
         }
 
       $scope.formatResult = function(row, col) {
         var info = searchMeta.parseExpr(col),
-          key = info.fn ? (info.fn.name + ':' + info.path + info.suffix) : col,
-          value = row[key];
+          value = row[info.alias];
         if (info.fn && info.fn.name === 'COUNT') {
           return value;
         }
       }
 
       function getAllFields(suffix, disabledIf) {
-        function formatFields(entityName, prefix) {
-          return _.transform(searchMeta.getEntity(entityName).fields, function(result, field) {
-            var item = {
-              id: prefix + field.name + (field.options ? suffix : ''),
-              text: field.label,
-              description: field.description
-            };
-            if (disabledIf(item.id)) {
-              item.disabled = true;
-            }
-            result.push(item);
-          }, []);
+        function formatFields(entityName, join) {
+          var prefix = join ? join.alias + '.' : '',
+            result = [];
+
+          function addFields(fields) {
+            _.each(fields, function(field) {
+              var item = {
+                id: prefix + field.name + (field.options ? suffix : ''),
+                text: field.label,
+                description: field.description
+              };
+              if (disabledIf(item.id)) {
+                item.disabled = true;
+              }
+              result.push(item);
+            });
+          }
+
+          // Add extra searchable fields from bridge entity
+          if (join && join.bridge) {
+            addFields(_.filter(searchMeta.getEntity(join.bridge).fields, function(field) {
+              return (field.name !== 'id' && field.name !== 'entity_id' && field.name !== 'entity_table' && !field.fk_entity);
+            }));
+          }
+
+          addFields(searchMeta.getEntity(entityName).fields);
+          return result;
         }
 
         var mainEntity = searchMeta.getEntity(ctrl.savedSearch.api_entity),
           result = [{
             text: mainEntity.title_plural,
             icon: mainEntity.icon,
-            children: formatFields(ctrl.savedSearch.api_entity, '')
+            children: formatFields(ctrl.savedSearch.api_entity)
           }];
         _.each(ctrl.savedSearch.api_params.join, function(join) {
-          var joinName = join[0].split(' AS '),
-            joinEntity = searchMeta.getEntity(joinName[0]);
+          var joinInfo = searchMeta.getJoin(join[0]),
+            joinEntity = searchMeta.getEntity(joinInfo.entity);
           result.push({
-            text: joinEntity.title_plural + ' (' + joinName[1] + ')',
+            text: joinInfo.label,
+            description: joinInfo.description,
             icon: joinEntity.icon,
-            children: formatFields(joinEntity.name, joinName[1] + '.')
+            children: formatFields(joinEntity.name, joinInfo)
           });
         });
         return result;
           enqueue(mainEntity);
         }
         _.each(ctrl.savedSearch.api_params.join, function(join) {
-          var joinName = join[0].split(' AS '),
-            joinEntity = searchMeta.getEntity(joinName[0]);
+          var joinInfo = searchMeta.getJoin(join[0]),
+            joinEntity = searchMeta.getEntity(joinInfo.entity),
+            bridgeEntity = joinInfo.bridge ? searchMeta.getEntity(joinInfo.bridge) : null;
           if (typeof joinEntity.optionsLoaded === 'undefined') {
             enqueue(joinEntity);
           }
+          if (bridgeEntity && typeof bridgeEntity.optionsLoaded === 'undefined') {
+            enqueue(bridgeEntity);
+          }
         });
         if (!_.isEmpty(entities)) {
           crmApi4(entities).then(function(results) {