diff --git a/docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md b/docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md index 8fed4d71..2b3e1293 100644 --- a/docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md +++ b/docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md @@ -49,11 +49,11 @@ Hermes task landing checklist: ## Current Baseline -- Current tracking checkpoint: 2026-06-07 P3.55 conversation meta payload/filter parity, prepared as `feat(conversations): align meta counts`. -- Latest implementation checkpoint: this checkpoint, prepared as `feat(conversations): align meta counts`. -- Latest documentation/tooling checkpoint: this tracker update records reused conversation sidebar meta parity from Chatwoot `ConversationsController#meta`, `ConversationFinder#perform_meta_only`, and `meta.json.jbuilder`. No route artifacts change. +- Current tracking checkpoint: 2026-06-07 P3.56 conversation meta query/source parity, prepared as `feat(conversations): filter meta query source`. +- Latest implementation checkpoint: this checkpoint, prepared as `feat(conversations): filter meta query source`. +- Latest documentation/tooling checkpoint: this tracker update records remaining reused conversation sidebar meta filters from Chatwoot `ConversationFinder#filter_by_query` and `#filter_by_source_id`. No route artifacts change. - Plan landing status: complete for the current known Hermes plans and user-confirmed scope. Future work should update this file directly instead of opening a parallel tracker. -- Worktree status at this implementation checkpoint: conversation meta now returns Chatwoot raw `{ meta: { mine_count, assigned_count, unassigned_count, all_count } }`, defaults to open conversations, ignores `assignee_type` for meta-only counts like `ConversationFinder#perform_meta_only`, applies status/inbox/team/labels/conversation_type filters, and scopes non-admin agents to assigned inboxes. P3.54 unread-count parity, P5.8b contact import DataImportJob parity, P3.53 first-response distribution parity, and prior report/profile/channel/contact checkpoints remain implemented. Live API/browser/enterprise smoke still needs the full PostgreSQL/Redis/Meilisearch/GoChat/Vite/Chrome stack. +- Worktree status at this implementation checkpoint: conversation meta now also applies Chatwoot's `q` message-content filter and `source_id` contact-inbox filter before computing `{ mine_count, assigned_count, unassigned_count, all_count }`, while ignoring activity/template messages for query matches like the reference incoming/outgoing-only scope. P3.55 meta payload/filter parity, P3.54 unread-count parity, P5.8b contact import DataImportJob parity, P3.53 first-response distribution parity, and prior report/profile/channel/contact checkpoints remain implemented. Live API/browser/enterprise smoke still needs the full PostgreSQL/Redis/Meilisearch/GoChat/Vite/Chrome stack. - Next executable implementation checkpoint: continue Phase 2/3 drift audit for deeper report data-source/metric-builder drift or the next reused-frontend mismatch, or run B12 live smoke when the full PostgreSQL/Redis/Meilisearch/GoChat/Vite/Chrome stack is available. Re-run Phase 6 placeholder audit after future route/smoke changes. - `go test ./...` passes when run outside the restricted socket sandbox for the latest implementation baseline; the latest docs/tooling checkpoint verified `scripts/parity_frontend_smoke.sh --check` with workspace-local temp/cache dirs after `/tmp` was full. - Route dump succeeds with `967` registered routes after profile MFA route tracking. @@ -156,6 +156,7 @@ This table is the shortest authoritative handoff view. If an older lower section | Priority | Workstream | Current state | Next checkpoint | Commit close rule | | --- | --- | --- | --- | --- | +| 0 | P3.56 conversation meta query/source parity | Implemented for reused conversation sidebar search/source counts: GoChat now follows inspected Chatwoot `ConversationFinder#filter_by_query` and `#filter_by_source_id` inside meta-only counts, joining public incoming/outgoing messages for `q` and contact inboxes for `source_id` before returning raw `{ meta }`. | Keep in Review; reopen from B12 conversation sidebar smoke or fresh reference evidence for PostgreSQL `ILIKE` case-folding drift, duplicate message join count drift, or deeper custom-role permission filtering. | Focused repository/handler meta query/source tests, full `go test ./...`, and `git diff --check` must pass. No route artifacts change. | | 0 | P3.55 conversation meta payload/filter parity | Implemented for reused conversation sidebar counts: GoChat now follows inspected Chatwoot `Api::V1::Accounts::ConversationsController#meta`, `ConversationFinder#perform_meta_only`, and `meta.json.jbuilder` by returning raw `{ meta }`, defaulting counts to open conversations, preserving meta-only `assignee_type` behavior, applying status/inbox/team/labels/conversation_type filters, and enforcing current-user inbox visibility for non-admins. | Keep in Review; reopen from B12 conversation sidebar smoke or fresh reference evidence for deeper custom-role permission filtering, `source_id/q` meta edge cases, or ActsAsTaggableOn label storage drift. | Focused repository/service/handler meta tests, full `go test ./...`, and `git diff --check` must pass. No route artifacts change. | | 0 | P3.54 conversation unread-count payload/permission parity | Implemented for reused sidebar unread badges: GoChat now follows inspected Chatwoot `Api::V1::Accounts::Conversations::UnreadCountsController` and `Conversations::UnreadCounts::Counter` by returning raw `{ payload }`, enforcing the `conversation_unread_counts` feature flag, using label ids for sidebar-visible labels, and applying current-user inbox/team visibility plus custom-role permission modes. | Keep in Review; reopen from B12 sidebar smoke or fresh reference evidence for Redis cache-builder timing, ActionCable unread-count notification throttling, or deeper team/inbox membership edge cases. | Focused conversation service/handler unread-count tests, full `go test ./...`, and `git diff --check` must pass. No route artifacts change. | | 0 | P5.8b contact import DataImportJob parity | Implemented for reused CRM import flow: GoChat now follows inspected Chatwoot `Api::V1::Accounts::ContactsController#import` missing-file error and moves successful imports behind a durable `contact:import` job when a WorkerPool is configured. The pending `data_imports` row carries the uploaded CSV bytes in `import_config` for worker replay until Go has a fuller ActiveStorage-equivalent attachment path. | Keep in Review; reopen from B12 CRM smoke or fresh reference evidence for ActiveStorage attachment metadata, CSV parser edge cases, import notification behavior, or exact `DataImport::ContactManager` validation drift. | Focused contact import service/handler tests, full `go test ./...`, and `git diff --check` must pass. No route artifacts change. | @@ -218,6 +219,7 @@ These rows are the executable development plan from this point forward. A checkp | ID | Owner files | Reference files | Work to land | Exit gate | | --- | --- | --- | --- | --- | +| P3.56 conversation meta query/source parity | `internal/repository/conversation_repo.go`, `internal/service/conversation_service.go`, `internal/handler/api/v1/conversation_handler.go`, conversation repository/handler tests | `reference/chatwoot/app/finders/conversation_finder.rb`, `reference/chatwoot/app/controllers/api/v1/accounts/conversations_controller.rb#meta`, `reference/chatwoot/app/views/api/v1/accounts/conversations/meta.json.jbuilder`, `reference/chatwoot/app/javascript/dashboard/api/inbox/conversation.js` | Conversation meta now applies the remaining finder filters before computing count payloads: `q` joins incoming/outgoing public messages and filters content, while `source_id` joins contact inboxes and filters `contact_inboxes.source_id`; activity/template messages do not satisfy query matches, and the raw `{ meta }` response from P3.55 is preserved. | Review by `feat(conversations): filter meta query source`; focused repository/handler tests cover message query, source filtering, and activity-message exclusion; full `go test ./...` and `git diff --check` must pass. No route artifacts change. | | P3.39 profile MFA route/payload parity | `internal/auth/mfa.go`, `internal/handler/api/v1/mfa_handler.go`, `internal/router/router.go`, `cmd/route_parity/main.go`, profile MFA handler/router tests | `reference/chatwoot/config/routes.rb:421-435`, `reference/chatwoot/app/controllers/api/v1/profiles_controller.rb`, `reference/chatwoot/app/controllers/api/v1/profile/mfa_controller.rb`, `reference/chatwoot/app/views/api/v1/profile/mfa/*.json.jbuilder`, `reference/chatwoot/app/javascript/dashboard/api/mfa.js`, `reference/chatwoot/app/javascript/dashboard/routes/dashboard/settings/profile/MfaSettings.vue` | Done. Profile MFA now matches the reused dashboard route and payload family: no-trailing and trailing profile MFA routes are registered; route parity tracks profile show/update/action plus MFA routes; status/create/verify/destroy/backup-code responses are raw Chatwoot shapes; setup secrets are persisted before verification; frontend `otp_code` enables MFA and returns backup codes; backup-code hashes are stored in user custom attributes; disable validates password plus OTP/backup-code. | Review by `feat(profile): align mfa payloads`; focused MFA handler tests cover raw status, setup, verify, backup-code regeneration, and disable payloads; route artifacts cover profile routes; full `go test ./...` and `git diff --check` must pass. | | P3.38 Facebook callbacks route/payload parity | `internal/handler/api/v1/facebook_channel_handler.go`, `internal/channel/facebook/provider.go`, `internal/model/channel/facebook.go`, `internal/service/inbox_service.go`, `internal/router/router.go`, `cmd/route_parity/main.go`, `migrations/000035_align_facebook_callback_fields.*`, Facebook callback handler/router tests | `reference/chatwoot/config/routes.rb:106-111`, `reference/chatwoot/app/controllers/api/v1/accounts/callbacks_controller.rb`, `reference/chatwoot/app/views/api/v1/accounts/callbacks/*.json.jbuilder`, `reference/chatwoot/spec/controllers/api/v1/accounts/callbacks_controller_spec.rb`, `reference/chatwoot/app/javascript/dashboard/api/channel/fbChannel.js`, `reference/chatwoot/app/javascript/dashboard/api/endPoints.js`, `reference/chatwoot/app/javascript/dashboard/store/modules/inboxes.js`, `reference/chatwoot/app/javascript/dashboard/routes/dashboard/settings/inbox/facebook/Reauthorize.vue` | Done. Facebook callbacks now match the reused dashboard route family: register page, page discovery, `.json` page discovery alias, and reauthorize routes are tracked and registered; callback payloads accept raw frontend params; short-lived login tokens are exchanged through a fakeable provider boundary; page discovery marks existing account pages; register creates Facebook inbox/channel rows and stores user/page tokens; reauthorize refreshes the matching page token and inbox channel config, clears `reauthorization_required`, and returns `{ data: inbox }`. | Review by `feat(channels): align facebook callbacks`; focused handler tests cover register, page discovery `.json`, successful reauthorization, and missing-page `422`; route artifacts cover the four Chatwoot callback routes; full `go test ./...` and `git diff --check` must pass. | | P3.37 account WhatsApp calls API parity | `internal/model/call.go`, `internal/service/whatsapp_call_service.go`, `internal/handler/api/v1/whatsapp_call_handler.go`, `internal/router/router.go`, `cmd/route_parity/main.go`, `migrations/000034_align_calls_whatsapp_fields.*`, WhatsApp call service/handler/router tests | `reference/chatwoot/config/routes.rb:237-245`, `reference/chatwoot/enterprise/app/controllers/api/v1/accounts/whatsapp_calls_controller.rb`, `reference/chatwoot/enterprise/app/views/api/v1/models/_whatsapp_call.json.jbuilder`, `reference/chatwoot/enterprise/app/views/api/v1/accounts/whatsapp_calls/*.json.jbuilder`, `reference/chatwoot/enterprise/app/services/whatsapp/call_service.rb`, `reference/chatwoot/enterprise/app/services/voice/call_message_builder.rb`, `reference/chatwoot/enterprise/app/models/call.rb`, `reference/chatwoot/spec/enterprise/controllers/api/v1/accounts/whatsapp_calls_controller_spec.rb`, `reference/chatwoot/app/javascript/dashboard/api/channel/whatsapp/whatsappCallsAPI.js` | Done. Account WhatsApp calls now match the reused dashboard route family: account-level show/initiate/accept/reject/terminate/upload-recording routes are tracked and registered; initiate resolves conversations by display ID, validates WhatsApp Cloud calling and contact phone, calls a fakeable Meta provider boundary, creates outbound `calls` rows and linked `voice_call` messages, and returns `{ status, call_id }`; accept/reject/terminate update call/message/conversation status; permission requests record conversation attributes; recording upload is idempotent over audio attachments. | Review by `feat(channels): align whatsapp calls api`; focused service tests cover outbound creation, permission request, accept/terminate/recording, guard errors, and no-answer termination; handler tests cover account route payloads; route artifacts cover the six enterprise routes; full `go test ./...` and `git diff --check` must pass. | @@ -298,6 +300,7 @@ This ledger records the committed parity checkpoints that future slices should b | Commit | Scope | Verification summary | Follow-up state | | --- | --- | --- | --- | +| `feat(conversations): filter meta query source` | Advances P3.56 with remaining Chatwoot conversation meta filter parity. `GET /conversations/meta` now applies `ConversationFinder#filter_by_query` and `#filter_by_source_id` before computing raw `{ meta }` counts, joining incoming/outgoing messages for `q` and account contact inboxes for `source_id` so sidebar/search counts follow the same filtered base as the conversation finder. | `go test ./internal/repository ./internal/handler/api/v1 -run 'ConversationRepo_GetMeta\|ConversationHandlerTestSuite/TestMeta' -count=1`; `go test ./internal/repository ./internal/service ./internal/handler/api/v1 -run 'Conversation.*Meta\|ConversationRepo\|ConversationService\|ConversationHandlerTestSuite' -count=1`; `go test ./...`; `git diff --check`. No route artifacts change. | Move P3.56 to Review; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. | | `feat(conversations): align meta counts` | Advances P3.55 with Chatwoot conversation meta count parity. `GET /conversations/meta` now returns raw `{ meta }` instead of the local envelope/status-count shape, computes `mine_count`, `assigned_count`, `unassigned_count`, and `all_count` from the same filtered base as `ConversationFinder#perform_meta_only`, defaults status to open, applies status/inbox/team/labels/conversation_type filters, ignores `assignee_type` for meta-only counts, and scopes non-admin users to assigned inboxes. | `go test ./internal/repository -run 'ConversationRepo_GetMeta' -count=1`; `go test ./internal/service -run 'ConversationService_GetMeta' -count=1`; `go test ./internal/handler/api/v1 -run 'ConversationHandlerTestSuite/TestMeta' -count=1`; `go test ./internal/repository ./internal/service ./internal/handler/api/v1 -run 'Conversation.*Meta\|ConversationRepo\|ConversationService\|ConversationHandlerTestSuite' -count=1`; `go test ./...`; `git diff --check`. No route artifacts change. | Move P3.55 to Review; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. | | `feat(conversations): align unread count payloads` | Advances P3.54 with Chatwoot sidebar unread-count parity. `GET /conversations/unread_counts` now returns raw `{ payload: { inboxes, labels, teams } }`, enforces the `conversation_unread_counts` feature flag with the reference `403` error, emits sidebar label ids instead of label names, and scopes counts by visible inbox/team membership plus custom-role permission modes. | `go test ./internal/service -run 'ConversationService_UnreadCounts' -count=1`; `go test ./internal/handler/api/v1 -run 'ConversationHandlerTestSuite/TestUnreadCounts' -count=1`; `go test ./internal/repository ./internal/service ./internal/handler/api/v1 -run 'UnreadCounts\|ConversationRepo\|ConversationService\|ConversationHandlerTestSuite' -count=1`; `go test ./...`; `git diff --check`. No route artifacts change. | Move P3.54 to Review; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. | | `feat(reports): align first response distribution range` | Advances P3.53 first-response distribution range parity by matching Chatwoot `V2::Reports::FirstResponseTimeDistributionBuilder` and `DateRangeHelper#range`. GoChat now allows `/reports/first_response_time_distribution` without `since/until`, treats partial ranges as no range filter, and still filters reporting events by `created_at >= since AND created_at < until` when both values are present. | `go test ./internal/service -run FirstResponseTimeDistribution -count=1`; `go test ./internal/handler/api/v1 -run AnalyticsHandler -count=1`; full `go test ./...`; `git diff --check`. Route artifacts are unchanged. | P3.53 moves to Review for current distribution builder evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, B12 live smoke, or fresh reference/smoke drift. | @@ -2601,3 +2604,4 @@ Verification milestone gates: - 2026-06-07: P5.8b contact import DataImportJob checkpoint prepared as `feat(crm): queue contact imports`; audited Chatwoot contacts import controller/specs plus `DataImportJob`/contact manager behavior. GoChat contact imports now return the exact missing-file `422 { error: "File is blank" }`, create pending `data_imports` with replayable CSV payloads, enqueue `contact:import` jobs on the low queue when workers are configured, and process imports asynchronously while preserving existing merge, label validation, custom-attribute, status, and counter behavior. Focused contact service and handler tests passed; full `go test ./...` and `git diff --check` passed. No route artifacts change. - 2026-06-07: P3.54 conversation unread-count checkpoint prepared as `feat(conversations): align unread count payloads`; audited Chatwoot unread counts controller, `Conversations::UnreadCounts::Counter`, and reused dashboard `conversationUnreadCounts` store. GoChat now returns raw `{ payload }`, enforces the feature flag with exact `403` error, emits sidebar label ids, and scopes unread badge counts by visible inbox/team membership plus custom-role permission modes. Focused repository/service/handler tests passed; full `go test ./...` and `git diff --check` passed. No route artifacts change. - 2026-06-07: P3.55 conversation meta checkpoint prepared as `feat(conversations): align meta counts`; audited Chatwoot `ConversationsController#meta`, `ConversationFinder#perform_meta_only`, `meta.json.jbuilder`, and reused dashboard conversation API/store. GoChat conversation meta now returns raw `{ meta }` with `mine_count`, `assigned_count`, `unassigned_count`, and `all_count`, defaults to open conversations, preserves meta-only assignee-type behavior, applies status/inbox/team/labels/conversation-type filters, and scopes non-admin agents by assigned inboxes. Focused repository/service/handler tests passed; full `go test ./...` and `git diff --check` passed. No route artifacts change. +- 2026-06-07: P3.56 conversation meta query/source checkpoint prepared as `feat(conversations): filter meta query source`; audited Chatwoot `ConversationFinder#filter_by_query` and `#filter_by_source_id` plus the reused conversation meta route. GoChat meta counts now join incoming/outgoing messages for `q`, ignore activity/template messages, join contact inboxes for `source_id`, and preserve raw `{ meta }` count payloads from P3.55. Focused repository/handler tests passed; full `go test ./...` and `git diff --check` passed. No route artifacts change. diff --git a/internal/handler/api/v1/conversation_handler_test.go b/internal/handler/api/v1/conversation_handler_test.go index df5e3ef8..7d3f62b0 100644 --- a/internal/handler/api/v1/conversation_handler_test.go +++ b/internal/handler/api/v1/conversation_handler_test.go @@ -236,6 +236,31 @@ func (s *ConversationHandlerTestSuite) TestMeta_FiltersStatusAndIgnoresAssigneeT assert.Equal(s.T(), int64(2), resp.Meta.AllCount) } +func (s *ConversationHandlerTestSuite) TestMeta_QueryAndSourceIDFilters() { + contactInbox := &model.ContactInbox{ContactID: s.testConv.ContactID, InboxID: s.testConv.InboxID, SourceID: "widget-source"} + s.Require().NoError(s.db.Create(contactInbox).Error) + s.Require().NoError(s.db.Model(s.testConv).Updates(map[string]any{"contact_inbox_id": contactInbox.ID, "assignee_id": s.testUser.ID}).Error) + other := &model.Conversation{AccountID: s.testAccount.ID, InboxID: s.testConv.InboxID, ContactID: s.testConv.ContactID, Status: "open", ChannelType: "web_widget", Channel: "web_widget"} + s.Require().NoError(s.db.Create(other).Error) + s.Require().NoError(s.db.Create(&model.Message{AccountID: s.testAccount.ID, InboxID: s.testConv.InboxID, ConversationID: s.testConv.ID, Content: "needle from widget", MessageType: string(model.MessageTypeIncoming)}).Error) + s.Require().NoError(s.db.Create(&model.Message{AccountID: s.testAccount.ID, InboxID: s.testConv.InboxID, ConversationID: other.ID, Content: "needle from other", MessageType: string(model.MessageTypeIncoming)}).Error) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", s.accountURL()+"/conversations/meta?q=needle&source_id=widget-source", nil) + s.router.ServeHTTP(w, req) + + assert.Equal(s.T(), http.StatusOK, w.Code) + var resp struct { + Meta struct { + MineCount int64 `json:"mine_count"` + AllCount int64 `json:"all_count"` + } `json:"meta"` + } + s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp)) + assert.Equal(s.T(), int64(1), resp.Meta.AllCount) + assert.Equal(s.T(), int64(1), resp.Meta.MineCount) +} + func (s *ConversationHandlerTestSuite) TestMeta_InvalidAccountID() { w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/invalid/conversations/meta", nil) diff --git a/internal/repository/conversation_repo.go b/internal/repository/conversation_repo.go index 203c8612..1ed25321 100644 --- a/internal/repository/conversation_repo.go +++ b/internal/repository/conversation_repo.go @@ -397,6 +397,8 @@ type ConversationMetaParams struct { TeamID *uint Labels []string ConversationType string + Query string + SourceID string RestrictedInboxIDs []uint RestrictToInboxes bool } @@ -446,6 +448,16 @@ func (r *ConversationRepo) conversationMetaBaseQuery(ctx context.Context, accoun query = query.Where(r.db.Where(strings.Join(conditions, " OR "), args...).Or("conversations.id IN (?)", tagSubquery)) } } + if strings.TrimSpace(params.Query) != "" { + searchTerm := "%" + strings.TrimSpace(params.Query) + "%" + query = query.Joins("JOIN messages ON messages.conversation_id = conversations.id"). + Where("messages.content LIKE ?", searchTerm). + Where("messages.message_type IN ?", []string{string(model.MessageTypeIncoming), string(model.MessageTypeOutgoing)}) + } + if strings.TrimSpace(params.SourceID) != "" { + query = query.Joins("JOIN contact_inboxes ON contact_inboxes.id = conversations.contact_inbox_id"). + Where("contact_inboxes.source_id = ?", strings.TrimSpace(params.SourceID)) + } switch params.ConversationType { case "mention": diff --git a/internal/repository/conversation_repo_test.go b/internal/repository/conversation_repo_test.go index c9cd2495..d49918de 100644 --- a/internal/repository/conversation_repo_test.go +++ b/internal/repository/conversation_repo_test.go @@ -523,6 +523,39 @@ func TestConversationRepo_GetMeta_FiltersStatusLabelsAndConversationType(t *test assert.Equal(t, int64(1), meta.UnassignedCount) } +func TestConversationRepo_GetMeta_FiltersQueryAndSourceID(t *testing.T) { + db := setupTestDB(t) + repo := NewConversationRepo(db) + userID := uint(88) + + account := &model.Account{Name: "MetaQueryOrg", Locale: "en", Active: true} + require.NoError(t, db.Create(account).Error) + inbox := &model.Inbox{AccountID: account.ID, Name: "MetaQueryInbox", ChannelType: "web_widget", ChannelID: 1} + require.NoError(t, db.Create(inbox).Error) + contact := &model.Contact{AccountID: account.ID, Name: "MetaQueryContact"} + require.NoError(t, db.Create(contact).Error) + contactInbox := &model.ContactInbox{ContactID: contact.ID, InboxID: inbox.ID, SourceID: "source-a"} + require.NoError(t, db.Create(contactInbox).Error) + otherContactInbox := &model.ContactInbox{ContactID: contact.ID, InboxID: inbox.ID, SourceID: "source-b"} + require.NoError(t, db.Create(otherContactInbox).Error) + + match := createTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open") + require.NoError(t, db.Model(match).Updates(map[string]any{"contact_inbox_id": contactInbox.ID, "assignee_id": userID}).Error) + otherSource := createTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open") + require.NoError(t, db.Model(otherSource).Update("contact_inbox_id", otherContactInbox.ID).Error) + activityOnly := createTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open") + require.NoError(t, db.Model(activityOnly).Update("contact_inbox_id", contactInbox.ID).Error) + + require.NoError(t, db.Create(&model.Message{AccountID: account.ID, InboxID: inbox.ID, ConversationID: match.ID, Content: "needle visible", MessageType: string(model.MessageTypeIncoming)}).Error) + require.NoError(t, db.Create(&model.Message{AccountID: account.ID, InboxID: inbox.ID, ConversationID: otherSource.ID, Content: "needle wrong source", MessageType: string(model.MessageTypeOutgoing)}).Error) + require.NoError(t, db.Create(&model.Message{AccountID: account.ID, InboxID: inbox.ID, ConversationID: activityOnly.ID, Content: "needle activity", MessageType: string(model.MessageTypeActivity)}).Error) + + meta, err := repo.GetMeta(context.Background(), account.ID, userID, ConversationMetaParams{Query: "needle", SourceID: "source-a"}) + require.NoError(t, err) + assert.Equal(t, int64(1), meta.AllCount) + assert.Equal(t, int64(1), meta.MineCount) +} + // ========== MarkUnread Tests ========== func TestConversationRepo_MarkUnread(t *testing.T) { diff --git a/internal/service/conversation_service.go b/internal/service/conversation_service.go index 82d8d5ab..28cca9d2 100644 --- a/internal/service/conversation_service.go +++ b/internal/service/conversation_service.go @@ -930,6 +930,8 @@ func (s *ConversationService) GetMeta(ctx context.Context, accountID, userID uin TeamID: params.TeamID, Labels: splitConversationMetaLabels(params.Labels), ConversationType: params.ConversationType, + Query: params.Query, + SourceID: params.SourceID, } if userID != 0 {