Skip to content

Instantly share code, notes, and snippets.

@shricodev
Created March 30, 2026 16:21
Show Gist options
  • Select an option

  • Save shricodev/314fa9f6d90859d6146d49d040eecf4e to your computer and use it in GitHub Desktop.

Select an option

Save shricodev/314fa9f6d90859d6146d49d040eecf4e to your computer and use it in GitHub Desktop.
Chatwoot - Opus 4.6
From 6ddb34fe2c9ecba3dd8b0b086ba72244d71bcfaa Mon Sep 17 00:00:00 2001
From: Shrijal Acharya <shrijal.acharya@gmail.com>
Date: Sat, 21 Mar 2026 23:04:31 +0545
Subject: [PATCH] opus 4.6
---
app/finders/conversation_finder.rb | 17 ++++++++++++-----
.../dashboard/components/ChatList.vue | 6 +++++-
app/javascript/dashboard/constants/globals.js | 1 +
.../dashboard/constants/permissions.js | 4 ++++
.../dashboard/i18n/locale/en/chatlist.json | 1 +
.../store/modules/conversationStats.js | 3 +++
.../store/modules/conversations/getters.js | 5 +++++
.../specs/conversationStats/getters.spec.js | 2 ++
.../specs/conversationStats/mutations.spec.js | 2 ++
app/listeners/participation_listener.rb | 10 ++++++++++
.../accounts/conversations/index.json.jbuilder | 1 +
.../accounts/conversations/meta.json.jbuilder | 1 +
docker-compose.yaml | 10 +++++-----
13 files changed, 52 insertions(+), 11 deletions(-)
diff --git a/app/finders/conversation_finder.rb b/app/finders/conversation_finder.rb
index fa437327d..cc3cf6148 100644
--- a/app/finders/conversation_finder.rb
+++ b/app/finders/conversation_finder.rb
@@ -40,7 +40,7 @@ class ConversationFinder
def perform
set_up
- mine_count, unassigned_count, all_count, = set_count_for_all_conversations
+ mine_count, unassigned_count, all_count, participating_count = set_count_for_all_conversations
assigned_count = all_count - unassigned_count
filter_by_assignee_type
@@ -51,7 +51,8 @@ class ConversationFinder
mine_count: mine_count,
assigned_count: assigned_count,
unassigned_count: unassigned_count,
- all_count: all_count
+ all_count: all_count,
+ participating_count: participating_count
}
}
end
@@ -59,7 +60,7 @@ class ConversationFinder
def perform_meta_only
set_up
- mine_count, unassigned_count, all_count, = set_count_for_all_conversations
+ mine_count, unassigned_count, all_count, participating_count = set_count_for_all_conversations
assigned_count = all_count - unassigned_count
{
@@ -67,7 +68,8 @@ class ConversationFinder
mine_count: mine_count,
assigned_count: assigned_count,
unassigned_count: unassigned_count,
- all_count: all_count
+ all_count: all_count,
+ participating_count: participating_count
}
}
end
@@ -131,6 +133,10 @@ class ConversationFinder
@conversations = @conversations.unassigned
when 'assigned'
@conversations = @conversations.assigned
+ when 'participating'
+ @conversations = @conversations.where(
+ id: current_user.participating_conversations.select(:id)
+ )
end
@conversations
end
@@ -187,7 +193,8 @@ class ConversationFinder
[
@conversations.assigned_to(current_user).count,
@conversations.unassigned.count,
- @conversations.count
+ @conversations.count,
+ @conversations.where(id: current_user.participating_conversations.select(:id)).count
]
end
diff --git a/app/javascript/dashboard/components/ChatList.vue b/app/javascript/dashboard/components/ChatList.vue
index eeb2ad6e7..460ec2e92 100644
--- a/app/javascript/dashboard/components/ChatList.vue
+++ b/app/javascript/dashboard/components/ChatList.vue
@@ -111,6 +111,7 @@ const advancedFilterTypes = ref(
const currentUser = useMapGetter('getCurrentUser');
const chatLists = useMapGetter('getFilteredConversations');
const mineChatsList = useMapGetter('getMineChats');
+const participatingChatsList = useMapGetter('getParticipatingChats');
const allChatList = useMapGetter('getAllStatusChats');
const unAssignedChatsList = useMapGetter('getUnAssignedChats');
const chatListLoading = useMapGetter('getChatListLoadingStatus');
@@ -207,7 +208,8 @@ const assigneeTabItems = computed(() => {
const showAssigneeInConversationCard = computed(() => {
return (
hasAppliedFiltersOrActiveFolders.value ||
- activeAssigneeTab.value === wootConstants.ASSIGNEE_TYPE.ALL
+ activeAssigneeTab.value === wootConstants.ASSIGNEE_TYPE.ALL ||
+ activeAssigneeTab.value === wootConstants.ASSIGNEE_TYPE.PARTICIPATING
);
});
@@ -318,6 +320,8 @@ const conversationList = computed(() => {
const filters = conversationFilters.value;
if (activeAssigneeTab.value === 'me') {
localConversationList = [...mineChatsList.value(filters)];
+ } else if (activeAssigneeTab.value === 'participating') {
+ localConversationList = [...participatingChatsList.value(filters)];
} else if (activeAssigneeTab.value === 'unassigned') {
localConversationList = [...unAssignedChatsList.value(filters)];
} else {
diff --git a/app/javascript/dashboard/constants/globals.js b/app/javascript/dashboard/constants/globals.js
index 21303efcb..fadbcaffd 100644
--- a/app/javascript/dashboard/constants/globals.js
+++ b/app/javascript/dashboard/constants/globals.js
@@ -2,6 +2,7 @@ export default {
GRAVATAR_URL: 'https://www.gravatar.com/avatar/',
ASSIGNEE_TYPE: {
ME: 'me',
+ PARTICIPATING: 'participating',
UNASSIGNED: 'unassigned',
ALL: 'all',
},
diff --git a/app/javascript/dashboard/constants/permissions.js b/app/javascript/dashboard/constants/permissions.js
index 1ecbd9793..2189215d6 100644
--- a/app/javascript/dashboard/constants/permissions.js
+++ b/app/javascript/dashboard/constants/permissions.js
@@ -34,6 +34,10 @@ export const ASSIGNEE_TYPE_TAB_PERMISSIONS = {
count: 'mineCount',
permissions: [...ROLES, ...CONVERSATION_PERMISSIONS],
},
+ participating: {
+ count: 'participatingCount',
+ permissions: [...ROLES, ...CONVERSATION_PERMISSIONS],
+ },
unassigned: {
count: 'unAssignedCount',
permissions: [
diff --git a/app/javascript/dashboard/i18n/locale/en/chatlist.json b/app/javascript/dashboard/i18n/locale/en/chatlist.json
index 0e8e87a04..c652797d8 100644
--- a/app/javascript/dashboard/i18n/locale/en/chatlist.json
+++ b/app/javascript/dashboard/i18n/locale/en/chatlist.json
@@ -16,6 +16,7 @@
"FILTER_ALL": "All",
"ASSIGNEE_TYPE_TABS": {
"me": "Mine",
+ "participating": "Participating",
"unassigned": "Unassigned",
"all": "All"
},
diff --git a/app/javascript/dashboard/store/modules/conversationStats.js b/app/javascript/dashboard/store/modules/conversationStats.js
index ba3e5c455..2d85d9f08 100644
--- a/app/javascript/dashboard/store/modules/conversationStats.js
+++ b/app/javascript/dashboard/store/modules/conversationStats.js
@@ -4,6 +4,7 @@ import { debounce } from '@chatwoot/utils';
const state = {
mineCount: 0,
+ participatingCount: 0,
unAssignedCount: 0,
allCount: 0,
};
@@ -54,11 +55,13 @@ export const mutations = {
$state,
{
mine_count: mineCount,
+ participating_count: participatingCount,
unassigned_count: unAssignedCount,
all_count: allCount,
} = {}
) {
$state.mineCount = mineCount;
+ $state.participatingCount = participatingCount;
$state.allCount = allCount;
$state.unAssignedCount = unAssignedCount;
$state.updatedOn = new Date();
diff --git a/app/javascript/dashboard/store/modules/conversations/getters.js b/app/javascript/dashboard/store/modules/conversations/getters.js
index 9f5744fbb..11b9a296f 100644
--- a/app/javascript/dashboard/store/modules/conversations/getters.js
+++ b/app/javascript/dashboard/store/modules/conversations/getters.js
@@ -102,6 +102,11 @@ const getters = {
return isUnAssigned && shouldFilter;
});
},
+ getParticipatingChats: _state => activeFilters => {
+ return _state.allConversations.filter(conversation => {
+ return applyPageFilters(conversation, activeFilters);
+ });
+ },
getAllStatusChats: (_state, _, __, rootGetters) => activeFilters => {
const currentUser = rootGetters.getCurrentUser;
const currentUserId = rootGetters.getCurrentUser.id;
diff --git a/app/javascript/dashboard/store/modules/specs/conversationStats/getters.spec.js b/app/javascript/dashboard/store/modules/specs/conversationStats/getters.spec.js
index 38b7a88bc..feb19e888 100644
--- a/app/javascript/dashboard/store/modules/specs/conversationStats/getters.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/conversationStats/getters.spec.js
@@ -4,11 +4,13 @@ describe('#getters', () => {
it('getCurrentPage', () => {
const state = {
mineCount: 1,
+ participatingCount: 1,
unAssignedCount: 1,
allCount: 2,
};
expect(getters.getStats(state)).toEqual({
mineCount: 1,
+ participatingCount: 1,
unAssignedCount: 1,
allCount: 2,
});
diff --git a/app/javascript/dashboard/store/modules/specs/conversationStats/mutations.spec.js b/app/javascript/dashboard/store/modules/specs/conversationStats/mutations.spec.js
index e625eb13c..22b702861 100644
--- a/app/javascript/dashboard/store/modules/specs/conversationStats/mutations.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/conversationStats/mutations.spec.js
@@ -7,11 +7,13 @@ describe('#mutations', () => {
const state = {};
mutations[types.SET_CONV_TAB_META](state, {
mine_count: 1,
+ participating_count: 1,
unassigned_count: 1,
all_count: 2,
});
expect(state).toEqual({
mineCount: 1,
+ participatingCount: 1,
unAssignedCount: 1,
allCount: 2,
updatedOn: expect.any(Date),
diff --git a/app/listeners/participation_listener.rb b/app/listeners/participation_listener.rb
index b9f94e252..e7d08aa5e 100644
--- a/app/listeners/participation_listener.rb
+++ b/app/listeners/participation_listener.rb
@@ -12,4 +12,14 @@ class ParticipationListener < BaseListener
Rails.logger.warn "Failed to create conversation participant for account #{conversation.account.id} " \
": user #{conversation.assignee_id} : conversation #{conversation.id}"
end
+
+ def message_created(event)
+ message = extract_message_and_account(event)[0]
+ return unless message.sender_type == 'User'
+ return unless message.outgoing? || message.private?
+
+ message.conversation.conversation_participants.find_or_create_by!(user_id: message.sender_id)
+ rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid
+ # Ignore race conditions
+ end
end
diff --git a/app/views/api/v1/accounts/conversations/index.json.jbuilder b/app/views/api/v1/accounts/conversations/index.json.jbuilder
index 670899f38..9ab045316 100644
--- a/app/views/api/v1/accounts/conversations/index.json.jbuilder
+++ b/app/views/api/v1/accounts/conversations/index.json.jbuilder
@@ -4,6 +4,7 @@ json.data do
json.assigned_count @conversations_count[:assigned_count]
json.unassigned_count @conversations_count[:unassigned_count]
json.all_count @conversations_count[:all_count]
+ json.participating_count @conversations_count[:participating_count]
end
json.payload do
json.array! @conversations do |conversation|
diff --git a/app/views/api/v1/accounts/conversations/meta.json.jbuilder b/app/views/api/v1/accounts/conversations/meta.json.jbuilder
index 0326ad52f..84cca79e6 100644
--- a/app/views/api/v1/accounts/conversations/meta.json.jbuilder
+++ b/app/views/api/v1/accounts/conversations/meta.json.jbuilder
@@ -3,4 +3,5 @@ json.meta do
json.assigned_count @conversations_count[:assigned_count]
json.unassigned_count @conversations_count[:unassigned_count]
json.all_count @conversations_count[:all_count]
+ json.participating_count @conversations_count[:participating_count]
end
diff --git a/docker-compose.yaml b/docker-compose.yaml
index facac830f..9ea7ff85c 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -41,7 +41,7 @@ services:
- NODE_ENV=development
- RAILS_ENV=development
entrypoint: docker/entrypoints/rails.sh
- command: ["bundle", "exec", "rails", "s", "-p", "3000", "-b", "0.0.0.0"]
+ command: ['bundle', 'exec', 'rails', 's', '-p', '3000', '-b', '0.0.0.0']
sidekiq:
<<: *base
@@ -59,7 +59,7 @@ services:
environment:
- NODE_ENV=development
- RAILS_ENV=development
- command: ["bundle", "exec", "sidekiq", "-C", "config/sidekiq.yml"]
+ command: ['bundle', 'exec', 'sidekiq', '-C', 'config/sidekiq.yml']
vite:
<<: *base
@@ -74,7 +74,7 @@ services:
- cache:/app/tmp/cache
- bundle:/usr/local/bundle
ports:
- - "3036:3036" # Vite dev server
+ - '3036:3036' # Vite dev server
environment:
- VITE_DEV_SERVER_HOST=0.0.0.0
- NODE_ENV=development
@@ -92,12 +92,12 @@ services:
environment:
- POSTGRES_DB=chatwoot
- POSTGRES_USER=postgres
- - POSTGRES_PASSWORD=
+ - POSTGRES_PASSWORD=chatwootpass
redis:
image: redis:alpine
restart: always
- command: ["sh", "-c", "redis-server --requirepass \"$REDIS_PASSWORD\""]
+ command: ['sh', '-c', 'redis-server --requirepass "$REDIS_PASSWORD"']
env_file: .env
volumes:
- redis:/data/redis
--
2.53.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment