-
Notifications
You must be signed in to change notification settings - Fork 819
FIX 11577 performance issues with large list of groups #11578
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 5
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ | |
| use SilverStripe\Forms\ListboxField; | ||
| use SilverStripe\Forms\LiteralField; | ||
| use SilverStripe\Forms\RequiredFields; | ||
| use SilverStripe\Forms\SearchableDropdownField; | ||
| use SilverStripe\Forms\Tab; | ||
| use SilverStripe\Forms\TabSet; | ||
| use SilverStripe\Forms\TextareaField; | ||
|
|
@@ -33,6 +34,7 @@ | |
| use SilverStripe\ORM\HasManyList; | ||
| use SilverStripe\ORM\Hierarchy\Hierarchy; | ||
| use SilverStripe\ORM\ManyManyList; | ||
| use SilverStripe\ORM\Search\SearchContext; | ||
| use SilverStripe\ORM\UnsavedRelationList; | ||
|
|
||
| /** | ||
|
|
@@ -91,6 +93,37 @@ class Group extends DataObject | |
| 'Sort' => true, | ||
| ]; | ||
|
|
||
| /** | ||
| * Specify the limit from when groups should be lazyloaded for the SearchableDropdownField | ||
| * | ||
| * @var int | ||
| * @config | ||
| */ | ||
| private static $dropdown_field_threshold = 100; | ||
|
|
||
| /** | ||
| * Create a search context which is used for the SearchableDropdownField | ||
| * in {@see Group::getCMSFields()} and {@see Member::getCMSFields()} | ||
| * | ||
| * @return SearchContext | ||
| */ | ||
| public static function get_search_context_for_dropdown(): SearchContext | ||
| { | ||
| $group = Group::singleton(); | ||
|
|
||
| return SearchContext::create( | ||
| Group::class, | ||
| FieldList::create( | ||
| TextField::create('Title', $group->fieldLabel('Title')), | ||
| TextField::create('Description', $group->fieldLabel('Description')), | ||
| ), | ||
| [ | ||
| 'Title' => 'PartialFilter', | ||
| 'Description' => 'PartialFilter', | ||
| ] | ||
| ); | ||
| } | ||
|
|
||
| public function getAllChildren() | ||
| { | ||
| $doSet = new ArrayList(); | ||
|
|
@@ -122,18 +155,34 @@ private function getDecodedBreadcrumbs() | |
| */ | ||
| public function getCMSFields() | ||
| { | ||
| $groups = Group::get(); | ||
| $threshold = Group::config()->get('dropdown_field_threshold'); | ||
| $overThreshold = $groups->count() > $threshold; | ||
|
|
||
| $fields = new FieldList( | ||
| new TabSet( | ||
| "Root", | ||
| new Tab( | ||
| 'Members', | ||
| _t(__CLASS__ . '.MEMBERS', 'Members'), | ||
| new TextField("Title", $this->fieldLabel('Title')), | ||
| $parentidfield = DropdownField::create( | ||
| $parentidfield = SearchableDropdownField::create( | ||
| 'ParentID', | ||
| $this->fieldLabel('Parent'), | ||
| $this->getDecodedBreadcrumbs() | ||
| )->setEmptyString(' '), | ||
| $groups, | ||
| null, | ||
| 'BreadcrumbTitle' | ||
| ) | ||
| ->setIsSearchable(true) | ||
| ->setUseSearchContext(true) | ||
| ->setSearchContext(Group::get_search_context_for_dropdown()) | ||
| ->setPlaceholder(_t( | ||
| __CLASS__ . '.PARENT_GROUP_PLACEHOLDER', | ||
| 'Select parent group', | ||
| 'Placeholder text for a dropdown' | ||
| )) | ||
| ->setIsLazyLoaded($overThreshold) | ||
| ->setLazyLoadLimit($threshold), | ||
| new TextareaField('Description', $this->fieldLabel('Description')) | ||
| ), | ||
| $permissionsTab = new Tab( | ||
|
|
@@ -460,6 +509,16 @@ public function stageChildren() | |
| ->sort('"Sort"'); | ||
| } | ||
|
|
||
| /** | ||
| * Label which can be used inside a SearchableDropdownField for example | ||
| * | ||
| * @return string | ||
| */ | ||
| public function getBreadcrumbTitle(): string | ||
| { | ||
| return $this->getBreadcrumbs(' > '); | ||
| } | ||
|
|
||
| /** | ||
| * @return string | ||
| */ | ||
|
|
@@ -503,12 +562,12 @@ public function validate() | |
| } | ||
| } | ||
|
|
||
| $currentGroups = Group::get() | ||
| ->filter('ID:not', $this->ID) | ||
| ->map('Code', 'Title') | ||
| ->toArray(); | ||
| $hasGroupWithSameTitle = Group::get() | ||
| ->exclude('ID', $this->ID) | ||
| ->filter('Title', $this->Title) | ||
| ->exists(); | ||
|
|
||
| if (in_array($this->Title, $currentGroups)) { | ||
| if ($hasGroupWithSameTitle) { | ||
| $result->addError( | ||
| _t( | ||
| 'SilverStripe\\Security\\Group.ValidationIdentifierAlreadyExists', | ||
|
|
@@ -711,16 +770,21 @@ public function requireDefaultRecords() | |
| */ | ||
| private function dedupeCode(): void | ||
| { | ||
| $currentGroups = Group::get() | ||
| ->exclude('ID', $this->ID) | ||
| ->map('Code', 'Title') | ||
| ->toArray(); | ||
| $code = $this->Code; | ||
| $count = 2; | ||
| while (isset($currentGroups[$code])) { | ||
| $code = $this->Code . '-' . $count; | ||
| $count++; | ||
|
|
||
| if ($code) { | ||
| while ($this->checkIfCodeExists($code)) { | ||
| $code = $this->Code . '-' . $count; | ||
| $count++; | ||
| } | ||
|
|
||
| $this->setField('Code', $code); | ||
| } | ||
| $this->setField('Code', $code); | ||
| } | ||
|
|
||
| private function checkIfCodeExists(string $code): bool | ||
| { | ||
| return Group::get()->filter('Code', $code)->exclude('ID', $this->ID)->exists(); | ||
| } | ||
|
Comment on lines
771
to
789
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How do changes to the deduping code relate to usage of a different form field?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is more or less the same issue as with the large list in the FormField. Without you are not able to save a group if you have many of them.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can check the output of my generater script above with and without the change. Without the script will get slower with each iteration. |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this new method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the Label for the FormField.