From 116710735ac7445caf2536dc58e8848a71a8e469 Mon Sep 17 00:00:00 2001 From: Jaap Jansma <jaap.jansma@civicoop.org> Date: Tue, 11 Feb 2020 17:16:46 +0100 Subject: [PATCH] Added exposure of aggregation on search/report --- CHANGELOG.md | 1 + .../Form/Output/AbstractUIOutputForm.php | 12 +- .../Form/AbstractSearch.php | 2 + CRM/DataprocessorSearch/Form/Search.php | 84 ++++++++++++ CRM/DataprocessorSearch/Search.php | 5 + .../DataFlow/AbstractDataFlow.php | 12 ++ .../DateFieldOutputHandler.php | 32 +++++ .../OptionFieldOutputHandler.php | 128 +++++++++++++++++- .../OutputHandlerAggregate.php | 15 ++ .../RawFieldOutputHandler.php | 32 +++++ .../OptionFieldOutputHandler.tpl | 8 ++ .../Form/Output/UIOutput/CriteriaForm.tpl | 4 + .../Form/Criteria/AggregateCriteria.tpl | 7 + .../Form/OutputConfiguration/Search.tpl | 5 + 14 files changed, 345 insertions(+), 2 deletions(-) create mode 100644 templates/CRM/Dataprocessor/Form/Field/Configuration/OptionFieldOutputHandler.tpl create mode 100644 templates/CRM/DataprocessorSearch/Form/Criteria/AggregateCriteria.tpl diff --git a/CHANGELOG.md b/CHANGELOG.md index fec9a57b..6be0d376 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Change Group Filter so that it also works with smart groups * Fixed bug with date filter * Added date group by function to date output field handler. +* Added exposure of Aggregation on the Search/Report output. # Version 1.1.0 diff --git a/CRM/Dataprocessor/Form/Output/AbstractUIOutputForm.php b/CRM/Dataprocessor/Form/Output/AbstractUIOutputForm.php index 768af4d7..bea25b7f 100644 --- a/CRM/Dataprocessor/Form/Output/AbstractUIOutputForm.php +++ b/CRM/Dataprocessor/Form/Output/AbstractUIOutputForm.php @@ -66,7 +66,7 @@ abstract class CRM_Dataprocessor_Form_Output_AbstractUIOutputForm extends CRM_Co $dataProcessorName = $this->getDataProcessorName(); $sql = " SELECT civicrm_data_processor.id as data_processor_id, civicrm_data_processor_output.id AS output_id - FROM civicrm_data_processor + FROM civicrm_data_processor INNER JOIN civicrm_data_processor_output ON civicrm_data_processor.id = civicrm_data_processor_output.data_processor_id WHERE is_active = 1 AND civicrm_data_processor.name = %1 AND civicrm_data_processor_output.type = %2 "; @@ -181,6 +181,7 @@ abstract class CRM_Dataprocessor_Form_Output_AbstractUIOutputForm extends CRM_Co $filterElements[$fieldSpec->alias]['template'] = $filterHandler->getTemplateFileName(); } $this->assign('filters', $filterElements); + $this->assign('additional_criteria_template', $this->getAdditionalCriteriaTemplate()); } } @@ -193,4 +194,13 @@ abstract class CRM_Dataprocessor_Form_Output_AbstractUIOutputForm extends CRM_Co protected function getCriteriaElementSize() { return 'full'; } + + /** + * Returns the name of the additional criteria template. + * + * @return false|String + */ + protected function getAdditionalCriteriaTemplate() { + return false; + } } diff --git a/CRM/DataprocessorSearch/Form/AbstractSearch.php b/CRM/DataprocessorSearch/Form/AbstractSearch.php index ea7509c6..29dd4bbf 100644 --- a/CRM/DataprocessorSearch/Form/AbstractSearch.php +++ b/CRM/DataprocessorSearch/Form/AbstractSearch.php @@ -228,6 +228,8 @@ abstract class CRM_DataprocessorSearch_Form_AbstractSearch extends CRM_Dataproce $sortFieldName = $sortField['name']; } + $this->alterDataProcessor($this->dataProcessorClass); + $output = civicrm_api3("DataProcessorOutput", "getsingle", array('id' => $export_id)); $outputClass = $factory->getOutputByName($output['type']); if ($outputClass instanceof \Civi\DataProcessor\Output\ExportOutputInterface) { diff --git a/CRM/DataprocessorSearch/Form/Search.php b/CRM/DataprocessorSearch/Form/Search.php index f398ba30..1234d22a 100644 --- a/CRM/DataprocessorSearch/Form/Search.php +++ b/CRM/DataprocessorSearch/Form/Search.php @@ -90,4 +90,88 @@ class CRM_DataprocessorSearch_Form_Search extends CRM_DataprocessorSearch_Form_A return $this->_taskList; } + /** + * Build the criteria form + */ + protected function buildCriteriaForm() { + parent::buildCriteriaForm(); + $this->buildAggregateForm(); + } + + /** + * Returns the name of the additional criteria template. + * + * @return false|String + */ + protected function getAdditionalCriteriaTemplate() { + if (isset($this->dataProcessorOutput['configuration']['expose_aggregate']) && $this->dataProcessorOutput['configuration']['expose_aggregate']) { + return "CRM/DataprocessorSearch/Form/Criteria/AggregateCriteria.tpl"; + } + return false; + } + + + /** + * Build the aggregate form + */ + protected function buildAggregateForm() { + if (!isset($this->dataProcessorOutput['configuration']['expose_aggregate']) || !$this->dataProcessorOutput['configuration']['expose_aggregate']) { + return; + } + $size = $this->getCriteriaElementSize(); + + $sizeClass = 'huge'; + $minWidth = 'min-width: 250px;'; + if ($size =='compact') { + $sizeClass = 'medium'; + $minWidth = ''; + } + + $aggregateFields = array(); + $defaults = array(); + foreach ($this->dataProcessorClass->getDataFlow()->getOutputFieldHandlers() as $outputFieldHandler) { + if ($outputFieldHandler instanceof \Civi\DataProcessor\FieldOutputHandler\OutputHandlerAggregate) { + $aggregateFields[$outputFieldHandler->getAggregateFieldSpec()->alias] = $outputFieldHandler->getOutputFieldSpecification()->title; + if ($outputFieldHandler->isAggregateField()) { + $defaults[] = $outputFieldHandler->getAggregateFieldSpec()->alias; + } + } + } + + $this->add('select', "aggregateFields", '', $aggregateFields, false, [ + 'style' => $minWidth, + 'class' => 'crm-select2 '.$sizeClass, + 'multiple' => TRUE, + 'placeholder' => E::ts('- Select -'), + ]); + + $this->setDefaults(['aggregateFields' => $defaults]); + } + + /** + * Alter the data processor. + * + * Use this function in child classes to add for example additional filters. + * + * E.g. The contact summary tab uses this to add additional filtering on the contact id of + * the displayed contact. + * + * @param \Civi\DataProcessor\ProcessorType\AbstractProcessorType $dataProcessorClass + */ + protected function alterDataProcessor(\Civi\DataProcessor\ProcessorType\AbstractProcessorType $dataProcessorClass) { + if (isset($this->dataProcessorOutput['configuration']['expose_aggregate']) && $this->dataProcessorOutput['configuration']['expose_aggregate']) { + $aggregateFields = $this->_formValues['aggregateFields']; + foreach ($this->dataProcessorClass->getDataFlow()->getOutputFieldHandlers() as $outputFieldHandler) { + if ($outputFieldHandler instanceof \Civi\DataProcessor\FieldOutputHandler\OutputHandlerAggregate) { + $alias = $outputFieldHandler->getAggregateFieldSpec()->alias; + if (in_array($alias, $aggregateFields) && !$outputFieldHandler->isAggregateField()) { + $outputFieldHandler->enableAggregation(); + } elseif (!in_array($alias, $aggregateFields) && $outputFieldHandler->isAggregateField()) { + $outputFieldHandler->disableAggregation(); + } + } + } + } + } + } diff --git a/CRM/DataprocessorSearch/Search.php b/CRM/DataprocessorSearch/Search.php index 625fca7d..d46c1b35 100644 --- a/CRM/DataprocessorSearch/Search.php +++ b/CRM/DataprocessorSearch/Search.php @@ -58,6 +58,7 @@ class CRM_DataprocessorSearch_Search implements UIOutputInterface { $form->add('wysiwyg', 'help_text', E::ts('Help text for this search'), array('rows' => 6, 'cols' => 80)); $form->add('checkbox', 'expanded_search', E::ts('Expand criteria form initially')); + $form->add('checkbox', 'expose_aggregate', E::ts('Expose aggregate options')); // navigation field $navigationOptions = $navigation->getNavigationOptions(); @@ -91,6 +92,9 @@ class CRM_DataprocessorSearch_Search implements UIOutputInterface { if (isset($output['configuration']['expanded_search'])) { $defaults['expanded_search'] = $output['configuration']['expanded_search']; } + if (isset($output['configuration']['expose_aggregate'])) { + $defaults['expose_aggregate'] = $output['configuration']['expose_aggregate']; + } } } if (!isset($defaults['permission'])) { @@ -124,6 +128,7 @@ class CRM_DataprocessorSearch_Search implements UIOutputInterface { $configuration['hidden_fields'] = $submittedValues['hidden_fields']; $configuration['help_text'] = $submittedValues['help_text']; $configuration['expanded_search'] = isset($submittedValues['expanded_search']) ? $submittedValues['expanded_search'] : false; + $configuration['expose_aggregate'] = isset($submittedValues['expose_aggregate']) ? $submittedValues['expose_aggregate'] : false; return $configuration; } diff --git a/Civi/DataProcessor/DataFlow/AbstractDataFlow.php b/Civi/DataProcessor/DataFlow/AbstractDataFlow.php index 9cf44820..59a30247 100644 --- a/Civi/DataProcessor/DataFlow/AbstractDataFlow.php +++ b/Civi/DataProcessor/DataFlow/AbstractDataFlow.php @@ -251,6 +251,18 @@ abstract class AbstractDataFlow { $this->aggregateOutputHandlers[] = $aggregateOutputHandler; } + /** + * @param \Civi\DataProcessor\DataFlow\OutputHandlerAggregate $aggregateOutputHandler + */ + public function removeAggregateOutputHandler(OutputHandlerAggregate $aggregateOutputHandler) { + foreach($this->aggregateOutputHandlers as $key => $item) { + if ($item->getAggregateFieldSpec()->alias == $aggregateOutputHandler->getAggregateFieldSpec()->alias) { + unset($this->aggregateOutputHandlers[$key]); + break; + } + } + } + /** * Adds a field for sorting * diff --git a/Civi/DataProcessor/FieldOutputHandler/DateFieldOutputHandler.php b/Civi/DataProcessor/FieldOutputHandler/DateFieldOutputHandler.php index 5d2c3584..77e170a5 100644 --- a/Civi/DataProcessor/FieldOutputHandler/DateFieldOutputHandler.php +++ b/Civi/DataProcessor/FieldOutputHandler/DateFieldOutputHandler.php @@ -183,6 +183,38 @@ class DateFieldOutputHandler extends AbstractSimpleFieldOutputHandler implements return $value; } + /** + * Enable aggregation for this field. + * + * @return void + */ + public function enableAggregation() { + try { + $dataFlow = $this->dataSource->ensureField($this->getAggregateFieldSpec()); + if ($dataFlow) { + $dataFlow->addAggregateOutputHandler($this); + } + } catch (\Exception $e) { + // Do nothing. + } + } + + /** + * Disable aggregation for this field. + * + * @return void + */ + public function disableAggregation() { + try { + $dataFlow = $this->dataSource->ensureField($this->getAggregateFieldSpec()); + if ($dataFlow) { + $dataFlow->removeAggregateOutputHandler($this); + } + } catch (\Exception $e) { + // Do nothing. + } + } + protected function getFunctions() { return array( 'date' => array( diff --git a/Civi/DataProcessor/FieldOutputHandler/OptionFieldOutputHandler.php b/Civi/DataProcessor/FieldOutputHandler/OptionFieldOutputHandler.php index 6c748c55..7f6ca5b7 100644 --- a/Civi/DataProcessor/FieldOutputHandler/OptionFieldOutputHandler.php +++ b/Civi/DataProcessor/FieldOutputHandler/OptionFieldOutputHandler.php @@ -11,7 +11,32 @@ use Civi\DataProcessor\Source\SourceInterface; use Civi\DataProcessor\DataSpecification\FieldSpecification; use Civi\DataProcessor\FieldOutputHandler\FieldOutput; -class OptionFieldOutputHandler extends AbstractSimpleFieldOutputHandler { +class OptionFieldOutputHandler extends AbstractSimpleFieldOutputHandler implements OutputHandlerAggregate { + + /** + * @var bool + */ + protected $isAggregateField = false; + + /** + * Initialize the processor + * + * @param String $alias + * @param String $title + * @param array $configuration + * @param \Civi\DataProcessor\ProcessorType\AbstractProcessorType $processorType + */ + public function initialize($alias, $title, $configuration) { + parent::initialize($alias, $title, $configuration); + $this->isAggregateField = isset($configuration['is_aggregate']) ? $configuration['is_aggregate'] : false; + + if ($this->isAggregateField) { + $dataFlow = $this->dataSource->ensureField($this->getAggregateFieldSpec()); + if ($dataFlow) { + $dataFlow->addAggregateOutputHandler($this); + } + } + } /** @@ -63,5 +88,106 @@ class OptionFieldOutputHandler extends AbstractSimpleFieldOutputHandler { return false; } + /** + * @return \Civi\DataProcessor\DataSpecification\FieldSpecification + */ + public function getAggregateFieldSpec() { + return $this->inputFieldSpec; + } + + /** + * @return bool + */ + public function isAggregateField() { + return $this->isAggregateField; + } + + /** + * Enable aggregation for this field. + * + * @return void + */ + public function enableAggregation() { + try { + $dataFlow = $this->dataSource->ensureField($this->getAggregateFieldSpec()); + if ($dataFlow) { + $dataFlow->addAggregateOutputHandler($this); + } + } catch (\Exception $e) { + // Do nothing. + } + } + + /** + * Disable aggregation for this field. + * + * @return void + */ + public function disableAggregation() { + try { + $dataFlow = $this->dataSource->ensureField($this->getAggregateFieldSpec()); + if ($dataFlow) { + $dataFlow->removeAggregateOutputHandler($this); + } + } catch (\Exception $e) { + // Do nothing. + } + } + + /** + * Returns the value. And if needed a formatting could be applied. + * E.g. when the value is a date field and you want to aggregate on the month + * you can then return the month here. + * + * @param $value + * + * @return mixed + */ + public function formatAggregationValue($value) { + return $value; + } + + /** + * When this handler has additional configuration you can add + * the fields on the form with this function. + * + * @param \CRM_Core_Form $form + * @param array $field + */ + public function buildConfigurationForm(\CRM_Core_Form $form, $field=array()) { + parent::buildConfigurationForm($form, $field); + $form->add('checkbox', 'is_aggregate', E::ts('Aggregate on this field')); + if (isset($field['configuration'])) { + $configuration = $field['configuration']; + $defaults = array(); + if (isset($configuration['is_aggregate'])) { + $defaults['is_aggregate'] = $configuration['is_aggregate']; + } + $form->setDefaults($defaults); + } + } + + /** + * Process the submitted values and create a configuration array + * + * @param $submittedValues + * @return array + */ + public function processConfiguration($submittedValues) { + $configuration = parent::processConfiguration($submittedValues); + $configuration['is_aggregate'] = isset($submittedValues['is_aggregate']) ? $submittedValues['is_aggregate'] : false; + return $configuration; + } + + /** + * When this handler has configuration specify the template file name + * for the configuration form. + * + * @return false|string + */ + public function getConfigurationTemplateFileName() { + return "CRM/Dataprocessor/Form/Field/Configuration/OptionFieldOutputHandler.tpl"; + } + } diff --git a/Civi/DataProcessor/FieldOutputHandler/OutputHandlerAggregate.php b/Civi/DataProcessor/FieldOutputHandler/OutputHandlerAggregate.php index 7abc605c..c419d446 100644 --- a/Civi/DataProcessor/FieldOutputHandler/OutputHandlerAggregate.php +++ b/Civi/DataProcessor/FieldOutputHandler/OutputHandlerAggregate.php @@ -18,6 +18,21 @@ interface OutputHandlerAggregate { */ public function isAggregateField(); + /** + * Enable aggregation for this field. + * + * @return void + */ + public function enableAggregation(); + + /** + * Disable aggregation for this field. + * + * @return void + */ + public function disableAggregation(); + + /** * Returns the value. And if needed a formatting could be applied. * E.g. when the value is a date field and you want to aggregate on the month diff --git a/Civi/DataProcessor/FieldOutputHandler/RawFieldOutputHandler.php b/Civi/DataProcessor/FieldOutputHandler/RawFieldOutputHandler.php index b3f6e668..076eed82 100644 --- a/Civi/DataProcessor/FieldOutputHandler/RawFieldOutputHandler.php +++ b/Civi/DataProcessor/FieldOutputHandler/RawFieldOutputHandler.php @@ -39,6 +39,38 @@ class RawFieldOutputHandler extends AbstractSimpleFieldOutputHandler implements } } + /** + * Enable aggregation for this field. + * + * @return void + */ + public function enableAggregation() { + try { + $dataFlow = $this->dataSource->ensureField($this->getAggregateFieldSpec()); + if ($dataFlow) { + $dataFlow->addAggregateOutputHandler($this); + } + } catch (\Exception $e) { + // Do nothing. + } + } + + /** + * Disable aggregation for this field. + * + * @return void + */ + public function disableAggregation() { + try { + $dataFlow = $this->dataSource->ensureField($this->getAggregateFieldSpec()); + if ($dataFlow) { + $dataFlow->removeAggregateOutputHandler($this); + } + } catch (\Exception $e) { + // Do nothing. + } + } + /** * When this handler has additional configuration you can add * the fields on the form with this function. diff --git a/templates/CRM/Dataprocessor/Form/Field/Configuration/OptionFieldOutputHandler.tpl b/templates/CRM/Dataprocessor/Form/Field/Configuration/OptionFieldOutputHandler.tpl new file mode 100644 index 00000000..fa7ddec4 --- /dev/null +++ b/templates/CRM/Dataprocessor/Form/Field/Configuration/OptionFieldOutputHandler.tpl @@ -0,0 +1,8 @@ +{crmScope extensionKey='dataprocessor'} + {include file="CRM/Dataprocessor/Form/Field/Configuration/SimpleFieldOutputHandler.tpl"} + <div class="crm-section"> + <div class="label">{$form.is_aggregate.label}</div> + <div class="content">{$form.is_aggregate.html}</div> + <div class="clear"></div> + </div> +{/crmScope} diff --git a/templates/CRM/Dataprocessor/Form/Output/UIOutput/CriteriaForm.tpl b/templates/CRM/Dataprocessor/Form/Output/UIOutput/CriteriaForm.tpl index a6836a62..54eb7eb7 100644 --- a/templates/CRM/Dataprocessor/Form/Output/UIOutput/CriteriaForm.tpl +++ b/templates/CRM/Dataprocessor/Form/Output/UIOutput/CriteriaForm.tpl @@ -17,7 +17,11 @@ {foreach from=$filters key=filterName item=filter} {include file=$filter.template filterName=$filter.alias filter=$filter.filter} {/foreach} + {if $additional_criteria_template} + {include file=$additional_criteria_template} + {/if} </table> + <div class="crm-submit-buttons">{include file="CRM/common/formButtons.tpl" location="botton"}</div> </div> </div> diff --git a/templates/CRM/DataprocessorSearch/Form/Criteria/AggregateCriteria.tpl b/templates/CRM/DataprocessorSearch/Form/Criteria/AggregateCriteria.tpl new file mode 100644 index 00000000..9b5fbb3e --- /dev/null +++ b/templates/CRM/DataprocessorSearch/Form/Criteria/AggregateCriteria.tpl @@ -0,0 +1,7 @@ +{crmScope extensionKey='dataprocessor'} +<tr> + <td class="label">{ts}Aggregate{/ts}</td> + <td> </td> + <td>{$form.aggregateFields.html}</td> +</tr> +{/crmScope} diff --git a/templates/CRM/DataprocessorSearch/Form/OutputConfiguration/Search.tpl b/templates/CRM/DataprocessorSearch/Form/OutputConfiguration/Search.tpl index 8237e188..4c22ac38 100644 --- a/templates/CRM/DataprocessorSearch/Form/OutputConfiguration/Search.tpl +++ b/templates/CRM/DataprocessorSearch/Form/OutputConfiguration/Search.tpl @@ -29,6 +29,11 @@ <div class="content">{$form.expanded_search.html}</div> <div class="clear"></div> </div> + <div class="crm-section"> + <div class="label">{$form.expose_aggregate.label}</div> + <div class="content">{$form.expose_aggregate.html}</div> + <div class="clear"></div> + </div> <div class="crm-section"> <div class="label">{$form.help_text.label}</div> <div class="content">{$form.help_text.html}</div> -- GitLab