feat: index and sync view_count for fof/discussion-views sort support
- Rename SavingJob to UpdateSearchJob - Add ViewsSearchJob for lightweight partial view_count updates - Add probabilistic sync throttle in DiscussionSeeder::viewingOn() to avoid an ES update on every page view - Add view_count to mapping, DiscussionSeeder::toDocument(), and translateSort - Log a warning and skip instead of 500ing on unknown sort fields
This commit is contained in:
parent
ff2a252e99
commit
80d66c98c4
|
|
@ -48,6 +48,7 @@ class SearchController extends ListDiscussionsController
|
|||
'lastPostedAt' => 'updated_at',
|
||||
'createdAt' => 'created_at',
|
||||
'commentCount' => 'comment_count',
|
||||
'view_count' => 'view_count',
|
||||
];
|
||||
|
||||
protected Collection $searchers;
|
||||
|
|
@ -107,9 +108,19 @@ class SearchController extends ListDiscussionsController
|
|||
$this->addFilters($filterQuery, $actor, $filters)
|
||||
);
|
||||
|
||||
$knownSortFields = array_merge(array_values($this->translateSort), ['rawId']);
|
||||
|
||||
foreach ($this->extractSort($request) as $field => $direction) {
|
||||
$field = $this->translateSort[$field] ?? $field;
|
||||
$builder->addSort(new Sort($field, $direction));
|
||||
$translated = $this->translateSort[$field] ?? $field;
|
||||
|
||||
if (!in_array($translated, $knownSortFields)) {
|
||||
resolve(\Psr\Log\LoggerInterface::class)->warning(
|
||||
"blomstra/search: unknown sort field \"{$field}\", ignoring."
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$builder->addSort(new Sort($translated, $direction));
|
||||
}
|
||||
|
||||
$response = $builder->search();
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
namespace Blomstra\Search\Commands;
|
||||
|
||||
use Blomstra\Search\Jobs\Job;
|
||||
use Blomstra\Search\Jobs\SavingJob;
|
||||
use Blomstra\Search\Jobs\UpdateSearchJob;
|
||||
use Blomstra\Search\Seeders\Seeder;
|
||||
use Elasticsearch\Client;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
|
|
@ -71,6 +71,7 @@ class BuildCommand extends Command
|
|||
'recipient_groups' => ['type' => 'integer'],
|
||||
'recipient_users' => ['type' => 'integer'],
|
||||
'comment_count' => ['type' => 'integer'],
|
||||
'view_count' => ['type' => 'integer'],
|
||||
],
|
||||
];
|
||||
|
||||
|
|
@ -181,7 +182,7 @@ class BuildCommand extends Command
|
|||
}
|
||||
|
||||
if ($collection->isNotEmpty()) {
|
||||
$queue->pushOn(Job::$onQueue, new SavingJob($collection, $seeder));
|
||||
$queue->pushOn(Job::$onQueue, new UpdateSearchJob($collection, $seeder));
|
||||
}
|
||||
|
||||
$this->info("IDs {$rangeFrom}–{$rangeTo} | type: {$seeder->type()} | queued: {$collection->count()}.");
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use Elasticsearch\Client;
|
|||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class SavingJob extends Job
|
||||
class UpdateSearchJob extends Job
|
||||
{
|
||||
public function handle(Client $client)
|
||||
{
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of blomstra/search.
|
||||
*
|
||||
* Copyright (c) 2022 Blomstra Ltd.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE.md
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Blomstra\Search\Jobs;
|
||||
|
||||
use Elasticsearch\Client;
|
||||
use Flarum\Api\Serializer\DiscussionSerializer;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Queue\AbstractJob;
|
||||
|
||||
class ViewsSearchJob extends AbstractJob
|
||||
{
|
||||
protected string $index;
|
||||
|
||||
public function __construct(protected int $discussionId)
|
||||
{
|
||||
$this->index = resolve('blomstra.search.elastic_index');
|
||||
|
||||
if (Job::$onQueue) {
|
||||
$this->onQueue(Job::$onQueue);
|
||||
}
|
||||
}
|
||||
|
||||
public function handle(Client $client): void
|
||||
{
|
||||
$discussion = Discussion::find($this->discussionId);
|
||||
|
||||
if (!$discussion) {
|
||||
return;
|
||||
}
|
||||
|
||||
$type = resolve(DiscussionSerializer::class)->getType(new Discussion());
|
||||
|
||||
$client->update([
|
||||
'index' => $this->index,
|
||||
'id' => "$type:{$this->discussionId}",
|
||||
'retry_on_conflict' => 3,
|
||||
'ignore' => [404],
|
||||
'body' => [
|
||||
'doc' => ['view_count' => (int) $discussion->view_count],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,8 @@ namespace Blomstra\Search;
|
|||
|
||||
use Blomstra\Search\Jobs\DeletingJob;
|
||||
use Blomstra\Search\Jobs\Job;
|
||||
use Blomstra\Search\Jobs\SavingJob;
|
||||
use Blomstra\Search\Jobs\UpdateSearchJob;
|
||||
use Blomstra\Search\Jobs\ViewsSearchJob;
|
||||
use Elasticsearch\Client as Elastic;
|
||||
use Elasticsearch\ClientBuilder;
|
||||
use Flarum\Api\Client;
|
||||
|
|
@ -108,12 +109,18 @@ class Provider extends AbstractServiceProvider
|
|||
/** @var string|Seeders\Seeder $seeder */
|
||||
foreach ($seeders as $seeder) {
|
||||
$seeder::savingOn($events, function ($model) use ($queue, $seeder) {
|
||||
$queue->pushOn(Job::$onQueue, new SavingJob(Collection::make([$model]), $seeder));
|
||||
$queue->pushOn(Job::$onQueue, new UpdateSearchJob(Collection::make([$model]), $seeder));
|
||||
});
|
||||
|
||||
$seeder::deletingOn($events, function ($model) use ($queue, $seeder) {
|
||||
$queue->pushOn(Job::$onQueue, new DeletingJob(Collection::make([$model]), $seeder));
|
||||
});
|
||||
|
||||
if (method_exists($seeder, 'viewingOn')) {
|
||||
$seeder::viewingOn($events, function (int $discussionId) use ($queue) {
|
||||
$queue->pushOn(Job::$onQueue, new ViewsSearchJob($discussionId));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ use Blomstra\Search\Save\Document;
|
|||
use Flarum\Api\Serializer\DiscussionSerializer;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Discussion\Event as Core;
|
||||
use Flarum\Extension\ExtensionManager;
|
||||
use FoF\Byobu\Events as Byobu;
|
||||
use FoF\DiscussionViews\Events\DiscussionWasViewed;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
|
@ -58,6 +60,27 @@ class DiscussionSeeder extends Seeder
|
|||
});
|
||||
}
|
||||
|
||||
public static function viewingOn(Dispatcher $events, callable $callable): void
|
||||
{
|
||||
if (!resolve(ExtensionManager::class)->isEnabled('fof-discussion-views')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$events->listen(DiscussionWasViewed::class, function (DiscussionWasViewed $event) use ($callable) {
|
||||
$viewCount = $event->discussion->view_count;
|
||||
|
||||
$shouldSync = match (true) {
|
||||
$viewCount < 15 => true,
|
||||
$viewCount < 100 => rand(1, 3) === 1,
|
||||
default => rand(1, 19) === 1,
|
||||
};
|
||||
|
||||
if ($shouldSync) {
|
||||
$callable($event->discussion->id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static function deletingOn(Dispatcher $events, callable $callable)
|
||||
{
|
||||
$events->listen([
|
||||
|
|
@ -107,6 +130,10 @@ class DiscussionSeeder extends Seeder
|
|||
$document['is_sticky'] = (bool) $model->is_sticky;
|
||||
}
|
||||
|
||||
if ($this->extensionEnabled('fof-discussion-views')) {
|
||||
$document['view_count'] = (int) ($model->view_count ?? 0);
|
||||
}
|
||||
|
||||
return $document;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue