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:
Clairton Rodrigo Heinzen 2022-11-29 09:51:37 -03:00 committed by GitHub
parent a397f01692
commit edcbd53425
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 148 additions and 17 deletions

View file

@ -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;
},

View file

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

View file

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

View file

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

View file

@ -1,6 +1,8 @@
export const MESSAGE_STATUS = {
FAILED: 'failed',
SENT: 'sent',
DELIVERED: 'delivered',
READ: 'read',
PROGRESS: 'progress',
};

View file

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

View file

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

View file

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

View file

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