Merge pull request #16411 from colemanw/api4EntityDoc
[civicrm-core.git] / ang / api4Explorer / Explorer.js
index 982f618d3f361942d3465f2b9ed4c2fe0ac8699e..542b84edae1f1a8994a02a2359d8c5d276aff6f7 100644 (file)
     $scope.actions = actions;
     $scope.fields = [];
     $scope.fieldsAndJoins = [];
+    $scope.selectFieldsAndJoins = [];
     $scope.availableParams = {};
     $scope.params = {};
     $scope.index = '';
+    $scope.selectedTab = {result: 'result', code: 'php'};
+    $scope.perm = {
+      accessDebugOutput: CRM.checkPerm('access debug output')
+    };
     var getMetaParams = {},
       objectParams = {orderBy: 'ASC', values: '', chain: ['Entity', '', '{}']},
+      docs = CRM.vars.api4.docs,
       helpTitle = '',
       helpContent = {};
     $scope.helpTitle = '';
     $scope.helpContent = {};
     $scope.entity = $routeParams.api4entity;
     $scope.result = [];
+    $scope.debug = null;
     $scope.status = 'default';
     $scope.loading = false;
     $scope.controls = {};
-    $scope.codeLabel = {
-      oop: ts('PHP (oop style)'),
-      php: ts('PHP (traditional)'),
-      js: ts('Javascript'),
-      cli: ts('Command Line')
-    };
-    $scope.code = codeDefaults();
-
-    function codeDefaults() {
-      return _.mapValues($scope.codeLabel, function(val, key) {
-        return key === 'oop' ? ts('Select an entity and action') : '';
-      });
-    }
+    $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: ''}
+        ]
+      },
+    ];
 
     if (!entities.length) {
       formatForSelect2(schema, entities, 'name', ['description']);
       return fields;
     }
 
-    function addJoins(fieldList) {
+    function addJoins(fieldList, addWildcard) {
       var fields = _.cloneDeep(fieldList),
         fks = _.findWhere(links, {entity: $scope.entity}) || {};
       _.each(fks.links, function(link) {
-        var linkFields = entityFields(link.entity);
+        var linkFields = _.cloneDeep(entityFields(link.entity)),
+          wildCard = addWildcard ? [{id: link.alias + '.*', text: link.alias + '.*', 'description': 'All core ' + link.entity + ' fields'}] : [];
         if (linkFields) {
           fields.push({
             text: link.alias,
             description: 'Join to ' + link.entity,
-            children: formatForSelect2(linkFields, [], 'name', ['description'], link.alias + '.')
+            children: wildCard.concat(formatForSelect2(linkFields, [], 'name', ['description'], link.alias + '.'))
           });
         }
       });
       }
     };
 
+    // Format the href for a @see help annotation
+    $scope.formatRef = function(see) {
+      var match = see.match(/^\\Civi\\Api4\\([a-zA-Z]+)$/);
+      if (match) {
+        return '#/explorer/' + match[1];
+      }
+      if (see[0] === '\\') {
+        return 'https://github.com/civicrm/civicrm-core/blob/master' + see.replace(/\\/i, '/') + '.php';
+      }
+      return see;
+    };
+
     $scope.fieldHelp = function(fieldName) {
       var field = getField(fieldName, $scope.entity, $scope.action);
       if (!field) {
         $scope.params.select = [];
       } else {
         $scope.params.select = ['row_count'];
+        $scope.index = '';
         if ($scope.params.limit == 25) {
           $scope.params.limit = 0;
         }
 
     function selectAction() {
       $scope.action = $routeParams.api4action;
-      $scope.fieldsAndJoins = [];
+      $scope.fieldsAndJoins.length = 0;
+      $scope.selectFieldsAndJoins.length = 0;
       if (!actions.length) {
         formatForSelect2(getEntity().actions, actions, 'name', ['description', 'params']);
       }
         $scope.fields = getFieldList($scope.action);
         if (_.contains(['get', 'update', 'delete', 'replace'], $scope.action)) {
           $scope.fieldsAndJoins = addJoins($scope.fields);
+          $scope.selectFieldsAndJoins = addJoins($scope.fields, true);
         } else {
           $scope.fieldsAndJoins = $scope.fields;
+          $scope.selectFieldsAndJoins = _.cloneDeep($scope.fields);
         }
+        $scope.selectFieldsAndJoins.unshift({id: '*', text: '*', 'description': 'All core ' + $scope.entity + ' fields'});
         _.each(actionInfo.params, function (param, name) {
           var format,
             defaultVal = _.cloneDeep(param.default);
               default:
                 format = 'raw';
             }
-            if (name == 'limit') {
+            if (name === 'limit') {
               defaultVal = 25;
             }
+            if (name === 'debug') {
+              defaultVal = true;
+            }
             if (name === 'values') {
               defaultVal = defaultValues(defaultVal);
             }
     }
 
     function writeCode() {
-      var code = codeDefaults(),
+      var code = {},
         entity = $scope.entity,
         action = $scope.action,
         params = getParams(),
-        index = isInt($scope.index) ? +$scope.index : $scope.index,
+        index = isInt($scope.index) ? +$scope.index : parseYaml($scope.index),
         result = 'result';
       if ($scope.entity && $scope.action) {
+        delete params.debug;
         if (action.slice(0, 3) === 'get') {
           result = entity.substr(0, 7) === 'Custom_' ? _.camelCase(entity.substr(7)) : entity;
           result = lcfirst(action.replace(/s$/, '').slice(3) || result);
         }
 
         // Write javascript
-        code.js = "CRM.api4('" + entity + "', '" + action + "', {";
+        var js = "'" + entity + "', '" + action + "', {";
         _.each(params, function(param, key) {
-          code.js += "\n  " + key + ': ' + stringify(param) +
+          js += "\n  " + key + ': ' + stringify(param) +
             (++i < paramCount ? ',' : '');
           if (key === 'checkPermissions') {
-            code.js += ' // IGNORED: permissions are always enforced from client-side requests';
+            js += ' // IGNORED: permissions are always enforced from client-side requests';
           }
         });
-        code.js += "\n}";
+        js += "\n}";
         if (index || index === 0) {
-          code.js += ', ' + JSON.stringify(index);
+          js += ', ' + JSON.stringify(index);
         }
-        code.js += ").then(function(" + results + ") {\n  // do something with " + results + " array\n}, function(failure) {\n  // handle failure\n});";
+        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 + "', [";
           }
         });
         code.oop += "\n  ->execute()";
-        if (_.isNumber(index)) {
+        if (isSelectRowCount) {
+          code.oop += "\n  ->count()";
+        } else if (_.isNumber(index)) {
           code.oop += !index ? '\n  ->first()' : (index === -1 ? '\n  ->last()' : '\n  ->itemAt(' + index + ')');
         } else if (index) {
-          code.oop += "\n  ->indexBy('" + index + "')";
-        } else if (isSelectRowCount) {
-          code.oop += "\n  ->count()";
+          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) {
         }
 
         // Write cli code
-        code.cli = 'cv api4 ' + entity + '.' + action + " '" + stringify(params) + "'";
+        code.cv = 'cv api4 ' + entity + '.' + action + " '" + stringify(params) + "'";
       }
-      _.each(code, function(val, type) {
-        $scope.code[type] = prettyPrintOne(_.escape(val));
+      _.each($scope.code, function(vals) {
+        _.each(vals.style, function(style) {
+          style.code = code[style.name] ? prettyPrintOne(code[style.name]) : '';
+        });
       });
     }
 
       $scope.loading = true;
       $http.post(CRM.url('civicrm/ajax/api4/' + $scope.entity + '/' + $scope.action, {
         params: angular.toJson(getParams()),
-        index: $scope.index
+        index: isInt($scope.index) ? +$scope.index : parseYaml($scope.index)
       }), null, {
         headers: {
           'X-Requested-With': 'XMLHttpRequest'
       }).then(function(resp) {
           $scope.loading = false;
           $scope.status = 'success';
+          $scope.debug = debugFormat(resp.data);
           $scope.result = [formatMeta(resp.data), prettyPrintOne(_.escape(JSON.stringify(resp.data.values, null, 2)), 'js', 1)];
         }, function(resp) {
           $scope.loading = false;
           $scope.status = 'danger';
+          $scope.debug = debugFormat(resp.data);
           $scope.result = [formatMeta(resp), prettyPrintOne(_.escape(JSON.stringify(resp.data, null, 2)))];
         });
     };
 
+    function debugFormat(data) {
+      var debug = data.debug ? prettyPrintOne(_.escape(JSON.stringify(data.debug, null, 2)).replace(/\\n/g, "\n")) : null;
+      delete data.debug;
+      return debug;
+    }
+
     /**
      * Format value to look like php code
      */
       $scope.helpTitle = helpTitle = $scope.entity;
       $scope.helpContent = helpContent = {
         description: entityInfo.description,
-        comment: entityInfo.comment
+        comment: entityInfo.comment,
+        see: entityInfo.see
       };
     }
 
     if (!$scope.entity) {
-      $scope.helpTitle = helpTitle = ts('Help');
-      $scope.helpContent = helpContent = {description: ts('Welcome to the api explorer.'), comment: ts('Select an entity to begin.')};
+      $scope.helpTitle = helpTitle = ts('APIv4 Explorer');
+      $scope.helpContent = helpContent = {description: docs.description, comment: docs.comment, see: docs.see};
     } else if (!actions.length && !getEntity().actions) {
       getMetaParams.actions = [$scope.entity, 'getActions', {chain: {fields: [$scope.entity, 'getFields', {action: '$name'}]}}];
       fetchMeta();
         $location.url('/explorer/' + $scope.entity + '/' + newVal);
       } else if (newVal) {
         $scope.helpTitle = helpTitle = $scope.entity + '::' + newVal;
-        $scope.helpContent = helpContent = _.pick(_.findWhere(getEntity().actions, {name: newVal}), ['description', 'comment']);
+        $scope.helpContent = helpContent = _.pick(_.findWhere(getEntity().actions, {name: newVal}), ['description', 'comment', 'see']);
       }
     });
 
-    $scope.indexHelp = {
-      description: ts('(string|int) Index results or select by index.'),
-      comment: ts('Pass a string to index the results by a field value. E.g. index: "name" will return an associative array with names as keys.') + '\n\n' +
-        ts('Pass an integer to return a single result; e.g. index: 0 will return the first result, 1 will return the second, and -1 will return the last.')
+    $scope.paramDoc = function(name) {
+      return docs.params[name];
     };
 
     $scope.$watch('params', writeCode, true);
               });
             } else if (dataType === 'Boolean') {
               $el.attr('placeholder', ts('- select -')).crmSelect2({allowClear: false, multiple: multi, placeholder: ts('- select -'), data: [
-                {id: '1', text: ts('Yes')},
-                {id: '0', text: ts('No')}
+                {id: 'true', text: ts('Yes')},
+                {id: 'false', text: ts('No')}
               ]});
             }
           } else if (dataType === 'Integer' && !multi) {