Skip to content

Commit

Permalink
APIv4 Explorer - performance boost with fewer watch expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
colemanw committed Apr 12, 2020
1 parent eae807e commit e74c275
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 103 deletions.
54 changes: 29 additions & 25 deletions ang/api4Explorer/Explorer.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ <h1 crm-page-title>
SelectRowCount
</label>
</div>
<div class="api4-input form-inline" ng-mouseenter="help(name, param)" ng-mouseleave="help()" ng-repeat="(name, param) in availableParams" ng-if="!isSpecial(name) && param.type[0] === 'bool' && param.default === null">
<label>{{ name }}<span class="crm-marker" ng-if="param.required"> *</span></label>
<div class="api4-input form-inline" ng-mouseenter="help(name, param)" ng-mouseleave="help()" ng-repeat="(name, param) in availableParams" ng-if="::!isSpecial(name) && param.type[0] === 'bool' && param.default === null">
<label>{{ name }}<span class="crm-marker" ng-if="::param.required"> *</span></label>
<label class="radio-inline">
<input type="radio" ng-model="params[name]" ng-value="true" />true
</label>
Expand All @@ -63,29 +63,29 @@ <h1 crm-page-title>
<input class="collapsible-optgroups form-control huge" ng-model="controls.select" crm-ui-select="{data: fieldsAndJoinsAndFunctionsAndWildcards}" placeholder="Add select" />
</div>
</fieldset>
<div class="api4-input form-inline" ng-mouseenter="help('fields', availableParams.fields)" ng-mouseleave="help()"ng-if="::availableParams.fields">
<div class="api4-input form-inline" ng-mouseenter="help('fields', availableParams.fields)" ng-mouseleave="help()" ng-if="::availableParams.fields">
<label for="api4-param-fields">fields<span class="crm-marker" ng-if="::availableParams.fields.required"> *</span></label>
<input class="form-control" ng-list crm-ui-select="::{data: fields, multiple: true}" id="api4-param-fields" ng-model="params.fields" style="width: 85%;"/>
</div>
<div class="api4-input form-inline" ng-mouseenter="help('action', availableParams.action)" ng-mouseleave="help()"ng-if="::availableParams.action">
<label for="api4-param-action">action<span class="crm-marker" ng-if="::availableParams.action.required"> *</span></label>
<input class="form-control" crm-ui-select="{data: actions, allowClear: true, placeholder: 'None'}" id="api4-param-action" ng-model="params.action"/>
</div>
<div class="api4-input form-inline" ng-mouseenter="help(name, param)" ng-mouseleave="help()" ng-repeat="(name, param) in availableParams" ng-if="!isSpecial(name) && (param.type[0] === 'string' || param.type[0] === 'int')">
<label for="api4-param-{{ name }}">{{ name }}<span class="crm-marker" ng-if="::param.required"> *</span></label>
<input class="form-control" ng-if="!param.options" type="{{ param.type[0] === 'int' && param.type.length === 1 ? 'number' : 'text' }}" id="api4-param-{{ name }}" ng-model="params[name]"/>
<select class="form-control" ng-if="param.options" ng-options="o for o in param.options" id="api4-param-{{ name }}" ng-model="params[name]"></select>
<div class="api4-input form-inline" ng-mouseenter="help(name, param)" ng-mouseleave="help()" ng-repeat="(name, param) in availableParams" ng-if="::!isSpecial(name) && (param.type[0] === 'string' || param.type[0] === 'int')">
<label for="api4-param-{{:: name }}">{{:: name }}<span class="crm-marker" ng-if="::param.required"> *</span></label>
<input class="form-control" ng-if="::!param.options" type="{{ param.type[0] === 'int' && param.type.length === 1 ? 'number' : 'text' }}" id="api4-param-{{:: name }}" ng-model="params[name]"/>
<select class="form-control" ng-if="::param.options" ng-options="o for o in param.options" id="api4-param-{{:: name }}" ng-model="params[name]"></select>
<a href class="crm-hover-button" title="Clear" ng-click="clearParam(name)" ng-show="!!params[name]"><i class="crm-i fa-times"></i></a>
</div>
<div class="api4-input" ng-mouseenter="help(name, param)" ng-mouseleave="help()" ng-repeat="(name, param) in availableParams" ng-if="!isSpecial(name) && (param.type[0] === 'array' || param.type[0] === 'mixed')">
<div class="api4-input" ng-mouseenter="help(name, param)" ng-mouseleave="help()" ng-repeat="(name, param) in availableParams" ng-if="::!isSpecial(name) && (param.type[0] === 'array' || param.type[0] === 'mixed')">
<label for="api4-param-{{:: name }}">{{:: name }}<span class="crm-marker" ng-if="::param.required"> *</span></label>
<textarea class="form-control" type="{{:: param.type[0] === 'int' && param.type.length === 1 ? 'number' : 'text' }}" id="api4-param-{{:: name }}" ng-model="params[name]">
</textarea>
</div>
<fieldset ng-if="::availableParams.where" class="api4-clause-fieldset" ng-mouseenter="help('where', availableParams.where)" ng-mouseleave="help()" crm-api4-clause="{type: 'where', clauses: params.where, required: availableParams.where.required, op: 'AND', label: 'where', fields: fieldsAndJoins}">
</fieldset>
<fieldset ng-repeat="name in ['values', 'defaults']" ng-if="::availableParams[name]" ng-mouseenter="help(name, availableParams[name])" ng-mouseleave="help()">
<legend>{{ name }}<span class="crm-marker" ng-if="::availableParams[name].required"> *</span></legend>
<legend>{{:: name }}<span class="crm-marker" ng-if="::availableParams[name].required"> *</span></legend>
<div class="api4-input form-inline" ng-repeat="clause in params[name]" ng-mouseenter="help('value: ' + clause[0], fieldHelp(clause[0]))" ng-mouseleave="help(name, availableParams[name])">
<input class="collapsible-optgroups form-control" ng-model="clause[0]" crm-ui-select="{formatResult: formatSelect2Item, formatSelection: formatSelect2Item, data: fieldList(name), allowClear: true, placeholder: 'Field'}" />
<input class="form-control" ng-model="clause[1]" api4-exp-value="{field: clause[0], action: action === 'getFields' ? params.action || 'get' : action}" />
Expand Down Expand Up @@ -171,16 +171,16 @@ <h4 ng-bind-html="helpContent.description"></h4>
<div class="api4-explorer-row">
<div class="panel panel-warning explorer-code-panel">
<ul class="panel-heading nav nav-tabs">
<li role="presentation" ng-repeat="tab in code" ng-class="{active: selectedTab.code === tab.lang}">
<a href ng-click="selectedTab.code = tab.lang">
{{ tab.lang }}
<li role="presentation" ng-repeat="lang in ::langs" ng-class="{active: selectedTab.code === lang}">
<a href ng-click="selectLang(lang)">
{{:: lang }}
</a>
</li>
</ul>
<div class="panel-body">
<table ng-repeat="tab in code" ng-show="selectedTab.code === tab.lang">
<tr ng-repeat="style in tab.style">
<td>{{ style.label }}</td>
<table>
<tr ng-repeat="style in code[selectedTab.code]">
<td>{{:: style.label }}</td>
<td><pre class="prettyprint" ng-bind-html="style.code"></pre></td>
</tr>
</table>
Expand All @@ -190,10 +190,12 @@ <h4 ng-bind-html="helpContent.description"></h4>
<ul class="panel-heading nav nav-tabs">
<li role="presentation" ng-class="{active: selectedTab.result === 'result'}">
<a href ng-click="selectedTab.result = 'result'">
<i class="fa fa-fw fa-circle-o" ng-if="status === 'default'"></i>
<i class="fa fa-fw fa-check-circle" ng-if="status === 'success'"></i>
<i class="fa fa-fw fa-minus-circle" ng-if="status === 'danger'"></i>
<i class="fa fa-fw fa-spinner fa-pulse" ng-if="status === 'warning'"></i>
<span ng-switch="status">
<i class="fa fa-fw fa-circle-o" ng-switch-when="default"></i>
<i class="fa fa-fw fa-check-circle" ng-switch-when="success"></i>
<i class="fa fa-fw fa-minus-circle" ng-switch-when="danger"></i>
<i class="fa fa-fw fa-spinner fa-pulse" ng-switch-when="warning"></i>
</span>
<span>{{:: ts('Result') }}</span>
</a>
</li>
Expand All @@ -210,12 +212,14 @@ <h4 ng-bind-html="helpContent.description"></h4>
</div>
<div ng-if="::perm.accessDebugOutput" ng-show="selectedTab.result === 'debug'">
<pre ng-if="debug" class="prettyprint" ng-bind-html="debug"></pre>
<p ng-if="!debug">
{{:: ts('To view debugging output, enable the debug param before executing.') }}
</p>
<p ng-if="!debug">
{{:: ts('Enable backtrace in system settings to see error backtraces.') }}
</p>
<div ng-if="!debug">
<p>
{{:: ts('To view debugging output, enable the debug param before executing.') }}
</p>
<p>
{{:: ts('Enable backtrace in system settings to see error backtraces.') }}
</p>
</div>
</div>
</div>
</div>
Expand Down
158 changes: 80 additions & 78 deletions ang/api4Explorer/Explorer.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,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']);
Expand Down Expand Up @@ -257,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';
}
Expand Down Expand Up @@ -499,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]) : '';
});
});
Expand Down

0 comments on commit e74c275

Please sign in to comment.