Commit 5823b2dd authored by StryKaizer's avatar StryKaizer Committed by borisson_

Issue #2656668 by borisson_, StryKaizer, Evaldas Užkuras, claudiu.cristea:...

Issue #2656668 by borisson_, StryKaizer, Evaldas Užkuras, claudiu.cristea: Facet's results sorting with multiple sorts
parent 686def44
......@@ -13,6 +13,7 @@ use Drupal\facets\Processor\PostQueryProcessorInterface;
use Drupal\facets\Processor\PreQueryProcessorInterface;
use Drupal\facets\Processor\ProcessorInterface;
use Drupal\facets\Processor\ProcessorPluginManager;
use Drupal\facets\Processor\WidgetOrderProcessorInterface;
use Drupal\facets\QueryType\QueryTypePluginManager;
use Drupal\facets\Widget\WidgetPluginManager;
......@@ -291,14 +292,39 @@ class DefaultFacetManager {
// @see \Drupal\facets\Processor\WidgetOrderProcessorInterface.
$results = $facet->getResults();
$active_sorts = [];
// Load all processors, because getProcessorsByStage does not return the
// correct configuration for the processors.
// @todo: Fix when https://www.drupal.org/node/2722267 is fixed.
$processors = $facet->getProcessors();
foreach ($facet->getProcessorsByStage(ProcessorInterface::STAGE_BUILD) as $processor) {
/** @var \Drupal\facets\Processor\BuildProcessorInterface $build_processor */
$build_processor = $this->processorPluginManager->createInstance($processor->getPluginDefinition()['id'], ['facet' => $facet]);
if (!$build_processor instanceof BuildProcessorInterface) {
throw new InvalidProcessorException(new FormattableMarkup("The processor @processor has a build definition but doesn't implement the required BuildProcessorInterface interface", ['@processor' => $processor->getPluginDefinition()['id']]));
if ($build_processor instanceof WidgetOrderProcessorInterface) {
// Sorting is handled last and together, to support nested sorts.
$active_sorts[] = $processors[$build_processor->getPluginId()];
}
else {
if (!$build_processor instanceof BuildProcessorInterface) {
throw new InvalidProcessorException("The processor {$processor->getPluginDefinition()['id']} has a build definition but doesn't implement the required BuildProcessorInterface interface");
}
$results = $build_processor->build($facet, $results);
}
$results = $build_processor->build($facet, $results);
}
uasort($results, function ($a, $b) use ($active_sorts) {
$return = 0;
foreach ($active_sorts as $sort) {
if ($return = $sort->sortResults($a, $b)) {
if ($sort->getConfiguration()['sort'] == 'DESC') {
$return *= -1;
}
break;
}
}
return $return;
});
$facet->setResults($results);
// No results behavior handling. Return a custom text or false depending on
......
......@@ -24,35 +24,11 @@ class ActiveWidgetOrderProcessor extends WidgetOrderPluginBase implements Widget
/**
* {@inheritdoc}
*/
public function sortResults(array $results, $order = 'ASC') {
if ($order === 'ASC') {
usort($results, 'self::sortActiveAsc');
}
else {
usort($results, 'self::sortActiveDesc');
}
return $results;
}
/**
* Sorts ascending.
*/
protected static function sortActiveAsc(Result $a, Result $b) {
public function sortResults(Result $a, Result $b) {
if ($a->isActive() == $b->isActive()) {
return 0;
}
return ($a->isActive()) ? -1 : 1;
}
/**
* Sorts descending.
*/
protected static function sortActiveDesc(Result $a, Result $b) {
if ($a->isActive() == $b->isActive()) {
return 0;
}
return ($a->isActive()) ? 1 : -1;
}
}
......@@ -23,35 +23,11 @@ class CountWidgetOrderProcessor extends WidgetOrderPluginBase implements WidgetO
/**
* {@inheritdoc}
*/
public function sortResults(array $results, $order = 'ASC') {
if ($order === 'ASC') {
usort($results, 'self::sortCountAsc');
}
else {
usort($results, 'self::sortCountDesc');
}
return $results;
}
/**
* Sorts ascending.
*/
protected static function sortCountAsc(Result $a, Result $b) {
public function sortResults(Result $a, Result $b) {
if ($a->getCount() == $b->getCount()) {
return 0;
}
return ($a->getCount() < $b->getCount()) ? -1 : 1;
}
/**
* Sorts descending.
*/
protected static function sortCountDesc(Result $a, Result $b) {
if ($a->getCount() == $b->getCount()) {
return 0;
}
return ($a->getCount() > $b->getCount()) ? -1 : 1;
}
}
......@@ -53,29 +53,13 @@ class DisplayValueWidgetOrderProcessor extends WidgetOrderPluginBase implements
/**
* {@inheritdoc}
*/
public function sortResults(array $results, $order = 'ASC') {
$transliteration = $this->transliteration;
if ($order === 'ASC') {
// Sorts ascending.
usort($results, function (Result $a, Result $b) use ($transliteration) {
return strnatcasecmp(
$transliteration->removeDiacritics($a->getDisplayValue()),
$transliteration->removeDiacritics($b->getDisplayValue())
);
});
}
else {
// Sorts descending.
usort($results, function (Result $a, Result $b) use ($transliteration) {
return strnatcasecmp(
$transliteration->removeDiacritics($b->getDisplayValue()),
$transliteration->removeDiacritics($a->getDisplayValue())
);
});
public function sortResults(Result $a, Result $b) {
$a = $this->transliteration->removeDiacritics($a->getDisplayValue());
$b = $this->transliteration->removeDiacritics($b->getDisplayValue());
if ($a == $b) {
return 0;
}
return $results;
return strnatcasecmp($a, $b);
}
}
......@@ -23,29 +23,8 @@ class RawValueWidgetOrderProcessor extends WidgetOrderPluginBase implements Widg
/**
* {@inheritdoc}
*/
public function sortResults(array $results, $order = 'ASC') {
if ($order === 'ASC') {
usort($results, 'self::sortRawValueAsc');
}
else {
usort($results, 'self::sortRawValueDesc');
}
return $results;
}
/**
* Sorts ascending.
*/
protected static function sortRawValueAsc(Result $a, Result $b) {
public function sortResults(Result $a, Result $b) {
return strnatcasecmp($a->getRawValue(), $b->getRawValue());
}
/**
* Sorts descending.
*/
protected static function sortRawValueDesc(Result $a, Result $b) {
return strnatcasecmp($b->getRawValue(), $a->getRawValue());
}
}
<?php
namespace Drupal\facets\Processor;
use Drupal\facets\Result\Result;
/**
* Processor runs before the renderable array is created.
......@@ -10,15 +11,14 @@ interface WidgetOrderProcessorInterface extends BuildProcessorInterface {
/**
* Orders results and return the new order of results.
*
* @param \Drupal\facets\Result\Result[] $results
* An array containing results.
* @param string $order
* A string denoting the order in which we should sort, either 'ASC' or
* 'DESC'.
* @param \Drupal\facets\Result\Result $a
* First result which should be compared.
* @param \Drupal\facets\Result\Result $b
* Second result which should be compared.
*
* @return \Drupal\facets\Result\Result[]
* The same array that was passed in, ordered by $order
* @return int
* -1, 0, or 1 depending which result
*/
public function sortResults(array $results, $order = 'ASC');
public function sortResults(Result $a, Result $b);
}
......@@ -128,6 +128,91 @@ class ProcessorIntegrationTest extends WebTestBase {
$this->checkSortByRaw();
}
/**
* Tests sorting of results.
*/
public function testResultSorting() {
$id = 'burrowing_owl';
$name = 'Burrowing owl';
$this->createFacet($name, $id, 'keywords');
$values = [
'facet_sorting[display_value_widget_order][status]' => TRUE,
'widget_configs[show_numbers]' => TRUE,
];
$this->drupalGet('admin/config/search/facets/' . $id . '/edit');
$this->drupalPostForm(NULL, $values, $this->t('Save'));
$expected_results = [
'apple',
'banana',
'grape',
'orange',
'strawberry',
];
$this->drupalGet('search-api-test-fulltext');
foreach ($expected_results as $k => $link) {
if ($k > 0) {
$x = $expected_results[($k - 1)];
$y = $expected_results[$k];
$this->assertStringPosition($x, $y);
}
}
// Sort by count, then by display value.
$values['facet_sorting[count_widget_order][status]'] = TRUE;
$values['processors[count_widget_order][weights][build]'] = 1;
$values['facet_sorting[display_value_widget_order][status]'] = TRUE;
$values['processors[display_value_widget_order][weights][build]'] = 2;
$this->drupalGet('admin/config/search/facets/' . $id . '/edit');
$this->drupalPostForm(NULL, $values, $this->t('Save'));
$expected_results = [
'banana',
'apple',
'strawberry',
'grape',
'orange',
];
$this->drupalGet('search-api-test-fulltext');
foreach ($expected_results as $k => $link) {
if ($k > 0) {
$x = $expected_results[($k - 1)];
$y = $expected_results[$k];
$this->assertStringPosition($x, $y);
}
}
$values['facet_sorting[display_value_widget_order][status]'] = TRUE;
$values['facet_sorting[count_widget_order][status]'] = TRUE;
$this->drupalGet('admin/config/search/facets/' . $id . '/edit');
$this->drupalPostForm(NULL, $values, $this->t('Save'));
$this->assertFieldChecked(
'edit-facet-sorting-display-value-widget-order-status'
);
$this->assertFieldChecked('edit-facet-sorting-count-widget-order-status');
$expected_results = [
'banana',
'apple',
'strawberry',
'grape',
'orange',
];
$this->drupalGet('search-api-test-fulltext');
foreach ($expected_results as $k => $link) {
if ($k > 0) {
$x = $expected_results[($k - 1)];
$y = $expected_results[$k];
$this->assertStringPosition($x, $y);
}
}
}
/**
* Tests the count limit processor.
*/
......
......@@ -2,12 +2,9 @@
namespace Drupal\Tests\facets\Unit\Plugin\processor;
use Drupal\facets\Entity\Facet;
use Drupal\facets\Plugin\facets\processor\ActiveWidgetOrderProcessor;
use Drupal\facets\Processor\ProcessorPluginManager;
use Drupal\facets\Result\Result;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Unit test for processor.
......@@ -19,7 +16,7 @@ class ActiveWidgetOrderProcessorTest extends UnitTestCase {
/**
* The processor to be tested.
*
* @var \Drupal\facets\processor\WidgetOrderProcessorInterface
* @var \Drupal\facets\Processor\WidgetOrderProcessorInterface
*/
protected $processor;
......@@ -55,74 +52,31 @@ class ActiveWidgetOrderProcessorTest extends UnitTestCase {
}
/**
* Tests sorting ascending.
* Tests sorting.
*/
public function testAscending() {
$sorted_results = $this->processor->sortResults($this->originalResults, 'ASC');
$expected_values = [TRUE, TRUE, TRUE, FALSE, FALSE];
foreach ($expected_values as $index => $value) {
$this->assertEquals($value, $sorted_results[$index]->isActive());
}
}
public function testSorting() {
$sort_value = $this->processor->sortResults($this->originalResults[0], $this->originalResults[1]);
$this->assertEquals(1, $sort_value);
/**
* Tests sorting descending.
*/
public function testDescending() {
$sorted_results = $this->processor->sortResults($this->originalResults, 'DESC');
$expected_values = array_reverse([TRUE, TRUE, TRUE, FALSE, FALSE]);
foreach ($expected_values as $index => $value) {
$this->assertEquals($value, $sorted_results[$index]->isActive());
}
$sort_value = $this->processor->sortResults($this->originalResults[1], $this->originalResults[2]);
$this->assertEquals(0, $sort_value);
$sort_value = $this->processor->sortResults($this->originalResults[2], $this->originalResults[3]);
$this->assertEquals(0, $sort_value);
$sort_value = $this->processor->sortResults($this->originalResults[3], $this->originalResults[4]);
$this->assertEquals(-1, $sort_value);
$sort_value = $this->processor->sortResults($this->originalResults[3], $this->originalResults[3]);
$this->assertEquals(0, $sort_value);
}
/**
* Tests configuration.
*/
public function testConfiguration() {
public function testDefaultConfiguration() {
$config = $this->processor->defaultConfiguration();
$this->assertEquals(['sort' => 'ASC'], $config);
}
/**
* Tests build.
*/
public function testBuild() {
$processor_definitions = [
'active_widget_order' => [
'id' => 'active_widget_order',
'class' => 'Drupal\facets\Plugin\facets\processor\ActiveWidgetOrderProcessor',
],
];
$manager = $this->getMockBuilder(ProcessorPluginManager::class)
->disableOriginalConstructor()
->getMock();
$manager->expects($this->once())
->method('getDefinitions')
->willReturn($processor_definitions);
$manager->expects($this->once())
->method('createInstance')
->willReturn($this->processor);
$container_builder = new ContainerBuilder();
$container_builder->set('plugin.manager.facets.processor', $manager);
\Drupal::setContainer($container_builder);
$facet = new Facet(
[
'id' => 'the_zoo',
'results' => $this->originalResults,
'processor_configs' => $processor_definitions,
],
'facets_facet'
);
$built = $this->processor->build($facet, $this->originalResults);
$this->assertEquals(TRUE, $built[0]->isActive());
$this->assertEquals(TRUE, $built[1]->isActive());
$this->assertEquals(TRUE, $built[2]->isActive());
$this->assertEquals(FALSE, $built[3]->isActive());
$this->assertEquals(FALSE, $built[4]->isActive());
}
}
......@@ -2,12 +2,9 @@
namespace Drupal\Tests\facets\Unit\Plugin\processor;
use Drupal\facets\Entity\Facet;
use Drupal\facets\Plugin\facets\processor\CountWidgetOrderProcessor;
use Drupal\facets\Processor\ProcessorPluginManager;
use Drupal\facets\Result\Result;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Unit test for processor.
......@@ -19,7 +16,7 @@ class CountWidgetOrderProcessorTest extends UnitTestCase {
/**
* The processor to be tested.
*
* @var \Drupal\facets\processor\WidgetOrderProcessorInterface
* @var \Drupal\facets\Processor\WidgetOrderProcessorInterface
*/
protected $processor;
......@@ -46,80 +43,25 @@ class CountWidgetOrderProcessorTest extends UnitTestCase {
}
/**
* Tests sorting ascending.
* Tests sorting.
*/
public function testAscending() {
public function testSorting() {
$sort_value = $this->processor->sortResults($this->originalResults[0], $this->originalResults[1]);
$this->assertEquals(1, $sort_value);
$sorted_results = $this->processor->sortResults($this->originalResults, 'ASC');
$sort_value = $this->processor->sortResults($this->originalResults[1], $this->originalResults[2]);
$this->assertEquals(-1, $sort_value);
$this->assertEquals(5, $sorted_results[0]->getCount());
$this->assertEquals('badger', $sorted_results[0]->getDisplayValue());
$this->assertEquals(10, $sorted_results[1]->getCount());
$this->assertEquals('llama', $sorted_results[1]->getDisplayValue());
$this->assertEquals(15, $sorted_results[2]->getCount());
$this->assertEquals('duck', $sorted_results[2]->getDisplayValue());
}
/**
* Tests sorting descending.
*/
public function testDescending() {
$sorted_results = $this->processor->sortResults($this->originalResults, 'DESC');
$this->assertEquals(15, $sorted_results[0]->getCount());
$this->assertEquals('duck', $sorted_results[0]->getDisplayValue());
$this->assertEquals(10, $sorted_results[1]->getCount());
$this->assertEquals('llama', $sorted_results[1]->getDisplayValue());
$this->assertEquals(5, $sorted_results[2]->getCount());
$this->assertEquals('badger', $sorted_results[2]->getDisplayValue());
$sort_value = $this->processor->sortResults($this->originalResults[2], $this->originalResults[2]);
$this->assertEquals(0, $sort_value);
}
/**
* Tests configuration.
*/
public function testConfiguration() {
public function testDefaultConfiguration() {
$config = $this->processor->defaultConfiguration();
$this->assertEquals(['sort' => 'ASC'], $config);
}
/**
* Tests build.
*/
public function testBuild() {
$processor_definitions = [
'count_widget_order' => [
'id' => 'count_widget_order',
'class' => 'Drupal\facets\Plugin\facets\processor\CountWidgetOrderProcessor',
],
];
$manager = $this->getMockBuilder(ProcessorPluginManager::class)
->disableOriginalConstructor()
->getMock();
$manager->expects($this->once())
->method('getDefinitions')
->willReturn($processor_definitions);
$manager->expects($this->once())
->method('createInstance')
->willReturn($this->processor);
$container_builder = new ContainerBuilder();
$container_builder->set('plugin.manager.facets.processor', $manager);
\Drupal::setContainer($container_builder);
$facet = new Facet(
[
'id' => 'the_zoo',
'results' => $this->originalResults,
'processor_configs' => $processor_definitions,
],
'facets_facet'
);
$built = $this->processor->build($facet, $this->originalResults);
$this->assertEquals('badger', $built[0]->getDisplayValue());
$this->assertEquals('llama', $built[1]->getDisplayValue());
$this->assertEquals('duck', $built[2]->getDisplayValue());
}
}
......@@ -3,12 +3,9 @@
namespace Drupal\Tests\facets\Unit\Plugin\processor;
use Drupal\Component\Transliteration\TransliterationInterface;
use Drupal\facets\Entity\Facet;
use Drupal\facets\Plugin\facets\processor\DisplayValueWidgetOrderProcessor;
use Drupal\facets\Processor\ProcessorPluginManager;
use Drupal\facets\Result\Result;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Unit test for processor.
......@@ -20,7 +17,7 @@ class DisplayValueWidgetOrderProcessorTest extends UnitTestCase {
/**
* The processor to be tested.
*
* @var \Drupal\facets\processor\WidgetOrderProcessorInterface
* @var \Drupal\facets\Processor\WidgetOrderProcessorInterface
*/
protected $processor;
......@@ -47,51 +44,44 @@ class DisplayValueWidgetOrderProcessorTest extends UnitTestCase {
new Result('2', '2', 22),
];
$transliteration = $this
->getMockBuilder(TransliterationInterface::class)
$transliteration = $this->getMockBuilder(TransliterationInterface::class)
->disableOriginalConstructor()
->getMock();
$transliteration->method('removeDiacritics')->willReturnCallback(function ($value) {
return str_replace('Ä', 'A', $value);
});
$transliteration
->expects($this->any())
->method('removeDiacritics')
->will($this->returnArgument(0));
$this->processor = new DisplayValueWidgetOrderProcessor([], 'display_value_widget_order', [], $transliteration);
}
/**
* Tests sorting ascending.
* Tests sorting.
*/
public function testAscending() {
$sorted_results = $this->processor->sortResults($this->originalResults, 'ASC');
$expected_values = [
'2',
'1977',
'FALSE',
'Hubbard',
'thetans',
'Tom',
'xenu',
];
foreach ($expected_values as $index => $value) {
$this->assertEquals($value, $sorted_results[$index]->getDisplayValue());
}
}
public function testSorting() {
$result_count = $this->processor->sortResults($this->originalResults[0], $this->originalResults[1]);
$this->assertEquals(-1, $result_count);
/**
* Tests sorting descending.
*/
public function testDescending() {
$sorted_results = $this->processor->sortResults($this->originalResults, 'DESC');
$expected_values = array_reverse([
'2',
'1977',
'FALSE',
'Hubbard',
'thetans',
'Tom',
'xenu',
]);
foreach ($expected_values as $index => $value) {
$this->assertEquals($value, $sorted_results[$index]->getDisplayValue());
}
$result_count = $this->processor->sortResults($this->originalResults[1], $this->originalResults[2]);
$this->assertEquals(1, $result_count);
$result_count = $this->processor->sortResults($this->originalResults[2], $this->originalResults[3]);
$this->assertEquals(1, $result_count);
$result_count = $this->processor->sortResults($this->originalResults[3], $this->originalResults[4]);
$this->assertEquals(1, $result_count);
$result_count = $this->processor->sortResults($this->originalResults[4], $this->originalResults[5]);
$this->assertEquals(1, $result_count);
$result_count = $this->processor->sortResults($this->originalResults[5], $this->originalResults[6]);
$this->assertEquals(1, $result_count);
$result_count = $this->processor->sortResults($this->originalResults[6], $this->originalResults[5]);
$this->assertEquals(-1, $result_count);
$result_count = $this->processor->sortResults($this->originalResults[3], $this->originalResults[3]);
$this->assertEquals(0, $result_count);
}
/**
......@@ -101,65 +91,21 @@ class DisplayValueWidgetOrderProcessorTest extends UnitTestCase {
$original = [
new Result('bb_test', 'Test AA', 10),
new Result('aa_test', 'Test BB', 10),
new Result('ab_test', 'Test ÄB', 10),
];
$sorted_results = $this->processor->sortResults($original, 'DESC');
$sorted_results = $this->processor->sortResults($original[0], $original[1]);
$this->assertEquals(-1, $sorted_results);
$this->assertEquals('Test BB', $sorted_results[0]->getDisplayValue());
$this->assertEquals('Test ÄB', $sorted_results[1]->getDisplayValue());
$this->assertEquals('Test AA', $sorted_results[2]->getDisplayValue());
$sorted_results = $this->processor->sortResults($original[1], $original[0]);
$this->assertEquals(1, $sorted_results);
}
/**
* Tests configuration.
*/
public function testConfiguration() {
public function testDefaultConfiguration() {
$config = $this->processor->defaultConfiguration();
$this->assertEquals(['sort' => 'ASC'], $config);
}
/**
* Tests build.
*/
public function testBuild() {
$processor_definitions = [
'display_value_widget_order' => [
'id' => 'display_value_widget_order',
'class' => 'Drupal\facets\Plugin\facets\processor\DisplayValueWidgetOrderProcessor',
],
];
$manager = $this->getMockBuilder(ProcessorPluginManager::class)
->disableOriginalConstructor()
->getMock();
$manager->expects($this->once())
->method('getDefinitions')
->willReturn($processor_definitions);
$manager->expects($this->once())
->method('createInstance')
->willReturn($this->processor);
$container_builder = new ContainerBuilder();
$container_builder->set('plugin.manager.facets.processor', $manager);
\Drupal::setContainer($container_builder);
$facet = new Facet(
[
'id' => 'the_zoo',