From b65fa6dcd5684ee73ad2cc0da0a231de4ee3a5cd Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Wed, 22 Jan 2020 06:46:30 -0500 Subject: [PATCH] Add debug info to api4 output --- CRM/Api4/Page/AJAX.php | 4 +- CRM/Api4/Page/Api4Explorer.php | 1 + Civi/API/Subscriber/XDebugSubscriber.php | 26 +++++++++---- .../Subscriber/PostSelectQuerySubscriber.php | 10 +++-- Civi/Api4/Generic/AbstractAction.php | 22 ++++++++++- Civi/Api4/Generic/Result.php | 4 ++ Civi/Api4/Generic/Traits/DAOActionTrait.php | 3 ++ Civi/Api4/Query/Api4SelectQuery.php | 10 +++++ ang/api4Explorer/Explorer.html | 39 ++++++++++++++----- ang/api4Explorer/Explorer.js | 13 +++++++ css/api4-explorer.css | 6 +++ 11 files changed, 114 insertions(+), 24 deletions(-) diff --git a/CRM/Api4/Page/AJAX.php b/CRM/Api4/Page/AJAX.php index 39c8febe29..da37b04682 100644 --- a/CRM/Api4/Page/AJAX.php +++ b/CRM/Api4/Page/AJAX.php @@ -89,8 +89,8 @@ class CRM_Api4_Page_AJAX extends CRM_Core_Page { ]; if (CRM_Core_Permission::check('view debug output')) { $response['error_message'] = $e->getMessage(); - if (\Civi::settings()->get('backtrace')) { - $response['backtrace'] = $e->getTrace(); + if (!empty($params['debug']) && \Civi::settings()->get('backtrace')) { + $response['debug']['backtrace'] = $e->getTrace(); } } } diff --git a/CRM/Api4/Page/Api4Explorer.php b/CRM/Api4/Page/Api4Explorer.php index 65af14d147..f1fb6f734d 100644 --- a/CRM/Api4/Page/Api4Explorer.php +++ b/CRM/Api4/Page/Api4Explorer.php @@ -30,6 +30,7 @@ class CRM_Api4_Page_Api4Explorer extends CRM_Core_Page { ]; Civi::resources() ->addVars('api4', $vars) + ->addPermissions(['access debug output']) ->addScriptFile('civicrm', 'js/load-bootstrap.js') ->addScriptFile('civicrm', 'bower_components/js-yaml/dist/js-yaml.min.js') ->addScriptFile('civicrm', 'bower_components/google-code-prettify/bin/prettify.min.js') diff --git a/Civi/API/Subscriber/XDebugSubscriber.php b/Civi/API/Subscriber/XDebugSubscriber.php index 4b2f6d9a24..aec0c1259e 100644 --- a/Civi/API/Subscriber/XDebugSubscriber.php +++ b/Civi/API/Subscriber/XDebugSubscriber.php @@ -36,14 +36,26 @@ class XDebugSubscriber implements EventSubscriberInterface { public function onApiRespond(\Civi\API\Event\RespondEvent $event) { $apiRequest = $event->getApiRequest(); $result = $event->getResponse(); - if (function_exists('xdebug_time_index') - && \CRM_Utils_Array::value('debug', $apiRequest['params']) - // result would not be an array for getvalue - && is_array($result) + if ( + function_exists('xdebug_time_index') + && !empty($apiRequest['params']['debug']) + && (empty($apiRequest['params']['check_permissions']) || \CRM_Core_Permission::check('view debug output')) ) { - $result['xdebug']['peakMemory'] = xdebug_peak_memory_usage(); - $result['xdebug']['memory'] = xdebug_memory_usage(); - $result['xdebug']['timeIndex'] = xdebug_time_index(); + if (is_a($result, '\Civi\Api4\Generic\Result')) { + $result->debug = $result->debug ?? []; + $debug =& $result->debug; + } + // result would not be an array for api3 getvalue + elseif (is_array($result)) { + $result['xdebug'] = $result['xdebug'] ?? []; + $debug =& $result['xdebug']; + } + else { + return; + } + $debug['peakMemory'] = xdebug_peak_memory_usage(); + $debug['memory'] = xdebug_memory_usage(); + $debug['timeIndex'] = xdebug_time_index(); $event->setResponse($result); } } diff --git a/Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php b/Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php index 00b2cc9439..7add31036b 100644 --- a/Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php +++ b/Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php @@ -240,9 +240,13 @@ class PostSelectQuerySubscriber implements EventSubscriberInterface { }, $selects, array_keys($selects)); $newSelect = sprintf('SELECT DISTINCT %s', implode(", ", $aliasedSelects)); - $sql = str_replace("\n", ' ', $query->getQuery()->toSQL()); - $originalSelect = substr($sql, 0, strpos($sql, ' FROM')); - $sql = str_replace($originalSelect, $newSelect, $sql); + $sql = $query->getQuery()->toSQL(); + // Replace the "SELECT" clause + $sql = $newSelect . substr($sql, strpos($sql, "\nFROM")); + + if (is_array($query->debugOutput)) { + $query->debugOutput['join_sql'][] = $sql; + } $relatedResults = []; $resultDAO = \CRM_Core_DAO::executeQuery($sql); diff --git a/Civi/Api4/Generic/AbstractAction.php b/Civi/Api4/Generic/AbstractAction.php index 965c5fc724..2457009948 100644 --- a/Civi/Api4/Generic/AbstractAction.php +++ b/Civi/Api4/Generic/AbstractAction.php @@ -26,8 +26,10 @@ use Civi\Api4\Utils\ActionUtil; /** * Base class for all api actions. * - * @method $this setCheckPermissions(bool $value) + * @method $this setCheckPermissions(bool $value) Enable/disable permission checks * @method bool getCheckPermissions() + * @method $this setDebug(bool $value) Enable/disable debug output + * @method bool getDebug() * @method $this setChain(array $chain) * @method array getChain() */ @@ -70,6 +72,13 @@ abstract class AbstractAction implements \ArrayAccess { */ protected $checkPermissions = TRUE; + /** + * Add debugging info to the api result. + * + * @var bool + */ + protected $debug = FALSE; + /** * @var string */ @@ -107,6 +116,8 @@ abstract class AbstractAction implements \ArrayAccess { */ private $_id; + protected $_debugOutput = []; + /** * Action constructor. * @@ -216,7 +227,14 @@ abstract class AbstractAction implements \ArrayAccess { /** @var \Civi\API\Kernel $kernel */ $kernel = \Civi::service('civi_api_kernel'); - return $kernel->runRequest($this); + $result = $kernel->runRequest($this); + if ($this->debug && (!$this->checkPermissions || \CRM_Core_Permission::check('view debug output'))) { + $result->debug = array_merge($result->debug, $this->_debugOutput); + } + else { + $result->debug = NULL; + } + return $result; } /** diff --git a/Civi/Api4/Generic/Result.php b/Civi/Api4/Generic/Result.php index ca10ef6f5d..c32bfa5749 100644 --- a/Civi/Api4/Generic/Result.php +++ b/Civi/Api4/Generic/Result.php @@ -23,6 +23,10 @@ class Result extends \ArrayObject { * @var string */ public $action; + /** + * @var array + */ + public $debug; /** * Api version * @var int diff --git a/Civi/Api4/Generic/Traits/DAOActionTrait.php b/Civi/Api4/Generic/Traits/DAOActionTrait.php index 941056a050..451e31f7f2 100644 --- a/Civi/Api4/Generic/Traits/DAOActionTrait.php +++ b/Civi/Api4/Generic/Traits/DAOActionTrait.php @@ -75,6 +75,9 @@ trait DAOActionTrait { $query->orderBy = $this->getOrderBy(); $query->limit = $this->getLimit(); $query->offset = $this->getOffset(); + if ($this->getDebug()) { + $query->debugOutput =& $this->_debugOutput; + } $result = $query->run(); if (is_array($result)) { \CRM_Utils_API_HTMLInputCoder::singleton()->decodeRows($result); diff --git a/Civi/Api4/Query/Api4SelectQuery.php b/Civi/Api4/Query/Api4SelectQuery.php index d0baccee6b..3556aaca8e 100644 --- a/Civi/Api4/Query/Api4SelectQuery.php +++ b/Civi/Api4/Query/Api4SelectQuery.php @@ -54,6 +54,13 @@ class Api4SelectQuery extends SelectQuery { */ protected $joinedTables = []; + /** + * If set to an array, this will start collecting debug info. + * + * @var null|array + */ + public $debugOutput = NULL; + /** * @param string $entity * @param bool $checkPermissions @@ -105,6 +112,9 @@ class Api4SelectQuery extends SelectQuery { $results = []; $sql = $this->query->toSQL(); + if (is_array($this->debugOutput)) { + $this->debugOutput['main_sql'] = $sql; + } $query = \CRM_Core_DAO::executeQuery($sql); while ($query->fetch()) { diff --git a/ang/api4Explorer/Explorer.html b/ang/api4Explorer/Explorer.html index bd7b8ba5e2..462581267b 100644 --- a/ang/api4Explorer/Explorer.html +++ b/ang/api4Explorer/Explorer.html @@ -142,17 +142,36 @@
-
-

- - - - - {{ ts('Result') }} -

-
+
-

+          
+

+          
+
+

+            

+ {{ ts('To view debugging output, enable the debug param before executing.') }} +

+

+ {{ ts('Enable backtrace in system settings to see error backtraces.') }} +

+
diff --git a/ang/api4Explorer/Explorer.js b/ang/api4Explorer/Explorer.js index 33596ec3e5..10a78d5ae6 100644 --- a/ang/api4Explorer/Explorer.js +++ b/ang/api4Explorer/Explorer.js @@ -30,6 +30,10 @@ $scope.availableParams = {}; $scope.params = {}; $scope.index = ''; + $scope.resultTab = {selected: 'result'}; + $scope.perm = { + accessDebugOutput: CRM.checkPerm('access debug output') + }; var getMetaParams = {}, objectParams = {orderBy: 'ASC', values: '', chain: ['Entity', '', '{}']}, docs = CRM.vars.api4.docs, @@ -39,6 +43,7 @@ $scope.helpContent = {}; $scope.entity = $routeParams.api4entity; $scope.result = []; + $scope.debug = null; $scope.status = 'default'; $scope.loading = false; $scope.controls = {}; @@ -495,14 +500,22 @@ }).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 */ diff --git a/css/api4-explorer.css b/css/api4-explorer.css index 9451bc26d6..8041edda26 100644 --- a/css/api4-explorer.css +++ b/css/api4-explorer.css @@ -33,6 +33,12 @@ border-bottom-left-radius: 0; margin-bottom: 0; } +#bootstrap-theme.api4-explorer-page .panel-heading.nav-tabs { + padding: 8px 0 0 20px; +} +#bootstrap-theme .panel-heading>li>a { + background-color: #f1f1f18c +} #bootstrap-theme.api4-explorer-page .explorer-code-panel table td:first-child { width: 5em; } -- 2.25.1