feat: Use display_name instead of name of the agent (#1097)

* feat: Use display_name instead of name of the agent
This commit is contained in:
Pranav Raj S 2020-07-27 22:19:26 +05:30 committed by GitHub
parent f30c8943d9
commit 2b1d445003
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 80 additions and 41 deletions

View file

@ -16,6 +16,14 @@ class Api::V1::ProfilesController < Api::BaseController
end end
def profile_params def profile_params
params.require(:profile).permit(:email, :name, :password, :password_confirmation, :avatar, :availability) params.require(:profile).permit(
:email,
:name,
:display_name,
:password,
:password_confirmation,
:avatar,
:availability
)
end end
end end

View file

@ -118,7 +118,12 @@ export default {
return axios.post(urlData.url, { email }); return axios.post(urlData.url, { email });
}, },
profileUpdate({ password, password_confirmation, ...profileAttributes }) { profileUpdate({
password,
password_confirmation,
displayName,
...profileAttributes
}) {
const formData = new FormData(); const formData = new FormData();
Object.keys(profileAttributes).forEach(key => { Object.keys(profileAttributes).forEach(key => {
const value = profileAttributes[key]; const value = profileAttributes[key];
@ -126,6 +131,7 @@ export default {
formData.append(`profile[${key}]`, value); formData.append(`profile[${key}]`, value);
} }
}); });
formData.append('profile[display_name]', displayName || '');
if (password && password_confirmation) { if (password && password_confirmation) {
formData.append('profile[password]', password); formData.append('profile[password]', password);
formData.append('profile[password_confirmation]', password_confirmation); formData.append('profile[password_confirmation]', password_confirmation);

View file

@ -58,12 +58,12 @@
<div class="current-user" @click.prevent="showOptions()"> <div class="current-user" @click.prevent="showOptions()">
<thumbnail <thumbnail
:src="currentUser.avatar_url" :src="currentUser.avatar_url"
:username="currentUser.name" :username="currentUserAvailableName"
:status="currentUser.availability_status" :status="currentUser.availability_status"
/> />
<div class="current-user--data"> <div class="current-user--data">
<h3 class="current-user--name"> <h3 class="current-user--name">
{{ currentUser.name }} {{ currentUserAvailableName }}
</h3> </h3>
<h5 class="current-user--role"> <h5 class="current-user--role">
{{ currentRole }} {{ currentRole }}
@ -202,6 +202,10 @@ export default {
uiFlags: 'agents/getUIFlags', uiFlags: 'agents/getUIFlags',
accountLabels: 'labels/getLabelsOnSidebar', accountLabels: 'labels/getLabelsOnSidebar',
}), }),
currentUserAvailableName() {
const { available_name: availableName } = this.currentUser;
return availableName;
},
showChangeAccountOption() { showChangeAccountOption() {
if (this.globalConfig.createNewAccountFromDashboard) { if (this.globalConfig.createNewAccountFromDashboard) {
return true; return true;

View file

@ -25,7 +25,7 @@
<multiselect <multiselect
v-model="currentChat.meta.assignee" v-model="currentChat.meta.assignee"
:options="agentList" :options="agentList"
label="name" label="available_name"
:allow-empty="true" :allow-empty="true"
deselect-label="Remove" deselect-label="Remove"
placeholder="Select Agent" placeholder="Select Agent"
@ -95,7 +95,7 @@ export default {
return [ return [
{ {
confirmed: true, confirmed: true,
name: 'None', available_name: 'None',
id: 0, id: 0,
role: 'agent', role: 'agent',
account_id: 0, account_id: 0,

View file

@ -95,10 +95,13 @@ export default {
: false; : false;
}, },
sentByMessage() { sentByMessage() {
return this.data.message_type === 1 && const { sender } = this.data;
!this.isHovered &&
this.data.sender !== undefined return this.data.message_type === 1 && !this.isHovered && sender
? { content: `Sent by: ${this.data.sender.name}`, classes: 'top' } ? {
content: `Sent by: ${sender.available_name || sender.name}`,
classes: 'top',
}
: false; : false;
}, },
wrapClass() { wrapClass() {

View file

@ -44,9 +44,14 @@
"LABEL": "Profile Image" "LABEL": "Profile Image"
}, },
"NAME": { "NAME": {
"LABEL": "Your name", "LABEL": "Your full name",
"ERROR": "Please enter a valid name", "ERROR": "Please enter a valid full name",
"PLACEHOLDER": "Please enter your name, this would be displayed in conversations" "PLACEHOLDER": "Please enter your full name"
},
"DISPLAY_NAME": {
"LABEL": "Display name",
"ERROR": "Please enter a valid display name",
"PLACEHOLDER": "Please enter a display name, this would be displayed in conversations"
}, },
"AVAILABILITY": { "AVAILABILITY": {
"LABEL": "Availability", "LABEL": "Availability",

View file

@ -291,16 +291,9 @@ export default {
inboxId: this.currentInboxId, inboxId: this.currentInboxId,
}); });
const { const {
data: { payload }, data: { payload: inboxMembers },
} = response; } = response;
payload.forEach(el => { this.selectedAgents = inboxMembers;
const [item] = this.agentList.filter(
agent => agent.id === el.user_id
);
if (item) {
this.selectedAgents.push(item);
}
});
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }

View file

@ -26,6 +26,17 @@
{{ $t('PROFILE_SETTINGS.FORM.NAME.ERROR') }} {{ $t('PROFILE_SETTINGS.FORM.NAME.ERROR') }}
</span> </span>
</label> </label>
<label :class="{ error: $v.displayName.$error }">
{{ $t('PROFILE_SETTINGS.FORM.DISPLAY_NAME.LABEL') }}
<input
v-model="displayName"
type="text"
:placeholder="
$t('PROFILE_SETTINGS.FORM.DISPLAY_NAME.PLACEHOLDER')
"
@input="$v.displayName.$touch"
/>
</label>
<label :class="{ error: $v.email.$error }"> <label :class="{ error: $v.email.$error }">
{{ $t('PROFILE_SETTINGS.FORM.EMAIL.LABEL') }} {{ $t('PROFILE_SETTINGS.FORM.EMAIL.LABEL') }}
<input <input
@ -130,6 +141,7 @@ export default {
avatarFile: '', avatarFile: '',
avatarUrl: '', avatarUrl: '',
name: '', name: '',
displayName: '',
email: '', email: '',
password: '', password: '',
passwordConfirmation: '', passwordConfirmation: '',
@ -141,6 +153,7 @@ export default {
name: { name: {
required, required,
}, },
displayName: {},
email: { email: {
required, required,
email, email,
@ -188,6 +201,7 @@ export default {
this.email = this.currentUser.email; this.email = this.currentUser.email;
this.avatarUrl = this.currentUser.avatar_url; this.avatarUrl = this.currentUser.avatar_url;
this.availability = this.currentUser.availability_status; this.availability = this.currentUser.availability_status;
this.displayName = this.currentUser.display_name;
}, },
async updateUser() { async updateUser() {
this.$v.$touch(); this.$v.$touch();
@ -203,6 +217,7 @@ export default {
email: this.email, email: this.email,
avatar: this.avatarFile, avatar: this.avatarFile,
password: this.password, password: this.password,
displayName: this.displayName,
availability: this.availability, availability: this.availability,
password_confirmation: this.passwordConfirmation, password_confirmation: this.passwordConfirmation,
}); });

View file

@ -21,7 +21,7 @@ const getters = {
getMineChats(_state) { getMineChats(_state) {
const currentUserID = authAPI.getCurrentUser().id; const currentUserID = authAPI.getCurrentUser().id;
return _state.allConversations.filter(chat => return _state.allConversations.filter(chat =>
chat.meta.assignee === null !chat.meta.assignee
? false ? false
: chat.status === _state.chatStatusFilter && : chat.status === _state.chatStatusFilter &&
chat.meta.assignee.id === currentUserID chat.meta.assignee.id === currentUserID
@ -29,8 +29,7 @@ const getters = {
}, },
getUnAssignedChats(_state) { getUnAssignedChats(_state) {
return _state.allConversations.filter( return _state.allConversations.filter(
chat => chat => !chat.meta.assignee && chat.status === _state.chatStatusFilter
chat.meta.assignee === null && chat.status === _state.chatStatusFilter
); );
}, },
getAllStatusChats(_state) { getAllStatusChats(_state) {

View file

@ -56,7 +56,7 @@ class ConversationReplyMailer < ApplicationMailer
def reply_email def reply_email
if inbound_email_enabled? if inbound_email_enabled?
"#{@agent.name} <reply+#{@conversation.uuid}@#{current_domain}>" "#{@agent.available_name} <reply+#{@conversation.uuid}@#{current_domain}>"
else else
@agent&.email @agent&.email
end end
@ -64,9 +64,9 @@ class ConversationReplyMailer < ApplicationMailer
def from_email def from_email
if inbound_email_enabled? if inbound_email_enabled?
"#{@agent.name} <#{account_support_email}>" "#{@agent.available_name} <#{account_support_email}>"
else else
"#{@agent.name} <#{ENV.fetch('MAILER_SENDER_EMAIL', 'accounts@chatwoot.com')}>" "#{@agent.available_name} <#{ENV.fetch('MAILER_SENDER_EMAIL', 'accounts@chatwoot.com')}>"
end end
end end

View file

@ -152,7 +152,7 @@ class Conversation < ApplicationRecord
def create_activity def create_activity
return unless Current.user return unless Current.user
user_name = Current.user&.name user_name = Current.user&.available_name
create_status_change_message(user_name) if saved_change_to_status? create_status_change_message(user_name) if saved_change_to_status?
create_assignee_change(user_name) if saved_change_to_assignee_id? create_assignee_change(user_name) if saved_change_to_assignee_id?
@ -208,7 +208,7 @@ class Conversation < ApplicationRecord
end end
def create_assignee_change(user_name) def create_assignee_change(user_name)
params = { assignee_name: assignee&.name, user_name: user_name }.compact params = { assignee_name: assignee&.available_name, user_name: user_name }.compact
key = assignee_id ? 'assigned' : 'removed' key = assignee_id ? 'assigned' : 'removed'
content = I18n.t("conversations.activity.assignee.#{key}", **params) content = I18n.t("conversations.activity.assignee.#{key}", **params)

View file

@ -97,7 +97,7 @@ class User < ApplicationRecord
account_users.find_by(account_id: Current.account.id) if Current.account account_users.find_by(account_id: Current.account.id) if Current.account
end end
def display_name def available_name
self[:display_name].presence || name self[:display_name].presence || name
end end
@ -134,6 +134,7 @@ class User < ApplicationRecord
{ {
id: id, id: id,
name: name, name: name,
available_name: available_name,
avatar_url: avatar_url, avatar_url: avatar_url,
type: 'user', type: 'user',
availability_status: availability_status availability_status: availability_status

View file

@ -25,7 +25,7 @@ class Conversations::EventDataPresenter < SimpleDelegator
end end
def push_meta def push_meta
{ sender: contact.push_event_data, assignee: assignee } { sender: contact.push_event_data, assignee: assignee&.push_event_data }
end end
def push_timestamps def push_timestamps

View file

@ -1,6 +1,5 @@
json.payload do json.payload do
json.array! @agents do |agent| json.array! @agents do |agent|
json.user_id agent.id json.partial! 'api/v1/models/user.json.jbuilder', resource: agent
json.name agent.name
end end
end end

View file

@ -3,7 +3,11 @@ json.meta do
json.partial! 'api/v1/models/contact.json.jbuilder', resource: conversation.contact json.partial! 'api/v1/models/contact.json.jbuilder', resource: conversation.contact
end end
json.channel conversation.inbox.try(:channel_type) json.channel conversation.inbox.try(:channel_type)
json.assignee conversation.assignee if conversation.assignee
json.assignee do
json.partial! 'api/v1/models/user.json.jbuilder', resource: conversation.assignee
end
end
end end
json.id conversation.display_id json.id conversation.display_id

View file

@ -2,6 +2,7 @@ json.account_id resource.account.id
json.availability_status resource.availability_status json.availability_status resource.availability_status
json.confirmed resource.confirmed? json.confirmed resource.confirmed?
json.email resource.email json.email resource.email
json.available_name resource.available_name
json.id resource.id json.id resource.id
json.name resource.name json.name resource.name
json.role resource.role json.role resource.role

View file

@ -3,6 +3,7 @@ json.provider resource.provider
json.uid resource.uid json.uid resource.uid
json.name resource.name json.name resource.name
json.display_name resource.display_name json.display_name resource.display_name
json.available_name resource.available_name
json.email resource.email json.email resource.email
json.account_id resource.active_account_user&.account_id json.account_id resource.active_account_user&.account_id
json.pubsub_token resource.pubsub_token json.pubsub_token resource.pubsub_token

View file

@ -1,7 +1,7 @@
json.payload do json.payload do
json.array! @inbox_members do |inbox_member| json.array! @inbox_members do |inbox_member|
json.id inbox_member.user.id json.id inbox_member.user.id
json.name inbox_member.user.name json.name inbox_member.user.available_name
json.avatar_url inbox_member.user.avatar_url json.avatar_url inbox_member.user.avatar_url
json.availability_status inbox_member.user.availability_status json.availability_status inbox_member.user.availability_status
end end

View file

@ -5,7 +5,7 @@
<% @messages.each do |message| %> <% @messages.each do |message| %>
<tr> <tr>
<td> <td>
<b><%= message.incoming? ? 'You' : message.sender.name %></b> <b><%= message.incoming? ? 'You' : message.sender.available_name %></b>
</td> </td>
</tr> </tr>
<tr> <tr>

View file

@ -108,13 +108,13 @@ RSpec.describe ConversationReplyMailer, type: :mailer do
it 'sets reply to email to be based on the domain' do it 'sets reply to email to be based on the domain' do
reply_to_email = "reply+#{message.conversation.uuid}@#{conversation.account.domain}" reply_to_email = "reply+#{message.conversation.uuid}@#{conversation.account.domain}"
reply_to = "#{agent.name} <#{reply_to_email}>" reply_to = "#{agent.available_name} <#{reply_to_email}>"
expect(mail['REPLY-TO'].value).to eq(reply_to) expect(mail['REPLY-TO'].value).to eq(reply_to)
expect(mail.reply_to).to eq([reply_to_email]) expect(mail.reply_to).to eq([reply_to_email])
end end
it 'sets the from email to be the support email' do it 'sets the from email to be the support email' do
expect(mail['FROM'].value).to eq("#{agent.name} <#{conversation.account.support_email}>") expect(mail['FROM'].value).to eq("#{agent.available_name} <#{conversation.account.support_email}>")
expect(mail.from).to eq([conversation.account.support_email]) expect(mail.from).to eq([conversation.account.support_email])
end end

View file

@ -88,8 +88,8 @@ RSpec.describe Conversation, type: :model do
it 'creates conversation activities' do it 'creates conversation activities' do
# create_activity # create_activity
expect(conversation.messages.pluck(:content)).to include("Conversation was marked resolved by #{old_assignee.name}") expect(conversation.messages.pluck(:content)).to include("Conversation was marked resolved by #{old_assignee.available_name}")
expect(conversation.messages.pluck(:content)).to include("Assigned to #{new_assignee.name} by #{old_assignee.name}") expect(conversation.messages.pluck(:content)).to include("Assigned to #{new_assignee.available_name} by #{old_assignee.available_name}")
end end
end end