From 3cd8c7ea770c29399ce2fb30d9a2ff27e23ce5bd Mon Sep 17 00:00:00 2001 From: Dylan Wagstaff Date: Mon, 13 Jun 2022 19:36:35 +1200 Subject: [PATCH] FIX submission performance issues with large data The more submissions a form receives, the more submission fields it must process just to be able to load `getCMSFields`. Arguably submission data does not belong here, but this is beyond the scope of this patch. On popular forms it is not improbable to be trying to process 300,000 submitted fields just to test the unique sets of name and title... however databases have the ability to do this without wasting PHP cycles and memory, leaving us with a much smaller set to process and hopefully bypassing one (of several) performance issues with this module. The consequence of not making allowance for this is that a page in the CMS suddenly stops saving or loading via web server or PHP (or both) process timeouts (e.g. saving takes longer than 30 seconds so saving never happens). --- code/Model/EditableFormField.php | 4 + code/Model/Submission/SubmittedFormField.php | 4 + code/UserForm.php | 166 ++++++++++--------- 3 files changed, 92 insertions(+), 82 deletions(-) diff --git a/code/Model/EditableFormField.php b/code/Model/EditableFormField.php index d01de75..ae61ab6 100755 --- a/code/Model/EditableFormField.php +++ b/code/Model/EditableFormField.php @@ -145,6 +145,10 @@ class EditableFormField extends DataObject 'ShowOnLoad' => true, ]; + private static $indexes = [ + 'Name' => 'Name', + ]; + /** * @config diff --git a/code/Model/Submission/SubmittedFormField.php b/code/Model/Submission/SubmittedFormField.php index 828d93b..cf7b703 100755 --- a/code/Model/Submission/SubmittedFormField.php +++ b/code/Model/Submission/SubmittedFormField.php @@ -34,6 +34,10 @@ class SubmittedFormField extends DataObject private static $table_name = 'SubmittedFormField'; + private static $indexes = [ + 'Name' => 'Name', + ]; + /** * @param Member $member * @param array $context diff --git a/code/UserForm.php b/code/UserForm.php index 2909959..38311fb 100644 --- a/code/UserForm.php +++ b/code/UserForm.php @@ -193,7 +193,6 @@ trait UserForm // define tabs $fields->findOrMakeTab('Root.FormOptions', _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.CONFIGURATION', 'Configuration')); $fields->findOrMakeTab('Root.Recipients', _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.RECIPIENTS', 'Recipients')); - $fields->findOrMakeTab('Root.Submissions', _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.SUBMISSIONS', 'Submissions')); // text to show on complete @@ -237,87 +236,8 @@ trait UserForm $fields->addFieldToTab('Root.Recipients', $emailRecipients); $fields->addFieldsToTab('Root.FormOptions', $this->getFormOptions()); - - // view the submissions - // make sure a numeric not a empty string is checked against this int column for SQL server - $parentID = (!empty($this->ID)) ? (int) $this->ID : 0; - - // get a list of all field names and values used for print and export CSV views of the GridField below. - $columnSQL = <<map() as $name => $title) { - $columns[$name] = trim(strtr($title ?? '', '.', ' ')); - } - - $config = GridFieldConfig::create(); - $config->addComponent(new GridFieldToolbarHeader()); - $config->addComponent($sort = new GridFieldSortableHeader()); - $config->addComponent($filter = new UserFormsGridFieldFilterHeader()); - $config->addComponent(new GridFieldDataColumns()); - $config->addComponent(new GridFieldEditButton()); - $config->addComponent(new GridFieldDeleteAction()); - $config->addComponent(new GridFieldPageCount('toolbar-header-right')); - $config->addComponent($pagination = new GridFieldPaginator(25)); - $config->addComponent(new GridFieldDetailForm(null, true, false)); - $config->addComponent(new GridFieldButtonRow('after')); - $config->addComponent($export = new GridFieldExportButton('buttons-after-left')); - $config->addComponent($print = new GridFieldPrintButton('buttons-after-left')); - - // show user form items in the summary tab - $summaryarray = array( - 'ID' => 'ID', - 'Created' => 'Created', - 'LastEdited' => 'Last Edited' - ); - - foreach (EditableFormField::get()->filter(array('ParentID' => $parentID)) as $eff) { - if ($eff->ShowInSummary) { - $summaryarray[$eff->Name] = $eff->Title ?: $eff->Name; - } - } - - $config->getComponentByType(GridFieldDataColumns::class)->setDisplayFields($summaryarray); - - /** - * Support for {@link https://github.com/colymba/GridFieldBulkEditingTools} - */ - if (class_exists(BulkManager::class)) { - $config->addComponent(new BulkManager); - } - - $sort->setThrowExceptionOnBadDataType(false); - $filter->setThrowExceptionOnBadDataType(false); - $pagination->setThrowExceptionOnBadDataType(false); - - // attach every column to the print view form - $columns['Created'] = 'Created'; - $columns['SubmittedBy.Email'] = 'Submitter'; - $filter->setColumns($columns); - - // print configuration - - $print->setPrintHasHeader(true); - $print->setPrintColumns($columns); - - // export configuration - $export->setCsvHasHeader(true); - $export->setExportColumns($columns); - - $submissions = GridField::create( - 'Submissions', - '', - $this->Submissions()->sort('Created', 'DESC'), - $config - ); + $submissions = $this->getSubmissionsGridField(); + $fields->findOrMakeTab('Root.Submissions', _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.SUBMISSIONS', 'Submissions')); $fields->addFieldToTab('Root.Submissions', $submissions); $fields->addFieldToTab( 'Root.FormOptions', @@ -344,6 +264,88 @@ SQL; return $fields; } + public function getSubmissionsGridField() + { + // view the submissions + // make sure a numeric not a empty string is checked against this int column for SQL server + $parentID = (!empty($this->ID)) ? (int) $this->ID : 0; + + // get a list of all field names and values used for print and export CSV views of the GridField below. + $columnSQL = <<map(); + + $config = GridFieldConfig::create(); + $config->addComponent(new GridFieldToolbarHeader()); + $config->addComponent($sort = new GridFieldSortableHeader()); + $config->addComponent($filter = new UserFormsGridFieldFilterHeader()); + $config->addComponent(new GridFieldDataColumns()); + $config->addComponent(new GridFieldEditButton()); + $config->addComponent(new GridFieldDeleteAction()); + $config->addComponent(new GridFieldPageCount('toolbar-header-right')); + $config->addComponent($pagination = new GridFieldPaginator(25)); + $config->addComponent(new GridFieldDetailForm(null, true, false)); + $config->addComponent(new GridFieldButtonRow('after')); + $config->addComponent($export = new GridFieldExportButton('buttons-after-left')); + $config->addComponent($print = new GridFieldPrintButton('buttons-after-left')); + + // show user form items in the summary tab + $summaryarray = array( + 'ID' => 'ID', + 'Created' => 'Created', + 'LastEdited' => 'Last Edited' + ); + + foreach (EditableFormField::get()->filter(['ParentID' => $parentID, 'ShowInSummary' => 1]) as $eff) { + $summaryarray[$eff->Name] = $eff->Title ?: $eff->Name; + } + + $config->getComponentByType(GridFieldDataColumns::class)->setDisplayFields($summaryarray); + + /** + * Support for {@link https://github.com/colymba/GridFieldBulkEditingTools} + */ + if (class_exists(BulkManager::class)) { + $config->addComponent(new BulkManager); + } + + $sort->setThrowExceptionOnBadDataType(false); + $filter->setThrowExceptionOnBadDataType(false); + $pagination->setThrowExceptionOnBadDataType(false); + + // attach every column to the print view form + $columns['Created'] = 'Created'; + $columns['SubmittedBy.Email'] = 'Submitter'; + $filter->setColumns($columns); + + // print configuration + $print->setPrintHasHeader(true); + $print->setPrintColumns($columns); + + // export configuration + $export->setCsvHasHeader(true); + $export->setExportColumns($columns); + + $submissions = GridField::create( + 'Submissions', + '', + $this->Submissions()->sort('Created', 'DESC'), + $config + ); + return $submissions; + } + /** * Allow overriding the EmailRecipients on a {@link DataExtension} * so you can customise who receives an email.