From edc6007fb6c59b3699b87f54cf3cc1ef7a0763ff Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 31 Dec 2025 20:03:44 +0530 Subject: [PATCH 1/3] feat: search by instructor name from command palette --- frontend/src/pages/Search/Search.vue | 10 ++-- lms/command_palette.py | 21 +++++++- lms/sqlite.py | 77 +++++++++++++++++++++++++++- 3 files changed, 102 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/Search/Search.vue b/frontend/src/pages/Search/Search.vue index 2a0ee6ef..8ed88883 100644 --- a/frontend/src/pages/Search/Search.vue +++ b/frontend/src/pages/Search/Search.vue @@ -79,7 +79,10 @@
@@ -87,7 +90,8 @@ dayjs( result.published_on || result.start_date || - result.creation + result.creation || + result.modified ).format('DD MMM YYYY') }}
@@ -164,7 +168,7 @@ const generateSearchResults = () => { searchResults.value.push(item) }) }) - searchResults.value.sort((a, b) => b.score - a.score) + //searchResults.value.sort((a, b) => b.score - a.score) } } diff --git a/lms/command_palette.py b/lms/command_palette.py index 0a182cee..d9803a4d 100644 --- a/lms/command_palette.py +++ b/lms/command_palette.py @@ -34,12 +34,25 @@ def prepare_search_results(result): out = [] for key in groups: + groups[key] = remove_duplicates(groups[key]) + groups[key].sort(key=lambda x: x.get("modified"), reverse=True) out.append({"title": key, "items": groups[key]}) return out +def remove_duplicates(items): + seen = set() + unique_items = [] + for item in items: + if item["name"] not in seen: + seen.add(item["name"]) + unique_items.append(item) + return unique_items + + def can_access_course(course, roles): + print(course.get("name"), course.get("published")) if can_create_course(roles): return True elif course.get("published"): @@ -73,10 +86,14 @@ def get_instructor_info(doctype, record): instructors = frappe.get_all( "Course Instructor", filters={"parenttype": doctype, "parent": record.get("name")}, pluck="instructor" ) - instructor = record.get("author") if len(instructors): - instructor = instructors[0] + for ins in instructors: + if ins.split("@")[0] in record.get("content"): + instructor = ins + break + if not instructor: + instructor = instructors[0] return frappe.db.get_value( "User", diff --git a/lms/sqlite.py b/lms/sqlite.py index 36ad7a4a..4f35939a 100644 --- a/lms/sqlite.py +++ b/lms/sqlite.py @@ -2,7 +2,7 @@ from contextlib import suppress import frappe from frappe.search.sqlite_search import SQLiteSearch, SQLiteSearchIndexMissingError -from frappe.utils import nowdate +from frappe.utils import get_datetime, nowdate class LearningSearch(SQLiteSearch): @@ -17,6 +17,8 @@ class LearningSearch(SQLiteSearch): "status", "company_name", "creation", + "parent", + "parenttype", ], "tokenizer": "unicode61 remove_diacritics 2 tokenchars '-_'", } @@ -60,6 +62,16 @@ class LearningSearch(SQLiteSearch): {"modified": "creation"}, ], }, + "Course Instructor": { + "fields": [ + "name", + {"title": "instructor"}, + {"content": "instructor"}, + "parent", + "parenttype", + "modified", + ] + }, } DOCTYPE_FIELDS = { @@ -92,6 +104,12 @@ class LearningSearch(SQLiteSearch): "modified", "owner", ], + "Course Instructor": [ + "name", + "instructor", + "parent", + "parenttype", + ], } def build_index(self): @@ -103,6 +121,63 @@ class LearningSearch(SQLiteSearch): def get_search_filters(self): return {} + def prepare_document(self, doc): + document = super().prepare_document(doc) + if not document: + return None + + if doc.doctype == "Course Instructor": + instructor = frappe.db.get_value("User", doc.instructor, "full_name") + if doc.parenttype == "LMS Course": + details = frappe.db.get_value( + "LMS Course", + doc.parent, + ["name", "title", "description", "published_on", "modified", "published"], + as_dict=True, + ) + document["published_on"] = details.get("published_on") + elif doc.parenttype == "LMS Batch": + details = frappe.db.get_value( + "LMS Batch", + doc.parent, + ["name", "title", "batch_details as description", "start_date", "modified", "published"], + as_dict=True, + ) + document["start_date"] = details.get("start_date") + + if details: + document["doctype"] = doc.parenttype + document["name"] = doc.parent + document["title"] = self._process_content(details.title) + document["content"] = self._process_content( + f"Instructor: {instructor}\n{details.description}\n{doc.instructor}" + ) + document["modified"] = self.get_modified_date(details, doc.parenttype) + document["published"] = details.get("published", 0) + + else: + if not document.get("modified"): + document["modified"] = self.get_modified_date(doc, doc.doctype) + + return document + + def get_modified_date(self, details, doctype): + modified_value = None + if doctype == "LMS Course": + modified_value = details.get("published_on") + elif doctype == "LMS Batch": + modified_value = details.get("start_date") + + if not modified_value: + modified_value = frappe.db.get_value(doctype, details.name, "creation") + print(details.name, modified_value) + + modified_value = get_datetime(modified_value) + print(modified_value) + modified_value = modified_value.timestamp() + print(modified_value) + return modified_value + @SQLiteSearch.scoring_function def get_doctype_boost(self, row, query, query_words): doctype = row["doctype"] From 7a24a83d9e8db9a84946a8e4f1291622a8c409c7 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Thu, 1 Jan 2026 12:06:15 +0530 Subject: [PATCH 2/3] fix: improved search result sorting --- frontend/src/pages/Search/Search.vue | 14 ++- lms/command_palette.py | 23 +++-- lms/sqlite.py | 149 ++++++++++++++------------- 3 files changed, 104 insertions(+), 82 deletions(-) diff --git a/frontend/src/pages/Search/Search.vue b/frontend/src/pages/Search/Search.vue index 8ed88883..d14760d4 100644 --- a/frontend/src/pages/Search/Search.vue +++ b/frontend/src/pages/Search/Search.vue @@ -168,10 +168,22 @@ const generateSearchResults = () => { searchResults.value.push(item) }) }) - //searchResults.value.sort((a, b) => b.score - a.score) + sortResults() } } +const sortResults = () => { + searchResults.value.sort((a, b) => { + const dateA = new Date( + a.published_on || a.start_date || a.creation || a.modified + ).getTime() + const dateB = new Date( + b.published_on || b.start_date || b.creation || b.modified + ).getTime() + return dateB - dateA + }) +} + const navigate = (result: any) => { if (result.doctype == 'LMS Course') { router.push({ diff --git a/lms/command_palette.py b/lms/command_palette.py index d9803a4d..986e1780 100644 --- a/lms/command_palette.py +++ b/lms/command_palette.py @@ -17,9 +17,20 @@ def search_sqlite(query: str): def prepare_search_results(result): + groups = get_grouped_results(result) + + out = [] + for key in groups: + groups[key] = remove_duplicates(groups[key]) + groups[key].sort(key=lambda x: x.get("modified"), reverse=True) + out.append({"title": key, "items": groups[key]}) + + return out + + +def get_grouped_results(result): roles = frappe.get_roles() groups = {} - for r in result["results"]: doctype = r["doctype"] if doctype == "LMS Course" and can_access_course(r, roles): @@ -31,14 +42,7 @@ def prepare_search_results(result): elif doctype == "Job Opportunity" and can_access_job(r, roles): r["author_info"] = get_instructor_info(doctype, r) groups.setdefault("Job Opportunities", []).append(r) - - out = [] - for key in groups: - groups[key] = remove_duplicates(groups[key]) - groups[key].sort(key=lambda x: x.get("modified"), reverse=True) - out.append({"title": key, "items": groups[key]}) - - return out + return groups def remove_duplicates(items): @@ -52,7 +56,6 @@ def remove_duplicates(items): def can_access_course(course, roles): - print(course.get("name"), course.get("published")) if can_create_course(roles): return True elif course.get("published"): diff --git a/lms/sqlite.py b/lms/sqlite.py index 4f35939a..680677b1 100644 --- a/lms/sqlite.py +++ b/lms/sqlite.py @@ -2,7 +2,7 @@ from contextlib import suppress import frappe from frappe.search.sqlite_search import SQLiteSearch, SQLiteSearchIndexMissingError -from frappe.utils import get_datetime, nowdate +from frappe.utils import get_datetime, getdate, nowdate class LearningSearch(SQLiteSearch): @@ -74,42 +74,54 @@ class LearningSearch(SQLiteSearch): }, } + COURSE_FIELDS = [ + "name", + "title", + "description", + "short_introduction", + "category", + "published", + "published_on", + "creation", + "modified", + "owner", + ] + + BATCH_FIELDS = [ + "name", + "title", + "description", + "batch_details", + "category", + "start_date", + "creation", + "modified", + "owner", + "published", + ] + + JOB_FIELDS = [ + "name", + "job_title", + "company_name", + "description", + "creation", + "modified", + "owner", + ] + + INSTRUCTOR_FIELDS = [ + "name", + "instructor", + "parent", + "parenttype", + ] + DOCTYPE_FIELDS = { - "LMS Course": [ - "name", - "title", - "description", - "short_introduction", - "category", - "creation", - "modified", - "owner", - ], - "LMS Batch": [ - "name", - "title", - "description", - "batch_details", - "category", - "creation", - "modified", - "owner", - ], - "Job Opportunity": [ - "name", - "job_title", - "company_name", - "description", - "creation", - "modified", - "owner", - ], - "Course Instructor": [ - "name", - "instructor", - "parent", - "parenttype", - ], + "LMS Course": COURSE_FIELDS, + "LMS Batch": BATCH_FIELDS, + "Job Opportunity": JOB_FIELDS, + "Course Instructor": INSTRUCTOR_FIELDS, } def build_index(self): @@ -127,41 +139,35 @@ class LearningSearch(SQLiteSearch): return None if doc.doctype == "Course Instructor": - instructor = frappe.db.get_value("User", doc.instructor, "full_name") - if doc.parenttype == "LMS Course": - details = frappe.db.get_value( - "LMS Course", - doc.parent, - ["name", "title", "description", "published_on", "modified", "published"], - as_dict=True, - ) - document["published_on"] = details.get("published_on") - elif doc.parenttype == "LMS Batch": - details = frappe.db.get_value( - "LMS Batch", - doc.parent, - ["name", "title", "batch_details as description", "start_date", "modified", "published"], - as_dict=True, - ) - document["start_date"] = details.get("start_date") - - if details: - document["doctype"] = doc.parenttype - document["name"] = doc.parent - document["title"] = self._process_content(details.title) - document["content"] = self._process_content( - f"Instructor: {instructor}\n{details.description}\n{doc.instructor}" - ) - document["modified"] = self.get_modified_date(details, doc.parenttype) - document["published"] = details.get("published", 0) - + document = self.get_instructor_details(doc, document) else: if not document.get("modified"): - document["modified"] = self.get_modified_date(doc, doc.doctype) + self.set_modified_date(doc, doc.doctype, document) return document - def get_modified_date(self, details, doctype): + def get_instructor_details(self, doc, document): + instructor = frappe.db.get_value("User", doc.instructor, "full_name") + fields = self.COURSE_FIELDS if doc.parenttype == "LMS Course" else self.BATCH_FIELDS + details = frappe.db.get_value(doc.parenttype, doc.parent, fields, as_dict=True) + + if details: + document["doctype"] = doc.parenttype + document["name"] = doc.parent + document["title"] = self._process_content(details.title) + document["published"] = details.get("published", 0) + document["content"] = self._process_content( + f"Instructor: {instructor}\n{details.description}\n{doc.instructor}" + ) + self.set_modified_date(details, doc.parenttype, document) + if doc.parenttype == "LMS Course": + document["published_on"] = details.get("published_on") + elif doc.parenttype == "LMS Batch": + document["start_date"] = details.get("start_date") + + return document + + def set_modified_date(self, details, doctype, document): modified_value = None if doctype == "LMS Course": modified_value = details.get("published_on") @@ -170,13 +176,14 @@ class LearningSearch(SQLiteSearch): if not modified_value: modified_value = frappe.db.get_value(doctype, details.name, "creation") - print(details.name, modified_value) modified_value = get_datetime(modified_value) - print(modified_value) - modified_value = modified_value.timestamp() - print(modified_value) - return modified_value + if doctype == "LMS Course": + document["published_on"] = getdate(modified_value) + elif doctype == "LMS Batch": + document["start_date"] = getdate(modified_value) + + document["modified"] = modified_value.timestamp() @SQLiteSearch.scoring_function def get_doctype_boost(self, row, query, query_words): From 204fb669c0eccbaa98fa4a805a0aa9d3d13a77a1 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Thu, 1 Jan 2026 12:23:31 +0530 Subject: [PATCH 3/3] test: fixed evaluator schedule test --- .../doctype/course_evaluator/test_course_evaluator.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lms/lms/doctype/course_evaluator/test_course_evaluator.py b/lms/lms/doctype/course_evaluator/test_course_evaluator.py index 61e3dc35..605bb553 100644 --- a/lms/lms/doctype/course_evaluator/test_course_evaluator.py +++ b/lms/lms/doctype/course_evaluator/test_course_evaluator.py @@ -52,13 +52,17 @@ class TestCourseEvaluator(UnitTestCase): return first_date def calculated_last_date_of_schedule(self, first_date): - last_day = add_days(first_date, 56) + last_day = add_days(getdate(), 56) offset_monday = (0 - last_day.weekday() + 7) % 7 # 0 for Monday offset_wednesday = (2 - last_day.weekday() + 7) % 7 # 2 for Wednesday - if offset_monday > offset_wednesday and offset_monday < 4: + + if offset_monday < offset_wednesday and offset_monday <= 4: last_day = add_days(last_day, offset_monday) - else: + elif offset_wednesday <= 4: last_day = add_days(last_day, offset_wednesday) + else: + last_day = add_days(last_day, min(offset_monday, offset_wednesday) + 7) + return last_day def test_unavailability_dates(self):