Added tag values as part of the indexed search terms
This allows finding content via tag name/values when just searching using normal seach terms. Added testing to cover. Related to #1577
This commit is contained in:
parent
f28daa01d9
commit
99587a0be6
6 changed files with 52 additions and 8 deletions
|
@ -6,6 +6,12 @@ use BookStack\Model;
|
|||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $value
|
||||
* @property int $order
|
||||
*/
|
||||
class Tag extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
|
|
@ -24,7 +24,7 @@ class Book extends Entity implements HasCoverImage
|
|||
{
|
||||
use HasFactory;
|
||||
|
||||
public $searchFactor = 1.5;
|
||||
public $searchFactor = 1.2;
|
||||
|
||||
protected $fillable = ['name', 'description'];
|
||||
protected $hidden = ['restricted', 'pivot', 'image_id', 'deleted_at'];
|
||||
|
|
|
@ -13,7 +13,7 @@ class Bookshelf extends Entity implements HasCoverImage
|
|||
|
||||
protected $table = 'bookshelves';
|
||||
|
||||
public $searchFactor = 1.5;
|
||||
public $searchFactor = 1.2;
|
||||
|
||||
protected $fillable = ['name', 'description', 'image_id'];
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ class Chapter extends BookChild
|
|||
{
|
||||
use HasFactory;
|
||||
|
||||
public $searchFactor = 1.5;
|
||||
public $searchFactor = 1.2;
|
||||
|
||||
protected $fillable = ['name', 'description', 'priority', 'book_id'];
|
||||
protected $hidden = ['restricted', 'pivot', 'deleted_at'];
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace BookStack\Entities\Tools;
|
||||
|
||||
use BookStack\Actions\Tag;
|
||||
use BookStack\Entities\EntityProvider;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\Page;
|
||||
|
@ -84,6 +85,7 @@ class SearchIndex
|
|||
|
||||
$entityModel->newQuery()
|
||||
->select($selectFields)
|
||||
->with(['tags:id,name,value,entity_id,entity_type'])
|
||||
->chunk($chunkSize, $chunkCallback);
|
||||
}
|
||||
}
|
||||
|
@ -154,6 +156,30 @@ class SearchIndex
|
|||
return $scoresByTerm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a scored term map from the given set of entity tags.
|
||||
*
|
||||
* @param Tag[] $tags
|
||||
*
|
||||
* @returns array<string, int>
|
||||
*/
|
||||
protected function generateTermScoreMapFromTags(array $tags): array
|
||||
{
|
||||
$scoreMap = [];
|
||||
$names = [];
|
||||
$values = [];
|
||||
|
||||
foreach($tags as $tag) {
|
||||
$names[] = $tag->name;
|
||||
$values[] = $tag->value;
|
||||
}
|
||||
|
||||
$nameMap = $this->generateTermScoreMapFromText(implode(' ', $names), 3);
|
||||
$valueMap = $this->generateTermScoreMapFromText(implode(' ', $values), 5);
|
||||
|
||||
return $this->mergeTermScoreMaps($nameMap, $valueMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* For the given text, return an array where the keys are the unique term words
|
||||
* and the values are the frequency of that term.
|
||||
|
@ -186,6 +212,7 @@ class SearchIndex
|
|||
protected function entityToTermDataArray(Entity $entity): array
|
||||
{
|
||||
$nameTermsMap = $this->generateTermScoreMapFromText($entity->name, 40 * $entity->searchFactor);
|
||||
$tagTermsMap = $this->generateTermScoreMapFromTags($entity->tags->all());
|
||||
|
||||
if ($entity instanceof Page) {
|
||||
$bodyTermsMap = $this->generateTermScoreMapFromHtml($entity->html);
|
||||
|
@ -193,7 +220,7 @@ class SearchIndex
|
|||
$bodyTermsMap = $this->generateTermScoreMapFromText($entity->description, $entity->searchFactor);
|
||||
}
|
||||
|
||||
$mergedScoreMap = $this->mergeTermScoreMaps($nameTermsMap, $bodyTermsMap);
|
||||
$mergedScoreMap = $this->mergeTermScoreMaps($nameTermsMap, $bodyTermsMap, $tagTermsMap);
|
||||
|
||||
$dataArray = [];
|
||||
$entityId = $entity->id;
|
||||
|
|
|
@ -334,8 +334,7 @@ class EntitySearchTest extends TestCase
|
|||
<h6>TermG</h6>
|
||||
']);
|
||||
|
||||
$entityRelationCols = ['entity_id' => $page->id, 'entity_type' => 'BookStack\\Page'];
|
||||
$scoreByTerm = SearchTerm::query()->where($entityRelationCols)->pluck('score', 'term');
|
||||
$scoreByTerm = $page->searchTerms()->pluck('score', 'term');
|
||||
|
||||
$this->assertEquals(1, $scoreByTerm->get('TermA'));
|
||||
$this->assertEquals(10, $scoreByTerm->get('TermB'));
|
||||
|
@ -354,10 +353,22 @@ class EntitySearchTest extends TestCase
|
|||
<p>TermA</p>
|
||||
']);
|
||||
|
||||
$entityRelationCols = ['entity_id' => $page->id, 'entity_type' => 'BookStack\\Page'];
|
||||
$scoreByTerm = SearchTerm::query()->where($entityRelationCols)->pluck('score', 'term');
|
||||
$scoreByTerm = $page->searchTerms()->pluck('score', 'term');
|
||||
|
||||
// Scores 40 for being in the name then 1 for being in the content
|
||||
$this->assertEquals(41, $scoreByTerm->get('TermA'));
|
||||
}
|
||||
|
||||
public function test_tag_names_and_values_are_indexed_for_search()
|
||||
{
|
||||
$page = $this->newPage(['name' => 'PageA', 'html' => '<p>content</p>', 'tags' => [
|
||||
['name' => 'Animal', 'value' => 'MeowieCat'],
|
||||
['name' => 'SuperImportant'],
|
||||
]]);
|
||||
|
||||
$scoreByTerm = $page->searchTerms()->pluck('score', 'term');
|
||||
$this->assertEquals(5, $scoreByTerm->get('MeowieCat'));
|
||||
$this->assertEquals(3, $scoreByTerm->get('Animal'));
|
||||
$this->assertEquals(3, $scoreByTerm->get('SuperImportant'));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue