Find a knowledge base article by words from different fields

This commit is contained in:
2026-08-21 09:40:14 -04:00
parent 44432c9b5a
commit 980b2e8be3
3 changed files with 198 additions and 25 deletions

View File

@@ -43,6 +43,39 @@ def _visible_articles():
KnowledgeBase.appid.notin_(retired)))
def _search_clause(search):
"""Articles containing EVERY word of the search, each word in the title, the
keywords, or the topic's name, in any order.
A single `ilike('%CMM Community%')` needs the whole phrase contiguous in ONE
field. An article tagged 'community' under the CMM topic has 'CMM' only in
its topic's name and 'Community' only in its keywords, so it matched nothing
and the search came back empty. Splitting the query and requiring each word
somewhere is what people mean when they type two words.
Kept in step with `_word_match` in shopdb/core/api/search.py, which does the
same job for global search. Two copies because the core helper is internal
and not on the plugin contract surface (ADR-001).
Active applications only for the topic. A retired application is not a topic
anyone should be offered: matching its name surfaced its articles and printed
the retired app as their subject, which reads as though it were still in
service.
"""
words = [w for w in search.split() if w] or ['']
return db.and_(*[
db.or_(
KnowledgeBase.shortdescription.ilike(f'%{w}%'),
KnowledgeBase.keywords.ilike(f'%{w}%'),
KnowledgeBase.appid.in_(
db.session.query(Application.appid).filter(
Application.appname.ilike(f'%{w}%'),
Application.isactive.is_(True))),
)
for w in words
])
@knowledgebase_bp.route('', methods=['GET'])
@jwt_required(optional=True)
def list_articles():
@@ -51,26 +84,11 @@ def list_articles():
query = _visible_articles()
# Search: title, keywords, and the topic (its Application's name). The topic
# is matched via an appid subquery instead of a join so it does not collide
# with the sort=='topic' join below; articles with no app just miss that
# clause and still match on title/keywords.
# The topic is matched via an appid subquery instead of a join so it does not
# collide with the sort=='topic' join below; articles with no app just miss
# that clause and still match on title/keywords.
if search := request.args.get('search'):
like = f'%{search}%'
# Active applications only. A retired application is not a topic anyone
# should be offered: matching its name surfaced its articles and printed
# the retired app as their subject, which reads as though it were still
# in service.
topic_appids = db.session.query(Application.appid).filter(
Application.appname.ilike(like),
Application.isactive.is_(True))
query = query.filter(
db.or_(
KnowledgeBase.shortdescription.ilike(like),
KnowledgeBase.keywords.ilike(like),
KnowledgeBase.appid.in_(topic_appids)
)
)
query = query.filter(_search_clause(search))
# Filter by topic/application
if appid := request.args.get('appid'):