Loading core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRevPub.php +2 −0 Changes for core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRevPub.php: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ use Drupal\entity_test\EntityTestDeleteForm; use Drupal\entity_test\EntityTestForm; use Drupal\entity_test\EntityTestViewBuilder as TestViewBuilder; use Drupal\views\EntityViewsData; /** * Defines the test entity class. Loading @@ -44,6 +45,7 @@ 'revision-delete' => RevisionDeleteForm::class, 'revision-revert' => RevisionRevertForm::class, ], 'views_data' => EntityViewsData::class, 'route_provider' => [ 'html' => DefaultHtmlRouteProvider::class, 'revision' => RevisionHtmlRouteProvider::class, Loading core/modules/workspaces/src/Hook/ViewsOperations.php +10 −2 Changes for core/modules/workspaces/src/Hook/ViewsOperations.php: 10 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -157,8 +157,16 @@ protected function alterQueryForEntityType(Sql $query, EntityTypeInterface $enti $base_entity_table = $entity_type->isTranslatable() ? $entity_type->getDataTable() : $entity_type->getBaseTable(); $base_fields = array_diff($table_mapping->getFieldNames($entity_type->getBaseTable()), [$entity_type->getKey('langcode')]); $revisionable_fields = array_diff($table_mapping->getFieldNames($entity_type->getRevisionDataTable()), $base_fields); // Only revisionable fields can change in a pending revision, so that's // what we need to read from the revision tables below. Views identifies // them by column name, so a multi-column field contributes one entry per // column. $revisionable_fields = []; foreach ($table_mapping->getFieldNames($base_entity_table) as $field_name) { if ($field_storage_definitions[$field_name]->isRevisionable()) { $revisionable_fields = array_merge($revisionable_fields, array_values($table_mapping->getColumnNames($field_name))); } } // Go through and look to see if we have to modify fields and filters. foreach ($query->fields as &$field_info) { Loading core/modules/workspaces/tests/src/Kernel/WorkspaceViewsIntegrationTest.php +128 −0 Changes for core/modules/workspaces/tests/src/Kernel/WorkspaceViewsIntegrationTest.php: 128 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -14,7 +14,10 @@ use Drupal\Tests\node\Traits\NodeCreationTrait; use Drupal\Tests\node\Traits\PromotedContentViewTestTrait; use Drupal\Tests\user\Traits\UserCreationTrait; use Drupal\taxonomy\Entity\Term; use Drupal\taxonomy\Entity\Vocabulary; use Drupal\Tests\views\Kernel\ViewsKernelTestBase; use Drupal\views\Entity\View; use Drupal\views\Views; use Drupal\views_ui\ViewUI; use Drupal\workspaces\Entity\Workspace; Loading Loading @@ -46,6 +49,7 @@ class WorkspaceViewsIntegrationTest extends ViewsKernelTestBase { 'filter', 'node', 'language', 'taxonomy', 'text', 'views_ui', 'workspaces', Loading @@ -70,6 +74,8 @@ protected function setUp($import_test_views = TRUE): void { $this->entityTypeManager = \Drupal::entityTypeManager(); $this->installEntitySchema('entity_test_mulrevpub'); $this->installEntitySchema('entity_test_revpub'); $this->installEntitySchema('taxonomy_term'); $this->installEntitySchema('node'); $this->installEntitySchema('user'); $this->installEntitySchema('workspace'); Loading Loading @@ -188,6 +194,128 @@ public function testViewsQueryAlter(): void { $this->assertIdenticalResultset($view, $expected, ['nid' => 'nid']); } /** * Tests the views query alter for an entity type without a data table. */ public function testViewsQueryAlterWithoutDataTable(): void { // This entity type has no data table, so the query reads the base table, // which holds revisionable and non-revisionable fields alike. Only the // revisionable ones (e.g. 'name') should be affected by the query alter. $storage = $this->entityTypeManager->getStorage('entity_test_revpub'); $entity = $storage->create(['name' => 'live', 'non_rev_field' => 'shared']); $entity->save(); Workspace::create(['id' => 'stage', 'label' => 'Stage'])->save(); $this->assertSame(['live'], $this->getRevPubViewResults('name', 'live')); $this->assertSame([], $this->getRevPubViewResults('name', 'stage')); $this->switchToWorkspace('stage'); $entity->set('name', 'stage'); $entity->save(); // The pending revision holds the new name, so that is what the filter has // to match on, and what the field has to return. $this->assertSame(['stage'], $this->getRevPubViewResults('name', 'stage')); $this->assertSame([], $this->getRevPubViewResults('name', 'live')); // A non-revisionable field is shared by every revision and only exists on // the base table. Redirecting it to the revision table would query a column // that doesn't exist. The name still comes from the pending revision. $this->assertSame(['stage'], $this->getRevPubViewResults('non_rev_field', 'shared')); // Live still sees the default revision. $this->switchToLive(); $this->assertSame(['live'], $this->getRevPubViewResults('name', 'live')); $this->assertSame([], $this->getRevPubViewResults('name', 'stage')); } /** * Returns the 'name' values of a view of 'entity_test_revpub' with a filter. * * @param string $field * The field to filter on. * @param string $value * The value to filter for. * * @return string[] * The name of each row the view returned. */ protected function getRevPubViewResults(string $field, string $value): array { $view = View::create([ 'id' => 'test_entity_test_revpub', 'base_table' => 'entity_test_revpub', 'base_field' => 'id', ]); $executable = Views::executableFactory()->get($view); $executable->setDisplay('default'); $executable->addHandler('default', 'field', 'entity_test_revpub', 'name', []); // Views only selects the column when something sorts on it. $executable->addHandler('default', 'sort', 'entity_test_revpub', 'name', []); $executable->addHandler('default', 'filter', 'entity_test_revpub', $field, [ 'operator' => '=', 'value' => $value, ]); $executable->execute(); // The rendered value comes from the loaded entity, which is already the // pending revision, so read the selected column instead. return array_map(fn ($row) => (string) $row->entity_test_revpub_name, $executable->result); } /** * Tests the views query alter for a field stored in more than one column. */ public function testViewsQueryAlterMultiColumnField(): void { Vocabulary::create(['vid' => 'tags', 'name' => 'Tags'])->save(); $term = Term::create([ 'vid' => 'tags', 'name' => 'Test term', 'description' => ['value' => 'live text', 'format' => 'plain_text'], ]); $term->save(); Workspace::create(['id' => 'stage', 'label' => 'Stage'])->save(); $this->assertSame(1, $this->countTermViewResults('live text')); $this->assertSame(0, $this->countTermViewResults('stage text')); $this->switchToWorkspace('stage'); $term->set('description', ['value' => 'stage text', 'format' => 'plain_text']); $term->save(); // Views names the column 'description__value', not 'description', so the // filter is only redirected when the column name is matched. $this->assertSame(1, $this->countTermViewResults('stage text')); $this->assertSame(0, $this->countTermViewResults('live text')); } /** * Counts the rows of a term view filtered on the description column. * * @param string $value * The description value to filter for. * * @return int * The number of rows the view returned. */ protected function countTermViewResults(string $value): int { $view = View::create([ 'id' => 'test_taxonomy_term', 'base_table' => 'taxonomy_term_field_data', 'base_field' => 'tid', ]); $executable = Views::executableFactory()->get($view); $executable->setDisplay('default'); $executable->addHandler('default', 'filter', 'taxonomy_term_field_data', 'description__value', [ 'operator' => '=', 'value' => $value, ]); $executable->execute(); return count($executable->result); } /** * Tests creating a view of workspace entities. * Loading Loading
core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRevPub.php +2 −0 Changes for core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRevPub.php: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ use Drupal\entity_test\EntityTestDeleteForm; use Drupal\entity_test\EntityTestForm; use Drupal\entity_test\EntityTestViewBuilder as TestViewBuilder; use Drupal\views\EntityViewsData; /** * Defines the test entity class. Loading @@ -44,6 +45,7 @@ 'revision-delete' => RevisionDeleteForm::class, 'revision-revert' => RevisionRevertForm::class, ], 'views_data' => EntityViewsData::class, 'route_provider' => [ 'html' => DefaultHtmlRouteProvider::class, 'revision' => RevisionHtmlRouteProvider::class, Loading
core/modules/workspaces/src/Hook/ViewsOperations.php +10 −2 Changes for core/modules/workspaces/src/Hook/ViewsOperations.php: 10 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -157,8 +157,16 @@ protected function alterQueryForEntityType(Sql $query, EntityTypeInterface $enti $base_entity_table = $entity_type->isTranslatable() ? $entity_type->getDataTable() : $entity_type->getBaseTable(); $base_fields = array_diff($table_mapping->getFieldNames($entity_type->getBaseTable()), [$entity_type->getKey('langcode')]); $revisionable_fields = array_diff($table_mapping->getFieldNames($entity_type->getRevisionDataTable()), $base_fields); // Only revisionable fields can change in a pending revision, so that's // what we need to read from the revision tables below. Views identifies // them by column name, so a multi-column field contributes one entry per // column. $revisionable_fields = []; foreach ($table_mapping->getFieldNames($base_entity_table) as $field_name) { if ($field_storage_definitions[$field_name]->isRevisionable()) { $revisionable_fields = array_merge($revisionable_fields, array_values($table_mapping->getColumnNames($field_name))); } } // Go through and look to see if we have to modify fields and filters. foreach ($query->fields as &$field_info) { Loading
core/modules/workspaces/tests/src/Kernel/WorkspaceViewsIntegrationTest.php +128 −0 Changes for core/modules/workspaces/tests/src/Kernel/WorkspaceViewsIntegrationTest.php: 128 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -14,7 +14,10 @@ use Drupal\Tests\node\Traits\NodeCreationTrait; use Drupal\Tests\node\Traits\PromotedContentViewTestTrait; use Drupal\Tests\user\Traits\UserCreationTrait; use Drupal\taxonomy\Entity\Term; use Drupal\taxonomy\Entity\Vocabulary; use Drupal\Tests\views\Kernel\ViewsKernelTestBase; use Drupal\views\Entity\View; use Drupal\views\Views; use Drupal\views_ui\ViewUI; use Drupal\workspaces\Entity\Workspace; Loading Loading @@ -46,6 +49,7 @@ class WorkspaceViewsIntegrationTest extends ViewsKernelTestBase { 'filter', 'node', 'language', 'taxonomy', 'text', 'views_ui', 'workspaces', Loading @@ -70,6 +74,8 @@ protected function setUp($import_test_views = TRUE): void { $this->entityTypeManager = \Drupal::entityTypeManager(); $this->installEntitySchema('entity_test_mulrevpub'); $this->installEntitySchema('entity_test_revpub'); $this->installEntitySchema('taxonomy_term'); $this->installEntitySchema('node'); $this->installEntitySchema('user'); $this->installEntitySchema('workspace'); Loading Loading @@ -188,6 +194,128 @@ public function testViewsQueryAlter(): void { $this->assertIdenticalResultset($view, $expected, ['nid' => 'nid']); } /** * Tests the views query alter for an entity type without a data table. */ public function testViewsQueryAlterWithoutDataTable(): void { // This entity type has no data table, so the query reads the base table, // which holds revisionable and non-revisionable fields alike. Only the // revisionable ones (e.g. 'name') should be affected by the query alter. $storage = $this->entityTypeManager->getStorage('entity_test_revpub'); $entity = $storage->create(['name' => 'live', 'non_rev_field' => 'shared']); $entity->save(); Workspace::create(['id' => 'stage', 'label' => 'Stage'])->save(); $this->assertSame(['live'], $this->getRevPubViewResults('name', 'live')); $this->assertSame([], $this->getRevPubViewResults('name', 'stage')); $this->switchToWorkspace('stage'); $entity->set('name', 'stage'); $entity->save(); // The pending revision holds the new name, so that is what the filter has // to match on, and what the field has to return. $this->assertSame(['stage'], $this->getRevPubViewResults('name', 'stage')); $this->assertSame([], $this->getRevPubViewResults('name', 'live')); // A non-revisionable field is shared by every revision and only exists on // the base table. Redirecting it to the revision table would query a column // that doesn't exist. The name still comes from the pending revision. $this->assertSame(['stage'], $this->getRevPubViewResults('non_rev_field', 'shared')); // Live still sees the default revision. $this->switchToLive(); $this->assertSame(['live'], $this->getRevPubViewResults('name', 'live')); $this->assertSame([], $this->getRevPubViewResults('name', 'stage')); } /** * Returns the 'name' values of a view of 'entity_test_revpub' with a filter. * * @param string $field * The field to filter on. * @param string $value * The value to filter for. * * @return string[] * The name of each row the view returned. */ protected function getRevPubViewResults(string $field, string $value): array { $view = View::create([ 'id' => 'test_entity_test_revpub', 'base_table' => 'entity_test_revpub', 'base_field' => 'id', ]); $executable = Views::executableFactory()->get($view); $executable->setDisplay('default'); $executable->addHandler('default', 'field', 'entity_test_revpub', 'name', []); // Views only selects the column when something sorts on it. $executable->addHandler('default', 'sort', 'entity_test_revpub', 'name', []); $executable->addHandler('default', 'filter', 'entity_test_revpub', $field, [ 'operator' => '=', 'value' => $value, ]); $executable->execute(); // The rendered value comes from the loaded entity, which is already the // pending revision, so read the selected column instead. return array_map(fn ($row) => (string) $row->entity_test_revpub_name, $executable->result); } /** * Tests the views query alter for a field stored in more than one column. */ public function testViewsQueryAlterMultiColumnField(): void { Vocabulary::create(['vid' => 'tags', 'name' => 'Tags'])->save(); $term = Term::create([ 'vid' => 'tags', 'name' => 'Test term', 'description' => ['value' => 'live text', 'format' => 'plain_text'], ]); $term->save(); Workspace::create(['id' => 'stage', 'label' => 'Stage'])->save(); $this->assertSame(1, $this->countTermViewResults('live text')); $this->assertSame(0, $this->countTermViewResults('stage text')); $this->switchToWorkspace('stage'); $term->set('description', ['value' => 'stage text', 'format' => 'plain_text']); $term->save(); // Views names the column 'description__value', not 'description', so the // filter is only redirected when the column name is matched. $this->assertSame(1, $this->countTermViewResults('stage text')); $this->assertSame(0, $this->countTermViewResults('live text')); } /** * Counts the rows of a term view filtered on the description column. * * @param string $value * The description value to filter for. * * @return int * The number of rows the view returned. */ protected function countTermViewResults(string $value): int { $view = View::create([ 'id' => 'test_taxonomy_term', 'base_table' => 'taxonomy_term_field_data', 'base_field' => 'tid', ]); $executable = Views::executableFactory()->get($view); $executable->setDisplay('default'); $executable->addHandler('default', 'filter', 'taxonomy_term_field_data', 'description__value', [ 'operator' => '=', 'value' => $value, ]); $executable->execute(); return count($executable->result); } /** * Tests creating a view of workspace entities. * Loading