mirror of
https://github.com/discourse/discourse.git
synced 2026-08-09 12:38:21 -05:00
DEV: Improve site setting search ranking (#32461)
This change does a couple of things. Change in behaviour: - Matches on the site setting name and keywords now get a higher ranking in search results. - When counting gaps for ranking fuzzy matches, we no consider the smallest number of gaps. Code organization: - Extract a separate SiteSettingMatcher object for the matching logic. - Flatten some conditionals to make control flow clearer. - Higher weight now means higher in search results.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { bind } from "discourse/lib/decorators";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import SiteSettingMatcher from "admin/lib/site-setting-matcher";
|
||||
|
||||
export default class SiteSettingFilter {
|
||||
constructor(siteSettings) {
|
||||
@@ -53,62 +54,55 @@ export default class SiteSettingFilter {
|
||||
matchesGroupedByCategory.push(all);
|
||||
}
|
||||
|
||||
const strippedQuery = filter.replace(/[^a-z0-9]/gi, "");
|
||||
let fuzzyRegex;
|
||||
let fuzzyRegexGaps;
|
||||
|
||||
if (strippedQuery.length > 2) {
|
||||
fuzzyRegex = new RegExp(strippedQuery.split("").join(".*"), "i");
|
||||
fuzzyRegexGaps = new RegExp(strippedQuery.split("").join("(.*)"), "i");
|
||||
}
|
||||
|
||||
this.siteSettings.forEach((settingsCategory) => {
|
||||
let fuzzyMatches = [];
|
||||
|
||||
const siteSettings = settingsCategory.siteSettings.filter((item) => {
|
||||
if (opts.onlyOverridden && !item.get("overridden")) {
|
||||
return false;
|
||||
}
|
||||
if (pluginFilter && item.plugin !== pluginFilter) {
|
||||
return false;
|
||||
}
|
||||
if (filter) {
|
||||
const setting = item.get("setting").toLowerCase();
|
||||
let filterResult =
|
||||
setting.includes(filter) ||
|
||||
setting.replace(/_/g, " ").includes(filter) ||
|
||||
item.get("description").toLowerCase().includes(filter) ||
|
||||
(item.get("keywords") || []).any((keyword) =>
|
||||
keyword
|
||||
.replace(/_/g, " ")
|
||||
.toLowerCase()
|
||||
.includes(filter.replace(/_/g, " "))
|
||||
) ||
|
||||
(item.get("value") || "").toString().toLowerCase().includes(filter);
|
||||
if (!filterResult && fuzzyRegex && fuzzyRegex.test(setting)) {
|
||||
// Tightens up fuzzy search results a bit.
|
||||
const fuzzySearchLimiter = 25;
|
||||
const strippedSetting = setting.replace(/[^a-z0-9]/gi, "");
|
||||
if (
|
||||
strippedSetting.length <=
|
||||
strippedQuery.length + fuzzySearchLimiter
|
||||
) {
|
||||
const gapResult = strippedSetting.match(fuzzyRegexGaps);
|
||||
if (gapResult) {
|
||||
item.weight = gapResult.filter((gap) => gap !== "").length;
|
||||
}
|
||||
fuzzyMatches.push(item);
|
||||
}
|
||||
}
|
||||
return filterResult;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
const siteSettings = settingsCategory.siteSettings.filter(
|
||||
(siteSetting) => {
|
||||
siteSetting.weight = 0;
|
||||
|
||||
if (fuzzyMatches.length > 0) {
|
||||
siteSettings.pushObjects(fuzzyMatches);
|
||||
}
|
||||
if (opts.onlyOverridden && !siteSetting.get("overridden")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pluginFilter && siteSetting.plugin !== pluginFilter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!filter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const matcher = new SiteSettingMatcher(filter, siteSetting);
|
||||
|
||||
if (matcher.isNameMatch) {
|
||||
siteSetting.weight = 10;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (matcher.isKeywordMatch) {
|
||||
siteSetting.weight = 5;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (matcher.isDescriptionMatch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (matcher.isValueMatch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (matcher.isFuzzyNameMatch) {
|
||||
siteSetting.weight += matcher.matchStrength;
|
||||
fuzzyMatches.push(siteSetting);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
if (siteSettings.length > 0) {
|
||||
matches.pushObjects(siteSettings);
|
||||
@@ -138,9 +132,9 @@ export default class SiteSettingFilter {
|
||||
@bind
|
||||
sortSettings(settings) {
|
||||
// Sort the site settings so that fuzzy results are at the bottom
|
||||
// and ordered by their gap count asc.
|
||||
// and ordered by their match strength.
|
||||
return settings.sort((a, b) => {
|
||||
return (a.weight || 0) - (b.weight || 0);
|
||||
return (b.weight || 0) - (a.weight || 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
export default class SiteSettingMatcher {
|
||||
constructor(filter, siteSetting) {
|
||||
this.filter = filter;
|
||||
this.siteSetting = siteSetting;
|
||||
this.strippedQuery = filter.replace(/[^a-z0-9]/gi, "");
|
||||
this.fuzzyRegex = new RegExp(this.strippedQuery.split("").join(".*"), "i");
|
||||
this.fuzzyRegexGaps = new RegExp(
|
||||
".*" + this.strippedQuery.split("").join("(.*)"),
|
||||
"i"
|
||||
);
|
||||
this.matchStrength = 0;
|
||||
}
|
||||
|
||||
get isNameMatch() {
|
||||
const name = this.siteSetting.setting.toLowerCase();
|
||||
|
||||
return (
|
||||
name.includes(this.filter) ||
|
||||
name.replace(/_/g, " ").includes(this.filter)
|
||||
);
|
||||
}
|
||||
|
||||
get isKeywordMatch() {
|
||||
return (this.siteSetting.keywords || []).any((keyword) =>
|
||||
keyword
|
||||
.replace(/_/g, " ")
|
||||
.toLowerCase()
|
||||
.includes(this.filter.replace(/_/g, " "))
|
||||
);
|
||||
}
|
||||
|
||||
get isDescriptionMatch() {
|
||||
return this.siteSetting.description.toLowerCase().includes(this.filter);
|
||||
}
|
||||
|
||||
get isValueMatch() {
|
||||
return (this.siteSetting.value || "")
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.includes(this.filter);
|
||||
}
|
||||
|
||||
get isFuzzyNameMatch() {
|
||||
const name = this.siteSetting.setting.toLowerCase();
|
||||
|
||||
if (this.strippedQuery.length < 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.fuzzyRegex.test(name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const fuzzySearchLimiter = 25;
|
||||
const strippedSetting = name.replace(/[^a-z0-9]/gi, "");
|
||||
|
||||
if (
|
||||
strippedSetting.length >
|
||||
this.strippedQuery.length + fuzzySearchLimiter
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const gapResult = strippedSetting.match(this.fuzzyRegexGaps);
|
||||
|
||||
if (gapResult) {
|
||||
// Discard empty gaps and disregard the full string match.
|
||||
const numberOfGaps = gapResult.filter((gap) => gap !== "").length - 1;
|
||||
|
||||
this.matchStrength -= numberOfGaps;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user