From d5f34bf9d0d2e0cdd3773ad2b7cb76911cbf1c23 Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Sun, 11 Aug 2024 10:09:08 +0530 Subject: [PATCH 01/50] feat: Replace `conversation/teamMixin` within the components (#9923) --- .../i18n/locale/en/teamsSettings.json | 3 ++- .../mixins/conversation/teamMixin.js | 22 ------------------- .../routes/dashboard/commands/commandbar.vue | 2 -- .../dashboard/commands/conversationHotKeys.js | 14 +++++++++++- .../conversation/ConversationAction.vue | 16 ++++++++++++-- 5 files changed, 29 insertions(+), 28 deletions(-) delete mode 100644 app/javascript/dashboard/mixins/conversation/teamMixin.js diff --git a/app/javascript/dashboard/i18n/locale/en/teamsSettings.json b/app/javascript/dashboard/i18n/locale/en/teamsSettings.json index c39c03569..6cbe55032 100644 --- a/app/javascript/dashboard/i18n/locale/en/teamsSettings.json +++ b/app/javascript/dashboard/i18n/locale/en/teamsSettings.json @@ -7,7 +7,8 @@ "LEARN_MORE": "Learn more about teams", "LIST": { "404": "There are no teams created on this account.", - "EDIT_TEAM": "Edit team" + "EDIT_TEAM": "Edit team", + "NONE": "None" }, "CREATE_FLOW": { "CREATE": { diff --git a/app/javascript/dashboard/mixins/conversation/teamMixin.js b/app/javascript/dashboard/mixins/conversation/teamMixin.js deleted file mode 100644 index 745f91589..000000000 --- a/app/javascript/dashboard/mixins/conversation/teamMixin.js +++ /dev/null @@ -1,22 +0,0 @@ -import { mapGetters } from 'vuex'; - -export default { - computed: { - ...mapGetters({ teams: 'teams/getTeams' }), - hasAnAssignedTeam() { - return !!this.currentChat?.meta?.team; - }, - teamsList() { - if (this.hasAnAssignedTeam) { - return [ - { - id: 0, - name: 'None', - }, - ...this.teams, - ]; - } - return this.teams; - }, - }, -}; diff --git a/app/javascript/dashboard/routes/dashboard/commands/commandbar.vue b/app/javascript/dashboard/routes/dashboard/commands/commandbar.vue index dff1f53f4..136295228 100644 --- a/app/javascript/dashboard/routes/dashboard/commands/commandbar.vue +++ b/app/javascript/dashboard/routes/dashboard/commands/commandbar.vue @@ -8,7 +8,6 @@ import goToCommandHotKeys from './goToCommandHotKeys'; import appearanceHotKeys from './appearanceHotKeys'; import agentMixin from 'dashboard/mixins/agentMixin'; import conversationLabelMixin from 'dashboard/mixins/conversation/labelMixin'; -import conversationTeamMixin from 'dashboard/mixins/conversation/teamMixin'; import { GENERAL_EVENTS } from '../../../helper/AnalyticsHelper/events'; export default { @@ -18,7 +17,6 @@ export default { bulkActionsHotKeysMixin, inboxHotKeysMixin, conversationLabelMixin, - conversationTeamMixin, appearanceHotKeys, goToCommandHotKeys, ], diff --git a/app/javascript/dashboard/routes/dashboard/commands/conversationHotKeys.js b/app/javascript/dashboard/routes/dashboard/commands/conversationHotKeys.js index 9c4a638b6..c680788fd 100644 --- a/app/javascript/dashboard/routes/dashboard/commands/conversationHotKeys.js +++ b/app/javascript/dashboard/routes/dashboard/commands/conversationHotKeys.js @@ -65,6 +65,7 @@ export default { currentChat: 'getSelectedChat', replyMode: 'draftMessages/getReplyEditorMode', contextMenuChatId: 'getContextMenuChatId', + teams: 'teams/getTeams', }), draftMessage() { return this.$store.getters['draftMessages/get'](this.draftKey); @@ -78,7 +79,18 @@ export default { conversationId() { return this.currentChat?.id; }, - + hasAnAssignedTeam() { + return !!this.currentChat?.meta?.team; + }, + teamsList() { + if (this.hasAnAssignedTeam) { + return [ + { id: 0, name: this.$t('TEAMS_SETTINGS.LIST.NONE') }, + ...this.teams, + ]; + } + return this.teams; + }, statusActions() { const isOpen = this.currentChat?.status === wootConstants.STATUS_TYPE.OPEN; diff --git a/app/javascript/dashboard/routes/dashboard/conversation/ConversationAction.vue b/app/javascript/dashboard/routes/dashboard/conversation/ConversationAction.vue index 96b30d1fd..d718d3598 100644 --- a/app/javascript/dashboard/routes/dashboard/conversation/ConversationAction.vue +++ b/app/javascript/dashboard/routes/dashboard/conversation/ConversationAction.vue @@ -6,7 +6,6 @@ import ContactDetailsItem from './ContactDetailsItem.vue'; import MultiselectDropdown from 'shared/components/ui/MultiselectDropdown.vue'; import ConversationLabels from './labels/LabelBox.vue'; import agentMixin from 'dashboard/mixins/agentMixin'; -import teamMixin from 'dashboard/mixins/conversation/teamMixin'; import { CONVERSATION_PRIORITY } from '../../../../shared/constants/messages'; import { CONVERSATION_EVENTS } from '../../../helper/AnalyticsHelper/events'; @@ -16,7 +15,7 @@ export default { MultiselectDropdown, ConversationLabels, }, - mixins: [agentMixin, teamMixin], + mixins: [agentMixin], props: { conversationId: { type: [Number, String], @@ -65,7 +64,20 @@ export default { ...mapGetters({ currentChat: 'getSelectedChat', currentUser: 'getCurrentUser', + teams: 'teams/getTeams', }), + hasAnAssignedTeam() { + return !!this.currentChat?.meta?.team; + }, + teamsList() { + if (this.hasAnAssignedTeam) { + return [ + { id: 0, name: this.$t('TEAMS_SETTINGS.LIST.NONE') }, + ...this.teams, + ]; + } + return this.teams; + }, assignedAgent: { get() { return this.currentChat.meta.assignee; From 4a63d1d89659bc5c41b6c62fd8222dfe4f2b433c Mon Sep 17 00:00:00 2001 From: Pranav Date: Sun, 11 Aug 2024 20:59:39 -0700 Subject: [PATCH 02/50] feat: Update the design for label management page (#9932) This PR is part of the settings design update series. It updates the design for the label management page. I've made a few changes to the SettingsLayout page to reduce boilerplate code. --- .../dashboard/i18n/locale/en/labelsMgmt.json | 9 +- .../dashboard/settings/SettingsLayout.vue | 26 +- .../dashboard/settings/labels/Index.vue | 284 ++++++++---------- .../settings/labels/labels.routes.js | 9 +- 4 files changed, 160 insertions(+), 168 deletions(-) diff --git a/app/javascript/dashboard/i18n/locale/en/labelsMgmt.json b/app/javascript/dashboard/i18n/locale/en/labelsMgmt.json index cb98993bd..a24266fb4 100644 --- a/app/javascript/dashboard/i18n/locale/en/labelsMgmt.json +++ b/app/javascript/dashboard/i18n/locale/en/labelsMgmt.json @@ -3,13 +3,18 @@ "HEADER": "Labels", "HEADER_BTN_TXT": "Add label", "LOADING": "Fetching labels", + "DESCRIPTION": "Labels help you categorize and prioritize conversations and leads. You can assign a label to a conversation or contact using the side panel.", + "LEARN_MORE": "Learn more about labels", "SEARCH_404": "There are no items matching this query", - "SIDEBAR_TXT": "

Labels

Labels help you to categorize conversations and prioritize them. You can assign label to a conversation from the sidepanel.

Labels are tied to the account and can be used to create custom workflows in your organization. You can assign custom color to a label, it makes it easier to identify the label. You will be able to display the label on the sidebar to filter the conversations easily.

", "LIST": { "404": "There are no labels available in this account.", "TITLE": "Manage labels", "DESC": "Labels let you group the conversations together.", - "TABLE_HEADER": ["Name", "Description", "Color"] + "TABLE_HEADER": [ + "Name", + "Description", + "Color" + ] }, "FORM": { "NAME": { diff --git a/app/javascript/dashboard/routes/dashboard/settings/SettingsLayout.vue b/app/javascript/dashboard/routes/dashboard/settings/SettingsLayout.vue index cfe642f54..916f821ad 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/SettingsLayout.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/SettingsLayout.vue @@ -4,21 +4,35 @@ defineProps({ type: Boolean, default: false, }, + noRecordsFound: { + type: Boolean, + default: false, + }, loadingMessage: { type: String, default: '', }, + noRecordsMessage: { + type: String, + default: '', + }, }); diff --git a/app/javascript/dashboard/routes/dashboard/settings/labels/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/labels/Index.vue index 9aed65d00..0fdaf16e8 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/labels/Index.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/labels/Index.vue @@ -1,138 +1,137 @@ - - - diff --git a/app/javascript/dashboard/routes/dashboard/settings/labels/labels.routes.js b/app/javascript/dashboard/routes/dashboard/settings/labels/labels.routes.js index 088c565d0..9f5edc2dd 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/labels/labels.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/labels/labels.routes.js @@ -1,18 +1,13 @@ import { frontendURL } from '../../../../helper/URLHelper'; -const SettingsContent = () => import('../Wrapper.vue'); +const SettingsWrapper = () => import('../SettingsWrapper.vue'); const Index = () => import('./Index.vue'); export default { routes: [ { path: frontendURL('accounts/:accountId/settings/labels'), - component: SettingsContent, - props: { - headerTitle: 'LABEL_MGMT.HEADER', - icon: 'tag', - showNewButton: false, - }, + component: SettingsWrapper, children: [ { path: '', From 6196a6d99a169cc95c9dc562acc27e9da984a709 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 12 Aug 2024 15:08:06 +0530 Subject: [PATCH 03/50] fix: last_activity_at is nil when conv is created (#9934) The payload does not include last_activity_at when the conversation is created. Because of this the frontend is not able to sort the conversations when appending this. Another problem is that the last_activity_at is not always present, it is added only when a message is created, and it updates it. So this can be nil when the conversation is created, so we fallback to created_at only at the presentation layer --- app/models/conversation.rb | 4 +++ .../conversations/event_data_presenter.rb | 1 + spec/models/conversation_spec.rb | 36 +++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/app/models/conversation.rb b/app/models/conversation.rb index 3a39b82fc..df4bbf31c 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -124,6 +124,10 @@ class Conversation < ApplicationRecord last_message_in_messaging_window?(messaging_window) end + def last_activity_at + self[:last_activity_at] || created_at + end + def last_incoming_message messages&.incoming&.last end diff --git a/app/presenters/conversations/event_data_presenter.rb b/app/presenters/conversations/event_data_presenter.rb index 67c7dc1dd..2617721ec 100644 --- a/app/presenters/conversations/event_data_presenter.rb +++ b/app/presenters/conversations/event_data_presenter.rb @@ -40,6 +40,7 @@ class Conversations::EventDataPresenter < SimpleDelegator { agent_last_seen_at: agent_last_seen_at.to_i, contact_last_seen_at: contact_last_seen_at.to_i, + last_activity_at: last_activity_at.to_i, timestamp: last_activity_at.to_i, created_at: created_at.to_i } diff --git a/spec/models/conversation_spec.rb b/spec/models/conversation_spec.rb index 0ca7303ef..5813d8c3c 100644 --- a/spec/models/conversation_spec.rb +++ b/spec/models/conversation_spec.rb @@ -525,6 +525,7 @@ RSpec.describe Conversation do id: conversation.display_id, messages: [], labels: [], + last_activity_at: conversation.last_activity_at.to_i, inbox_id: conversation.inbox_id, status: conversation.status, contact_inbox: conversation.contact_inbox, @@ -881,4 +882,39 @@ RSpec.describe Conversation do expect(conversation.cached_label_list_array).to eq %w[customer-support enterprise paid-customer] end end + + describe '#last_activity_at' do + let(:conversation) { create(:conversation) } + let(:message_params) do + { + conversation: conversation, + account: conversation.account, + inbox: conversation.inbox, + sender: conversation.assignee + } + end + + context 'when a new conversation is created' do + it 'sets last_activity_at to the created_at time' do + expect(conversation.last_activity_at).to eq(conversation.created_at) + end + end + + context 'when a new message is added' do + it 'updates the last_activity_at to the new message\'s created_at time' do + message = create(:message, created_at: 1.hour.from_now, **message_params) + conversation.reload + expect(conversation.last_activity_at).to be_within(1.second).of(message.created_at) + end + end + + context 'when multiple messages are added' do + it 'sets last_activity_at to the most recent message\'s created_at time' do + create(:message, created_at: 2.hours.ago, **message_params) + latest_message = create(:message, created_at: 1.hour.from_now, **message_params) + conversation.reload + expect(conversation.last_activity_at).to be_within(1.second).of(latest_message.created_at) + end + end + end end From dcefd58240fdeff5b3edd1c8e1b41c4dc7ecddc5 Mon Sep 17 00:00:00 2001 From: Pranav Date: Mon, 12 Aug 2024 15:10:42 +0530 Subject: [PATCH 04/50] Bump version to v3.11.1 --- config/app.yml | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/app.yml b/config/app.yml index 7b4ee064b..7a5e89326 100644 --- a/config/app.yml +++ b/config/app.yml @@ -1,5 +1,5 @@ shared: &shared - version: '3.11.0' + version: '3.11.1' development: <<: *shared diff --git a/package.json b/package.json index cce8ecd32..0e31bb65b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@chatwoot/chatwoot", - "version": "3.11.0", + "version": "3.11.1", "license": "MIT", "scripts": { "eslint": "eslint app/**/*.{js,vue}", From 6e12ba04abd3185c0471603440434725c37d8d9f Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 12 Aug 2024 15:08:06 +0530 Subject: [PATCH 05/50] fix: last_activity_at is nil when conv is created (#9934) The payload does not include last_activity_at when the conversation is created. Because of this the frontend is not able to sort the conversations when appending this. Another problem is that the last_activity_at is not always present, it is added only when a message is created, and it updates it. So this can be nil when the conversation is created, so we fallback to created_at only at the presentation layer --- app/models/conversation.rb | 4 +++ .../conversations/event_data_presenter.rb | 1 + spec/models/conversation_spec.rb | 36 +++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/app/models/conversation.rb b/app/models/conversation.rb index 3a39b82fc..df4bbf31c 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -124,6 +124,10 @@ class Conversation < ApplicationRecord last_message_in_messaging_window?(messaging_window) end + def last_activity_at + self[:last_activity_at] || created_at + end + def last_incoming_message messages&.incoming&.last end diff --git a/app/presenters/conversations/event_data_presenter.rb b/app/presenters/conversations/event_data_presenter.rb index 67c7dc1dd..2617721ec 100644 --- a/app/presenters/conversations/event_data_presenter.rb +++ b/app/presenters/conversations/event_data_presenter.rb @@ -40,6 +40,7 @@ class Conversations::EventDataPresenter < SimpleDelegator { agent_last_seen_at: agent_last_seen_at.to_i, contact_last_seen_at: contact_last_seen_at.to_i, + last_activity_at: last_activity_at.to_i, timestamp: last_activity_at.to_i, created_at: created_at.to_i } diff --git a/spec/models/conversation_spec.rb b/spec/models/conversation_spec.rb index 0ca7303ef..5813d8c3c 100644 --- a/spec/models/conversation_spec.rb +++ b/spec/models/conversation_spec.rb @@ -525,6 +525,7 @@ RSpec.describe Conversation do id: conversation.display_id, messages: [], labels: [], + last_activity_at: conversation.last_activity_at.to_i, inbox_id: conversation.inbox_id, status: conversation.status, contact_inbox: conversation.contact_inbox, @@ -881,4 +882,39 @@ RSpec.describe Conversation do expect(conversation.cached_label_list_array).to eq %w[customer-support enterprise paid-customer] end end + + describe '#last_activity_at' do + let(:conversation) { create(:conversation) } + let(:message_params) do + { + conversation: conversation, + account: conversation.account, + inbox: conversation.inbox, + sender: conversation.assignee + } + end + + context 'when a new conversation is created' do + it 'sets last_activity_at to the created_at time' do + expect(conversation.last_activity_at).to eq(conversation.created_at) + end + end + + context 'when a new message is added' do + it 'updates the last_activity_at to the new message\'s created_at time' do + message = create(:message, created_at: 1.hour.from_now, **message_params) + conversation.reload + expect(conversation.last_activity_at).to be_within(1.second).of(message.created_at) + end + end + + context 'when multiple messages are added' do + it 'sets last_activity_at to the most recent message\'s created_at time' do + create(:message, created_at: 2.hours.ago, **message_params) + latest_message = create(:message, created_at: 1.hour.from_now, **message_params) + conversation.reload + expect(conversation.last_activity_at).to be_within(1.second).of(latest_message.created_at) + end + end + end end From 0b0e26645564e61ab3e634a12467acc707d0b8ef Mon Sep 17 00:00:00 2001 From: Pranav Date: Mon, 12 Aug 2024 15:10:42 +0530 Subject: [PATCH 06/50] Bump version to v3.11.1 --- config/app.yml | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/app.yml b/config/app.yml index 7b4ee064b..7a5e89326 100644 --- a/config/app.yml +++ b/config/app.yml @@ -1,5 +1,5 @@ shared: &shared - version: '3.11.0' + version: '3.11.1' development: <<: *shared diff --git a/package.json b/package.json index 144cf1f3f..efcc03cc9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@chatwoot/chatwoot", - "version": "3.11.0", + "version": "3.11.1", "license": "MIT", "scripts": { "eslint": "eslint app/**/*.{js,vue}", From 96d60674aa53ee5a736f2d5a0bdf72e47d91fc51 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 12 Aug 2024 15:47:54 +0530 Subject: [PATCH 07/50] chore(deps): Update browserlistdb (#9933) --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2a4381ecf..6bb82aa15 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8287,9 +8287,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001214, caniuse-lite@^1.0.30001449, caniuse-lite@^1.0.30001503, caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001599: - version "1.0.30001624" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001624.tgz" - integrity sha512-0dWnQG87UevOCPYaOR49CBcLBwoZLpws+k6W37nLjWUhumP1Isusj0p2u+3KhjNloRWK9OKMgjBBzPujQHw4nA== + version "1.0.30001651" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz" + integrity sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg== capture-exit@^2.0.0: version "2.0.0" From 452096f4b2b54ca7f05400643765405d0f57c41d Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Mon, 12 Aug 2024 15:50:21 +0530 Subject: [PATCH 08/50] feat: Replace `rtlMixin` to a composable (#9924) This PR will replace the usage of `rtlMixin` to the `useUISettings` composable, and moved the method to component itself. --- app/javascript/dashboard/App.vue | 10 ++---- .../widgets/conversation/ReplyBox.vue | 5 ++- .../contacts/components/ContactsTable.vue | 24 +++++++------ .../components/NotificationPanel.vue | 9 ++--- .../settings/reports/components/CsatTable.vue | 11 +++--- .../components/overview/AgentTable.vue | 12 ++++--- .../dashboard/store/modules/accounts.js | 8 +++++ .../modules/specs/account/getters.spec.js | 34 +++++++++++++++++++ app/javascript/shared/mixins/rtlMixin.js | 27 --------------- .../shared/mixins/specs/rtlMixin.spec.js | 29 ---------------- 10 files changed, 74 insertions(+), 95 deletions(-) delete mode 100644 app/javascript/shared/mixins/rtlMixin.js delete mode 100644 app/javascript/shared/mixins/specs/rtlMixin.spec.js diff --git a/app/javascript/dashboard/App.vue b/app/javascript/dashboard/App.vue index efb89f489..da169d61f 100644 --- a/app/javascript/dashboard/App.vue +++ b/app/javascript/dashboard/App.vue @@ -10,7 +10,6 @@ import PaymentPendingBanner from './components/app/PaymentPendingBanner.vue'; import PendingEmailVerificationBanner from './components/app/PendingEmailVerificationBanner.vue'; import vueActionCable from './helper/actionCable'; import WootSnackbarBox from './components/SnackbarContainer.vue'; -import rtlMixin from 'shared/mixins/rtlMixin'; import { setColorTheme } from './helper/themeHelper'; import { isOnOnboardingView } from 'v3/helpers/RouteHelper'; import { @@ -32,9 +31,6 @@ export default { UpgradeBanner, PendingEmailVerificationBanner, }, - - mixins: [rtlMixin], - data() { return { showAddAccountModal: false, @@ -46,6 +42,7 @@ export default { computed: { ...mapGetters({ getAccount: 'accounts/getAccount', + isRTL: 'accounts/isRTL', currentUser: 'getCurrentUser', authUIFlags: 'getAuthUIFlags', accountUIFlags: 'accounts/getUIFlags', @@ -102,7 +99,6 @@ export default { this.getAccount(this.currentAccountId); const { pubsub_token: pubsubToken } = this.currentUser || {}; this.setLocale(locale); - this.updateRTLDirectionView(locale); this.latestChatwootVersion = latestChatwootVersion; vueActionCable.init(pubsubToken); this.reconnectService = new ReconnectService(this.$store, router); @@ -124,8 +120,8 @@ export default { v-if="!authUIFlags.isFetching && !accountUIFlags.isFetchingItem" id="app" class="flex-grow-0 w-full h-full min-h-0 app-wrapper" - :class="{ 'app-rtl--wrapper': isRTLView }" - :dir="isRTLView ? 'rtl' : 'ltr'" + :class="{ 'app-rtl--wrapper': isRTL }" + :dir="isRTL ? 'rtl' : 'ltr'" >