feat: Ability to specify a subject line for outbound emails (#3168)
This commit is contained in:
parent
2f2d2b4f73
commit
46867e89cb
6 changed files with 83 additions and 14 deletions
|
@ -155,6 +155,11 @@
|
||||||
"LABEL": "Inbox",
|
"LABEL": "Inbox",
|
||||||
"ERROR": "Select an inbox"
|
"ERROR": "Select an inbox"
|
||||||
},
|
},
|
||||||
|
"SUBJECT": {
|
||||||
|
"LABEL": "Subject",
|
||||||
|
"PLACEHOLDER": "Subject",
|
||||||
|
"ERROR": "Subject can't be empty"
|
||||||
|
},
|
||||||
"MESSAGE": {
|
"MESSAGE": {
|
||||||
"LABEL": "Message",
|
"LABEL": "Message",
|
||||||
"PLACEHOLDER": "Write your message here",
|
"PLACEHOLDER": "Write your message here",
|
||||||
|
|
|
@ -41,6 +41,22 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</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="row">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<label :class="{ error: $v.message.$error }">
|
<label :class="{ error: $v.message.$error }">
|
||||||
|
@ -75,6 +91,7 @@ import { mapGetters } from 'vuex';
|
||||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail';
|
import Thumbnail from 'dashboard/components/widgets/Thumbnail';
|
||||||
|
|
||||||
import alertMixin from 'shared/mixins/alertMixin';
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
|
import { INBOX_TYPES } from 'shared/mixins/inboxMixin';
|
||||||
import { ExceptionWithMessage } from 'shared/helpers/CustomErrors';
|
import { ExceptionWithMessage } from 'shared/helpers/CustomErrors';
|
||||||
import { required } from 'vuelidate/lib/validators';
|
import { required } from 'vuelidate/lib/validators';
|
||||||
|
|
||||||
|
@ -96,11 +113,15 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
name: '',
|
name: '',
|
||||||
|
subject: '',
|
||||||
message: '',
|
message: '',
|
||||||
selectedInbox: '',
|
selectedInbox: '',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
validations: {
|
validations: {
|
||||||
|
subject: {
|
||||||
|
required,
|
||||||
|
},
|
||||||
message: {
|
message: {
|
||||||
required,
|
required,
|
||||||
},
|
},
|
||||||
|
@ -119,6 +140,7 @@ export default {
|
||||||
sourceId: this.targetInbox.source_id,
|
sourceId: this.targetInbox.source_id,
|
||||||
contactId: this.contact.id,
|
contactId: this.contact.id,
|
||||||
message: { content: this.message },
|
message: { content: this.message },
|
||||||
|
mailSubject: this.subject,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
targetInbox: {
|
targetInbox: {
|
||||||
|
@ -138,6 +160,12 @@ export default {
|
||||||
inboxes() {
|
inboxes() {
|
||||||
return this.contact.contactableInboxes || [];
|
return this.contact.contactableInboxes || [];
|
||||||
},
|
},
|
||||||
|
isAnEmailInbox() {
|
||||||
|
return (
|
||||||
|
this.selectedInbox &&
|
||||||
|
this.selectedInbox.inbox.channel_type === INBOX_TYPES.EMAIL
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onCancel() {
|
onCancel() {
|
||||||
|
@ -154,7 +182,6 @@ export default {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const payload = this.getNewConversation;
|
const payload = this.getNewConversation;
|
||||||
|
|
||||||
await this.onSubmit(payload);
|
await this.onSubmit(payload);
|
||||||
this.onSuccess();
|
this.onSuccess();
|
||||||
this.showAlert(this.$t('NEW_CONVERSATION.FORM.SUCCESS_MESSAGE'));
|
this.showAlert(this.$t('NEW_CONVERSATION.FORM.SUCCESS_MESSAGE'));
|
||||||
|
|
|
@ -24,12 +24,15 @@ export const actions = {
|
||||||
commit(types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG, {
|
commit(types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG, {
|
||||||
isCreating: true,
|
isCreating: true,
|
||||||
});
|
});
|
||||||
const { inboxId, message, contactId, sourceId } = params;
|
const { inboxId, message, contactId, sourceId, mailSubject } = params;
|
||||||
try {
|
try {
|
||||||
const { data } = await ConversationApi.create({
|
const { data } = await ConversationApi.create({
|
||||||
inbox_id: inboxId,
|
inbox_id: inboxId,
|
||||||
contact_id: contactId,
|
contact_id: contactId,
|
||||||
source_id: sourceId,
|
source_id: sourceId,
|
||||||
|
additional_attributes: {
|
||||||
|
mail_subject: mailSubject,
|
||||||
|
},
|
||||||
message,
|
message,
|
||||||
});
|
});
|
||||||
commit(types.default.ADD_CONTACT_CONVERSATION, {
|
commit(types.default.ADD_CONTACT_CONVERSATION, {
|
||||||
|
|
|
@ -44,7 +44,13 @@ describe('#actions', () => {
|
||||||
axios.post.mockResolvedValue({ data: conversationList[0] });
|
axios.post.mockResolvedValue({ data: conversationList[0] });
|
||||||
await actions.create(
|
await actions.create(
|
||||||
{ commit },
|
{ 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([
|
expect(commit.mock.calls).toEqual([
|
||||||
[types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG, { isCreating: true }],
|
[types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG, { isCreating: true }],
|
||||||
|
@ -65,7 +71,13 @@ describe('#actions', () => {
|
||||||
await expect(
|
await expect(
|
||||||
actions.create(
|
actions.create(
|
||||||
{ commit },
|
{ 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);
|
).rejects.toThrow(Error);
|
||||||
expect(commit.mock.calls).toEqual([
|
expect(commit.mock.calls).toEqual([
|
||||||
|
|
|
@ -12,7 +12,6 @@ class ConversationReplyMailer < ApplicationMailer
|
||||||
new_messages = @conversation.messages.chat.where('id >= ?', last_queued_id)
|
new_messages = @conversation.messages.chat.where('id >= ?', last_queued_id)
|
||||||
@messages = recap_messages + new_messages
|
@messages = recap_messages + new_messages
|
||||||
@messages = @messages.select(&:email_reply_summarizable?)
|
@messages = @messages.select(&:email_reply_summarizable?)
|
||||||
|
|
||||||
mail({
|
mail({
|
||||||
to: @contact&.email,
|
to: @contact&.email,
|
||||||
from: from_email_with_name,
|
from: from_email_with_name,
|
||||||
|
@ -110,14 +109,15 @@ class ConversationReplyMailer < ApplicationMailer
|
||||||
end
|
end
|
||||||
|
|
||||||
def mail_subject
|
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')
|
chat_count = @conversation.messages.chat.count
|
||||||
"[##{@conversation.display_id}] #{subject_line}"
|
if chat_count > 1
|
||||||
end
|
"Re: #{subject}"
|
||||||
|
else
|
||||||
def incoming_mail_subject
|
subject
|
||||||
@incoming_mail_subject ||= @conversation.additional_attributes['mail_subject']
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def reply_email
|
def reply_email
|
||||||
|
|
|
@ -25,6 +25,15 @@ RSpec.describe ConversationReplyMailer, type: :mailer do
|
||||||
bcc_emails: 'agent_bcc1@example.com'
|
bcc_emails: 'agent_bcc1@example.com'
|
||||||
})
|
})
|
||||||
end
|
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
|
let(:cc_message) do
|
||||||
create(:message,
|
create(:message,
|
||||||
account: account,
|
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(: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 }
|
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")
|
expect(mail.subject).to eq("[##{message.conversation.display_id}] New messages on this conversation")
|
||||||
end
|
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
|
it 'not have private notes' do
|
||||||
# make the message private
|
# make the message private
|
||||||
private_message.private = true
|
private_message.private = true
|
||||||
|
@ -91,10 +107,16 @@ RSpec.describe ConversationReplyMailer, type: :mailer do
|
||||||
message_2.save
|
message_2.save
|
||||||
end
|
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")
|
expect(mail.subject).to eq("[##{message_2.conversation.display_id}] New messages on this conversation")
|
||||||
end
|
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
|
it 'not have private notes' do
|
||||||
# make the message private
|
# make the message private
|
||||||
private_message.private = true
|
private_message.private = true
|
||||||
|
|
Loading…
Reference in a new issue