X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=ang%2Fapi4Explorer%2FExplorer.js;h=a9f0459c48fee4e61506405a7d8b5559c430d640;hb=f28caa2ca141cb74f6f0abc2f8feee7f5a926ce8;hp=b589bdaaf3e420d137891fb8fa411281d5e3d012;hpb=40cbd643a97e4a9c7ff63279e5e88a5a13f1be8b;p=civicrm-core.git diff --git a/ang/api4Explorer/Explorer.js b/ang/api4Explorer/Explorer.js index b589bdaaf3..a9f0459c48 100644 --- a/ang/api4Explorer/Explorer.js +++ b/ang/api4Explorer/Explorer.js @@ -25,14 +25,17 @@ $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,12 +214,19 @@ (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) { - var specialParams = ['select', 'fields', 'action', 'where', 'values', 'defaults', 'orderBy', 'chain']; + var specialParams = ['select', 'fields', 'action', 'where', 'values', 'defaults', 'orderBy', 'chain', 'groupBy', 'having']; + if ($scope.availableParams.limit && $scope.availableParams.offset) { + specialParams.push('limit', 'offset'); + } return _.contains(specialParams, name); }; @@ -256,6 +246,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 +318,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 +328,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); @@ -373,26 +386,42 @@ deep: format === 'json' }); } - if (typeof objectParams[name] !== 'undefined') { - $scope.$watch('params.' + name, function(values) { + if (typeof objectParams[name] !== 'undefined' && name !== 'orderBy') { + $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() { if (field) { - var defaultOp = _.cloneDeep(objectParams[name]); - if (name === 'chain') { - var num = $scope.params.chain.length; - defaultOp[0] = field; - field = 'name_me_' + num; + if (typeof objectParams[name] === 'undefined') { + $scope.params[name].push(field); + } else { + var defaultOp = _.cloneDeep(objectParams[name]); + if (name === 'chain') { + var num = $scope.params.chain.length; + defaultOp[0] = field; + field = 'name_me_' + num; + } + $scope.params[name].push([field, defaultOp]); } - $scope.params[name].push([field, defaultOp]); $scope.controls[name] = null; } }); @@ -404,6 +433,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 +493,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]) + "')"; - } - } - code.oop += ";\n"; - if (!_.isNumber(index) && !isSelectRowCount(params)) { - code.oop += "foreach ($" + results + ' as $' + ((_.isString(index) && index) ? index + ' => $' : '') + result + ') {\n // do something\n}'; + case 'cli': + // Write cli code + code.cv = 'cv api4 ' + entity + '.' + action + " '" + stringify(params) + "'"; } - - // 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]) : ''; }); }); @@ -709,6 +765,9 @@ $scope.save = function() { var model = { title: '', + description: '', + visibility: 'User and User Admin Only', + group_type: [], id: null, entity: $scope.entity, params: JSON.parse(angular.toJson($scope.params)) @@ -731,9 +790,28 @@ 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) { - model.description = $('#api-save-search-select-group').select2('data').extra.description; + _.assign(model, $('#api-save-search-select-group').select2('data').extra); } }); $scope.cancel = function() { @@ -743,13 +821,15 @@ $('.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 = $('#api-save-search-select-group').select2('data').extra.saved_search_id; + savedSearch.id = model.saved_search_id; } crmApi4('SavedSearch', 'save', {records: [savedSearch], chain: {group: ['Group', 'save', {'records': [group]}]}}) .then(function(result) { @@ -758,12 +838,12 @@ }; }); - 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 = ''; @@ -771,7 +851,7 @@ scope.operators = CRM.vars.api4.operators; scope.addGroup = function(op) { - scope.data.where.push([op, []]); + scope.data.clauses.push([op, []]); }; scope.removeGroup = function() { @@ -779,7 +859,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', ''); }; @@ -796,12 +876,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]) {