UndefinedOffset / SortableGridField

Adds drag and drop functionality to Silverstripe's GridField
BSD 3-Clause "New" or "Revised" License
93 stars 62 forks source link

Creating new DataObject results in database query with empty table and column names #139

Open leonixyz opened 6 months ago

leonixyz commented 6 months ago

Affected Version

{
        "php": "^8.1",
        "silverstripe/recipe-plugin": "~2.0.0@stable",
        "silverstripe/vendor-plugin": "~2.0.0@stable",
        "silverstripe/recipe-cms": "~5.0.0@stable",
        "silverstripe/login-forms": "~5.0.0@stable",
        "silverstripe/display-logic": "^3.0",
        "undefinedoffset/sortablegridfield": "^2.2"
}

Description

A wrong query is issued to the database, having empty table name and empty column names.

UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3

If, after hitting the error, you examine the database, you'll find the many-to-many table has the new record correctly appended, but the SortOrder column is set at 0. Manually changing it to 1 solves the problem. But anytime you need to create a new one, the problem is back.

https://github.com/UndefinedOffset/SortableGridField/assets/4563795/5e79c425-d62d-4536-a301-8e7ef5fc0c92

Stack trace:

[Emergency] Uncaught SilverStripe\ORM\Connect\DatabaseException: Couldn't run query: UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3 Incorrect table name ''
GET /admin/views/View/EditForm/field/View/item/3/ItemEditForm/field/AttachmentsCategories/item/15

Line 64 in /var/www/html/vendor/silverstripe/framework/src/ORM/Connect/DBConnector.php
Source

55         if (!empty($sql)) {
56             $formatter = new SQLFormatter();
57             $formattedSQL = $formatter->formatPlain($sql);
58             $msg = "Couldn't run query:\n\n{$formattedSQL}\n\n{$msg}";
59         }
60 
61         if ($errorLevel === E_USER_ERROR) {
62             // Treating errors as exceptions better allows for responding to errors
63             // in code, such as credential checking during installation
64             throw new DatabaseException($msg, 0, null, $sql, $parameters);
65         } else {
66             user_error($msg ?? '', $errorLevel ?? 0);
67         }
68     }
69 
70     /**

Trace

    SilverStripe\ORM\Connect\DBConnector->databaseError(Couldn't run query: UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3 Incorrect table name '', 256, UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
    MySQLiConnector.php:194
    SilverStripe\ORM\Connect\MySQLiConnector->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
    Database.php:159
    SilverStripe\ORM\Connect\Database->SilverStripe\ORM\Connect\{closure}(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
    Database.php:258
    SilverStripe\ORM\Connect\Database->benchmarkQuery(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, Closure)
    Database.php:160
    SilverStripe\ORM\Connect\Database->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
    MySQLDatabase.php:381
    SilverStripe\ORM\Connect\MySQLDatabase->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
    DB.php:341
    SilverStripe\ORM\DB::query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
    GridFieldSortableRows.php:329
    UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows->fixSortColumn(SilverStripe\Forms\GridField\GridField, SilverStripe\ORM\ManyManyList)
    GridFieldSortableRows.php:151
    UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows->getManipulatedData(SilverStripe\Forms\GridField\GridField, SilverStripe\ORM\ManyManyList)
    GridField.php:411
    SilverStripe\Forms\GridField\GridField->getManipulatedList()
    GridFieldDetailForm_ItemRequest.php:573
    SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getGridFieldItemAdjacencies()
    GridFieldDetailForm_ItemRequest.php:654
    SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getAdjacentRecordID(-1)
    GridFieldDetailForm_ItemRequest.php:682
    SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getPreviousRecordID()
    GridFieldDetailForm_ItemRequest.php:334
    SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getRightGroupField()
    GridFieldDetailForm_ItemRequest.php:420
    SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getFormActions()
    VersionedGridFieldItemRequest.php:90
    SilverStripe\Versioned\VersionedGridFieldItemRequest->getFormActions()
    GridFieldDetailForm_ItemRequest.php:242
    SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->ItemEditForm()
    GridFieldDetailForm_ItemRequest.php:160
    SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->edit(SilverStripe\Control\HTTPRequest)
    RequestHandler.php:323
    SilverStripe\Control\RequestHandler->handleAction(SilverStripe\Control\HTTPRequest, edit)
    RequestHandler.php:202
    SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
    GridFieldDetailForm.php:149
    SilverStripe\Forms\GridField\GridFieldDetailForm->handleItem(SilverStripe\Forms\GridField\GridField, SilverStripe\Control\HTTPRequest)
    GridField.php:1237
    SilverStripe\Forms\GridField\GridField->handleRequest(SilverStripe\Control\HTTPRequest)
    RequestHandler.php:226
    SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
    RequestHandler.php:226
    SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
    GridFieldDetailForm.php:149
    SilverStripe\Forms\GridField\GridFieldDetailForm->handleItem(SilverStripe\Forms\GridField\GridField, SilverStripe\Control\HTTPRequest)
    GridField.php:1237
    SilverStripe\Forms\GridField\GridField->handleRequest(SilverStripe\Control\HTTPRequest)
    RequestHandler.php:226
    SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
    RequestHandler.php:226
    SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
    Controller.php:202
    SilverStripe\Control\Controller->handleRequest(SilverStripe\Control\HTTPRequest)
    LeftAndMain.php:799
    SilverStripe\Admin\LeftAndMain->handleRequest(SilverStripe\Control\HTTPRequest)
    AdminRootController.php:124
    SilverStripe\Admin\AdminRootController->handleRequest(SilverStripe\Control\HTTPRequest)
    Director.php:349
    SilverStripe\Control\Director->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
    VersionedHTTPMiddleware.php:41
    SilverStripe\Versioned\VersionedHTTPMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
    HTTPMiddlewareAware.php:62
    SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
    LoginSessionMiddleware.php:53
    SilverStripe\SessionManager\Middleware\LoginSessionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
    HTTPMiddlewareAware.php:62
    SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
    ExecMetricMiddleware.php:20
    SilverStripe\Control\Middleware\ExecMetricMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
    HTTPMiddlewareAware.php:62
    SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
    ConfirmationMiddleware.php:254
    SilverStripe\Control\Middleware\ConfirmationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
    HTTPMiddlewareAware.php:62
    SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
    ConfirmationMiddleware.php:254
    SilverStripe\Control\Middleware\ConfirmationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
    HTTPMiddlewareAware.php:62
    SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
    PasswordExpirationMiddleware.php:84
    SilverStripe\Security\PasswordExpirationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
    HTTPMiddlewareAware.php:62
    SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
    BasicAuthMiddleware.php:68
    SilverStripe\Security\BasicAuthMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
    HTTPMiddlewareAware.php:62
    SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
    AuthenticationMiddleware.php:61
    SilverStripe\Security\AuthenticationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
    HTTPMiddlewareAware.php:62
    SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
    CanonicalURLMiddleware.php:245
    SilverStripe\Control\Middleware\CanonicalURLMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
    HTTPMiddlewareAware.php:62
    SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
    HTTPCacheControlMiddleware.php:41
    SilverStripe\Control\Middleware\HTTPCacheControlMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
    HTTPMiddlewareAware.php:62
    SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
    ChangeDetectionMiddleware.php:28
    SilverStripe\Control\Middleware\ChangeDetectionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
    HTTPMiddlewareAware.php:62
    SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
    FlushMiddleware.php:31
    SilverStripe\Control\Middleware\FlushMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
    HTTPMiddlewareAware.php:62
    SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
    SessionMiddleware.php:20
    SilverStripe\Control\Middleware\SessionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
    HTTPMiddlewareAware.php:62
    SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
    AllowedHostsMiddleware.php:60
    SilverStripe\Control\Middleware\AllowedHostsMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
    HTTPMiddlewareAware.php:62
    SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
    TrustedProxyMiddleware.php:176
    SilverStripe\Control\Middleware\TrustedProxyMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
    HTTPMiddlewareAware.php:62
    SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
    HTTPMiddlewareAware.php:65
    SilverStripe\Control\Director->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
    Director.php:358
    SilverStripe\Control\Director->handleRequest(SilverStripe\Control\HTTPRequest)
    HTTPApplication.php:114
    SilverStripe\Control\HTTPApplication::SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
    call_user_func(Closure, SilverStripe\Control\HTTPRequest)
    HTTPApplication.php:137
    SilverStripe\Control\HTTPApplication->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
    HTTPMiddlewareAware.php:65
    SilverStripe\Control\HTTPApplication->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
    HTTPApplication.php:138
    SilverStripe\Control\HTTPApplication->execute(SilverStripe\Control\HTTPRequest, Closure, )
    HTTPApplication.php:113
    SilverStripe\Control\HTTPApplication->handle(SilverStripe\Control\HTTPRequest)
    index.php:24

Steps to Reproduce

Given the following relations, whenever you try to create a new Button, disregarding whether you do it from the View gridfield, or from the MetricGroup gridfield, you get a 500 and the above error.

┌─────────────┐                                                                       
│             │                                                                       
│     View    ├───────┐*                                                              
│             │*      ├─────────┐       ┌─────────────────────┐      ┌───────────────┐
└─────────────┘       │         │       │                     │      │               │
                      │  Button ├───────┤ AttachmentsCategory ├──────┤  Attachments  │
┌──────────────┐      │         │1     *│                     │*    *│               │
│              │      ├─────────┘       └─────────────────────┘      └───────────────┘
│ MetricGroup  │      │*                                                              
│              ├──────┘                                                               
└──────────────┘*                                                                     

Please forgive me if the code is redundant.

GridFieldConfig.php

<?php

use UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows;
use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;

class SortableGridFieldConfig extends GridFieldConfig_RelationEditor {
    public static function create(mixed ...$args) {
        $config = GridFieldConfig_RelationEditor::create(50);
        $config->addComponent(GridFieldSortableRows::create('SortOrder'));
        return $config;
    }
}

Attachments.php

<?php

use SilverStripe\AssetAdmin\Forms\UploadField;
use SilverStripe\Assets\File;
use Silverstripe\Forms\CheckboxField;
use Silverstripe\Forms\FieldList;
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\TabSet;
use Silverstripe\Forms\TextField;
use Silverstripe\ORM\DataObject;

class Button extends DataObject {
    private static $db = [
        'AdminOnly' => 'Boolean(0)',
        'Description' => 'Text',
        'Name' => 'Text',
    ];

    private static $has_many = [
        'AttachmentsCategories' => AttachmentsCategory::class,
    ];

    private static $belongs_many_many = [
        'Views' => View::class,
        'MetricGroups' => MetricGroup::class,
    ];

    private static $searchable_fields = [
        'Name',
        'Description',
    ];

    private static $summary_fields = [
        'Name' => 'Name',
        'Description' => 'Description',
        'IsAdminOnly' => 'Admin Only',
    ];

    private static $casting = [
        'IsAdminOnly' => 'Text',
    ];

    private function boolToText($input) {
        if($input) {
            return 'yes';
        } else {
            return 'no';
        }
    }

    public function getIsAdminOnly() {
        return $this->boolToText($this->AdminOnly);
    }

    public function AttachmentsCategories() {
        return $this->getComponents('AttachmentsCategories')->sort('SortOrder');
    }

    public function getCMSfields() {
        $fields = FieldList::create(TabSet::create('Root'));
        $fields->addFieldsToTab('Root.Main', [
            CheckboxField::create('AdminOnly', 'Visible only to Administrators'),
            TextField::create('Name'),
            TextField::create('Description'),
        ]);
        $fields->addFieldsToTab('Root.Main', [
            GridField::create(
                'AttachmentsCategories',
                'Attachments Categories',
                $this->AttachmentsCategories(),
                $gridConfig = SortableGridFieldConfig::create(),
            ),
        ]);
        return $fields;
    }
}

class AttachmentsCategory extends DataObject {
    private static $db = [
        'Name' => 'Text',
        'SortOrder' => 'Int',
    ];

    private static $has_one = [
        'Button' => Button::class,
    ];

    private static $many_many = [
        'Attachments' => Attachment::class,
    ];

    private static $many_many_extraFields = [
        'Attachments' => [
            'SortOrder' => 'Int',
        ],
    ];

    public function Attachments() {
        return $this->getManyManyComponents('Attachments')->sort('SortOrder');
    }

    public function getCMSfields() {
        $fields = FieldList::create(TabSet::create('Root'));
        $fields->addFieldsToTab('Root.Main', [
            TextField::create('Name'),
        ]);
        $fields->addFieldsToTab('Root.Main', [
            GridField::create(
                'Attachments',
                'Attachments',
                $this->Attachments(),
                SortableGridFieldConfig::create(),
            ),
        ]);
        return $fields;
    }
}

class Attachment extends DataObject {
    private static $db = [
        'Name' => 'Text',
    ];

    private static $has_one = [
        'File' => File::class,
    ];

    private static $belongs_many_many = [
        'Category' => AttachmentsCategory::class,
    ];

    public function getCMSfields() {
        $fields = FieldList::create(TabSet::create('Root'));
        $fields->addFieldsToTab('Root.Main', [
            TextField::create('Name'),
            UploadField::create('File'),
        ]);

        return $fields;
    }
}

MetricGroup.php

<?php

use Silverstripe\Forms\CheckboxField;
use Silverstripe\Forms\FieldList;
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
use Silverstripe\Forms\HTMLEditor\HTMLEditorField;
use SilverStripe\Forms\TabSet;
use Silverstripe\Forms\TextField;
use Silverstripe\ORM\DataObject;
use SilverStripe\Security\Security;

class MetricGroup extends DataObject {
    private static $db = [
        'AdminOnly' => 'Boolean(0)',
        'Description' => 'HTMLText',
        'Enabled' => 'Boolean(1)',
        'Subtitle' => 'Text',
        'Title' => 'Text',
    ];

    private static $defaults = [
        'Enabled' => true,
    ];

    private static $belongs_many_many = [
        'View' => View::class,
    ];

    private static $many_many = [
        'Buttons' => Button::class,
        'Metrics' => Metric::class,
    ];

    private static $many_many_extraFields = [
        'Metrics' => [
            'SortOrder' => 'Int',
        ],
        'Buttons' => [
            'SortOrder' => 'Int',
        ],
    ];

    private static $summary_fields = [
        'Title' => 'Title',
        'Subtitle' => 'Subtitle',
        'IsEnabled' => 'Enabled',
        'IsAdminOnly' => 'Admin Only',
        'MetricsCount' => 'Metrics',
    ];

    private static $casting = [
        'IsEnabled' => 'Text',
        'IsAdminOnly' => 'Text',
        'MetricsCount' => 'Text',
    ];

    private function boolToText($input) {
        if($input) {
            return 'yes';
        } else {
            return 'no';
        }
    }

    public function getIsEnabled() {
        return $this->boolToText($this->Enabled);
    }

    public function getIsAdminOnly() {
        return $this->boolToText($this->AdminOnly);
    }

    public function getMetricsCount() {
        return count($this->Metrics());
    }

    public function Metrics() {
        $member = Security::getCurrentUser();
        $metrics = $this->getManyManyComponents('Metrics')->sort('SortOrder');
        if (!$member->inGroup('administrators')) {
            $metrics = $metrics->filter(['AdminOnly' => false]);
        }
        return $metrics;
    }

    public function Buttons() {
        $member = Security::getCurrentUser();
        $buttons = $this->owner->getManyManyComponents('Buttons')->sort('SortOrder');
        if (!$member->inGroup('administrators')) {
            $buttons = $buttons->filter(['AdminOnly' => false]);
        }
        return $buttons;
    }

    public function getCMSfields() {
        $fields = FieldList::create(TabSet::create('Root'));
        $fields->addFieldsToTab('Root.Main', [
            CheckboxField::create('Enabled'),
            CheckboxField::create('AdminOnly', 'Visible only to Administrators'),
            TextField::create('Title'),
            TextField::create('Subtitle'),
            HTMLEditorField::create('Description'),
            GridField::create(
                'Metrics',
                'Metrics',
                $this->Metrics(),
                SortableGridFieldConfig::create()
                    ->removeComponentsByType(SilverStripe\Forms\GridField\GridFieldAddNewButton::class),
            ),
        ]);
        $fields->addFieldsToTab('Root.Buttons', [
            GridField::create(
                'AttachmentsCategories',
                'Attachments Categories',
                $this->Buttons(),
                $gridConfig = SortableGridFieldConfig::create(),
            ),
        ]);
        $gridConfig->getComponentByType(GridFieldAddExistingAutocompleter::class)->setResultsFormat('$Name ($Description)');
        return $fields;
    }
}

View.php

<?php

use SilverStripe\AssetAdmin\Forms\UploadField;
use SilverStripe\Assets\File;
use Silverstripe\Forms\CheckboxField;
use Silverstripe\Forms\FieldList;
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
use Silverstripe\Forms\HTMLEditor\HTMLEditorField;
use SilverStripe\Forms\TabSet;
use Silverstripe\Forms\TextField;
use Silverstripe\ORM\DataObject;
use SilverStripe\Security\Member;
use SilverStripe\View\Parsers\ShortcodeParser;
use SilverStripe\Security\Security;

class View extends DataObject {

    private static $db = [
        'Enabled' => 'Boolean(1)',
        'Name' => 'Text',
        'Code' => 'Text',
        'Factory' => 'Text',
        'Description' => 'HTMLText',
    ];

    private static $defaults = [
        'Enabled' => true,
    ];

    private static $has_one = [
        'Picture' => File::class,
        'Graphic' => File::class,
    ];

    private static $belongs_many_many = [
        'Owners' => Member::class,
    ];

    private static $many_many = [
        'Buttons' => Button::class,
        'MetricGroups' => MetricGroup::class,
    ];

    private static $many_many_extraFields = [
        'MetricGroups' => [
            'SortOrder' => 'Int',
        ],
        'Buttons' => [
            'SortOrder' => 'Int',
        ],
    ];

    private static $summary_fields = [
        'Name' => 'Name',
        'Code' => 'Code',
        'IsEnabled' => 'Enabled',
    ];

    private static $casting = [
        'IsEnabled' => 'Text',
    ];

    private function boolToText($input) {
        if($input) {
            return 'yes';
        } else {
            return 'no';
        }
    }

    public function getIsEnabled() {
        return $this->boolToText($this->Enabled);
    }

    public function MetricGroups() {
        $member = Security::getCurrentUser();
        $metric_groups = $this->getManyManyComponents('MetricGroups')->sort('SortOrder');
        if (!$member->inGroup('administrators')) {
            $metric_groups = $metric_groups->filter(['AdminOnly' => false]);
        }
        return $metric_groups;
    }

    public function Buttons() {
        $member = Security::getCurrentUser();
        $buttons = $this->owner->getManyManyComponents('Buttons')->sort('SortOrder');
        if (!$member->inGroup('administrators')) {
            $buttons = $buttons->filter(['AdminOnly' => false]);
        }
        return $buttons;
    }

    public function getCMSfields() {
        $fields = FieldList::create(TabSet::create('Root'));
        $fields->addFieldsToTab('Root.Main', [
            CheckboxField::create('Enabled'),
            TextField::create('Name'),
            TextField::create('Code'),
            TextField::create('Factory'),
            HTMLEditorField::create('Description'),
            UploadField::create('Graphic', 'Graphic for main menu'),
            UploadField::create('Picture', 'Synoptic'),
        ]);
        $fields->addFieldsToTab('Root.MetricGroups', [    
            GridField::create(
                'MetricGroups',
                'Metric Groups',
                $this->MetricGroups(),
                SortableGridFieldConfig::create(),
            ),
        ]);
        $fields->addFieldsToTab('Root.Buttons', [
            GridField::create(
                'AttachmentsCategories',
                'Attachments Categories',
                $this->Buttons(),
                $gridConfig = SortableGridFieldConfig::create(),
            ),
        ]);
        $gridConfig->getComponentByType(GridFieldAddExistingAutocompleter::class)->setResultsFormat('$Name ($Description)');
        return $fields;
    }
}
UndefinedOffset commented 6 months ago

@leonixyz One issue in the View class is that either you need to name the gridfield Buttons or call setCustomRelationName on GridFieldSortableRows to tell it what the name of the relationship is so it can find the correct relationship. In the View class the GridField's name is AttachmentsCategories for the buttons relationship. It looks like this is the case for all of the buttons at least. Double check your GridField names (or call that setCustomRelationName) and let me know if that fixes your issue 😄.