Skip to content
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

Fix relevance sort #2773

Merged
merged 6 commits into from
Apr 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/Api/Controller/AbstractSerializeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Flarum\Api\JsonApiResponse;
use Illuminate\Contracts\Container\Container;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
Expand Down Expand Up @@ -255,6 +256,11 @@ protected function buildParameters(ServerRequestInterface $request)
return new Parameters($request->getQueryParams());
}

protected function sortIsDefault(ServerRequestInterface $request): bool
{
return ! Arr::get($request->getQueryParams(), 'sort');
}

/**
* Set the serializer that will serialize data for the endpoint.
*
Expand Down
3 changes: 2 additions & 1 deletion src/Api/Controller/ListDiscussionsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,13 @@ protected function data(ServerRequestInterface $request, Document $document)
$actor = $request->getAttribute('actor');
$filters = $this->extractFilter($request);
$sort = $this->extractSort($request);
$sortIsDefault = $this->sortIsDefault($request);

$limit = $this->extractLimit($request);
$offset = $this->extractOffset($request);
$include = array_merge($this->extractInclude($request), ['state']);

$criteria = new QueryCriteria($actor, $filters, $sort);
$criteria = new QueryCriteria($actor, $filters, $sort, $sortIsDefault);
if (array_key_exists('q', $filters)) {
$results = $this->searcher->search($criteria, $limit, $offset);
} else {
Expand Down
3 changes: 2 additions & 1 deletion src/Api/Controller/ListPostsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,13 @@ protected function data(ServerRequestInterface $request, Document $document)

$filters = $this->extractFilter($request);
$sort = $this->extractSort($request);
$sortIsDefault = $this->sortIsDefault($request);

$limit = $this->extractLimit($request);
$offset = $this->extractOffset($request);
$include = $this->extractInclude($request);

$results = $this->filterer->filter(new QueryCriteria($actor, $filters, $sort), $limit, $offset);
$results = $this->filterer->filter(new QueryCriteria($actor, $filters, $sort, $sortIsDefault), $limit, $offset);

$document->addPaginationLinks(
$this->url->to('api')->route('posts.index'),
Expand Down
3 changes: 2 additions & 1 deletion src/Api/Controller/ListUsersController.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,13 @@ protected function data(ServerRequestInterface $request, Document $document)

$filters = $this->extractFilter($request);
$sort = $this->extractSort($request);
$sortIsDefault = $this->sortIsDefault($request);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we rename sortIsDefault to isDefaultSorting or maybe isSortingByDefault.

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mixed feelings about this one. Any use of sorting describes whether or not any sort is being applied. But this variable should describe whether the sort currently being used (whatever that is, could be no sort at all) is the controller's default sort.

Alternatively, we could use userRequestedSort or userSpecifiedSort and invert everything, but that sounds very verbose.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, we only want to know if we're falling back to the default sort.

Though I was more looking for just a isSomething naming, since that's the usual convention for boolean variables/methods where possible. But isDefaultSort might be weird.

Let's keep it as is then!


$limit = $this->extractLimit($request);
$offset = $this->extractOffset($request);
$include = $this->extractInclude($request);

$criteria = new QueryCriteria($actor, $filters, $sort);
$criteria = new QueryCriteria($actor, $filters, $sort, $sortIsDefault);
if (array_key_exists('q', $filters)) {
$results = $this->searcher->search($criteria, $limit, $offset);
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/Filter/AbstractFilterer.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public function filter(QueryCriteria $criteria, int $limit = null, int $offset =
}
}

$this->applySort($filterState, $criteria->sort);
$this->applySort($filterState, $criteria->sort, $criteria->sortIsDefault);
$this->applyOffset($filterState, $offset);
$this->applyLimit($filterState, $limit + 1);

Expand Down
12 changes: 9 additions & 3 deletions src/Query/ApplyQueryParametersTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,28 @@

use Illuminate\Support\Str;

/**
* @internal
*/
trait ApplyQueryParametersTrait
{
/**
* Apply sort criteria to a discussion query.
*
* @param AbstractQueryState $query
* @param array $sort
* @param bool $sortIsDefault
*/
protected function applySort(AbstractQueryState $query, array $sort = null)
protected function applySort(AbstractQueryState $query, array $sort = null, bool $sortIsDefault = false)
{
$sort = $sort ?: $query->getDefaultSort();
if ($sortIsDefault && ! empty($query->getDefaultSort())) {
$sort = $query->getDefaultSort();
}

if (is_callable($sort)) {
$sort($query->getQuery());
} else {
foreach ($sort as $field => $order) {
foreach ((array) $sort as $field => $order) {
if (is_array($order)) {
foreach ($order as $value) {
$query->getQuery()->orderByRaw(Str::snake($field).' != ?', [$value]);
Expand Down
11 changes: 10 additions & 1 deletion src/Query/QueryCriteria.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,26 @@ class QueryCriteria
*/
public $sort;

/**
* Is the sort for this request the default sort from the controller?
* If false, the current request specifies a sort.
*
* @var bool
*/
public $sortIsDefault;

/**
* @param User $actor The user performing the query.
* @param array $query The query params.
* @param array $sort An array of sort-order pairs, where the column is the
* key, and the order is the value. The order may be 'asc', 'desc', or
* an array of IDs to order by.
*/
public function __construct(User $actor, $query, array $sort = null)
public function __construct(User $actor, $query, array $sort = null, bool $sortIsDefault = false)
{
$this->actor = $actor;
$this->query = $query;
$this->sort = $sort;
$this->sortIsDefault = $sortIsDefault;
}
}
2 changes: 1 addition & 1 deletion src/Search/AbstractSearcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public function search(QueryCriteria $criteria, $limit = null, $offset = 0): Que
$search = new SearchState($query->getQuery(), $actor);

$this->gambits->apply($search, $criteria->query['q']);
$this->applySort($search, $criteria->sort);
$this->applySort($search, $criteria->sort, $criteria->sortIsDefault);
$this->applyOffset($search, $offset);
$this->applyLimit($search, $limit + 1);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;

class ListTest extends TestCase
class ListWithFulltextSearchTest extends TestCase
{
use RetrievesAuthorizedUsers;

Expand All @@ -31,12 +31,15 @@ protected function setUp(): void
// We clean it up explcitly at the end.
$this->database()->table('discussions')->insert([
['id' => 1, 'title' => 'lightsail in title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'comment_count' => 1],
['id' => 2, 'title' => 'not in title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'comment_count' => 1],
['id' => 2, 'title' => 'not in title and older', 'created_at' => Carbon::createFromDate(2020, 01, 01)->toDateTimeString(), 'user_id' => 1, 'comment_count' => 1],
['id' => 3, 'title' => 'not in title either', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'comment_count' => 1],
]);

$this->database()->table('posts')->insert([
['id' => 1, 'discussion_id' => 1, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p>not in text</p></t>'],
['id' => 2, 'discussion_id' => 2, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p>lightsail in text</p></t>'],
['id' => 3, 'discussion_id' => 2, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p>another lightsail for discussion 2!</p></t>'],
['id' => 4, 'discussion_id' => 3, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p>just one lightsail for discussion 3.</p></t>'],
]);

// We need to call these again, since we rolled back the transaction started by `::app()`.
Expand All @@ -52,8 +55,8 @@ protected function tearDown(): void
{
parent::tearDown();

$this->database()->table('discussions')->whereIn('id', [1, 2])->delete();
$this->database()->table('posts')->whereIn('id', [1, 2])->delete();
$this->database()->table('discussions')->whereIn('id', [1, 2, 3])->delete();
$this->database()->table('posts')->whereIn('id', [1, 2, 3, 4])->delete();
}

/**
Expand All @@ -74,8 +77,7 @@ public function can_search_for_word_in_post()
return $row['id'];
}, $data['data']);

// Order-independent comparison
$this->assertEquals(['3'], $ids, 'IDs do not match');
$this->assertEquals(['2', '3'], $ids, 'IDs do not match');
}

/**
Expand All @@ -96,8 +98,7 @@ public function ignores_non_word_characters_when_searching()
return $row['id'];
}, $data['data']);

// Order-independent comparison
$this->assertEquals(['3'], $ids, 'IDs do not match');
$this->assertEquals(['2', '3'], $ids, 'IDs do not match');
}

/**
Expand Down