feat: Ability to specify a subject line for outbound emails (#3168)

This commit is contained in:
Aswin Dev P.S 2021-10-27 13:09:29 +05:30 committed by GitHub
parent 2f2d2b4f73
commit 46867e89cb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 83 additions and 14 deletions

View file

@ -155,6 +155,11 @@
"LABEL": "Inbox",
"ERROR": "Select an inbox"
},
"SUBJECT": {
"LABEL": "Subject",
"PLACEHOLDER": "Subject",
"ERROR": "Subject can't be empty"
},
"MESSAGE": {
"LABEL": "Message",
"PLACEHOLDER": "Write your message here",

View file

@ -41,6 +41,22 @@
</label>
</div>
</div>
<div class="row" v-if="isAnEmailInbox">
<div class="columns">
<label :class="{ error: $v.message.$error }">
{{ $t('NEW_CONVERSATION.FORM.SUBJECT.LABEL') }}
<input
v-model="subject"
type="text"
:placeholder="$t('NEW_CONVERSATION.FORM.SUBJECT.PLACEHOLDER')"
@input="$v.message.$touch"
/>
<span v-if="$v.message.$error" class="message">
{{ $t('NEW_CONVERSATION.FORM.SUBJECT.ERROR') }}
</span>
</label>
</div>
</div>
<div class="row">
<div class="columns">
<label :class="{ error: $v.message.$error }">
@ -75,6 +91,7 @@ import { mapGetters } from 'vuex';
import Thumbnail from 'dashboard/components/widgets/Thumbnail';
import alertMixin from 'shared/mixins/alertMixin';
import { INBOX_TYPES } from 'shared/mixins/inboxMixin';
import { ExceptionWithMessage } from 'shared/helpers/CustomErrors';
import { required } from 'vuelidate/lib/validators';
@ -96,11 +113,15 @@ export default {
data() {
return {
name: '',
subject: '',
message: '',
selectedInbox: '',
};
},
validations: {
subject: {
required,
},
message: {
required,
},
@ -119,6 +140,7 @@ export default {
sourceId: this.targetInbox.source_id,
contactId: this.contact.id,
message: { content: this.message },
mailSubject: this.subject,
};
},
targetInbox: {
@ -138,6 +160,12 @@ export default {
inboxes() {
return this.contact.contactableInboxes || [];
},
isAnEmailInbox() {
return (
this.selectedInbox &&
this.selectedInbox.inbox.channel_type === INBOX_TYPES.EMAIL
);
},
},
methods: {
onCancel() {
@ -154,7 +182,6 @@ export default {
}
try {
const payload = this.getNewConversation;
await this.onSubmit(payload);
this.onSuccess();
this.showAlert(this.$t('NEW_CONVERSATION.FORM.SUCCESS_MESSAGE'));

View file

@ -24,12 +24,15 @@ export const actions = {
commit(types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG, {
isCreating: true,
});
const { inboxId, message, contactId, sourceId } = params;
const { inboxId, message, contactId, sourceId, mailSubject } = params;
try {
const { data } = await ConversationApi.create({
inbox_id: inboxId,
contact_id: contactId,
source_id: sourceId,
additional_attributes: {
mail_subject: mailSubject,
},
message,
});
commit(types.default.ADD_CONTACT_CONVERSATION, {

View file

@ -44,7 +44,13 @@ describe('#actions', () => {
axios.post.mockResolvedValue({ data: conversationList[0] });
await actions.create(
{ commit },
{ inboxId: 1, message: { content: 'hi' }, contactId: 4, sourceId: 5 }
{
inboxId: 1,
message: { content: 'hi' },
contactId: 4,
sourceId: 5,
mailSubject: 'Mail Subject',
}
);
expect(commit.mock.calls).toEqual([
[types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG, { isCreating: true }],
@ -65,7 +71,13 @@ describe('#actions', () => {
await expect(
actions.create(
{ commit },
{ inboxId: 1, message: { content: 'hi' }, contactId: 4, sourceId: 5 }
{
inboxId: 1,
message: { content: 'hi' },
contactId: 4,
sourceId: 5,
mailSubject: 'Mail Subject',
}
)
).rejects.toThrow(Error);
expect(commit.mock.calls).toEqual([

View file

@ -12,7 +12,6 @@ class ConversationReplyMailer < ApplicationMailer
new_messages = @conversation.messages.chat.where('id >= ?', last_queued_id)
@messages = recap_messages + new_messages
@messages = @messages.select(&:email_reply_summarizable?)
mail({
to: @contact&.email,
from: from_email_with_name,
@ -110,14 +109,15 @@ class ConversationReplyMailer < ApplicationMailer
end
def mail_subject
return "Re: #{incoming_mail_subject}" if incoming_mail_subject
subject = @conversation.additional_attributes['mail_subject']
return "[##{@conversation.display_id}] #{I18n.t('conversations.reply.email_subject')}" if subject.nil?
subject_line = I18n.t('conversations.reply.email_subject')
"[##{@conversation.display_id}] #{subject_line}"
end
def incoming_mail_subject
@incoming_mail_subject ||= @conversation.additional_attributes['mail_subject']
chat_count = @conversation.messages.chat.count
if chat_count > 1
"Re: #{subject}"
else
subject
end
end
def reply_email

View file

@ -25,6 +25,15 @@ RSpec.describe ConversationReplyMailer, type: :mailer do
bcc_emails: 'agent_bcc1@example.com'
})
end
let(:new_message) do
create(:message,
account: account,
conversation: conversation,
content_attributes: {
cc_emails: 'agent_cc2@example.com',
bcc_emails: 'agent_bcc2@example.com'
})
end
let(:cc_message) do
create(:message,
account: account,
@ -39,10 +48,17 @@ RSpec.describe ConversationReplyMailer, type: :mailer do
let(:mail) { described_class.reply_with_summary(message.conversation, message.id).deliver_now }
let(:cc_mail) { described_class.reply_with_summary(cc_message.conversation, message.id).deliver_now }
it 'renders the subject' do
it 'renders the default subject' do
expect(mail.subject).to eq("[##{message.conversation.display_id}] New messages on this conversation")
end
it 'renders the subject in conversation as reply' do
conversation.additional_attributes = { 'mail_subject': 'Mail Subject' }
conversation.save
new_message.save
expect(mail.subject).to eq('Re: Mail Subject')
end
it 'not have private notes' do
# make the message private
private_message.private = true
@ -91,10 +107,16 @@ RSpec.describe ConversationReplyMailer, type: :mailer do
message_2.save
end
it 'renders the subject' do
it 'renders the default subject' do
expect(mail.subject).to eq("[##{message_2.conversation.display_id}] New messages on this conversation")
end
it 'renders the subject in conversation' do
conversation.additional_attributes = { 'mail_subject': 'Mail Subject' }
conversation.save
expect(mail.subject).to eq('Mail Subject')
end
it 'not have private notes' do
# make the message private
private_message.private = true