savedSearch: '<'
},
templateUrl: '~/crmSearchAdmin/crmSearchAdmin.html',
- controller: function($scope, $element, $location, $timeout, crmApi4, dialogService, searchMeta, formatForSelect2) {
+ controller: function($scope, $element, $location, $timeout, crmApi4, dialogService, searchMeta) {
var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
ctrl = this,
+ afformLoad,
fieldsForJoinGetters = {};
this.DEFAULT_AGGREGATE_FN = 'GROUP_CONCAT';
-
+ this.afformEnabled = CRM.crmSearchAdmin.afformEnabled;
+ this.afformAdminEnabled = CRM.crmSearchAdmin.afformAdminEnabled;
this.displayTypes = _.indexBy(CRM.crmSearchAdmin.displayTypes, 'id');
$scope.controls = {tab: 'compose', joinType: 'LEFT'};
});
loadFieldOptions();
+ loadAfforms();
};
function onChangeAnything() {
} else if (params.id) {
apiCalls.deleteGroup = ['Group', 'delete', {where: [['saved_search_id', '=', params.id]]}];
}
+ _.remove(params.displays, {trashed: true});
if (params.displays && params.displays.length) {
chain.displays = ['SearchDisplay', 'replace', {where: [['saved_search_id', '=', '$id']], records: params.displays}];
} else if (params.id) {
} else if (!display.trashed) {
$scope.selectTab('display_' + index);
}
+ if (display.trashed && afformLoad) {
+ afformLoad.then(function() {
+ if (ctrl.afforms[display.name]) {
+ var msg = ctrl.afforms[display.name].length === 1 ?
+ ts('Form "%1" will be deleted if the embedded display "%2" is deleted.', {1: ctrl.afforms[display.name][0].title, 2: display.label}) :
+ ts('%1 forms will be deleted if the embedded display "%2" is deleted.', {1: ctrl.afforms[display.name].length, 2: display.label});
+ CRM.alert(msg, ts('Display embedded'), 'alert');
+ }
+ });
+ }
} else {
$scope.selectTab('compose');
ctrl.savedSearch.displays.splice(index, 1);
function reconcileAggregateColumns() {
_.each(ctrl.savedSearch.api_params.select, function(col, pos) {
var info = searchMeta.parseExpr(col),
- fieldExpr = info.path + info.suffix;
+ fieldExpr = (_.findWhere(info.args, {type: 'field'}) || {}).value;
if (ctrl.canAggregate(col)) {
// Ensure all non-grouped columns are aggregated if using GROUP BY
if (!info.fn || info.fn.category !== 'aggregate') {
- ctrl.savedSearch.api_params.select[pos] = ctrl.DEFAULT_AGGREGATE_FN + '(DISTINCT ' + fieldExpr + ') AS ' + ctrl.DEFAULT_AGGREGATE_FN + '_DISTINCT_' + fieldExpr.replace(/[.:]/g, '_');
+ ctrl.savedSearch.api_params.select[pos] = ctrl.DEFAULT_AGGREGATE_FN + '(DISTINCT ' + fieldExpr + ') AS ' + ctrl.DEFAULT_AGGREGATE_FN + '_' + fieldExpr.replace(/[.:]/g, '_');
}
} else {
// Remove aggregate functions when no grouping
// Deletes an item from an array param
this.clearParam = function(name, idx) {
+ if (name === 'select') {
+ // Function selectors use `ng-repeat` with `track by $index` so must be refreshed when splicing the array
+ ctrl.hideFuncitons();
+ }
ctrl.savedSearch.api_params[name].splice(idx, 1);
};
+ this.hideFuncitons = function() {
+ $scope.controls.showFunctions = false;
+ };
+
function onChangeSelect(newSelect, oldSelect) {
// When removing a column from SELECT, also remove from ORDER BY & HAVING
_.each(_.difference(oldSelect, newSelect), function(col) {
if (!ctrl.savedSearch.api_params.groupBy.length) {
return false;
}
- var info = searchMeta.parseExpr(col);
+ var arg = _.findWhere(searchMeta.parseExpr(col).args, {type: 'field'}) || {};
+ // If the column is not a database field, no
+ if (!arg.field || !arg.field.entity || arg.field.type !== 'Field') {
+ return false;
+ }
// If the column is used for a groupBy, no
- if (ctrl.savedSearch.api_params.groupBy.indexOf(info.path) > -1) {
+ if (ctrl.savedSearch.api_params.groupBy.indexOf(arg.path) > -1) {
return false;
}
// If the entity this column belongs to is being grouped by primary key, then also no
- var idField = searchMeta.getEntity(info.field.entity).primary_key[0];
- return ctrl.savedSearch.api_params.groupBy.indexOf(info.prefix + idField) < 0;
+ var idField = searchMeta.getEntity(arg.field.entity).primary_key[0];
+ return ctrl.savedSearch.api_params.groupBy.indexOf(arg.prefix + idField) < 0;
};
$scope.fieldsForGroupBy = function() {
};
function getFieldsForJoin(joinEntity) {
- return {results: ctrl.getAllFields(':name', ['Field', 'Custom'], null, joinEntity)};
+ return {results: ctrl.getAllFields(':name', ['Field'], null, joinEntity)};
}
+ // @return {function}
$scope.fieldsForJoin = function(joinEntity) {
if (!fieldsForJoinGetters[joinEntity]) {
fieldsForJoinGetters[joinEntity] = _.wrap(joinEntity, getFieldsForJoin);
this.getAllFields = function(suffix, allowedTypes, disabledIf, topJoin) {
disabledIf = disabledIf || _.noop;
- function formatFields(entityName, join) {
+
+ function formatEntityFields(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;
- }
- if (!allowedTypes || _.includes(allowedTypes, field.type)) {
- result.push(item);
- }
- });
- }
-
// Add extra searchable fields from bridge entity
if (join && join.bridge) {
- addFields(_.filter(searchMeta.getEntity(join.bridge).fields, function(field) {
+ formatFields(_.filter(searchMeta.getEntity(join.bridge).fields, function(field) {
return (field.name !== 'id' && field.name !== 'entity_id' && field.name !== 'entity_table' && !field.fk_entity);
- }));
+ }), result, prefix);
}
- addFields(searchMeta.getEntity(entityName).fields);
+ formatFields(searchMeta.getEntity(entityName).fields, result, prefix);
+ return result;
+ }
+
+ function formatFields(fields, result, prefix) {
+ prefix = typeof prefix === 'undefined' ? '' : prefix;
+ _.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;
+ }
+ if (!allowedTypes || _.includes(allowedTypes, field.type)) {
+ result.push(item);
+ }
+ });
return result;
}
text: joinInfo.label,
description: joinInfo.description,
icon: joinEntity.icon,
- children: formatFields(joinEntity.name, joinInfo)
+ children: formatEntityFields(joinEntity.name, joinInfo)
});
}
result.push({
text: mainEntity.title_plural,
icon: mainEntity.icon,
- children: formatFields(ctrl.savedSearch.api_entity)
+ children: formatEntityFields(ctrl.savedSearch.api_entity)
});
+
+ // Include SearchKit's pseudo-fields if specifically requested
+ if (allowedTypes && _.includes(allowedTypes, 'Pseudo')) {
+ result.push({
+ text: ts('Extra'),
+ icon: 'fa-gear',
+ children: formatFields(CRM.crmSearchAdmin.pseudoFields, [])
+ });
+ }
+
_.each(joinEntities, addJoin);
return result;
};
var item = {
id: info.alias,
text: ctrl.getFieldLabel(name),
- description: info.field && info.field.description
+ description: info.fn ? info.fn.description : info.args[0].field && info.args[0].field.description
};
if (disabledIf(item.id)) {
item.disabled = true;
});
};
+ this.isPseudoField = function(name) {
+ return _.findIndex(CRM.crmSearchAdmin.pseudoFields, {name: name}) >= 0;
+ };
+
/**
* Fetch pseudoconstants for main entity + joined entities
*
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.isAggregate = isAggregate;
- link.path = link.path.replace(/\[/g, '[' + join.alias + '.');
+ link.path = link.path.replace(/\[/g, '[' + joinPrefix);
+ if (isAggregate) {
+ link.path = link.path.replace(/[.:]/g, '_');
+ }
link.join = join.alias;
addTitle(link, join.label);
links.push(link);
// Links to implicit joins
_.each(ctrl.savedSearch.api_params.select, function(fieldName) {
if (!_.includes(fieldName, ' AS ')) {
- var info = searchMeta.parseExpr(fieldName);
- if (info.field && !info.suffix && !info.fn && (info.field.fk_entity || info.field.name !== info.field.fieldName)) {
+ var info = searchMeta.parseExpr(fieldName).args[0];
+ if (info.field && !info.suffix && !info.fn && info.field.type === 'Field' && (info.field.fk_entity || info.field.name !== info.field.fieldName)) {
var idFieldName = info.field.fk_entity ? fieldName : fieldName.substr(0, fieldName.lastIndexOf('.')),
- idField = searchMeta.parseExpr(idFieldName).field;
+ idField = searchMeta.parseExpr(idFieldName).args[0].field;
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);
return _.uniq(links, 'path');
};
+ function loadAfforms() {
+ if (ctrl.afformEnabled && ctrl.savedSearch.id) {
+ var findDisplays = _.transform(ctrl.savedSearch.displays, function(findDisplays, display) {
+ if (display.id && display.name) {
+ findDisplays.push(['search_displays', 'CONTAINS', ctrl.savedSearch.name + '.' + display.name]);
+ }
+ }, [['search_displays', 'CONTAINS', ctrl.savedSearch.name]]);
+ afformLoad = crmApi4('Afform', 'get', {
+ select: ['name', 'title', 'search_displays'],
+ where: [['OR', findDisplays]]
+ }).then(function(afforms) {
+ ctrl.afforms = {};
+ _.each(afforms, function(afform) {
+ _.each(_.uniq(afform.search_displays), function(searchNameDisplayName) {
+ var displayName = searchNameDisplayName.split('.')[1] || '';
+ ctrl.afforms[displayName] = ctrl.afforms[displayName] || [];
+ ctrl.afforms[displayName].push({
+ title: afform.title,
+ link: ctrl.afformAdminEnabled ? CRM.url('civicrm/admin/afform#/edit/' + afform.name) : '',
+ });
+ });
+ });
+ });
+ }
+ }
+
+ // Creating an Afform opens a new tab, so when switching back to this tab, re-check for Afforms
+ $(window).on('focus', _.debounce(function() {
+ $scope.$apply(loadAfforms);
+ }, 10000, {leading: true, trailing: false}));
+
}
});