fix: keep pending widget conversations visible and searchable
Build and publish Docker images / Build and publish images (push) Successful in 2m25s
Build and publish Docker images / Build and publish images (push) Successful in 2m25s
This commit is contained in:
@@ -808,6 +808,7 @@ func Bootstrap(env string) (*App, error) {
|
||||
widgetOfflineMessageRepo := repository.NewWidgetOfflineMessageRepo(db)
|
||||
widgetService := service.NewWidgetService(inboxRepo, contactRepo, contactInboxRepo, conversationRepo, messageRepo, widgetTypingAdapter, widgetThemeConfigRepo, preChatFormRepo, widgetFileUploadRepo, widgetOfflineMessageRepo, inboxMemberRepo, tagRepo, campaignRepo)
|
||||
widgetService.SetWorkerPool(workerPool)
|
||||
widgetService.SetSearchIndexer(searchIndexer)
|
||||
widgetService.SetDispatcher(channelDispatcher)
|
||||
widgetService.SetRealtimeEventBridge(realtimeBridge)
|
||||
widgetHandler := widget.NewHandler(widgetService)
|
||||
|
||||
@@ -68,6 +68,7 @@ type WidgetService struct {
|
||||
worker *worker.WorkerPool
|
||||
dispatcher *channel.Dispatcher
|
||||
realtime *wsevent.BridgeListener
|
||||
searchIndexer SearchIndexer
|
||||
}
|
||||
|
||||
// NewWidgetService creates a new Widget service.
|
||||
@@ -120,6 +121,22 @@ func (s *WidgetService) SetRealtimeEventBridge(bridge *wsevent.BridgeListener) {
|
||||
s.realtime = bridge
|
||||
}
|
||||
|
||||
func (s *WidgetService) SetSearchIndexer(indexer SearchIndexer) {
|
||||
s.searchIndexer = indexer
|
||||
}
|
||||
|
||||
func (s *WidgetService) indexConversation(ctx context.Context, conversation *model.Conversation) {
|
||||
if s.searchIndexer != nil && conversation != nil {
|
||||
logSearchIndexError("conversation", conversation.ID, s.searchIndexer.IndexConversation(ctx, conversation))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WidgetService) indexMessage(ctx context.Context, message *model.Message) {
|
||||
if s.searchIndexer != nil && message != nil {
|
||||
logSearchIndexError("message", message.ID, s.searchIndexer.IndexMessage(ctx, message))
|
||||
}
|
||||
}
|
||||
|
||||
// --- DTOs ---
|
||||
|
||||
// WidgetInitRequest is the DTO for the /widget/init endpoint.
|
||||
@@ -536,6 +553,8 @@ func (s *WidgetService) createIncomingMessage(ctx context.Context, conversation
|
||||
if s.realtime != nil {
|
||||
s.realtime.PublishEnqueued(ctx, realtimeJobs)
|
||||
}
|
||||
s.indexMessage(ctx, message)
|
||||
s.indexConversation(ctx, conversation)
|
||||
return attachments, nil
|
||||
}
|
||||
|
||||
@@ -967,6 +986,7 @@ func (s *WidgetService) PublicCreateConversation(ctx context.Context, inboxIdent
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
s.indexConversation(ctx, conversation)
|
||||
return conversation, nil
|
||||
}
|
||||
|
||||
@@ -996,6 +1016,7 @@ func (s *WidgetService) PublicToggleStatus(ctx context.Context, inboxIdentifier,
|
||||
if err := s.conversationRepo.Update(ctx, conversation); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.indexConversation(ctx, conversation)
|
||||
return conversation, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -749,6 +749,8 @@ func TestWidgetService_SendMessage_AutomaticCaptainTakeover(t *testing.T) {
|
||||
require.NoError(t, db.Create(&model.AgentBotInbox{AgentBotID: bot.ID, InboxID: inbox.ID, Status: model.AgentBotInboxActive}).Error)
|
||||
require.NoError(t, db.Create(&model.CaptainPreference{AccountID: account.ID, AutoReplyEnabled: true}).Error)
|
||||
svc.SetWorkerPool(worker.NewWorkerPool(db))
|
||||
indexer := &mockServiceSearchIndexer{}
|
||||
svc.SetSearchIndexer(indexer)
|
||||
|
||||
initResp, err := svc.Init(context.Background(), WidgetInitRequest{WebsiteToken: "test_ws_token_123"})
|
||||
require.NoError(t, err)
|
||||
@@ -761,6 +763,8 @@ func TestWidgetService_SendMessage_AutomaticCaptainTakeover(t *testing.T) {
|
||||
require.NotNil(t, conversation.AssigneeAgentBotID)
|
||||
assert.Equal(t, bot.ID, *conversation.AssigneeAgentBotID)
|
||||
assert.Equal(t, uint(1), conversation.AITakeoverVersion)
|
||||
assert.Contains(t, indexer.indexedConversationIDs, response.ConversationID)
|
||||
assert.Contains(t, indexer.indexed, "message")
|
||||
var job model.BackgroundJob
|
||||
require.NoError(t, db.Where("job_type = ?", TaskTypeCaptainConversationResponseBuilder).First(&job).Error)
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
filterItemsByPermission,
|
||||
} from 'dashboard/helper/permissionsHelper.js';
|
||||
import { matchesFilters } from '../store/modules/conversations/helpers/filterHelpers';
|
||||
import { isConversationUnassigned } from '../store/modules/conversations/helpers';
|
||||
import { CONVERSATION_EVENTS } from '../helper/AnalyticsHelper/events';
|
||||
import { ASSIGNEE_TYPE_TAB_PERMISSIONS } from 'dashboard/constants/permissions.js';
|
||||
|
||||
@@ -305,7 +306,7 @@ function filterByAssigneeTab(conversations) {
|
||||
);
|
||||
}
|
||||
if (activeAssigneeTab.value === wootConstants.ASSIGNEE_TYPE.UNASSIGNED) {
|
||||
return conversations.filter(c => !c.meta?.assignee);
|
||||
return conversations.filter(isConversationUnassigned);
|
||||
}
|
||||
return [...conversations];
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { MESSAGE_TYPE } from 'shared/constants/messages';
|
||||
import { applyPageFilters, applyRoleFilter, sortComparator } from './helpers';
|
||||
import {
|
||||
applyPageFilters,
|
||||
applyRoleFilter,
|
||||
isConversationUnassigned,
|
||||
sortComparator,
|
||||
} from './helpers';
|
||||
import filterQueryGenerator from 'dashboard/helper/filterQueryGenerator';
|
||||
import { matchesFilters } from './helpers/filterHelpers';
|
||||
import {
|
||||
@@ -99,7 +104,7 @@ const getters = {
|
||||
},
|
||||
getUnAssignedChats: _state => activeFilters => {
|
||||
return _state.allConversations.filter(conversation => {
|
||||
const isUnAssigned = !conversation.meta.assignee;
|
||||
const isUnAssigned = isConversationUnassigned(conversation);
|
||||
const shouldFilter = applyPageFilters(conversation, activeFilters);
|
||||
return isUnAssigned && shouldFilter;
|
||||
});
|
||||
|
||||
@@ -11,6 +11,11 @@ export const findPendingMessageIndex = (chat, message) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const isConversationUnassigned = conversation => {
|
||||
const { assignee, assignee_type: assigneeType } = conversation.meta || {};
|
||||
return !assignee || assigneeType === 'AgentBot';
|
||||
};
|
||||
|
||||
export const filterByStatus = (chatStatus, filterStatus) =>
|
||||
filterStatus === 'all' ? true : chatStatus === filterStatus;
|
||||
|
||||
@@ -95,7 +100,7 @@ export const applyRoleFilter = (
|
||||
}
|
||||
|
||||
const conversationAssignee = conversation.meta.assignee;
|
||||
const isUnassigned = !conversationAssignee;
|
||||
const isUnassigned = isConversationUnassigned(conversation);
|
||||
const isAssignedToUser = conversationAssignee?.id === currentUserId;
|
||||
|
||||
// Check unassigned management permission
|
||||
|
||||
@@ -159,6 +159,13 @@ describe('#getters', () => {
|
||||
meta: { team: { id: 5 } },
|
||||
labels: ['sales'],
|
||||
},
|
||||
{
|
||||
id: 33,
|
||||
inbox_id: 4,
|
||||
status: 1,
|
||||
meta: { assignee: { id: 9 }, assignee_type: 'AgentBot' },
|
||||
labels: [],
|
||||
},
|
||||
];
|
||||
|
||||
expect(
|
||||
@@ -180,6 +187,13 @@ describe('#getters', () => {
|
||||
meta: { team: { id: 5 } },
|
||||
labels: ['sales'],
|
||||
},
|
||||
{
|
||||
id: 33,
|
||||
inbox_id: 4,
|
||||
status: 1,
|
||||
meta: { assignee: { id: 9 }, assignee_type: 'AgentBot' },
|
||||
labels: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
findPendingMessageIndex,
|
||||
isConversationUnassigned,
|
||||
applyPageFilters,
|
||||
filterByInbox,
|
||||
filterByTeam,
|
||||
@@ -56,6 +57,24 @@ describe('#findPendingMessageIndex', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#isConversationUnassigned', () => {
|
||||
it('treats an agent-bot conversation as unassigned for agent queues', () => {
|
||||
expect(
|
||||
isConversationUnassigned({
|
||||
meta: { assignee: { id: 7 }, assignee_type: 'AgentBot' },
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('does not treat a human-assigned conversation as unassigned', () => {
|
||||
expect(
|
||||
isConversationUnassigned({
|
||||
meta: { assignee: { id: 7 }, assignee_type: 'User' },
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#applyPageFilters', () => {
|
||||
describe('#filter-team', () => {
|
||||
it('returns true if conversation has team and team filter is active', () => {
|
||||
|
||||
Reference in New Issue
Block a user