feat: Read/Delivery status for Whatsapp Cloud API (#5157)
Process field statuses received in webhook WhatsApp cloud API ref: #1021 Co-authored-by: Sojan <sojan@pepalo.com> Co-authored-by: Nithin David <1277421+nithindavid@users.noreply.github.com>
This commit is contained in:
parent
a397f01692
commit
edcbd53425
9 changed files with 148 additions and 17 deletions
|
@ -59,10 +59,12 @@
|
|||
:story-sender="storySender"
|
||||
:story-id="storyId"
|
||||
:is-a-tweet="isATweet"
|
||||
:is-a-whatsapp-channel="isAWhatsAppChannel"
|
||||
:has-instagram-story="hasInstagramStory"
|
||||
:is-email="isEmailContentType"
|
||||
:is-private="data.private"
|
||||
:message-type="data.message_type"
|
||||
:message-status="status"
|
||||
:readable-time="readableTime"
|
||||
:source-id="data.source_id"
|
||||
:inbox-id="data.inbox_id"
|
||||
|
@ -157,6 +159,10 @@ export default {
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isAWhatsAppChannel: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
hasInstagramStory: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
|
@ -231,6 +237,9 @@ export default {
|
|||
sender() {
|
||||
return this.data.sender || {};
|
||||
},
|
||||
status() {
|
||||
return this.data.status;
|
||||
},
|
||||
storySender() {
|
||||
return this.contentAttributes.story_sender || null;
|
||||
},
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
class="message--read ph-no-capture"
|
||||
:data="message"
|
||||
:is-a-tweet="isATweet"
|
||||
:is-a-whatsapp-channel="isAWhatsAppChannel"
|
||||
:has-instagram-story="hasInstagramStory"
|
||||
:has-user-read-message="
|
||||
hasUserReadMessage(message.created_at, getLastSeenAt)
|
||||
|
@ -60,6 +61,7 @@
|
|||
class="message--unread ph-no-capture"
|
||||
:data="message"
|
||||
:is-a-tweet="isATweet"
|
||||
:is-a-whatsapp-channel="isAWhatsAppChannel"
|
||||
:has-instagram-story="hasInstagramStory"
|
||||
:has-user-read-message="
|
||||
hasUserReadMessage(message.created_at, getLastSeenAt)
|
||||
|
|
|
@ -3,20 +3,30 @@
|
|||
<span class="time" :class="{ delivered: messageRead }">{{
|
||||
readableTime
|
||||
}}</span>
|
||||
<span v-if="showSentIndicator" class="time">
|
||||
<span v-if="showReadIndicator" class="read-indicator-wrap">
|
||||
<fluent-icon
|
||||
v-tooltip.top-start="$t('CHAT_LIST.SENT')"
|
||||
icon="checkmark"
|
||||
v-tooltip.top-start="$t('CHAT_LIST.MESSAGE_READ')"
|
||||
icon="checkmark-double"
|
||||
class="action--icon read-tick read-indicator"
|
||||
size="14"
|
||||
/>
|
||||
</span>
|
||||
<span v-if="showDeliveredIndicator" class="read-indicator-wrap">
|
||||
<fluent-icon
|
||||
v-tooltip.top-start="$t('CHAT_LIST.DELIVERED')"
|
||||
icon="checkmark-double"
|
||||
class="action--icon read-tick"
|
||||
size="14"
|
||||
/>
|
||||
</span>
|
||||
<span v-if="showSentIndicator" class="read-indicator-wrap">
|
||||
<fluent-icon
|
||||
v-tooltip.top-start="$t('CHAT_LIST.SENT')"
|
||||
icon="checkmark"
|
||||
class="action--icon read-tick"
|
||||
size="14"
|
||||
/>
|
||||
</span>
|
||||
<fluent-icon
|
||||
v-if="messageRead"
|
||||
v-tooltip.top-start="$t('CHAT_LIST.MESSAGE_READ')"
|
||||
icon="checkmark-double"
|
||||
class="action--icon read-tick"
|
||||
size="12"
|
||||
/>
|
||||
<fluent-icon
|
||||
v-if="isEmail"
|
||||
v-tooltip.top-start="$t('CHAT_LIST.RECEIVED_VIA_EMAIL')"
|
||||
|
@ -74,7 +84,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { MESSAGE_TYPE } from 'shared/constants/messages';
|
||||
import { MESSAGE_TYPE, MESSAGE_STATUS } from 'shared/constants/messages';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||
|
||||
|
@ -117,6 +127,10 @@ export default {
|
|||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
messageStatus: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
sourceId: {
|
||||
type: String,
|
||||
default: '',
|
||||
|
@ -144,6 +158,15 @@ export default {
|
|||
isOutgoing() {
|
||||
return MESSAGE_TYPE.OUTGOING === this.messageType;
|
||||
},
|
||||
isDelivered() {
|
||||
return MESSAGE_STATUS.DELIVERED === this.messageStatus;
|
||||
},
|
||||
isRead() {
|
||||
return MESSAGE_STATUS.READ === this.messageStatus;
|
||||
},
|
||||
isSent() {
|
||||
return MESSAGE_STATUS.SENT === this.messageStatus;
|
||||
},
|
||||
screenName() {
|
||||
const { additional_attributes: additionalAttributes = {} } =
|
||||
this.sender || {};
|
||||
|
@ -168,7 +191,23 @@ export default {
|
|||
return (
|
||||
this.isOutgoing &&
|
||||
this.sourceId &&
|
||||
(this.isAnEmailChannel || this.isAWhatsAppChannel)
|
||||
(this.isAnEmailChannel || (this.isAWhatsAppChannel && this.isSent))
|
||||
);
|
||||
},
|
||||
showDeliveredIndicator() {
|
||||
return (
|
||||
this.isOutgoing &&
|
||||
this.sourceId &&
|
||||
this.isAWhatsAppChannel &&
|
||||
this.isDelivered
|
||||
);
|
||||
},
|
||||
showReadIndicator() {
|
||||
return (
|
||||
this.isOutgoing &&
|
||||
this.sourceId &&
|
||||
this.isAWhatsAppChannel &&
|
||||
this.isRead
|
||||
);
|
||||
},
|
||||
},
|
||||
|
@ -185,16 +224,20 @@ export default {
|
|||
|
||||
.right {
|
||||
.message-text--metadata {
|
||||
align-items: center;
|
||||
.time {
|
||||
color: var(--w-100);
|
||||
}
|
||||
|
||||
.action--icon {
|
||||
color: var(--white);
|
||||
&.read-tick {
|
||||
color: var(--v-100);
|
||||
margin-top: calc(var(--space-micro) + var(--space-micro) / 2);
|
||||
}
|
||||
color: var(--white);
|
||||
|
||||
&.read-indicator {
|
||||
color: var(--g-300);
|
||||
}
|
||||
}
|
||||
|
||||
.lock--icon--private {
|
||||
|
@ -296,4 +339,10 @@ export default {
|
|||
.delivered-icon {
|
||||
margin-left: -var(--space-normal);
|
||||
}
|
||||
|
||||
.read-indicator-wrap {
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -56,6 +56,8 @@
|
|||
"REPLY_TO_TWEET": "Reply to this tweet",
|
||||
"LINK_TO_STORY": "Go to instagram story",
|
||||
"SENT": "Sent successfully",
|
||||
"READ": "Read successfully",
|
||||
"DELIVERED": "Delivered successfully",
|
||||
"NO_MESSAGES": "No Messages",
|
||||
"NO_CONTENT": "No content available",
|
||||
"HIDE_QUOTED_TEXT": "Hide Quoted Text",
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
export const MESSAGE_STATUS = {
|
||||
FAILED: 'failed',
|
||||
SENT: 'sent',
|
||||
DELIVERED: 'delivered',
|
||||
READ: 'read',
|
||||
PROGRESS: 'progress',
|
||||
};
|
||||
|
||||
|
|
|
@ -65,8 +65,9 @@ class Message < ApplicationRecord
|
|||
# [:in_reply_to] : Used to reply to a particular tweet in threads
|
||||
# [:deleted] : Used to denote whether the message was deleted by the agent
|
||||
# [:external_created_at] : Can specify if the message was created at a different timestamp externally
|
||||
# [: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], coder: JSON
|
||||
:external_created_at, :story_sender, :story_id, :external_error], coder: JSON
|
||||
|
||||
store :external_source_ids, accessors: [:slack], coder: JSON, prefix: :external_source_id
|
||||
|
||||
|
|
|
@ -7,11 +7,38 @@ class Whatsapp::IncomingMessageBaseService
|
|||
def perform
|
||||
processed_params
|
||||
|
||||
perform_statuses
|
||||
|
||||
set_contact
|
||||
return unless @contact
|
||||
|
||||
set_conversation
|
||||
|
||||
perform_messages
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def perform_statuses
|
||||
return if @processed_params[:statuses].blank?
|
||||
|
||||
status = @processed_params[:statuses].first
|
||||
@message = Message.find_by(source_id: status[:id])
|
||||
return unless @message
|
||||
|
||||
update_message_with_status(@message, status)
|
||||
end
|
||||
|
||||
def update_message_with_status(message, status)
|
||||
message.status = status[:status]
|
||||
if status[:status] == 'failed' && status[:errors].present?
|
||||
error = status[:errors]&.first
|
||||
message.external_error = "#{error[:code]}: #{error[:title]}"
|
||||
end
|
||||
message.save!
|
||||
end
|
||||
|
||||
def perform_messages
|
||||
return if @processed_params[:messages].blank? || unprocessable_message_type?
|
||||
|
||||
@message = @conversation.messages.build(
|
||||
|
@ -27,8 +54,6 @@ class Whatsapp::IncomingMessageBaseService
|
|||
@message.save!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def processed_params
|
||||
@processed_params ||= params
|
||||
end
|
||||
|
|
|
@ -5,6 +5,7 @@ json.echo_id message.echo_id if message.echo_id
|
|||
json.conversation_id message.conversation.display_id
|
||||
json.message_type message.message_type_before_type_cast
|
||||
json.content_type message.content_type
|
||||
json.status message.status
|
||||
json.content_attributes message.content_attributes
|
||||
json.created_at message.created_at.to_i
|
||||
json.private message.private
|
||||
|
|
|
@ -70,6 +70,46 @@ describe Whatsapp::IncomingMessageService do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when valid status params' do
|
||||
let(:from) { '2423423243' }
|
||||
let(:contact_inbox) { create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: from) }
|
||||
let(:params) do
|
||||
{
|
||||
'contacts' => [{ 'profile' => { 'name' => 'Sojan Jose' }, 'wa_id' => from }],
|
||||
'messages' => [{ 'from' => from, 'id' => from, 'text' => { 'body' => 'Test' },
|
||||
'timestamp' => '1633034394', 'type' => 'text' }]
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
before do
|
||||
create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox)
|
||||
described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
|
||||
end
|
||||
|
||||
it 'update status message to read' do
|
||||
status_params = {
|
||||
'statuses' => [{ 'recipient_id' => from, 'id' => from, 'status' => 'read' }]
|
||||
}.with_indifferent_access
|
||||
message = Message.find_by!(source_id: from)
|
||||
expect(message.status).to eq('sent')
|
||||
described_class.new(inbox: whatsapp_channel.inbox, params: status_params).perform
|
||||
expect(message.reload.status).to eq('read')
|
||||
end
|
||||
|
||||
it 'update status message to failed' do
|
||||
status_params = {
|
||||
'statuses' => [{ 'recipient_id' => from, 'id' => from, 'status' => 'failed',
|
||||
'errors' => [{ 'code': 123, 'title': 'abc' }] }]
|
||||
}.with_indifferent_access
|
||||
|
||||
message = Message.find_by!(source_id: from)
|
||||
expect(message.status).to eq('sent')
|
||||
described_class.new(inbox: whatsapp_channel.inbox, params: status_params).perform
|
||||
expect(message.reload.status).to eq('failed')
|
||||
expect(message.external_error).to eq('123: abc')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when valid interactive message params' do
|
||||
it 'creates appropriate conversations, message and contacts' do
|
||||
params = {
|
||||
|
|
Loading…
Reference in a new issue