feat: make ngram min_gram configurable via admin setting
Adds a 'Minimum search query length' setting (1–4, default 3). The admin shows a warning when the analyzer or min-search-length have changed since the last index build.
This commit is contained in:
parent
4412583cd9
commit
f486292f15
|
|
@ -33,5 +33,6 @@ return [
|
|||
|
||||
(new Flarum\Settings())
|
||||
->default('blomstra-search.search-discussion-subjects', true)
|
||||
->default('blomstra-search.search-post-bodies', true),
|
||||
->default('blomstra-search.search-post-bodies', true)
|
||||
->default('blomstra-search.min-search-length', Commands\BuildCommand::DEFAULT_MIN_SEARCH_LENGTH),
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
(()=>{var t={n:a=>{var e=a&&a.__esModule?()=>a.default:()=>a;return t.d(e,{a:e}),e},d:(a,e)=>{for(var r in e)t.o(e,r)&&!t.o(a,r)&&Object.defineProperty(a,r,{enumerable:!0,get:e[r]})},o:(t,a)=>Object.prototype.hasOwnProperty.call(t,a),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},a={};(()=>{"use strict";function e(t,a){return e=Object.setPrototypeOf||function(t,a){return t.__proto__=a,t},e(t,a)}t.r(a);const r=flarum.core.compat["admin/app"];var n=t.n(r);const s=flarum.core.compat["common/extend"],i=flarum.core.compat["admin/components/DashboardPage"];var o=t.n(i);const l=flarum.core.compat["admin/components/DashboardWidget"];var c=t.n(l);const d=flarum.core.compat["common/components/Alert"];var p=t.n(d),u=function(t){var a,r;function s(){return t.apply(this,arguments)||this}r=t,(a=s).prototype=Object.create(r.prototype),a.prototype.constructor=a,e(a,r);var i=s.prototype;return i.className=function(){return"ReindexWarningWidget"},i.content=function(){return m(p(),{type:"warning",dismissible:!1,icon:"fas fa-exclamation-triangle",title:n().translator.trans("blomstra-search.admin.reindex-required.title")},n().translator.trans("blomstra-search.admin.reindex-required.detail"))},s}(c());n().initializers.add("blomstra-search",(function(){var t=n().data.settings["blomstra-search.active-index"],a=n().data.settings["blomstra-search.index-compatible"];t&&"v2"!==a&&(0,s.extend)(o().prototype,"availableWidgets",(function(t){t.add("blomstra-search-reindex",m(u),110)}));var e=new Map;["arabic","armenian","basque","bengali","brazilian","bulgarian","catalan","cjk","czech","danish","dutch","english","estonian","finnish","french","galician","german","greek","hindi","hungarian","indonesian","irish","italian","latvian","lithuanian","norwegian","persian","portuguese","romanian","russian","sorani","spanish","swedish","turkish","thai"].forEach((function(t){e.set(t,t)})),n().extensionData.for("blomstra-search").registerSetting({setting:"blomstra-search.elastic-endpoint",label:n().translator.trans("blomstra-search.admin.elastic-endpoint"),type:"input"}).registerSetting({setting:"blomstra-search.elastic-username",label:n().translator.trans("blomstra-search.admin.elastic-username"),type:"input"}).registerSetting({setting:"blomstra-search.elastic-password",label:n().translator.trans("blomstra-search.admin.elastic-password"),type:"password"}).registerSetting({setting:"blomstra-search.elastic-index",label:n().translator.trans("blomstra-search.admin.elastic-index"),default:"flarum",type:"input"}).registerSetting({setting:"blomstra-search.analyzer-language",label:n().translator.trans("blomstra-search.admin.analyzer.label"),help:n().translator.trans("blomstra-search.admin.analyzer.help"),type:"select",options:Object.fromEntries(e.entries()),default:"english"}).registerSetting({setting:"blomstra-search.search-discussion-subjects",label:n().translator.trans("blomstra-search.admin.search-discussion-subjects"),type:"switch"}).registerSetting({setting:"blomstra-search.search-post-bodies",label:n().translator.trans("blomstra-search.admin.search-post-bodies"),type:"switch"})}))})(),module.exports=a})();
|
||||
(()=>{var t={n:a=>{var e=a&&a.__esModule?()=>a.default:()=>a;return t.d(e,{a:e}),e},d:(a,e)=>{for(var r in e)t.o(e,r)&&!t.o(a,r)&&Object.defineProperty(a,r,{enumerable:!0,get:e[r]})},o:(t,a)=>Object.prototype.hasOwnProperty.call(t,a)};(()=>{"use strict";function a(t,e){return a=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,a){return t.__proto__=a,t},a(t,e)}const e=flarum.core.compat["admin/app"];var r=t.n(e);const n=flarum.core.compat["common/extend"],s=flarum.core.compat["admin/components/DashboardPage"];var i=t.n(s);const o=flarum.core.compat["admin/components/DashboardWidget"];var l=t.n(o);const c=flarum.core.compat["common/components/Alert"];var h=t.n(c),d=function(t){var e,n;function s(){return t.apply(this,arguments)||this}n=t,(e=s).prototype=Object.create(n.prototype),e.prototype.constructor=e,a(e,n);var i=s.prototype;return i.className=function(){return"ReindexWarningWidget"},i.content=function(){return m(h(),{type:"warning",dismissible:!1,icon:"fas fa-exclamation-triangle",title:r().translator.trans("blomstra-search.admin.reindex-required.title")},r().translator.trans("blomstra-search.admin.reindex-required.detail"))},s}(l());r().initializers.add("blomstra-search",function(){var t=r().data.settings["blomstra-search.active-index"],a=r().data.settings["blomstra-search.index-compatible"];t&&"v2"!==a&&(0,n.extend)(i().prototype,"availableWidgets",function(t){t.add("blomstra-search-reindex",m(d),110)});var e=new Map;["arabic","armenian","basque","bengali","brazilian","bulgarian","catalan","cjk","czech","danish","dutch","english","estonian","finnish","french","galician","german","greek","hindi","hungarian","indonesian","irish","italian","latvian","lithuanian","norwegian","persian","portuguese","romanian","russian","sorani","spanish","swedish","turkish","thai"].forEach(function(t){e.set(t,t)}),r().extensionData.for("blomstra-search").registerSetting(function(){var a=r().data.settings["blomstra-search.indexed-analyzer"];if(!t||!a)return null;var e=this.setting("blomstra-search.analyzer-language")()||"english",n=this.setting("blomstra-search.min-search-length")()||String(3),s=String(r().data.settings["blomstra-search.indexed-min-search-length"]||3);return e===a&&n===s?null:m(h(),{type:"warning",dismissible:!1,icon:"fas fa-exclamation-triangle"},r().translator.trans("blomstra-search.admin.index-settings-changed"))}).registerSetting({setting:"blomstra-search.elastic-endpoint",label:r().translator.trans("blomstra-search.admin.elastic-endpoint"),type:"input"}).registerSetting({setting:"blomstra-search.elastic-username",label:r().translator.trans("blomstra-search.admin.elastic-username"),type:"input"}).registerSetting({setting:"blomstra-search.elastic-password",label:r().translator.trans("blomstra-search.admin.elastic-password"),type:"password"}).registerSetting({setting:"blomstra-search.elastic-index",label:r().translator.trans("blomstra-search.admin.elastic-index"),default:"flarum",type:"input"}).registerSetting({setting:"blomstra-search.analyzer-language",label:r().translator.trans("blomstra-search.admin.analyzer.label"),help:r().translator.trans("blomstra-search.admin.analyzer.help"),type:"select",options:Object.fromEntries(e.entries()),default:"english"}).registerSetting({setting:"blomstra-search.search-discussion-subjects",label:r().translator.trans("blomstra-search.admin.search-discussion-subjects"),type:"switch"}).registerSetting({setting:"blomstra-search.search-post-bodies",label:r().translator.trans("blomstra-search.admin.search-post-bodies"),type:"switch"}).registerSetting({setting:"blomstra-search.min-search-length",label:r().translator.trans("blomstra-search.admin.min-search-length.label"),help:r().translator.trans("blomstra-search.admin.min-search-length.help"),type:"select",options:{1:"1",2:"2",3:"3",4:"4"},default:String(3)})})})(),module.exports={}})();
|
||||
//# sourceMappingURL=admin.js.map
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -78,6 +78,25 @@ app.initializers.add('blomstra-search', () => {
|
|||
|
||||
app.extensionData
|
||||
.for('blomstra-search')
|
||||
.registerSetting(function (this: any) {
|
||||
// No index yet, or first build predates this tracking — stay silent.
|
||||
const indexedAnalyzer = app.data.settings['blomstra-search.indexed-analyzer'];
|
||||
if (!activeIndex || !indexedAnalyzer) return null;
|
||||
|
||||
const currentAnalyzer = this.setting('blomstra-search.analyzer-language')() || 'english';
|
||||
const currentMinLength = this.setting('blomstra-search.min-search-length')();
|
||||
const indexedMinLength = String(
|
||||
app.data.settings['blomstra-search.indexed-min-search-length'] || app.data.settings['blomstra-search.min-search-length']
|
||||
);
|
||||
|
||||
if (currentAnalyzer === indexedAnalyzer && currentMinLength === indexedMinLength) return null;
|
||||
|
||||
return m(
|
||||
Alert,
|
||||
{ type: 'warning', dismissible: false, icon: 'fas fa-exclamation-triangle' },
|
||||
app.translator.trans('blomstra-search.admin.index-settings-changed')
|
||||
);
|
||||
})
|
||||
.registerSetting({
|
||||
setting: 'blomstra-search.elastic-endpoint',
|
||||
label: app.translator.trans('blomstra-search.admin.elastic-endpoint'),
|
||||
|
|
@ -116,5 +135,13 @@ app.initializers.add('blomstra-search', () => {
|
|||
setting: 'blomstra-search.search-post-bodies',
|
||||
label: app.translator.trans('blomstra-search.admin.search-post-bodies'),
|
||||
type: 'switch',
|
||||
})
|
||||
.registerSetting({
|
||||
setting: 'blomstra-search.min-search-length',
|
||||
label: app.translator.trans('blomstra-search.admin.min-search-length.label'),
|
||||
help: app.translator.trans('blomstra-search.admin.min-search-length.help'),
|
||||
type: 'select',
|
||||
options: { '1': '1', '2': '2', '3': '3', '4': '4' },
|
||||
default: app.data.settings['blomstra-search.min-search-length'],
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,6 +9,15 @@ import DiscussionsSearchSource from './SearchSources/DiscussionsSearchSource';
|
|||
import extendDiscussionState from './PaginatedListStates/extendDiscussionState';
|
||||
|
||||
app.initializers.add('blomstra-search', () => {
|
||||
const minLength = parseInt(app.data.settings['blomstra-search.min-search-length'] || String(Search.MIN_SEARCH_LEN), 10);
|
||||
if (minLength !== Search.MIN_SEARCH_LEN) {
|
||||
// Flarum provides no extension point for MIN_SEARCH_LEN, so we overwrite the
|
||||
// static property directly. TypeScript `readonly` is compile-time only — at
|
||||
// runtime this is a plain property assignment and is safe as long as no code
|
||||
// reads MIN_SEARCH_LEN before this initializer runs.
|
||||
(Search as any).MIN_SEARCH_LEN = minLength;
|
||||
}
|
||||
|
||||
extend(Search.prototype, 'sourceItems', function (this: Search<SearchAttrs>, items: ItemList<SearchSource>) {
|
||||
items.replace('discussions', new DiscussionsSearchSource());
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ blomstra-search:
|
|||
label: Analyzer language
|
||||
help: |
|
||||
The analyzer makes search understand stop words and undertakes language
|
||||
specific improvements for indexing.
|
||||
specific improvements for indexing. Changing this requires a full index rebuild to take effect.
|
||||
index-settings-changed: "Search index settings have changed. Run: php flarum blomstra:search:index build"
|
||||
reindex-required:
|
||||
title: Search index rebuild required
|
||||
detail: "The search index is not compatible with this version of the extension. Run: php flarum blomstra:search:index build"
|
||||
|
|
@ -16,3 +17,9 @@ blomstra-search:
|
|||
search-post-bodies: Search inside comments
|
||||
match-sentences: Match search term against full sentence
|
||||
match-words: Match search term against full words
|
||||
min-search-length:
|
||||
label: Minimum search query length
|
||||
help: |
|
||||
Minimum number of characters required before a search is triggered. Lower this to 1 or 2
|
||||
for CJK (Chinese, Japanese, Korean) communities where single characters carry full meaning.
|
||||
Changing this requires a full index rebuild to take effect.
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ class BuildCommand extends Command
|
|||
*/
|
||||
public const INDEX_COMPAT_VERSION = 'v2';
|
||||
|
||||
/** Matches Flarum's Search::MIN_SEARCH_LEN — the default minimum query length. */
|
||||
public const DEFAULT_MIN_SEARCH_LENGTH = 3;
|
||||
|
||||
protected $signature = 'blomstra:search:index
|
||||
{action? : build | promote | rollback | discard | mapping | fill}
|
||||
{--fresh}
|
||||
|
|
@ -248,6 +251,7 @@ HELP;
|
|||
$settings->set('blomstra-search.active-index', $staging);
|
||||
$settings->set('blomstra-search.staging-index', null);
|
||||
$settings->set('blomstra-search.index-compatible', self::INDEX_COMPAT_VERSION);
|
||||
$this->saveIndexedConfig($client, $settings, $staging);
|
||||
}
|
||||
|
||||
protected function runRollback(Client $client, string $alias, SettingsRepositoryInterface $settings): void
|
||||
|
|
@ -274,6 +278,7 @@ HELP;
|
|||
|
||||
$settings->set('blomstra-search.active-index', $backup);
|
||||
$settings->set('blomstra-search.backup-index', null);
|
||||
$this->saveIndexedConfig($client, $settings, $backup);
|
||||
}
|
||||
|
||||
protected function runDiscard(Client $client, SettingsRepositoryInterface $settings): void
|
||||
|
|
@ -416,7 +421,7 @@ HELP;
|
|||
|
||||
$client->indices()->create([
|
||||
'index' => $concrete,
|
||||
'body' => ['settings' => $this->indexSettings($settings)],
|
||||
'body' => ['settings' => $this->buildIndexSettings($settings)],
|
||||
]);
|
||||
|
||||
$client->indices()->putAlias(['index' => $concrete, 'name' => $alias]);
|
||||
|
|
@ -424,6 +429,8 @@ HELP;
|
|||
$settings->set('blomstra-search.active-index', $concrete);
|
||||
$settings->set('blomstra-search.index-compatible', self::INDEX_COMPAT_VERSION);
|
||||
|
||||
$this->saveIndexedConfig($client, $settings, $concrete);
|
||||
|
||||
foreach ($seeders as $seeder) {
|
||||
$this->setContinueAt($settings, $seeder->type(), null);
|
||||
}
|
||||
|
|
@ -466,7 +473,7 @@ HELP;
|
|||
|
||||
$client->indices()->create([
|
||||
'index' => $staging,
|
||||
'body' => ['settings' => $this->indexSettings($settings)],
|
||||
'body' => ['settings' => $this->buildIndexSettings($settings)],
|
||||
]);
|
||||
|
||||
$settings->set('blomstra-search.staging-index', $staging);
|
||||
|
|
@ -480,14 +487,40 @@ HELP;
|
|||
return $staging;
|
||||
}
|
||||
|
||||
protected function indexSettings(SettingsRepositoryInterface $settings): array
|
||||
/**
|
||||
* Persist the analysis config that is actually live in ES for the given index.
|
||||
* Reading from ES (rather than from Flarum settings) means rollbacks are also
|
||||
* covered: the stored values always reflect the index that is currently aliased,
|
||||
* not the settings at the time the command ran.
|
||||
*/
|
||||
protected function saveIndexedConfig(Client $client, SettingsRepositoryInterface $settings, string $indexName): void
|
||||
{
|
||||
$response = $client->indices()->getSettings(['index' => $indexName]);
|
||||
$analysis = Arr::get($response, "$indexName.settings.index.analysis", []);
|
||||
|
||||
$analyzer = Arr::get($analysis, 'analyzer.flarum_analyzer.type', 'english');
|
||||
$minGram = (int) Arr::get($analysis, 'filter.partial_search_filter.min_gram', self::DEFAULT_MIN_SEARCH_LENGTH);
|
||||
|
||||
$settings->set('blomstra-search.indexed-analyzer', $analyzer);
|
||||
$settings->set('blomstra-search.indexed-min-search-length', $minGram);
|
||||
}
|
||||
|
||||
protected function buildIndexSettings(SettingsRepositoryInterface $settings): array
|
||||
{
|
||||
$language = $settings->get('blomstra-search.analyzer-language') ?: 'english';
|
||||
$minGram = max(1, (int) ($settings->get('blomstra-search.min-search-length') ?: self::DEFAULT_MIN_SEARCH_LENGTH));
|
||||
$maxGram = 10;
|
||||
|
||||
if ($minGram >= $maxGram) {
|
||||
throw new \InvalidArgumentException("min_gram ($minGram) must be less than max_gram ($maxGram).");
|
||||
}
|
||||
|
||||
return [
|
||||
'index.max_ngram_diff' => 7,
|
||||
'index.max_ngram_diff' => $maxGram - $minGram,
|
||||
'analysis' => [
|
||||
'analyzer' => [
|
||||
'flarum_analyzer' => [
|
||||
'type' => $settings->get('blomstra-search.analyzer-language') ?: 'english',
|
||||
'type' => $language,
|
||||
],
|
||||
'flarum_analyzer_partial' => [
|
||||
'type' => 'custom',
|
||||
|
|
@ -498,8 +531,8 @@ HELP;
|
|||
'filter' => [
|
||||
'partial_search_filter' => [
|
||||
'type' => 'ngram',
|
||||
'min_gram' => 3,
|
||||
'max_gram' => 10,
|
||||
'min_gram' => $minGram,
|
||||
'max_gram' => $maxGram,
|
||||
'token_chars' => ['letter', 'digit', 'symbol'],
|
||||
],
|
||||
],
|
||||
|
|
|
|||
Loading…
Reference in New Issue