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 @@