X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=ang%2Fapi4Explorer%2FExplorer.js;h=1db4d564f80503629b035488940bb9f78c762360;hb=9ba91a07dd4109f600b76d1a237a639af2c169af;hp=ecf2d45973740570c1e723ac30eea164f1a5c989;hpb=3f13cb67cad4d22cc8153454d34dc10a88785672;p=civicrm-core.git diff --git a/ang/api4Explorer/Explorer.js b/ang/api4Explorer/Explorer.js index ecf2d45973..1db4d564f8 100644 --- a/ang/api4Explorer/Explorer.js +++ b/ang/api4Explorer/Explorer.js @@ -20,19 +20,22 @@ }); }); - angular.module('api4Explorer').controller('Api4Explorer', function($scope, $routeParams, $location, $timeout, $http, crmUiHelp, crmApi4) { + angular.module('api4Explorer').controller('Api4Explorer', function($scope, $routeParams, $location, $timeout, $http, crmUiHelp, crmApi4, dialogService) { var ts = $scope.ts = CRM.ts(); $scope.entities = entities; $scope.actions = actions; $scope.fields = []; + $scope.havingOptions = []; $scope.fieldsAndJoins = []; - $scope.selectFieldsAndJoins = []; + $scope.fieldsAndJoinsAndFunctions = []; + $scope.fieldsAndJoinsAndFunctionsAndWildcards = []; $scope.availableParams = {}; $scope.params = {}; $scope.index = ''; $scope.selectedTab = {result: 'result', code: 'php'}; $scope.perm = { - accessDebugOutput: CRM.checkPerm('access debug output') + accessDebugOutput: CRM.checkPerm('access debug output'), + editGroups: CRM.checkPerm('edit groups') }; marked.setOptions({highlight: prettyPrintOne}); var getMetaParams = {}, @@ -48,35 +51,24 @@ $scope.status = 'default'; $scope.loading = false; $scope.controls = {}; - $scope.code = [ - { - lang: 'php', - style: [ - {name: 'oop', label: ts('OOP Style'), code: ''}, - {name: 'php', label: ts('Traditional'), code: ''} - ] - }, - { - lang: 'js', - style: [ - {name: 'js', label: ts('Single Call'), code: ''}, - {name: 'js2', label: ts('Batch Calls'), code: ''} - ] - }, - { - lang: 'ang', - style: [ - {name: 'ang', label: ts('Single Call'), code: ''}, - {name: 'ang2', label: ts('Batch Calls'), code: ''} - ] - }, - { - lang: 'cli', - style: [ - {name: 'cv', label: ts('CV'), code: ''} - ] - }, - ]; + $scope.langs = ['php', 'js', 'ang', 'cli']; + $scope.code = { + php: [ + {name: 'oop', label: ts('OOP Style'), code: ''}, + {name: 'php', label: ts('Traditional'), code: ''} + ], + js: [ + {name: 'js', label: ts('Single Call'), code: ''}, + {name: 'js2', label: ts('Batch Calls'), code: ''} + ], + ang: [ + {name: 'ang', label: ts('Single Call'), code: ''}, + {name: 'ang2', label: ts('Batch Calls'), code: ''} + ], + cli: [ + {name: 'cv', label: ts('CV'), code: ''} + ] + }; if (!entities.length) { formatForSelect2(schema, entities, 'name', ['description']); @@ -107,15 +99,6 @@ } } - // Turn a flat array into a select2 array - function arrayToSelect2(array) { - var out = []; - _.each(array, function(item) { - out.push({id: item, text: item}); - }); - return out; - } - // Reformat an existing array of objects for compatibility with select2 function formatForSelect2(input, container, key, extra, prefix) { _.each(input, function(item) { @@ -231,8 +214,12 @@ (row.description ? '

' + _.escape(row.description) + '

' : ''); }; - $scope.clearParam = function(name) { - $scope.params[name] = $scope.availableParams[name].default; + $scope.clearParam = function(name, idx) { + if (typeof idx === 'undefined') { + $scope.params[name] = $scope.availableParams[name].default; + } else { + $scope.params[name].splice(idx, 1); + } }; $scope.isSpecial = function(name) { @@ -256,6 +243,11 @@ return isSelectRowCount($scope.params); }; + $scope.selectLang = function(lang) { + $scope.selectedTab.code = lang; + writeCode(); + }; + function isSelectRowCount(params) { return params && params.select && params.select.length === 1 && params.select[0] === 'row_count'; } @@ -323,7 +315,8 @@ function selectAction() { $scope.action = $routeParams.api4action; $scope.fieldsAndJoins.length = 0; - $scope.selectFieldsAndJoins.length = 0; + $scope.fieldsAndJoinsAndFunctions.length = 0; + $scope.fieldsAndJoinsAndFunctionsAndWildcards.length = 0; if (!actions.length) { formatForSelect2(getEntity().actions, actions, 'name', ['description', 'params']); } @@ -332,12 +325,29 @@ $scope.fields = getFieldList($scope.action); if (_.contains(['get', 'update', 'delete', 'replace'], $scope.action)) { $scope.fieldsAndJoins = addJoins($scope.fields); - $scope.selectFieldsAndJoins = addJoins($scope.fields, true); + var fieldsAndFunctions = _.cloneDeep($scope.fields); + // SQL functions are supported if HAVING is + if (actionInfo.params.having) { + fieldsAndFunctions.push({ + text: ts('FUNCTION'), + description: ts('Calculate result of a SQL function'), + children: _.transform(CRM.vars.api4.functions, function(result, fn) { + result.push({ + id: fn.name + '() AS ' + fn.name.toLowerCase(), + text: fn.name + '()', + description: fn.name + '(' + describeSqlFn(fn.params) + ')' + }); + }) + }); + } + $scope.fieldsAndJoinsAndFunctions = addJoins(fieldsAndFunctions, true); + $scope.fieldsAndJoinsAndFunctionsAndWildcards = addJoins(fieldsAndFunctions, true); } else { $scope.fieldsAndJoins = $scope.fields; - $scope.selectFieldsAndJoins = _.cloneDeep($scope.fields); + $scope.fieldsAndJoinsAndFunctions = $scope.fields; + $scope.fieldsAndJoinsAndFunctionsAndWildcards = _.cloneDeep($scope.fields); } - $scope.selectFieldsAndJoins.unshift({id: '*', text: '*', 'description': 'All core ' + $scope.entity + ' fields'}); + $scope.fieldsAndJoinsAndFunctionsAndWildcards.unshift({id: '*', text: '*', 'description': 'All core ' + $scope.entity + ' fields'}); _.each(actionInfo.params, function (param, name) { var format, defaultVal = _.cloneDeep(param.default); @@ -376,12 +386,24 @@ if (typeof objectParams[name] !== 'undefined') { $scope.$watch('params.' + name, function(values) { // Remove empty values - _.each(values, function(clause, index) { + _.each(values, function (clause, index) { if (!clause || !clause[0]) { - $scope.params[name].splice(index, 1); + $scope.clearParam(name, index); } }); }, true); + } + if (name === 'select' && actionInfo.params.having) { + $scope.$watchCollection('params.select', function(values) { + $scope.havingOptions.length = 0; + _.each(values, function(item) { + var pieces = item.split(' AS '), + alias = _.trim(pieces[pieces.length - 1]); + $scope.havingOptions.push({id: alias, text: alias}); + }); + }); + } + if (typeof objectParams[name] !== 'undefined' || name === 'groupBy' || name === 'select') { $scope.$watch('controls.' + name, function(value) { var field = value; $timeout(function() { @@ -404,6 +426,25 @@ writeCode(); } + function describeSqlFn(params) { + var desc = ' '; + _.each(params, function(param) { + desc += ' '; + if (param.prefix) { + desc += _.filter(param.prefix).join('|') + ' '; + } + if (param.expr === 1) { + desc += 'expr '; + } else if (param.expr > 1) { + desc += 'expr, ... '; + } + if (param.suffix) { + desc += ' ' + _.filter(param.suffix).join('|') + ' '; + } + }); + return desc.replace(/[ ]+/g, ' '); + } + function defaultValues(defaultVal) { _.each($scope.fields, function(field) { if (field.required) { @@ -445,59 +486,67 @@ results = result + 'Count'; } - // Write javascript - var js = "'" + entity + "', '" + action + "', {"; - _.each(params, function(param, key) { - js += "\n " + key + ': ' + stringify(param) + - (++i < paramCount ? ',' : ''); - if (key === 'checkPermissions') { - js += ' // IGNORED: permissions are always enforced from client-side requests'; - } - }); - js += "\n}"; - if (index || index === 0) { - js += ', ' + JSON.stringify(index); - } - code.js = "CRM.api4(" + js + ").then(function(" + results + ") {\n // do something with " + results + " array\n}, function(failure) {\n // handle failure\n});"; - code.js2 = "CRM.api4({" + results + ': [' + js + "]}).then(function(batch) {\n // do something with batch." + results + " array\n}, function(failure) {\n // handle failure\n});"; - code.ang = "crmApi4(" + js + ").then(function(" + results + ") {\n // do something with " + results + " array\n}, function(failure) {\n // handle failure\n});"; - code.ang2 = "crmApi4({" + results + ': [' + js + "]}).then(function(batch) {\n // do something with batch." + results + " array\n}, function(failure) {\n // handle failure\n});"; - - // Write php code - code.php = '$' + results + " = civicrm_api4('" + entity + "', '" + action + "', ["; - _.each(params, function(param, key) { - code.php += "\n '" + key + "' => " + phpFormat(param, 4) + ','; - }); - code.php += "\n]"; - if (index || index === 0) { - code.php += ', ' + phpFormat(index); - } - code.php += ");"; + switch ($scope.selectedTab.code) { + case 'js': + case 'ang': + // Write javascript + var js = "'" + entity + "', '" + action + "', {"; + _.each(params, function(param, key) { + js += "\n " + key + ': ' + stringify(param) + + (++i < paramCount ? ',' : ''); + if (key === 'checkPermissions') { + js += ' // IGNORED: permissions are always enforced from client-side requests'; + } + }); + js += "\n}"; + if (index || index === 0) { + js += ', ' + JSON.stringify(index); + } + code.js = "CRM.api4(" + js + ").then(function(" + results + ") {\n // do something with " + results + " array\n}, function(failure) {\n // handle failure\n});"; + code.js2 = "CRM.api4({" + results + ': [' + js + "]}).then(function(batch) {\n // do something with batch." + results + " array\n}, function(failure) {\n // handle failure\n});"; + code.ang = "crmApi4(" + js + ").then(function(" + results + ") {\n // do something with " + results + " array\n}, function(failure) {\n // handle failure\n});"; + code.ang2 = "crmApi4({" + results + ': [' + js + "]}).then(function(batch) {\n // do something with batch." + results + " array\n}, function(failure) {\n // handle failure\n});"; + break; + + case 'php': + // Write php code + code.php = '$' + results + " = civicrm_api4('" + entity + "', '" + action + "', ["; + _.each(params, function(param, key) { + code.php += "\n '" + key + "' => " + phpFormat(param, 4) + ','; + }); + code.php += "\n]"; + if (index || index === 0) { + code.php += ', ' + phpFormat(index); + } + code.php += ");"; + + // Write oop code + code.oop = '$' + results + " = " + formatOOP(entity, action, params, 2) + "\n ->execute()"; + if (isSelectRowCount(params)) { + code.oop += "\n ->count()"; + } else if (_.isNumber(index)) { + code.oop += !index ? '\n ->first()' : (index === -1 ? '\n ->last()' : '\n ->itemAt(' + index + ')'); + } else if (index) { + if (_.isString(index) || (_.isPlainObject(index) && !index[0] && !index['0'])) { + code.oop += "\n ->indexBy('" + (_.isPlainObject(index) ? _.keys(index)[0] : index) + "')"; + } + if (_.isArray(index) || _.isPlainObject(index)) { + code.oop += "\n ->column('" + (_.isArray(index) ? index[0] : _.values(index)[0]) + "')"; + } + } + code.oop += ";\n"; + if (!_.isNumber(index) && !isSelectRowCount(params)) { + code.oop += "foreach ($" + results + ' as $' + ((_.isString(index) && index) ? index + ' => $' : '') + result + ') {\n // do something\n}'; + } + break; - // Write oop code - code.oop = '$' + results + " = " + formatOOP(entity, action, params, 2) + "\n ->execute()"; - if (isSelectRowCount(params)) { - code.oop += "\n ->count()"; - } else if (_.isNumber(index)) { - code.oop += !index ? '\n ->first()' : (index === -1 ? '\n ->last()' : '\n ->itemAt(' + index + ')'); - } else if (index) { - if (_.isString(index) || (_.isPlainObject(index) && !index[0] && !index['0'])) { - code.oop += "\n ->indexBy('" + (_.isPlainObject(index) ? _.keys(index)[0] : index) + "')"; - } - if (_.isArray(index) || _.isPlainObject(index)) { - code.oop += "\n ->column('" + (_.isArray(index) ? index[0] : _.values(index)[0]) + "')"; - } + case 'cli': + // Write cli code + code.cv = 'cv api4 ' + entity + '.' + action + " '" + stringify(params) + "'"; } - code.oop += ";\n"; - if (!_.isNumber(index) && !isSelectRowCount(params)) { - code.oop += "foreach ($" + results + ' as $' + ((_.isString(index) && index) ? index + ' => $' : '') + result + ') {\n // do something\n}'; - } - - // Write cli code - code.cv = 'cv api4 ' + entity + '.' + action + " '" + stringify(params) + "'"; } _.each($scope.code, function(vals) { - _.each(vals.style, function(style) { + _.each(vals, function(style) { style.code = code[style.name] ? prettyPrintOne(code[style.name]) : ''; }); }); @@ -681,18 +730,124 @@ return docs.params[name]; }; + $scope.executeDoc = function() { + var doc = { + description: ts('Runs API call on the CiviCRM database.'), + comment: ts('Results and debugging info will be displayed below.') + }; + if ($scope.action === 'delete') { + doc.WARNING = ts('This API call will be executed on the real database. Deleting data cannot be undone.'); + } + else if ($scope.action && $scope.action.slice(0, 3) !== 'get') { + doc.WARNING = ts('This API call will be executed on the real database. It cannot be undone.'); + } + return doc; + }; + + $scope.saveDoc = function() { + return { + description: ts('Save API call as a smart group.'), + comment: ts('Create a SavedSearch using these API params to populate a smart group.') + + '\n\n' + ts('NOTE: you must select contact id as the only field.') + }; + }; + $scope.$watch('params', writeCode, true); $scope.$watch('index', writeCode); writeCode(); + $scope.save = function() { + $scope.params.limit = $scope.params.offset = 0; + if ($scope.params.chain.length) { + CRM.alert(ts('Smart groups are not compatible with API chaining.'), ts('Error'), 'error', {expires: 5000}); + return; + } + if ($scope.params.select.length !== 1 || !_.includes($scope.params.select[0], 'id')) { + CRM.alert(ts('To create a smart group, the API must select contact id and no other fields.'), ts('Error'), 'error', {expires: 5000}); + return; + } + var model = { + title: '', + description: '', + visibility: 'User and User Admin Only', + group_type: [], + id: null, + entity: $scope.entity, + params: JSON.parse(angular.toJson($scope.params)) + }; + model.params.version = 4; + delete model.params.chain; + delete model.params.debug; + delete model.params.limit; + delete model.params.offset; + delete model.params.orderBy; + delete model.params.checkPermissions; + var options = CRM.utils.adjustDialogDefaults({ + width: '500px', + autoOpen: false, + title: ts('Save smart group') + }); + dialogService.open('saveSearchDialog', '~/api4Explorer/SaveSearch.html', model, options); + }; + }); + + angular.module('api4Explorer').controller('SaveSearchCtrl', function($scope, crmApi4, dialogService) { + var ts = $scope.ts = CRM.ts(), + model = $scope.model; + $scope.groupEntityRefParams = { + entity: 'Group', + api: { + params: {is_hidden: 0, is_active: 1, 'saved_search_id.api_entity': model.entity}, + extra: ['saved_search_id', 'description', 'visibility', 'group_type'] + }, + select: { + allowClear: true, + minimumInputLength: 0, + placeholder: ts('Select existing group') + } + }; + if (!CRM.checkPerm('administer reserved groups')) { + $scope.groupEntityRefParams.api.params.is_reserved = 0; + } + $scope.perm = { + administerReservedGroups: CRM.checkPerm('administer reserved groups') + }; + $scope.options = CRM.vars.api4.groupOptions; + $scope.$watch('model.id', function(id) { + if (id) { + _.assign(model, $('#api-save-search-select-group').select2('data').extra); + } + }); + $scope.cancel = function() { + dialogService.cancel('saveSearchDialog'); + }; + $scope.save = function() { + $('.ui-dialog:visible').block(); + var group = model.id ? {id: model.id} : {title: model.title}; + group.description = model.description; + group.visibility = model.visibility; + group.group_type = model.group_type; + group.saved_search_id = '$id'; + var savedSearch = { + api_entity: model.entity, + api_params: model.params + }; + if (group.id) { + savedSearch.id = model.saved_search_id; + } + crmApi4('SavedSearch', 'save', {records: [savedSearch], chain: {group: ['Group', 'save', {'records': [group]}]}}) + .then(function(result) { + dialogService.close('saveSearchDialog', result[0]); + }); + }; }); - angular.module('api4Explorer').directive('crmApi4WhereClause', function($timeout) { + angular.module('api4Explorer').directive('crmApi4Clause', function($timeout) { return { scope: { - data: '=crmApi4WhereClause' + data: '=crmApi4Clause' }, - templateUrl: '~/api4Explorer/WhereClause.html', + templateUrl: '~/api4Explorer/Clause.html', link: function (scope, element, attrs) { var ts = scope.ts = CRM.ts(); scope.newClause = ''; @@ -700,7 +855,7 @@ scope.operators = CRM.vars.api4.operators; scope.addGroup = function(op) { - scope.data.where.push([op, []]); + scope.data.clauses.push([op, []]); }; scope.removeGroup = function() { @@ -708,7 +863,7 @@ }; scope.onSort = function(event, ui) { - $('.api4-where-fieldset').toggleClass('api4-sorting', event.type === 'sortstart'); + $(element).closest('.api4-clause-fieldset').toggleClass('api4-sorting', event.type === 'sortstart'); $('.api4-input.form-inline').css('margin-left', ''); }; @@ -725,12 +880,12 @@ var field = value; $timeout(function() { if (field) { - scope.data.where.push([field, '=', '']); + scope.data.clauses.push([field, '=', '']); scope.newClause = null; } }); }); - scope.$watch('data.where', function(values) { + scope.$watch('data.clauses', function(values) { // Remove empty values _.each(values, function(clause, index) { if (typeof clause !== 'undefined' && !clause[0]) {