From 41c7e43312ebecb00ca0c7e22e35690e5d081701 Mon Sep 17 00:00:00 2001 From: Kaveh-Vakili Date: Fri, 17 Jul 2026 16:51:20 -0400 Subject: [PATCH 1/8] Search Bar Enhancement: add message content search backend - SearchRoomMessagesReactor: add limit, offset, includeMessageText keys; remove projectId fallback so omitting it searches across all projects; wire 6-arg searchMessages; update reactor description - ModelInferenceLogsUtils: add 6-arg searchMessages overload with pagination and optional message text; backward-compat 3-arg delegate; add room_name/date_created selectors; LOWER() wrap for case-insensitive LIKE; % wildcards on keyword; null-guard on projectId --- .../ModelInferenceLogsUtils.java | 39 ++++++++++--- .../reactors/SearchRoomMessagesReactor.java | 57 +++++++++++++------ 2 files changed, 72 insertions(+), 24 deletions(-) diff --git a/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java b/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java index 184baa5311f..a4b00cdb421 100644 --- a/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java +++ b/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java @@ -1435,34 +1435,57 @@ public static boolean doSetRoomToPinned(String userId, String roomId, boolean pi * @return a list of matching messages (room_id, message_text, message_id) */ public static List> searchMessages(String userId, String projectId, String keyword) { + return searchMessages(userId, projectId, keyword, -1, -1, true); + } + + public static List> searchMessages(String userId, String projectId, String keyword, + long limit, long offset, boolean includeMessageText) { IRDBMSEngine modelInferenceLogsDb = SystemEngineRegistry.getModelInferenceLogsDb(); SelectQueryStruct qs = new SelectQueryStruct(); - // Always select room_id and message_id + // Always select room_id, message_id, and room metadata needed by the frontend qs.addSelector(new QueryColumnSelector("ROOM__ROOM_ID", "room_id")); qs.addSelector(new QueryColumnSelector("MESSAGE__MESSAGE_ID", "message_id")); + qs.addSelector(new QueryColumnSelector("ROOM__ROOM_NAME", "room_name")); + qs.addSelector(new QueryColumnSelector("ROOM__DATE_CREATED", "date_created")); - // Build a selector for message_text out of message_data, adapted to DB type + // Always build the BLOB selector — needed for the WHERE filter regardless QueryFunctionSelector messageTextSelector = modelInferenceLogsDb.getQueryUtil() .getBlobToStringFunctionSelector(new QueryColumnSelector("MESSAGE__MESSAGE_DATA"), "message_text"); - qs.addSelector(messageTextSelector); + if (includeMessageText) { + qs.addSelector(messageTextSelector); + } + + // Wrap decoded text in LOWER() for case-insensitive LIKE filtering + QueryFunctionSelector lowerMessageSelector = new QueryFunctionSelector(); + lowerMessageSelector.setFunction(QueryFunctionHelper.LOWER); + lowerMessageSelector.addInnerSelector(messageTextSelector); + lowerMessageSelector.setAlias("message_text_lower"); // JOIN, filters, and ordering qs.addRelation("MESSAGE__ROOM_ID", "ROOM__ROOM_ID", "left.join"); qs.addExplicitFilter( SimpleQueryFilter.makeColToValFilter("ROOM__IS_ACTIVE", "==", true, PixelDataType.BOOLEAN)); - qs.addExplicitFilter(SimpleQueryFilter.makeColToValFilter("ROOM__PROJECT_ID", "==", projectId)); + if (projectId != null) { + qs.addExplicitFilter(SimpleQueryFilter.makeColToValFilter("ROOM__PROJECT_ID", "==", projectId)); + } qs.addExplicitFilter(SimpleQueryFilter.makeColToValFilter("ROOM__USER_ID", "==", userId)); - // Add filter on decoded message text - qs.addExplicitFilter(SimpleQueryFilter.makeColToValFilter(messageTextSelector, // use the computed selector (the - // decoded/casted field) - "?like", keyword.toLowerCase(), // (may want '?ilike' if framework supports, for case-insensitive) + // Case-insensitive substring filter: LOWER(CAST(message_data AS TEXT)) LIKE '%keyword%' + qs.addExplicitFilter(SimpleQueryFilter.makeColToValFilter(lowerMessageSelector, + "?like", "%" + keyword.toLowerCase() + "%", PixelDataType.CONST_STRING)); qs.addOrderBy("ROOM__DATE_CREATED", "DESC"); qs.addOrderBy("MESSAGE__DATE_CREATED", "DESC"); + if (limit > 0) { + qs.setLimit(limit); + } + if (offset > 0) { + qs.setOffSet(offset); + } + return QueryExecutionUtility.flushRsToMap(modelInferenceLogsDb, qs); } diff --git a/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java b/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java index de7db84f1e0..28a4b778a32 100644 --- a/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java +++ b/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java @@ -43,60 +43,85 @@ public class SearchRoomMessagesReactor extends AbstractReactor { private static final Logger classLogger = LogManager.getLogger(SearchRoomMessagesReactor.class); + private static final String INCLUDE_MESSAGE_TEXT = "includeMessageText"; + public SearchRoomMessagesReactor() { - // this expects projectId and search term - this.keysToGet = new String[] { ReactorKeysEnum.PROJECT.getKey(), ReactorKeysEnum.SEARCH.getKey(), }; - this.keyRequired = new int[] { 0, 1 }; + this.keysToGet = new String[] { + ReactorKeysEnum.PROJECT.getKey(), + ReactorKeysEnum.SEARCH.getKey(), + ReactorKeysEnum.LIMIT.getKey(), + ReactorKeysEnum.OFFSET.getKey(), + INCLUDE_MESSAGE_TEXT + }; + this.keyRequired = new int[] { 0, 1, 0, 0, 0 }; } @Override public NounMetadata execute() { organizeKeys(); - // Get user User user = this.insight.getUser(); if (user == null) { throw new IllegalArgumentException("You are not properly logged in"); } String userId = user.getPrimaryLoginToken().getId(); - // Get projectId + // null = search across all projects String projectId = this.keyValue.get(this.keysToGet[0]); - if (projectId == null) { - projectId = this.insight.getContextProjectId(); - } - // Get keyword String keyword = this.keyValue.get(this.keysToGet[1]); if (keyword == null || keyword.trim().isEmpty()) { throw new IllegalArgumentException("Search keyword must be provided"); } - // Query messages + long limit = -1; + String limitStr = this.keyValue.get(this.keysToGet[2]); + if (limitStr != null) { + limit = Long.parseLong(limitStr); + } + + long offset = -1; + String offsetStr = this.keyValue.get(this.keysToGet[3]); + if (offsetStr != null) { + offset = Long.parseLong(offsetStr); + } + + boolean includeMessageText = false; + String includeStr = this.keyValue.get(this.keysToGet[4]); + if (includeStr != null) { + includeMessageText = Boolean.parseBoolean(includeStr); + } + List> results; try { - results = ModelInferenceLogsUtils.searchMessages(userId, projectId, keyword); + results = ModelInferenceLogsUtils.searchMessages(userId, projectId, keyword, limit, offset, includeMessageText); } catch (Exception e) { classLogger.error("Error searching room messages", e); throw new RuntimeException("Could not search room messages: " + e.getMessage(), e); } - // Return results as VECTOR return new NounMetadata(results, PixelDataType.VECTOR); } @Override public String getReactorDescription() { - return "This reactor searches through the messages in the user's conversation rooms for a given keyword within a specified project. " - + "It returns the matching messages, including relevant details such as room ID, message text, and message ID, for rooms the user has access to."; + return "Searches through the messages in the user's conversation rooms for a given keyword. " + + "Returns room_id and message_id for each match. Optionally returns message_text when includeMessageText=true. " + + "Omitting projectId searches across all projects. Supports limit and offset for pagination."; } @Override protected String getDescriptionForKey(String key) { if (key.equals(ReactorKeysEnum.PROJECT.getKey())) { - return "The project ID for which to search room messages. If no project ID is passed, then all rooms for the user will be searched."; + return "Optional project ID to scope the search. Omit to search across all projects the user has access to."; } else if (key.equals(ReactorKeysEnum.SEARCH.getKey())) { - return "The search term to use to search for within the messages. All messages containing this text (case-insensitive) will be returned."; + return "The keyword to search for within message content (case-insensitive)."; + } else if (key.equals(ReactorKeysEnum.LIMIT.getKey())) { + return "Maximum number of results to return. Defaults to no cap when omitted."; + } else if (key.equals(ReactorKeysEnum.OFFSET.getKey())) { + return "Number of results to skip for pagination."; + } else if (key.equals(INCLUDE_MESSAGE_TEXT)) { + return "Whether to include message_text in results. Defaults to false."; } return super.getDescriptionForKey(key); } From e36cb3446fd21b69e5d53071f0e9b10614b13193 Mon Sep 17 00:00:00 2001 From: Kaveh-Vakili Date: Tue, 21 Jul 2026 09:06:03 -0400 Subject: [PATCH 2/8] Address PR review: fix limit/offset parsing, simplify LIKE filter, add pagination tiebreaker, fix Javadoc --- .../ModelInferenceLogsUtils.java | 16 ++------ .../reactors/SearchRoomMessagesReactor.java | 39 ++++++++++--------- 2 files changed, 24 insertions(+), 31 deletions(-) diff --git a/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java b/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java index a4b00cdb421..12f37daff52 100644 --- a/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java +++ b/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java @@ -1432,7 +1432,7 @@ public static boolean doSetRoomToPinned(String userId, String roomId, boolean pi * @param userId the user to search for * @param projectId the project to search within * @param keyword the text keyword to find in message bodies - * @return a list of matching messages (room_id, message_text, message_id) + * @return a list of matching messages (room_id, message_id, room_name, date_created) */ public static List> searchMessages(String userId, String projectId, String keyword) { return searchMessages(userId, projectId, keyword, -1, -1, true); @@ -1456,12 +1456,6 @@ public static List> searchMessages(String userId, String pro qs.addSelector(messageTextSelector); } - // Wrap decoded text in LOWER() for case-insensitive LIKE filtering - QueryFunctionSelector lowerMessageSelector = new QueryFunctionSelector(); - lowerMessageSelector.setFunction(QueryFunctionHelper.LOWER); - lowerMessageSelector.addInnerSelector(messageTextSelector); - lowerMessageSelector.setAlias("message_text_lower"); - // JOIN, filters, and ordering qs.addRelation("MESSAGE__ROOM_ID", "ROOM__ROOM_ID", "left.join"); qs.addExplicitFilter( @@ -1470,14 +1464,12 @@ public static List> searchMessages(String userId, String pro qs.addExplicitFilter(SimpleQueryFilter.makeColToValFilter("ROOM__PROJECT_ID", "==", projectId)); } qs.addExplicitFilter(SimpleQueryFilter.makeColToValFilter("ROOM__USER_ID", "==", userId)); - - // Case-insensitive substring filter: LOWER(CAST(message_data AS TEXT)) LIKE '%keyword%' - qs.addExplicitFilter(SimpleQueryFilter.makeColToValFilter(lowerMessageSelector, - "?like", "%" + keyword.toLowerCase() + "%", - PixelDataType.CONST_STRING)); + qs.addExplicitFilter(SimpleQueryFilter.makeColToValFilter(messageTextSelector, + "?like", keyword, PixelDataType.CONST_STRING)); qs.addOrderBy("ROOM__DATE_CREATED", "DESC"); qs.addOrderBy("MESSAGE__DATE_CREATED", "DESC"); + qs.addOrderBy("MESSAGE__MESSAGE_ID", "DESC"); if (limit > 0) { qs.setLimit(limit); diff --git a/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java b/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java index 28a4b778a32..4d2b5a20f4a 100644 --- a/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java +++ b/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java @@ -74,26 +74,26 @@ public NounMetadata execute() { throw new IllegalArgumentException("Search keyword must be provided"); } - long limit = -1; - String limitStr = this.keyValue.get(this.keysToGet[2]); - if (limitStr != null) { - limit = Long.parseLong(limitStr); - } - - long offset = -1; - String offsetStr = this.keyValue.get(this.keysToGet[3]); - if (offsetStr != null) { - offset = Long.parseLong(offsetStr); - } - - boolean includeMessageText = false; - String includeStr = this.keyValue.get(this.keysToGet[4]); - if (includeStr != null) { - includeMessageText = Boolean.parseBoolean(includeStr); - } - List> results; try { + long limit = -1; + String limitStr = this.keyValue.get(this.keysToGet[2]); + if (limitStr != null) { + limit = Long.parseLong(limitStr); + } + + long offset = -1; + String offsetStr = this.keyValue.get(this.keysToGet[3]); + if (offsetStr != null) { + offset = Long.parseLong(offsetStr); + } + + boolean includeMessageText = false; + String includeStr = this.keyValue.get(this.keysToGet[4]); + if (includeStr != null) { + includeMessageText = Boolean.parseBoolean(includeStr); + } + results = ModelInferenceLogsUtils.searchMessages(userId, projectId, keyword, limit, offset, includeMessageText); } catch (Exception e) { classLogger.error("Error searching room messages", e); @@ -106,7 +106,8 @@ public NounMetadata execute() { @Override public String getReactorDescription() { return "Searches through the messages in the user's conversation rooms for a given keyword. " - + "Returns room_id and message_id for each match. Optionally returns message_text when includeMessageText=true. " + + "Returns room_id, message_id, room_name, and date_created for each match. " + + "Optionally returns message_text when includeMessageText=true. " + "Omitting projectId searches across all projects. Supports limit and offset for pagination."; } From 14d5da8a7def7b51f0808b1b210de20cca9fa87e Mon Sep 17 00:00:00 2001 From: Kaveh-Vakili Date: Wed, 22 Jul 2026 12:14:14 -0400 Subject: [PATCH 3/8] Default includeMessageText to true for backward compatibility --- .../inferencetracking/reactors/SearchRoomMessagesReactor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java b/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java index 4d2b5a20f4a..c1530f3affc 100644 --- a/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java +++ b/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java @@ -88,7 +88,7 @@ public NounMetadata execute() { offset = Long.parseLong(offsetStr); } - boolean includeMessageText = false; + boolean includeMessageText = true; String includeStr = this.keyValue.get(this.keysToGet[4]); if (includeStr != null) { includeMessageText = Boolean.parseBoolean(includeStr); @@ -122,7 +122,7 @@ protected String getDescriptionForKey(String key) { } else if (key.equals(ReactorKeysEnum.OFFSET.getKey())) { return "Number of results to skip for pagination."; } else if (key.equals(INCLUDE_MESSAGE_TEXT)) { - return "Whether to include message_text in results. Defaults to false."; + return "Whether to include message_text in results. Defaults to true."; } return super.getDescriptionForKey(key); } From c09fc7e891459325f069727bf9c331c69e135539 Mon Sep 17 00:00:00 2001 From: Kaveh-Vakili Date: Thu, 23 Jul 2026 13:24:41 -0400 Subject: [PATCH 4/8] Restore project ID fallback to insight context; restore limit/offset/includeMessageText --- .../reactors/SearchRoomMessagesReactor.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java b/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java index c1530f3affc..320b15b3097 100644 --- a/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java +++ b/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java @@ -66,8 +66,10 @@ public NounMetadata execute() { } String userId = user.getPrimaryLoginToken().getId(); - // null = search across all projects String projectId = this.keyValue.get(this.keysToGet[0]); + if (projectId == null) { + projectId = this.insight.getContextProjectId(); + } String keyword = this.keyValue.get(this.keysToGet[1]); if (keyword == null || keyword.trim().isEmpty()) { @@ -107,14 +109,14 @@ public NounMetadata execute() { public String getReactorDescription() { return "Searches through the messages in the user's conversation rooms for a given keyword. " + "Returns room_id, message_id, room_name, and date_created for each match. " - + "Optionally returns message_text when includeMessageText=true. " - + "Omitting projectId searches across all projects. Supports limit and offset for pagination."; + + "Optionally returns message_text when includeMessageText=true (default). " + + "Falls back to the current insight's project when projectId is omitted. Supports limit and offset for pagination."; } @Override protected String getDescriptionForKey(String key) { if (key.equals(ReactorKeysEnum.PROJECT.getKey())) { - return "Optional project ID to scope the search. Omit to search across all projects the user has access to."; + return "Optional project ID to scope the search. Defaults to the current insight's project when omitted."; } else if (key.equals(ReactorKeysEnum.SEARCH.getKey())) { return "The keyword to search for within message content (case-insensitive)."; } else if (key.equals(ReactorKeysEnum.LIMIT.getKey())) { @@ -126,4 +128,4 @@ protected String getDescriptionForKey(String key) { } return super.getDescriptionForKey(key); } -} \ No newline at end of file +} From cbcd693a1a18ab282bb1b20008668cde96b15aa7 Mon Sep 17 00:00:00 2001 From: Kaveh-Vakili Date: Thu, 23 Jul 2026 14:40:51 -0400 Subject: [PATCH 5/8] Use MESSAGE date_created; use getLong/getBoolean helpers; fix OFFSET-without-LIMIT guard; fix description --- .../ModelInferenceLogsUtils.java | 11 ++++---- .../reactors/SearchRoomMessagesReactor.java | 25 +++++-------------- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java b/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java index 12f37daff52..0214d788b1f 100644 --- a/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java +++ b/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java @@ -1432,7 +1432,7 @@ public static boolean doSetRoomToPinned(String userId, String roomId, boolean pi * @param userId the user to search for * @param projectId the project to search within * @param keyword the text keyword to find in message bodies - * @return a list of matching messages (room_id, message_id, room_name, date_created) + * @return a list of matching messages (room_id, message_id, room_name, date_created from MESSAGE) */ public static List> searchMessages(String userId, String projectId, String keyword) { return searchMessages(userId, projectId, keyword, -1, -1, true); @@ -1447,7 +1447,7 @@ public static List> searchMessages(String userId, String pro qs.addSelector(new QueryColumnSelector("ROOM__ROOM_ID", "room_id")); qs.addSelector(new QueryColumnSelector("MESSAGE__MESSAGE_ID", "message_id")); qs.addSelector(new QueryColumnSelector("ROOM__ROOM_NAME", "room_name")); - qs.addSelector(new QueryColumnSelector("ROOM__DATE_CREATED", "date_created")); + qs.addSelector(new QueryColumnSelector("MESSAGE__DATE_CREATED", "date_created")); // Always build the BLOB selector — needed for the WHERE filter regardless QueryFunctionSelector messageTextSelector = modelInferenceLogsDb.getQueryUtil() @@ -1467,15 +1467,14 @@ public static List> searchMessages(String userId, String pro qs.addExplicitFilter(SimpleQueryFilter.makeColToValFilter(messageTextSelector, "?like", keyword, PixelDataType.CONST_STRING)); - qs.addOrderBy("ROOM__DATE_CREATED", "DESC"); qs.addOrderBy("MESSAGE__DATE_CREATED", "DESC"); qs.addOrderBy("MESSAGE__MESSAGE_ID", "DESC"); if (limit > 0) { qs.setLimit(limit); - } - if (offset > 0) { - qs.setOffSet(offset); + if (offset > 0) { + qs.setOffSet(offset); + } } return QueryExecutionUtility.flushRsToMap(modelInferenceLogsDb, qs); diff --git a/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java b/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java index 320b15b3097..17906f4b3ef 100644 --- a/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java +++ b/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java @@ -76,26 +76,12 @@ public NounMetadata execute() { throw new IllegalArgumentException("Search keyword must be provided"); } + long limit = getLong(ReactorKeysEnum.LIMIT.getKey(), -1L); + long offset = getLong(ReactorKeysEnum.OFFSET.getKey(), -1L); + boolean includeMessageText = getBoolean(INCLUDE_MESSAGE_TEXT, true); + List> results; try { - long limit = -1; - String limitStr = this.keyValue.get(this.keysToGet[2]); - if (limitStr != null) { - limit = Long.parseLong(limitStr); - } - - long offset = -1; - String offsetStr = this.keyValue.get(this.keysToGet[3]); - if (offsetStr != null) { - offset = Long.parseLong(offsetStr); - } - - boolean includeMessageText = true; - String includeStr = this.keyValue.get(this.keysToGet[4]); - if (includeStr != null) { - includeMessageText = Boolean.parseBoolean(includeStr); - } - results = ModelInferenceLogsUtils.searchMessages(userId, projectId, keyword, limit, offset, includeMessageText); } catch (Exception e) { classLogger.error("Error searching room messages", e); @@ -108,7 +94,8 @@ public NounMetadata execute() { @Override public String getReactorDescription() { return "Searches through the messages in the user's conversation rooms for a given keyword. " - + "Returns room_id, message_id, room_name, and date_created for each match. " + + "Returns room_id, message_id, room_name, and date_created (message timestamp) for each match. " + + "Case-insensitive matching is handled by the query framework's ?like comparator. " + "Optionally returns message_text when includeMessageText=true (default). " + "Falls back to the current insight's project when projectId is omitted. Supports limit and offset for pagination."; } From 3acef8a6d12774ff399d07392fb59e7eb8e02e12 Mon Sep 17 00:00:00 2001 From: Kaveh-Vakili Date: Fri, 7 Aug 2026 18:07:32 -0400 Subject: [PATCH 6/8] fix: dedupe search results to one row per room before pagination searchMessages applied limit/offset at message grain, so a single room with many matching messages could fill an entire page and hide other rooms that only matched once. Reduce to one row per room (most recent match) before limit/offset are applied, so pagination is over matching rooms rather than raw message rows. --- .../ModelInferenceLogsUtils.java | 42 +++++++++++++++---- .../reactors/SearchRoomMessagesReactor.java | 6 ++- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java b/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java index 0214d788b1f..42098d76524 100644 --- a/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java +++ b/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java @@ -40,6 +40,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -107,6 +108,11 @@ public class ModelInferenceLogsUtils { private static final String FEEDBACK_TABLE_NAME = "FEEDBACK__"; static boolean initialized = false; + // Safety cap on raw MESSAGE rows fetched for a single searchMessages() call, + // before dedup-to-room and limit/offset are applied. Well above any realistic + // per-search match count, just to bound worst-case memory/query cost. + private static final long MAX_SEARCH_MESSAGE_ROWS = 5000; + /** * Initializes and migrates the model-inference logging database schema. * @@ -1427,12 +1433,15 @@ public static boolean doSetRoomToPinned(String userId, String roomId, boolean pi /** * Searches messages for a user and project by keyword. Handles message_data as * a binary field (bytea/blob/varbinary). Converts/casts as necessary for each - * DB so text search via LIKE is possible. + * DB so text search via LIKE is possible. Results are deduplicated to one row + * per room (that room's most recent match) before limit/offset are applied, + * so pagination is over matching rooms rather than raw message rows. * * @param userId the user to search for * @param projectId the project to search within * @param keyword the text keyword to find in message bodies - * @return a list of matching messages (room_id, message_id, room_name, date_created from MESSAGE) + * @return a list of matching rooms, one row per room (room_id, message_id, + * room_name, date_created from that room's most recent matching message) */ public static List> searchMessages(String userId, String projectId, String keyword) { return searchMessages(userId, projectId, keyword, -1, -1, true); @@ -1470,14 +1479,33 @@ public static List> searchMessages(String userId, String pro qs.addOrderBy("MESSAGE__DATE_CREATED", "DESC"); qs.addOrderBy("MESSAGE__MESSAGE_ID", "DESC"); - if (limit > 0) { - qs.setLimit(limit); - if (offset > 0) { - qs.setOffSet(offset); + // Fetch at message grain (a room can have many matches) capped at a safety + // ceiling, then reduce to one row per room below, BEFORE limit/offset are + // applied. Applying limit/offset directly to this query would paginate raw + // message rows, so a single chatty room could fill an entire page and hide + // every other matching room. + qs.setLimit(MAX_SEARCH_MESSAGE_ROWS); + + List> rawRows = QueryExecutionUtility.flushRsToMap(modelInferenceLogsDb, qs); + + // Reduce to one row per room. Rows are already ordered by + // MESSAGE__DATE_CREATED DESC, MESSAGE__MESSAGE_ID DESC, so the first row + // seen for a room is that room's most recent match. + Map> dedupedByRoom = new LinkedHashMap<>(); + for (Map row : rawRows) { + Object roomId = row.get("room_id"); + if (roomId != null) { + dedupedByRoom.putIfAbsent(roomId.toString(), row); } } + List> dedupedRows = new ArrayList<>(dedupedByRoom.values()); - return QueryExecutionUtility.flushRsToMap(modelInferenceLogsDb, qs); + if (limit > 0) { + int start = offset > 0 ? (int) Math.min(offset, dedupedRows.size()) : 0; + int end = (int) Math.min((long) start + limit, dedupedRows.size()); + return dedupedRows.subList(start, end); + } + return dedupedRows; } /** diff --git a/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java b/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java index 17906f4b3ef..b2aaa2d2b39 100644 --- a/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java +++ b/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java @@ -94,10 +94,12 @@ public NounMetadata execute() { @Override public String getReactorDescription() { return "Searches through the messages in the user's conversation rooms for a given keyword. " - + "Returns room_id, message_id, room_name, and date_created (message timestamp) for each match. " + + "Returns one row per matching room (its most recent matching message): room_id, message_id, " + + "room_name, and date_created. " + "Case-insensitive matching is handled by the query framework's ?like comparator. " + "Optionally returns message_text when includeMessageText=true (default). " - + "Falls back to the current insight's project when projectId is omitted. Supports limit and offset for pagination."; + + "Falls back to the current insight's project when projectId is omitted. " + + "Supports limit and offset for pagination over matching rooms."; } @Override From 3c79ca0c928b694e26b26cce6a92a4f9464c8de7 Mon Sep 17 00:00:00 2001 From: Kunal Patel Date: Tue, 11 Aug 2026 10:25:09 -0400 Subject: [PATCH 7/8] fix: dedupe logic and room filtering --- .../ModelInferenceLogsUtils.java | 103 +++++++++--------- .../GetUserConversationRoomsReactor.java | 57 ++++++---- .../reactors/SearchRoomMessagesReactor.java | 59 +++++++--- 3 files changed, 130 insertions(+), 89 deletions(-) diff --git a/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java b/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java index cc282a0c32f..9b16619ba27 100644 --- a/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java +++ b/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java @@ -40,7 +40,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -108,11 +107,6 @@ public class ModelInferenceLogsUtils { private static final String FEEDBACK_TABLE_NAME = "FEEDBACK__"; static boolean initialized = false; - // Safety cap on raw MESSAGE rows fetched for a single searchMessages() call, - // before dedup-to-room and limit/offset are applied. Well above any realistic - // per-search match count, just to bound worst-case memory/query cost. - private static final long MAX_SEARCH_MESSAGE_ROWS = 5000; - /** * Initializes and migrates the model-inference logging database schema. * @@ -1461,79 +1455,68 @@ public static boolean doSetRoomToPinned(String userId, String roomId, boolean pi /** * Searches messages for a user and project by keyword. Handles message_data as * a binary field (bytea/blob/varbinary). Converts/casts as necessary for each - * DB so text search via LIKE is possible. Results are deduplicated to one row - * per room (that room's most recent match) before limit/offset are applied, - * so pagination is over matching rooms rather than raw message rows. + * DB so text search via LIKE is possible. Results are grouped to one row per + * room in SQL before limit/offset are applied, so pagination operates on rooms + * rather than raw message rows. * * @param userId the user to search for - * @param projectId the project to search within + * @param projectId the project to search within, or null/blank to search all + * projects for the user * @param keyword the text keyword to find in message bodies - * @return a list of matching rooms, one row per room (room_id, message_id, - * room_name, date_created from that room's most recent matching message) + * @return a list of matching rooms, one row per room (room_id, room_name, and + * date_created from that room's most recent matching message) */ public static List> searchMessages(String userId, String projectId, String keyword) { - return searchMessages(userId, projectId, keyword, -1, -1, true); + return searchMessages(userId, projectId, keyword, -1, 0, false, false); } public static List> searchMessages(String userId, String projectId, String keyword, - long limit, long offset, boolean includeMessageText) { + long limit, long offset, boolean includeUnnamedRooms, boolean includeChildRooms) { IRDBMSEngine modelInferenceLogsDb = SystemEngineRegistry.getModelInferenceLogsDb(); SelectQueryStruct qs = new SelectQueryStruct(); - // Always select room_id, message_id, and room metadata needed by the frontend + // Return room-grain results. date_created is the room's latest matching + // message timestamp and drives stable room-level pagination. qs.addSelector(new QueryColumnSelector("ROOM__ROOM_ID", "room_id")); - qs.addSelector(new QueryColumnSelector("MESSAGE__MESSAGE_ID", "message_id")); qs.addSelector(new QueryColumnSelector("ROOM__ROOM_NAME", "room_name")); - qs.addSelector(new QueryColumnSelector("MESSAGE__DATE_CREATED", "date_created")); + QueryFunctionSelector latestMatchSelector = QueryFunctionSelector.makeFunctionSelector(QueryFunctionHelper.MAX, + "MESSAGE__DATE_CREATED", "date_created"); + qs.addSelector(latestMatchSelector); - // Always build the BLOB selector — needed for the WHERE filter regardless + // Use the search-specific conversion so malformed searchable content cannot + // abort an otherwise unrelated room/project search. QueryFunctionSelector messageTextSelector = modelInferenceLogsDb.getQueryUtil() .getBlobToStringFunctionSelector(new QueryColumnSelector("MESSAGE__MESSAGE_DATA"), "message_text"); - if (includeMessageText) { - qs.addSelector(messageTextSelector); - } - // JOIN, filters, and ordering - qs.addRelation("MESSAGE__ROOM_ID", "ROOM__ROOM_ID", "left.join"); + // JOIN, filters, grouping, and ordering + qs.addRelation("MESSAGE__ROOM_ID", "ROOM__ROOM_ID", "inner.join"); qs.addExplicitFilter( SimpleQueryFilter.makeColToValFilter("ROOM__IS_ACTIVE", "==", true, PixelDataType.BOOLEAN)); - if (projectId != null) { + if (projectId != null && !projectId.trim().isEmpty()) { qs.addExplicitFilter(SimpleQueryFilter.makeColToValFilter("ROOM__PROJECT_ID", "==", projectId)); } qs.addExplicitFilter(SimpleQueryFilter.makeColToValFilter("ROOM__USER_ID", "==", userId)); + if (!includeUnnamedRooms) { + qs.addExplicitFilter(SimpleQueryFilter.makeColToValFilter("ROOM__ROOM_NAME", "!=", null)); + qs.addExplicitFilter(SimpleQueryFilter.makeColToValFilter("ROOM__ROOM_NAME", "!=", "")); + } + if (!includeChildRooms) { + qs.addExplicitFilter(SimpleQueryFilter.makeColToValFilter("ROOM__PARENT_ROOM_ID", "==", null)); + } qs.addExplicitFilter(SimpleQueryFilter.makeColToValFilter(messageTextSelector, "?like", keyword, PixelDataType.CONST_STRING)); - qs.addOrderBy("MESSAGE__DATE_CREATED", "DESC"); - qs.addOrderBy("MESSAGE__MESSAGE_ID", "DESC"); - - // Fetch at message grain (a room can have many matches) capped at a safety - // ceiling, then reduce to one row per room below, BEFORE limit/offset are - // applied. Applying limit/offset directly to this query would paginate raw - // message rows, so a single chatty room could fill an entire page and hide - // every other matching room. - qs.setLimit(MAX_SEARCH_MESSAGE_ROWS); - - List> rawRows = QueryExecutionUtility.flushRsToMap(modelInferenceLogsDb, qs); - - // Reduce to one row per room. Rows are already ordered by - // MESSAGE__DATE_CREATED DESC, MESSAGE__MESSAGE_ID DESC, so the first row - // seen for a room is that room's most recent match. - Map> dedupedByRoom = new LinkedHashMap<>(); - for (Map row : rawRows) { - Object roomId = row.get("room_id"); - if (roomId != null) { - dedupedByRoom.putIfAbsent(roomId.toString(), row); - } - } - List> dedupedRows = new ArrayList<>(dedupedByRoom.values()); - + qs.addGroupBy(new QueryColumnSelector("ROOM__ROOM_ID")); + qs.addGroupBy(new QueryColumnSelector("ROOM__ROOM_NAME")); + qs.addOrderBy(new QueryColumnOrderBySelector("date_created", "DESC")); + qs.addOrderBy(new QueryColumnOrderBySelector("room_id", "DESC")); if (limit > 0) { - int start = offset > 0 ? (int) Math.min(offset, dedupedRows.size()) : 0; - int end = (int) Math.min((long) start + limit, dedupedRows.size()); - return dedupedRows.subList(start, end); + qs.setLimit(limit); + } + if (offset > 0) { + qs.setOffSet(offset); } - return dedupedRows; + return QueryExecutionUtility.flushRsToMap(modelInferenceLogsDb, qs); } /** @@ -1791,11 +1774,18 @@ public static List> doVerifyConversation(String userId, Stri */ public static List> getUserConversations(String userId, String projectId, long limit, long offset, String sortDir, String search, Boolean pinned) { - return getUserConversations(userId, projectId, limit, offset, sortDir, search, pinned, null); + return getUserConversations(userId, projectId, limit, offset, sortDir, search, pinned, null, false, false); } public static List> getUserConversations(String userId, String projectId, long limit, long offset, String sortDir, String search, Boolean pinned, String roomOptionsSearch) { + return getUserConversations(userId, projectId, limit, offset, sortDir, search, pinned, roomOptionsSearch, + false, false); + } + + public static List> getUserConversations(String userId, String projectId, long limit, + long offset, String sortDir, String search, Boolean pinned, String roomOptionsSearch, + boolean includeUnnamedRooms, boolean includeChildRooms) { IRDBMSEngine modelInferenceLogsDb = SystemEngineRegistry.getModelInferenceLogsDb(); SelectQueryStruct qs = new SelectQueryStruct(); qs.addSelector(new QueryColumnSelector("ROOM__ROOM_ID")); @@ -1815,6 +1805,13 @@ public static List> getUserConversations(String userId, Stri if (projectId != null) { subQs.addExplicitFilter(SimpleQueryFilter.makeColToValFilter("ROOM__PROJECT_ID", "==", projectId)); } + if (!includeUnnamedRooms) { + subQs.addExplicitFilter(SimpleQueryFilter.makeColToValFilter("ROOM__ROOM_NAME", "!=", null)); + subQs.addExplicitFilter(SimpleQueryFilter.makeColToValFilter("ROOM__ROOM_NAME", "!=", "")); + } + if (!includeChildRooms) { + subQs.addExplicitFilter(SimpleQueryFilter.makeColToValFilter("ROOM__PARENT_ROOM_ID", "==", null)); + } qs.addExplicitFilter(SimpleQueryFilter.makeColToSubQuery("ROOM__ROOM_ID", "==", subQs)); // SEARCH diff --git a/src/prerna/engine/impl/model/inferencetracking/reactors/GetUserConversationRoomsReactor.java b/src/prerna/engine/impl/model/inferencetracking/reactors/GetUserConversationRoomsReactor.java index 7eee081f40f..e5f2e8e9a6e 100644 --- a/src/prerna/engine/impl/model/inferencetracking/reactors/GetUserConversationRoomsReactor.java +++ b/src/prerna/engine/impl/model/inferencetracking/reactors/GetUserConversationRoomsReactor.java @@ -37,13 +37,15 @@ import prerna.sablecc2.om.ReactorKeysEnum; import prerna.sablecc2.om.nounmeta.NounMetadata; -public class GetUserConversationRoomsReactor extends AbstractReactor { - - public GetUserConversationRoomsReactor() { - this.keysToGet = new String[] { ReactorKeysEnum.PROJECT.getKey(), ReactorKeysEnum.LIMIT.getKey(), - ReactorKeysEnum.OFFSET.getKey(), ReactorKeysEnum.SEARCH.getKey(), ReactorKeysEnum.SORT.getKey(), - ReactorKeysEnum.PINNED.getKey(), "roomOptionsSearch" }; - this.keyRequired = new int[] { 0, 0, 0, 0, 0, 0, 0 }; +public class GetUserConversationRoomsReactor extends AbstractReactor { + private static final String INCLUDE_UNNAMED_ROOMS = "includeUnnamedRooms"; + private static final String INCLUDE_CHILD_ROOMS = "includeChildRooms"; + + public GetUserConversationRoomsReactor() { + this.keysToGet = new String[] { ReactorKeysEnum.PROJECT.getKey(), ReactorKeysEnum.LIMIT.getKey(), + ReactorKeysEnum.OFFSET.getKey(), ReactorKeysEnum.SEARCH.getKey(), ReactorKeysEnum.SORT.getKey(), + ReactorKeysEnum.PINNED.getKey(), "roomOptionsSearch", INCLUDE_UNNAMED_ROOMS, INCLUDE_CHILD_ROOMS }; + this.keyRequired = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; } @Override @@ -53,10 +55,13 @@ public NounMetadata execute() { if (user == null) { throw new IllegalArgumentException("You are not properly logged in"); } - String projectId = this.keyValue.get(this.keysToGet[0]); - if (projectId == null) { - projectId = this.insight.getContextProjectId(); - } + String projectId = this.keyValue.get(this.keysToGet[0]); + if (projectId == null) { + projectId = this.insight.getContextProjectId(); + } + if (projectId == null) { + projectId = this.insight.getProjectId(); + } long limit = getLong(ReactorKeysEnum.LIMIT.getKey(), -1L); long offset = getLong(ReactorKeysEnum.OFFSET.getKey(), -1L); @@ -68,7 +73,11 @@ public NounMetadata execute() { sortDir = "DESC"; } - String search = this.keyValue.get(ReactorKeysEnum.SEARCH.getKey()); + String search = this.keyValue.get(ReactorKeysEnum.SEARCH.getKey()); + if (search != null && !search.trim().isEmpty() + && (projectId == null || projectId.trim().isEmpty())) { + throw new IllegalArgumentException("A project must be provided or available from the current insight"); + } // Optional pinned filter: true/false to filter, null/absent to ignore Boolean pinned = null; @@ -79,17 +88,19 @@ public NounMetadata execute() { // Optional free-text search against the OPTIONS JSON column. String roomOptionsSearch = this.keyValue.get("roomOptionsSearch"); - if (roomOptionsSearch != null) { + if (roomOptionsSearch != null) { roomOptionsSearch = roomOptionsSearch.trim(); if (roomOptionsSearch.isEmpty()) { roomOptionsSearch = null; } - } - - // Call new overload of getUserConversations - List> output = ModelInferenceLogsUtils.getUserConversations( - user.getPrimaryLoginToken().getId(), projectId, limit, offset, sortDir, search, pinned, - roomOptionsSearch); + } + boolean includeUnnamedRooms = getBoolean(INCLUDE_UNNAMED_ROOMS, false); + boolean includeChildRooms = getBoolean(INCLUDE_CHILD_ROOMS, false); + + // Call new overload of getUserConversations + List> output = ModelInferenceLogsUtils.getUserConversations( + user.getPrimaryLoginToken().getId(), projectId, limit, offset, sortDir, search, pinned, + roomOptionsSearch, includeUnnamedRooms, includeChildRooms); return new NounMetadata(output, PixelDataType.VECTOR); } @@ -113,8 +124,12 @@ protected String getDescriptionForKey(String key) { return "Sort direction by room creation date. Accepts ASC or DESC (default is DESC)."; } else if (ReactorKeysEnum.PINNED.getKey().equals(key)) { return "Optional pinned filter: true for pinned rooms only, false for unpinned rooms only, omit for no pinned filter."; - } else if ("roomOptionsSearch".equals(key)) { - return "Optional free-text search term applied against the room's options JSON. Any room whose options contain this substring is included."; + } else if ("roomOptionsSearch".equals(key)) { + return "Optional free-text search term applied against the room's options JSON. Any room whose options contain this substring is included."; + } else if (INCLUDE_UNNAMED_ROOMS.equals(key)) { + return "Whether to include rooms with a null or empty name. Defaults to false."; + } else if (INCLUDE_CHILD_ROOMS.equals(key)) { + return "Whether to include rooms that have a parent room. Defaults to false."; } return super.getDescriptionForKey(key); } diff --git a/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java b/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java index b2aaa2d2b39..96a5fbc79e6 100644 --- a/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java +++ b/src/prerna/engine/impl/model/inferencetracking/reactors/SearchRoomMessagesReactor.java @@ -43,7 +43,8 @@ public class SearchRoomMessagesReactor extends AbstractReactor { private static final Logger classLogger = LogManager.getLogger(SearchRoomMessagesReactor.class); - private static final String INCLUDE_MESSAGE_TEXT = "includeMessageText"; + private static final String INCLUDE_UNNAMED_ROOMS = "includeUnnamedRooms"; + private static final String INCLUDE_CHILD_ROOMS = "includeChildRooms"; public SearchRoomMessagesReactor() { this.keysToGet = new String[] { @@ -51,9 +52,10 @@ public SearchRoomMessagesReactor() { ReactorKeysEnum.SEARCH.getKey(), ReactorKeysEnum.LIMIT.getKey(), ReactorKeysEnum.OFFSET.getKey(), - INCLUDE_MESSAGE_TEXT + INCLUDE_UNNAMED_ROOMS, + INCLUDE_CHILD_ROOMS }; - this.keyRequired = new int[] { 0, 1, 0, 0, 0 }; + this.keyRequired = new int[] { 0, 1, 0, 0, 0, 0 }; } @Override @@ -67,22 +69,47 @@ public NounMetadata execute() { String userId = user.getPrimaryLoginToken().getId(); String projectId = this.keyValue.get(this.keysToGet[0]); - if (projectId == null) { + if (projectId == null || projectId.trim().isEmpty()) { projectId = this.insight.getContextProjectId(); } + if (projectId == null || projectId.trim().isEmpty()) { + projectId = this.insight.getProjectId(); + } + if (projectId == null || projectId.trim().isEmpty()) { + projectId = null; + } String keyword = this.keyValue.get(this.keysToGet[1]); if (keyword == null || keyword.trim().isEmpty()) { throw new IllegalArgumentException("Search keyword must be provided"); } - long limit = getLong(ReactorKeysEnum.LIMIT.getKey(), -1L); - long offset = getLong(ReactorKeysEnum.OFFSET.getKey(), -1L); - boolean includeMessageText = getBoolean(INCLUDE_MESSAGE_TEXT, true); + Long requestedLimit; + Long requestedOffset; + try { + requestedLimit = getLong(ReactorKeysEnum.LIMIT.getKey()); + requestedOffset = getLong(ReactorKeysEnum.OFFSET.getKey()); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Limit and offset must be whole numbers", e); + } + if (requestedLimit != null && requestedLimit <= 0) { + throw new IllegalArgumentException("Limit must be greater than zero"); + } + if (requestedOffset != null && requestedOffset < 0) { + throw new IllegalArgumentException("Offset must be zero or greater"); + } + if (requestedOffset != null && requestedOffset > 0 && requestedLimit == null) { + throw new IllegalArgumentException("A positive limit is required when offset is provided"); + } + long limit = requestedLimit == null ? -1L : requestedLimit; + long offset = requestedOffset == null ? 0L : requestedOffset; + boolean includeUnnamedRooms = getBoolean(INCLUDE_UNNAMED_ROOMS, false); + boolean includeChildRooms = getBoolean(INCLUDE_CHILD_ROOMS, false); List> results; try { - results = ModelInferenceLogsUtils.searchMessages(userId, projectId, keyword, limit, offset, includeMessageText); + results = ModelInferenceLogsUtils.searchMessages(userId, projectId, keyword, limit, offset, + includeUnnamedRooms, includeChildRooms); } catch (Exception e) { classLogger.error("Error searching room messages", e); throw new RuntimeException("Could not search room messages: " + e.getMessage(), e); @@ -94,26 +121,28 @@ public NounMetadata execute() { @Override public String getReactorDescription() { return "Searches through the messages in the user's conversation rooms for a given keyword. " - + "Returns one row per matching room (its most recent matching message): room_id, message_id, " - + "room_name, and date_created. " + + "Returns one row per matching room: room_id, room_name, and the latest matching date_created. " + "Case-insensitive matching is handled by the query framework's ?like comparator. " - + "Optionally returns message_text when includeMessageText=true (default). " - + "Falls back to the current insight's project when projectId is omitted. " + + "Unnamed and child rooms are excluded unless explicitly included. " + + "Falls back to the current insight's context/project when projectId is omitted; " + + "if none is available, searches all projects for the user. " + "Supports limit and offset for pagination over matching rooms."; } @Override protected String getDescriptionForKey(String key) { if (key.equals(ReactorKeysEnum.PROJECT.getKey())) { - return "Optional project ID to scope the search. Defaults to the current insight's project when omitted."; + return "Optional project ID to scope the search. Falls back to the current insight's project, then searches all projects for the user if no project is available."; } else if (key.equals(ReactorKeysEnum.SEARCH.getKey())) { return "The keyword to search for within message content (case-insensitive)."; } else if (key.equals(ReactorKeysEnum.LIMIT.getKey())) { return "Maximum number of results to return. Defaults to no cap when omitted."; } else if (key.equals(ReactorKeysEnum.OFFSET.getKey())) { return "Number of results to skip for pagination."; - } else if (key.equals(INCLUDE_MESSAGE_TEXT)) { - return "Whether to include message_text in results. Defaults to true."; + } else if (key.equals(INCLUDE_UNNAMED_ROOMS)) { + return "Whether to include rooms with a null or empty name. Defaults to false."; + } else if (key.equals(INCLUDE_CHILD_ROOMS)) { + return "Whether to include rooms that have a parent room. Defaults to false."; } return super.getDescriptionForKey(key); } From 1f500e2f04c2b1fddafab0bbb8ab1a51bc8af353 Mon Sep 17 00:00:00 2001 From: Kaveh-Vakili Date: Mon, 17 Aug 2026 15:02:44 -0400 Subject: [PATCH 8/8] fix: use ROOM__DATE_CREATED in SearchRoomMessages to match GetPlaygroundRooms date field --- .../model/inferencetracking/ModelInferenceLogsUtils.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java b/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java index 1119117e2f5..75618c2af98 100644 --- a/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java +++ b/src/prerna/engine/impl/model/inferencetracking/ModelInferenceLogsUtils.java @@ -1515,13 +1515,9 @@ public static List> searchMessages(String userId, String pro IRDBMSEngine modelInferenceLogsDb = SystemEngineRegistry.getModelInferenceLogsDb(); SelectQueryStruct qs = new SelectQueryStruct(); - // Return room-grain results. date_created is the room's latest matching - // message timestamp and drives stable room-level pagination. qs.addSelector(new QueryColumnSelector("ROOM__ROOM_ID", "room_id")); qs.addSelector(new QueryColumnSelector("ROOM__ROOM_NAME", "room_name")); - QueryFunctionSelector latestMatchSelector = QueryFunctionSelector.makeFunctionSelector(QueryFunctionHelper.MAX, - "MESSAGE__DATE_CREATED", "date_created"); - qs.addSelector(latestMatchSelector); + qs.addSelector(new QueryColumnSelector("ROOM__DATE_CREATED", "date_created")); // Use the search-specific conversion so malformed searchable content cannot // abort an otherwise unrelated room/project search. @@ -1546,8 +1542,6 @@ public static List> searchMessages(String userId, String pro qs.addExplicitFilter(SimpleQueryFilter.makeColToValFilter(messageTextSelector, "?like", keyword, PixelDataType.CONST_STRING)); - qs.addGroupBy(new QueryColumnSelector("ROOM__ROOM_ID")); - qs.addGroupBy(new QueryColumnSelector("ROOM__ROOM_NAME")); qs.addOrderBy(new QueryColumnOrderBySelector("date_created", "DESC")); qs.addOrderBy(new QueryColumnOrderBySelector("room_id", "DESC")); if (limit > 0) {