From ccb3672ff88c85308750acead257abdae8702aa1 Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Tue, 20 May 2025 16:04:56 +0530 Subject: [PATCH 001/111] fix: Status not updating when creating a Linear issue (#11523) --- .../api/v1/accounts/integrations/linear_controller.rb | 3 ++- lib/linear.rb | 3 ++- .../api/v1/accounts/integrations/linear_controller_spec.rb | 1 + spec/lib/integrations/linear/processor_service_spec.rb | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/v1/accounts/integrations/linear_controller.rb b/app/controllers/api/v1/accounts/integrations/linear_controller.rb index 4e5348e88..c66f06909 100644 --- a/app/controllers/api/v1/accounts/integrations/linear_controller.rb +++ b/app/controllers/api/v1/accounts/integrations/linear_controller.rb @@ -94,7 +94,8 @@ class Api::V1::Accounts::Integrations::LinearController < Api::V1::Accounts::Bas end def permitted_params - params.permit(:team_id, :project_id, :conversation_id, :issue_id, :link_id, :title, :description, :assignee_id, :priority, label_ids: []) + params.permit(:team_id, :project_id, :conversation_id, :issue_id, :link_id, :title, :description, :assignee_id, :priority, :state_id, + label_ids: []) end def fetch_hook diff --git a/lib/linear.rb b/lib/linear.rb index 9a998c34a..8bf967fc3 100644 --- a/lib/linear.rb +++ b/lib/linear.rb @@ -57,7 +57,8 @@ class Linear assigneeId: params[:assignee_id], priority: params[:priority], labelIds: params[:label_ids], - projectId: params[:project_id] + projectId: params[:project_id], + stateId: params[:state_id] }.compact mutation = Linear::Mutations.issue_create(variables) response = post({ query: mutation }) diff --git a/spec/controllers/api/v1/accounts/integrations/linear_controller_spec.rb b/spec/controllers/api/v1/accounts/integrations/linear_controller_spec.rb index b1341e65e..0f27e2bd2 100644 --- a/spec/controllers/api/v1/accounts/integrations/linear_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/integrations/linear_controller_spec.rb @@ -100,6 +100,7 @@ RSpec.describe 'Linear Integration API', type: :request do description: 'This is a sample issue.', assignee_id: 'user1', priority: 'high', + state_id: 'state1', label_ids: ['label1'] } end diff --git a/spec/lib/integrations/linear/processor_service_spec.rb b/spec/lib/integrations/linear/processor_service_spec.rb index 07cf27654..807e93c71 100644 --- a/spec/lib/integrations/linear/processor_service_spec.rb +++ b/spec/lib/integrations/linear/processor_service_spec.rb @@ -76,6 +76,7 @@ describe Integrations::Linear::ProcessorService do description: 'Issue description', assignee_id: 'user1', priority: 2, + state_id: 'state1', label_ids: %w[bug] } end From 27ec791353dadc990a67b10701e19981421f6470 Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Wed, 21 May 2025 04:09:18 +0530 Subject: [PATCH 002/111] fix: Display message content for CSAT messages in non-widget inboxes (#11528) We made so many improvements for CSAT via https://github.com/chatwoot/chatwoot/pull/11485. However, we missed showing message content in the dashboard for CSAT URLs created in non-widget inboxes. This PR fixes the issue by ensuring that CSAT-configured messages are passed along with CSAT responses, otherwise defaulting to the translation. --- app/models/message.rb | 8 +++++++- spec/models/message_spec.rb | 31 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/app/models/message.rb b/app/models/message.rb index f5d7712d2..a952e0265 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -185,7 +185,13 @@ class Message < ApplicationRecord # move this to a presenter return self[:content] if !input_csat? || inbox.web_widget? - I18n.t('conversations.survey.response', link: "#{ENV.fetch('FRONTEND_URL', nil)}/survey/responses/#{conversation.uuid}") + survey_link = "#{ENV.fetch('FRONTEND_URL', nil)}/survey/responses/#{conversation.uuid}" + + if inbox.csat_config&.dig('message').present? + "#{inbox.csat_config['message']} #{survey_link}" + else + I18n.t('conversations.survey.response', link: survey_link) + end end def email_notifiable_message? diff --git a/spec/models/message_spec.rb b/spec/models/message_spec.rb index 4c66e37ee..b4be3ac5a 100644 --- a/spec/models/message_spec.rb +++ b/spec/models/message_spec.rb @@ -475,4 +475,35 @@ RSpec.describe Message do end end end + + describe '#content' do + let(:conversation) { create(:conversation) } + let(:message) { create(:message, conversation: conversation, content_type: 'input_csat', content: 'Original content') } + + it 'returns original content for web widget inbox' do + allow(message.inbox).to receive(:web_widget?).and_return(true) + expect(message.content).to eq('Original content') + end + + context 'when inbox is not a web widget' do + before do + allow(message.inbox).to receive(:web_widget?).and_return(false) + allow(ENV).to receive(:fetch).with('FRONTEND_URL', nil).and_return('https://app.chatwoot.com') + end + + it 'returns custom message with survey link when csat message is configured' do + allow(message.inbox).to receive(:csat_config).and_return({ 'message' => 'Custom survey message:' }) + expected_content = "Custom survey message: https://app.chatwoot.com/survey/responses/#{conversation.uuid}" + expect(message.content).to eq(expected_content) + end + + it 'returns default message with survey link when no custom csat message' do + allow(message.inbox).to receive(:csat_config).and_return(nil) + allow(I18n).to receive(:t).with('conversations.survey.response', link: "https://app.chatwoot.com/survey/responses/#{conversation.uuid}") + .and_return("Please rate your conversation: https://app.chatwoot.com/survey/responses/#{conversation.uuid}") + expected_content = "Please rate your conversation: https://app.chatwoot.com/survey/responses/#{conversation.uuid}" + expect(message.content).to eq(expected_content) + end + end + end end From 2ee63656e24633a8c74f6f0e1e724d8f7fed9a28 Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Wed, 21 May 2025 06:04:30 +0530 Subject: [PATCH 003/111] feat: Prevent saving preferences and status when impersonating (#11164) This PR will prevent saving user preferences and online status when impersonating. Previously, these settings could be updated during impersonation, causing the user to see a different view or UI settings. Fixes https://linear.app/chatwoot/issue/CW-4163/impersonation-improvements --- .../sidebar/SidebarProfileMenuStatus.vue | 7 + .../components/layout/AvailabilityStatus.vue | 12 ++ .../composables/spec/useImpersonation.spec.js | 37 +++++ .../dashboard/composables/useImpersonation.js | 10 ++ .../dashboard/constants/sessionStorage.js | 3 + .../dashboard/i18n/locale/en/settings.json | 3 +- .../dashboard/store/modules/auth.js | 13 +- app/javascript/dashboard/store/utils/api.js | 7 + .../shared/helpers/sessionStorage.js | 26 ++++ .../helpers/specs/sessionStorage.spec.js | 137 ++++++++++++++++++ app/javascript/v3/views/login/Index.vue | 14 +- app/models/concerns/sso_authenticatable.rb | 4 + app/views/super_admin/users/_impersonate.erb | 2 +- 13 files changed, 270 insertions(+), 5 deletions(-) create mode 100644 app/javascript/dashboard/composables/spec/useImpersonation.spec.js create mode 100644 app/javascript/dashboard/composables/useImpersonation.js create mode 100644 app/javascript/dashboard/constants/sessionStorage.js create mode 100644 app/javascript/shared/helpers/sessionStorage.js create mode 100644 app/javascript/shared/helpers/specs/sessionStorage.spec.js diff --git a/app/javascript/dashboard/components-next/sidebar/SidebarProfileMenuStatus.vue b/app/javascript/dashboard/components-next/sidebar/SidebarProfileMenuStatus.vue index 0fdf96162..fef196162 100644 --- a/app/javascript/dashboard/components-next/sidebar/SidebarProfileMenuStatus.vue +++ b/app/javascript/dashboard/components-next/sidebar/SidebarProfileMenuStatus.vue @@ -4,6 +4,7 @@ import { useMapGetter, useStore } from 'dashboard/composables/store'; import wootConstants from 'dashboard/constants/globals'; import { useAlert } from 'dashboard/composables'; import { useI18n } from 'vue-i18n'; +import { useImpersonation } from 'dashboard/composables/useImpersonation'; import { DropdownContainer, @@ -20,6 +21,8 @@ const currentUserAvailability = useMapGetter('getCurrentUserAvailability'); const currentAccountId = useMapGetter('getCurrentAccountId'); const currentUserAutoOffline = useMapGetter('getCurrentUserAutoOffline'); +const { isImpersonating } = useImpersonation(); + const { AVAILABILITY_STATUS_KEYS } = wootConstants; const statusList = computed(() => { return [ @@ -46,6 +49,10 @@ const activeStatus = computed(() => { }); function changeAvailabilityStatus(availability) { + if (isImpersonating.value) { + useAlert(t('PROFILE_SETTINGS.FORM.AVAILABILITY.IMPERSONATING_ERROR')); + return; + } try { store.dispatch('updateAvailability', { availability, diff --git a/app/javascript/dashboard/components/layout/AvailabilityStatus.vue b/app/javascript/dashboard/components/layout/AvailabilityStatus.vue index 04190c7a4..a4995aee8 100644 --- a/app/javascript/dashboard/components/layout/AvailabilityStatus.vue +++ b/app/javascript/dashboard/components/layout/AvailabilityStatus.vue @@ -1,6 +1,7 @@ + + diff --git a/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactsCard.vue b/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactsCard.vue index a3b0bf37c..b2b0dbfa0 100644 --- a/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactsCard.vue +++ b/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactsCard.vue @@ -7,6 +7,7 @@ import ContactsForm from 'dashboard/components-next/Contacts/ContactsForm/Contac import Button from 'dashboard/components-next/button/Button.vue'; import Avatar from 'dashboard/components-next/avatar/Avatar.vue'; import Flag from 'dashboard/components-next/flag/Flag.vue'; +import ContactDeleteSection from 'dashboard/components-next/Contacts/ContactsCard/ContactDeleteSection.vue'; import countries from 'shared/constants/countries'; const props = defineProps({ @@ -149,15 +150,15 @@ const onClickViewDetails = () => emit('showContact', props.id); /> diff --git a/app/javascript/dashboard/components-next/Contacts/ContactsForm/ConfirmContactDeleteDialog.vue b/app/javascript/dashboard/components-next/Contacts/ContactsForm/ConfirmContactDeleteDialog.vue index d9c0deb1b..f43a50883 100644 --- a/app/javascript/dashboard/components-next/Contacts/ContactsForm/ConfirmContactDeleteDialog.vue +++ b/app/javascript/dashboard/components-next/Contacts/ContactsForm/ConfirmContactDeleteDialog.vue @@ -47,11 +47,7 @@ defineExpose({ dialogRef }); ref="dialogRef" type="alert" :title="t('CONTACTS_LAYOUT.DETAILS.DELETE_DIALOG.TITLE')" - :description=" - t('CONTACTS_LAYOUT.DETAILS.DELETE_DIALOG.DESCRIPTION', { - contactName: props.selectedContact.name, - }) - " + :description="t('CONTACTS_LAYOUT.DETAILS.DELETE_DIALOG.DESCRIPTION')" :confirm-button-label="t('CONTACTS_LAYOUT.DETAILS.DELETE_DIALOG.CONFIRM')" @confirm="handleDialogConfirm" /> diff --git a/app/javascript/dashboard/components-next/button/Button.vue b/app/javascript/dashboard/components-next/button/Button.vue index c54bd395a..dde1d3d9a 100644 --- a/app/javascript/dashboard/components-next/button/Button.vue +++ b/app/javascript/dashboard/components-next/button/Button.vue @@ -117,7 +117,7 @@ const STYLE_CONFIG = { 'text-n-ruby-11 hover:enabled:bg-n-ruby-9/10 focus-visible:bg-n-ruby-9/10 outline-n-ruby-8', ghost: 'text-n-ruby-11 hover:enabled:bg-n-alpha-2 focus-visible:bg-n-alpha-2 outline-transparent', - link: 'text-n-ruby-9 hover:enabled:underline focus-visible:underline outline-transparent', + link: 'text-n-ruby-9 dark:text-n-ruby-11 hover:enabled:underline focus-visible:underline outline-transparent', }, amber: { solid: diff --git a/app/javascript/dashboard/i18n/locale/en/contact.json b/app/javascript/dashboard/i18n/locale/en/contact.json index fc4509809..4c599ffe8 100644 --- a/app/javascript/dashboard/i18n/locale/en/contact.json +++ b/app/javascript/dashboard/i18n/locale/en/contact.json @@ -458,6 +458,10 @@ "PLACEHOLDER": "Add Twitter" } } + }, + "DELETE_CONTACT": { + "MESSAGE": "This action is permanent and irreversible.", + "BUTTON": "Delete now" } }, "DETAILS": { @@ -467,7 +471,7 @@ "DELETE_CONTACT": "Delete contact", "DELETE_DIALOG": { "TITLE": "Confirm Deletion", - "DESCRIPTION": "Are you sure you want to delete this {contactName} contact?", + "DESCRIPTION": "Are you sure you want to delete this contact?", "CONFIRM": "Yes, Delete", "API": { "SUCCESS_MESSAGE": "Contact deleted successfully", From 1602b071db8079909cb04c13c279959ff6fbe6ac Mon Sep 17 00:00:00 2001 From: Pranav Date: Tue, 20 May 2025 21:35:29 -0700 Subject: [PATCH 008/111] feat: Add components to show steps in the copilot thinking process (#11530) This PR adds the components for new Copilot UI - Added a Header component - Added a thinking block. - Update the outline on copilot input --------- Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> --- .../copilot/CopilotHeader.story.vue | 21 +++++++ .../components-next/copilot/CopilotHeader.vue | 32 ++++++++++ .../components-next/copilot/CopilotInput.vue | 9 +-- .../copilot/CopilotThinkingBlock.vue | 25 ++++++++ .../copilot/CopilotThinkingGroup.story.vue | 34 +++++++++++ .../copilot/CopilotThinkingGroup.vue | 61 +++++++++++++++++++ .../i18n/locale/en/integrations.json | 2 + 7 files changed, 178 insertions(+), 6 deletions(-) create mode 100644 app/javascript/dashboard/components-next/copilot/CopilotHeader.story.vue create mode 100644 app/javascript/dashboard/components-next/copilot/CopilotHeader.vue create mode 100644 app/javascript/dashboard/components-next/copilot/CopilotThinkingBlock.vue create mode 100644 app/javascript/dashboard/components-next/copilot/CopilotThinkingGroup.story.vue create mode 100644 app/javascript/dashboard/components-next/copilot/CopilotThinkingGroup.vue diff --git a/app/javascript/dashboard/components-next/copilot/CopilotHeader.story.vue b/app/javascript/dashboard/components-next/copilot/CopilotHeader.story.vue new file mode 100644 index 000000000..78a345093 --- /dev/null +++ b/app/javascript/dashboard/components-next/copilot/CopilotHeader.story.vue @@ -0,0 +1,21 @@ + + + diff --git a/app/javascript/dashboard/components-next/copilot/CopilotHeader.vue b/app/javascript/dashboard/components-next/copilot/CopilotHeader.vue new file mode 100644 index 000000000..c7a8696f3 --- /dev/null +++ b/app/javascript/dashboard/components-next/copilot/CopilotHeader.vue @@ -0,0 +1,32 @@ + + + diff --git a/app/javascript/dashboard/components-next/copilot/CopilotInput.vue b/app/javascript/dashboard/components-next/copilot/CopilotInput.vue index bf14945b5..c8f1a0056 100644 --- a/app/javascript/dashboard/components-next/copilot/CopilotInput.vue +++ b/app/javascript/dashboard/components-next/copilot/CopilotInput.vue @@ -13,19 +13,16 @@ const sendMessage = () => {