diff --git a/app/builders/messages/instagram/message_builder.rb b/app/builders/messages/instagram/message_builder.rb index 5610e0671..e9debb767 100644 --- a/app/builders/messages/instagram/message_builder.rb +++ b/app/builders/messages/instagram/message_builder.rb @@ -48,6 +48,10 @@ class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder @outgoing_echo ? recipient_id : sender_id end + def message_is_unsupported? + message[:is_unsupported].present? && @messaging[:message][:is_unsupported] == true + end + def sender_id @messaging[:sender][:id] end @@ -118,7 +122,7 @@ class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder end def message_params - { + params = { account_id: conversation.account_id, inbox_id: conversation.inbox_id, message_type: message_type, @@ -129,6 +133,9 @@ class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder in_reply_to_external_id: message_reply_attributes } } + + params[:content_attributes][:is_unsupported] = true if message_is_unsupported? + params end def already_sent_from_chatwoot? diff --git a/app/controllers/api/v1/accounts/conversations_controller.rb b/app/controllers/api/v1/accounts/conversations_controller.rb index a3d3ad645..281ff95de 100644 --- a/app/controllers/api/v1/accounts/conversations_controller.rb +++ b/app/controllers/api/v1/accounts/conversations_controller.rb @@ -60,13 +60,26 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro end def toggle_status - if params[:status].present? + # FIXME: move this logic into a service object + if pending_to_open_by_bot? + @conversation.bot_handoff! + elsif params[:status].present? set_conversation_status @status = @conversation.save! else @status = @conversation.toggle_status end - assign_conversation if @conversation.status == 'open' && Current.user.is_a?(User) && Current.user&.agent? + assign_conversation if should_assign_conversation? + end + + def pending_to_open_by_bot? + return false unless Current.user.is_a?(AgentBot) + + @conversation.status == 'pending' && params[:status] == 'open' + end + + def should_assign_conversation? + @conversation.status == 'open' && Current.user.is_a?(User) && Current.user&.agent? end def toggle_priority diff --git a/app/controllers/concerns/access_token_auth_helper.rb b/app/controllers/concerns/access_token_auth_helper.rb index 2f4dc4337..c35a28d7d 100644 --- a/app/controllers/concerns/access_token_auth_helper.rb +++ b/app/controllers/concerns/access_token_auth_helper.rb @@ -14,7 +14,7 @@ module AccessTokenAuthHelper render_unauthorized('Invalid Access Token') && return if @access_token.blank? @resource = @access_token.owner - Current.user = @resource if current_user.is_a?(User) + Current.user = @resource if [User, AgentBot].include?(@resource.class) end def validate_bot_access_token! diff --git a/app/javascript/dashboard/mixins/specs/time.spec.js b/app/javascript/dashboard/mixins/specs/time.spec.js index 160cc9ff8..af120eb50 100644 --- a/app/javascript/dashboard/mixins/specs/time.spec.js +++ b/app/javascript/dashboard/mixins/specs/time.spec.js @@ -1,5 +1,4 @@ import TimeMixin from '../time'; -import { format } from 'date-fns'; describe('#messageStamp', () => { it('returns correct value', () => { @@ -11,10 +10,20 @@ describe('#messageStamp', () => { }); describe('#messageTimestamp', () => { + beforeEach(() => { + jest.useFakeTimers('modern'); + + const mockDate = new Date(2023, 4, 5); + jest.setSystemTime(mockDate); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + it('should return the message date in the specified format if the message was sent in the current year', () => { - const currentEpochTime = Math.floor(new Date().getTime() / 1000); - expect(TimeMixin.methods.messageTimestamp(currentEpochTime)).toEqual( - format(new Date(currentEpochTime * 1000), 'MMM d, yyyy') + expect(TimeMixin.methods.messageTimestamp(1680777464)).toEqual( + 'Apr 6, 2023' ); }); it('should return the message date and time in a different format if the message was sent in a different year', () => { diff --git a/app/javascript/dashboard/store/modules/conversations/helpers.js b/app/javascript/dashboard/store/modules/conversations/helpers.js index 8f01de272..0063c8cfc 100644 --- a/app/javascript/dashboard/store/modules/conversations/helpers.js +++ b/app/javascript/dashboard/store/modules/conversations/helpers.js @@ -108,6 +108,7 @@ const sortConfig = { }; export const sortComparator = (a, b, sortKey) => { - const [sortMethod, sortDirection] = SORT_OPTIONS[sortKey] || []; + const [sortMethod, sortDirection] = + SORT_OPTIONS[sortKey] || SORT_OPTIONS.last_activity_at_desc; return sortConfig[sortMethod](a, b, sortDirection); }; diff --git a/app/javascript/dashboard/store/modules/specs/conversations/conversations.fixtures.js b/app/javascript/dashboard/store/modules/specs/conversations/conversations.fixtures.js new file mode 100644 index 000000000..96655b9ec --- /dev/null +++ b/app/javascript/dashboard/store/modules/specs/conversations/conversations.fixtures.js @@ -0,0 +1,34 @@ +export default [ + { + created_at: 1702411932, // Dec 12, 2023 12:12:12 + id: 1, + last_activity_at: 1704408443, // Jan 04, 2024 14:47:23 + messages: [{ content: 'test1' }], + priority: 'medium', + waiting_since: 0, // not waiting + }, + { + created_at: 1699819932, // Nov 12, 2023 12:12:12 + id: 2, + last_activity_at: 1704485532, // Jan 05, 2024 12:12:12 + messages: [{ content: 'test2' }], + priority: 'low', + waiting_since: 1683645800, // May 09 2023 15:23:20 + }, + { + created_at: 1641413532, // Jan 05, 2022 12:12:12 + id: 3, + last_activity_at: 1704408567, // Jan 04, 2024 14:49:27 + messages: [{ content: 'test3' }], + priority: 'low', + waiting_since: 0, // not waiting + }, + { + created_at: 1641413531, // Jan 05, 2022 12:12:11 + id: 4, + last_activity_at: 1704408566, // Jan 04, 2024 14:49:26 + messages: [{ content: 'test4' }], + priority: 'high', + waiting_since: 1683645801, // May 09 2023 15:23:21 + }, +]; diff --git a/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js b/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js index a32cc853e..1a2225da6 100644 --- a/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js +++ b/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js @@ -1,392 +1,130 @@ import commonHelpers from '../../../../helper/commons'; import getters from '../../conversations/getters'; +/* + Order of conversations in the fixture is as follows: + - lastActivity: c0 < c3 < c2 < c1 + - createdAt: c3 < c2 < c1 < c0 + - priority: c1 < c2 < c0 < c3 + - waitingSince: c1 > c3 > c0 < c2 +*/ +import conversations from './conversations.fixtures'; // loads .last() helper commonHelpers(); describe('#getters', () => { describe('#getAllConversations', () => { - it('order conversations based on last activity', () => { - const state = { - allConversations: [ - { - id: 1, - messages: [ - { - content: 'test1', - }, - ], - created_at: 2466424490, - last_activity_at: 2466424490, - }, - { - id: 2, - messages: [{ content: 'test2' }], - created_at: 1466424480, - last_activity_at: 1466424480, - }, - ], - }; - + it('returns conversations ordered by lastActivityAt in descending order if no sort order is available', () => { + const state = { allConversations: [...conversations] }; expect(getters.getAllConversations(state)).toEqual([ - { - id: 1, - messages: [ - { - content: 'test1', - }, - ], - created_at: 2466424490, - last_activity_at: 2466424490, - }, - { - id: 2, - messages: [{ content: 'test2' }], - created_at: 1466424480, - last_activity_at: 1466424480, - }, - ]); - }); - it('order conversations based on last activity with ascending order', () => { - const state = { - allConversations: [ - { - id: 1, - messages: [ - { - content: 'test1', - }, - ], - created_at: 2466424490, - last_activity_at: 2466424490, - }, - { - id: 2, - messages: [{ content: 'test2' }], - created_at: 1466424480, - last_activity_at: 1466424480, - }, - ], - chatSortFilter: 'latest_last', - }; - - expect(getters.getAllConversations(state)).toEqual([ - { - id: 2, - messages: [{ content: 'test2' }], - created_at: 1466424480, - last_activity_at: 1466424480, - }, - { - id: 1, - messages: [ - { - content: 'test1', - }, - ], - created_at: 2466424490, - last_activity_at: 2466424490, - }, + conversations[1], + conversations[2], + conversations[3], + conversations[0], ]); }); - it('order conversations based on created at', () => { + it('returns conversations ordered by lastActivityAt in descending order if invalid sort order is available', () => { const state = { - allConversations: [ - { - id: 1, - messages: [ - { - content: 'test1', - }, - ], - created_at: 1683645801, // Tuesday, 9 May 2023 - last_activity_at: 2466424490, - }, - { - id: 2, - messages: [{ content: 'test2' }], - created_at: 1652109801, // Monday, 9 May 2022 - last_activity_at: 1466424480, - }, - ], - chatSortFilter: 'created_at_last', + allConversations: [...conversations], + chatSortFilter: 'latest', }; - expect(getters.getAllConversations(state)).toEqual([ - { - id: 2, - messages: [{ content: 'test2' }], - created_at: 1652109801, - last_activity_at: 1466424480, - }, - { - id: 1, - messages: [ - { - content: 'test1', - }, - ], - created_at: 1683645801, - last_activity_at: 2466424490, - }, + conversations[1], + conversations[2], + conversations[3], + conversations[0], ]); }); - it('order conversations based on created at with descending order', () => { + it('returns conversations ordered by lastActivityAt in descending order if chatStatusFilter = last_activity_at_desc', () => { const state = { - allConversations: [ - { - id: 1, - messages: [ - { - content: 'test1', - }, - ], - created_at: 1683645801, // Tuesday, 9 May 2023 - last_activity_at: 2466424490, - }, - { - id: 2, - messages: [{ content: 'test2' }], - created_at: 1652109801, // Monday, 9 May 2022 - last_activity_at: 1466424480, - }, - ], - chatSortFilter: 'created_at_first', + allConversations: [...conversations], + chatSortFilter: 'last_activity_at_desc', }; - expect(getters.getAllConversations(state)).toEqual([ - { - id: 1, - messages: [ - { - content: 'test1', - }, - ], - created_at: 1683645801, - last_activity_at: 2466424490, - }, - { - id: 2, - messages: [{ content: 'test2' }], - created_at: 1652109801, - last_activity_at: 1466424480, - }, + conversations[1], + conversations[2], + conversations[3], + conversations[0], ]); }); - it('order conversations based on default order', () => { + it('returns conversations ordered by lastActivityAt in ascending order if chatStatusFilter = last_activity_at_asc', () => { const state = { - allConversations: [ - { - id: 1, - messages: [ - { - content: 'test1', - }, - ], - created_at: 2466424490, - last_activity_at: 2466424490, - }, - { - id: 2, - messages: [{ content: 'test2' }], - created_at: 1466424480, - last_activity_at: 1466424480, - }, - ], + allConversations: [...conversations], + chatSortFilter: 'last_activity_at_asc', }; - expect(getters.getAllConversations(state)).toEqual([ - { - id: 1, - messages: [ - { - content: 'test1', - }, - ], - created_at: 2466424490, - last_activity_at: 2466424490, - }, - { - id: 2, - messages: [{ content: 'test2' }], - created_at: 1466424480, - last_activity_at: 1466424480, - }, - ]); - }); - it('order conversations based on priority', () => { - const state = { - allConversations: [ - { - id: 1, - messages: [ - { - content: 'test1', - }, - ], - priority: 'low', - created_at: 1683645801, - last_activity_at: 2466424490, - }, - { - id: 2, - messages: [{ content: 'test2' }], - priority: 'urgent', - created_at: 1652109801, - last_activity_at: 1466424480, - }, - { - id: 3, - messages: [{ content: 'test3' }], - priority: 'medium', - created_at: 1652109801, - last_activity_at: 1466421280, - }, - ], - chatSortFilter: 'priority_first', - }; - - expect(getters.getAllConversations(state)).toEqual([ - { - id: 2, - messages: [{ content: 'test2' }], - priority: 'urgent', - created_at: 1652109801, - last_activity_at: 1466424480, - }, - { - id: 3, - messages: [{ content: 'test3' }], - priority: 'medium', - created_at: 1652109801, - last_activity_at: 1466421280, - }, - { - id: 1, - messages: [ - { - content: 'test1', - }, - ], - priority: 'low', - created_at: 1683645801, - last_activity_at: 2466424490, - }, + conversations[0], + conversations[3], + conversations[2], + conversations[1], ]); }); - it('order conversations based on with descending order', () => { + it('returns conversations ordered by createdAt in descending order if chatStatusFilter = created_at_desc', () => { const state = { - allConversations: [ - { - id: 1, - messages: [ - { - content: 'test1', - }, - ], - priority: 'low', - created_at: 1683645801, - last_activity_at: 2466424490, - }, - { - id: 2, - messages: [{ content: 'test2' }], - priority: 'urgent', - created_at: 1652109801, - last_activity_at: 1466424480, - }, - { - id: 3, - messages: [{ content: 'test3' }], - priority: 'medium', - created_at: 1652109801, - last_activity_at: 1466421280, - }, - ], - chatSortFilter: 'priority_last', + allConversations: [...conversations], + chatSortFilter: 'created_at_desc', }; - expect(getters.getAllConversations(state)).toEqual([ - { - id: 1, - messages: [ - { - content: 'test1', - }, - ], - priority: 'low', - created_at: 1683645801, - last_activity_at: 2466424490, - }, - { - id: 3, - messages: [{ content: 'test3' }], - priority: 'medium', - created_at: 1652109801, - last_activity_at: 1466421280, - }, - { - id: 2, - messages: [{ content: 'test2' }], - priority: 'urgent', - created_at: 1652109801, - last_activity_at: 1466424480, - }, + conversations[0], + conversations[1], + conversations[2], + conversations[3], ]); }); - it('order conversations based on waiting_since', () => { + it('returns conversations ordered by createdAt in ascending order if chatStatusFilter = created_at_asc', () => { const state = { - allConversations: [ - { - id: 3, - created_at: 1683645800, - waiting_since: 0, - }, - { - id: 4, - created_at: 1683645799, - waiting_since: 0, - }, - { - id: 1, - created_at: 1683645801, - waiting_since: 1683645802, - }, - { - id: 2, - created_at: 1683645803, - waiting_since: 1683645800, - }, - ], - chatSortFilter: 'waiting_since_last', + allConversations: [...conversations], + chatSortFilter: 'created_at_asc', }; - expect(getters.getAllConversations(state)).toEqual([ - { - id: 2, - created_at: 1683645803, - waiting_since: 1683645800, - }, - { - id: 1, - created_at: 1683645801, - waiting_since: 1683645802, - }, - { - id: 4, - created_at: 1683645799, - waiting_since: 0, - }, - { - id: 3, - created_at: 1683645800, - waiting_since: 0, - }, + conversations[3], + conversations[2], + conversations[1], + conversations[0], + ]); + }); + + it('returns conversations ordered by priority in descending order if chatStatusFilter = priority_desc', () => { + const state = { + allConversations: [...conversations], + chatSortFilter: 'priority_desc', + }; + expect(getters.getAllConversations(state)).toEqual([ + conversations[3], + conversations[0], + conversations[1], + conversations[2], + ]); + }); + + it('returns conversations ordered by priority in ascending order if chatStatusFilter = priority_asc', () => { + const state = { + allConversations: [...conversations], + chatSortFilter: 'priority_asc', + }; + expect(getters.getAllConversations(state)).toEqual([ + conversations[1], + conversations[2], + conversations[0], + conversations[3], + ]); + }); + + it('returns conversations ordered by longest waiting if chatStatusFilter = waiting_since_asc', () => { + const state = { + allConversations: [...conversations], + chatSortFilter: 'waiting_since_asc', + }; + expect(getters.getAllConversations(state)).toEqual([ + conversations[1], + conversations[3], + conversations[2], + conversations[0], ]); }); }); diff --git a/app/javascript/widget/components/AgentMessage.vue b/app/javascript/widget/components/AgentMessage.vue index 6020af3dc..8526281ef 100755 --- a/app/javascript/widget/components/AgentMessage.vue +++ b/app/javascript/widget/components/AgentMessage.vue @@ -19,11 +19,7 @@
- +
- +