feat: Custom attributes in automations and refactor (#4548)
* Custom attributes * Custom Attrs Manifest * Fix dropdown values for custom attributes * Handle edit mode for custom attributes * Ported duplicate logic to a mixin * fix Code climate issue * Fix Codeclimate complexity warning * Bug fix - Custom attributes getting duplicated * Bug fixes and Code Climate issue fix * Code Climate Issues Breakdown * Fix test spec * Add labels for Custom attributes in dropdown * Refactor * Refactor Automion mixin * Refactor Mixin * Refactor getOperator * Fix getOperatorType * File name method refactor * Refactor appendNewCondition * spec update * Refactor methods * Mixin Spec update * Automation Mixins Test Specs * Mixin Spec Rerun * Automation validations mixin spec * Automation helper test spec * Send custom_attr key * Fix spec fixtures * fix: Changes for custom attribute type and lower case search * fix: Specs * fix: Specs * fix: Ruby version change * fix: Ruby version change * Removes Lowercased values and fix label value in api payload * Fix specs * Fixed Query Spec * Removed disabled labels if no attributes are present * Code Climate Fixes * fix: custom attribute with indifferent access * fix: custom attribute with indifferent access * Fix specs * Minor label fix * REtrigger circle ci build * Update app/javascript/shared/mixins/specs/automationMixin.spec.js * Update app/javascript/shared/mixins/specs/automationMixin.spec.js * fix: Custom attribute case insensitivity search * Add missing reset action method to input * Set team_input to single select instead of multiple * fix: remove value case check for date,boolean and number data type * fix: cognitive complexity * fix: cognitive complexity * fix: Fixed activity message for automation system * fix: Fixed activity message for automation system * fix: Fixed activity message for automation system * fix: codeclimate * fix: codeclimate * fix: action cable events for label update * fix: codeclimate, conversation modela number of methods * fix: codeclimate, conversation modela number of methods * fix: codeclimate, conversation modela number of methods * fix: codeclimate, conversation modela number of methods * Fix margin bottom for attachment button * Remove margin bottom to avoid conflict from macros * Fix automation action query generator using the right key * fix: not running message created event for activity message * fix: not running message created event for activity message * codeclimate fix * codeclimate fix * codeclimate fix * Update app/javascript/dashboard/mixins/automations/methodsMixin.js Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> * Update app/javascript/shared/mixins/specs/automationHelper.spec.js Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> * Update app/javascript/dashboard/helper/automationHelper.js Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> * Update app/javascript/dashboard/mixins/automations/methodsMixin.js Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: Pranav Raj S <pranav@chatwoot.com> Co-authored-by: Tejaswini <tejaswini@chatwoot.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: Sojan Jose <sojan@pepalo.com> Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
1
.vscode/settings.json
vendored
Normal file
1
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@@ -18,14 +18,32 @@
|
|||||||
<div v-if="showActionInput" class="filter__answer--wrap">
|
<div v-if="showActionInput" class="filter__answer--wrap">
|
||||||
<div v-if="inputType">
|
<div v-if="inputType">
|
||||||
<div
|
<div
|
||||||
v-if="inputType === 'multi_select'"
|
v-if="inputType === 'search_select'"
|
||||||
class="multiselect-wrap--small"
|
class="multiselect-wrap--small"
|
||||||
>
|
>
|
||||||
<multiselect
|
<multiselect
|
||||||
v-model="action_params"
|
v-model="action_params"
|
||||||
track-by="id"
|
track-by="id"
|
||||||
label="name"
|
label="name"
|
||||||
:placeholder="'Select'"
|
:placeholder="$t('FORMS.MULTISELECT.SELECT')"
|
||||||
|
selected-label
|
||||||
|
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||||
|
deselect-label=""
|
||||||
|
:max-height="160"
|
||||||
|
:options="dropdownValues"
|
||||||
|
:allow-empty="false"
|
||||||
|
:option-height="104"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="inputType === 'multi_select'"
|
||||||
|
class="multiselect-wrap--small"
|
||||||
|
>
|
||||||
|
<multiselect
|
||||||
|
v-model="action_params"
|
||||||
|
track-by="id"
|
||||||
|
label="name"
|
||||||
|
:placeholder="$t('FORMS.MULTISELECT.SELECT')"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
selected-label
|
selected-label
|
||||||
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||||
@@ -33,6 +51,7 @@
|
|||||||
:max-height="160"
|
:max-height="160"
|
||||||
:options="dropdownValues"
|
:options="dropdownValues"
|
||||||
:allow-empty="false"
|
:allow-empty="false"
|
||||||
|
:option-height="104"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
@@ -260,6 +279,6 @@ export default {
|
|||||||
margin-bottom: var(--space-zero);
|
margin-bottom: var(--space-zero);
|
||||||
}
|
}
|
||||||
.action-message {
|
.action-message {
|
||||||
margin: var(--space-small) 0 0;
|
margin: var(--space-small) var(--space-zero) var(--space-zero);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
v-for="attribute in filterAttributes"
|
v-for="attribute in filterAttributes"
|
||||||
:key="attribute.key"
|
:key="attribute.key"
|
||||||
:value="attribute.key"
|
:value="attribute.key"
|
||||||
|
:disabled="attribute.disabled"
|
||||||
>
|
>
|
||||||
{{ attribute.name }}
|
{{ attribute.name }}
|
||||||
</option>
|
</option>
|
||||||
@@ -173,6 +174,10 @@ export default {
|
|||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
customAttributeType: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
attributeKey: {
|
attributeKey: {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const generatePayload = data => {
|
|||||||
let payload = actions.map(item => {
|
let payload = actions.map(item => {
|
||||||
if (Array.isArray(item.action_params)) {
|
if (Array.isArray(item.action_params)) {
|
||||||
item.action_params = formatArray(item.action_params);
|
item.action_params = formatArray(item.action_params);
|
||||||
} else if (typeof item.values === 'object') {
|
} else if (typeof item.action_params === 'object') {
|
||||||
item.action_params = [item.action_params.id];
|
item.action_params = [item.action_params.id];
|
||||||
} else if (!item.action_params) {
|
} else if (!item.action_params) {
|
||||||
item.action_params = [];
|
item.action_params = [];
|
||||||
|
|||||||
242
app/javascript/dashboard/helper/automationHelper.js
Normal file
242
app/javascript/dashboard/helper/automationHelper.js
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
import {
|
||||||
|
OPERATOR_TYPES_1,
|
||||||
|
OPERATOR_TYPES_3,
|
||||||
|
OPERATOR_TYPES_4,
|
||||||
|
} from 'dashboard/routes/dashboard/settings/automation/operators';
|
||||||
|
import filterQueryGenerator from './filterQueryGenerator';
|
||||||
|
import actionQueryGenerator from './actionQueryGenerator';
|
||||||
|
const MESSAGE_CONDITION_VALUES = [
|
||||||
|
{
|
||||||
|
id: 'incoming',
|
||||||
|
name: 'Incoming Message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'outgoing',
|
||||||
|
name: 'Outgoing Message',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getCustomAttributeInputType = key => {
|
||||||
|
const customAttributeMap = {
|
||||||
|
date: 'date',
|
||||||
|
text: 'plain_text',
|
||||||
|
list: 'search_select',
|
||||||
|
checkbox: 'search_select',
|
||||||
|
};
|
||||||
|
|
||||||
|
return customAttributeMap[key] || 'plain_text';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isACustomAttribute = (customAttributes, key) => {
|
||||||
|
return customAttributes.find(attr => {
|
||||||
|
return attr.attribute_key === key;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCustomAttributeListDropdownValues = (
|
||||||
|
customAttributes,
|
||||||
|
type
|
||||||
|
) => {
|
||||||
|
return customAttributes
|
||||||
|
.find(attr => attr.attribute_key === type)
|
||||||
|
.attribute_values.map(item => {
|
||||||
|
return {
|
||||||
|
id: item,
|
||||||
|
name: item,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isCustomAttributeCheckbox = (customAttributes, key) => {
|
||||||
|
return customAttributes.find(attr => {
|
||||||
|
return (
|
||||||
|
attr.attribute_key === key && attr.attribute_display_type === 'checkbox'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isCustomAttributeList = (customAttributes, type) => {
|
||||||
|
return customAttributes.find(attr => {
|
||||||
|
return (
|
||||||
|
attr.attribute_key === type && attr.attribute_display_type === 'list'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getOperatorTypes = key => {
|
||||||
|
const operatorMap = {
|
||||||
|
list: OPERATOR_TYPES_1,
|
||||||
|
text: OPERATOR_TYPES_3,
|
||||||
|
number: OPERATOR_TYPES_1,
|
||||||
|
link: OPERATOR_TYPES_1,
|
||||||
|
date: OPERATOR_TYPES_4,
|
||||||
|
checkbox: OPERATOR_TYPES_1,
|
||||||
|
};
|
||||||
|
|
||||||
|
return operatorMap[key] || OPERATOR_TYPES_1;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateCustomAttributeTypes = (customAttributes, type) => {
|
||||||
|
return customAttributes.map(attr => {
|
||||||
|
return {
|
||||||
|
key: attr.attribute_key,
|
||||||
|
name: attr.attribute_display_name,
|
||||||
|
inputType: getCustomAttributeInputType(attr.attribute_display_type),
|
||||||
|
filterOperators: getOperatorTypes(attr.attribute_display_type),
|
||||||
|
customAttributeType: type,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateConditionOptions = (options, key = 'id') => {
|
||||||
|
return options.map(i => {
|
||||||
|
return {
|
||||||
|
id: i[key],
|
||||||
|
name: i.title,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getActionOptions = ({ teams, labels, type }) => {
|
||||||
|
const actionsMap = {
|
||||||
|
assign_team: teams,
|
||||||
|
send_email_to_team: teams,
|
||||||
|
add_label: generateConditionOptions(labels, 'title'),
|
||||||
|
};
|
||||||
|
return actionsMap[type];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getConditionOptions = ({
|
||||||
|
agents,
|
||||||
|
booleanFilterOptions,
|
||||||
|
campaigns,
|
||||||
|
contacts,
|
||||||
|
countries,
|
||||||
|
customAttributes,
|
||||||
|
inboxes,
|
||||||
|
languages,
|
||||||
|
statusFilterOptions,
|
||||||
|
teams,
|
||||||
|
type,
|
||||||
|
}) => {
|
||||||
|
if (isCustomAttributeCheckbox(customAttributes, type)) {
|
||||||
|
return booleanFilterOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCustomAttributeList(customAttributes, type)) {
|
||||||
|
return getCustomAttributeListDropdownValues(customAttributes, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
const conditionFilterMaps = {
|
||||||
|
status: statusFilterOptions,
|
||||||
|
assignee_id: agents,
|
||||||
|
contact: contacts,
|
||||||
|
inbox_id: inboxes,
|
||||||
|
team_id: teams,
|
||||||
|
campaigns: generateConditionOptions(campaigns),
|
||||||
|
browser_language: languages,
|
||||||
|
country_code: countries,
|
||||||
|
message_type: MESSAGE_CONDITION_VALUES,
|
||||||
|
};
|
||||||
|
|
||||||
|
return conditionFilterMaps[type];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFileName = (action, files = []) => {
|
||||||
|
const blobId = action.action_params[0];
|
||||||
|
if (!blobId) return '';
|
||||||
|
if (action.action_name === 'send_attachment') {
|
||||||
|
const file = files.find(item => item.blob_id === blobId);
|
||||||
|
if (file) return file.filename.toString();
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDefaultConditions = eventName => {
|
||||||
|
if (eventName === 'message_created') {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
attribute_key: 'message_type',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: '',
|
||||||
|
query_operator: 'and',
|
||||||
|
custom_attribute_type: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
attribute_key: 'status',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: '',
|
||||||
|
query_operator: 'and',
|
||||||
|
custom_attribute_type: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDefaultActions = () => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
action_name: 'assign_team',
|
||||||
|
action_params: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const filterCustomAttributes = customAttributes => {
|
||||||
|
return customAttributes.map(attr => {
|
||||||
|
return {
|
||||||
|
key: attr.attribute_key,
|
||||||
|
name: attr.attribute_display_name,
|
||||||
|
type: attr.attribute_display_type,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getStandardAttributeInputType = (automationTypes, event, key) => {
|
||||||
|
return automationTypes[event].conditions.find(item => item.key === key)
|
||||||
|
.inputType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateAutomationPayload = payload => {
|
||||||
|
const automation = JSON.parse(JSON.stringify(payload));
|
||||||
|
automation.conditions[automation.conditions.length - 1].query_operator = null;
|
||||||
|
automation.conditions = filterQueryGenerator(automation.conditions).payload;
|
||||||
|
automation.actions = actionQueryGenerator(automation.actions);
|
||||||
|
return automation;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isCustomAttribute = (attrs, key) => {
|
||||||
|
return attrs.find(attr => attr.key === key);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateCustomAttributes = (
|
||||||
|
conversationAttributes = [],
|
||||||
|
contactAttribtues = [],
|
||||||
|
conversationlabel,
|
||||||
|
contactlabel
|
||||||
|
) => {
|
||||||
|
const customAttributes = [];
|
||||||
|
if (conversationAttributes.length) {
|
||||||
|
customAttributes.push(
|
||||||
|
{
|
||||||
|
key: `conversation_custom_attribute`,
|
||||||
|
name: conversationlabel,
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
...conversationAttributes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (contactAttribtues.length) {
|
||||||
|
customAttributes.push(
|
||||||
|
{
|
||||||
|
key: `contact_custom_attribute`,
|
||||||
|
name: contactlabel,
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
...contactAttribtues
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return customAttributes;
|
||||||
|
};
|
||||||
@@ -1,15 +1,3 @@
|
|||||||
const lowerCaseValues = (operator, values) => {
|
|
||||||
if (operator === 'equal_to' || operator === 'not_equal_to') {
|
|
||||||
values = values.map(val => {
|
|
||||||
if (typeof val === 'string') {
|
|
||||||
return val.toLowerCase();
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return values;
|
|
||||||
};
|
|
||||||
|
|
||||||
const generatePayload = data => {
|
const generatePayload = data => {
|
||||||
// Make a copy of data to avoid vue data reactivity issues
|
// Make a copy of data to avoid vue data reactivity issues
|
||||||
const filters = JSON.parse(JSON.stringify(data));
|
const filters = JSON.parse(JSON.stringify(data));
|
||||||
@@ -23,8 +11,6 @@ const generatePayload = data => {
|
|||||||
} else {
|
} else {
|
||||||
item.values = [item.values];
|
item.values = [item.values];
|
||||||
}
|
}
|
||||||
// Convert all values to lowerCase if operator_type is 'equal_to' or 'not_equal_to'
|
|
||||||
item.values = lowerCaseValues(item.filter_operator, item.values);
|
|
||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
// For every query added, the query_operator is set default to and so the
|
// For every query added, the query_operator is set default to and so the
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const testData = [
|
|||||||
attribute_key: 'status',
|
attribute_key: 'status',
|
||||||
filter_operator: 'equal_to',
|
filter_operator: 'equal_to',
|
||||||
values: [
|
values: [
|
||||||
{ id: 'PENDING', name: 'Pending' },
|
{ id: 'pending', name: 'Pending' },
|
||||||
{ id: 'resolved', name: 'Resolved' },
|
{ id: 'resolved', name: 'Resolved' },
|
||||||
],
|
],
|
||||||
query_operator: 'and',
|
query_operator: 'and',
|
||||||
@@ -18,7 +18,7 @@ const testData = [
|
|||||||
account_id: 1,
|
account_id: 1,
|
||||||
auto_offline: true,
|
auto_offline: true,
|
||||||
confirmed: true,
|
confirmed: true,
|
||||||
email: 'fayazara@gmail.com',
|
email: 'fayaz@test.com',
|
||||||
available_name: 'Fayaz',
|
available_name: 'Fayaz',
|
||||||
name: 'Fayaz',
|
name: 'Fayaz',
|
||||||
role: 'agent',
|
role: 'agent',
|
||||||
@@ -52,7 +52,7 @@ const finalResult = {
|
|||||||
{
|
{
|
||||||
attribute_key: 'id',
|
attribute_key: 'id',
|
||||||
filter_operator: 'equal_to',
|
filter_operator: 'equal_to',
|
||||||
values: ['this is a test'],
|
values: ['This is a test'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -86,7 +86,9 @@
|
|||||||
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
|
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
|
||||||
},
|
},
|
||||||
"CONDITION": {
|
"CONDITION": {
|
||||||
"DELETE_MESSAGE": "You need to have atleast one condition to save"
|
"DELETE_MESSAGE": "You need to have atleast one condition to save",
|
||||||
|
"CONTACT_CUSTOM_ATTR_LABEL": "Contact Custom Attributes",
|
||||||
|
"CONVERSATION_CUSTOM_ATTR_LABEL": "Conversation Custom Attributes"
|
||||||
},
|
},
|
||||||
"ACTION": {
|
"ACTION": {
|
||||||
"DELETE_MESSAGE": "You need to have atleast one action to save",
|
"DELETE_MESSAGE": "You need to have atleast one action to save",
|
||||||
|
|||||||
@@ -54,7 +54,8 @@
|
|||||||
"MULTISELECT": {
|
"MULTISELECT": {
|
||||||
"ENTER_TO_SELECT": "Press enter to select",
|
"ENTER_TO_SELECT": "Press enter to select",
|
||||||
"ENTER_TO_REMOVE": "Press enter to remove",
|
"ENTER_TO_REMOVE": "Press enter to remove",
|
||||||
"SELECT_ONE": "Select one"
|
"SELECT_ONE": "Select one",
|
||||||
|
"SELECT": "Select"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"NOTIFICATIONS_PAGE": {
|
"NOTIFICATIONS_PAGE": {
|
||||||
|
|||||||
289
app/javascript/dashboard/mixins/automations/methodsMixin.js
Normal file
289
app/javascript/dashboard/mixins/automations/methodsMixin.js
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
import languages from 'dashboard/components/widgets/conversation/advancedFilterItems/languages';
|
||||||
|
import countries from 'shared/constants/countries';
|
||||||
|
import {
|
||||||
|
generateCustomAttributeTypes,
|
||||||
|
getActionOptions,
|
||||||
|
getConditionOptions,
|
||||||
|
getCustomAttributeInputType,
|
||||||
|
getOperatorTypes,
|
||||||
|
isACustomAttribute,
|
||||||
|
getFileName,
|
||||||
|
getDefaultConditions,
|
||||||
|
getDefaultActions,
|
||||||
|
filterCustomAttributes,
|
||||||
|
generateAutomationPayload,
|
||||||
|
getStandardAttributeInputType,
|
||||||
|
isCustomAttribute,
|
||||||
|
generateCustomAttributes,
|
||||||
|
} from 'dashboard/helper/automationHelper';
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
agents: 'agents/getAgents',
|
||||||
|
campaigns: 'campaigns/getAllCampaigns',
|
||||||
|
contacts: 'contacts/getContacts',
|
||||||
|
inboxes: 'inboxes/getInboxes',
|
||||||
|
labels: 'labels/getLabels',
|
||||||
|
teams: 'teams/getTeams',
|
||||||
|
}),
|
||||||
|
booleanFilterOptions() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: true,
|
||||||
|
name: this.$t('FILTER.ATTRIBUTE_LABELS.TRUE'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: false,
|
||||||
|
name: this.$t('FILTER.ATTRIBUTE_LABELS.FALSE'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
statusFilterOptions() {
|
||||||
|
const statusFilters = this.$t('CHAT_LIST.CHAT_STATUS_FILTER_ITEMS');
|
||||||
|
return [
|
||||||
|
...Object.keys(statusFilters).map(status => {
|
||||||
|
return {
|
||||||
|
id: status,
|
||||||
|
name: statusFilters[status].TEXT,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
id: 'all',
|
||||||
|
name: this.$t('CHAT_LIST.FILTER_ALL'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getFileName,
|
||||||
|
onEventChange() {
|
||||||
|
this.automation.conditions = getDefaultConditions(
|
||||||
|
this.automation.event_name
|
||||||
|
);
|
||||||
|
this.automation.actions = getDefaultActions();
|
||||||
|
},
|
||||||
|
getAttributes(key) {
|
||||||
|
return this.automationTypes[key].conditions;
|
||||||
|
},
|
||||||
|
getInputType(key) {
|
||||||
|
const customAttribute = isACustomAttribute(this.allCustomAttributes, key);
|
||||||
|
if (customAttribute) {
|
||||||
|
return getCustomAttributeInputType(
|
||||||
|
customAttribute.attribute_display_type
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const type = this.getAutomationType(key);
|
||||||
|
return type.inputType;
|
||||||
|
},
|
||||||
|
getOperators(key) {
|
||||||
|
if (this.mode === 'edit') {
|
||||||
|
const customAttribute = isACustomAttribute(
|
||||||
|
this.allCustomAttributes,
|
||||||
|
key
|
||||||
|
);
|
||||||
|
if (customAttribute) {
|
||||||
|
return getOperatorTypes(customAttribute.attribute_display_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const type = this.getAutomationType(key);
|
||||||
|
return type.filterOperators;
|
||||||
|
},
|
||||||
|
getAutomationType(key) {
|
||||||
|
return this.automationTypes[this.automation.event_name].conditions.find(
|
||||||
|
condition => condition.key === key
|
||||||
|
);
|
||||||
|
},
|
||||||
|
getCustomAttributeType(key) {
|
||||||
|
const type = this.automationTypes[
|
||||||
|
this.automation.event_name
|
||||||
|
].conditions.find(i => i.key === key).customAttributeType;
|
||||||
|
return type;
|
||||||
|
},
|
||||||
|
getConditionDropdownValues(type) {
|
||||||
|
const {
|
||||||
|
agents,
|
||||||
|
allCustomAttributes: customAttributes,
|
||||||
|
booleanFilterOptions,
|
||||||
|
campaigns,
|
||||||
|
contacts,
|
||||||
|
inboxes,
|
||||||
|
statusFilterOptions,
|
||||||
|
teams,
|
||||||
|
} = this;
|
||||||
|
return getConditionOptions({
|
||||||
|
agents,
|
||||||
|
booleanFilterOptions,
|
||||||
|
campaigns,
|
||||||
|
contacts,
|
||||||
|
customAttributes,
|
||||||
|
inboxes,
|
||||||
|
statusFilterOptions,
|
||||||
|
teams,
|
||||||
|
languages,
|
||||||
|
countries,
|
||||||
|
type,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
appendNewCondition() {
|
||||||
|
this.automation.conditions.push(
|
||||||
|
...getDefaultConditions(this.automation.event_name)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
appendNewAction() {
|
||||||
|
this.automation.actions.push(...getDefaultActions());
|
||||||
|
},
|
||||||
|
removeFilter(index) {
|
||||||
|
if (this.automation.conditions.length <= 1) {
|
||||||
|
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('AUTOMATION.ACTION.DELETE_MESSAGE'));
|
||||||
|
} else {
|
||||||
|
this.automation.actions.splice(index, 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitAutomation() {
|
||||||
|
this.$v.$touch();
|
||||||
|
if (this.$v.$invalid) return;
|
||||||
|
const automation = generateAutomationPayload(this.automation);
|
||||||
|
this.$emit('saveAutomation', automation, this.mode);
|
||||||
|
},
|
||||||
|
resetFilter(index, currentCondition) {
|
||||||
|
this.automation.conditions[index].filter_operator = this.automationTypes[
|
||||||
|
this.automation.event_name
|
||||||
|
].conditions.find(
|
||||||
|
condition => condition.key === currentCondition.attribute_key
|
||||||
|
).filterOperators[0].value;
|
||||||
|
this.automation.conditions[index].values = '';
|
||||||
|
},
|
||||||
|
showUserInput(type) {
|
||||||
|
return !(type === 'is_present' || type === 'is_not_present');
|
||||||
|
},
|
||||||
|
showActionInput(action) {
|
||||||
|
if (action === 'send_email_to_team' || action === 'send_message')
|
||||||
|
return false;
|
||||||
|
const type = this.automationActionTypes.find(i => i.key === action)
|
||||||
|
.inputType;
|
||||||
|
return !!type;
|
||||||
|
},
|
||||||
|
resetAction(index) {
|
||||||
|
this.automation.actions[index].action_params = [];
|
||||||
|
},
|
||||||
|
manifestConditions(automation) {
|
||||||
|
const customAttributes = filterCustomAttributes(this.allCustomAttributes);
|
||||||
|
const conditions = automation.conditions.map(condition => {
|
||||||
|
const customAttr = isCustomAttribute(
|
||||||
|
customAttributes,
|
||||||
|
condition.attribute_key
|
||||||
|
);
|
||||||
|
let inputType = 'plain_text';
|
||||||
|
if (customAttr) {
|
||||||
|
inputType = getCustomAttributeInputType(customAttr.type);
|
||||||
|
} else {
|
||||||
|
inputType = getStandardAttributeInputType(
|
||||||
|
this.automationTypes,
|
||||||
|
automation.event_name,
|
||||||
|
condition.attribute_key
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (inputType === 'plain_text' || inputType === 'date') {
|
||||||
|
return {
|
||||||
|
...condition,
|
||||||
|
values: condition.values[0],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...condition,
|
||||||
|
query_operator: condition.query_operator || 'and',
|
||||||
|
values: [
|
||||||
|
...this.getConditionDropdownValues(condition.attribute_key),
|
||||||
|
].filter(item => [...condition.values].includes(item.id)),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return conditions;
|
||||||
|
},
|
||||||
|
generateActionsArray(action) {
|
||||||
|
const params = action.action_params;
|
||||||
|
let actionParams = [];
|
||||||
|
const inputType = this.automationActionTypes.find(
|
||||||
|
item => item.key === action.action_name
|
||||||
|
).inputType;
|
||||||
|
if (inputType === 'multi_select' || inputType === 'search_select') {
|
||||||
|
actionParams = [
|
||||||
|
...this.getActionDropdownValues(action.action_name),
|
||||||
|
].filter(item => [...params].includes(item.id));
|
||||||
|
} else if (inputType === 'team_message') {
|
||||||
|
actionParams = {
|
||||||
|
team_ids: [
|
||||||
|
...this.getActionDropdownValues(action.action_name),
|
||||||
|
].filter(item => [...params[0].team_ids].includes(item.id)),
|
||||||
|
message: params[0].message,
|
||||||
|
};
|
||||||
|
} else actionParams = [...params];
|
||||||
|
return actionParams;
|
||||||
|
},
|
||||||
|
manifestActions(automation) {
|
||||||
|
let actionParams = [];
|
||||||
|
const actions = automation.actions.map(action => {
|
||||||
|
if (action.action_params.length) {
|
||||||
|
actionParams = this.generateActionsArray(action);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...action,
|
||||||
|
action_params: actionParams,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return actions;
|
||||||
|
},
|
||||||
|
formatAutomation(automation) {
|
||||||
|
this.automation = {
|
||||||
|
...automation,
|
||||||
|
conditions: this.manifestConditions(automation),
|
||||||
|
actions: this.manifestActions(automation),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getActionDropdownValues(type) {
|
||||||
|
const { labels, teams } = this;
|
||||||
|
return getActionOptions({ labels, teams, type });
|
||||||
|
},
|
||||||
|
manifestCustomAttributes() {
|
||||||
|
const conversationCustomAttributesRaw = this.$store.getters[
|
||||||
|
'attributes/getAttributesByModel'
|
||||||
|
]('conversation_attribute');
|
||||||
|
|
||||||
|
const contactCustomAttributesRaw = this.$store.getters[
|
||||||
|
'attributes/getAttributesByModel'
|
||||||
|
]('contact_attribute');
|
||||||
|
const conversationCustomAttributeTypes = generateCustomAttributeTypes(
|
||||||
|
conversationCustomAttributesRaw,
|
||||||
|
'conversation_attribute'
|
||||||
|
);
|
||||||
|
const contactCustomAttributeTypes = generateCustomAttributeTypes(
|
||||||
|
contactCustomAttributesRaw,
|
||||||
|
'contact_attribute'
|
||||||
|
);
|
||||||
|
let manifestedCustomAttributes = generateCustomAttributes(
|
||||||
|
conversationCustomAttributeTypes,
|
||||||
|
contactCustomAttributeTypes,
|
||||||
|
this.$t('AUTOMATION.CONDITION.CONVERSATION_CUSTOM_ATTR_LABEL'),
|
||||||
|
this.$t('AUTOMATION.CONDITION.CONTACT_CUSTOM_ATTR_LABEL')
|
||||||
|
);
|
||||||
|
this.automationTypes.message_created.conditions.push(
|
||||||
|
...manifestedCustomAttributes
|
||||||
|
);
|
||||||
|
this.automationTypes.conversation_created.conditions.push(
|
||||||
|
...manifestedCustomAttributes
|
||||||
|
);
|
||||||
|
this.automationTypes.conversation_updated.conditions.push(
|
||||||
|
...manifestedCustomAttributes
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { required, requiredIf } from 'vuelidate/lib/validators';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
validations: {
|
||||||
|
automation: {
|
||||||
|
name: {
|
||||||
|
required,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
required,
|
||||||
|
},
|
||||||
|
event_name: {
|
||||||
|
required,
|
||||||
|
},
|
||||||
|
conditions: {
|
||||||
|
required,
|
||||||
|
$each: {
|
||||||
|
values: {
|
||||||
|
required: requiredIf(prop => {
|
||||||
|
return !(
|
||||||
|
prop.filter_operator === 'is_present' ||
|
||||||
|
prop.filter_operator === 'is_not_present'
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
required,
|
||||||
|
$each: {
|
||||||
|
action_params: {
|
||||||
|
required: requiredIf(prop => {
|
||||||
|
return !(
|
||||||
|
prop.action_name === 'mute_conversation' ||
|
||||||
|
prop.action_name === 'snooze_conversation' ||
|
||||||
|
prop.action_name === 'resolve_conversation'
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -68,6 +68,9 @@
|
|||||||
)
|
)
|
||||||
"
|
"
|
||||||
:show-query-operator="i !== automation.conditions.length - 1"
|
:show-query-operator="i !== automation.conditions.length - 1"
|
||||||
|
:custom-attribute-type="
|
||||||
|
getCustomAttributeType(automation.conditions[i].attribute_key)
|
||||||
|
"
|
||||||
:v="$v.automation.conditions.$each[i]"
|
:v="$v.automation.conditions.$each[i]"
|
||||||
@resetFilter="resetFilter(i, automation.conditions[i])"
|
@resetFilter="resetFilter(i, automation.conditions[i])"
|
||||||
@removeFilter="removeFilter(i)"
|
@removeFilter="removeFilter(i)"
|
||||||
@@ -138,75 +141,32 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import alertMixin from 'shared/mixins/alertMixin';
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
import { required, requiredIf } from 'vuelidate/lib/validators';
|
import automationMethodsMixin from 'dashboard/mixins/automations/methodsMixin';
|
||||||
|
import automationValidationsMixin from 'dashboard/mixins/automations/validationsMixin';
|
||||||
import filterInputBox from 'dashboard/components/widgets/FilterInput/Index.vue';
|
import filterInputBox from 'dashboard/components/widgets/FilterInput/Index.vue';
|
||||||
import automationActionInput from 'dashboard/components/widgets/AutomationActionInput.vue';
|
import automationActionInput from 'dashboard/components/widgets/AutomationActionInput.vue';
|
||||||
import languages from 'dashboard/components/widgets/conversation/advancedFilterItems/languages';
|
|
||||||
import countries from '/app/javascript/shared/constants/countries.js';
|
|
||||||
import {
|
import {
|
||||||
AUTOMATION_RULE_EVENTS,
|
AUTOMATION_RULE_EVENTS,
|
||||||
AUTOMATION_ACTION_TYPES,
|
AUTOMATION_ACTION_TYPES,
|
||||||
AUTOMATIONS,
|
AUTOMATIONS,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import filterQueryGenerator from 'dashboard/helper/filterQueryGenerator.js';
|
|
||||||
import actionQueryGenerator from 'dashboard/helper/actionQueryGenerator.js';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
filterInputBox,
|
filterInputBox,
|
||||||
automationActionInput,
|
automationActionInput,
|
||||||
},
|
},
|
||||||
mixins: [alertMixin],
|
mixins: [alertMixin, automationMethodsMixin, automationValidationsMixin],
|
||||||
props: {
|
props: {
|
||||||
onClose: {
|
onClose: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: () => {},
|
default: () => {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
validations: {
|
|
||||||
automation: {
|
|
||||||
name: {
|
|
||||||
required,
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
required,
|
|
||||||
},
|
|
||||||
event_name: {
|
|
||||||
required,
|
|
||||||
},
|
|
||||||
conditions: {
|
|
||||||
required,
|
|
||||||
$each: {
|
|
||||||
values: {
|
|
||||||
required: requiredIf(prop => {
|
|
||||||
return !(
|
|
||||||
prop.filter_operator === 'is_present' ||
|
|
||||||
prop.filter_operator === 'is_not_present'
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
required,
|
|
||||||
$each: {
|
|
||||||
action_params: {
|
|
||||||
required: requiredIf(prop => {
|
|
||||||
if (prop.action_name === 'send_email_to_team') return true;
|
|
||||||
return !(
|
|
||||||
prop.action_name === 'mute_conversation' ||
|
|
||||||
prop.action_name === 'snooze_conversation' ||
|
|
||||||
prop.action_name === 'resolve_conversation'
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
automationTypes: AUTOMATIONS,
|
automationTypes: JSON.parse(JSON.stringify(AUTOMATIONS)),
|
||||||
automationRuleEvent: AUTOMATION_RULE_EVENTS[0].key,
|
automationRuleEvent: AUTOMATION_RULE_EVENTS[0].key,
|
||||||
automationRuleEvents: AUTOMATION_RULE_EVENTS,
|
automationRuleEvents: AUTOMATION_RULE_EVENTS,
|
||||||
automationActionTypes: AUTOMATION_ACTION_TYPES,
|
automationActionTypes: AUTOMATION_ACTION_TYPES,
|
||||||
@@ -222,6 +182,7 @@ export default {
|
|||||||
filter_operator: 'equal_to',
|
filter_operator: 'equal_to',
|
||||||
values: '',
|
values: '',
|
||||||
query_operator: 'and',
|
query_operator: 'and',
|
||||||
|
custom_attribute_type: '',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
actions: [
|
actions: [
|
||||||
@@ -232,24 +193,11 @@ export default {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
showDeleteConfirmationModal: false,
|
showDeleteConfirmationModal: false,
|
||||||
|
allCustomAttributes: [],
|
||||||
|
mode: 'create',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
conditions() {
|
|
||||||
return this.automationTypes[this.automation.event_name].conditions;
|
|
||||||
},
|
|
||||||
actions() {
|
|
||||||
return this.automationTypes[this.automation.event_name].actions;
|
|
||||||
},
|
|
||||||
filterAttributes() {
|
|
||||||
return this.filterTypes.map(type => {
|
|
||||||
return {
|
|
||||||
key: type.attributeKey,
|
|
||||||
name: type.attributeName,
|
|
||||||
attributeI18nKey: type.attributeI18nKey,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
hasAutomationMutated() {
|
hasAutomationMutated() {
|
||||||
if (
|
if (
|
||||||
this.automation.conditions[0].values ||
|
this.automation.conditions[0].values ||
|
||||||
@@ -259,200 +207,15 @@ export default {
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
mounted() {
|
||||||
onEventChange() {
|
this.$store.dispatch('inboxes/get');
|
||||||
if (this.automation.event_name === 'message_created') {
|
this.$store.dispatch('agents/get');
|
||||||
this.automation.conditions = [
|
this.$store.dispatch('contacts/get');
|
||||||
{
|
this.$store.dispatch('teams/get');
|
||||||
attribute_key: 'message_type',
|
this.$store.dispatch('labels/get');
|
||||||
filter_operator: 'equal_to',
|
this.$store.dispatch('campaigns/get');
|
||||||
values: '',
|
this.allCustomAttributes = this.$store.getters['attributes/getAttributes'];
|
||||||
query_operator: 'and',
|
this.manifestCustomAttributes();
|
||||||
},
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
this.automation.conditions = [
|
|
||||||
{
|
|
||||||
attribute_key: 'status',
|
|
||||||
filter_operator: 'equal_to',
|
|
||||||
values: '',
|
|
||||||
query_operator: 'and',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
this.automation.actions = [
|
|
||||||
{
|
|
||||||
action_name: 'assign_team',
|
|
||||||
action_params: [],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
getAttributes(key) {
|
|
||||||
return this.automationTypes[key].conditions;
|
|
||||||
},
|
|
||||||
getInputType(key) {
|
|
||||||
const type = this.automationTypes[
|
|
||||||
this.automation.event_name
|
|
||||||
].conditions.find(condition => condition.key === key);
|
|
||||||
return type.inputType;
|
|
||||||
},
|
|
||||||
getOperators(key) {
|
|
||||||
const type = this.automationTypes[
|
|
||||||
this.automation.event_name
|
|
||||||
].conditions.find(condition => condition.key === key);
|
|
||||||
return type.filterOperators;
|
|
||||||
},
|
|
||||||
getConditionDropdownValues(type) {
|
|
||||||
const statusFilters = this.$t('CHAT_LIST.CHAT_STATUS_FILTER_ITEMS');
|
|
||||||
switch (type) {
|
|
||||||
case 'status':
|
|
||||||
return [
|
|
||||||
...Object.keys(statusFilters).map(status => {
|
|
||||||
return {
|
|
||||||
id: status,
|
|
||||||
name: statusFilters[status].TEXT,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
id: 'all',
|
|
||||||
name: this.$t('CHAT_LIST.FILTER_ALL'),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
case 'assignee_id':
|
|
||||||
return this.$store.getters['agents/getAgents'];
|
|
||||||
case 'contact':
|
|
||||||
return this.$store.getters['contacts/getContacts'];
|
|
||||||
case 'inbox_id':
|
|
||||||
return this.$store.getters['inboxes/getInboxes'];
|
|
||||||
case 'team_id':
|
|
||||||
return this.$store.getters['teams/getTeams'];
|
|
||||||
case 'campaign_id':
|
|
||||||
return this.$store.getters['campaigns/getAllCampaigns'].map(i => {
|
|
||||||
return {
|
|
||||||
id: i.id,
|
|
||||||
name: i.title,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
case 'labels':
|
|
||||||
return this.$store.getters['labels/getLabels'].map(i => {
|
|
||||||
return {
|
|
||||||
id: i.id,
|
|
||||||
name: i.title,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
case 'browser_language':
|
|
||||||
return languages;
|
|
||||||
case 'country_code':
|
|
||||||
return countries;
|
|
||||||
case 'message_type':
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
id: 'incoming',
|
|
||||||
name: 'Incoming Message',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'outgoing',
|
|
||||||
name: 'Outgoing Message',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
default:
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getActionDropdownValues(type) {
|
|
||||||
switch (type) {
|
|
||||||
case 'assign_team':
|
|
||||||
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,
|
|
||||||
name: i.title,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
default:
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
appendNewCondition() {
|
|
||||||
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({
|
|
||||||
action_name: 'assign_team',
|
|
||||||
action_params: [],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
removeFilter(index) {
|
|
||||||
if (this.automation.conditions.length <= 1) {
|
|
||||||
this.showAlert(this.$t('FILTER.FILTER_DELETE_ERROR'));
|
|
||||||
} else {
|
|
||||||
this.automation.conditions.splice(index, 1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
removeAction(index) {
|
|
||||||
if (this.automation.actions.length <= 1) {
|
|
||||||
this.showAlert(this.$t('FILTER.FILTER_DELETE_ERROR'));
|
|
||||||
} else {
|
|
||||||
this.automation.actions.splice(index, 1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
submitAutomation() {
|
|
||||||
this.$v.$touch();
|
|
||||||
if (this.$v.$invalid) return;
|
|
||||||
const automation = JSON.parse(JSON.stringify(this.automation));
|
|
||||||
automation.conditions[
|
|
||||||
automation.conditions.length - 1
|
|
||||||
].query_operator = null;
|
|
||||||
automation.conditions = filterQueryGenerator(
|
|
||||||
automation.conditions
|
|
||||||
).payload;
|
|
||||||
automation.actions = actionQueryGenerator(automation.actions);
|
|
||||||
this.$emit('saveAutomation', automation);
|
|
||||||
},
|
|
||||||
resetFilter(index, currentCondition) {
|
|
||||||
this.automation.conditions[index].filter_operator = this.automationTypes[
|
|
||||||
this.automation.event_name
|
|
||||||
].conditions.find(
|
|
||||||
condition => condition.key === currentCondition.attribute_key
|
|
||||||
).filterOperators[0].value;
|
|
||||||
this.automation.conditions[index].values = '';
|
|
||||||
},
|
|
||||||
resetAction(index) {
|
|
||||||
this.automation.actions[index].action_params = [];
|
|
||||||
},
|
|
||||||
showUserInput(operatorType) {
|
|
||||||
if (operatorType === 'is_present' || operatorType === 'is_not_present')
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
showActionInput(actionName) {
|
|
||||||
if (actionName === 'send_email_to_team' || actionName === 'send_message')
|
|
||||||
return false;
|
|
||||||
const type = AUTOMATION_ACTION_TYPES.find(
|
|
||||||
action => action.key === actionName
|
|
||||||
).inputType;
|
|
||||||
if (type === null) return false;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="column">
|
<div class="column">
|
||||||
<woot-modal-header :header-title="$t('AUTOMATION.EDIT.TITLE')" />
|
<woot-modal-header :header-title="$t('AUTOMATION.EDIT.TITLE')" />
|
||||||
<div class="row modal-content">
|
<div class="row modal-content">
|
||||||
<div class="medium-12 columns">
|
<div v-if="automation" class="medium-12 columns">
|
||||||
<woot-input
|
<woot-input
|
||||||
v-model="automation.name"
|
v-model="automation.name"
|
||||||
:label="$t('AUTOMATION.ADD.FORM.NAME.LABEL')"
|
:label="$t('AUTOMATION.ADD.FORM.NAME.LABEL')"
|
||||||
@@ -64,6 +64,9 @@
|
|||||||
automation.conditions[i].attribute_key
|
automation.conditions[i].attribute_key
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
:custom-attribute-type="
|
||||||
|
getCustomAttributeType(automation.conditions[i].attribute_key)
|
||||||
|
"
|
||||||
:show-query-operator="i !== automation.conditions.length - 1"
|
:show-query-operator="i !== automation.conditions.length - 1"
|
||||||
:v="$v.automation.conditions.$each[i]"
|
:v="$v.automation.conditions.$each[i]"
|
||||||
@resetFilter="resetFilter(i, automation.conditions[i])"
|
@resetFilter="resetFilter(i, automation.conditions[i])"
|
||||||
@@ -94,19 +97,11 @@
|
|||||||
:key="i"
|
:key="i"
|
||||||
v-model="automation.actions[i]"
|
v-model="automation.actions[i]"
|
||||||
:action-types="automationActionTypes"
|
:action-types="automationActionTypes"
|
||||||
:dropdown-values="
|
:dropdown-values="getActionDropdownValues(action.action_name)"
|
||||||
getActionDropdownValues(automation.actions[i].action_name)
|
:show-action-input="showActionInput(action.action_name)"
|
||||||
"
|
|
||||||
:show-action-input="
|
|
||||||
showActionInput(automation.actions[i].action_name)
|
|
||||||
"
|
|
||||||
:v="$v.automation.actions.$each[i]"
|
:v="$v.automation.actions.$each[i]"
|
||||||
:initial-file-name="
|
:initial-file-name="getFileName(action, automation.files)"
|
||||||
getFileName(
|
@resetAction="resetAction(i)"
|
||||||
automation.actions[i].action_params[0],
|
|
||||||
automation.actions[i].action_name
|
|
||||||
)
|
|
||||||
"
|
|
||||||
@removeAction="removeAction(i)"
|
@removeAction="removeAction(i)"
|
||||||
/>
|
/>
|
||||||
<div class="filter-actions">
|
<div class="filter-actions">
|
||||||
@@ -144,25 +139,23 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import alertMixin from 'shared/mixins/alertMixin';
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
import { required, requiredIf } from 'vuelidate/lib/validators';
|
import automationMethodsMixin from 'dashboard/mixins/automations/methodsMixin';
|
||||||
|
import automationValidationsMixin from 'dashboard/mixins/automations/validationsMixin';
|
||||||
import filterInputBox from 'dashboard/components/widgets/FilterInput/Index.vue';
|
import filterInputBox from 'dashboard/components/widgets/FilterInput/Index.vue';
|
||||||
import automationActionInput from 'dashboard/components/widgets/AutomationActionInput.vue';
|
import automationActionInput from 'dashboard/components/widgets/AutomationActionInput.vue';
|
||||||
import languages from 'dashboard/components/widgets/conversation/advancedFilterItems/languages';
|
|
||||||
import countries from 'shared/constants/countries.js';
|
|
||||||
import {
|
import {
|
||||||
AUTOMATION_RULE_EVENTS,
|
AUTOMATION_RULE_EVENTS,
|
||||||
AUTOMATION_ACTION_TYPES,
|
AUTOMATION_ACTION_TYPES,
|
||||||
AUTOMATIONS,
|
AUTOMATIONS,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import filterQueryGenerator from 'dashboard/helper/filterQueryGenerator.js';
|
|
||||||
import actionQueryGenerator from 'dashboard/helper/actionQueryGenerator.js';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
filterInputBox,
|
filterInputBox,
|
||||||
automationActionInput,
|
automationActionInput,
|
||||||
},
|
},
|
||||||
mixins: [alertMixin],
|
mixins: [alertMixin, automationMethodsMixin, automationValidationsMixin],
|
||||||
props: {
|
props: {
|
||||||
onClose: {
|
onClose: {
|
||||||
type: Function,
|
type: Function,
|
||||||
@@ -173,356 +166,35 @@ export default {
|
|||||||
default: () => {},
|
default: () => {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
validations: {
|
|
||||||
automation: {
|
|
||||||
name: {
|
|
||||||
required,
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
required,
|
|
||||||
},
|
|
||||||
event_name: {
|
|
||||||
required,
|
|
||||||
},
|
|
||||||
conditions: {
|
|
||||||
required,
|
|
||||||
$each: {
|
|
||||||
values: {
|
|
||||||
required: requiredIf(prop => {
|
|
||||||
return !(
|
|
||||||
prop.filter_operator === 'is_present' ||
|
|
||||||
prop.filter_operator === 'is_not_present'
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
required,
|
|
||||||
$each: {
|
|
||||||
action_params: {
|
|
||||||
required: requiredIf(prop => {
|
|
||||||
return !(
|
|
||||||
prop.action_name === 'mute_conversation' ||
|
|
||||||
prop.action_name === 'snooze_conversation' ||
|
|
||||||
prop.action_name === 'resolve_conversation'
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
automationTypes: AUTOMATIONS,
|
automationTypes: JSON.parse(JSON.stringify(AUTOMATIONS)),
|
||||||
automationRuleEvent: AUTOMATION_RULE_EVENTS[0].key,
|
automationRuleEvent: AUTOMATION_RULE_EVENTS[0].key,
|
||||||
automationRuleEvents: AUTOMATION_RULE_EVENTS,
|
automationRuleEvents: AUTOMATION_RULE_EVENTS,
|
||||||
automationActionTypes: AUTOMATION_ACTION_TYPES,
|
automationActionTypes: AUTOMATION_ACTION_TYPES,
|
||||||
automationMutated: false,
|
automationMutated: false,
|
||||||
show: true,
|
show: true,
|
||||||
automation: {
|
automation: null,
|
||||||
name: null,
|
|
||||||
description: null,
|
|
||||||
event_name: 'conversation_created',
|
|
||||||
conditions: [
|
|
||||||
{
|
|
||||||
attribute_key: 'status',
|
|
||||||
filter_operator: 'equal_to',
|
|
||||||
values: '',
|
|
||||||
query_operator: 'and',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
action_name: 'assign_team',
|
|
||||||
action_params: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
showDeleteConfirmationModal: false,
|
showDeleteConfirmationModal: false,
|
||||||
|
allCustomAttributes: [],
|
||||||
|
mode: 'edit',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
conditions() {
|
hasAutomationMutated() {
|
||||||
return this.automationTypes[this.automation.event_name].conditions;
|
if (
|
||||||
},
|
this.automation.conditions[0].values ||
|
||||||
actions() {
|
this.automation.actions[0].action_params.length
|
||||||
return this.automationTypes[this.automation.event_name].actions;
|
)
|
||||||
},
|
return true;
|
||||||
filterAttributes() {
|
return false;
|
||||||
return this.filterTypes.map(type => {
|
|
||||||
return {
|
|
||||||
key: type.attributeKey,
|
|
||||||
name: type.attributeName,
|
|
||||||
attributeI18nKey: type.attributeI18nKey,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.manifestCustomAttributes();
|
||||||
|
this.allCustomAttributes = this.$store.getters['attributes/getAttributes'];
|
||||||
this.formatAutomation(this.selectedResponse);
|
this.formatAutomation(this.selectedResponse);
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
onEventChange() {
|
|
||||||
if (this.automation.event_name === 'message_created') {
|
|
||||||
this.automation.conditions = [
|
|
||||||
{
|
|
||||||
attribute_key: 'message_type',
|
|
||||||
filter_operator: 'equal_to',
|
|
||||||
values: '',
|
|
||||||
query_operator: 'and',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
this.automation.conditions = [
|
|
||||||
{
|
|
||||||
attribute_key: 'status',
|
|
||||||
filter_operator: 'equal_to',
|
|
||||||
values: '',
|
|
||||||
query_operator: 'and',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
this.automation.actions = [
|
|
||||||
{
|
|
||||||
action_name: 'assign_team',
|
|
||||||
action_params: [],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
getAttributes(key) {
|
|
||||||
return this.automationTypes[key].conditions;
|
|
||||||
},
|
|
||||||
getInputType(key) {
|
|
||||||
const type = this.automationTypes[
|
|
||||||
this.automation.event_name
|
|
||||||
].conditions.find(condition => condition.key === key);
|
|
||||||
return type.inputType;
|
|
||||||
},
|
|
||||||
getOperators(key) {
|
|
||||||
const type = this.automationTypes[
|
|
||||||
this.automation.event_name
|
|
||||||
].conditions.find(condition => condition.key === key);
|
|
||||||
return type.filterOperators;
|
|
||||||
},
|
|
||||||
getConditionDropdownValues(type) {
|
|
||||||
const statusFilters = this.$t('CHAT_LIST.CHAT_STATUS_FILTER_ITEMS');
|
|
||||||
switch (type) {
|
|
||||||
case 'status':
|
|
||||||
return [
|
|
||||||
...Object.keys(statusFilters).map(status => {
|
|
||||||
return {
|
|
||||||
id: status,
|
|
||||||
name: statusFilters[status].TEXT,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
id: 'all',
|
|
||||||
name: this.$t('CHAT_LIST.FILTER_ALL'),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
case 'assignee_id':
|
|
||||||
return this.$store.getters['agents/getAgents'];
|
|
||||||
case 'contact':
|
|
||||||
return this.$store.getters['contacts/getContacts'];
|
|
||||||
case 'inbox_id':
|
|
||||||
return this.$store.getters['inboxes/getInboxes'];
|
|
||||||
case 'team_id':
|
|
||||||
return this.$store.getters['teams/getTeams'];
|
|
||||||
case 'campaign_id':
|
|
||||||
return this.$store.getters['campaigns/getAllCampaigns'].map(i => {
|
|
||||||
return {
|
|
||||||
id: i.id,
|
|
||||||
name: i.title,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
case 'labels':
|
|
||||||
return this.$store.getters['labels/getLabels'].map(i => {
|
|
||||||
return {
|
|
||||||
id: i.id,
|
|
||||||
name: i.title,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
case 'browser_language':
|
|
||||||
return languages;
|
|
||||||
case 'country_code':
|
|
||||||
return countries;
|
|
||||||
case 'message_type':
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
id: 'incoming',
|
|
||||||
name: 'Incoming Message',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'outgoing',
|
|
||||||
name: 'Outgoing Message',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
default:
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getActionDropdownValues(type) {
|
|
||||||
switch (type) {
|
|
||||||
case 'assign_team':
|
|
||||||
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,
|
|
||||||
name: i.title,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
default:
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
appendNewCondition() {
|
|
||||||
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({
|
|
||||||
action_name: 'assign_team',
|
|
||||||
action_params: [],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
removeFilter(index) {
|
|
||||||
if (this.automation.conditions.length <= 1) {
|
|
||||||
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('AUTOMATION.ACTION.DELETE_MESSAGE'));
|
|
||||||
} else {
|
|
||||||
this.automation.actions.splice(index, 1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
submitAutomation() {
|
|
||||||
this.$v.$touch();
|
|
||||||
if (this.$v.$invalid) return;
|
|
||||||
const automation = JSON.parse(JSON.stringify(this.automation));
|
|
||||||
automation.conditions[
|
|
||||||
automation.conditions.length - 1
|
|
||||||
].query_operator = null;
|
|
||||||
automation.conditions = filterQueryGenerator(
|
|
||||||
automation.conditions
|
|
||||||
).payload;
|
|
||||||
automation.actions = actionQueryGenerator(automation.actions);
|
|
||||||
this.$emit('saveAutomation', automation, 'EDIT');
|
|
||||||
},
|
|
||||||
resetFilter(index, currentCondition) {
|
|
||||||
this.automation.conditions[index].filter_operator = this.automationTypes[
|
|
||||||
this.automation.event_name
|
|
||||||
].conditions.find(
|
|
||||||
condition => condition.key === currentCondition.attribute_key
|
|
||||||
).filterOperators[0].value;
|
|
||||||
this.automation.conditions[index].values = '';
|
|
||||||
},
|
|
||||||
showUserInput(operatorType) {
|
|
||||||
if (operatorType === 'is_present' || operatorType === 'is_not_present')
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
formatAutomation(automation) {
|
|
||||||
const formattedConditions = automation.conditions.map(condition => {
|
|
||||||
const inputType = this.automationTypes[
|
|
||||||
automation.event_name
|
|
||||||
].conditions.find(item => item.key === condition.attribute_key)
|
|
||||||
.inputType;
|
|
||||||
if (inputType === 'plain_text') {
|
|
||||||
return {
|
|
||||||
...condition,
|
|
||||||
values: condition.values[0],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...condition,
|
|
||||||
values: [
|
|
||||||
...this.getConditionDropdownValues(condition.attribute_key),
|
|
||||||
].filter(item => [...condition.values].includes(item.id)),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const formattedActions = automation.actions.map(action => {
|
|
||||||
let actionParams = [];
|
|
||||||
if (action.action_params.length) {
|
|
||||||
const inputType = AUTOMATION_ACTION_TYPES.find(
|
|
||||||
item => item.key === action.action_name
|
|
||||||
).inputType;
|
|
||||||
if (inputType === 'multi_select') {
|
|
||||||
actionParams = [
|
|
||||||
...this.getActionDropdownValues(action.action_name),
|
|
||||||
].filter(item => [...action.action_params].includes(item.id));
|
|
||||||
} else if (inputType === 'team_message') {
|
|
||||||
actionParams = {
|
|
||||||
team_ids: [
|
|
||||||
...this.getActionDropdownValues(action.action_name),
|
|
||||||
].filter(item =>
|
|
||||||
[...action.action_params[0].team_ids].includes(item.id)
|
|
||||||
),
|
|
||||||
message: action.action_params[0].message,
|
|
||||||
};
|
|
||||||
} else actionParams = [...action.action_params];
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...action,
|
|
||||||
action_params: actionParams,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
this.automation = {
|
|
||||||
...automation,
|
|
||||||
conditions: formattedConditions,
|
|
||||||
actions: formattedActions,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
showActionInput(actionName) {
|
|
||||||
if (actionName === 'send_email_to_team' || actionName === 'send_message')
|
|
||||||
return false;
|
|
||||||
const type = AUTOMATION_ACTION_TYPES.find(
|
|
||||||
action => action.key === actionName
|
|
||||||
).inputType;
|
|
||||||
if (type === null) return false;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
getFileName(id, actionType) {
|
|
||||||
if (!id) return '';
|
|
||||||
if (actionType === 'send_attachment') {
|
|
||||||
const file = this.automation.files.find(item => item.blob_id === id);
|
|
||||||
// replace `blob_id.toString()` with file name once api is fixed.
|
|
||||||
if (file) return file.filename.toString();
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -170,6 +170,12 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.$store.dispatch('inboxes/get');
|
||||||
|
this.$store.dispatch('agents/get');
|
||||||
|
this.$store.dispatch('contacts/get');
|
||||||
|
this.$store.dispatch('teams/get');
|
||||||
|
this.$store.dispatch('labels/get');
|
||||||
|
this.$store.dispatch('campaigns/get');
|
||||||
this.$store.dispatch('automations/get');
|
this.$store.dispatch('automations/get');
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -220,18 +226,18 @@ export default {
|
|||||||
async submitAutomation(payload, mode) {
|
async submitAutomation(payload, mode) {
|
||||||
try {
|
try {
|
||||||
const action =
|
const action =
|
||||||
mode === 'EDIT' ? 'automations/update' : 'automations/create';
|
mode === 'edit' ? 'automations/update' : 'automations/create';
|
||||||
const successMessage =
|
const successMessage =
|
||||||
mode === 'EDIT'
|
mode === 'edit'
|
||||||
? this.$t('AUTOMATION.EDIT.API.SUCCESS_MESSAGE')
|
? this.$t('AUTOMATION.EDIT.API.SUCCESS_MESSAGE')
|
||||||
: this.$t('AUTOMATION.ADD.API.SUCCESS_MESSAGE');
|
: this.$t('AUTOMATION.ADD.API.SUCCESS_MESSAGE');
|
||||||
await this.$store.dispatch(action, payload);
|
await this.$store.dispatch(action, payload);
|
||||||
this.showAlert(this.$t(successMessage));
|
this.showAlert(successMessage);
|
||||||
this.hideAddPopup();
|
this.hideAddPopup();
|
||||||
this.hideEditPopup();
|
this.hideEditPopup();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
mode === 'EDIT'
|
mode === 'edit'
|
||||||
? this.$t('AUTOMATION.EDIT.API.ERROR_MESSAGE')
|
? this.$t('AUTOMATION.EDIT.API.ERROR_MESSAGE')
|
||||||
: this.$t('AUTOMATION.ADD.API.ERROR_MESSAGE');
|
: this.$t('AUTOMATION.ADD.API.ERROR_MESSAGE');
|
||||||
this.showAlert(errorMessage);
|
this.showAlert(errorMessage);
|
||||||
|
|||||||
@@ -1,51 +1,8 @@
|
|||||||
const OPERATOR_TYPES_1 = [
|
import {
|
||||||
{
|
OPERATOR_TYPES_1,
|
||||||
value: 'equal_to',
|
OPERATOR_TYPES_2,
|
||||||
label: 'Equal to',
|
OPERATOR_TYPES_3,
|
||||||
},
|
} from './operators';
|
||||||
{
|
|
||||||
value: 'not_equal_to',
|
|
||||||
label: 'Not equal to',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const OPERATOR_TYPES_2 = [
|
|
||||||
{
|
|
||||||
value: 'equal_to',
|
|
||||||
label: 'Equal to',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'not_equal_to',
|
|
||||||
label: 'Not equal to',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'contains',
|
|
||||||
label: 'Contains',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'does_not_contain',
|
|
||||||
label: 'Does not contain',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const OPERATOR_TYPES_3 = [
|
|
||||||
{
|
|
||||||
value: 'equal_to',
|
|
||||||
label: 'Equal to',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'not_equal_to',
|
|
||||||
label: 'Not equal to',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'is_present',
|
|
||||||
label: 'Is present',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'is_not_present',
|
|
||||||
label: 'Is not present',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const AUTOMATIONS = {
|
export const AUTOMATIONS = {
|
||||||
message_created: {
|
message_created: {
|
||||||
@@ -343,7 +300,7 @@ export const AUTOMATION_ACTION_TYPES = [
|
|||||||
{
|
{
|
||||||
key: 'assign_team',
|
key: 'assign_team',
|
||||||
label: 'Assign a team',
|
label: 'Assign a team',
|
||||||
inputType: 'multi_select',
|
inputType: 'search_select',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'add_label',
|
key: 'add_label',
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
export const OPERATOR_TYPES_1 = [
|
||||||
|
{
|
||||||
|
value: 'equal_to',
|
||||||
|
label: 'Equal to',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'not_equal_to',
|
||||||
|
label: 'Not equal to',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const OPERATOR_TYPES_2 = [
|
||||||
|
{
|
||||||
|
value: 'equal_to',
|
||||||
|
label: 'Equal to',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'not_equal_to',
|
||||||
|
label: 'Not equal to',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'contains',
|
||||||
|
label: 'Contains',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'does_not_contain',
|
||||||
|
label: 'Does not contain',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const OPERATOR_TYPES_3 = [
|
||||||
|
{
|
||||||
|
value: 'equal_to',
|
||||||
|
label: 'Equal to',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'not_equal_to',
|
||||||
|
label: 'Not equal to',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'is_present',
|
||||||
|
label: 'Is present',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'is_not_present',
|
||||||
|
label: 'Is not present',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const OPERATOR_TYPES_4 = [
|
||||||
|
{
|
||||||
|
value: 'equal_to',
|
||||||
|
label: 'Equal to',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'not_equal_to',
|
||||||
|
label: 'Not equal to',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'is_present',
|
||||||
|
label: 'Is present',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'is_not_present',
|
||||||
|
label: 'Is not present',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'is_greater_than',
|
||||||
|
label: 'Is greater than',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'is_less_than',
|
||||||
|
label: 'Is less than',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const OPERATOR_TYPES_5 = [
|
||||||
|
{
|
||||||
|
value: 'is_greater_than',
|
||||||
|
label: 'Is greater than',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'is_less_than',
|
||||||
|
label: 'Is less than',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'days_before',
|
||||||
|
label: 'Is x days before',
|
||||||
|
},
|
||||||
|
];
|
||||||
763
app/javascript/shared/mixins/specs/automationFixtures.js
Normal file
763
app/javascript/shared/mixins/specs/automationFixtures.js
Normal file
@@ -0,0 +1,763 @@
|
|||||||
|
import allLanguages from '../../../dashboard/components/widgets/conversation/advancedFilterItems/languages.js';
|
||||||
|
|
||||||
|
import allCountries from '../../../shared/constants/countries.js';
|
||||||
|
|
||||||
|
export const customAttributes = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
attribute_display_name: 'Signed Up At',
|
||||||
|
attribute_display_type: 'date',
|
||||||
|
attribute_description: 'This is a test',
|
||||||
|
attribute_key: 'signed_up_at',
|
||||||
|
attribute_values: [],
|
||||||
|
attribute_model: 'conversation_attribute',
|
||||||
|
default_value: null,
|
||||||
|
created_at: '2022-01-26T08:06:39.470Z',
|
||||||
|
updated_at: '2022-01-26T08:06:39.470Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
attribute_display_name: 'Prime User',
|
||||||
|
attribute_display_type: 'checkbox',
|
||||||
|
attribute_description: 'Test',
|
||||||
|
attribute_key: 'prime_user',
|
||||||
|
attribute_values: [],
|
||||||
|
attribute_model: 'contact_attribute',
|
||||||
|
default_value: null,
|
||||||
|
created_at: '2022-01-26T08:07:29.664Z',
|
||||||
|
updated_at: '2022-01-26T08:07:29.664Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
attribute_display_name: 'Test',
|
||||||
|
attribute_display_type: 'text',
|
||||||
|
attribute_description: 'Test',
|
||||||
|
attribute_key: 'test',
|
||||||
|
attribute_values: [],
|
||||||
|
attribute_model: 'conversation_attribute',
|
||||||
|
default_value: null,
|
||||||
|
created_at: '2022-01-26T08:07:58.325Z',
|
||||||
|
updated_at: '2022-01-26T08:07:58.325Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
attribute_display_name: 'Link',
|
||||||
|
attribute_display_type: 'link',
|
||||||
|
attribute_description: 'Test',
|
||||||
|
attribute_key: 'link',
|
||||||
|
attribute_values: [],
|
||||||
|
attribute_model: 'conversation_attribute',
|
||||||
|
default_value: null,
|
||||||
|
created_at: '2022-02-07T07:31:51.562Z',
|
||||||
|
updated_at: '2022-02-07T07:31:51.562Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
attribute_display_name: 'My List',
|
||||||
|
attribute_display_type: 'list',
|
||||||
|
attribute_description: 'This is a sample list',
|
||||||
|
attribute_key: 'my_list',
|
||||||
|
attribute_values: ['item1', 'item2', 'item3'],
|
||||||
|
attribute_model: 'conversation_attribute',
|
||||||
|
default_value: null,
|
||||||
|
created_at: '2022-02-21T20:31:34.175Z',
|
||||||
|
updated_at: '2022-02-21T20:31:34.175Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
attribute_display_name: 'My Check',
|
||||||
|
attribute_display_type: 'checkbox',
|
||||||
|
attribute_description: 'Test Checkbox',
|
||||||
|
attribute_key: 'my_check',
|
||||||
|
attribute_values: [],
|
||||||
|
attribute_model: 'conversation_attribute',
|
||||||
|
default_value: null,
|
||||||
|
created_at: '2022-02-21T20:31:53.385Z',
|
||||||
|
updated_at: '2022-02-21T20:31:53.385Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
attribute_display_name: 'ConList',
|
||||||
|
attribute_display_type: 'list',
|
||||||
|
attribute_description: 'This is a test list\n',
|
||||||
|
attribute_key: 'conlist',
|
||||||
|
attribute_values: ['Hello', 'Test', 'Test2'],
|
||||||
|
attribute_model: 'contact_attribute',
|
||||||
|
default_value: null,
|
||||||
|
created_at: '2022-02-28T12:58:05.005Z',
|
||||||
|
updated_at: '2022-02-28T12:58:05.005Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
attribute_display_name: 'asdf',
|
||||||
|
attribute_display_type: 'link',
|
||||||
|
attribute_description: 'This is a some text',
|
||||||
|
attribute_key: 'asdf',
|
||||||
|
attribute_values: [],
|
||||||
|
attribute_model: 'contact_attribute',
|
||||||
|
default_value: null,
|
||||||
|
created_at: '2022-04-21T05:48:16.168Z',
|
||||||
|
updated_at: '2022-04-21T05:48:16.168Z',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export const emptyAutomation = {
|
||||||
|
name: null,
|
||||||
|
description: null,
|
||||||
|
event_name: 'conversation_created',
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
attribute_key: 'status',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: '',
|
||||||
|
query_operator: 'and',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
action_name: 'assign_team',
|
||||||
|
action_params: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
export const filterAttributes = [
|
||||||
|
{
|
||||||
|
key: 'status',
|
||||||
|
name: 'Status',
|
||||||
|
attributeI18nKey: 'STATUS',
|
||||||
|
inputType: 'multi_select',
|
||||||
|
filterOperators: [
|
||||||
|
{ value: 'equal_to', label: 'Equal to' },
|
||||||
|
{ value: 'not_equal_to', label: 'Not equal to' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'browser_language',
|
||||||
|
name: 'Browser Language',
|
||||||
|
attributeI18nKey: 'BROWSER_LANGUAGE',
|
||||||
|
inputType: 'search_select',
|
||||||
|
filterOperators: [
|
||||||
|
{ value: 'equal_to', label: 'Equal to' },
|
||||||
|
{ value: 'not_equal_to', label: 'Not equal to' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'country_code',
|
||||||
|
name: 'Country',
|
||||||
|
attributeI18nKey: 'COUNTRY_NAME',
|
||||||
|
inputType: 'search_select',
|
||||||
|
filterOperators: [
|
||||||
|
{ value: 'equal_to', label: 'Equal to' },
|
||||||
|
{ value: 'not_equal_to', label: 'Not equal to' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'referer',
|
||||||
|
name: 'Referrer Link',
|
||||||
|
attributeI18nKey: 'REFERER_LINK',
|
||||||
|
inputType: 'plain_text',
|
||||||
|
filterOperators: [
|
||||||
|
{ value: 'equal_to', label: 'Equal to' },
|
||||||
|
{ value: 'not_equal_to', label: 'Not equal to' },
|
||||||
|
{ value: 'contains', label: 'Contains' },
|
||||||
|
{ value: 'does_not_contain', label: 'Does not contain' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'inbox_id',
|
||||||
|
name: 'Inbox',
|
||||||
|
attributeI18nKey: 'INBOX',
|
||||||
|
inputType: 'multi_select',
|
||||||
|
filterOperators: [
|
||||||
|
{ value: 'equal_to', label: 'Equal to' },
|
||||||
|
{ value: 'not_equal_to', label: 'Not equal to' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'conversation_custom_attribute',
|
||||||
|
name: 'Conversation Custom Attributes',
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'signed_up_at',
|
||||||
|
name: 'Signed Up At',
|
||||||
|
inputType: 'date',
|
||||||
|
filterOperators: [
|
||||||
|
{ value: 'equal_to', label: 'Equal to' },
|
||||||
|
{ value: 'not_equal_to', label: 'Not equal to' },
|
||||||
|
{ value: 'is_present', label: 'Is present' },
|
||||||
|
{ value: 'is_not_present', label: 'Is not present' },
|
||||||
|
{ value: 'is_greater_than', label: 'Is greater than' },
|
||||||
|
{ value: 'is_less_than', label: 'Is less than' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'test',
|
||||||
|
name: 'Test',
|
||||||
|
inputType: 'plain_text',
|
||||||
|
filterOperators: [
|
||||||
|
{ value: 'equal_to', label: 'Equal to' },
|
||||||
|
{ value: 'not_equal_to', label: 'Not equal to' },
|
||||||
|
{ value: 'is_present', label: 'Is present' },
|
||||||
|
{ value: 'is_not_present', label: 'Is not present' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'link',
|
||||||
|
name: 'Link',
|
||||||
|
inputType: 'plain_text',
|
||||||
|
filterOperators: [
|
||||||
|
{ value: 'equal_to', label: 'Equal to' },
|
||||||
|
{ value: 'not_equal_to', label: 'Not equal to' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'my_list',
|
||||||
|
name: 'My List',
|
||||||
|
inputType: 'search_select',
|
||||||
|
filterOperators: [
|
||||||
|
{ value: 'equal_to', label: 'Equal to' },
|
||||||
|
{ value: 'not_equal_to', label: 'Not equal to' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'my_check',
|
||||||
|
name: 'My Check',
|
||||||
|
inputType: 'search_select',
|
||||||
|
filterOperators: [
|
||||||
|
{ value: 'equal_to', label: 'Equal to' },
|
||||||
|
{ value: 'not_equal_to', label: 'Not equal to' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'contact_custom_attribute',
|
||||||
|
name: 'Contact Custom Attributes',
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'prime_user',
|
||||||
|
name: 'Prime User',
|
||||||
|
inputType: 'search_select',
|
||||||
|
filterOperators: [
|
||||||
|
{ value: 'equal_to', label: 'Equal to' },
|
||||||
|
{ value: 'not_equal_to', label: 'Not equal to' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'conlist',
|
||||||
|
name: 'ConList',
|
||||||
|
inputType: 'search_select',
|
||||||
|
filterOperators: [
|
||||||
|
{ value: 'equal_to', label: 'Equal to' },
|
||||||
|
{ value: 'not_equal_to', label: 'Not equal to' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'asdf',
|
||||||
|
name: 'asdf',
|
||||||
|
inputType: 'plain_text',
|
||||||
|
filterOperators: [
|
||||||
|
{ value: 'equal_to', label: 'Equal to' },
|
||||||
|
{ value: 'not_equal_to', label: 'Not equal to' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export const automation = {
|
||||||
|
id: 164,
|
||||||
|
account_id: 1,
|
||||||
|
name: 'Attachment',
|
||||||
|
description: 'Yo',
|
||||||
|
event_name: 'conversation_created',
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
values: [{ id: 'open', name: 'Open' }],
|
||||||
|
attribute_key: 'status',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
query_operator: 'and',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [{ action_name: 'send_attachment', action_params: [59] }],
|
||||||
|
created_on: 1652717181,
|
||||||
|
active: true,
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
id: 50,
|
||||||
|
automation_rule_id: 164,
|
||||||
|
file_type: 'image/jpeg',
|
||||||
|
account_id: 1,
|
||||||
|
file_url:
|
||||||
|
'http://localhost:3000/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBRQT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--965b4c27f4c5e47c526f0f38266b25417b72e5dd/pfp.jpeg',
|
||||||
|
blob_id: 59,
|
||||||
|
filename: 'pfp.jpeg',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
export const agents = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
account_id: 1,
|
||||||
|
availability_status: 'online',
|
||||||
|
auto_offline: true,
|
||||||
|
confirmed: true,
|
||||||
|
email: 'john@acme.inc',
|
||||||
|
available_name: 'Fayaz',
|
||||||
|
name: 'Fayaz',
|
||||||
|
role: 'administrator',
|
||||||
|
thumbnail:
|
||||||
|
'https://www.gravatar.com/avatar/0d722ac7bc3b3c92c030d0da9690d981?d=404',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
account_id: 1,
|
||||||
|
availability_status: 'offline',
|
||||||
|
auto_offline: true,
|
||||||
|
confirmed: true,
|
||||||
|
email: 'john@doe.com',
|
||||||
|
available_name: 'John',
|
||||||
|
name: 'John',
|
||||||
|
role: 'agent',
|
||||||
|
thumbnail:
|
||||||
|
'https://www.gravatar.com/avatar/6a6c19fea4a3676970167ce51f39e6ee?d=404',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export const booleanFilterOptions = [
|
||||||
|
{
|
||||||
|
id: true,
|
||||||
|
name: 'True',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: false,
|
||||||
|
name: 'False',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export const teams = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'sales team',
|
||||||
|
description: 'This is our internal sales team',
|
||||||
|
allow_auto_assign: true,
|
||||||
|
account_id: 1,
|
||||||
|
is_member: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'fayaz',
|
||||||
|
description: 'Test',
|
||||||
|
allow_auto_assign: true,
|
||||||
|
account_id: 1,
|
||||||
|
is_member: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export const campaigns = [];
|
||||||
|
export const contacts = [
|
||||||
|
{
|
||||||
|
additional_attributes: {},
|
||||||
|
availability_status: 'offline',
|
||||||
|
email: 'asd123123@asd.com',
|
||||||
|
id: 32,
|
||||||
|
name: 'asd123123',
|
||||||
|
phone_number: null,
|
||||||
|
identifier: null,
|
||||||
|
thumbnail:
|
||||||
|
'https://www.gravatar.com/avatar/46000d9a1eef3e24a02ca9d6c2a8f494?d=404',
|
||||||
|
custom_attributes: {},
|
||||||
|
conversations_count: 5,
|
||||||
|
last_activity_at: 1650519706,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
additional_attributes: {},
|
||||||
|
availability_status: 'offline',
|
||||||
|
email: 'barry_allen@a.com',
|
||||||
|
id: 29,
|
||||||
|
name: 'barry_allen',
|
||||||
|
phone_number: null,
|
||||||
|
identifier: null,
|
||||||
|
thumbnail:
|
||||||
|
'https://www.gravatar.com/avatar/ab5ff99efa3bc1f74db1dc2885f9e2ce?d=404',
|
||||||
|
custom_attributes: {},
|
||||||
|
conversations_count: 1,
|
||||||
|
last_activity_at: 1643728899,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export const inboxes = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
avatar_url: '',
|
||||||
|
channel_id: 1,
|
||||||
|
name: 'Acme Support',
|
||||||
|
channel_type: 'Channel::WebWidget',
|
||||||
|
greeting_enabled: false,
|
||||||
|
greeting_message: '',
|
||||||
|
working_hours_enabled: false,
|
||||||
|
enable_email_collect: true,
|
||||||
|
csat_survey_enabled: true,
|
||||||
|
enable_auto_assignment: true,
|
||||||
|
out_of_office_message:
|
||||||
|
'We are unavailable at the moment. Leave a message we will respond once we are back.',
|
||||||
|
working_hours: [
|
||||||
|
{
|
||||||
|
day_of_week: 0,
|
||||||
|
closed_all_day: true,
|
||||||
|
open_hour: null,
|
||||||
|
open_minutes: null,
|
||||||
|
close_hour: null,
|
||||||
|
close_minutes: null,
|
||||||
|
open_all_day: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
day_of_week: 1,
|
||||||
|
closed_all_day: false,
|
||||||
|
open_hour: 9,
|
||||||
|
open_minutes: 0,
|
||||||
|
close_hour: 17,
|
||||||
|
close_minutes: 0,
|
||||||
|
open_all_day: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
day_of_week: 2,
|
||||||
|
closed_all_day: false,
|
||||||
|
open_hour: 9,
|
||||||
|
open_minutes: 0,
|
||||||
|
close_hour: 17,
|
||||||
|
close_minutes: 0,
|
||||||
|
open_all_day: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
day_of_week: 3,
|
||||||
|
closed_all_day: false,
|
||||||
|
open_hour: 9,
|
||||||
|
open_minutes: 0,
|
||||||
|
close_hour: 17,
|
||||||
|
close_minutes: 0,
|
||||||
|
open_all_day: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
day_of_week: 4,
|
||||||
|
closed_all_day: false,
|
||||||
|
open_hour: 9,
|
||||||
|
open_minutes: 0,
|
||||||
|
close_hour: 17,
|
||||||
|
close_minutes: 0,
|
||||||
|
open_all_day: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
day_of_week: 5,
|
||||||
|
closed_all_day: false,
|
||||||
|
open_hour: 9,
|
||||||
|
open_minutes: 0,
|
||||||
|
close_hour: 17,
|
||||||
|
close_minutes: 0,
|
||||||
|
open_all_day: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
day_of_week: 6,
|
||||||
|
closed_all_day: true,
|
||||||
|
open_hour: null,
|
||||||
|
open_minutes: null,
|
||||||
|
close_hour: null,
|
||||||
|
close_minutes: null,
|
||||||
|
open_all_day: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timezone: 'America/Los_Angeles',
|
||||||
|
callback_webhook_url: null,
|
||||||
|
allow_messages_after_resolved: true,
|
||||||
|
widget_color: '#1f93ff',
|
||||||
|
website_url: 'https://acme.inc',
|
||||||
|
hmac_mandatory: false,
|
||||||
|
welcome_title: '',
|
||||||
|
welcome_tagline: '',
|
||||||
|
web_widget_script:
|
||||||
|
'\n <script>\n (function(d,t) {\n var BASE_URL="http://localhost:3000";\n var g=d.createElement(t),s=d.getElementsByTagName(t)[0];\n g.src=BASE_URL+"/packs/js/sdk.js";\n g.defer = true;\n g.async = true;\n s.parentNode.insertBefore(g,s);\n g.onload=function(){\n window.chatwootSDK.run({\n websiteToken: \'yZ7USzaEs7hrwUAHLGwjbxJ1\',\n baseUrl: BASE_URL\n })\n }\n })(document,"script");\n </script>\n ',
|
||||||
|
website_token: 'yZ7USzaEs7hrwUAHLGwjbxJ1',
|
||||||
|
selected_feature_flags: ['attachments', 'emoji_picker', 'end_conversation'],
|
||||||
|
reply_time: 'in_a_few_minutes',
|
||||||
|
hmac_token: 'rRJW1BHu4aFMMey4SE7tWr8A',
|
||||||
|
pre_chat_form_enabled: false,
|
||||||
|
pre_chat_form_options: {
|
||||||
|
pre_chat_fields: [
|
||||||
|
{
|
||||||
|
name: 'emailAddress',
|
||||||
|
type: 'email',
|
||||||
|
label: 'Email Id',
|
||||||
|
enabled: false,
|
||||||
|
required: true,
|
||||||
|
field_type: 'standard',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fullName',
|
||||||
|
type: 'text',
|
||||||
|
label: 'Full name',
|
||||||
|
enabled: false,
|
||||||
|
required: false,
|
||||||
|
field_type: 'standard',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'phoneNumber',
|
||||||
|
type: 'text',
|
||||||
|
label: 'Phone number',
|
||||||
|
enabled: false,
|
||||||
|
required: false,
|
||||||
|
field_type: 'standard',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pre_chat_message: 'Share your queries or comments here.',
|
||||||
|
},
|
||||||
|
continuity_via_email: true,
|
||||||
|
phone_number: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
avatar_url: '',
|
||||||
|
channel_id: 1,
|
||||||
|
name: 'Email',
|
||||||
|
channel_type: 'Channel::Email',
|
||||||
|
greeting_enabled: false,
|
||||||
|
greeting_message: null,
|
||||||
|
working_hours_enabled: false,
|
||||||
|
enable_email_collect: true,
|
||||||
|
csat_survey_enabled: false,
|
||||||
|
enable_auto_assignment: true,
|
||||||
|
out_of_office_message: null,
|
||||||
|
working_hours: [
|
||||||
|
{
|
||||||
|
day_of_week: 0,
|
||||||
|
closed_all_day: true,
|
||||||
|
open_hour: null,
|
||||||
|
open_minutes: null,
|
||||||
|
close_hour: null,
|
||||||
|
close_minutes: null,
|
||||||
|
open_all_day: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
day_of_week: 1,
|
||||||
|
closed_all_day: false,
|
||||||
|
open_hour: 9,
|
||||||
|
open_minutes: 0,
|
||||||
|
close_hour: 17,
|
||||||
|
close_minutes: 0,
|
||||||
|
open_all_day: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
day_of_week: 2,
|
||||||
|
closed_all_day: false,
|
||||||
|
open_hour: 9,
|
||||||
|
open_minutes: 0,
|
||||||
|
close_hour: 17,
|
||||||
|
close_minutes: 0,
|
||||||
|
open_all_day: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
day_of_week: 3,
|
||||||
|
closed_all_day: false,
|
||||||
|
open_hour: 9,
|
||||||
|
open_minutes: 0,
|
||||||
|
close_hour: 17,
|
||||||
|
close_minutes: 0,
|
||||||
|
open_all_day: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
day_of_week: 4,
|
||||||
|
closed_all_day: false,
|
||||||
|
open_hour: 9,
|
||||||
|
open_minutes: 0,
|
||||||
|
close_hour: 17,
|
||||||
|
close_minutes: 0,
|
||||||
|
open_all_day: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
day_of_week: 5,
|
||||||
|
closed_all_day: false,
|
||||||
|
open_hour: 9,
|
||||||
|
open_minutes: 0,
|
||||||
|
close_hour: 17,
|
||||||
|
close_minutes: 0,
|
||||||
|
open_all_day: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
day_of_week: 6,
|
||||||
|
closed_all_day: true,
|
||||||
|
open_hour: null,
|
||||||
|
open_minutes: null,
|
||||||
|
close_hour: null,
|
||||||
|
close_minutes: null,
|
||||||
|
open_all_day: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timezone: 'UTC',
|
||||||
|
callback_webhook_url: null,
|
||||||
|
allow_messages_after_resolved: true,
|
||||||
|
widget_color: null,
|
||||||
|
website_url: null,
|
||||||
|
hmac_mandatory: null,
|
||||||
|
welcome_title: null,
|
||||||
|
welcome_tagline: null,
|
||||||
|
web_widget_script: null,
|
||||||
|
website_token: null,
|
||||||
|
selected_feature_flags: null,
|
||||||
|
reply_time: null,
|
||||||
|
phone_number: null,
|
||||||
|
forward_to_email: '9ae8ebb96c7f2d6705009f5add6d1a2d@false',
|
||||||
|
email: 'fayaz@chatwoot.com',
|
||||||
|
imap_login: '',
|
||||||
|
imap_password: '',
|
||||||
|
imap_address: '',
|
||||||
|
imap_port: 0,
|
||||||
|
imap_enabled: false,
|
||||||
|
imap_enable_ssl: true,
|
||||||
|
smtp_login: '',
|
||||||
|
smtp_password: '',
|
||||||
|
smtp_address: '',
|
||||||
|
smtp_port: 0,
|
||||||
|
smtp_enabled: false,
|
||||||
|
smtp_domain: '',
|
||||||
|
smtp_enable_ssl_tls: false,
|
||||||
|
smtp_enable_starttls_auto: true,
|
||||||
|
smtp_openssl_verify_mode: 'none',
|
||||||
|
smtp_authentication: 'login',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export const labels = [
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: 'testlabel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: 'snoozes',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export const statusFilterOptions = [
|
||||||
|
{ id: 'open', name: 'Open' },
|
||||||
|
{ id: 'resolved', name: 'Resolved' },
|
||||||
|
{ id: 'pending', name: 'Pending' },
|
||||||
|
{ id: 'snoozed', name: 'Snoozed' },
|
||||||
|
{ id: 'all', name: 'All' },
|
||||||
|
];
|
||||||
|
export const languages = allLanguages;
|
||||||
|
export const countries = allCountries;
|
||||||
|
export const MESSAGE_CONDITION_VALUES = [
|
||||||
|
{
|
||||||
|
id: 'incoming',
|
||||||
|
name: 'Incoming Message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'outgoing',
|
||||||
|
name: 'Outgoing Message',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const automationToSubmit = {
|
||||||
|
name: 'Fayaz',
|
||||||
|
description: 'Hello',
|
||||||
|
event_name: 'conversation_created',
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
attribute_key: 'status',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: [{ id: 'open', name: 'Open' }],
|
||||||
|
query_operator: 'and',
|
||||||
|
custom_attribute_type: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{ action_name: 'add_label', action_params: [{ id: 2, name: 'testlabel' }] },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const savedAutomation = {
|
||||||
|
id: 165,
|
||||||
|
account_id: 1,
|
||||||
|
name: 'Fayaz',
|
||||||
|
description: 'Hello',
|
||||||
|
event_name: 'conversation_created',
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
values: ['open'],
|
||||||
|
attribute_key: 'status',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
action_name: 'add_label',
|
||||||
|
action_params: [2],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
created_on: 1652776043,
|
||||||
|
active: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const contactAttrs = [
|
||||||
|
{
|
||||||
|
key: 'contact_list',
|
||||||
|
name: 'Contact List',
|
||||||
|
inputType: 'search_select',
|
||||||
|
filterOperators: [
|
||||||
|
{
|
||||||
|
value: 'equal_to',
|
||||||
|
label: 'Equal to',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'not_equal_to',
|
||||||
|
label: 'Not equal to',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export const conversationAttrs = [
|
||||||
|
{
|
||||||
|
key: 'text_attr',
|
||||||
|
name: 'Text Attr',
|
||||||
|
inputType: 'plain_text',
|
||||||
|
filterOperators: [
|
||||||
|
{
|
||||||
|
value: 'equal_to',
|
||||||
|
label: 'Equal to',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'not_equal_to',
|
||||||
|
label: 'Not equal to',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'is_present',
|
||||||
|
label: 'Is present',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'is_not_present',
|
||||||
|
label: 'Is not present',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export const expectedOutputForCustomAttributeGenerator = [
|
||||||
|
{
|
||||||
|
key: 'conversation_custom_attribute',
|
||||||
|
name: 'Conversation Custom Attributes',
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'text_attr',
|
||||||
|
name: 'Text Attr',
|
||||||
|
inputType: 'plain_text',
|
||||||
|
filterOperators: [
|
||||||
|
{ value: 'equal_to', label: 'Equal to' },
|
||||||
|
{ value: 'not_equal_to', label: 'Not equal to' },
|
||||||
|
{ value: 'is_present', label: 'Is present' },
|
||||||
|
{ value: 'is_not_present', label: 'Is not present' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'contact_custom_attribute',
|
||||||
|
name: 'Contact Custom Attributes',
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'contact_list',
|
||||||
|
name: 'Contact List',
|
||||||
|
inputType: 'search_select',
|
||||||
|
filterOperators: [
|
||||||
|
{ value: 'equal_to', label: 'Equal to' },
|
||||||
|
{ value: 'not_equal_to', label: 'Not equal to' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
323
app/javascript/shared/mixins/specs/automationHelper.spec.js
Normal file
323
app/javascript/shared/mixins/specs/automationHelper.spec.js
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
import * as helpers from 'dashboard/helper/automationHelper';
|
||||||
|
import {
|
||||||
|
OPERATOR_TYPES_1,
|
||||||
|
OPERATOR_TYPES_3,
|
||||||
|
OPERATOR_TYPES_4,
|
||||||
|
} from 'dashboard/routes/dashboard/settings/automation/operators';
|
||||||
|
import {
|
||||||
|
customAttributes,
|
||||||
|
labels,
|
||||||
|
automation,
|
||||||
|
contactAttrs,
|
||||||
|
conversationAttrs,
|
||||||
|
expectedOutputForCustomAttributeGenerator,
|
||||||
|
} from './automationFixtures';
|
||||||
|
import { AUTOMATIONS } from 'dashboard/routes/dashboard/settings/automation/constants';
|
||||||
|
|
||||||
|
describe('automationMethodsMixin', () => {
|
||||||
|
it('getCustomAttributeInputType returns the attribute input type', () => {
|
||||||
|
expect(helpers.getCustomAttributeInputType('date')).toEqual('date');
|
||||||
|
expect(helpers.getCustomAttributeInputType('date')).not.toEqual(
|
||||||
|
'some_random_value'
|
||||||
|
);
|
||||||
|
expect(helpers.getCustomAttributeInputType('text')).toEqual('plain_text');
|
||||||
|
expect(helpers.getCustomAttributeInputType('list')).toEqual(
|
||||||
|
'search_select'
|
||||||
|
);
|
||||||
|
expect(helpers.getCustomAttributeInputType('checkbox')).toEqual(
|
||||||
|
'search_select'
|
||||||
|
);
|
||||||
|
expect(helpers.getCustomAttributeInputType('some_random_text')).toEqual(
|
||||||
|
'plain_text'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('isACustomAttribute returns the custom attribute value if true', () => {
|
||||||
|
expect(
|
||||||
|
helpers.isACustomAttribute(customAttributes, 'signed_up_at')
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(helpers.isACustomAttribute(customAttributes, 'status')).toBeFalsy();
|
||||||
|
});
|
||||||
|
it('getCustomAttributeListDropdownValues returns the attribute dropdown values', () => {
|
||||||
|
const myListValues = [
|
||||||
|
{
|
||||||
|
id: 'item1',
|
||||||
|
name: 'item1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'item2',
|
||||||
|
name: 'item2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'item3',
|
||||||
|
name: 'item3',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
expect(
|
||||||
|
helpers.getCustomAttributeListDropdownValues(customAttributes, 'my_list')
|
||||||
|
).toEqual(myListValues);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isCustomAttributeCheckbox checks if attribute is a checkbox', () => {
|
||||||
|
expect(
|
||||||
|
helpers.isCustomAttributeCheckbox(customAttributes, 'prime_user')
|
||||||
|
.attribute_display_type
|
||||||
|
).toEqual('checkbox');
|
||||||
|
expect(
|
||||||
|
helpers.isCustomAttributeCheckbox(customAttributes, 'my_check')
|
||||||
|
.attribute_display_type
|
||||||
|
).toEqual('checkbox');
|
||||||
|
expect(
|
||||||
|
helpers.isCustomAttributeCheckbox(customAttributes, 'my_list')
|
||||||
|
).not.toEqual('checkbox');
|
||||||
|
});
|
||||||
|
it('isCustomAttributeList checks if attribute is a list', () => {
|
||||||
|
expect(
|
||||||
|
helpers.isCustomAttributeList(customAttributes, 'my_list')
|
||||||
|
.attribute_display_type
|
||||||
|
).toEqual('list');
|
||||||
|
});
|
||||||
|
it('getOperatorTypes returns the correct custom attribute operators', () => {
|
||||||
|
expect(helpers.getOperatorTypes('list')).toEqual(OPERATOR_TYPES_1);
|
||||||
|
expect(helpers.getOperatorTypes('text')).toEqual(OPERATOR_TYPES_3);
|
||||||
|
expect(helpers.getOperatorTypes('number')).toEqual(OPERATOR_TYPES_1);
|
||||||
|
expect(helpers.getOperatorTypes('link')).toEqual(OPERATOR_TYPES_1);
|
||||||
|
expect(helpers.getOperatorTypes('date')).toEqual(OPERATOR_TYPES_4);
|
||||||
|
expect(helpers.getOperatorTypes('checkbox')).toEqual(OPERATOR_TYPES_1);
|
||||||
|
expect(helpers.getOperatorTypes('some_random')).toEqual(OPERATOR_TYPES_1);
|
||||||
|
});
|
||||||
|
it('generateConditionOptions returns expected conditions options array', () => {
|
||||||
|
const testConditions = [
|
||||||
|
{
|
||||||
|
id: 123,
|
||||||
|
title: 'Fayaz',
|
||||||
|
email: 'test@test.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'John',
|
||||||
|
id: 324,
|
||||||
|
email: 'test@john.com',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const expectedConditions = [
|
||||||
|
{
|
||||||
|
id: 123,
|
||||||
|
name: 'Fayaz',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 324,
|
||||||
|
name: 'John',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
expect(helpers.generateConditionOptions(testConditions)).toEqual(
|
||||||
|
expectedConditions
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('getActionOptions returns expected actions options array', () => {
|
||||||
|
const expectedOptions = [
|
||||||
|
{
|
||||||
|
id: 'testlabel',
|
||||||
|
name: 'testlabel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'snoozes',
|
||||||
|
name: 'snoozes',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
expect(helpers.getActionOptions({ labels, type: 'add_label' })).toEqual(
|
||||||
|
expectedOptions
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('getConditionOptions returns expected conditions options', () => {
|
||||||
|
const testOptions = [
|
||||||
|
{
|
||||||
|
id: 'open',
|
||||||
|
name: 'Open',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'resolved',
|
||||||
|
name: 'Resolved',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'pending',
|
||||||
|
name: 'Pending',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'snoozed',
|
||||||
|
name: 'Snoozed',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'all',
|
||||||
|
name: 'All',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const expectedOptions = [
|
||||||
|
{
|
||||||
|
id: 'open',
|
||||||
|
name: 'Open',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'resolved',
|
||||||
|
name: 'Resolved',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'pending',
|
||||||
|
name: 'Pending',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'snoozed',
|
||||||
|
name: 'Snoozed',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'all',
|
||||||
|
name: 'All',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
expect(
|
||||||
|
helpers.getConditionOptions({
|
||||||
|
customAttributes,
|
||||||
|
campaigns: [],
|
||||||
|
statusFilterOptions: testOptions,
|
||||||
|
type: 'status',
|
||||||
|
})
|
||||||
|
).toEqual(expectedOptions);
|
||||||
|
});
|
||||||
|
it('getFileName returns the correct file name', () => {
|
||||||
|
expect(
|
||||||
|
helpers.getFileName(automation.actions[0], automation.files)
|
||||||
|
).toEqual('pfp.jpeg');
|
||||||
|
});
|
||||||
|
it('getDefaultConditions returns the resp default condition model', () => {
|
||||||
|
const messageCreatedModel = [
|
||||||
|
{
|
||||||
|
attribute_key: 'message_type',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: '',
|
||||||
|
query_operator: 'and',
|
||||||
|
custom_attribute_type: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const genericConditionModel = [
|
||||||
|
{
|
||||||
|
attribute_key: 'status',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: '',
|
||||||
|
query_operator: 'and',
|
||||||
|
custom_attribute_type: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
expect(helpers.getDefaultConditions('message_created')).toEqual(
|
||||||
|
messageCreatedModel
|
||||||
|
);
|
||||||
|
expect(helpers.getDefaultConditions()).toEqual(genericConditionModel);
|
||||||
|
});
|
||||||
|
it('getDefaultActions returns the resp default action model', () => {
|
||||||
|
const genericActionModel = [
|
||||||
|
{
|
||||||
|
action_name: 'assign_team',
|
||||||
|
action_params: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
expect(helpers.getDefaultActions()).toEqual(genericActionModel);
|
||||||
|
});
|
||||||
|
it('filterCustomAttributes filters the raw custom attributes', () => {
|
||||||
|
const filteredAttributes = [
|
||||||
|
{ key: 'signed_up_at', name: 'Signed Up At', type: 'date' },
|
||||||
|
{ key: 'prime_user', name: 'Prime User', type: 'checkbox' },
|
||||||
|
{ key: 'test', name: 'Test', type: 'text' },
|
||||||
|
{ key: 'link', name: 'Link', type: 'link' },
|
||||||
|
{ key: 'my_list', name: 'My List', type: 'list' },
|
||||||
|
{ key: 'my_check', name: 'My Check', type: 'checkbox' },
|
||||||
|
{ key: 'conlist', name: 'ConList', type: 'list' },
|
||||||
|
{ key: 'asdf', name: 'asdf', type: 'link' },
|
||||||
|
];
|
||||||
|
expect(helpers.filterCustomAttributes(customAttributes)).toEqual(
|
||||||
|
filteredAttributes
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('getStandardAttributeInputType returns the resp default action model', () => {
|
||||||
|
expect(
|
||||||
|
helpers.getStandardAttributeInputType(
|
||||||
|
AUTOMATIONS,
|
||||||
|
'message_created',
|
||||||
|
'message_type'
|
||||||
|
)
|
||||||
|
).toEqual('search_select');
|
||||||
|
expect(
|
||||||
|
helpers.getStandardAttributeInputType(
|
||||||
|
AUTOMATIONS,
|
||||||
|
'conversation_created',
|
||||||
|
'status'
|
||||||
|
)
|
||||||
|
).toEqual('multi_select');
|
||||||
|
expect(
|
||||||
|
helpers.getStandardAttributeInputType(
|
||||||
|
AUTOMATIONS,
|
||||||
|
'conversation_updated',
|
||||||
|
'referer'
|
||||||
|
)
|
||||||
|
).toEqual('plain_text');
|
||||||
|
});
|
||||||
|
it('generateAutomationPayload returns the resp default action model', () => {
|
||||||
|
const testPayload = {
|
||||||
|
name: 'Test',
|
||||||
|
description: 'This is a test',
|
||||||
|
event_name: 'conversation_created',
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
attribute_key: 'status',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: [{ id: 'open', name: 'Open' }],
|
||||||
|
query_operator: 'and',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
action_name: 'add_label',
|
||||||
|
action_params: [{ id: 2, name: 'testlabel' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const expectedPayload = {
|
||||||
|
name: 'Test',
|
||||||
|
description: 'This is a test',
|
||||||
|
event_name: 'conversation_created',
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
attribute_key: 'status',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: ['open'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
action_name: 'add_label',
|
||||||
|
action_params: [2],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
expect(helpers.generateAutomationPayload(testPayload)).toEqual(
|
||||||
|
expectedPayload
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('isCustomAttribute returns the resp default action model', () => {
|
||||||
|
const attrs = helpers.filterCustomAttributes(customAttributes);
|
||||||
|
expect(helpers.isCustomAttribute(attrs, 'my_list')).toBeTruthy();
|
||||||
|
expect(helpers.isCustomAttribute(attrs, 'my_check')).toBeTruthy();
|
||||||
|
expect(helpers.isCustomAttribute(attrs, 'signed_up_at')).toBeTruthy();
|
||||||
|
expect(helpers.isCustomAttribute(attrs, 'link')).toBeTruthy();
|
||||||
|
expect(helpers.isCustomAttribute(attrs, 'prime_user')).toBeTruthy();
|
||||||
|
expect(helpers.isCustomAttribute(attrs, 'hello')).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generateCustomAttributes generates and returns correct condition attribute', () => {
|
||||||
|
expect(
|
||||||
|
helpers.generateCustomAttributes(
|
||||||
|
conversationAttrs,
|
||||||
|
contactAttrs,
|
||||||
|
'Conversation Custom Attributes',
|
||||||
|
'Contact Custom Attributes'
|
||||||
|
)
|
||||||
|
).toEqual(expectedOutputForCustomAttributeGenerator);
|
||||||
|
});
|
||||||
|
});
|
||||||
448
app/javascript/shared/mixins/specs/automationMixin.spec.js
Normal file
448
app/javascript/shared/mixins/specs/automationMixin.spec.js
Normal file
@@ -0,0 +1,448 @@
|
|||||||
|
import methodsMixin from '../../../dashboard/mixins/automations/methodsMixin';
|
||||||
|
import validationsMixin from '../../../dashboard/mixins/automations/validationsMixin';
|
||||||
|
import {
|
||||||
|
automation,
|
||||||
|
customAttributes,
|
||||||
|
agents,
|
||||||
|
booleanFilterOptions,
|
||||||
|
teams,
|
||||||
|
labels,
|
||||||
|
statusFilterOptions,
|
||||||
|
campaigns,
|
||||||
|
contacts,
|
||||||
|
inboxes,
|
||||||
|
languages,
|
||||||
|
countries,
|
||||||
|
MESSAGE_CONDITION_VALUES,
|
||||||
|
automationToSubmit,
|
||||||
|
savedAutomation,
|
||||||
|
} from './automationFixtures';
|
||||||
|
import {
|
||||||
|
AUTOMATIONS,
|
||||||
|
AUTOMATION_ACTION_TYPES,
|
||||||
|
} from '../../../dashboard/routes/dashboard/settings/automation/constants.js';
|
||||||
|
|
||||||
|
import { createWrapper, createLocalVue } from '@vue/test-utils';
|
||||||
|
import Vue from 'vue';
|
||||||
|
import Vuex from 'vuex';
|
||||||
|
const localVue = createLocalVue();
|
||||||
|
localVue.use(Vuex);
|
||||||
|
|
||||||
|
// Vuelidate required to test submit method
|
||||||
|
import Vuelidate from 'vuelidate';
|
||||||
|
Vue.use(Vuelidate);
|
||||||
|
|
||||||
|
const createComponent = (
|
||||||
|
mixins,
|
||||||
|
data,
|
||||||
|
computed = {},
|
||||||
|
methods = {},
|
||||||
|
validations
|
||||||
|
) => {
|
||||||
|
const Component = {
|
||||||
|
render() {},
|
||||||
|
mixins,
|
||||||
|
data,
|
||||||
|
computed,
|
||||||
|
methods,
|
||||||
|
validations,
|
||||||
|
};
|
||||||
|
const Constructor = Vue.extend(Component);
|
||||||
|
const vm = new Constructor().$mount();
|
||||||
|
return createWrapper(vm);
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateComputedProperties = () => {
|
||||||
|
return {
|
||||||
|
statusFilterOptions() {
|
||||||
|
return statusFilterOptions;
|
||||||
|
},
|
||||||
|
agents() {
|
||||||
|
return agents;
|
||||||
|
},
|
||||||
|
customAttributes() {
|
||||||
|
return customAttributes;
|
||||||
|
},
|
||||||
|
labels() {
|
||||||
|
return labels;
|
||||||
|
},
|
||||||
|
teams() {
|
||||||
|
return teams;
|
||||||
|
},
|
||||||
|
booleanFilterOptions() {
|
||||||
|
return booleanFilterOptions;
|
||||||
|
},
|
||||||
|
campaigns() {
|
||||||
|
return campaigns;
|
||||||
|
},
|
||||||
|
contacts() {
|
||||||
|
return contacts;
|
||||||
|
},
|
||||||
|
inboxes() {
|
||||||
|
return inboxes;
|
||||||
|
},
|
||||||
|
languages() {
|
||||||
|
return languages;
|
||||||
|
},
|
||||||
|
countries() {
|
||||||
|
return countries;
|
||||||
|
},
|
||||||
|
MESSAGE_CONDITION_VALUES() {
|
||||||
|
return MESSAGE_CONDITION_VALUES;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('automationMethodsMixin', () => {
|
||||||
|
it('getFileName returns the correct file name', () => {
|
||||||
|
const data = () => {
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
const wrapper = createComponent([methodsMixin], data);
|
||||||
|
expect(
|
||||||
|
wrapper.vm.getFileName(automation.actions[0], automation.files)
|
||||||
|
).toEqual(automation.files[0].filename);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getAttributes returns all attributes', () => {
|
||||||
|
const data = () => {
|
||||||
|
return {
|
||||||
|
automationTypes: AUTOMATIONS,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const wrapper = createComponent([methodsMixin], data);
|
||||||
|
expect(wrapper.vm.getAttributes('conversation_created')).toEqual(
|
||||||
|
AUTOMATIONS.conversation_created.conditions
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getAttributes returns all respective attributes', () => {
|
||||||
|
const data = () => {
|
||||||
|
return {
|
||||||
|
allCustomAttributes: customAttributes,
|
||||||
|
automationTypes: AUTOMATIONS,
|
||||||
|
automation,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const wrapper = createComponent([methodsMixin], data);
|
||||||
|
expect(wrapper.vm.getInputType('status')).toEqual('multi_select');
|
||||||
|
expect(wrapper.vm.getInputType('my_list')).toEqual('search_select');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getOperators returns all respective operators', () => {
|
||||||
|
const data = () => {
|
||||||
|
return {
|
||||||
|
allCustomAttributes: customAttributes,
|
||||||
|
automationTypes: AUTOMATIONS,
|
||||||
|
automation,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const wrapper = createComponent([methodsMixin], data);
|
||||||
|
expect(wrapper.vm.getOperators('status')).toEqual(
|
||||||
|
AUTOMATIONS.conversation_created.conditions[0].filterOperators
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getAutomationType returns the correct automationType', () => {
|
||||||
|
const data = () => {
|
||||||
|
return {
|
||||||
|
automationTypes: AUTOMATIONS,
|
||||||
|
automation,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const wrapper = createComponent([methodsMixin], data);
|
||||||
|
expect(wrapper.vm.getAutomationType('status')).toEqual(
|
||||||
|
AUTOMATIONS[automation.event_name].conditions[0]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getConditionDropdownValues returns respective condition dropdown values', () => {
|
||||||
|
const computed = generateComputedProperties();
|
||||||
|
const data = () => {
|
||||||
|
return {
|
||||||
|
allCustomAttributes: customAttributes,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const wrapper = createComponent([methodsMixin], data, computed);
|
||||||
|
expect(wrapper.vm.getConditionDropdownValues('status')).toEqual(
|
||||||
|
statusFilterOptions
|
||||||
|
);
|
||||||
|
expect(wrapper.vm.getConditionDropdownValues('team_id')).toEqual(teams);
|
||||||
|
expect(wrapper.vm.getConditionDropdownValues('assignee_id')).toEqual(
|
||||||
|
agents
|
||||||
|
);
|
||||||
|
expect(wrapper.vm.getConditionDropdownValues('contact')).toEqual(contacts);
|
||||||
|
expect(wrapper.vm.getConditionDropdownValues('inbox_id')).toEqual(inboxes);
|
||||||
|
expect(wrapper.vm.getConditionDropdownValues('campaigns')).toEqual(
|
||||||
|
campaigns
|
||||||
|
);
|
||||||
|
expect(wrapper.vm.getConditionDropdownValues('browser_language')).toEqual(
|
||||||
|
languages
|
||||||
|
);
|
||||||
|
expect(wrapper.vm.getConditionDropdownValues('country_code')).toEqual(
|
||||||
|
countries
|
||||||
|
);
|
||||||
|
expect(wrapper.vm.getConditionDropdownValues('message_type')).toEqual(
|
||||||
|
MESSAGE_CONDITION_VALUES
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('appendNewCondition appends a new condition to the automation data property', () => {
|
||||||
|
const condition = {
|
||||||
|
attribute_key: 'status',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: '',
|
||||||
|
query_operator: 'and',
|
||||||
|
custom_attribute_type: '',
|
||||||
|
};
|
||||||
|
const data = () => {
|
||||||
|
return {
|
||||||
|
automation,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const wrapper = createComponent([methodsMixin], data);
|
||||||
|
wrapper.vm.appendNewCondition();
|
||||||
|
expect(automation.conditions[automation.conditions.length - 1]).toEqual(
|
||||||
|
condition
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('appendNewAction appends a new condition to the automation data property', () => {
|
||||||
|
const action = {
|
||||||
|
action_name: 'assign_team',
|
||||||
|
action_params: [],
|
||||||
|
};
|
||||||
|
const data = () => {
|
||||||
|
return {
|
||||||
|
automation,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const wrapper = createComponent([methodsMixin], data);
|
||||||
|
wrapper.vm.appendNewAction();
|
||||||
|
expect(automation.actions[automation.actions.length - 1]).toEqual(action);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removeFilter removes the given condition in the automation', () => {
|
||||||
|
const data = () => {
|
||||||
|
return {
|
||||||
|
automation,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const wrapper = createComponent([methodsMixin], data);
|
||||||
|
wrapper.vm.removeFilter(0);
|
||||||
|
expect(automation.conditions.length).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removeAction removes the given action in the automation', () => {
|
||||||
|
const data = () => {
|
||||||
|
return {
|
||||||
|
automation,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const wrapper = createComponent([methodsMixin], data);
|
||||||
|
wrapper.vm.removeAction(0);
|
||||||
|
expect(automation.actions.length).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resetFilter resets the current automation conditions', () => {
|
||||||
|
const data = () => {
|
||||||
|
return {
|
||||||
|
automation: automationToSubmit,
|
||||||
|
automationTypes: AUTOMATIONS,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const conditionAfterReset = {
|
||||||
|
attribute_key: 'status',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: '',
|
||||||
|
query_operator: 'and',
|
||||||
|
custom_attribute_type: '',
|
||||||
|
};
|
||||||
|
const wrapper = createComponent([methodsMixin], data);
|
||||||
|
wrapper.vm.resetFilter(0, automationToSubmit.conditions[0]);
|
||||||
|
expect(automation.conditions[0]).toEqual(conditionAfterReset);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('showUserInput returns boolean value based on the operator type', () => {
|
||||||
|
const data = () => {
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
const wrapper = createComponent([methodsMixin], data);
|
||||||
|
expect(wrapper.vm.showUserInput('is_present')).toBeFalsy();
|
||||||
|
expect(wrapper.vm.showUserInput('is_not_present')).toBeFalsy();
|
||||||
|
expect(wrapper.vm.showUserInput('equal_to')).toBeTruthy();
|
||||||
|
expect(wrapper.vm.showUserInput('not_equal_to')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('showActionInput returns boolean value based on the action type', () => {
|
||||||
|
const data = () => {
|
||||||
|
return {
|
||||||
|
automationActionTypes: AUTOMATION_ACTION_TYPES,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const wrapper = createComponent([methodsMixin], data);
|
||||||
|
expect(wrapper.vm.showActionInput('send_email_to_team')).toBeFalsy();
|
||||||
|
expect(wrapper.vm.showActionInput('send_message')).toBeFalsy();
|
||||||
|
expect(wrapper.vm.showActionInput('send_webhook_event')).toBeTruthy();
|
||||||
|
expect(wrapper.vm.showActionInput('resolve_conversation')).toBeFalsy();
|
||||||
|
expect(wrapper.vm.showActionInput('add_label')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resetAction resets the action to default state', () => {
|
||||||
|
const data = () => {
|
||||||
|
return {
|
||||||
|
automation,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const wrapper = createComponent([methodsMixin], data);
|
||||||
|
wrapper.vm.resetAction(0);
|
||||||
|
expect(automation.actions[0].action_params).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('manifestConditions resets the action to default state', () => {
|
||||||
|
const data = () => {
|
||||||
|
return {
|
||||||
|
automation: {},
|
||||||
|
allCustomAttributes: customAttributes,
|
||||||
|
automationTypes: AUTOMATIONS,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const methods = {
|
||||||
|
getConditionDropdownValues() {
|
||||||
|
return statusFilterOptions;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const manifestedConditions = [
|
||||||
|
{
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
id: 'open',
|
||||||
|
name: 'Open',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
attribute_key: 'status',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
query_operator: 'and',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const wrapper = createComponent([methodsMixin], data, {}, methods);
|
||||||
|
expect(wrapper.vm.manifestConditions(savedAutomation)).toEqual(
|
||||||
|
manifestedConditions
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generateActionsArray return the manifested actions array', () => {
|
||||||
|
const data = () => {
|
||||||
|
return {
|
||||||
|
automationActionTypes: AUTOMATION_ACTION_TYPES,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const computed = {
|
||||||
|
labels() {
|
||||||
|
return labels;
|
||||||
|
},
|
||||||
|
teams() {
|
||||||
|
return teams;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const methods = {
|
||||||
|
getActionDropdownValues() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'testlabel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'snoozes',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const testAction = {
|
||||||
|
action_name: 'add_label',
|
||||||
|
action_params: [2],
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectedActionArray = [
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'testlabel',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const wrapper = createComponent([methodsMixin], data, computed, methods);
|
||||||
|
expect(wrapper.vm.generateActionsArray(testAction)).toEqual(
|
||||||
|
expectedActionArray
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('manifestActions manifest the received action and generate the correct array', () => {
|
||||||
|
const data = () => {
|
||||||
|
return {
|
||||||
|
automation: {},
|
||||||
|
allCustomAttributes: customAttributes,
|
||||||
|
automationTypes: AUTOMATIONS,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const methods = {
|
||||||
|
generateActionsArray() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'testlabel',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const expectedActions = [
|
||||||
|
{
|
||||||
|
action_name: 'add_label',
|
||||||
|
action_params: [
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'testlabel',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const wrapper = createComponent([methodsMixin], data, {}, methods);
|
||||||
|
expect(wrapper.vm.manifestActions(savedAutomation)).toEqual(
|
||||||
|
expectedActions
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getActionDropdownValues returns Action dropdown Values', () => {
|
||||||
|
const data = () => {
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
const computed = {
|
||||||
|
labels() {
|
||||||
|
return labels;
|
||||||
|
},
|
||||||
|
teams() {
|
||||||
|
return teams;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const expectedActionDropdownValues = [
|
||||||
|
{ id: 'testlabel', name: 'testlabel' },
|
||||||
|
{ id: 'snoozes', name: 'snoozes' },
|
||||||
|
];
|
||||||
|
const wrapper = createComponent([methodsMixin], data, computed);
|
||||||
|
expect(wrapper.vm.getActionDropdownValues('add_label')).toEqual(
|
||||||
|
expectedActionDropdownValues
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('automationValidationsMixin', () => {
|
||||||
|
it('automationValidationsMixin is present', () => {
|
||||||
|
const data = () => {
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
const wrapper = createComponent([validationsMixin], data, {}, {});
|
||||||
|
expect(typeof wrapper.vm.$options.validations).toBe('object');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -34,9 +34,10 @@ class AutomationRuleListener < BaseListener
|
|||||||
end
|
end
|
||||||
|
|
||||||
def message_created(event_obj)
|
def message_created(event_obj)
|
||||||
return if performed_by_automation?(event_obj)
|
|
||||||
|
|
||||||
message = event_obj.data[:message]
|
message = event_obj.data[:message]
|
||||||
|
|
||||||
|
return if ignore_message_created_event?(event_obj)
|
||||||
|
|
||||||
account = message.try(:account)
|
account = message.try(:account)
|
||||||
changed_attributes = event_obj.data[:changed_attributes]
|
changed_attributes = event_obj.data[:changed_attributes]
|
||||||
|
|
||||||
@@ -68,4 +69,9 @@ class AutomationRuleListener < BaseListener
|
|||||||
def performed_by_automation?(event_obj)
|
def performed_by_automation?(event_obj)
|
||||||
event_obj.data[:performed_by].present? && event_obj.data[:performed_by].instance_of?(AutomationRule)
|
event_obj.data[:performed_by].present? && event_obj.data[:performed_by].instance_of?(AutomationRule)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def ignore_message_created_event?(event_obj)
|
||||||
|
message = event_obj.data[:message]
|
||||||
|
performed_by_automation?(event_obj) || message.activity?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ module ActivityMessageHandler
|
|||||||
def create_activity
|
def create_activity
|
||||||
user_name = Current.user.name if Current.user.present?
|
user_name = Current.user.name if Current.user.present?
|
||||||
status_change_activity(user_name) if saved_change_to_status?
|
status_change_activity(user_name) if saved_change_to_status?
|
||||||
create_label_change(user_name) if saved_change_to_label_list?
|
create_label_change(label_activity_message_ownner(user_name)) if saved_change_to_label_list?
|
||||||
end
|
end
|
||||||
|
|
||||||
def status_change_activity(user_name)
|
def status_change_activity(user_name)
|
||||||
@@ -107,4 +107,9 @@ module ActivityMessageHandler
|
|||||||
content = generate_assignee_change_activity_content(user_name)
|
content = generate_assignee_change_activity_content(user_name)
|
||||||
::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 label_activity_message_ownner(user_name)
|
||||||
|
user_name = 'Automation System' if !user_name && Current.executed_by.present?
|
||||||
|
user_name
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -262,6 +262,8 @@ class Conversation < ApplicationRecord
|
|||||||
previous_labels, current_labels = previous_changes[:label_list]
|
previous_labels, current_labels = previous_changes[:label_list]
|
||||||
return unless (previous_labels.is_a? Array) && (current_labels.is_a? Array)
|
return unless (previous_labels.is_a? Array) && (current_labels.is_a? Array)
|
||||||
|
|
||||||
|
dispatcher_dispatch(CONVERSATION_UPDATED, previous_changes)
|
||||||
|
|
||||||
create_label_added(user_name, current_labels - previous_labels)
|
create_label_added(user_name, current_labels - previous_labels)
|
||||||
create_label_removed(user_name, previous_labels - current_labels)
|
create_label_removed(user_name, previous_labels - current_labels)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ class Conversations::EventDataPresenter < SimpleDelegator
|
|||||||
[messages.chat.last&.push_event_data].compact
|
[messages.chat.last&.push_event_data].compact
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def label_list
|
||||||
|
labels.pluck(:id, :name)
|
||||||
|
end
|
||||||
|
|
||||||
def push_meta
|
def push_meta
|
||||||
{
|
{
|
||||||
sender: contact.push_event_data,
|
sender: contact.push_event_data,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
class ActionService
|
class ActionService
|
||||||
def initialize(conversation)
|
def initialize(conversation)
|
||||||
@conversation = conversation
|
@conversation = conversation.reload
|
||||||
end
|
end
|
||||||
|
|
||||||
def mute_conversation(_params)
|
def mute_conversation(_params)
|
||||||
@@ -22,7 +22,7 @@ class ActionService
|
|||||||
def add_label(labels)
|
def add_label(labels)
|
||||||
return if labels.empty?
|
return if labels.empty?
|
||||||
|
|
||||||
@conversation.add_labels(labels)
|
@conversation.reload.add_labels(labels)
|
||||||
end
|
end
|
||||||
|
|
||||||
def assign_best_agent(agent_ids = [])
|
def assign_best_agent(agent_ids = [])
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ class AutomationRules::ActionService < ActionService
|
|||||||
|
|
||||||
def perform
|
def perform
|
||||||
@rule.actions.each do |action|
|
@rule.actions.each do |action|
|
||||||
|
@conversation.reload
|
||||||
action = action.with_indifferent_access
|
action = action.with_indifferent_access
|
||||||
begin
|
begin
|
||||||
send(action[:action_name], action[:action_params])
|
send(action[:action_name], action[:action_params])
|
||||||
|
|||||||
@@ -54,6 +54,14 @@ class FilterService
|
|||||||
query_hash['values'].map { |x| Message.message_types[x.to_sym] }
|
query_hash['values'].map { |x| Message.message_types[x.to_sym] }
|
||||||
when 'content'
|
when 'content'
|
||||||
string_filter_values(query_hash)
|
string_filter_values(query_hash)
|
||||||
|
else
|
||||||
|
case_insensitive_values(query_hash)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def case_insensitive_values(query_hash)
|
||||||
|
if query_hash['custom_attribute_type'].present? && query_hash['values'][0].is_a?(String)
|
||||||
|
string_filter_values(query_hash)
|
||||||
else
|
else
|
||||||
query_hash['values']
|
query_hash['values']
|
||||||
end
|
end
|
||||||
@@ -91,23 +99,39 @@ class FilterService
|
|||||||
end
|
end
|
||||||
|
|
||||||
def custom_attribute_query(query_hash, custom_attribute_type, current_index)
|
def custom_attribute_query(query_hash, custom_attribute_type, current_index)
|
||||||
attribute_key = query_hash[:attribute_key]
|
@attribute_key = query_hash[:attribute_key]
|
||||||
query_operator = query_hash[:query_operator]
|
@custom_attribute_type = custom_attribute_type
|
||||||
attribute_model = custom_attribute_type.presence || self.class::ATTRIBUTE_MODEL
|
|
||||||
|
|
||||||
attribute_type = custom_attribute(attribute_key, @account, attribute_model).try(:attribute_display_type)
|
attribute_data_type
|
||||||
filter_operator_value = filter_operation(query_hash, current_index)
|
|
||||||
attribute_data_type = self.class::ATTRIBUTE_TYPES[attribute_type]
|
|
||||||
|
|
||||||
return ' ' if @custom_attribute.blank?
|
return ' ' if @custom_attribute.blank?
|
||||||
|
|
||||||
table_name = attribute_model == 'conversation_attribute' ? 'conversations' : 'contacts'
|
build_custom_attr_query(query_hash, current_index)
|
||||||
|
|
||||||
" LOWER(#{table_name}.custom_attributes ->> '#{attribute_key}')::#{attribute_data_type} #{filter_operator_value} #{query_operator} "
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def attribute_model
|
||||||
|
@attribute_model = @custom_attribute_type.presence || self.class::ATTRIBUTE_MODEL
|
||||||
|
end
|
||||||
|
|
||||||
|
def attribute_data_type
|
||||||
|
attribute_type = custom_attribute(@attribute_key, @account, attribute_model).try(:attribute_display_type)
|
||||||
|
@attribute_data_type = self.class::ATTRIBUTE_TYPES[attribute_type]
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_custom_attr_query(query_hash, current_index)
|
||||||
|
filter_operator_value = filter_operation(query_hash, current_index)
|
||||||
|
query_operator = query_hash[:query_operator]
|
||||||
|
table_name = attribute_model == 'conversation_attribute' ? 'conversations' : 'contacts'
|
||||||
|
|
||||||
|
if attribute_data_type == 'text'
|
||||||
|
" LOWER(#{table_name}.custom_attributes ->> '#{@attribute_key}')::#{attribute_data_type} #{filter_operator_value} #{query_operator} "
|
||||||
|
else
|
||||||
|
" (#{table_name}.custom_attributes ->> '#{@attribute_key}')::#{attribute_data_type} #{filter_operator_value} #{query_operator} "
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def custom_attribute(attribute_key, account, custom_attribute_type)
|
def custom_attribute(attribute_key, account, custom_attribute_type)
|
||||||
current_account = account || Current.account
|
current_account = account || Current.account
|
||||||
attribute_model = custom_attribute_type.presence || self.class::ATTRIBUTE_MODEL
|
attribute_model = custom_attribute_type.presence || self.class::ATTRIBUTE_MODEL
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ describe ActionCableListener do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'broadcast event with label data' do
|
it 'broadcast event with label data' do
|
||||||
expect(conversation.push_event_data[:labels]).to eq(conversation.label_list)
|
expect(conversation.reload.push_event_data[:labels]).to eq(conversation.labels.pluck(:id, :name))
|
||||||
|
|
||||||
expect(ActionCableBroadcastJob).to receive(:perform_later).with(
|
expect(ActionCableBroadcastJob).to receive(:perform_later).with(
|
||||||
[agent.pubsub_token, admin.pubsub_token, conversation.contact_inbox.pubsub_token],
|
[agent.pubsub_token, admin.pubsub_token, conversation.contact_inbox.pubsub_token],
|
||||||
|
|||||||
@@ -17,6 +17,17 @@ describe AutomationRuleListener do
|
|||||||
attribute_model: 'contact_attribute',
|
attribute_model: 'contact_attribute',
|
||||||
attribute_display_type: 'list',
|
attribute_display_type: 'list',
|
||||||
attribute_values: %w[regular platinum gold])
|
attribute_values: %w[regular platinum gold])
|
||||||
|
create(:custom_attribute_definition,
|
||||||
|
attribute_key: 'priority',
|
||||||
|
account: account,
|
||||||
|
attribute_model: 'conversation_attribute',
|
||||||
|
attribute_display_type: 'list',
|
||||||
|
attribute_values: %w[P0 P1 P2])
|
||||||
|
create(:custom_attribute_definition,
|
||||||
|
attribute_key: 'cloud_customer',
|
||||||
|
attribute_display_type: 'checkbox',
|
||||||
|
account: account,
|
||||||
|
attribute_model: 'contact_attribute')
|
||||||
create(:team_member, user: user_1, team: team)
|
create(:team_member, user: user_1, team: team)
|
||||||
create(:team_member, user: user_2, team: team)
|
create(:team_member, user: user_2, team: team)
|
||||||
create(:account_user, user: user_2, account: account)
|
create(:account_user, user: user_2, account: account)
|
||||||
@@ -260,6 +271,51 @@ describe AutomationRuleListener do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when rule matches based on custom_attributes' do
|
||||||
|
before do
|
||||||
|
conversation.update!(custom_attributes: { priority: 'P2' })
|
||||||
|
conversation.contact.update!(custom_attributes: { cloud_customer: false })
|
||||||
|
|
||||||
|
automation_rule.update!(
|
||||||
|
event_name: 'conversation_updated',
|
||||||
|
name: 'Priority customer check',
|
||||||
|
description: 'Add labels, assign team after conversation updated',
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
attribute_key: 'priority',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: ['P2'],
|
||||||
|
custom_attribute_type: 'conversation_attribute',
|
||||||
|
query_operator: 'AND'
|
||||||
|
}.with_indifferent_access,
|
||||||
|
{
|
||||||
|
attribute_key: 'cloud_customer',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: [false],
|
||||||
|
custom_attribute_type: 'contact_attribute',
|
||||||
|
query_operator: nil
|
||||||
|
}.with_indifferent_access
|
||||||
|
]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'triggers automation rule to assign team' do
|
||||||
|
expect(conversation.team_id).not_to eq(team.id)
|
||||||
|
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([])
|
||||||
|
listener.conversation_updated(event)
|
||||||
|
conversation.reload
|
||||||
|
|
||||||
|
expect(conversation.labels.pluck(:name)).to contain_exactly('support', 'priority_customer')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when conditions based on attribute_changed' do
|
context 'when conditions based on attribute_changed' do
|
||||||
before do
|
before do
|
||||||
automation_rule.update!(
|
automation_rule.update!(
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ RSpec.describe Conversation, type: :model do
|
|||||||
notifiable_assignee_change: false,
|
notifiable_assignee_change: false,
|
||||||
changed_attributes: changed_attributes,
|
changed_attributes: changed_attributes,
|
||||||
performed_by: nil
|
performed_by: nil
|
||||||
)
|
).exactly(2).times
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'runs after_update callbacks' do
|
it 'runs after_update callbacks' do
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ RSpec.describe Conversations::EventDataPresenter do
|
|||||||
},
|
},
|
||||||
id: conversation.display_id,
|
id: conversation.display_id,
|
||||||
messages: [],
|
messages: [],
|
||||||
inbox_id: conversation.inbox_id,
|
|
||||||
labels: [],
|
labels: [],
|
||||||
|
inbox_id: conversation.inbox_id,
|
||||||
status: conversation.status,
|
status: conversation.status,
|
||||||
contact_inbox: conversation.contact_inbox,
|
contact_inbox: conversation.contact_inbox,
|
||||||
can_reply: conversation.can_reply?,
|
can_reply: conversation.can_reply?,
|
||||||
|
|||||||
Reference in New Issue
Block a user