]> BookStack Code Mirror - bookstack/commitdiff
Added tag values as part of the indexed search terms
authorDan Brown <redacted>
Fri, 12 Nov 2021 17:06:01 +0000 (17:06 +0000)
committerDan Brown <redacted>
Fri, 12 Nov 2021 17:06:01 +0000 (17:06 +0000)
This allows finding content via tag name/values when just searching
using normal seach terms.
Added testing to cover.

Related to #1577

app/Actions/Tag.php
app/Entities/Models/Book.php
app/Entities/Models/Bookshelf.php
app/Entities/Models/Chapter.php
app/Entities/Tools/SearchIndex.php
tests/Entity/EntitySearchTest.php

index db9328b7d59e0f4b2ebd74faf449a523bc3a1426..609c299ad887f2758c973ec197f1b572f1a11eb2 100644 (file)
@@ -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;
index 359f7961c8721250771d6ef9544bc7c663353feb..735d25a991e0835ccd7a7c8dbe2b1453d7972ee3 100644 (file)
@@ -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'];
index b426858c30682e5fb9490a57ef038e8af4afbe92..e4d9775b70b470813bd7562d49e04d87e2872934 100644 (file)
@@ -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'];
 
index f4d1a281d0889d7dfc9fc14556a5a43b44dac68f..224ded935048ef86e581376ca1bd380c43a4787e 100644 (file)
@@ -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'];
index bde5ef8606c79bc6ec40a80c4f2cd71c21802150..05de341f96089085a370a42a4d7a185e097944b4 100644 (file)
@@ -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;
index bd50a13ac6912806ac4a3846f5f488c3e87b2c94..08fabba0ca029c1ea1eb8707c189a51ee77c2bf9 100644 (file)
@@ -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'));
+    }
 }