From 932cf6a13c48bf21f972366df9e0c647b7088c34 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Wed, 22 Nov 2023 11:56:15 +1300 Subject: [PATCH] NEW Add fixtures for arbitrary data gridfield behat tests --- _config/extensions.yml | 14 +- .../ArbitraryDataAdmin.php | 240 ++++++++++++++++++ .../ArbitraryDataModel.php | 205 +++++++++++++++ .../DatabaseBuildExtension.php | 144 +++++++++++ .../ArbitraryDataAdmin_Content.ss | 35 +++ .../ArbitraryDataAdmin_EditForm.ss | 1 + .../ArbitraryDataAdmin_ImportSpec.ss | 21 ++ 7 files changed, 659 insertions(+), 1 deletion(-) create mode 100644 code/GridFieldArbitraryData/ArbitraryDataAdmin.php create mode 100644 code/GridFieldArbitraryData/ArbitraryDataModel.php create mode 100644 code/GridFieldArbitraryData/DatabaseBuildExtension.php create mode 100644 templates/SilverStripe/FrameworkTest/GridFieldArbitraryData/ArbitraryDataAdmin_Content.ss create mode 100644 templates/SilverStripe/FrameworkTest/GridFieldArbitraryData/ArbitraryDataAdmin_EditForm.ss create mode 100644 templates/SilverStripe/FrameworkTest/GridFieldArbitraryData/ArbitraryDataAdmin_ImportSpec.ss diff --git a/_config/extensions.yml b/_config/extensions.yml index 29a123c..8ff7300 100644 --- a/_config/extensions.yml +++ b/_config/extensions.yml @@ -14,7 +14,19 @@ SilverStripe\FrameworkTest\Model\Company: SilverStripe\FrameworkTest\Model\Employee: extensions: - SilverStripe\FrameworkTest\Extension\TestDataObjectExtension - + +SilverStripe\ORM\DatabaseAdmin: + extensions: + - SilverStripe\FrameworkTest\GridFieldArbitraryData\DatabaseBuildExtension + +--- +Only: + moduleexists: 'silverstripe/testsession' +--- +SilverStripe\TestSession\TestSessionEnvironment: + extensions: + - SilverStripe\FrameworkTest\GridFieldArbitraryData\DatabaseBuildExtension + --- Only: moduleexists: 'dnadesign/silverstripe-elemental' diff --git a/code/GridFieldArbitraryData/ArbitraryDataAdmin.php b/code/GridFieldArbitraryData/ArbitraryDataAdmin.php new file mode 100644 index 0000000..9d1bdc9 --- /dev/null +++ b/code/GridFieldArbitraryData/ArbitraryDataAdmin.php @@ -0,0 +1,240 @@ + 'handleAction' + ]; + + private ?string $tab = null; + + private static int $num_initial_items = 30; + + /** + * Directly copied from ModelAdmin with minor tweaks + */ + protected function init() + { + parent::init(); + + $this->tab = $this->getRequest()->param('Tab'); + + // accessing the admin directly + if ($this->tab === null) { + $this->tab = self::TAB_ARRAYDATA; + } + + if ($this->tab !== self::TAB_ARRAYDATA && $this->tab !== self::TAB_CUSTOM_MODEL) { + throw new RuntimeException("Unexpected url segment: {$this->tab}"); + } + } + + public function getList() + { + $list = ArrayList::create(); + + switch ($this->tab) { + case self::TAB_ARRAYDATA: + foreach (self::getInitialRecords() as $stub) { + $list->add(ArrayData::create($stub)); + } + break; + case self::TAB_CUSTOM_MODEL: + $rawData = SQLSelect::create()->setFrom(ArbitraryDataModel::TABLE_NAME)->execute(); + foreach ($rawData as $record) { + $list->add(ArbitraryDataModel::create($record)); + } + $list->setDataClass(ArbitraryDataModel::class); + break; + default: + throw new RuntimeException("Unexpected tab: {$this->tab}"); + } + + $this->extend('updateList', $list); + + return $list; + } + + public static function getInitialRecords() + { + $numRecords = static::config()->get('num_initial_items'); + $records = []; + for ($id = 1; $id <= $numRecords; $id++) { + $records[] = [ + 'ID' => $id, + 'Title' => "item $id", + ]; + } + return $records; + } + + protected function getGridFieldConfig(): GridFieldConfig + { + if ($this->tab === self::TAB_CUSTOM_MODEL) { + $config = GridFieldConfig_RecordEditor::create(); + } else { + // This is effectively the same as a GridFieldConfig_RecordViewer, but without removing the GridFieldFilterHeader. + $config = GridFieldConfig_Base::create(); + $config->addComponent(GridFieldViewButton::create()); + $config->addComponent(GridFieldDetailForm::create()); + $fieldNames = array_keys(self::getInitialRecords()[0]); + $config->getComponentByType(GridFieldDataColumns::class)->setDisplayFields(array_combine($fieldNames, $fieldNames)); + $fields = array_map(fn ($name) => $name === 'ID' ? HiddenField::create($name) : TextField::create($name), $fieldNames); + $config->getComponentByType(GridFieldDetailForm::class)->setFields(FieldList::create($fields)); + $searchContext = BasicSearchContext::create(ArrayData::class); + $searchFields = array_map( + fn ($name) => $name === 'ID' + ? HiddenField::create(BasicSearchContext::config()->get('general_search_field_name')) + : TextField::create($name), + $fieldNames + ); + $searchContext->setFields(FieldList::create($searchFields)); + $config->getComponentByType(GridFieldFilterHeader::class)->setSearchContext($searchContext); + } + + $config->getComponentByType(GridFieldPaginator::class)->setItemsPerPage(10); + + $exportButton = GridFieldExportButton::create('buttons-before-left'); + // $exportButton->setExportColumns($this->getExportFields()); + + $config->addComponents([ + $exportButton, + GridFieldPrintButton::create('buttons-before-left') + ]); + + $this->extend('updateGridFieldConfig', $config); + + return $config; + } + + /** + * Directly copied from ModelAdmin with minor tweaks + */ + protected function getGridField(): GridField + { + $field = GridField::create( + $this->tab, + false, + $this->getList(), + $this->getGridFieldConfig() + ); + + $this->extend('updateGridField', $field); + + return $field; + } + + /** + * Directly copied from ModelAdmin with minor tweaks + */ + public function getEditForm($id = null, $fields = null) + { + $form = Form::create( + $this, + 'EditForm', + FieldList::create($this->getGridField()), + FieldList::create() + )->setHTMLID('Form_EditForm'); + + $form->addExtraClass('cms-edit-form cms-panel-padded center flexbox-area-grow'); + $form->setTemplate($this->getTemplatesWithSuffix('_EditForm')); + $editFormAction = Controller::join_links($this->Link($this->tab), 'EditForm'); + $form->setFormAction($editFormAction); + $form->setAttribute('data-pjax-fragment', 'CurrentForm'); + + $this->extend('updateEditForm', $form); + + return $form; + } + + /** + * Directly copied from ModelAdmin with minor tweaks + */ + protected function getManagedTabs() + { + $tabs = [ + self::TAB_ARRAYDATA => 'ArrayData', + self::TAB_CUSTOM_MODEL => 'Custom Model', + ]; + $forms = new ArrayList(); + + foreach ($tabs as $tab => $title) { + $forms->push(new ArrayData([ + 'Title' => $title, + 'Tab' => $tab, + 'Link' => $this->Link($tab), + 'LinkOrCurrent' => ($tab === $this->tab) ? 'current' : 'link' + ])); + } + + return $forms; + } + + /** + * Directly copied from ModelAdmin with minor tweaks + */ + public function Link($action = null) + { + if (!$action) { + $action = $this->tab; + } + return parent::Link($action); + } + + /** + * Directly copied from ModelAdmin with minor tweaks + */ + public function Breadcrumbs($unlinked = false) + { + $items = parent::Breadcrumbs($unlinked); + + // Show the class name rather than ModelAdmin title as root node + $params = $this->getRequest()->getVars(); + if (isset($params['url'])) { + unset($params['url']); + } + + $items[0]->Title = $this->tab; + $items[0]->Link = Controller::join_links( + $this->Link($this->tab), + '?' . http_build_query($params ?? []) + ); + + return $items; + } +} diff --git a/code/GridFieldArbitraryData/ArbitraryDataModel.php b/code/GridFieldArbitraryData/ArbitraryDataModel.php new file mode 100644 index 0000000..60784f7 --- /dev/null +++ b/code/GridFieldArbitraryData/ArbitraryDataModel.php @@ -0,0 +1,205 @@ +ID; + $now = DBDatetime::now()->Rfc2822(); + $record = $this->array; + $record['LastEdited'] = $now; + if ($isNew) { + $record['Created'] = $now; + } + + // Remove anything that isn't storable in the DB, such as Security ID + $dbColumns = DB::field_list(self::TABLE_NAME); + foreach ($record as $fieldName => $value) { + if (!array_key_exists($fieldName, $dbColumns)) { + unset($record[$fieldName]); + } + } + + // This is basically a fancy SQLInsert or SQLUpdate - I just copied DataObject so I didn't have to think. + $manipulation = [ + 'command' => $isNew ? 'insert' : 'update', + 'fields' => $record, + ]; + if (!$isNew) { + $manipulation['id'] = $this->ID; + } + DB::manipulate([self::TABLE_NAME => $manipulation]); + + if ($isNew) { + // Must save the ID in this object so GridField knows what URL to redirect to. + $this->ID = DB::get_generated_id(self::TABLE_NAME); + } + } + + public function delete() + { + if (!$this->ID) { + throw new LogicException('DataObject::delete() called on a record without an ID'); + } + SQLDelete::create()->setFrom(self::TABLE_NAME)->setWhere(['ID' => $this->ID])->execute(); + $this->ID = 0; + } + + /** + * Sets the value from the form + */ + public function setCastedField($fieldName, $val) + { + $this->$fieldName = $val; + } + + /** + * Gives a localisable plural name for the class. + * + * Used in add button, breadcrumbs, and toasts + */ + public function i18n_singular_name() + { + return _t(__CLASS__ . '.SINGULAR_NAME', 'Arbitrary Datum'); + } + + /** + * Gives a localisable plural name for the class. + * + * Used in filter header as the placeholder text + */ + public function i18n_plural_name() + { + return _t(__CLASS__ . '.PLURAL_NAME', 'Arbitrary Data'); + } + + /** + * Used to auto-detect gridfield columns + */ + public function summaryFields() + { + $fieldNames = $this->getFieldNames(); + $summaryFields = array_combine($fieldNames, $fieldNames); + unset($summaryFields['ID']); + return $summaryFields; + } + + public function getDefaultSearchContext() + { + return BasicSearchContext::create(static::class); + } + + public function scaffoldSearchFields() + { + $fieldNames = $this->getFieldNames(); + $fields = [HiddenField::create(BasicSearchContext::config()->get('general_search_field_name'))]; + foreach ($fieldNames as $fieldName) { + if ($fieldName === 'ID' || $fieldName === 'Created' || $fieldName === 'LastEdited') { + continue; + } + $fields[] = TextField::create($fieldName); + } + return FieldList::create($fields); + } + + public function getCMSFields(): FieldList + { + $fieldNames = $this->getFieldNames(); + $fields = []; + foreach ($fieldNames as $fieldName) { + switch ($fieldName) { + case 'ID': + $fields[] = HiddenField::create($fieldName); + break; + case 'Created': + case 'LastEdited': + $fields[] = DatetimeField::create($fieldName)->performReadonlyTransformation(); + break; + default: + $fields[] = TextField::create($fieldName); + } + } + return FieldList::create($fields); + } + + // Note that a FieldsValidator is used by default, but we can add additional validation if we want + // by implementing this method: + // public function getCMSCompositeValidator() + // { + // return CompositeValidator::create([ + // FieldsValidator::create(), + // RequiredFields::create(['Title']), + // ]); + // } + + public function canCreate() + { + return true; + } + + public function canEdit() + { + return true; + } + + public function canDelete() + { + return true; + } + + private function getFieldNames() + { + $fieldNames = array_keys(ArbitraryDataAdmin::getInitialRecords()[0]); + $fieldNames[] = 'Created'; + $fieldNames[] = 'LastEdited'; + return $fieldNames; + } +} diff --git a/code/GridFieldArbitraryData/DatabaseBuildExtension.php b/code/GridFieldArbitraryData/DatabaseBuildExtension.php new file mode 100644 index 0000000..1267045 --- /dev/null +++ b/code/GridFieldArbitraryData/DatabaseBuildExtension.php @@ -0,0 +1,144 @@ +buildTable(true); + $this->populateData(); + } + + /** + * This extension hook is on DatabaseAdmin, after dev/build has finished building the database. + */ + protected function onAfterBuild(bool $quiet, bool $populate, bool $testMode): void + { + if ($testMode) { + return; + } + + if (!$quiet) { + if (Director::is_cli()) { + echo "\nCREATING TABLE FOR FRAMEWORKTEST ARBITRARY DATA\n\n"; + } else { + echo "\n

Creating table for frameworktest arbitrary data

'; + } + + if ($populate) { + if (!$quiet) { + if (Director::is_cli()) { + echo "\nCREATING DATABASE RECORDS FOR FRAMEWORKTEST ARBITRARY DATA\n\n"; + } else { + echo "\n

Creating database records arbitrary data

'; + } + } + + if (!$quiet) { + echo (Director::is_cli()) ? "\n Frameworktest database build completed!\n\n" : '

Frameworktest database build completed!

'; + } + } + + private function buildTable(bool $quiet): void + { + $tableName = ArbitraryDataModel::TABLE_NAME; + + // Log data + if (!$quiet) { + $showRecordCounts = DatabaseAdmin::config()->get('show_record_counts'); + if ($showRecordCounts && DB::get_schema()->hasTable($tableName)) { + try { + $count = SQLSelect::create()->setFrom($tableName)->count(); + $countSuffix = " ($count records)"; + } catch (\Exception $e) { + $countSuffix = ' (error getting record count)'; + } + } else { + $countSuffix = ""; + } + + if (Director::is_cli()) { + echo " * $tableName$countSuffix\n"; + } else { + echo "
  • $tableName$countSuffix
  • \n"; + } + } + + // Get field schema + $fields = [ + 'ID' => 'PrimaryKey', + 'LastEdited' => 'DBDatetime', + 'Created' => 'DBDatetime', + ]; + $fieldNames = array_keys(ArbitraryDataAdmin::getInitialRecords()[0]); + foreach ($fieldNames as $fieldName) { + if ($fieldName === 'ID') { + continue; + } + $fields[$fieldName] = 'Varchar'; + } + + // Write the table to the database + DB::get_schema()->schemaUpdate(function () use ($tableName, $fields) { + DB::require_table( + $tableName, + $fields, + null, + true, + DataObject::config()->get('create_table_options') + ); + }); + } + + private function populateData(): void + { + $tableName = ArbitraryDataModel::TABLE_NAME; + $count = SQLSelect::create()->setFrom($tableName)->count(); + + if ($count <= 0) { + $now = DBDatetime::now()->Rfc2822(); + $data = ArbitraryDataAdmin::getInitialRecords(); + foreach ($data as $record) { + unset($record['ID']); + $record['LastEdited'] = $now; + $record['Created'] = $now; + + $item = ArbitraryDataModel::create($record); + $item->write(); + } + + DB::alteration_message('Added default records for frameworktest arbitrary data', 'created'); + } + } +} diff --git a/templates/SilverStripe/FrameworkTest/GridFieldArbitraryData/ArbitraryDataAdmin_Content.ss b/templates/SilverStripe/FrameworkTest/GridFieldArbitraryData/ArbitraryDataAdmin_Content.ss new file mode 100644 index 0000000..03b7979 --- /dev/null +++ b/templates/SilverStripe/FrameworkTest/GridFieldArbitraryData/ArbitraryDataAdmin_Content.ss @@ -0,0 +1,35 @@ +
    + +
    +
    + +
    + +
    +
      + <% loop $ManagedTabs %> +
    • + $Title +
    • + <% end_loop %> +
    +
    +
    + +
    + $Tools + +
    + $EditForm +
    +
    + +
    diff --git a/templates/SilverStripe/FrameworkTest/GridFieldArbitraryData/ArbitraryDataAdmin_EditForm.ss b/templates/SilverStripe/FrameworkTest/GridFieldArbitraryData/ArbitraryDataAdmin_EditForm.ss new file mode 100644 index 0000000..f757219 --- /dev/null +++ b/templates/SilverStripe/FrameworkTest/GridFieldArbitraryData/ArbitraryDataAdmin_EditForm.ss @@ -0,0 +1 @@ +<% include SilverStripe/Forms/Form %> diff --git a/templates/SilverStripe/FrameworkTest/GridFieldArbitraryData/ArbitraryDataAdmin_ImportSpec.ss b/templates/SilverStripe/FrameworkTest/GridFieldArbitraryData/ArbitraryDataAdmin_ImportSpec.ss new file mode 100644 index 0000000..d4b94c8 --- /dev/null +++ b/templates/SilverStripe/FrameworkTest/GridFieldArbitraryData/ArbitraryDataAdmin_ImportSpec.ss @@ -0,0 +1,21 @@ +
    + <%t SilverStripe\Admin\ModelAdmin.IMPORTSPECLINK 'Show Specification for {model}' model=$ModelName %> +
    +

    <%t SilverStripe\Admin\ModelAdmin.IMPORTSPECTITLE 'Specification for {model}' model=$ModelName %>

    +
    <%t SilverStripe\Admin\ModelAdmin.IMPORTSPECFIELDS 'Database columns' %>
    + <% loop $Fields %> +
    +
    $Name
    +
    $Description
    +
    + <% end_loop %> + +
    <%t SilverStripe\Admin\ModelAdmin.IMPORTSPECRELATIONS 'Relations' %>
    + <% loop $Relations %> +
    +
    $Name
    +
    $Description
    +
    + <% end_loop %> +
    +