fix: Automation Bugs and minor enhancements (#3936)
This commit is contained in:
parent
e345a4486d
commit
5ad6db07b4
17 changed files with 203 additions and 129 deletions
|
@ -69,13 +69,13 @@ const settings = accountId => ({
|
|||
),
|
||||
toStateName: 'attributes_list',
|
||||
},
|
||||
// {
|
||||
// icon: 'automation',
|
||||
// label: 'AUTOMATION',
|
||||
// hasSubMenu: false,
|
||||
// toState: frontendURL(`accounts/${accountId}/settings/automation/list`),
|
||||
// toStateName: 'automation_list',
|
||||
// },
|
||||
{
|
||||
icon: 'automation',
|
||||
label: 'AUTOMATION',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(`accounts/${accountId}/settings/automation/list`),
|
||||
toStateName: 'automation_list',
|
||||
},
|
||||
{
|
||||
icon: 'chat-multiple',
|
||||
label: 'CANNED_RESPONSES',
|
||||
|
|
|
@ -15,6 +15,13 @@
|
|||
size="14"
|
||||
/>
|
||||
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
||||
<span
|
||||
v-if="menuItem.label === 'AUTOMATION'"
|
||||
data-view-component="true"
|
||||
label="Beta"
|
||||
class="beta"
|
||||
>Beta
|
||||
</span>
|
||||
</router-link>
|
||||
|
||||
<ul v-if="hasSubMenu" class="nested vertical menu">
|
||||
|
@ -221,4 +228,17 @@ export default {
|
|||
color: var(--w-500);
|
||||
}
|
||||
}
|
||||
.beta {
|
||||
padding-right: var(--space-smaller) !important;
|
||||
padding-left: var(--space-smaller) !important;
|
||||
margin-left: var(--space-half) !important;
|
||||
display: inline-block;
|
||||
font-size: var(--font-size-mini);
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 18px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 2em;
|
||||
color: var(--g-800);
|
||||
border-color: var(--g-700);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"AUTOMATION": {
|
||||
"HEADER": "Automation",
|
||||
"HEADER": "Automations",
|
||||
"HEADER_BTN_TXT": "Add Automation Rule",
|
||||
"LOADING": "Fetching automation rules",
|
||||
"SIDEBAR_TXT": "<p><b>Automation Rules</b> <p>Automation can replace and automate existing processes that require manual effort. You can do many things with automation, including adding labels and assigning conversation to the best agent. So the team focuses on what they do best and spends more little time on manual tasks.</p>",
|
||||
|
@ -64,7 +64,7 @@
|
|||
},
|
||||
"EDIT": {
|
||||
"TITLE": "Edit Automation Rule",
|
||||
"SUBMIT": "Edit",
|
||||
"SUBMIT": "Update",
|
||||
"CANCEL_BUTTON_TEXT": "Cancel",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Automation rule updated successfully",
|
||||
|
@ -84,6 +84,12 @@
|
|||
"DELETE": "Delete",
|
||||
"CANCEL": "Cancel",
|
||||
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
|
||||
},
|
||||
"CONDITION": {
|
||||
"DELETE_MESSAGE": "You need to have atleast one condition to save"
|
||||
},
|
||||
"ACTION": {
|
||||
"DELETE_MESSAGE": "You need to have atleast one action to save"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -351,7 +351,7 @@ export default {
|
|||
getActionDropdownValues(type) {
|
||||
switch (type) {
|
||||
case 'assign_team':
|
||||
case 'send_message':
|
||||
case 'send_email_to_team':
|
||||
return this.$store.getters['teams/getTeams'];
|
||||
case 'add_label':
|
||||
return this.$store.getters['labels/getLabels'].map(i => {
|
||||
|
@ -365,12 +365,24 @@ export default {
|
|||
}
|
||||
},
|
||||
appendNewCondition() {
|
||||
this.automation.conditions.push({
|
||||
attribute_key: 'status',
|
||||
filter_operator: 'equal_to',
|
||||
values: '',
|
||||
query_operator: 'and',
|
||||
});
|
||||
switch (this.automation.event_name) {
|
||||
case 'message_created':
|
||||
this.automation.conditions.push({
|
||||
attribute_key: 'message_type',
|
||||
filter_operator: 'equal_to',
|
||||
values: '',
|
||||
query_operator: 'and',
|
||||
});
|
||||
break;
|
||||
default:
|
||||
this.automation.conditions.push({
|
||||
attribute_key: 'status',
|
||||
filter_operator: 'equal_to',
|
||||
values: '',
|
||||
query_operator: 'and',
|
||||
});
|
||||
break;
|
||||
}
|
||||
},
|
||||
appendNewAction() {
|
||||
this.automation.actions.push({
|
||||
|
|
|
@ -121,10 +121,10 @@
|
|||
variant="clear"
|
||||
@click.prevent="onClose"
|
||||
>
|
||||
{{ $t('AUTOMATION.ADD.CANCEL_BUTTON_TEXT') }}
|
||||
{{ $t('AUTOMATION.EDIT.CANCEL_BUTTON_TEXT') }}
|
||||
</woot-button>
|
||||
<woot-button @click="submitAutomation">
|
||||
{{ $t('AUTOMATION.ADD.SUBMIT') }}
|
||||
{{ $t('AUTOMATION.EDIT.SUBMIT') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -351,9 +351,9 @@ export default {
|
|||
getActionDropdownValues(type) {
|
||||
switch (type) {
|
||||
case 'assign_team':
|
||||
case 'send_message':
|
||||
return this.$store.getters['teams/getTeams'];
|
||||
case 'add_label':
|
||||
case 'send_email_to_team':
|
||||
return this.$store.getters['teams/getTeams'];
|
||||
case 'add_label':
|
||||
return this.$store.getters['labels/getLabels'].map(i => {
|
||||
return {
|
||||
id: i.title,
|
||||
|
@ -365,12 +365,32 @@ export default {
|
|||
}
|
||||
},
|
||||
appendNewCondition() {
|
||||
this.automation.conditions.push({
|
||||
attribute_key: 'status',
|
||||
filter_operator: 'equal_to',
|
||||
values: '',
|
||||
query_operator: 'and',
|
||||
});
|
||||
if (
|
||||
!this.automation.conditions[this.automation.conditions.length - 1]
|
||||
.query_operator
|
||||
) {
|
||||
this.automation.conditions[
|
||||
this.automation.conditions.length - 1
|
||||
].query_operator = 'and';
|
||||
}
|
||||
switch (this.automation.event_name) {
|
||||
case 'message_created':
|
||||
this.automation.conditions.push({
|
||||
attribute_key: 'message_type',
|
||||
filter_operator: 'equal_to',
|
||||
values: '',
|
||||
query_operator: 'and',
|
||||
});
|
||||
break;
|
||||
default:
|
||||
this.automation.conditions.push({
|
||||
attribute_key: 'status',
|
||||
filter_operator: 'equal_to',
|
||||
values: '',
|
||||
query_operator: 'and',
|
||||
});
|
||||
break;
|
||||
}
|
||||
},
|
||||
appendNewAction() {
|
||||
this.automation.actions.push({
|
||||
|
@ -380,14 +400,14 @@ export default {
|
|||
},
|
||||
removeFilter(index) {
|
||||
if (this.automation.conditions.length <= 1) {
|
||||
this.showAlert(this.$t('FILTER.FILTER_DELETE_ERROR'));
|
||||
this.showAlert(this.$t('AUTOMATION.CONDITION.DELETE_MESSAGE'));
|
||||
} else {
|
||||
this.automation.conditions.splice(index, 1);
|
||||
}
|
||||
},
|
||||
removeAction(index) {
|
||||
if (this.automation.actions.length <= 1) {
|
||||
this.showAlert(this.$t('FILTER.FILTER_DELETE_ERROR'));
|
||||
this.showAlert(this.$t('AUTOMATION.ACTION.DELETE_MESSAGE'));
|
||||
} else {
|
||||
this.automation.actions.splice(index, 1);
|
||||
}
|
||||
|
@ -421,8 +441,9 @@ export default {
|
|||
const formattedConditions = automation.conditions.map(condition => {
|
||||
const inputType = this.automationTypes[
|
||||
automation.event_name
|
||||
].conditions.find(item => item.key === condition.attribute_key)
|
||||
.inputType;
|
||||
].conditions.find(
|
||||
item => item.key === condition.attribute_key
|
||||
).inputType;
|
||||
if (inputType === 'plain_text') {
|
||||
return {
|
||||
...condition,
|
||||
|
|
|
@ -219,7 +219,7 @@ export default {
|
|||
const action =
|
||||
mode === 'EDIT' ? 'automations/update' : 'automations/create';
|
||||
const successMessage =
|
||||
mode === 'edit'
|
||||
mode === 'EDIT'
|
||||
? this.$t('AUTOMATION.EDIT.API.SUCCESS_MESSAGE')
|
||||
: this.$t('AUTOMATION.ADD.API.SUCCESS_MESSAGE');
|
||||
|
||||
|
@ -229,7 +229,7 @@ export default {
|
|||
this.hideEditPopup();
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
mode === 'edit'
|
||||
mode === 'EDIT'
|
||||
? this.$t('AUTOMATION.EDIT.API.ERROR_MESSAGE')
|
||||
: this.$t('AUTOMATION.ADD.API.ERROR_MESSAGE');
|
||||
this.showAlert(errorMessage);
|
||||
|
|
|
@ -58,8 +58,8 @@ export const AUTOMATIONS = {
|
|||
filterOperators: OPERATOR_TYPES_1,
|
||||
},
|
||||
{
|
||||
key: 'message_contains',
|
||||
name: 'Message Contains',
|
||||
key: 'content',
|
||||
name: 'Message Content',
|
||||
attributeI18nKey: 'MESSAGE_CONTAINS',
|
||||
inputType: 'plain_text',
|
||||
filterOperators: OPERATOR_TYPES_2,
|
||||
|
@ -76,11 +76,11 @@ export const AUTOMATIONS = {
|
|||
name: 'Add a label',
|
||||
attributeI18nKey: 'ADD_LABEL',
|
||||
},
|
||||
{
|
||||
key: 'send_message',
|
||||
name: 'Send an email to team',
|
||||
attributeI18nKey: 'SEND_MESSAGE',
|
||||
},
|
||||
// {
|
||||
// key: 'send_email_to_team',
|
||||
// name: 'Send an email to team',
|
||||
// attributeI18nKey: 'SEND_MESSAGE',
|
||||
// },
|
||||
],
|
||||
},
|
||||
conversation_created: {
|
||||
|
@ -120,11 +120,11 @@ export const AUTOMATIONS = {
|
|||
name: 'Assign a team',
|
||||
attributeI18nKey: 'ASSIGN_TEAM',
|
||||
},
|
||||
{
|
||||
key: 'send_message',
|
||||
name: 'Send an email to team',
|
||||
attributeI18nKey: 'SEND_MESSAGE',
|
||||
},
|
||||
// {
|
||||
// key: 'send_email_to_team',
|
||||
// name: 'Send an email to team',
|
||||
// attributeI18nKey: 'SEND_MESSAGE',
|
||||
// },
|
||||
{
|
||||
key: 'assign_agent',
|
||||
name: 'Assign an agent',
|
||||
|
@ -183,11 +183,11 @@ export const AUTOMATIONS = {
|
|||
name: 'Assign a team',
|
||||
attributeI18nKey: 'ASSIGN_TEAM',
|
||||
},
|
||||
{
|
||||
key: 'send_message',
|
||||
name: 'Send an email to team',
|
||||
attributeI18nKey: 'SEND_MESSAGE',
|
||||
},
|
||||
// {
|
||||
// key: 'send_email_to_team',
|
||||
// name: 'Send an email to team',
|
||||
// attributeI18nKey: 'SEND_MESSAGE',
|
||||
// },
|
||||
{
|
||||
key: 'assign_agent',
|
||||
name: 'Assign an agent',
|
||||
|
@ -222,8 +222,8 @@ export const AUTOMATION_ACTION_TYPES = [
|
|||
key: 'add_label',
|
||||
label: 'Add a label',
|
||||
},
|
||||
{
|
||||
key: 'send_message',
|
||||
label: 'Send an email to team',
|
||||
},
|
||||
// {
|
||||
// key: 'send_email_to_team',
|
||||
// label: 'Send an email to team',
|
||||
// },
|
||||
];
|
||||
|
|
|
@ -48,7 +48,7 @@ export const actions = {
|
|||
commit(types.SET_AUTOMATION_UI_FLAG, { isUpdating: true });
|
||||
try {
|
||||
const response = await AutomationAPI.update(id, updateObj);
|
||||
commit(types.EDIT_AUTOMATION, response.data);
|
||||
commit(types.EDIT_AUTOMATION, response.data.payload);
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
} finally {
|
||||
|
|
|
@ -50,7 +50,9 @@ describe('#actions', () => {
|
|||
|
||||
describe('#update', () => {
|
||||
it('sends correct actions if API is success', async () => {
|
||||
axios.patch.mockResolvedValue({ data: automationsList[0] });
|
||||
axios.patch.mockResolvedValue({
|
||||
data: { payload: automationsList[0] },
|
||||
});
|
||||
await actions.update({ commit }, automationsList[0]);
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[types.default.SET_AUTOMATION_UI_FLAG, { isUpdating: true }],
|
||||
|
|
|
@ -1,64 +1,36 @@
|
|||
export default [
|
||||
{
|
||||
id: 12,
|
||||
name: 'Test 5',
|
||||
description: 'Hello',
|
||||
id: 46,
|
||||
account_id: 1,
|
||||
name: 'Test',
|
||||
description: 'This is a test',
|
||||
event_name: 'conversation_created',
|
||||
conditions: [
|
||||
{
|
||||
values: ['open'],
|
||||
attribute_key: 'status',
|
||||
query_operator: null,
|
||||
filter_operator: 'equal_to',
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
action_name: 'add_label',
|
||||
action_params: [{}],
|
||||
},
|
||||
],
|
||||
created_on: '2022-01-14T09:17:55.689Z',
|
||||
actions: [{ action_name: 'add_label', action_params: ['testlabel'] }],
|
||||
created_on: '2022-02-08T10:46:32.387Z',
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
id: 47,
|
||||
account_id: 1,
|
||||
name: 'Auto resolve conversation',
|
||||
description: 'Auto resolves conversation',
|
||||
event_name: 'conversation_updated',
|
||||
name: 'Snooze',
|
||||
description: 'Test Description',
|
||||
event_name: 'conversation_created',
|
||||
conditions: [
|
||||
{
|
||||
values: ['resolved'],
|
||||
values: ['pending'],
|
||||
attribute_key: 'status',
|
||||
query_operator: null,
|
||||
filter_operator: 'equal_to',
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
action_name: 'add_label',
|
||||
action_params: [{}],
|
||||
},
|
||||
],
|
||||
created_on: '2022-01-14T13:06:31.843Z',
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
account_id: 1,
|
||||
name: 'Fayaz',
|
||||
description: 'This is a test',
|
||||
event_name: 'conversation_created',
|
||||
conditions: {},
|
||||
actions: [
|
||||
{
|
||||
action_name: 'add_label',
|
||||
action_params: [{}],
|
||||
},
|
||||
],
|
||||
created_on: '2022-01-17T06:46:08.098Z',
|
||||
actions: [{ action_name: 'assign_team', action_params: [1] }],
|
||||
created_on: '2022-02-08T11:19:44.714Z',
|
||||
active: true,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -18,40 +18,18 @@ describe('#mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
// describe('#EDIT_AUTOMATION', () => {
|
||||
// it('update automation record', () => {
|
||||
// const state = { records: [automations[0]] };
|
||||
// mutations[types.EDIT_AUTOMATION](state, {
|
||||
// id: 12,
|
||||
// account_id: 1,
|
||||
// name: 'Test Automation',
|
||||
// description: 'This is a test',
|
||||
// event_name: 'conversation_created',
|
||||
// conditions: [
|
||||
// {
|
||||
// values: ['open'],
|
||||
// attribute_key: 'status',
|
||||
// query_operator: null,
|
||||
// filter_operator: 'equal_to',
|
||||
// },
|
||||
// ],
|
||||
// actions: [
|
||||
// {
|
||||
// action_name: 'add_label',
|
||||
// action_params: [{}],
|
||||
// },
|
||||
// ],
|
||||
// created_on: '2022-01-14T09:17:55.689Z',
|
||||
// active: true,
|
||||
// });
|
||||
// expect(state.records[0].name).toEqual('Test Automation');
|
||||
// });
|
||||
// });
|
||||
describe('#EDIT_AUTOMATION', () => {
|
||||
it('update automation record', () => {
|
||||
const state = { records: [automations[0]] };
|
||||
mutations[types.EDIT_AUTOMATION](state, automations[0]);
|
||||
expect(state.records[0].name).toEqual('Test 5');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#DELETE_AUTOMATION', () => {
|
||||
it('delete automation record', () => {
|
||||
const state = { records: [automations[0]] };
|
||||
mutations[types.DELETE_AUTOMATION](state, 12);
|
||||
mutations[types.DELETE_AUTOMATION](state, 46);
|
||||
expect(state.records).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,14 @@
|
|||
class AutomationRuleListener < BaseListener
|
||||
def conversation_updated(event_obj)
|
||||
conversation = event_obj.data[:conversation]
|
||||
return unless rule_present?('conversation_updated', conversation)
|
||||
|
||||
@rules.each do |rule|
|
||||
conditions_match = ::AutomationRules::ConditionsFilterService.new(rule, conversation).perform
|
||||
AutomationRules::ActionService.new(rule, conversation).perform if conditions_match.present?
|
||||
end
|
||||
end
|
||||
|
||||
def conversation_status_changed(event_obj)
|
||||
conversation = event_obj.data[:conversation]
|
||||
return unless rule_present?('conversation_status_changed', conversation)
|
||||
|
|
|
@ -162,6 +162,7 @@ class Conversation < ApplicationRecord
|
|||
def execute_after_update_commit_callbacks
|
||||
notify_status_change
|
||||
create_activity
|
||||
notify_conversation_updation
|
||||
end
|
||||
|
||||
def ensure_snooze_until_reset
|
||||
|
@ -181,6 +182,10 @@ class Conversation < ApplicationRecord
|
|||
dispatcher_dispatch(CONVERSATION_CREATED)
|
||||
end
|
||||
|
||||
def notify_conversation_updation
|
||||
dispatcher_dispatch(CONVERSATION_UPDATED)
|
||||
end
|
||||
|
||||
def self_assign?(assignee_id)
|
||||
assignee_id.present? && Current.user&.id == assignee_id
|
||||
end
|
||||
|
|
|
@ -21,14 +21,14 @@ class AutomationRules::ConditionsFilterService < FilterService
|
|||
records.any?
|
||||
end
|
||||
|
||||
def message_conditions(_message)
|
||||
def message_conditions(message)
|
||||
message_filters = @filters['messages']
|
||||
|
||||
@rule.conditions.each_with_index do |query_hash, current_index|
|
||||
current_filter = message_filters[query_hash['attribute_key']]
|
||||
@query_string += message_query_string(current_filter, query_hash.with_indifferent_access, current_index)
|
||||
end
|
||||
records = Message.where(conversation: @conversation).where(@query_string, @filter_values.with_indifferent_access)
|
||||
records = Message.where(id: message.id).where(@query_string, @filter_values.with_indifferent_access)
|
||||
records.any?
|
||||
end
|
||||
|
||||
|
|
|
@ -5,5 +5,5 @@ json.description automation_rule.description
|
|||
json.event_name automation_rule.event_name
|
||||
json.conditions automation_rule.conditions
|
||||
json.actions automation_rule.actions
|
||||
json.created_on automation_rule.created_at
|
||||
json.created_on automation_rule.created_at.to_i
|
||||
json.active automation_rule.active?
|
||||
|
|
|
@ -14,6 +14,7 @@ module Events::Types
|
|||
|
||||
# conversation events
|
||||
CONVERSATION_CREATED = 'conversation.created'
|
||||
CONVERSATION_UPDATED = 'conversation.updated'
|
||||
CONVERSATION_READ = 'conversation.read'
|
||||
# FIXME: deprecate the opened and resolved events in future in favor of status changed event.
|
||||
CONVERSATION_OPENED = 'conversation.opened'
|
||||
|
|
|
@ -70,6 +70,53 @@ describe AutomationRuleListener do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#conversation_updated' do
|
||||
before do
|
||||
automation_rule.update!(
|
||||
event_name: 'conversation_updated',
|
||||
name: 'Call actions conversation updated',
|
||||
description: 'Add labels, assign team after conversation updated'
|
||||
)
|
||||
end
|
||||
|
||||
let!(:event) do
|
||||
Events::Base.new('conversation_updated', Time.zone.now, { conversation: conversation })
|
||||
end
|
||||
|
||||
context 'when rule matches' do
|
||||
it 'triggers automation rule to assign team' do
|
||||
expect(conversation.team_id).not_to eq(team.id)
|
||||
|
||||
automation_rule
|
||||
listener.conversation_updated(event)
|
||||
|
||||
conversation.reload
|
||||
expect(conversation.team_id).to eq(team.id)
|
||||
end
|
||||
|
||||
it 'triggers automation rule to add label' do
|
||||
expect(conversation.labels).to eq([])
|
||||
|
||||
automation_rule
|
||||
listener.conversation_updated(event)
|
||||
|
||||
conversation.reload
|
||||
expect(conversation.labels.pluck(:name)).to eq(%w[support priority_customer])
|
||||
end
|
||||
|
||||
it 'triggers automation rule to assign best agents' do
|
||||
expect(conversation.assignee).to be_nil
|
||||
|
||||
automation_rule
|
||||
listener.conversation_updated(event)
|
||||
|
||||
conversation.reload
|
||||
|
||||
expect(conversation.assignee).to eq(user_1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#message_created' do
|
||||
before do
|
||||
automation_rule.update!(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue