feat: New automation actions (#4033)

This commit is contained in:
Fayaz Ahmed 2022-03-29 13:27:16 +05:30 committed by GitHub
parent cffc984ef6
commit c674393c02
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 192 additions and 35 deletions

View file

@ -34,7 +34,7 @@ class Api::V1::Accounts::AutomationRulesController < Api::V1::Accounts::BaseCont
params.permit( params.permit(
:name, :description, :event_name, :account_id, :active, :name, :description, :event_name, :account_id, :active,
conditions: [:attribute_key, :filter_operator, :query_operator, { values: [] }], conditions: [:attribute_key, :filter_operator, :query_operator, { values: [] }],
actions: [:action_name, { action_params: [{}] }] actions: [:action_name, { action_params: [] }]
) )
end end

View file

@ -7,7 +7,8 @@
<select <select
v-model="action_name" v-model="action_name"
class="action__question" class="action__question"
@change="resetFilter()" :class="{ 'full-width': !inputType }"
@change="resetAction()"
> >
<option <option
v-for="attribute in actionTypes" v-for="attribute in actionTypes"
@ -18,19 +19,38 @@
</option> </option>
</select> </select>
<div class="filter__answer--wrap"> <div class="filter__answer--wrap">
<div class="multiselect-wrap--small"> <div v-if="inputType">
<multiselect <div
v-if="inputType === 'multi_select'"
class="multiselect-wrap--small"
>
<multiselect
v-model="action_params"
track-by="id"
label="name"
:placeholder="'Select'"
:multiple="true"
selected-label
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
deselect-label=""
:max-height="160"
:options="dropdownValues"
:allow-empty="false"
/>
</div>
<input
v-else-if="inputType === 'email'"
v-model="action_params" v-model="action_params"
track-by="id" type="email"
label="name" class="answer--text-input"
:placeholder="'Select'" placeholder="Enter email"
:multiple="true" />
selected-label <input
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')" v-else-if="inputType === 'url'"
deselect-label="" v-model="action_params"
:max-height="160" type="url"
:options="dropdownValues" class="answer--text-input"
:allow-empty="false" placeholder="Enter url"
/> />
</div> </div>
</div> </div>
@ -91,13 +111,17 @@ export default {
this.$emit('input', { ...payload, action_params: value }); this.$emit('input', { ...payload, action_params: value });
}, },
}, },
inputType() {
return this.actionTypes.find(action => action.key === this.action_name)
.inputType;
},
}, },
methods: { methods: {
removeAction() { removeAction() {
this.$emit('removeAction'); this.$emit('removeAction');
}, },
resetFilter() { resetAction() {
this.$emit('resetFilter'); this.$emit('resetAction');
}, },
}, },
}; };
@ -136,6 +160,10 @@ export default {
max-width: 50%; max-width: 50%;
} }
.action__question.full-width {
max-width: 100%;
}
.filter__answer--wrap { .filter__answer--wrap {
margin-right: var(--space-smaller); margin-right: var(--space-smaller);
flex-grow: 1; flex-grow: 1;

View file

@ -101,6 +101,7 @@
getActionDropdownValues(automation.actions[i].action_name) getActionDropdownValues(automation.actions[i].action_name)
" "
:v="$v.automation.actions.$each[i]" :v="$v.automation.actions.$each[i]"
@resetAction="resetAction(i)"
@removeAction="removeAction(i)" @removeAction="removeAction(i)"
/> />
<div class="filter-actions"> <div class="filter-actions">
@ -187,7 +188,13 @@ export default {
required, required,
$each: { $each: {
action_params: { action_params: {
required, required: requiredIf(prop => {
return !(
prop.action_name === 'mute_conversation' ||
prop.action_name === 'snooze_convresation' ||
prop.action_name === 'resolve_convresation'
);
}),
}, },
}, },
}, },
@ -351,7 +358,6 @@ export default {
getActionDropdownValues(type) { getActionDropdownValues(type) {
switch (type) { switch (type) {
case 'assign_team': case 'assign_team':
case 'send_email_to_team':
return this.$store.getters['teams/getTeams']; return this.$store.getters['teams/getTeams'];
case 'add_label': case 'add_label':
return this.$store.getters['labels/getLabels'].map(i => { return this.$store.getters['labels/getLabels'].map(i => {
@ -424,6 +430,9 @@ export default {
).filterOperators[0].value; ).filterOperators[0].value;
this.automation.conditions[index].values = ''; this.automation.conditions[index].values = '';
}, },
resetAction(index) {
this.automation.actions[index].action_params = [];
},
showUserInput(operatorType) { showUserInput(operatorType) {
if (operatorType === 'is_present' || operatorType === 'is_not_present') if (operatorType === 'is_present' || operatorType === 'is_not_present')
return false; return false;

View file

@ -351,7 +351,6 @@ export default {
getActionDropdownValues(type) { getActionDropdownValues(type) {
switch (type) { switch (type) {
case 'assign_team': case 'assign_team':
case 'send_email_to_team':
return this.$store.getters['teams/getTeams']; return this.$store.getters['teams/getTeams'];
case 'add_label': case 'add_label':
return this.$store.getters['labels/getLabels'].map(i => { return this.$store.getters['labels/getLabels'].map(i => {

View file

@ -79,8 +79,33 @@ export const AUTOMATIONS = {
// { // {
// key: 'send_email_to_team', // key: 'send_email_to_team',
// name: 'Send an email to team', // name: 'Send an email to team',
// attributeI18nKey: 'SEND_EMAIL_TO_TEAM', // attributeI18nKey: 'SEND_MESSAGE',
// }, // },
{
key: 'send_email_transcript',
name: 'Send an email transcript',
attributeI18nKey: 'SEND_EMAIL_TRANSCRIPT',
},
{
key: 'mute_conversation',
name: 'Mute conversation',
attributeI18nKey: 'MUTE_CONVERSATION',
},
{
key: 'snooze_convresation',
name: 'Snooze conversation',
attributeI18nKey: 'MUTE_CONVERSATION',
},
{
key: 'resolve_convresation',
name: 'Resolve conversation',
attributeI18nKey: 'RESOLVE_CONVERSATION',
},
{
key: 'send_webhook_event',
name: 'Send Webhook Event',
attributeI18nKey: 'SEND_WEBHOOK_EVENT',
},
], ],
}, },
conversation_created: { conversation_created: {
@ -120,15 +145,40 @@ export const AUTOMATIONS = {
name: 'Assign a team', name: 'Assign a team',
attributeI18nKey: 'ASSIGN_TEAM', attributeI18nKey: 'ASSIGN_TEAM',
}, },
{
key: 'assign_agent',
name: 'Assign an agent',
attributeI18nKey: 'ASSIGN_AGENT',
},
// { // {
// key: 'send_email_to_team', // key: 'send_email_to_team',
// name: 'Send an email to team', // name: 'Send an email to team',
// attributeI18nKey: 'SEND_MESSAGE', // attributeI18nKey: 'SEND_MESSAGE',
// }, // },
{ {
key: 'assign_agent', key: 'send_email_transcript',
name: 'Assign an agent', name: 'Send an email transcript',
attributeI18nKey: 'ASSIGN_AGENT', attributeI18nKey: 'SEND_EMAIL_TRANSCRIPT',
},
{
key: 'mute_conversation',
name: 'Mute conversation',
attributeI18nKey: 'MUTE_CONVERSATION',
},
{
key: 'snooze_convresation',
name: 'Snooze conversation',
attributeI18nKey: 'MUTE_CONVERSATION',
},
{
key: 'resolve_convresation',
name: 'Resolve conversation',
attributeI18nKey: 'RESOLVE_CONVERSATION',
},
{
key: 'send_webhook_event',
name: 'Send Webhook Event',
attributeI18nKey: 'SEND_WEBHOOK_EVENT',
}, },
], ],
}, },
@ -183,16 +233,40 @@ export const AUTOMATIONS = {
name: 'Assign a team', name: 'Assign a team',
attributeI18nKey: 'ASSIGN_TEAM', attributeI18nKey: 'ASSIGN_TEAM',
}, },
// {
// key: 'send_email_to_team',
// name: 'Send an email to team',
// attributeI18nKey: 'SEND_EMAIL_TO_TEAM',
// },
{ {
key: 'assign_agent', key: 'assign_agent',
name: 'Assign an agent', name: 'Assign an agent',
attributeI18nKey: 'ASSIGN_AGENT', attributeI18nKey: 'ASSIGN_AGENT',
attributeKey: 'assignee_id', },
// {
// key: 'send_email_to_team',
// name: 'Send an email to team',
// attributeI18nKey: 'SEND_MESSAGE',
// },
{
key: 'send_email_transcript',
name: 'Send an email transcript',
attributeI18nKey: 'SEND_EMAIL_TRANSCRIPT',
},
{
key: 'mute_conversation',
name: 'Mute conversation',
attributeI18nKey: 'MUTE_CONVERSATION',
},
{
key: 'snooze_convresation',
name: 'Snooze conversation',
attributeI18nKey: 'MUTE_CONVERSATION',
},
{
key: 'resolve_convresation',
name: 'Resolve conversation',
attributeI18nKey: 'RESOLVE_CONVERSATION',
},
{
key: 'send_webhook_event',
name: 'Send Webhook Event',
attributeI18nKey: 'SEND_WEBHOOK_EVENT',
}, },
], ],
}, },
@ -217,13 +291,41 @@ export const AUTOMATION_ACTION_TYPES = [
{ {
key: 'assign_team', key: 'assign_team',
label: 'Assign a team', label: 'Assign a team',
inputType: 'multi_select',
}, },
{ {
key: 'add_label', key: 'add_label',
label: 'Add a label', label: 'Add a label',
inputType: 'multi_select',
}, },
// { // {
// key: 'send_email_to_team', // key: 'send_email_to_team',
// label: 'Send an email to team', // label: 'Send an email to team',
// inputType: 'multi_select',
// }, // },
{
key: 'send_email_transcript',
label: 'Send an email transcript',
inputType: 'email',
},
{
key: 'mute_conversation',
label: 'Mute conversation',
inputType: null,
},
{
key: 'snooze_convresation',
label: 'Snooze conversation',
inputType: null,
},
{
key: 'resolve_convresation',
label: 'Resolve conversation',
inputType: null,
},
{
key: 'send_webhook_event',
label: 'Send Webhook Event',
inputType: 'url',
},
]; ];

View file

@ -10,6 +10,8 @@ module ActivityMessageHandler
end end
def status_change_activity(user_name) def status_change_activity(user_name)
return send_automation_activity if Current.executed_by.present?
create_status_change_message(user_name) create_status_change_message(user_name)
end end
@ -29,6 +31,11 @@ module ActivityMessageHandler
::Conversations::ActivityMessageJob.perform_later(self, activity_message_params(content)) if content ::Conversations::ActivityMessageJob.perform_later(self, activity_message_params(content)) if content
end end
def send_automation_activity
content = I18n.t("conversations.activity.status.#{status}", user_name: 'Automation System')
::Conversations::ActivityMessageJob.perform_later(self, activity_message_params(content)) if content
end
def create_label_added(user_name, labels = []) def create_label_added(user_name, labels = [])
return unless labels.size.positive? return unless labels.size.positive?

View file

@ -21,21 +21,27 @@ class AutomationRules::ActionService
private private
def send_email_transcript(email) def send_email_transcript(emails)
ConversationReplyMailer.with(account: conversation.account).conversation_transcript(@conversation, email)&.deliver_later emails.each do |email|
ConversationReplyMailer.with(account: @conversation.account).conversation_transcript(@conversation, email)&.deliver_later
end
end end
def mute_conversation(_params) def mute_conversation(_params)
@conversation.mute! @conversation.mute!
end end
def snooze_conversation(_params)
@conversation.ensure_snooze_until_reset
end
def change_status(status) def change_status(status)
@conversation.update!(status: status[0]) @conversation.update!(status: status[0])
end end
def send_webhook_events(webhook_url) def send_webhook_event(webhook_url)
payload = @conversation.webhook_data.merge(event: "automation_event: #{@rule.event_name}") payload = @conversation.webhook_data.merge(event: "automation_event: #{@rule.event_name}")
WebhookJob.perform_later(webhook_url, payload) WebhookJob.perform_later(webhook_url[0], payload)
end end
def send_message(message) def send_message(message)

View file

@ -28,7 +28,7 @@ class FilterService
@filter_values["value_#{current_index}"] = filter_values(query_hash) @filter_values["value_#{current_index}"] = filter_values(query_hash)
equals_to_filter_string(query_hash[:filter_operator], current_index) equals_to_filter_string(query_hash[:filter_operator], current_index)
when 'contains', 'does_not_contain' when 'contains', 'does_not_contain'
@filter_values["value_#{current_index}"] = "%#{filter_values(query_hash)}%" @filter_values["value_#{current_index}"] = "%#{string_filter_values(query_hash)}%"
like_filter_string(query_hash[:filter_operator], current_index) like_filter_string(query_hash[:filter_operator], current_index)
when 'is_present' when 'is_present'
@filter_values["value_#{current_index}"] = 'IS NOT NULL' @filter_values["value_#{current_index}"] = 'IS NOT NULL'
@ -57,6 +57,12 @@ class FilterService
end end
end end
def string_filter_values(query_hash)
return query_hash['values'][0] if query_hash['values'].is_a?(Array)
query_hash['values']
end
def lt_gt_filter_values(query_hash) def lt_gt_filter_values(query_hash)
attribute_key = query_hash[:attribute_key] attribute_key = query_hash[:attribute_key]
attribute_type = custom_attribute(attribute_key).try(:attribute_display_type) attribute_type = custom_attribute(attribute_key).try(:attribute_display_type)

View file

@ -31,7 +31,7 @@ describe AutomationRuleListener do
}, },
{ 'action_name' => 'assign_team', 'action_params' => [team.id] }, { 'action_name' => 'assign_team', 'action_params' => [team.id] },
{ 'action_name' => 'add_label', 'action_params' => %w[support priority_customer] }, { 'action_name' => 'add_label', 'action_params' => %w[support priority_customer] },
{ 'action_name' => 'send_webhook_events', 'action_params' => 'https://www.example.com' }, { 'action_name' => 'send_webhook_event', 'action_params' => ['https://www.example.com'] },
{ 'action_name' => 'assign_best_agent', 'action_params' => [user_1.id] }, { 'action_name' => 'assign_best_agent', 'action_params' => [user_1.id] },
{ 'action_name' => 'send_email_transcript', 'action_params' => 'new_agent@example.com' }, { 'action_name' => 'send_email_transcript', 'action_params' => 'new_agent@example.com' },
{ 'action_name' => 'mute_conversation', 'action_params' => nil }, { 'action_name' => 'mute_conversation', 'action_params' => nil },