Merge branch 'develop' into CW-2773
This commit is contained in:
@@ -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?
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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
|
||||
},
|
||||
];
|
||||
@@ -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],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,11 +19,7 @@
|
||||
<reply-to-chip :reply-to="replyTo" />
|
||||
</div>
|
||||
<div class="flex gap-1">
|
||||
<drag-wrapper
|
||||
class="space-y-2"
|
||||
direction="right"
|
||||
@dragged="toggleReply"
|
||||
>
|
||||
<div class="space-y-2">
|
||||
<AgentMessageBubble
|
||||
v-if="shouldDisplayAgentMessage"
|
||||
:content-type="contentType"
|
||||
@@ -54,7 +50,7 @@
|
||||
<file-bubble v-else :url="attachment.data_url" />
|
||||
</div>
|
||||
</div>
|
||||
</drag-wrapper>
|
||||
</div>
|
||||
<div class="flex flex-col justify-end">
|
||||
<message-reply-button
|
||||
class="transition-opacity delay-75 opacity-0 group-hover:opacity-100 sm:opacity-0"
|
||||
@@ -96,7 +92,6 @@ import messageMixin from '../mixins/messageMixin';
|
||||
import { isASubmittedFormMessage } from 'shared/helpers/MessageTypeHelper';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||
import ReplyToChip from 'widget/components/ReplyToChip.vue';
|
||||
import DragWrapper from 'widget/components/DragWrapper.vue';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
|
||||
export default {
|
||||
@@ -109,7 +104,6 @@ export default {
|
||||
FileBubble,
|
||||
MessageReplyButton,
|
||||
ReplyToChip,
|
||||
DragWrapper,
|
||||
},
|
||||
mixins: [timeMixin, configMixin, messageMixin, darkModeMixin],
|
||||
props: {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
class Notification::ReopenSnoozedNotificationsJob < ApplicationJob
|
||||
queue_as :low
|
||||
|
||||
def perform
|
||||
# rubocop:disable Rails/SkipsModelValidations
|
||||
Notification.where(snoozed_until: 3.days.ago..Time.current)
|
||||
.update_all(snoozed_until: nil, updated_at: Time.current, last_activity_at: Time.current)
|
||||
# rubocop:enable Rails/SkipsModelValidations
|
||||
end
|
||||
end
|
||||
@@ -11,6 +11,9 @@ class TriggerScheduledItemsJob < ApplicationJob
|
||||
# Job to reopen snoozed conversations
|
||||
Conversations::ReopenSnoozedConversationsJob.perform_later
|
||||
|
||||
# Job to reopen snoozed notifications
|
||||
Notification::ReopenSnoozedNotificationsJob.perform_later
|
||||
|
||||
# Job to auto-resolve conversations
|
||||
Account::ConversationsResolutionSchedulerJob.perform_later
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ class Message < ApplicationRecord
|
||||
# [:external_error : Can specify if the message creation failed due to an error at external API
|
||||
store :content_attributes, accessors: [:submitted_email, :items, :submitted_values, :email, :in_reply_to, :deleted,
|
||||
:external_created_at, :story_sender, :story_id, :external_error,
|
||||
:translations, :in_reply_to_external_id], coder: JSON
|
||||
:translations, :in_reply_to_external_id, :is_unsupported], coder: JSON
|
||||
|
||||
store :external_source_ids, accessors: [:slack], coder: JSON, prefix: :external_source_id
|
||||
|
||||
@@ -299,6 +299,10 @@ class Message < ApplicationRecord
|
||||
end
|
||||
|
||||
def dispatch_update_event
|
||||
# ref: https://github.com/rails/rails/issues/44500
|
||||
# we want to skip the update event if the message is not updated
|
||||
return if previous_changes.blank?
|
||||
|
||||
Rails.configuration.dispatcher.dispatch(MESSAGE_UPDATED, Time.zone.now, message: self, performed_by: Current.executed_by,
|
||||
previous_changes: previous_changes)
|
||||
end
|
||||
|
||||
@@ -21,7 +21,7 @@ class InboxPolicy < ApplicationPolicy
|
||||
|
||||
def show?
|
||||
# FIXME: for agent bots, lets bring this validation to policies as well in future
|
||||
return true if @user.blank?
|
||||
return true if @user.is_a?(AgentBot)
|
||||
|
||||
Current.user.assigned_inboxes.include? record
|
||||
end
|
||||
|
||||
@@ -323,6 +323,9 @@ RSpec.describe 'Conversations API', type: :request do
|
||||
|
||||
describe 'POST /api/v1/accounts/{account.id}/conversations/:id/toggle_status' do
|
||||
let(:conversation) { create(:conversation, account: account) }
|
||||
let(:inbox) { create(:inbox, account: account) }
|
||||
let(:pending_conversation) { create(:conversation, inbox: inbox, account: account, status: 'pending') }
|
||||
let(:agent_bot) { create(:agent_bot, account: account) }
|
||||
|
||||
context 'when it is an unauthenticated user' do
|
||||
it 'returns unauthorized' do
|
||||
@@ -424,6 +427,42 @@ RSpec.describe 'Conversations API', type: :request do
|
||||
# expect(conversation.reload.status).to eq('pending')
|
||||
# end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated bot' do
|
||||
# this test will basically ensure that the status actually changes
|
||||
# regardless of the value to be done
|
||||
it 'returns authorized for arbritrary status' do
|
||||
create(:agent_bot_inbox, inbox: inbox, agent_bot: agent_bot)
|
||||
|
||||
conversation.update!(status: 'open')
|
||||
expect(conversation.reload.status).to eq('open')
|
||||
snoozed_until = (DateTime.now.utc + 2.days).to_i
|
||||
|
||||
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_status",
|
||||
headers: { api_access_token: agent_bot.access_token.token },
|
||||
params: { status: 'snoozed', snoozed_until: snoozed_until },
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(conversation.reload.status).to eq('snoozed')
|
||||
end
|
||||
|
||||
it 'triggers handoff event when moving from pending to open' do
|
||||
create(:agent_bot_inbox, inbox: inbox, agent_bot: agent_bot)
|
||||
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
||||
|
||||
post "/api/v1/accounts/#{account.id}/conversations/#{pending_conversation.display_id}/toggle_status",
|
||||
headers: { api_access_token: agent_bot.access_token.token },
|
||||
params: { status: 'open' },
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(pending_conversation.reload.status).to eq('open')
|
||||
expect(Rails.configuration.dispatcher).to have_received(:dispatch)
|
||||
.with(Events::Types::CONVERSATION_BOT_HANDOFF, kind_of(Time), conversation: pending_conversation, notifiable_assignee_change: false,
|
||||
changed_attributes: anything, performed_by: anything)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v1/accounts/{account.id}/conversations/:id/toggle_priority' do
|
||||
|
||||
@@ -273,6 +273,33 @@ FactoryBot.define do
|
||||
initialize_with { attributes }
|
||||
end
|
||||
|
||||
factory :instagram_message_unsupported_event, class: Hash do
|
||||
entry do
|
||||
[
|
||||
{
|
||||
'id': 'instagram-message-unsupported-id-123',
|
||||
'time': '2021-09-08T06:34:04+0000',
|
||||
'messaging': [
|
||||
{
|
||||
'sender': {
|
||||
'id': 'Sender-id-1'
|
||||
},
|
||||
'recipient': {
|
||||
'id': 'chatwoot-app-user-id-1'
|
||||
},
|
||||
'timestamp': '2021-09-08T06:34:04+0000',
|
||||
'message': {
|
||||
'mid': 'unsupported-message-id-1',
|
||||
'is_unsupported': true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
end
|
||||
initialize_with { attributes }
|
||||
end
|
||||
|
||||
factory :messaging_seen_event, class: Hash do
|
||||
entry do
|
||||
[
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Notification::ReopenSnoozedNotificationsJob do
|
||||
let!(:snoozed_till_5_minutes_ago) { create(:notification, snoozed_until: 5.minutes.ago) }
|
||||
let!(:snoozed_till_tomorrow) { create(:notification, snoozed_until: 1.day.from_now) }
|
||||
let!(:snoozed_indefinitely) { create(:notification) }
|
||||
|
||||
it 'enqueues the job' do
|
||||
expect { described_class.perform_later }.to have_enqueued_job(described_class)
|
||||
.on_queue('low')
|
||||
end
|
||||
|
||||
context 'when called' do
|
||||
it 'reopens snoozed notifications whose snooze until has passed' do
|
||||
described_class.perform_now
|
||||
|
||||
expect(snoozed_till_5_minutes_ago.reload.snoozed_until).to be_nil
|
||||
expect(snoozed_till_tomorrow.reload.snoozed_until.to_date).to eq 1.day.from_now.to_date
|
||||
expect(snoozed_indefinitely.reload.snoozed_until).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -26,6 +26,11 @@ RSpec.describe TriggerScheduledItemsJob do
|
||||
described_class.perform_now
|
||||
end
|
||||
|
||||
it 'triggers Notification::ReopenSnoozedNotificationsJob' do
|
||||
expect(Notification::ReopenSnoozedNotificationsJob).to receive(:perform_later).once
|
||||
described_class.perform_now
|
||||
end
|
||||
|
||||
it 'triggers Account::ConversationsResolutionSchedulerJob' do
|
||||
expect(Account::ConversationsResolutionSchedulerJob).to receive(:perform_later).once
|
||||
described_class.perform_now
|
||||
|
||||
@@ -28,6 +28,7 @@ describe Webhooks::InstagramEventsJob do
|
||||
let!(:story_mention_params) { build(:instagram_story_mention_event).with_indifferent_access }
|
||||
let!(:story_mention_echo_params) { build(:instagram_story_mention_event_with_echo).with_indifferent_access }
|
||||
let!(:messaging_seen_event) { build(:messaging_seen_event).with_indifferent_access }
|
||||
let!(:unsupported_message_event) { build(:instagram_message_unsupported_event).with_indifferent_access }
|
||||
let(:fb_object) { double }
|
||||
|
||||
describe '#perform' do
|
||||
@@ -45,6 +46,7 @@ describe Webhooks::InstagramEventsJob do
|
||||
expect(instagram_inbox.contacts.last.additional_attributes['social_profiles']['instagram']).to eq 'some_user_name'
|
||||
expect(instagram_inbox.conversations.count).to be 1
|
||||
expect(instagram_inbox.messages.count).to be 1
|
||||
expect(instagram_inbox.messages.last.content_attributes['is_unsupported']).to be_nil
|
||||
end
|
||||
|
||||
it 'creates standby message in the instagram inbox' do
|
||||
@@ -157,6 +159,22 @@ describe Webhooks::InstagramEventsJob do
|
||||
expect(Instagram::ReadStatusService).to receive(:new).with(params: messaging_seen_event[:entry][0][:messaging][0]).and_call_original
|
||||
instagram_webhook.perform_now(messaging_seen_event[:entry])
|
||||
end
|
||||
|
||||
it 'handles unsupported message' do
|
||||
allow(Koala::Facebook::API).to receive(:new).and_return(fb_object)
|
||||
allow(fb_object).to receive(:get_object).and_return(
|
||||
return_object.with_indifferent_access
|
||||
)
|
||||
|
||||
instagram_webhook.perform_now(unsupported_message_event[:entry])
|
||||
instagram_inbox.reload
|
||||
|
||||
expect(instagram_inbox.contacts.count).to be 1
|
||||
expect(instagram_inbox.contacts.last.additional_attributes['social_profiles']['instagram']).to eq 'some_user_name'
|
||||
expect(instagram_inbox.conversations.count).to be 1
|
||||
expect(instagram_inbox.messages.count).to be 1
|
||||
expect(instagram_inbox.messages.last.content_attributes['is_unsupported']).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user