From 17e44bdc16a8eafaca6a0874ea7e58870e22c750 Mon Sep 17 00:00:00 2001 From: Gabriel Birman <25272206+gbirman@users.noreply.github.com> Date: Thu, 11 Dec 2025 14:15:26 -0500 Subject: [PATCH 1/3] update search types from backend --- .../models/documentSearchResponseItem.ts | 2 ++ .../documentSearchResponseItemSubType.ts | 9 +++++ .../generated/models/documentSubType.ts | 18 ++++++++++ .../generated/models/emailSearchResult.ts | 11 +++--- .../models/emailSearchResultSender.ts | 12 ------- .../service-search/generated/models/index.ts | 3 +- .../service-search/openapi.json | 35 ++++++++++++++++--- 7 files changed, 69 insertions(+), 21 deletions(-) create mode 100644 js/app/packages/service-clients/service-search/generated/models/documentSearchResponseItemSubType.ts create mode 100644 js/app/packages/service-clients/service-search/generated/models/documentSubType.ts delete mode 100644 js/app/packages/service-clients/service-search/generated/models/emailSearchResultSender.ts diff --git a/js/app/packages/service-clients/service-search/generated/models/documentSearchResponseItem.ts b/js/app/packages/service-clients/service-search/generated/models/documentSearchResponseItem.ts index f83ab8b18fe..624187efe41 100644 --- a/js/app/packages/service-clients/service-search/generated/models/documentSearchResponseItem.ts +++ b/js/app/packages/service-clients/service-search/generated/models/documentSearchResponseItem.ts @@ -6,6 +6,7 @@ */ import type { DocumentSearchResult } from './documentSearchResult'; import type { DocumentSearchResponseItemFileType } from './documentSearchResponseItemFileType'; +import type { DocumentSearchResponseItemSubType } from './documentSearchResponseItemSubType'; /** * A single response item, part of the DocumentSearchResponse object @@ -26,4 +27,5 @@ for consistency in our data model. */ id: string; name: string; owner_id: string; + sub_type?: DocumentSearchResponseItemSubType; } diff --git a/js/app/packages/service-clients/service-search/generated/models/documentSearchResponseItemSubType.ts b/js/app/packages/service-clients/service-search/generated/models/documentSearchResponseItemSubType.ts new file mode 100644 index 00000000000..e75e055811c --- /dev/null +++ b/js/app/packages/service-clients/service-search/generated/models/documentSearchResponseItemSubType.ts @@ -0,0 +1,9 @@ +/** + * Generated by orval v7.13.0 🍺 + * Do not edit manually. + * search_service + * OpenAPI spec version: 0.1.0 + */ +import type { DocumentSubType } from './documentSubType'; + +export type DocumentSearchResponseItemSubType = null | DocumentSubType; diff --git a/js/app/packages/service-clients/service-search/generated/models/documentSubType.ts b/js/app/packages/service-clients/service-search/generated/models/documentSubType.ts new file mode 100644 index 00000000000..b6b112dc44f --- /dev/null +++ b/js/app/packages/service-clients/service-search/generated/models/documentSubType.ts @@ -0,0 +1,18 @@ +/** + * Generated by orval v7.13.0 🍺 + * Do not edit manually. + * search_service + * OpenAPI spec version: 0.1.0 + */ + +/** + * The document sub type enum represents all values of document sub types. +These values should match the `document_sub_type_value` table in macrodb. + */ +export type DocumentSubType = typeof DocumentSubType[keyof typeof DocumentSubType]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const DocumentSubType = { + task: 'task', +} as const; diff --git a/js/app/packages/service-clients/service-search/generated/models/emailSearchResult.ts b/js/app/packages/service-clients/service-search/generated/models/emailSearchResult.ts index 073dfc96faa..5f37d150263 100644 --- a/js/app/packages/service-clients/service-search/generated/models/emailSearchResult.ts +++ b/js/app/packages/service-clients/service-search/generated/models/emailSearchResult.ts @@ -7,7 +7,6 @@ import type { SearchHighlight } from './searchHighlight'; import type { EmailSearchResultMessageId } from './emailSearchResultMessageId'; import type { EmailSearchResultScore } from './emailSearchResultScore'; -import type { EmailSearchResultSender } from './emailSearchResultSender'; import type { EmailSearchResultSentAt } from './emailSearchResultSentAt'; /** @@ -25,13 +24,17 @@ export interface EmailSearchResult { /** The email message id. This is only present if the search result is on the message content */ message_id?: EmailSearchResultMessageId; + /** The pretty sender. +If the match is on the subject, the pretty sender is the latest sender on the thread. +This could be the sender's email if there is no contact name for the sender. */ + pretty_sender: string; /** This is only present if the search result is on the message content */ recipients: string[]; /** The score of the result */ score?: EmailSearchResultScore; - /** The sender -This is only present if the search result is on the message content */ - sender?: EmailSearchResultSender; + /** The sender. +If the match is on the subject, the sender is the latest sender on the thread. */ + sender: string; /** When the email message was sent This is only present if the search result is on the message content */ sent_at?: EmailSearchResultSentAt; diff --git a/js/app/packages/service-clients/service-search/generated/models/emailSearchResultSender.ts b/js/app/packages/service-clients/service-search/generated/models/emailSearchResultSender.ts deleted file mode 100644 index fc23160c9e1..00000000000 --- a/js/app/packages/service-clients/service-search/generated/models/emailSearchResultSender.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Generated by orval v7.13.0 🍺 - * Do not edit manually. - * search_service - * OpenAPI spec version: 0.1.0 - */ - -/** - * The sender -This is only present if the search result is on the message content - */ -export type EmailSearchResultSender = string | null; diff --git a/js/app/packages/service-clients/service-search/generated/models/index.ts b/js/app/packages/service-clients/service-search/generated/models/index.ts index 140fd0b4c5d..b1ff46ee5d2 100644 --- a/js/app/packages/service-clients/service-search/generated/models/index.ts +++ b/js/app/packages/service-clients/service-search/generated/models/index.ts @@ -66,6 +66,7 @@ export * from './documentSearchRequestAllOfTwoTerms'; export * from './documentSearchResponse'; export * from './documentSearchResponseItem'; export * from './documentSearchResponseItemFileType'; +export * from './documentSearchResponseItemSubType'; export * from './documentSearchResponseItemWithMetadata'; export * from './documentSearchResponseItemWithMetadataAllOf'; export * from './documentSearchResponseItemWithMetadataAllOfMetadata'; @@ -73,6 +74,7 @@ export * from './documentSearchResult'; export * from './documentSearchResultNodeId'; export * from './documentSearchResultRawContent'; export * from './documentSearchResultScore'; +export * from './documentSubType'; export * from './emailFilters'; export * from './emailSearchParams'; export * from './emailSearchRequest'; @@ -92,7 +94,6 @@ export * from './emailSearchResponseItemWithMetadataAllOfViewedAt'; export * from './emailSearchResult'; export * from './emailSearchResultMessageId'; export * from './emailSearchResultScore'; -export * from './emailSearchResultSender'; export * from './emailSearchResultSentAt'; export * from './emptyResponse'; export * from './errorResponse'; diff --git a/js/app/packages/service-clients/service-search/openapi.json b/js/app/packages/service-clients/service-search/openapi.json index fd073242e05..27b64078e5b 100644 --- a/js/app/packages/service-clients/service-search/openapi.json +++ b/js/app/packages/service-clients/service-search/openapi.json @@ -1317,7 +1317,16 @@ "description": "Standardized fields that all item types will share.\nThese field names are being aligned across all item types\nfor consistency in our data model." }, "name": { "type": "string" }, - "owner_id": { "type": "string" } + "owner_id": { "type": "string" }, + "sub_type": { + "oneOf": [ + { "type": "null" }, + { + "$ref": "#/components/schemas/DocumentSubType", + "description": "The sub type of the document if present." + } + ] + } } }, "DocumentSearchResponseItemWithMetadata": { @@ -1364,6 +1373,11 @@ } } }, + "DocumentSubType": { + "type": "string", + "description": "The document sub type enum represents all values of document sub types.\nThese values should match the `document_sub_type_value` table in macrodb.", + "enum": ["task"] + }, "EmailFilters": { "type": "object", "description": "The email filters used to filter down what emails you search over.", @@ -1506,7 +1520,15 @@ "EmailSearchResult": { "type": "object", "description": "A email message match for a given thread id", - "required": ["recipients", "cc", "bcc", "labels", "highlight"], + "required": [ + "sender", + "pretty_sender", + "recipients", + "cc", + "bcc", + "labels", + "highlight" + ], "properties": { "bcc": { "type": "array", @@ -1529,8 +1551,13 @@ }, "message_id": { "type": ["string", "null"], + "format": "uuid", "description": "The email message id.\nThis is only present if the search result is on the message content" }, + "pretty_sender": { + "type": "string", + "description": "The pretty sender.\nIf the match is on the subject, the pretty sender is the latest sender on the thread.\nThis could be the sender's email if there is no contact name for the sender." + }, "recipients": { "type": "array", "items": { "type": "string" }, @@ -1542,8 +1569,8 @@ "description": "The score of the result" }, "sender": { - "type": ["string", "null"], - "description": "The sender\nThis is only present if the search result is on the message content" + "type": "string", + "description": "The sender.\nIf the match is on the subject, the sender is the latest sender on the thread." }, "sent_at": { "type": ["integer", "null"], From e77ef613273107745d5a24318438086b3913b2cc Mon Sep 17 00:00:00 2001 From: Gabriel Birman <25272206+gbirman@users.noreply.github.com> Date: Thu, 11 Dec 2025 15:27:20 -0500 Subject: [PATCH 2/3] email search improve --- .../macro-entity/src/queries/search.ts | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/js/app/packages/macro-entity/src/queries/search.ts b/js/app/packages/macro-entity/src/queries/search.ts index b9db054c5de..e3caf2ceb85 100644 --- a/js/app/packages/macro-entity/src/queries/search.ts +++ b/js/app/packages/macro-entity/src/queries/search.ts @@ -1,7 +1,6 @@ import { useChannelsContext } from '@core/component/ChannelsProvider'; import { blockNameToDefaultFile } from '@core/constant/allBlocks'; import { ENABLE_SEARCH_SERVICE } from '@core/constant/featureFlags'; -import { idToDisplayName } from '@core/user'; import { isErr } from '@core/util/maybeResult'; import { extractSearchSnippet, @@ -183,7 +182,16 @@ const useMapSearchResponseItem = () => { }; } case 'email': { - const emailResult = result.email_message_search_results.at(0); + const messageHits = result.email_message_search_results.filter( + (m) => m.message_id + ); + // NOTE: guaranteed to be empty or singleton array + const threadHits = result.email_message_search_results.filter( + (m) => !m.message_id + ); + + const singleMessage = messageHits.length === 1; + const search = getSearchData({ results: result.email_message_search_results, type: 'email', @@ -191,42 +199,34 @@ const useMapSearchResponseItem = () => { const name = result.name ?? blockNameToDefaultFile('email'); - const senderName = - emailResult?.sender || - result.email_message_search_results.at(1)?.sender || - idToDisplayName(result.user_id); + // TODO: display sender for each message in the content hit list + const combinedSenders = + messageHits.map((m) => m.pretty_sender).join(', ') || + threadHits.at(0)?.pretty_sender; - // NOTE: this shouldn't happen unless it's a name only match that doesn't produce a name highlight for some reason - // including this as a fallback for now - if (!emailResult) { - return { - type: 'email', - id: result.thread_id, - name, - ownerId: result.owner_id, - createdAt: result.created_at, - updatedAt: result.updated_at, - viewedAt: result.viewed_at ?? undefined, - isRead: false, - isImportant: false, - done: false, - senderName, - search, - }; - } + // TODO: we probably want to get the actual latest message info on the full thread + const latestMessageSentAt = Math.max( + ...messageHits.map((m) => m.sent_at).filter((m) => m != null) + ); return { type: 'email', id: result.thread_id, name, ownerId: result.owner_id, - createdAt: emailResult.sent_at ?? result.updated_at, - updatedAt: emailResult.sent_at ?? result.updated_at, + createdAt: latestMessageSentAt ?? result.created_at, + updatedAt: latestMessageSentAt ?? result.updated_at, viewedAt: result.viewed_at ?? undefined, - isRead: !emailResult.labels.includes('UNREAD'), - isImportant: emailResult.labels.includes('IMPORTANT'), - done: !emailResult.labels.includes('INBOX'), - senderName, + isRead: singleMessage + ? !messageHits[0].labels.includes('UNREAD') + : false, + isImportant: singleMessage + ? messageHits[0].labels.includes('IMPORTANT') + : false, + done: singleMessage + ? !messageHits[0].labels.includes('INBOX') + : false, + senderName: combinedSenders, search, }; } From d793b10f40adbec5ac85fee2391edec204863952 Mon Sep 17 00:00:00 2001 From: Gabriel Birman <25272206+gbirman@users.noreply.github.com> Date: Thu, 11 Dec 2025 15:52:37 -0500 Subject: [PATCH 3/3] deduplicate names --- js/app/packages/macro-entity/src/queries/search.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/app/packages/macro-entity/src/queries/search.ts b/js/app/packages/macro-entity/src/queries/search.ts index e3caf2ceb85..fbe4a533589 100644 --- a/js/app/packages/macro-entity/src/queries/search.ts +++ b/js/app/packages/macro-entity/src/queries/search.ts @@ -201,7 +201,7 @@ const useMapSearchResponseItem = () => { // TODO: display sender for each message in the content hit list const combinedSenders = - messageHits.map((m) => m.pretty_sender).join(', ') || + [...new Set(messageHits.map((m) => m.pretty_sender))].join(', ') || threadHits.at(0)?.pretty_sender; // TODO: we probably want to get the actual latest message info on the full thread