feat: Add the ability to add new automation rule (#3459)
* Add automation modal * Fix the v-model for automations * Actions and Condition dropdowns for automations * Fix merge conflicts * Handle event change and confirmation * Appends new action * Removes actions * Automation api integration * Api integration for creating automations * Registers vuex module to the global store * Automations table * Updarted labels and actions * Integrate automation api * Fixes the mutation error - removed the data key wrapper * Fixed the automation condition models to work with respective event types * Remove temporary fixes added to the api request * Displa timestamp and automation status values * Added the clone buton * Removed uncessary helper method * Specs for automations * Handle WIP code * Remove the payload wrap * Fix the action query payload * Removed unnecessary files * Disabled Automations routes * Ability to delete automations * Fix specs * Fixed merge conflicts Co-authored-by: Fayaz Ahmed <15716057+fayazara@users.noreply.github.com> Co-authored-by: fayazara <fayazara@gmail.com> Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This commit is contained in:
9
app/javascript/dashboard/api/automation.js
Normal file
9
app/javascript/dashboard/api/automation.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import ApiClient from './ApiClient';
|
||||
|
||||
class AutomationsAPI extends ApiClient {
|
||||
constructor() {
|
||||
super('automation_rules', { accountScoped: true });
|
||||
}
|
||||
}
|
||||
|
||||
export default new AutomationsAPI();
|
||||
14
app/javascript/dashboard/api/specs/automation.spec.js
Normal file
14
app/javascript/dashboard/api/specs/automation.spec.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import automations from '../automation';
|
||||
import ApiClient from '../ApiClient';
|
||||
|
||||
describe('#AutomationsAPI', () => {
|
||||
it('creates correct instance', () => {
|
||||
expect(automations).toBeInstanceOf(ApiClient);
|
||||
expect(automations).toHaveProperty('get');
|
||||
expect(automations).toHaveProperty('show');
|
||||
expect(automations).toHaveProperty('create');
|
||||
expect(automations).toHaveProperty('update');
|
||||
expect(automations).toHaveProperty('delete');
|
||||
expect(automations.url).toBe('/api/v1/automation_rules');
|
||||
});
|
||||
});
|
||||
@@ -70,7 +70,7 @@ const settings = accountId => ({
|
||||
toStateName: 'attributes_list',
|
||||
},
|
||||
{
|
||||
icon: 'autocorrect',
|
||||
icon: 'automation',
|
||||
label: 'AUTOMATION',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(`accounts/${accountId}/settings/automation/list`),
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<div
|
||||
class="filter"
|
||||
:class="{ error: v.action_params.$dirty && v.action_params.$error }"
|
||||
>
|
||||
<div class="filter-inputs">
|
||||
<select
|
||||
v-model="action_name"
|
||||
class="action__question"
|
||||
@change="resetFilter()"
|
||||
>
|
||||
<option
|
||||
v-for="attribute in actionTypes"
|
||||
:key="attribute.key"
|
||||
:value="attribute.key"
|
||||
>
|
||||
{{ attribute.label }}
|
||||
</option>
|
||||
</select>
|
||||
<div class="filter__answer--wrap">
|
||||
<div class="multiselect-wrap--small">
|
||||
<multiselect
|
||||
v-model="action_params"
|
||||
track-by="id"
|
||||
label="name"
|
||||
:placeholder="'Select'"
|
||||
:multiple="true"
|
||||
selected-label
|
||||
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||
deselect-label=""
|
||||
:max-height="160"
|
||||
:options="dropdownValues"
|
||||
:allow-empty="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<woot-button
|
||||
icon="dismiss"
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
@click="removeAction"
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
v-if="v.action_params.$dirty && v.action_params.$error"
|
||||
class="filter-error"
|
||||
>
|
||||
{{ $t('FILTER.EMPTY_VALUE_ERROR') }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => null,
|
||||
},
|
||||
actionTypes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
dropdownValues: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
v: {
|
||||
type: Object,
|
||||
default: () => null,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
action_name: {
|
||||
get() {
|
||||
if (!this.value) return null;
|
||||
return this.value.action_name;
|
||||
},
|
||||
set(value) {
|
||||
const payload = this.value || {};
|
||||
this.$emit('input', { ...payload, action_name: value });
|
||||
},
|
||||
},
|
||||
action_params: {
|
||||
get() {
|
||||
if (!this.value) return null;
|
||||
return this.value.action_params;
|
||||
},
|
||||
set(value) {
|
||||
const payload = this.value || {};
|
||||
this.$emit('input', { ...payload, action_params: value });
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
removeAction() {
|
||||
this.$emit('removeAction');
|
||||
},
|
||||
resetFilter() {
|
||||
this.$emit('resetFilter');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.filter {
|
||||
background: var(--color-background);
|
||||
padding: var(--space-small);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-medium);
|
||||
margin-bottom: var(--space-small);
|
||||
}
|
||||
|
||||
.filter.error {
|
||||
background: var(--r-50);
|
||||
}
|
||||
|
||||
.filter-inputs {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.filter-error {
|
||||
color: var(--r-500);
|
||||
display: block;
|
||||
margin: var(--space-smaller) 0;
|
||||
}
|
||||
|
||||
.action__question,
|
||||
.filter__operator {
|
||||
margin-bottom: var(--space-zero);
|
||||
margin-right: var(--space-smaller);
|
||||
}
|
||||
|
||||
.action__question {
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.filter__answer--wrap {
|
||||
margin-right: var(--space-smaller);
|
||||
flex-grow: 1;
|
||||
|
||||
input {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.filter__answer {
|
||||
&.answer--text-input {
|
||||
margin-bottom: var(--space-zero);
|
||||
}
|
||||
}
|
||||
|
||||
.filter__join-operator-wrap {
|
||||
position: relative;
|
||||
z-index: var(--z-index-twenty);
|
||||
margin: var(--space-zero);
|
||||
}
|
||||
|
||||
.filter__join-operator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
margin: var(--space-one) var(--space-zero);
|
||||
|
||||
.operator__line {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.operator__select {
|
||||
position: relative;
|
||||
width: auto;
|
||||
margin-bottom: var(--space-zero) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect {
|
||||
margin-bottom: var(--space-zero);
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="filters">
|
||||
<div class="filter">
|
||||
<div class="filter" :class="{ error: v.values.$dirty && v.values.$error }">
|
||||
<div class="filter-inputs">
|
||||
<select
|
||||
v-if="groupedFilters"
|
||||
@@ -163,7 +163,7 @@ export default {
|
||||
},
|
||||
groupedFilters: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
default: false,
|
||||
},
|
||||
filterGroups: {
|
||||
type: Array,
|
||||
@@ -230,6 +230,10 @@ export default {
|
||||
border-radius: var(--border-radius-medium);
|
||||
}
|
||||
|
||||
.filter.error {
|
||||
background: var(--r-50);
|
||||
}
|
||||
|
||||
.filter-inputs {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
17
app/javascript/dashboard/helper/actionQueryGenerator.js
Normal file
17
app/javascript/dashboard/helper/actionQueryGenerator.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const generatePayload = data => {
|
||||
let payload = data.map(item => {
|
||||
if (Array.isArray(item.action_params)) {
|
||||
item.action_params = item.action_params.map(val => val.id);
|
||||
} else if (typeof item.values === 'object') {
|
||||
item.action_params = [item.action_params.id];
|
||||
} else if (!item.action_params) {
|
||||
item.action_params = [];
|
||||
} else {
|
||||
item.action_params = [item.action_params];
|
||||
}
|
||||
return item;
|
||||
});
|
||||
return payload;
|
||||
};
|
||||
|
||||
export default generatePayload;
|
||||
@@ -0,0 +1,41 @@
|
||||
import actionQueryGenerator from '../actionQueryGenerator';
|
||||
|
||||
const testData = [
|
||||
{
|
||||
action_name: 'add_label',
|
||||
action_params: [{ id: 'testlabel', name: 'testlabel' }],
|
||||
},
|
||||
{
|
||||
action_name: 'assign_team',
|
||||
action_params: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'sales team',
|
||||
description: 'This is our internal sales team',
|
||||
allow_auto_assign: true,
|
||||
account_id: 1,
|
||||
is_member: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const finalResult = [
|
||||
{
|
||||
action_name: 'add_label',
|
||||
action_params: ['testlabel'],
|
||||
},
|
||||
{
|
||||
action_name: 'assign_team',
|
||||
action_params: [1],
|
||||
},
|
||||
];
|
||||
|
||||
describe('#actionQueryGenerator', () => {
|
||||
it('returns the correct format of filter query', () => {
|
||||
expect(actionQueryGenerator(testData)).toEqual(finalResult);
|
||||
expect(
|
||||
actionQueryGenerator(testData).every(i => Array.isArray(i.action_params))
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,82 @@
|
||||
{
|
||||
"AUTOMATION": {
|
||||
"HEADER": "Automation",
|
||||
"HEADER_BTN_TXT": "Add Automation Rule"
|
||||
"HEADER_BTN_TXT": "Add Automation Rule",
|
||||
"LOADING": "Fetching automation rules",
|
||||
"SIDEBAR_TXT": "<p><b>Automation Rules</b> <p>Automation can replace and automate existing processes that require manual effort. You can do many things with automation, including adding labels and assigning conversation to the best agent. So the team focuses on what they do best and spends more little time on manual tasks.</p>",
|
||||
"ADD": {
|
||||
"TITLE": "Add Automation Rule",
|
||||
"SUBMIT": "Create",
|
||||
"CANCEL_BUTTON_TEXT": "Cancel",
|
||||
"FORM": {
|
||||
"NAME": {
|
||||
"LABEL": "Rule Name",
|
||||
"PLACEHOLDER": "Enter rule name",
|
||||
"ERROR": "Name is required"
|
||||
},
|
||||
"DESC": {
|
||||
"LABEL": "Description",
|
||||
"PLACEHOLDER": "Enter rule description",
|
||||
"ERROR": "Description is required"
|
||||
},
|
||||
"EVENT": {
|
||||
"LABEL": "Event",
|
||||
"PLACEHOLDER": "Please select one",
|
||||
"ERROR": "Event is required"
|
||||
},
|
||||
"CONDITIONS": {
|
||||
"LABEL": "Conditions"
|
||||
},
|
||||
"ACTIONS": {
|
||||
"LABEL": "Actions"
|
||||
}
|
||||
},
|
||||
"CONDITION_BUTTON_LABEL": "Add Condition",
|
||||
"ACTION_BUTTON_LABEL": "Add Action",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Automation rule added successfully",
|
||||
"ERROR_MESSAGE": "Could not able to create a automation rule, Please try again later"
|
||||
}
|
||||
},
|
||||
"LIST": {
|
||||
"TABLE_HEADER": [
|
||||
"Name",
|
||||
"Description",
|
||||
"Active",
|
||||
"Created on"
|
||||
],
|
||||
"404": "No automation rules found"
|
||||
},
|
||||
"DELETE": {
|
||||
"TITLE": "Delete Automation Rule",
|
||||
"SUBMIT": "Delete",
|
||||
"CANCEL_BUTTON_TEXT": "Cancel",
|
||||
"CONFIRM": {
|
||||
"TITLE": "Confirm Deletion",
|
||||
"MESSAGE": "Are you sure to delete ",
|
||||
"YES": "Yes, Delete ",
|
||||
"NO": "No, Keep "
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Automation rule deleted successfully",
|
||||
"ERROR_MESSAGE": "Could not able to delete a automation rule, Please try again later"
|
||||
}
|
||||
},
|
||||
"EDIT": {
|
||||
"TITLE": "Edit Automation Rule",
|
||||
"SUBMIT": "Edit",
|
||||
"CANCEL_BUTTON_TEXT": "Cancel",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Automation rule updated successfully",
|
||||
"ERROR_MESSAGE": "Could not update automation rule, Please try again later"
|
||||
}
|
||||
},
|
||||
"FORM": {
|
||||
"EDIT": "Edit",
|
||||
"CREATE": "Create",
|
||||
"DELETE": "Delete",
|
||||
"CANCEL": "Cancel",
|
||||
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,446 @@
|
||||
<template>
|
||||
<div class="column">
|
||||
<woot-modal-header :header-title="$t('AUTOMATION.ADD.TITLE')" />
|
||||
<div class="row modal-content">
|
||||
<div class="medium-12 columns">
|
||||
<woot-input
|
||||
v-model="automation.name"
|
||||
:label="$t('AUTOMATION.ADD.FORM.NAME.LABEL')"
|
||||
type="text"
|
||||
:class="{ error: $v.automation.name.$error }"
|
||||
:error="
|
||||
$v.automation.name.$error
|
||||
? $t('AUTOMATION.ADD.FORM.NAME.ERROR')
|
||||
: ''
|
||||
"
|
||||
:placeholder="$t('AUTOMATION.ADD.FORM.NAME.PLACEHOLDER')"
|
||||
@blur="$v.automation.name.$touch"
|
||||
/>
|
||||
<woot-input
|
||||
v-model="automation.description"
|
||||
:label="$t('AUTOMATION.ADD.FORM.DESC.LABEL')"
|
||||
type="text"
|
||||
:class="{ error: $v.automation.description.$error }"
|
||||
:error="
|
||||
$v.automation.description.$error
|
||||
? $t('AUTOMATION.ADD.FORM.DESC.ERROR')
|
||||
: ''
|
||||
"
|
||||
:placeholder="$t('AUTOMATION.ADD.FORM.DESC.PLACEHOLDER')"
|
||||
@blur="$v.automation.description.$touch"
|
||||
/>
|
||||
<div class="event_wrapper">
|
||||
<label :class="{ error: $v.automation.event_name.$error }">
|
||||
{{ $t('AUTOMATION.ADD.FORM.EVENT.LABEL') }}
|
||||
<select v-model="automation.event_name" @change="onEventChange()">
|
||||
<option
|
||||
v-for="event in automationRuleEvents"
|
||||
:key="event.key"
|
||||
:value="event.key"
|
||||
>
|
||||
{{ event.value }}
|
||||
</option>
|
||||
</select>
|
||||
<span v-if="$v.automation.event_name.$error" class="message">
|
||||
{{ $t('AUTOMATION.ADD.FORM.EVENT.ERROR') }}
|
||||
</span>
|
||||
</label>
|
||||
<p v-if="hasAutomationMutated" class="info-message">
|
||||
{{ $t('AUTOMATION.FORM.RESET_MESSAGE') }}
|
||||
</p>
|
||||
</div>
|
||||
<!-- // Conditions Start -->
|
||||
<section>
|
||||
<label>
|
||||
{{ $t('AUTOMATION.ADD.FORM.CONDITIONS.LABEL') }}
|
||||
</label>
|
||||
<div class="medium-12 columns filters-wrap">
|
||||
<filter-input-box
|
||||
v-for="(condition, i) in automation.conditions"
|
||||
:key="i"
|
||||
v-model="automation.conditions[i]"
|
||||
:filter-attributes="getAttributes(automation.event_name)"
|
||||
:input-type="getInputType(automation.conditions[i].attribute_key)"
|
||||
:operators="getOperators(automation.conditions[i].attribute_key)"
|
||||
:dropdown-values="
|
||||
getConditionDropdownValues(
|
||||
automation.conditions[i].attribute_key
|
||||
)
|
||||
"
|
||||
:show-query-operator="i !== automation.conditions.length - 1"
|
||||
:v="$v.automation.conditions.$each[i]"
|
||||
@resetFilter="resetFilter(i, automation.conditions[i])"
|
||||
@removeFilter="removeFilter(i)"
|
||||
/>
|
||||
<div class="filter-actions">
|
||||
<woot-button
|
||||
icon="add"
|
||||
color-scheme="success"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
@click="appendNewCondition"
|
||||
>
|
||||
{{ $t('AUTOMATION.ADD.CONDITION_BUTTON_LABEL') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- // Conditions End -->
|
||||
<!-- // Actions Start -->
|
||||
<section>
|
||||
<label>
|
||||
{{ $t('AUTOMATION.ADD.FORM.ACTIONS.LABEL') }}
|
||||
</label>
|
||||
<div class="medium-12 columns filters-wrap">
|
||||
<automation-action-input
|
||||
v-for="(action, i) in automation.actions"
|
||||
:key="i"
|
||||
v-model="automation.actions[i]"
|
||||
:action-types="automationActionTypes"
|
||||
:dropdown-values="
|
||||
getActionDropdownValues(automation.actions[i].action_name)
|
||||
"
|
||||
:v="$v.automation.actions.$each[i]"
|
||||
@removeAction="removeAction(i)"
|
||||
/>
|
||||
<div class="filter-actions">
|
||||
<woot-button
|
||||
icon="add"
|
||||
color-scheme="success"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
@click="appendNewAction"
|
||||
>
|
||||
{{ $t('AUTOMATION.ADD.ACTION_BUTTON_LABEL') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- // Actions End -->
|
||||
<div class="medium-12 columns">
|
||||
<div class="modal-footer justify-content-end w-full">
|
||||
<woot-button class="button clear" @click.prevent="onClose">
|
||||
{{ $t('AUTOMATION.ADD.CANCEL_BUTTON_TEXT') }}
|
||||
</woot-button>
|
||||
<woot-button @click="submitAutomation">
|
||||
{{ $t('AUTOMATION.ADD.SUBMIT') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import { required, requiredIf } from 'vuelidate/lib/validators';
|
||||
import filterInputBox from 'dashboard/components/widgets/FilterInput/Index.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 {
|
||||
AUTOMATION_RULE_EVENTS,
|
||||
AUTOMATION_ACTION_TYPES,
|
||||
AUTOMATIONS,
|
||||
} from './constants';
|
||||
import filterQueryGenerator from 'dashboard/helper/filterQueryGenerator.js';
|
||||
import actionQueryGenerator from 'dashboard/helper/actionQueryGenerator.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
filterInputBox,
|
||||
automationActionInput,
|
||||
},
|
||||
mixins: [alertMixin],
|
||||
props: {
|
||||
onClose: {
|
||||
type: Function,
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
automationTypes: AUTOMATIONS,
|
||||
automationRuleEvent: AUTOMATION_RULE_EVENTS[0].key,
|
||||
automationRuleEvents: AUTOMATION_RULE_EVENTS,
|
||||
automationActionTypes: AUTOMATION_ACTION_TYPES,
|
||||
automationMutated: false,
|
||||
show: true,
|
||||
automation: {
|
||||
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,
|
||||
};
|
||||
},
|
||||
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() {
|
||||
if (
|
||||
this.automation.conditions[0].values ||
|
||||
this.automation.actions[0].action_params.length
|
||||
)
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
},
|
||||
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_message':
|
||||
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() {
|
||||
this.automation.conditions.push({
|
||||
attribute_key: 'status',
|
||||
filter_operator: 'equal_to',
|
||||
values: '',
|
||||
query_operator: 'and',
|
||||
});
|
||||
},
|
||||
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;
|
||||
this.automation.conditions[
|
||||
this.automation.conditions.length - 1
|
||||
].query_operator = null;
|
||||
this.automation.conditions = filterQueryGenerator(
|
||||
this.automation.conditions
|
||||
).payload;
|
||||
this.automation.actions = actionQueryGenerator(this.automation.actions);
|
||||
this.$emit('saveAutomation', this.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 = '';
|
||||
},
|
||||
showUserInput(operatorType) {
|
||||
if (operatorType === 'is_present' || operatorType === 'is_not_present')
|
||||
return false;
|
||||
return true;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.filters-wrap {
|
||||
padding: var(--space-normal);
|
||||
border-radius: var(--border-radius-large);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-background-light);
|
||||
margin-bottom: var(--space-normal);
|
||||
}
|
||||
|
||||
.filter-actions {
|
||||
margin-top: var(--space-normal);
|
||||
}
|
||||
.event_wrapper {
|
||||
select {
|
||||
margin: 0;
|
||||
}
|
||||
.info-message {
|
||||
font-size: var(--font-size-mini);
|
||||
color: #868686;
|
||||
text-align: right;
|
||||
}
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
</style>
|
||||
@@ -4,9 +4,209 @@
|
||||
color-scheme="success"
|
||||
class-names="button--fixed-right-top"
|
||||
icon="add-circle"
|
||||
@click="openAddPopup()"
|
||||
>
|
||||
{{ $t('AUTOMATION.HEADER_BTN_TXT') }}
|
||||
</woot-button>
|
||||
<div class="row">
|
||||
<div class="small-8 columns with-right-space">
|
||||
<p
|
||||
v-if="!uiFlags.isFetching && !records.length"
|
||||
class="no-items-error-message"
|
||||
>
|
||||
{{ $t('AUTOMATION.LIST.404') }}
|
||||
</p>
|
||||
<woot-loading-state
|
||||
v-if="uiFlags.isFetching"
|
||||
:message="$t('AUTOMATION.LOADING')"
|
||||
/>
|
||||
<table v-if="!uiFlags.isFetching && records.length" class="woot-table">
|
||||
<thead>
|
||||
<th
|
||||
v-for="thHeader in $t('AUTOMATION.LIST.TABLE_HEADER')"
|
||||
:key="thHeader"
|
||||
>
|
||||
{{ thHeader }}
|
||||
</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(automation, index) in records" :key="index">
|
||||
<td>{{ automation.name }}</td>
|
||||
<td>{{ automation.description }}</td>
|
||||
<td>
|
||||
<fluent-icon
|
||||
v-if="automation.active"
|
||||
icon="checkmark-square"
|
||||
type="solid"
|
||||
/>
|
||||
<fluent-icon v-else icon="square" />
|
||||
</td>
|
||||
<td>{{ readableTime(automation.created_on) }}</td>
|
||||
<td class="button-wrapper">
|
||||
<!-- <woot-button
|
||||
v-tooltip.top="$t('AUTOMATION.FORM.EDIT')"
|
||||
variant="smooth"
|
||||
size="tiny"
|
||||
color-scheme="secondary"
|
||||
class-names="grey-btn"
|
||||
:is-loading="loading[automation.id]"
|
||||
icon="edit"
|
||||
@click="openEditPopup(automation)"
|
||||
>
|
||||
</woot-button>
|
||||
<woot-button
|
||||
v-tooltip.top="'Clone'"
|
||||
variant="smooth"
|
||||
size="tiny"
|
||||
color-scheme="primary"
|
||||
class-names="grey-btn"
|
||||
:is-loading="loading[automation.id]"
|
||||
icon="copy"
|
||||
@click="openEditPopup(automation)"
|
||||
>
|
||||
</woot-button> -->
|
||||
<woot-button
|
||||
v-tooltip.top="$t('AUTOMATION.FORM.DELETE')"
|
||||
variant="smooth"
|
||||
color-scheme="alert"
|
||||
size="tiny"
|
||||
icon="dismiss-circle"
|
||||
class-names="grey-btn"
|
||||
:is-loading="loading[automation.id]"
|
||||
@click="openDeletePopup(automation, index)"
|
||||
>
|
||||
</woot-button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="small-4 columns">
|
||||
<span v-html="$t('AUTOMATION.SIDEBAR_TXT')"></span>
|
||||
</div>
|
||||
</div>
|
||||
<woot-modal
|
||||
:show.sync="showAddPopup"
|
||||
size="medium"
|
||||
:on-close="hideAddPopup"
|
||||
>
|
||||
<add-automation-rule
|
||||
v-if="showAddPopup"
|
||||
:on-close="hideAddPopup"
|
||||
@saveAutomation="onCreateAutomation"
|
||||
/>
|
||||
</woot-modal>
|
||||
|
||||
<woot-delete-modal
|
||||
:show.sync="showDeleteConfirmationPopup"
|
||||
:on-close="closeDeletePopup"
|
||||
:on-confirm="confirmDeletion"
|
||||
:title="$t('LABEL_MGMT.DELETE.CONFIRM.TITLE')"
|
||||
:message="deleteMessage"
|
||||
:confirm-text="deleteConfirmText"
|
||||
:reject-text="deleteRejectText"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script></script>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import AddAutomationRule from './AddAutomationRule.vue';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import timeMixin from 'dashboard/mixins/time';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AddAutomationRule,
|
||||
},
|
||||
mixins: [alertMixin, timeMixin],
|
||||
data() {
|
||||
return {
|
||||
loading: {},
|
||||
showAddPopup: false,
|
||||
showEditPopup: false,
|
||||
showDeleteConfirmationPopup: false,
|
||||
selectedResponse: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
records: ['automations/getAutomations'],
|
||||
uiFlags: 'automations/getUIFlags',
|
||||
}),
|
||||
// Delete Modal
|
||||
deleteConfirmText() {
|
||||
return `${this.$t('AUTOMATION.DELETE.CONFIRM.YES')} ${
|
||||
this.selectedResponse.name
|
||||
}`;
|
||||
},
|
||||
deleteRejectText() {
|
||||
return `${this.$t('AUTOMATION.DELETE.CONFIRM.NO')} ${
|
||||
this.selectedResponse.name
|
||||
}`;
|
||||
},
|
||||
deleteMessage() {
|
||||
return `${this.$t('AUTOMATION.DELETE.CONFIRM.MESSAGE')} ${
|
||||
this.selectedResponse.name
|
||||
} ?`;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('automations/get');
|
||||
},
|
||||
methods: {
|
||||
openAddPopup() {
|
||||
this.showAddPopup = true;
|
||||
},
|
||||
hideAddPopup() {
|
||||
this.showAddPopup = false;
|
||||
},
|
||||
openEditPopup(response) {
|
||||
this.showEditPopup = true;
|
||||
this.selectedResponse = response;
|
||||
},
|
||||
hideEditPopup() {
|
||||
this.showEditPopup = false;
|
||||
},
|
||||
openDeletePopup(response) {
|
||||
this.showDeleteConfirmationPopup = true;
|
||||
this.selectedResponse = response;
|
||||
},
|
||||
closeDeletePopup() {
|
||||
this.showDeleteConfirmationPopup = false;
|
||||
},
|
||||
confirmDeletion() {
|
||||
this.loading[this.selectedResponse.id] = true;
|
||||
this.closeDeletePopup();
|
||||
this.deleteAutomation(this.selectedResponse.id);
|
||||
},
|
||||
async deleteAutomation(id) {
|
||||
try {
|
||||
await this.$store.dispatch('automations/delete', id);
|
||||
this.showAlert(this.$t('AUTOMATION.DELETE.API.SUCCESS_MESSAGE'));
|
||||
this.loading[this.selectedResponse.id] = false;
|
||||
} catch (error) {
|
||||
this.showAlert(this.$t('AUTOMATION.DELETE.API.ERROR_MESSAGE'));
|
||||
}
|
||||
},
|
||||
async onCreateAutomation(payload) {
|
||||
try {
|
||||
await await this.$store.dispatch('automations/create', payload);
|
||||
this.showAlert(this.$t('AUTOMATION.ADD.API.SUCCESS_MESSAGE'));
|
||||
this.hideAddPopup();
|
||||
} catch (error) {
|
||||
this.showAlert(this.$t('AUTOMATION.ADD.API.ERROR_MESSAGE'));
|
||||
}
|
||||
},
|
||||
readableTime(date) {
|
||||
return this.messageStamp(new Date(date), 'LLL d, h:mm a');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.automation__status-checkbox {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -9,7 +9,7 @@ export default {
|
||||
component: SettingsContent,
|
||||
props: {
|
||||
headerTitle: 'AUTOMATION.HEADER',
|
||||
icon: 'autocorrect',
|
||||
icon: 'automation',
|
||||
showNewButton: false,
|
||||
},
|
||||
children: [
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
const OPERATOR_TYPES_1 = [
|
||||
{
|
||||
value: 'equal_to',
|
||||
label: 'Equal to',
|
||||
},
|
||||
{
|
||||
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 = {
|
||||
message_created: {
|
||||
conditions: [
|
||||
{
|
||||
key: 'message_type',
|
||||
name: 'Message Type',
|
||||
attributeI18nKey: 'MESSAGE_TYPE',
|
||||
inputType: 'search_select',
|
||||
filterOperators: OPERATOR_TYPES_1,
|
||||
},
|
||||
{
|
||||
key: 'message_contains',
|
||||
name: 'Message Contains',
|
||||
attributeI18nKey: 'MESSAGE_CONTAINS',
|
||||
inputType: 'plain_text',
|
||||
filterOperators: OPERATOR_TYPES_2,
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
key: 'assign_team',
|
||||
name: 'Assign a team',
|
||||
attributeI18nKey: 'ASSIGN_TEAM',
|
||||
},
|
||||
{
|
||||
key: 'add_label',
|
||||
name: 'Add a label',
|
||||
attributeI18nKey: 'ADD_LABEL',
|
||||
},
|
||||
{
|
||||
key: 'send_message',
|
||||
name: 'Send an email to team',
|
||||
attributeI18nKey: 'SEND_MESSAGE',
|
||||
},
|
||||
],
|
||||
},
|
||||
conversation_created: {
|
||||
conditions: [
|
||||
{
|
||||
key: 'status',
|
||||
name: 'Status',
|
||||
attributeI18nKey: 'STATUS',
|
||||
inputType: 'multi_select',
|
||||
filterOperators: OPERATOR_TYPES_1,
|
||||
},
|
||||
{
|
||||
key: 'browser_language',
|
||||
name: 'Browser Language',
|
||||
attributeI18nKey: 'BROWSER_LANGUAGE',
|
||||
inputType: 'search_select',
|
||||
filterOperators: OPERATOR_TYPES_1,
|
||||
},
|
||||
{
|
||||
key: 'country_code',
|
||||
name: 'Country',
|
||||
attributeI18nKey: 'COUNTRY_NAME',
|
||||
inputType: 'search_select',
|
||||
filterOperators: OPERATOR_TYPES_1,
|
||||
},
|
||||
{
|
||||
key: 'referrer',
|
||||
name: 'Referrer Link',
|
||||
attributeI18nKey: 'REFERER_LINK',
|
||||
inputType: 'plain_text',
|
||||
filterOperators: OPERATOR_TYPES_2,
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
key: 'assign_team',
|
||||
name: 'Assign a team',
|
||||
attributeI18nKey: 'ASSIGN_TEAM',
|
||||
},
|
||||
{
|
||||
key: 'send_message',
|
||||
name: 'Send an email to team',
|
||||
attributeI18nKey: 'SEND_MESSAGE',
|
||||
},
|
||||
{
|
||||
key: 'assign_agent',
|
||||
name: 'Assign an agent',
|
||||
attributeI18nKey: 'ASSIGN_AGENT',
|
||||
},
|
||||
],
|
||||
},
|
||||
conversation_updated: {
|
||||
conditions: [
|
||||
{
|
||||
key: 'status',
|
||||
name: 'Status',
|
||||
attributeI18nKey: 'STATUS',
|
||||
inputType: 'multi_select',
|
||||
filterOperators: OPERATOR_TYPES_1,
|
||||
},
|
||||
{
|
||||
key: 'browser_language',
|
||||
name: 'Browser Language',
|
||||
attributeI18nKey: 'BROWSER_LANGUAGE',
|
||||
inputType: 'search_select',
|
||||
filterOperators: OPERATOR_TYPES_1,
|
||||
},
|
||||
{
|
||||
key: 'country_code',
|
||||
name: 'Country',
|
||||
attributeI18nKey: 'COUNTRY_NAME',
|
||||
inputType: 'search_select',
|
||||
filterOperators: OPERATOR_TYPES_1,
|
||||
},
|
||||
{
|
||||
key: 'referer',
|
||||
name: 'Referrer Link',
|
||||
attributeI18nKey: 'REFERER_LINK',
|
||||
inputType: 'plain_text',
|
||||
filterOperators: OPERATOR_TYPES_2,
|
||||
},
|
||||
{
|
||||
key: 'assignee_id',
|
||||
name: 'Assignee',
|
||||
attributeI18nKey: 'ASSIGNEE_NAME',
|
||||
inputType: 'search_select',
|
||||
filterOperators: OPERATOR_TYPES_3,
|
||||
},
|
||||
{
|
||||
key: 'team_id',
|
||||
name: 'Team',
|
||||
attributeI18nKey: 'TEAM_NAME',
|
||||
inputType: 'search_select',
|
||||
filterOperators: OPERATOR_TYPES_3,
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
key: 'assign_team',
|
||||
name: 'Assign a team',
|
||||
attributeI18nKey: 'ASSIGN_TEAM',
|
||||
},
|
||||
{
|
||||
key: 'send_message',
|
||||
name: 'Send an email to team',
|
||||
attributeI18nKey: 'SEND_MESSAGE',
|
||||
},
|
||||
{
|
||||
key: 'assign_agent',
|
||||
name: 'Assign an agent',
|
||||
attributeI18nKey: 'ASSIGN_AGENT',
|
||||
attributeKey: 'assignee_id',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const AUTOMATION_RULE_EVENTS = [
|
||||
{
|
||||
key: 'conversation_created',
|
||||
value: 'Conversation Created',
|
||||
},
|
||||
{
|
||||
key: 'conversation_updated',
|
||||
value: 'Conversation Updated',
|
||||
},
|
||||
{
|
||||
key: 'message_created',
|
||||
value: 'Message Created',
|
||||
},
|
||||
];
|
||||
|
||||
export const AUTOMATION_ACTION_TYPES = [
|
||||
{
|
||||
key: 'assign_team',
|
||||
label: 'Assign a team',
|
||||
},
|
||||
{
|
||||
key: 'add_label',
|
||||
label: 'Add a label',
|
||||
},
|
||||
{
|
||||
key: 'send_message',
|
||||
label: 'Send an email to team',
|
||||
},
|
||||
];
|
||||
@@ -11,6 +11,7 @@ import reports from './reports/reports.routes';
|
||||
import campaigns from './campaigns/campaigns.routes';
|
||||
import teams from './teams/teams.routes';
|
||||
import attributes from './attributes/attributes.routes';
|
||||
import automation from './automation/automation.routes';
|
||||
import store from '../../../store';
|
||||
|
||||
export default {
|
||||
@@ -38,5 +39,6 @@ export default {
|
||||
...campaigns.routes,
|
||||
...integrationapps.routes,
|
||||
...attributes.routes,
|
||||
...automation.routes,
|
||||
],
|
||||
};
|
||||
|
||||
@@ -31,6 +31,7 @@ import teams from './modules/teams';
|
||||
import userNotificationSettings from './modules/userNotificationSettings';
|
||||
import webhooks from './modules/webhooks';
|
||||
import attributes from './modules/attributes';
|
||||
import automations from './modules/automations';
|
||||
import customViews from './modules/customViews';
|
||||
|
||||
Vue.use(Vuex);
|
||||
@@ -66,6 +67,7 @@ export default new Vuex.Store({
|
||||
userNotificationSettings,
|
||||
webhooks,
|
||||
attributes,
|
||||
automations,
|
||||
customViews,
|
||||
},
|
||||
});
|
||||
|
||||
89
app/javascript/dashboard/store/modules/automations.js
Normal file
89
app/javascript/dashboard/store/modules/automations.js
Normal file
@@ -0,0 +1,89 @@
|
||||
import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
|
||||
import types from '../mutation-types';
|
||||
import AutomationAPI from '../../api/automation';
|
||||
|
||||
export const state = {
|
||||
records: [],
|
||||
uiFlags: {
|
||||
isFetching: false,
|
||||
isCreating: false,
|
||||
isDeleting: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const getters = {
|
||||
getAutomations(_state) {
|
||||
return _state.records;
|
||||
},
|
||||
getUIFlags(_state) {
|
||||
return _state.uiFlags;
|
||||
},
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
get: async function getAutomations({ commit }) {
|
||||
commit(types.SET_AUTOMATION_UI_FLAG, { isFetching: true });
|
||||
try {
|
||||
const response = await AutomationAPI.get();
|
||||
commit(types.SET_AUTOMATIONS, response.data.payload);
|
||||
} catch (error) {
|
||||
// Ignore error
|
||||
} finally {
|
||||
commit(types.SET_AUTOMATION_UI_FLAG, { isFetching: false });
|
||||
}
|
||||
},
|
||||
create: async function createAutomation({ commit }, automationObj) {
|
||||
commit(types.SET_AUTOMATION_UI_FLAG, { isCreating: true });
|
||||
try {
|
||||
const response = await AutomationAPI.create(automationObj);
|
||||
commit(types.ADD_AUTOMATION, response.data);
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
} finally {
|
||||
commit(types.SET_AUTOMATION_UI_FLAG, { isCreating: false });
|
||||
}
|
||||
},
|
||||
update: async ({ commit }, { id, ...updateObj }) => {
|
||||
commit(types.SET_AUTOMATION_UI_FLAG, { isUpdating: true });
|
||||
try {
|
||||
const response = await AutomationAPI.update(id, updateObj);
|
||||
commit(types.EDIT_AUTOMATION, response.data);
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
} finally {
|
||||
commit(types.SET_AUTOMATION_UI_FLAG, { isUpdating: false });
|
||||
}
|
||||
},
|
||||
delete: async ({ commit }, id) => {
|
||||
commit(types.SET_AUTOMATION_UI_FLAG, { isDeleting: true });
|
||||
try {
|
||||
await AutomationAPI.delete(id);
|
||||
commit(types.DELETE_AUTOMATION, id);
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
} finally {
|
||||
commit(types.SET_AUTOMATION_UI_FLAG, { isDeleting: false });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export const mutations = {
|
||||
[types.SET_AUTOMATION_UI_FLAG](_state, data) {
|
||||
_state.uiFlags = {
|
||||
..._state.uiFlags,
|
||||
...data,
|
||||
};
|
||||
},
|
||||
[types.ADD_AUTOMATION]: MutationHelpers.create,
|
||||
[types.SET_AUTOMATIONS]: MutationHelpers.set,
|
||||
// [types.EDIT_AUTOMATION]: MutationHelpers.update,
|
||||
[types.DELETE_AUTOMATION]: MutationHelpers.destroy,
|
||||
};
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
actions,
|
||||
state,
|
||||
getters,
|
||||
mutations,
|
||||
};
|
||||
@@ -0,0 +1,94 @@
|
||||
import axios from 'axios';
|
||||
import { actions } from '../../automations';
|
||||
import * as types from '../../../mutation-types';
|
||||
import automationsList from './fixtures';
|
||||
|
||||
const commit = jest.fn();
|
||||
global.axios = axios;
|
||||
jest.mock('axios');
|
||||
|
||||
describe('#actions', () => {
|
||||
describe('#get', () => {
|
||||
it('sends correct actions if API is success', async () => {
|
||||
axios.get.mockResolvedValue({ data: { payload: automationsList } });
|
||||
await actions.get({ commit });
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[types.default.SET_AUTOMATION_UI_FLAG, { isFetching: true }],
|
||||
[types.default.SET_AUTOMATIONS, automationsList],
|
||||
[types.default.SET_AUTOMATION_UI_FLAG, { isFetching: false }],
|
||||
]);
|
||||
});
|
||||
it('sends correct actions if API is error', async () => {
|
||||
axios.get.mockRejectedValue({ message: 'Incorrect header' });
|
||||
await actions.get({ commit });
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[types.default.SET_AUTOMATION_UI_FLAG, { isFetching: true }],
|
||||
[types.default.SET_AUTOMATION_UI_FLAG, { isFetching: false }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#create', () => {
|
||||
it('sends correct actions if API is success', async () => {
|
||||
axios.post.mockResolvedValue({ data: automationsList[0] });
|
||||
await actions.create({ commit }, automationsList[0]);
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[types.default.SET_AUTOMATION_UI_FLAG, { isCreating: true }],
|
||||
[types.default.ADD_AUTOMATION, automationsList[0]],
|
||||
[types.default.SET_AUTOMATION_UI_FLAG, { isCreating: false }],
|
||||
]);
|
||||
});
|
||||
it('sends correct actions if API is error', async () => {
|
||||
axios.post.mockRejectedValue({ message: 'Incorrect header' });
|
||||
await expect(actions.create({ commit })).rejects.toThrow(Error);
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[types.default.SET_AUTOMATION_UI_FLAG, { isCreating: true }],
|
||||
[types.default.SET_AUTOMATION_UI_FLAG, { isCreating: false }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
// API Work in progress
|
||||
// describe('#update', () => {
|
||||
// it('sends correct actions if API is success', async () => {
|
||||
// axios.patch.mockResolvedValue({ data: automationsList[0] });
|
||||
// await actions.update({ commit }, automationsList[0]);
|
||||
// expect(commit.mock.calls).toEqual([
|
||||
// [types.default.SET_AUTOMATION_UI_FLAG, { isUpdating: true }],
|
||||
// [types.default.EDIT_AUTOMATION, automationsList[0]],
|
||||
// [types.default.SET_AUTOMATION_UI_FLAG, { isUpdating: false }],
|
||||
// ]);
|
||||
// });
|
||||
// it('sends correct actions if API is error', async () => {
|
||||
// axios.patch.mockRejectedValue({ message: 'Incorrect header' });
|
||||
// await expect(
|
||||
// actions.update({ commit }, automationsList[0])
|
||||
// ).rejects.toThrow(Error);
|
||||
// expect(commit.mock.calls).toEqual([
|
||||
// [types.default.SET_AUTOMATION_UI_FLAG, { isUpdating: true }],
|
||||
// [types.default.SET_AUTOMATION_UI_FLAG, { isUpdating: false }],
|
||||
// ]);
|
||||
// });
|
||||
// });
|
||||
|
||||
describe('#delete', () => {
|
||||
it('sends correct actions if API is success', async () => {
|
||||
axios.delete.mockResolvedValue({ data: automationsList[0] });
|
||||
await actions.delete({ commit }, automationsList[0].id);
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[types.default.SET_AUTOMATION_UI_FLAG, { isDeleting: true }],
|
||||
[types.default.DELETE_AUTOMATION, automationsList[0].id],
|
||||
[types.default.SET_AUTOMATION_UI_FLAG, { isDeleting: false }],
|
||||
]);
|
||||
});
|
||||
it('sends correct actions if API is error', async () => {
|
||||
axios.delete.mockRejectedValue({ message: 'Incorrect header' });
|
||||
await expect(
|
||||
actions.delete({ commit }, automationsList[0].id)
|
||||
).rejects.toThrow(Error);
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[types.default.SET_AUTOMATION_UI_FLAG, { isDeleting: true }],
|
||||
[types.default.SET_AUTOMATION_UI_FLAG, { isDeleting: false }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
export default [
|
||||
{
|
||||
id: 12,
|
||||
account_id: 1,
|
||||
name: 'Test',
|
||||
description: 'This is a test',
|
||||
event_name: 'conversation_created',
|
||||
conditions: [
|
||||
{
|
||||
values: ['open'],
|
||||
attribute_key: 'status',
|
||||
query_operator: null,
|
||||
filter_operator: 'equal_to',
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
action_name: 'add_label',
|
||||
action_params: [{}],
|
||||
},
|
||||
],
|
||||
created_on: '2022-01-14T09:17:55.689Z',
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
account_id: 1,
|
||||
name: 'Auto resolve conversation',
|
||||
description: 'Auto resolves conversation',
|
||||
event_name: 'conversation_updated',
|
||||
conditions: [
|
||||
{
|
||||
values: ['resolved'],
|
||||
attribute_key: 'status',
|
||||
query_operator: null,
|
||||
filter_operator: 'equal_to',
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
action_name: 'add_label',
|
||||
action_params: [{}],
|
||||
},
|
||||
],
|
||||
created_on: '2022-01-14T13:06:31.843Z',
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
account_id: 1,
|
||||
name: 'Fayaz',
|
||||
description: 'This is a test',
|
||||
event_name: 'conversation_created',
|
||||
conditions: {},
|
||||
actions: [
|
||||
{
|
||||
action_name: 'add_label',
|
||||
action_params: [{}],
|
||||
},
|
||||
],
|
||||
created_on: '2022-01-17T06:46:08.098Z',
|
||||
active: true,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,25 @@
|
||||
import { getters } from '../../automations';
|
||||
import automations from './fixtures';
|
||||
describe('#getters', () => {
|
||||
it('getAutomations', () => {
|
||||
const state = { records: automations };
|
||||
expect(getters.getAutomations(state)).toEqual(automations);
|
||||
});
|
||||
|
||||
it('getUIFlags', () => {
|
||||
const state = {
|
||||
uiFlags: {
|
||||
isFetching: true,
|
||||
isCreating: false,
|
||||
isUpdating: false,
|
||||
isDeleting: false,
|
||||
},
|
||||
};
|
||||
expect(getters.getUIFlags(state)).toEqual({
|
||||
isFetching: true,
|
||||
isCreating: false,
|
||||
isUpdating: false,
|
||||
isDeleting: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import types from '../../../mutation-types';
|
||||
import { mutations } from '../../automations';
|
||||
import automations from './fixtures';
|
||||
describe('#mutations', () => {
|
||||
describe('#SET_automations', () => {
|
||||
it('set autonmation records', () => {
|
||||
const state = { records: [] };
|
||||
mutations[types.SET_AUTOMATIONS](state, automations);
|
||||
expect(state.records).toEqual(automations);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#ADD_AUTOMATION', () => {
|
||||
it('push newly created automatuion to the store', () => {
|
||||
const state = { records: [automations[0]] };
|
||||
mutations[types.ADD_AUTOMATION](state, automations[1]);
|
||||
expect(state.records).toEqual([automations[0], automations[1]]);
|
||||
});
|
||||
});
|
||||
|
||||
// describe('#EDIT_AUTOMATION', () => {
|
||||
// it('update automation record', () => {
|
||||
// const state = { records: [automations[0]] };
|
||||
// mutations[types.EDIT_AUTOMATION](state, {
|
||||
// id: 12,
|
||||
// account_id: 1,
|
||||
// name: 'Test Automation',
|
||||
// description: 'This is a test',
|
||||
// event_name: 'conversation_created',
|
||||
// conditions: [
|
||||
// {
|
||||
// values: ['open'],
|
||||
// attribute_key: 'status',
|
||||
// query_operator: null,
|
||||
// filter_operator: 'equal_to',
|
||||
// },
|
||||
// ],
|
||||
// actions: [
|
||||
// {
|
||||
// action_name: 'add_label',
|
||||
// action_params: [{}],
|
||||
// },
|
||||
// ],
|
||||
// created_on: '2022-01-14T09:17:55.689Z',
|
||||
// active: true,
|
||||
// });
|
||||
// expect(state.records[0].name).toEqual('Test Automation');
|
||||
// });
|
||||
// });
|
||||
|
||||
describe('#DELETE_AUTOMATION', () => {
|
||||
it('delete automation record', () => {
|
||||
const state = { records: [automations[0]] };
|
||||
mutations[types.DELETE_AUTOMATION](state, 12);
|
||||
expect(state.records).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -189,6 +189,13 @@ export default {
|
||||
EDIT_CUSTOM_ATTRIBUTE: 'EDIT_CUSTOM_ATTRIBUTE',
|
||||
DELETE_CUSTOM_ATTRIBUTE: 'DELETE_CUSTOM_ATTRIBUTE',
|
||||
|
||||
// Automations
|
||||
SET_AUTOMATION_UI_FLAG: 'SET_AUTOMATION_UI_FLAG',
|
||||
SET_AUTOMATIONS: 'SET_AUTOMATIONS',
|
||||
ADD_AUTOMATION: 'ADD_AUTOMATION',
|
||||
EDIT_AUTOMATION: 'EDIT_AUTOMATION',
|
||||
DELETE_AUTOMATION: 'DELETE_AUTOMATION',
|
||||
|
||||
// Custom Views
|
||||
SET_CUSTOM_VIEW_UI_FLAG: 'SET_CUSTOM_VIEW_UI_FLAG',
|
||||
SET_CUSTOM_VIEW: 'SET_CUSTOM_VIEW',
|
||||
|
||||
Reference in New Issue
Block a user