From 53e25852753f5ef50cf095ab0c46277a1cebe891 Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Tue, 10 Jun 2025 23:29:25 +0530 Subject: [PATCH 1/4] feat: Show conversation count for filters/folders in header (#11698) # Pull Request Template ## Description Fixes https://linear.app/chatwoot/issue/CW-4467/show-the-conversation-count-at-the-top-for-filters-and-folders ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? ### Screenshots image image ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --- .../dashboard/components/ChatList.vue | 2 + .../dashboard/components/ChatListHeader.vue | 40 +++++++++---------- package.json | 2 +- pnpm-lock.yaml | 10 ++--- 4 files changed, 28 insertions(+), 26 deletions(-) diff --git a/app/javascript/dashboard/components/ChatList.vue b/app/javascript/dashboard/components/ChatList.vue index 098bc41c1..de895c76e 100644 --- a/app/javascript/dashboard/components/ChatList.vue +++ b/app/javascript/dashboard/components/ChatList.vue @@ -853,6 +853,8 @@ watch(conversationFilters, (newVal, oldVal) => { :has-active-folders="hasActiveFolders" :active-status="activeStatus" :is-on-expanded-layout="isOnExpandedLayout" + :conversation-stats="conversationStats" + :is-list-loading="chatListLoading" @add-folders="onClickOpenAddFoldersModal" @delete-folders="onClickOpenDeleteFoldersModal" @filters-modal="onToggleAdvanceFiltersModal" diff --git a/app/javascript/dashboard/components/ChatListHeader.vue b/app/javascript/dashboard/components/ChatListHeader.vue index b4ce9f342..c184103c0 100644 --- a/app/javascript/dashboard/components/ChatListHeader.vue +++ b/app/javascript/dashboard/components/ChatListHeader.vue @@ -2,6 +2,7 @@ import { computed } from 'vue'; import { useUISettings } from 'dashboard/composables/useUISettings'; import { useMapGetter } from 'dashboard/composables/store.js'; +import { formatNumber } from '@chatwoot/utils'; import wootConstants from 'dashboard/constants/globals'; import { FEATURE_FLAGS } from 'dashboard/featureFlags'; @@ -10,26 +11,13 @@ import SwitchLayout from 'dashboard/routes/dashboard/conversation/search/SwitchL import NextButton from 'dashboard/components-next/button/Button.vue'; const props = defineProps({ - pageTitle: { - type: String, - required: true, - }, - hasAppliedFilters: { - type: Boolean, - required: true, - }, - hasActiveFolders: { - type: Boolean, - required: true, - }, - activeStatus: { - type: String, - required: true, - }, - isOnExpandedLayout: { - type: Boolean, - required: true, - }, + pageTitle: { type: String, required: true }, + hasAppliedFilters: { type: Boolean, required: true }, + hasActiveFolders: { type: Boolean, required: true }, + activeStatus: { type: String, required: true }, + isOnExpandedLayout: { type: Boolean, required: true }, + conversationStats: { type: Object, required: true }, + isListLoading: { type: Boolean, required: true }, }); const emit = defineEmits([ @@ -62,6 +50,9 @@ const showV4View = computed(() => { ); }); +const allCount = computed(() => props.conversationStats?.allCount || 0); +const formattedAllCount = computed(() => formatNumber(allCount.value)); + const toggleConversationLayout = () => { const { LAYOUT_TYPES } = wootConstants; const { @@ -92,6 +83,15 @@ const toggleConversationLayout = () => { > {{ pageTitle }} + + {{ formattedAllCount }} + =10'} '@codemirror/commands@6.7.0': @@ -5255,7 +5255,7 @@ snapshots: prosemirror-utils: 1.2.2(prosemirror-model@1.22.3)(prosemirror-state@1.4.3) prosemirror-view: 1.34.1 - '@chatwoot/utils@0.0.46': + '@chatwoot/utils@0.0.47': dependencies: date-fns: 2.30.0 From 4a83e701581de9ec0624b1e1f22366d681410e69 Mon Sep 17 00:00:00 2001 From: Pranav Date: Tue, 10 Jun 2025 15:12:32 -0400 Subject: [PATCH 2/4] fix: Avoid throwing 406 for non-json requests (#11701) Users get confused between app routes and API routes. Instead of hitting /api, they append /app in the API call, which ends up calling the dashboard controller and throws an error. To fix this, we added a check to throw a 406 Not Acceptable for non-HTML requests. But Meta requires Accept: \*/\* to return 200 for the integration to be accepted. This change will only throw an error for JSON requests. Fixes #11697 Fixes https://github.com/chatwoot/chatwoot/issues/11251 Fixes https://github.com/chatwoot/chatwoot/issues/11205 --- app/controllers/dashboard_controller.rb | 2 +- spec/controllers/dashboard_controller_spec.rb | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index a2cb466f1..6a4ce2461 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -15,7 +15,7 @@ class DashboardController < ActionController::Base private def ensure_html_format - head :not_acceptable unless request.format.html? + render json: { error: 'Please use API routes instead of dashboard routes for JSON requests' }, status: :not_acceptable if request.format.json? end def set_global_config diff --git a/spec/controllers/dashboard_controller_spec.rb b/spec/controllers/dashboard_controller_spec.rb index 517443858..e022e22c7 100644 --- a/spec/controllers/dashboard_controller_spec.rb +++ b/spec/controllers/dashboard_controller_spec.rb @@ -19,9 +19,10 @@ describe '/app/login', type: :request do end context 'with non-HTML format' do - it 'returns not acceptable for JSON' do - get '/app/login', params: { format: 'json' } + it 'returns not acceptable for JSON with error message' do + get '/app/login', headers: { 'Accept' => 'application/json' } expect(response).to have_http_status(:not_acceptable) + expect(response.parsed_body).to eq({ 'error' => 'Please use API routes instead of dashboard routes for JSON requests' }) end end From 4303007786d527755bece2a2795729041c00fc27 Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Wed, 11 Jun 2025 01:10:02 +0530 Subject: [PATCH 3/4] feat: Enhance Linear integration UX with multi-issue support and improved placement (#11668) Fixes https://linear.app/chatwoot/issue/CW-4150/support-for-multiple-issues-linking-in-linear This PR significantly improves the Linear integration user experience by relocating the Linear integration from the conversation header to the contact panel and adding support for multiple issue linking per conversation. ### Key Changes - **Relocated Linear integration**: Moved from conversation header to contact panel for better organization and accessibility - **Multi-issue support**: Added ability to link/create multiple Linear issues for a single conversation - **Integration CTA**: Added a dedicated call-to-action section for users who haven't connected their Linear account yet - **UI/UX improvements**: Enhanced design consistency and user flow
Screenshots #### Multiple Issues Support ![link-multiple-issues](https://github.com/user-attachments/assets/b56cfa7d-6f98-42db-b4bb-361ae59d0eae) #### Integration CTA ![link-multiple-issues](https://github.com/user-attachments/assets/a895fcbe-780a-47f8-9fa4-3a2af8b243e1) --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: iamsivin Co-authored-by: Pranav Co-authored-by: Pranav --- .../conversation/ConversationHeader.vue | 24 -- .../widgets/conversation/linear/Issue.vue | 123 ------- .../conversation/linear/IssueHeader.vue | 44 ++- .../conversation/linear/IssuesList.vue | 132 ++++++++ .../conversation/linear/LinearIssueItem.vue | 111 +++++++ .../conversation/linear/LinearSetupCTA.vue | 54 ++++ .../widgets/conversation/linear/index.vue | 161 ---------- .../dashboard/composables/useUISettings.js | 1 + .../i18n/locale/en/conversation.json | 1 + .../i18n/locale/en/integrations.json | 7 + .../dashboard/conversation/ContactPanel.vue | 301 ++++++++++-------- 11 files changed, 494 insertions(+), 465 deletions(-) delete mode 100644 app/javascript/dashboard/components/widgets/conversation/linear/Issue.vue create mode 100644 app/javascript/dashboard/components/widgets/conversation/linear/IssuesList.vue create mode 100644 app/javascript/dashboard/components/widgets/conversation/linear/LinearIssueItem.vue create mode 100644 app/javascript/dashboard/components/widgets/conversation/linear/LinearSetupCTA.vue delete mode 100644 app/javascript/dashboard/components/widgets/conversation/linear/index.vue diff --git a/app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue b/app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue index ec05b0eb4..7a3489265 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue @@ -11,8 +11,6 @@ import SLACardLabel from './components/SLACardLabel.vue'; import wootConstants from 'dashboard/constants/globals'; import { conversationListPageURL } from 'dashboard/helper/URLHelper'; import { snoozedReopenTime } from 'dashboard/helper/snoozeHelpers'; -import { FEATURE_FLAGS } from 'dashboard/featureFlags'; -import Linear from './linear/index.vue'; import { useInbox } from 'dashboard/composables/useInbox'; import { useI18n } from 'vue-i18n'; @@ -36,12 +34,6 @@ const { isAWebWidgetInbox } = useInbox(); const currentChat = computed(() => store.getters.getSelectedChat); const accountId = computed(() => store.getters.getCurrentAccountId); -const isFeatureEnabledonAccount = computed( - () => store.getters['accounts/isFeatureEnabledonAccount'] -); -const appIntegrations = computed( - () => store.getters['integrations/getAppIntegrations'] -); const chatMetadata = computed(() => props.chat.meta); @@ -92,16 +84,6 @@ const hasMultipleInboxes = computed( ); const hasSlaPolicyId = computed(() => props.chat?.sla_policy_id); - -const isLinearIntegrationEnabled = computed(() => - appIntegrations.value.find( - integration => integration.id === 'linear' && !!integration.hooks.length - ) -); - -const isLinearFeatureEnabled = computed(() => - isFeatureEnabledonAccount.value(accountId.value, FEATURE_FLAGS.LINEAR) -);