From 32c25047c4dab96f3eb0c034c42d8e354f4aff61 Mon Sep 17 00:00:00 2001
From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Date: Tue, 27 Aug 2024 08:00:05 +0530
Subject: [PATCH 1/5] feat: Rewrite `reportMixin` to a composable (#10029)
# Pull Request Template
## Description
The PR will replace the usage of `reportMixin` with the help of
`useReportMetrics()` composable.
Fixes
https://linear.app/chatwoot/issue/CW-3450/rewrite-reportmixin-mixin-to-a-composable
**Files updated**
1. dashboard/routes/dashboard/settings/reports/Index.vue
2. dashboard/routes/dashboard/settings/reports/BotReports.vue
3. dashboard/routes/dashboard/settings/reports/ReportContainer.vue
4.
dashboard/routes/dashboard/settings/reports/components/WootReports.vue
5.
dashboard/routes/dashboard/settings/reports/components/ChartElements/ChartStats.vue
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
Test the all the reports view.
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---
.../spec/fixtures/reportFixtures.js | 37 +++++
.../composables/spec/useReportMetrics.spec.js | 61 ++++++++
.../dashboard/composables/useReportMetrics.js | 57 ++++++++
.../dashboard/mixins/reportMixin.js | 51 -------
.../mixins/specs/reportMixin.spec.js | 136 ------------------
.../mixins/specs/reportMixinFixtures.js | 37 -----
.../dashboard/settings/reports/BotReports.vue | 2 -
.../dashboard/settings/reports/Index.vue | 2 -
.../settings/reports/ReportContainer.vue | 25 +++-
.../components/ChartElements/ChartStats.vue | 47 +++---
.../reports/components/WootReports.vue | 2 -
11 files changed, 200 insertions(+), 257 deletions(-)
create mode 100644 app/javascript/dashboard/composables/spec/fixtures/reportFixtures.js
create mode 100644 app/javascript/dashboard/composables/spec/useReportMetrics.spec.js
create mode 100644 app/javascript/dashboard/composables/useReportMetrics.js
delete mode 100644 app/javascript/dashboard/mixins/reportMixin.js
delete mode 100644 app/javascript/dashboard/mixins/specs/reportMixin.spec.js
delete mode 100644 app/javascript/dashboard/mixins/specs/reportMixinFixtures.js
diff --git a/app/javascript/dashboard/composables/spec/fixtures/reportFixtures.js b/app/javascript/dashboard/composables/spec/fixtures/reportFixtures.js
new file mode 100644
index 000000000..93880e1ca
--- /dev/null
+++ b/app/javascript/dashboard/composables/spec/fixtures/reportFixtures.js
@@ -0,0 +1,37 @@
+export const summary = {
+ avg_first_response_time: '198.6666666666667',
+ avg_resolution_time: '208.3333333333333',
+ conversations_count: 5000,
+ incoming_messages_count: 5,
+ outgoing_messages_count: 3,
+ previous: {
+ avg_first_response_time: '89.0',
+ avg_resolution_time: '145.0',
+ conversations_count: 4,
+ incoming_messages_count: 5,
+ outgoing_messages_count: 4,
+ resolutions_count: 0,
+ },
+ resolutions_count: 3,
+};
+
+export const botSummary = {
+ bot_resolutions_count: 10,
+ bot_handoffs_count: 20,
+ previous: {
+ bot_resolutions_count: 8,
+ bot_handoffs_count: 5,
+ },
+};
+
+export const report = {
+ data: [
+ { value: '0.00', timestamp: 1647541800, count: 0 },
+ { value: '0.00', timestamp: 1647628200, count: 0 },
+ { value: '0.00', timestamp: 1647714600, count: 0 },
+ { value: '0.00', timestamp: 1647801000, count: 0 },
+ { value: '0.01', timestamp: 1647887400, count: 4 },
+ { value: '0.00', timestamp: 1647973800, count: 0 },
+ { value: '0.00', timestamp: 1648060200, count: 0 },
+ ],
+};
diff --git a/app/javascript/dashboard/composables/spec/useReportMetrics.spec.js b/app/javascript/dashboard/composables/spec/useReportMetrics.spec.js
new file mode 100644
index 000000000..194f07e75
--- /dev/null
+++ b/app/javascript/dashboard/composables/spec/useReportMetrics.spec.js
@@ -0,0 +1,61 @@
+import { ref } from 'vue';
+import { useReportMetrics } from '../useReportMetrics';
+import { useMapGetter } from 'dashboard/composables/store';
+import { summary, botSummary } from './fixtures/reportFixtures';
+
+vi.mock('dashboard/composables/store');
+vi.mock('@chatwoot/utils', () => ({
+ formatTime: vi.fn(time => `formatted_${time}`),
+}));
+
+describe('useReportMetrics', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ useMapGetter.mockReturnValue(ref(summary));
+ });
+
+ it('calculates trend correctly', () => {
+ const { calculateTrend } = useReportMetrics();
+
+ expect(calculateTrend('conversations_count')).toBe(124900);
+ expect(calculateTrend('incoming_messages_count')).toBe(0);
+ expect(calculateTrend('avg_first_response_time')).toBe(123);
+ });
+
+ it('returns 0 for trend when previous value is not available', () => {
+ const { calculateTrend } = useReportMetrics();
+
+ expect(calculateTrend('non_existent_key')).toBe(0);
+ });
+
+ it('identifies average metric types correctly', () => {
+ const { isAverageMetricType } = useReportMetrics();
+
+ expect(isAverageMetricType('avg_first_response_time')).toBe(true);
+ expect(isAverageMetricType('avg_resolution_time')).toBe(true);
+ expect(isAverageMetricType('reply_time')).toBe(true);
+ expect(isAverageMetricType('conversations_count')).toBe(false);
+ });
+
+ it('displays metrics correctly for account', () => {
+ const { displayMetric } = useReportMetrics();
+
+ expect(displayMetric('conversations_count')).toBe('5,000');
+ expect(displayMetric('incoming_messages_count')).toBe('5');
+ });
+
+ it('displays the metric for bot', () => {
+ const customKey = 'getBotSummary';
+ useMapGetter.mockReturnValue(ref(botSummary));
+ const { displayMetric } = useReportMetrics(customKey);
+
+ expect(displayMetric('bot_resolutions_count')).toBe('10');
+ expect(displayMetric('bot_handoffs_count')).toBe('20');
+ });
+
+ it('handles non-existent metrics', () => {
+ const { displayMetric } = useReportMetrics();
+
+ expect(displayMetric('non_existent_key')).toBe('0');
+ });
+});
diff --git a/app/javascript/dashboard/composables/useReportMetrics.js b/app/javascript/dashboard/composables/useReportMetrics.js
new file mode 100644
index 000000000..37168e256
--- /dev/null
+++ b/app/javascript/dashboard/composables/useReportMetrics.js
@@ -0,0 +1,57 @@
+import { useMapGetter } from 'dashboard/composables/store';
+import { formatTime } from '@chatwoot/utils';
+
+/**
+ * A composable function for report metrics calculations and display.
+ *
+ * @param {string} [accountSummaryKey='getAccountSummary'] - The key for accessing account summary data.
+ * @returns {Object} An object containing utility functions for report metrics.
+ */
+export function useReportMetrics(accountSummaryKey = 'getAccountSummary') {
+ const accountSummary = useMapGetter(accountSummaryKey);
+
+ /**
+ * Calculates the trend percentage for a given metric.
+ *
+ * @param {string} key - The key of the metric to calculate trend for.
+ * @returns {number} The calculated trend percentage, rounded to the nearest integer.
+ */
+ const calculateTrend = key => {
+ if (!accountSummary.value.previous[key]) return 0;
+ const diff = accountSummary.value[key] - accountSummary.value.previous[key];
+ return Math.round((diff / accountSummary.value.previous[key]) * 100);
+ };
+
+ /**
+ * Checks if a given metric key represents an average metric type.
+ *
+ * @param {string} key - The key of the metric to check.
+ * @returns {boolean} True if the metric is an average type, false otherwise.
+ */
+ const isAverageMetricType = key => {
+ return [
+ 'avg_first_response_time',
+ 'avg_resolution_time',
+ 'reply_time',
+ ].includes(key);
+ };
+
+ /**
+ * Formats and displays a metric value based on its type.
+ *
+ * @param {string} key - The key of the metric to display.
+ * @returns {string} The formatted metric value as a string.
+ */
+ const displayMetric = key => {
+ if (isAverageMetricType(key)) {
+ return formatTime(accountSummary.value[key]);
+ }
+ return Number(accountSummary.value[key] || '').toLocaleString();
+ };
+
+ return {
+ calculateTrend,
+ isAverageMetricType,
+ displayMetric,
+ };
+}
diff --git a/app/javascript/dashboard/mixins/reportMixin.js b/app/javascript/dashboard/mixins/reportMixin.js
deleted file mode 100644
index d57af1ad2..000000000
--- a/app/javascript/dashboard/mixins/reportMixin.js
+++ /dev/null
@@ -1,51 +0,0 @@
-import { mapGetters } from 'vuex';
-import { formatTime } from '@chatwoot/utils';
-
-export default {
- props: {
- accountSummaryKey: {
- type: String,
- default: 'getAccountSummary',
- },
- },
- computed: {
- ...mapGetters({
- accountReport: 'getAccountReports',
- }),
- accountSummary() {
- return this.$store.getters[this.accountSummaryKey];
- },
- },
- methods: {
- calculateTrend(key) {
- if (!this.accountSummary.previous[key]) return 0;
- const diff = this.accountSummary[key] - this.accountSummary.previous[key];
- return Math.round((diff / this.accountSummary.previous[key]) * 100);
- },
- displayMetric(key) {
- if (this.isAverageMetricType(key)) {
- return formatTime(this.accountSummary[key]);
- }
- return Number(this.accountSummary[key] || '').toLocaleString();
- },
- displayInfoText(key) {
- if (this.metrics[this.currentSelection].KEY !== key) {
- return '';
- }
- if (this.isAverageMetricType(key)) {
- const total = this.accountReport.data
- .map(item => item.count)
- .reduce((prev, curr) => prev + curr, 0);
- return `${this.metrics[this.currentSelection].INFO_TEXT} ${total}`;
- }
- return '';
- },
- isAverageMetricType(key) {
- return [
- 'avg_first_response_time',
- 'avg_resolution_time',
- 'reply_time',
- ].includes(key);
- },
- },
-};
diff --git a/app/javascript/dashboard/mixins/specs/reportMixin.spec.js b/app/javascript/dashboard/mixins/specs/reportMixin.spec.js
deleted file mode 100644
index c0bc1e15f..000000000
--- a/app/javascript/dashboard/mixins/specs/reportMixin.spec.js
+++ /dev/null
@@ -1,136 +0,0 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
-import reportMixin from '../reportMixin';
-import reportFixtures from './reportMixinFixtures';
-import Vuex from 'vuex';
-const localVue = createLocalVue();
-localVue.use(Vuex);
-
-describe('reportMixin', () => {
- let getters;
- let store;
- beforeEach(() => {
- getters = {
- getAccountSummary: () => reportFixtures.summary,
- getBotSummary: () => reportFixtures.botSummary,
- getAccountReports: () => reportFixtures.report,
- };
- store = new Vuex.Store({ getters });
- });
-
- it('display the metric for account', async () => {
- const Component = {
- render() {},
- title: 'TestComponent',
- mixins: [reportMixin],
- };
- const wrapper = shallowMount(Component, { store, localVue });
- await wrapper.setProps({
- accountSummaryKey: 'getAccountSummary',
- });
- expect(wrapper.vm.displayMetric('conversations_count')).toEqual('5,000');
- expect(wrapper.vm.displayMetric('avg_first_response_time')).toEqual(
- '3 Min 18 Sec'
- );
- });
-
- it('display the metric for bot', async () => {
- const Component = {
- render() {},
- title: 'TestComponent',
- mixins: [reportMixin],
- };
- const wrapper = shallowMount(Component, { store, localVue });
- await wrapper.setProps({
- accountSummaryKey: 'getBotSummary',
- });
- expect(wrapper.vm.displayMetric('bot_resolutions_count')).toEqual('10');
- expect(wrapper.vm.displayMetric('bot_handoffs_count')).toEqual('20');
- });
-
- it('display the metric', () => {
- const Component = {
- render() {},
- title: 'TestComponent',
- mixins: [reportMixin],
- };
- const wrapper = shallowMount(Component, { store, localVue });
- expect(wrapper.vm.displayMetric('conversations_count')).toEqual('5,000');
- expect(wrapper.vm.displayMetric('avg_first_response_time')).toEqual(
- '3 Min 18 Sec'
- );
- });
-
- it('calculate the trend', () => {
- const Component = {
- render() {},
- title: 'TestComponent',
- mixins: [reportMixin],
- };
- const wrapper = shallowMount(Component, { store, localVue });
- expect(wrapper.vm.calculateTrend('conversations_count')).toEqual(124900);
- expect(wrapper.vm.calculateTrend('resolutions_count')).toEqual(0);
- });
-
- it('display info text', () => {
- const Component = {
- render() {},
- title: 'TestComponent',
- mixins: [reportMixin],
- data() {
- return {
- currentSelection: 0,
- };
- },
- computed: {
- metrics() {
- return [
- {
- DESC: '( Avg )',
- INFO_TEXT: 'Total number of conversations used for computation:',
- KEY: 'avg_first_response_time',
- NAME: 'First Response Time',
- },
- ];
- },
- },
- };
- const wrapper = shallowMount(Component, { store, localVue });
- expect(wrapper.vm.displayInfoText('avg_first_response_time')).toEqual(
- 'Total number of conversations used for computation: 4'
- );
- });
-
- it('do not display info text', () => {
- const Component = {
- render() {},
- title: 'TestComponent',
- mixins: [reportMixin],
- data() {
- return {
- currentSelection: 0,
- };
- },
- computed: {
- metrics() {
- return [
- {
- DESC: '( Total )',
- INFO_TEXT: '',
- KEY: 'conversation_count',
- NAME: 'Conversations',
- },
- {
- DESC: '( Avg )',
- INFO_TEXT: 'Total number of conversations used for computation:',
- KEY: 'avg_first_response_time',
- NAME: 'First Response Time',
- },
- ];
- },
- },
- };
- const wrapper = shallowMount(Component, { store, localVue });
- expect(wrapper.vm.displayInfoText('conversation_count')).toEqual('');
- expect(wrapper.vm.displayInfoText('incoming_messages_count')).toEqual('');
- });
-});
diff --git a/app/javascript/dashboard/mixins/specs/reportMixinFixtures.js b/app/javascript/dashboard/mixins/specs/reportMixinFixtures.js
deleted file mode 100644
index 591bf7c1f..000000000
--- a/app/javascript/dashboard/mixins/specs/reportMixinFixtures.js
+++ /dev/null
@@ -1,37 +0,0 @@
-export default {
- summary: {
- avg_first_response_time: '198.6666666666667',
- avg_resolution_time: '208.3333333333333',
- conversations_count: 5000,
- incoming_messages_count: 5,
- outgoing_messages_count: 3,
- previous: {
- avg_first_response_time: '89.0',
- avg_resolution_time: '145.0',
- conversations_count: 4,
- incoming_messages_count: 5,
- outgoing_messages_count: 4,
- resolutions_count: 0,
- },
- resolutions_count: 3,
- },
- botSummary: {
- bot_resolutions_count: 10,
- bot_handoffs_count: 20,
- previous: {
- bot_resolutions_count: 8,
- bot_handoffs_count: 5,
- },
- },
- report: {
- data: [
- { value: '0.00', timestamp: 1647541800, count: 0 },
- { value: '0.00', timestamp: 1647628200, count: 0 },
- { value: '0.00', timestamp: 1647714600, count: 0 },
- { value: '0.00', timestamp: 1647801000, count: 0 },
- { value: '0.01', timestamp: 1647887400, count: 4 },
- { value: '0.00', timestamp: 1647973800, count: 0 },
- { value: '0.00', timestamp: 1648060200, count: 0 },
- ],
- },
-};
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/BotReports.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/BotReports.vue
index 492775542..78b433a42 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/reports/BotReports.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/BotReports.vue
@@ -3,7 +3,6 @@ import { useAlert } from 'dashboard/composables';
import BotMetrics from './components/BotMetrics.vue';
import ReportFilterSelector from './components/FilterSelector.vue';
import { GROUP_BY_FILTER } from './constants';
-import reportMixin from 'dashboard/mixins/reportMixin';
import ReportContainer from './ReportContainer.vue';
import { REPORTS_EVENTS } from '../../../../helper/AnalyticsHelper/events';
@@ -14,7 +13,6 @@ export default {
ReportFilterSelector,
ReportContainer,
},
- mixins: [reportMixin],
data() {
return {
from: 0,
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue
index 1f96b161f..da2f2f232 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue
@@ -4,7 +4,6 @@ import fromUnixTime from 'date-fns/fromUnixTime';
import format from 'date-fns/format';
import ReportFilterSelector from './components/FilterSelector.vue';
import { GROUP_BY_FILTER } from './constants';
-import reportMixin from 'dashboard/mixins/reportMixin';
import { REPORTS_EVENTS } from '../../../../helper/AnalyticsHelper/events';
import ReportContainer from './ReportContainer.vue';
@@ -24,7 +23,6 @@ export default {
ReportFilterSelector,
ReportContainer,
},
- mixins: [reportMixin],
data() {
return {
from: 0,
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue
index f90c3730c..fc323d037 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue
@@ -1,19 +1,23 @@
@@ -29,7 +34,7 @@ export default {
{{ metric.NAME }}
-
+
{{ displayMetric(metric.KEY) }}
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue
index 2d5e52462..51fb8932d 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue
@@ -3,7 +3,6 @@ import { useAlert } from 'dashboard/composables';
import ReportFilters from './ReportFilters.vue';
import ReportContainer from '../ReportContainer.vue';
import { GROUP_BY_FILTER } from '../constants';
-import reportMixin from '../../../../../mixins/reportMixin';
import { generateFileName } from '../../../../../helper/downloadHelper';
import { REPORTS_EVENTS } from '../../../../../helper/AnalyticsHelper/events';
@@ -22,7 +21,6 @@ export default {
ReportFilters,
ReportContainer,
},
- mixins: [reportMixin],
props: {
type: {
type: String,
From f82ec3b885ec2e52b460da0522b6f9c69a33fa54 Mon Sep 17 00:00:00 2001
From: Fayaz Ahmed
Date: Tue, 27 Aug 2024 08:06:51 +0530
Subject: [PATCH 2/5] chore: Repalce message formatter mixin with
useMessageFormatter [CW-3470] (#9986)
# Pull Request Template
## Description
Replaced the old messageFormatterMixin with a useMessageFormatter
composable
---
.../components/widgets/AIAssistanceModal.vue | 10 +--
.../widgets/conversation/Message.vue | 9 +-
.../widgets/conversation/MessagePreview.vue | 9 +-
.../widgets/conversation/ReplyBox.vue | 8 +-
.../components/MessageContextMenu.vue | 9 +-
.../modules/notes/components/ContactNote.vue | 11 ++-
.../search/components/MessageContent.vue | 10 ++-
.../conversation/search/PopOverSearch.vue | 2 -
.../dashboard/settings/billing/Index.vue | 6 +-
.../settings/campaigns/CampaignCard.vue | 10 ++-
.../dashboard/settings/integrations/Slack.vue | 3 +-
.../Slack/SelectChannelWarning.vue | 10 ++-
.../Slack/SlackIntegrationHelpText.vue | 9 +-
.../portal/components/SearchSuggestions.vue | 6 +-
.../shared/components/ChatOptions.vue | 9 +-
.../specs/useMessageFormatter.spec.js | 79 ++++++++++++++++++
.../shared/composables/useMessageFormatter.js | 82 +++++++++++++++++++
.../shared/mixins/messageFormatterMixin.js | 40 ---------
.../specs/messageFormatterMixin.spec.js | 17 ----
.../views/playground/Index.vue | 9 +-
.../widget/components/AgentMessageBubble.vue | 14 +++-
.../widget/components/PreChat/Form.vue | 10 ++-
.../widget/components/UnreadMessage.vue | 10 ++-
.../widget/components/UserMessageBubble.vue | 9 +-
.../widget/components/template/Article.vue | 10 ++-
25 files changed, 287 insertions(+), 114 deletions(-)
create mode 100644 app/javascript/shared/composables/specs/useMessageFormatter.spec.js
create mode 100644 app/javascript/shared/composables/useMessageFormatter.js
delete mode 100644 app/javascript/shared/mixins/messageFormatterMixin.js
delete mode 100644 app/javascript/shared/mixins/specs/messageFormatterMixin.spec.js
diff --git a/app/javascript/dashboard/components/widgets/AIAssistanceModal.vue b/app/javascript/dashboard/components/widgets/AIAssistanceModal.vue
index 547746131..8f3100d80 100644
--- a/app/javascript/dashboard/components/widgets/AIAssistanceModal.vue
+++ b/app/javascript/dashboard/components/widgets/AIAssistanceModal.vue
@@ -1,13 +1,12 @@
diff --git a/app/javascript/portal/components/SearchSuggestions.vue b/app/javascript/portal/components/SearchSuggestions.vue
index 5838e8c7c..cb4adc9fc 100644
--- a/app/javascript/portal/components/SearchSuggestions.vue
+++ b/app/javascript/portal/components/SearchSuggestions.vue
@@ -1,10 +1,9 @@
From bc6420019fce6902f4515e3ce69d0a8b4a8d846c Mon Sep 17 00:00:00 2001
From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Date: Tue, 27 Aug 2024 12:30:08 +0530
Subject: [PATCH 3/5] feat: Rewrite `automations/methodsMixin` to a composable
(#9956)
Co-authored-by: Shivam Mishra
---
.../composables/spec/useAutomation.spec.js | 295 ++++++++++++
.../composables/spec/useMacros.spec.js | 2 +-
.../dashboard/composables/useAutomation.js | 349 ++++++++++++++
.../dashboard/composables/useMacros.js | 2 +-
.../dashboard/constants/automation.js | 70 +++
.../dashboard/helper/automationHelper.js | 175 ++++---
.../helper}/specs/automationHelper.spec.js | 296 ++++++++----
.../specs/fixtures}/automationFixtures.js | 4 +-
.../mixins/automations/methodsMixin.js | 314 ------------
.../settings/automation/AddAutomationRule.vue | 110 ++++-
.../automation/EditAutomationRule.vue | 123 ++++-
.../mixins/specs/automationMixin.spec.js | 450 ------------------
12 files changed, 1220 insertions(+), 970 deletions(-)
create mode 100644 app/javascript/dashboard/composables/spec/useAutomation.spec.js
create mode 100644 app/javascript/dashboard/composables/useAutomation.js
create mode 100644 app/javascript/dashboard/constants/automation.js
rename app/javascript/{shared/mixins => dashboard/helper}/specs/automationHelper.spec.js (53%)
rename app/javascript/{shared/mixins/specs => dashboard/helper/specs/fixtures}/automationFixtures.js (99%)
delete mode 100644 app/javascript/dashboard/mixins/automations/methodsMixin.js
delete mode 100644 app/javascript/shared/mixins/specs/automationMixin.spec.js
diff --git a/app/javascript/dashboard/composables/spec/useAutomation.spec.js b/app/javascript/dashboard/composables/spec/useAutomation.spec.js
new file mode 100644
index 000000000..c0881730b
--- /dev/null
+++ b/app/javascript/dashboard/composables/spec/useAutomation.spec.js
@@ -0,0 +1,295 @@
+import { useAutomation } from '../useAutomation';
+import { useStoreGetters, useMapGetter } from 'dashboard/composables/store';
+import { useAlert } from 'dashboard/composables';
+import { useI18n } from '../useI18n';
+import * as automationHelper from 'dashboard/helper/automationHelper';
+import {
+ customAttributes,
+ agents,
+ teams,
+ labels,
+ statusFilterOptions,
+ campaigns,
+ contacts,
+ inboxes,
+ languages,
+ countries,
+ slaPolicies,
+} from 'dashboard/helper/specs/fixtures/automationFixtures.js';
+import { MESSAGE_CONDITION_VALUES } from 'dashboard/constants/automation';
+
+vi.mock('dashboard/composables/store');
+vi.mock('dashboard/composables');
+vi.mock('../useI18n');
+vi.mock('dashboard/helper/automationHelper');
+
+describe('useAutomation', () => {
+ beforeEach(() => {
+ useStoreGetters.mockReturnValue({
+ 'attributes/getAttributes': { value: customAttributes },
+ 'attributes/getAttributesByModel': {
+ value: model => {
+ return model === 'conversation_attribute'
+ ? [{ id: 1, name: 'Conversation Attribute' }]
+ : [{ id: 2, name: 'Contact Attribute' }];
+ },
+ },
+ });
+ useMapGetter.mockImplementation(getter => {
+ const getterMap = {
+ 'agents/getAgents': agents,
+ 'campaigns/getAllCampaigns': campaigns,
+ 'contacts/getContacts': contacts,
+ 'inboxes/getInboxes': inboxes,
+ 'labels/getLabels': labels,
+ 'teams/getTeams': teams,
+ 'sla/getSLA': slaPolicies,
+ };
+ return { value: getterMap[getter] };
+ });
+ useI18n.mockReturnValue({ t: key => key });
+ useAlert.mockReturnValue(vi.fn());
+
+ // Mock getConditionOptions for different types
+ automationHelper.getConditionOptions.mockImplementation(options => {
+ const { type } = options;
+ switch (type) {
+ case 'status':
+ return statusFilterOptions;
+ case 'team_id':
+ return teams;
+ case 'assignee_id':
+ return agents;
+ case 'contact':
+ return contacts;
+ case 'inbox_id':
+ return inboxes;
+ case 'campaigns':
+ return campaigns;
+ case 'browser_language':
+ return languages;
+ case 'country_code':
+ return countries;
+ case 'message_type':
+ return MESSAGE_CONDITION_VALUES;
+ default:
+ return [];
+ }
+ });
+
+ // Mock getActionOptions for different types
+ automationHelper.getActionOptions.mockImplementation(options => {
+ const { type } = options;
+ switch (type) {
+ case 'add_label':
+ return labels;
+ case 'assign_team':
+ return teams;
+ case 'assign_agent':
+ return agents;
+ case 'send_email_to_team':
+ return teams;
+ case 'send_message':
+ return [];
+ case 'add_sla':
+ return slaPolicies;
+ default:
+ return [];
+ }
+ });
+ });
+
+ it('initializes computed properties correctly', () => {
+ const {
+ agents: computedAgents,
+ campaigns: computedCampaigns,
+ contacts: computedContacts,
+ inboxes: computedInboxes,
+ labels: computedLabels,
+ teams: computedTeams,
+ slaPolicies: computedSlaPolicies,
+ } = useAutomation();
+
+ expect(computedAgents.value).toEqual(agents);
+ expect(computedCampaigns.value).toEqual(campaigns);
+ expect(computedContacts.value).toEqual(contacts);
+ expect(computedInboxes.value).toEqual(inboxes);
+ expect(computedLabels.value).toEqual(labels);
+ expect(computedTeams.value).toEqual(teams);
+ expect(computedSlaPolicies.value).toEqual(slaPolicies);
+ });
+
+ it('appends new condition and action correctly', () => {
+ const { appendNewCondition, appendNewAction } = useAutomation();
+ const mockAutomation = {
+ event_name: 'message_created',
+ conditions: [],
+ actions: [],
+ };
+
+ automationHelper.getDefaultConditions.mockReturnValue([{}]);
+ automationHelper.getDefaultActions.mockReturnValue([{}]);
+
+ appendNewCondition(mockAutomation);
+ appendNewAction(mockAutomation);
+
+ expect(automationHelper.getDefaultConditions).toHaveBeenCalledWith(
+ 'message_created'
+ );
+ expect(automationHelper.getDefaultActions).toHaveBeenCalled();
+ expect(mockAutomation.conditions).toHaveLength(1);
+ expect(mockAutomation.actions).toHaveLength(1);
+ });
+
+ it('removes filter and action correctly', () => {
+ const { removeFilter, removeAction } = useAutomation();
+ const mockAutomation = {
+ conditions: [{ id: 1 }, { id: 2 }],
+ actions: [{ id: 1 }, { id: 2 }],
+ };
+
+ removeFilter(mockAutomation, 0);
+ removeAction(mockAutomation, 0);
+
+ expect(mockAutomation.conditions).toHaveLength(1);
+ expect(mockAutomation.actions).toHaveLength(1);
+ expect(mockAutomation.conditions[0].id).toBe(2);
+ expect(mockAutomation.actions[0].id).toBe(2);
+ });
+
+ it('resets filter and action correctly', () => {
+ const { resetFilter, resetAction } = useAutomation();
+ const mockAutomation = {
+ event_name: 'message_created',
+ conditions: [
+ {
+ attribute_key: 'status',
+ filter_operator: 'equal_to',
+ values: 'open',
+ },
+ ],
+ actions: [{ action_name: 'assign_agent', action_params: [1] }],
+ };
+ const mockAutomationTypes = {
+ message_created: {
+ conditions: [
+ { key: 'status', filterOperators: [{ value: 'not_equal_to' }] },
+ ],
+ },
+ };
+
+ resetFilter(
+ mockAutomation,
+ mockAutomationTypes,
+ 0,
+ mockAutomation.conditions[0]
+ );
+ resetAction(mockAutomation, 0);
+
+ expect(mockAutomation.conditions[0].filter_operator).toBe('not_equal_to');
+ expect(mockAutomation.conditions[0].values).toBe('');
+ expect(mockAutomation.actions[0].action_params).toEqual([]);
+ });
+
+ it('formats automation correctly', () => {
+ const { formatAutomation } = useAutomation();
+ const mockAutomation = {
+ conditions: [{ attribute_key: 'status', values: ['open'] }],
+ actions: [{ action_name: 'assign_agent', action_params: [1] }],
+ };
+ const mockAutomationTypes = {};
+ const mockAutomationActionTypes = [
+ { key: 'assign_agent', inputType: 'search_select' },
+ ];
+
+ automationHelper.getConditionOptions.mockReturnValue([
+ { id: 'open', name: 'open' },
+ ]);
+ automationHelper.getActionOptions.mockReturnValue([
+ { id: 1, name: 'Agent 1' },
+ ]);
+
+ const result = formatAutomation(
+ mockAutomation,
+ customAttributes,
+ mockAutomationTypes,
+ mockAutomationActionTypes
+ );
+
+ expect(result.conditions[0].values).toEqual([{ id: 'open', name: 'open' }]);
+ expect(result.actions[0].action_params).toEqual([
+ { id: 1, name: 'Agent 1' },
+ ]);
+ });
+
+ it('manifests custom attributes correctly', () => {
+ const { manifestCustomAttributes } = useAutomation();
+ const mockAutomationTypes = {
+ message_created: { conditions: [] },
+ conversation_created: { conditions: [] },
+ conversation_updated: { conditions: [] },
+ conversation_opened: { conditions: [] },
+ };
+
+ automationHelper.generateCustomAttributeTypes.mockReturnValue([]);
+ automationHelper.generateCustomAttributes.mockReturnValue([]);
+
+ manifestCustomAttributes(mockAutomationTypes);
+
+ expect(automationHelper.generateCustomAttributeTypes).toHaveBeenCalledTimes(
+ 2
+ );
+ expect(automationHelper.generateCustomAttributes).toHaveBeenCalledTimes(1);
+ Object.values(mockAutomationTypes).forEach(type => {
+ expect(type.conditions).toHaveLength(0);
+ });
+ });
+
+ it('gets condition dropdown values correctly', () => {
+ const { getConditionDropdownValues } = useAutomation();
+
+ expect(getConditionDropdownValues('status')).toEqual(statusFilterOptions);
+ expect(getConditionDropdownValues('team_id')).toEqual(teams);
+ expect(getConditionDropdownValues('assignee_id')).toEqual(agents);
+ expect(getConditionDropdownValues('contact')).toEqual(contacts);
+ expect(getConditionDropdownValues('inbox_id')).toEqual(inboxes);
+ expect(getConditionDropdownValues('campaigns')).toEqual(campaigns);
+ expect(getConditionDropdownValues('browser_language')).toEqual(languages);
+ expect(getConditionDropdownValues('country_code')).toEqual(countries);
+ expect(getConditionDropdownValues('message_type')).toEqual(
+ MESSAGE_CONDITION_VALUES
+ );
+ });
+
+ it('gets action dropdown values correctly', () => {
+ const { getActionDropdownValues } = useAutomation();
+
+ expect(getActionDropdownValues('add_label')).toEqual(labels);
+ expect(getActionDropdownValues('assign_team')).toEqual(teams);
+ expect(getActionDropdownValues('assign_agent')).toEqual(agents);
+ expect(getActionDropdownValues('send_email_to_team')).toEqual(teams);
+ expect(getActionDropdownValues('send_message')).toEqual([]);
+ expect(getActionDropdownValues('add_sla')).toEqual(slaPolicies);
+ });
+
+ it('handles event change correctly', () => {
+ const { onEventChange } = useAutomation();
+ const mockAutomation = {
+ event_name: 'message_created',
+ conditions: [],
+ actions: [],
+ };
+
+ automationHelper.getDefaultConditions.mockReturnValue([{}]);
+ automationHelper.getDefaultActions.mockReturnValue([{}]);
+
+ onEventChange(mockAutomation);
+
+ expect(automationHelper.getDefaultConditions).toHaveBeenCalledWith(
+ 'message_created'
+ );
+ expect(automationHelper.getDefaultActions).toHaveBeenCalled();
+ expect(mockAutomation.conditions).toHaveLength(1);
+ expect(mockAutomation.actions).toHaveLength(1);
+ });
+});
diff --git a/app/javascript/dashboard/composables/spec/useMacros.spec.js b/app/javascript/dashboard/composables/spec/useMacros.spec.js
index d268547a5..a72d2ddfe 100644
--- a/app/javascript/dashboard/composables/spec/useMacros.spec.js
+++ b/app/javascript/dashboard/composables/spec/useMacros.spec.js
@@ -1,7 +1,7 @@
import { describe, it, expect, vi } from 'vitest';
import { useMacros } from '../useMacros';
import { useStoreGetters } from 'dashboard/composables/store';
-import { PRIORITY_CONDITION_VALUES } from 'dashboard/helper/automationHelper.js';
+import { PRIORITY_CONDITION_VALUES } from 'dashboard/constants/automation';
vi.mock('dashboard/composables/store');
vi.mock('dashboard/helper/automationHelper.js');
diff --git a/app/javascript/dashboard/composables/useAutomation.js b/app/javascript/dashboard/composables/useAutomation.js
new file mode 100644
index 000000000..986a47ec7
--- /dev/null
+++ b/app/javascript/dashboard/composables/useAutomation.js
@@ -0,0 +1,349 @@
+import { computed } from 'vue';
+import { useStoreGetters, useMapGetter } from 'dashboard/composables/store';
+import { useAlert } from 'dashboard/composables';
+import { useI18n } from './useI18n';
+import languages from 'dashboard/components/widgets/conversation/advancedFilterItems/languages';
+import countries from 'shared/constants/countries';
+import {
+ generateCustomAttributeTypes,
+ getActionOptions,
+ getConditionOptions,
+ getCustomAttributeInputType,
+ getDefaultConditions,
+ getDefaultActions,
+ filterCustomAttributes,
+ getStandardAttributeInputType,
+ isCustomAttribute,
+ generateCustomAttributes,
+} from 'dashboard/helper/automationHelper';
+
+/**
+ * Composable for handling automation-related functionality.
+ * @returns {Object} An object containing various automation-related functions and computed properties.
+ */
+export function useAutomation() {
+ const getters = useStoreGetters();
+ const { t } = useI18n();
+
+ const agents = useMapGetter('agents/getAgents');
+ const campaigns = useMapGetter('campaigns/getAllCampaigns');
+ const contacts = useMapGetter('contacts/getContacts');
+ const inboxes = useMapGetter('inboxes/getInboxes');
+ const labels = useMapGetter('labels/getLabels');
+ const teams = useMapGetter('teams/getTeams');
+ const slaPolicies = useMapGetter('sla/getSLA');
+
+ const booleanFilterOptions = computed(() => [
+ { id: true, name: t('FILTER.ATTRIBUTE_LABELS.TRUE') },
+ { id: false, name: t('FILTER.ATTRIBUTE_LABELS.FALSE') },
+ ]);
+
+ const statusFilterOptions = computed(() => {
+ const statusFilters = t('CHAT_LIST.CHAT_STATUS_FILTER_ITEMS');
+ return [
+ ...Object.keys(statusFilters).map(status => ({
+ id: status,
+ name: statusFilters[status].TEXT,
+ })),
+ { id: 'all', name: t('CHAT_LIST.FILTER_ALL') },
+ ];
+ });
+
+ /**
+ * Handles the event change for an automation.
+ * @param {Object} automation - The automation object to update.
+ */
+ const onEventChange = automation => {
+ automation.conditions = getDefaultConditions(automation.event_name);
+ automation.actions = getDefaultActions();
+ };
+
+ /**
+ * Gets the condition dropdown values for a given type.
+ * @param {string} type - The type of condition.
+ * @returns {Array} An array of condition dropdown values.
+ */
+ const getConditionDropdownValues = type => {
+ return getConditionOptions({
+ agents: agents.value,
+ booleanFilterOptions: booleanFilterOptions.value,
+ campaigns: campaigns.value,
+ contacts: contacts.value,
+ customAttributes: getters['attributes/getAttributes'].value,
+ inboxes: inboxes.value,
+ statusFilterOptions: statusFilterOptions.value,
+ teams: teams.value,
+ languages,
+ countries,
+ type,
+ });
+ };
+
+ /**
+ * Appends a new condition to the automation.
+ * @param {Object} automation - The automation object to update.
+ */
+ const appendNewCondition = automation => {
+ automation.conditions.push(...getDefaultConditions(automation.event_name));
+ };
+
+ /**
+ * Appends a new action to the automation.
+ * @param {Object} automation - The automation object to update.
+ */
+ const appendNewAction = automation => {
+ automation.actions.push(...getDefaultActions());
+ };
+
+ /**
+ * Removes a filter from the automation.
+ * @param {Object} automation - The automation object to update.
+ * @param {number} index - The index of the filter to remove.
+ */
+ const removeFilter = (automation, index) => {
+ if (automation.conditions.length <= 1) {
+ useAlert(t('AUTOMATION.CONDITION.DELETE_MESSAGE'));
+ } else {
+ automation.conditions.splice(index, 1);
+ }
+ };
+
+ /**
+ * Removes an action from the automation.
+ * @param {Object} automation - The automation object to update.
+ * @param {number} index - The index of the action to remove.
+ */
+ const removeAction = (automation, index) => {
+ if (automation.actions.length <= 1) {
+ useAlert(t('AUTOMATION.ACTION.DELETE_MESSAGE'));
+ } else {
+ automation.actions.splice(index, 1);
+ }
+ };
+
+ /**
+ * Resets a filter in the automation.
+ * @param {Object} automation - The automation object to update.
+ * @param {Object} automationTypes - The automation types object.
+ * @param {number} index - The index of the filter to reset.
+ * @param {Object} currentCondition - The current condition object.
+ */
+ const resetFilter = (
+ automation,
+ automationTypes,
+ index,
+ currentCondition
+ ) => {
+ automation.conditions[index].filter_operator = automationTypes[
+ automation.event_name
+ ].conditions.find(
+ condition => condition.key === currentCondition.attribute_key
+ ).filterOperators[0].value;
+ automation.conditions[index].values = '';
+ };
+
+ /**
+ * Resets an action in the automation.
+ * @param {Object} automation - The automation object to update.
+ * @param {number} index - The index of the action to reset.
+ */
+ const resetAction = (automation, index) => {
+ automation.actions[index].action_params = [];
+ };
+
+ /**
+ * This function sets the conditions for automation.
+ * It help to format the conditions for the automation when we open the edit automation modal.
+ * @param {Object} automation - The automation object containing conditions to manifest.
+ * @param {Array} allCustomAttributes - List of all custom attributes.
+ * @param {Object} automationTypes - Object containing automation type definitions.
+ * @returns {Array} An array of manifested conditions.
+ */
+ const manifestConditions = (
+ automation,
+ allCustomAttributes,
+ automationTypes
+ ) => {
+ const customAttributes = filterCustomAttributes(allCustomAttributes);
+ return automation.conditions.map(condition => {
+ const customAttr = isCustomAttribute(
+ customAttributes,
+ condition.attribute_key
+ );
+ let inputType = 'plain_text';
+ if (customAttr) {
+ inputType = getCustomAttributeInputType(customAttr.type);
+ } else {
+ inputType = getStandardAttributeInputType(
+ automationTypes,
+ automation.event_name,
+ condition.attribute_key
+ );
+ }
+ if (inputType === 'plain_text' || inputType === 'date') {
+ return { ...condition, values: condition.values[0] };
+ }
+ if (inputType === 'comma_separated_plain_text') {
+ return { ...condition, values: condition.values.join(',') };
+ }
+ return {
+ ...condition,
+ query_operator: condition.query_operator || 'and',
+ values: [...getConditionDropdownValues(condition.attribute_key)].filter(
+ item => [...condition.values].includes(item.id)
+ ),
+ };
+ });
+ };
+
+ /**
+ * Gets the action dropdown values for a given type.
+ * @param {string} type - The type of action.
+ * @returns {Array} An array of action dropdown values.
+ */
+ const getActionDropdownValues = type => {
+ return getActionOptions({
+ agents: agents.value,
+ labels: labels.value,
+ teams: teams.value,
+ slaPolicies: slaPolicies.value,
+ languages,
+ type,
+ });
+ };
+
+ /**
+ * Generates an array of actions for the automation.
+ * @param {Object} action - The action object.
+ * @param {Array} automationActionTypes - List of available automation action types.
+ * @returns {Array|Object} Generated actions array or object based on input type.
+ */
+ const generateActionsArray = (action, automationActionTypes) => {
+ const params = action.action_params;
+ const inputType = automationActionTypes.find(
+ item => item.key === action.action_name
+ ).inputType;
+ if (inputType === 'multi_select' || inputType === 'search_select') {
+ return [...getActionDropdownValues(action.action_name)].filter(item =>
+ [...params].includes(item.id)
+ );
+ }
+ if (inputType === 'team_message') {
+ return {
+ team_ids: [...getActionDropdownValues(action.action_name)].filter(
+ item => [...params[0].team_ids].includes(item.id)
+ ),
+ message: params[0].message,
+ };
+ }
+ return [...params];
+ };
+
+ /**
+ * This function sets the actions for automation.
+ * It help to format the actions for the automation when we open the edit automation modal.
+ * @param {Object} automation - The automation object containing actions.
+ * @param {Array} automationActionTypes - List of available automation action types.
+ * @returns {Array} An array of manifested actions.
+ */
+ const manifestActions = (automation, automationActionTypes) => {
+ return automation.actions.map(action => ({
+ ...action,
+ action_params: action.action_params.length
+ ? generateActionsArray(action, automationActionTypes)
+ : [],
+ }));
+ };
+
+ /**
+ * Formats the automation object for use when we edit the automation.
+ * It help to format the conditions and actions for the automation when we open the edit automation modal.
+ * @param {Object} automation - The automation object to format.
+ * @param {Array} allCustomAttributes - List of all custom attributes.
+ * @param {Object} automationTypes - Object containing automation type definitions.
+ * @param {Array} automationActionTypes - List of available automation action types.
+ * @returns {Object} A new object with formatted automation data, including automation conditions and actions.
+ */
+ const formatAutomation = (
+ automation,
+ allCustomAttributes,
+ automationTypes,
+ automationActionTypes
+ ) => {
+ return {
+ ...automation,
+ conditions: manifestConditions(
+ automation,
+ allCustomAttributes,
+ automationTypes
+ ),
+ actions: manifestActions(automation, automationActionTypes),
+ };
+ };
+
+ /**
+ * This function formats the custom attributes for automation types.
+ * It retrieves custom attributes for conversations and contacts,
+ * generates custom attribute types, and adds them to the relevant automation types.
+ * @param {Object} automationTypes - The automation types object to update with custom attributes.
+ */
+ const manifestCustomAttributes = automationTypes => {
+ const conversationCustomAttributesRaw = getters[
+ 'attributes/getAttributesByModel'
+ ].value('conversation_attribute');
+ const contactCustomAttributesRaw =
+ getters['attributes/getAttributesByModel'].value('contact_attribute');
+
+ const conversationCustomAttributeTypes = generateCustomAttributeTypes(
+ conversationCustomAttributesRaw,
+ 'conversation_attribute'
+ );
+ const contactCustomAttributeTypes = generateCustomAttributeTypes(
+ contactCustomAttributesRaw,
+ 'contact_attribute'
+ );
+
+ const manifestedCustomAttributes = generateCustomAttributes(
+ conversationCustomAttributeTypes,
+ contactCustomAttributeTypes,
+ t('AUTOMATION.CONDITION.CONVERSATION_CUSTOM_ATTR_LABEL'),
+ t('AUTOMATION.CONDITION.CONTACT_CUSTOM_ATTR_LABEL')
+ );
+
+ automationTypes.message_created.conditions.push(
+ ...manifestedCustomAttributes
+ );
+ automationTypes.conversation_created.conditions.push(
+ ...manifestedCustomAttributes
+ );
+ automationTypes.conversation_updated.conditions.push(
+ ...manifestedCustomAttributes
+ );
+ automationTypes.conversation_opened.conditions.push(
+ ...manifestedCustomAttributes
+ );
+ };
+
+ return {
+ agents,
+ campaigns,
+ contacts,
+ inboxes,
+ labels,
+ teams,
+ slaPolicies,
+ booleanFilterOptions,
+ statusFilterOptions,
+ onEventChange,
+ getConditionDropdownValues,
+ appendNewCondition,
+ appendNewAction,
+ removeFilter,
+ removeAction,
+ resetFilter,
+ resetAction,
+ formatAutomation,
+ getActionDropdownValues,
+ manifestCustomAttributes,
+ };
+}
diff --git a/app/javascript/dashboard/composables/useMacros.js b/app/javascript/dashboard/composables/useMacros.js
index 6967331ea..0489e2d1f 100644
--- a/app/javascript/dashboard/composables/useMacros.js
+++ b/app/javascript/dashboard/composables/useMacros.js
@@ -1,6 +1,6 @@
import { computed } from 'vue';
import { useStoreGetters } from 'dashboard/composables/store';
-import { PRIORITY_CONDITION_VALUES } from 'dashboard/helper/automationHelper.js';
+import { PRIORITY_CONDITION_VALUES } from 'dashboard/constants/automation';
/**
* Composable for handling macro-related functionality
diff --git a/app/javascript/dashboard/constants/automation.js b/app/javascript/dashboard/constants/automation.js
new file mode 100644
index 000000000..3aa9754d5
--- /dev/null
+++ b/app/javascript/dashboard/constants/automation.js
@@ -0,0 +1,70 @@
+export const DEFAULT_MESSAGE_CREATED_CONDITION = [
+ {
+ attribute_key: 'message_type',
+ filter_operator: 'equal_to',
+ values: '',
+ query_operator: 'and',
+ custom_attribute_type: '',
+ },
+];
+
+export const DEFAULT_CONVERSATION_OPENED_CONDITION = [
+ {
+ attribute_key: 'browser_language',
+ filter_operator: 'equal_to',
+ values: '',
+ query_operator: 'and',
+ custom_attribute_type: '',
+ },
+];
+
+export const DEFAULT_OTHER_CONDITION = [
+ {
+ attribute_key: 'status',
+ filter_operator: 'equal_to',
+ values: '',
+ query_operator: 'and',
+ custom_attribute_type: '',
+ },
+];
+
+export const DEFAULT_ACTIONS = [
+ {
+ action_name: 'assign_agent',
+ action_params: [],
+ },
+];
+
+export const MESSAGE_CONDITION_VALUES = [
+ {
+ id: 'incoming',
+ name: 'Incoming Message',
+ },
+ {
+ id: 'outgoing',
+ name: 'Outgoing Message',
+ },
+];
+
+export const PRIORITY_CONDITION_VALUES = [
+ {
+ id: 'nil',
+ name: 'None',
+ },
+ {
+ id: 'low',
+ name: 'Low',
+ },
+ {
+ id: 'medium',
+ name: 'Medium',
+ },
+ {
+ id: 'high',
+ name: 'High',
+ },
+ {
+ id: 'urgent',
+ name: 'Urgent',
+ },
+];
diff --git a/app/javascript/dashboard/helper/automationHelper.js b/app/javascript/dashboard/helper/automationHelper.js
index 33e920edc..6ff941822 100644
--- a/app/javascript/dashboard/helper/automationHelper.js
+++ b/app/javascript/dashboard/helper/automationHelper.js
@@ -3,41 +3,16 @@ import {
OPERATOR_TYPES_3,
OPERATOR_TYPES_4,
} from 'dashboard/routes/dashboard/settings/automation/operators';
+import {
+ DEFAULT_MESSAGE_CREATED_CONDITION,
+ DEFAULT_CONVERSATION_OPENED_CONDITION,
+ DEFAULT_OTHER_CONDITION,
+ DEFAULT_ACTIONS,
+ MESSAGE_CONDITION_VALUES,
+ PRIORITY_CONDITION_VALUES,
+} from 'dashboard/constants/automation';
import filterQueryGenerator from './filterQueryGenerator';
import actionQueryGenerator from './actionQueryGenerator';
-const MESSAGE_CONDITION_VALUES = [
- {
- id: 'incoming',
- name: 'Incoming Message',
- },
- {
- id: 'outgoing',
- name: 'Outgoing Message',
- },
-];
-
-export const PRIORITY_CONDITION_VALUES = [
- {
- id: 'nil',
- name: 'None',
- },
- {
- id: 'low',
- name: 'Low',
- },
- {
- id: 'medium',
- name: 'Medium',
- },
- {
- id: 'high',
- name: 'High',
- },
- {
- id: 'urgent',
- name: 'Urgent',
- },
-];
export const getCustomAttributeInputType = key => {
const customAttributeMap = {
@@ -198,45 +173,16 @@ export const getFileName = (action, files = []) => {
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 DEFAULT_MESSAGE_CREATED_CONDITION;
}
if (eventName === 'conversation_opened') {
- return [
- {
- attribute_key: 'browser_language',
- filter_operator: 'equal_to',
- values: '',
- query_operator: 'and',
- custom_attribute_type: '',
- },
- ];
+ return DEFAULT_CONVERSATION_OPENED_CONDITION;
}
- return [
- {
- attribute_key: 'status',
- filter_operator: 'equal_to',
- values: '',
- query_operator: 'and',
- custom_attribute_type: '',
- },
- ];
+ return DEFAULT_OTHER_CONDITION;
};
export const getDefaultActions = () => {
- return [
- {
- action_name: 'assign_agent',
- action_params: [],
- },
- ];
+ return DEFAULT_ACTIONS;
};
export const filterCustomAttributes = customAttributes => {
@@ -297,3 +243,100 @@ export const generateCustomAttributes = (
}
return customAttributes;
};
+
+/**
+ * Get attributes for a given key from automation types.
+ * @param {Object} automationTypes - Object containing automation types.
+ * @param {string} key - The key to get attributes for.
+ * @returns {Array} Array of condition objects for the given key.
+ */
+export const getAttributes = (automationTypes, key) => {
+ return automationTypes[key].conditions;
+};
+
+/**
+ * Get the automation type for a given key.
+ * @param {Object} automationTypes - Object containing automation types.
+ * @param {Object} automation - The automation object.
+ * @param {string} key - The key to get the automation type for.
+ * @returns {Object} The automation type object.
+ */
+export const getAutomationType = (automationTypes, automation, key) => {
+ return automationTypes[automation.event_name].conditions.find(
+ condition => condition.key === key
+ );
+};
+
+/**
+ * Get the input type for a given key.
+ * @param {Array} allCustomAttributes - Array of all custom attributes.
+ * @param {Object} automationTypes - Object containing automation types.
+ * @param {Object} automation - The automation object.
+ * @param {string} key - The key to get the input type for.
+ * @returns {string} The input type.
+ */
+export const getInputType = (
+ allCustomAttributes,
+ automationTypes,
+ automation,
+ key
+) => {
+ const customAttribute = isACustomAttribute(allCustomAttributes, key);
+ if (customAttribute) {
+ return getCustomAttributeInputType(customAttribute.attribute_display_type);
+ }
+ const type = getAutomationType(automationTypes, automation, key);
+ return type.inputType;
+};
+
+/**
+ * Get operators for a given key.
+ * @param {Array} allCustomAttributes - Array of all custom attributes.
+ * @param {Object} automationTypes - Object containing automation types.
+ * @param {Object} automation - The automation object.
+ * @param {string} mode - The mode ('edit' or other).
+ * @param {string} key - The key to get operators for.
+ * @returns {Array} Array of operators.
+ */
+export const getOperators = (
+ allCustomAttributes,
+ automationTypes,
+ automation,
+ mode,
+ key
+) => {
+ if (mode === 'edit') {
+ const customAttribute = isACustomAttribute(allCustomAttributes, key);
+ if (customAttribute) {
+ return getOperatorTypes(customAttribute.attribute_display_type);
+ }
+ }
+ const type = getAutomationType(automationTypes, automation, key);
+ return type.filterOperators;
+};
+
+/**
+ * Get the custom attribute type for a given key.
+ * @param {Object} automationTypes - Object containing automation types.
+ * @param {Object} automation - The automation object.
+ * @param {string} key - The key to get the custom attribute type for.
+ * @returns {string} The custom attribute type.
+ */
+export const getCustomAttributeType = (automationTypes, automation, key) => {
+ return automationTypes[automation.event_name].conditions.find(
+ i => i.key === key
+ ).customAttributeType;
+};
+
+/**
+ * Determine if an action input should be shown.
+ * @param {Array} automationActionTypes - Array of automation action type objects.
+ * @param {string} action - The action to check.
+ * @returns {boolean} True if the action input should be shown, false otherwise.
+ */
+export const showActionInput = (automationActionTypes, action) => {
+ if (action === 'send_email_to_team' || action === 'send_message')
+ return false;
+ const type = automationActionTypes.find(i => i.key === action).inputType;
+ return !!type;
+};
diff --git a/app/javascript/shared/mixins/specs/automationHelper.spec.js b/app/javascript/dashboard/helper/specs/automationHelper.spec.js
similarity index 53%
rename from app/javascript/shared/mixins/specs/automationHelper.spec.js
rename to app/javascript/dashboard/helper/specs/automationHelper.spec.js
index 481613323..b55da1bad 100644
--- a/app/javascript/shared/mixins/specs/automationHelper.spec.js
+++ b/app/javascript/dashboard/helper/specs/automationHelper.spec.js
@@ -11,11 +11,11 @@ import {
contactAttrs,
conversationAttrs,
expectedOutputForCustomAttributeGenerator,
-} from './automationFixtures';
+} from './fixtures/automationFixtures';
import { AUTOMATIONS } from 'dashboard/routes/dashboard/settings/automation/constants';
-describe('automationMethodsMixin', () => {
- it('getCustomAttributeInputType returns the attribute input type', () => {
+describe('getCustomAttributeInputType', () => {
+ it('returns the attribute input type', () => {
expect(helpers.getCustomAttributeInputType('date')).toEqual('date');
expect(helpers.getCustomAttributeInputType('date')).not.toEqual(
'some_random_value'
@@ -31,33 +31,32 @@ describe('automationMethodsMixin', () => {
'plain_text'
);
});
- it('isACustomAttribute returns the custom attribute value if true', () => {
+});
+
+describe('isACustomAttribute', () => {
+ it('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', () => {
+});
+
+describe('getCustomAttributeListDropdownValues', () => {
+ it('returns the attribute dropdown values', () => {
const myListValues = [
- {
- id: 'item1',
- name: 'item1',
- },
- {
- id: 'item2',
- name: 'item2',
- },
- {
- id: 'item3',
- name: 'item3',
- },
+ { 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', () => {
+describe('isCustomAttributeCheckbox', () => {
+ it('checks if attribute is a checkbox', () => {
expect(
helpers.isCustomAttributeCheckbox(customAttributes, 'prime_user')
.attribute_display_type
@@ -70,13 +69,19 @@ describe('automationMethodsMixin', () => {
helpers.isCustomAttributeCheckbox(customAttributes, 'my_list')
).not.toEqual('checkbox');
});
- it('isCustomAttributeList checks if attribute is a list', () => {
+});
+
+describe('isCustomAttributeList', () => {
+ it('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', () => {
+});
+
+describe('getOperatorTypes', () => {
+ it('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);
@@ -85,93 +90,44 @@ describe('automationMethodsMixin', () => {
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',
- },
- ];
+});
+describe('generateConditionOptions', () => {
+ it('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',
- },
+ { id: 123, name: 'Fayaz' },
+ { id: 324, name: 'John' },
];
expect(helpers.generateConditionOptions(testConditions)).toEqual(
expectedConditions
);
});
- it('getActionOptions returns expected actions options array', () => {
+});
+
+describe('getActionOptions', () => {
+ it('returns expected actions options array', () => {
const expectedOptions = [
- {
- id: 'testlabel',
- name: 'testlabel',
- },
- {
- id: 'snoozes',
- name: 'snoozes',
- },
+ { id: 'testlabel', name: 'testlabel' },
+ { id: 'snoozes', name: 'snoozes' },
];
expect(helpers.getActionOptions({ labels, type: 'add_label' })).toEqual(
expectedOptions
);
});
- it('getConditionOptions returns expected conditions options', () => {
+});
+
+describe('getConditionOptions', () => {
+ it('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',
- },
+ { id: 'open', name: 'Open' },
+ { id: 'resolved', name: 'Resolved' },
+ { id: 'pending', name: 'Pending' },
+ { id: 'snoozed', name: 'Snoozed' },
+ { id: 'all', name: 'All' },
];
expect(
helpers.getConditionOptions({
@@ -180,14 +136,20 @@ describe('automationMethodsMixin', () => {
statusFilterOptions: testOptions,
type: 'status',
})
- ).toEqual(expectedOptions);
+ ).toEqual(testOptions);
});
- it('getFileName returns the correct file name', () => {
+});
+
+describe('getFileName', () => {
+ it('returns the correct file name', () => {
expect(
helpers.getFileName(automation.actions[0], automation.files)
).toEqual('pfp.jpeg');
});
- it('getDefaultConditions returns the resp default condition model', () => {
+});
+
+describe('getDefaultConditions', () => {
+ it('returns the resp default condition model', () => {
const messageCreatedModel = [
{
attribute_key: 'message_type',
@@ -211,7 +173,10 @@ describe('automationMethodsMixin', () => {
);
expect(helpers.getDefaultConditions()).toEqual(genericConditionModel);
});
- it('getDefaultActions returns the resp default action model', () => {
+});
+
+describe('getDefaultActions', () => {
+ it('returns the resp default action model', () => {
const genericActionModel = [
{
action_name: 'assign_agent',
@@ -220,7 +185,10 @@ describe('automationMethodsMixin', () => {
];
expect(helpers.getDefaultActions()).toEqual(genericActionModel);
});
- it('filterCustomAttributes filters the raw custom attributes', () => {
+});
+
+describe('filterCustomAttributes', () => {
+ it('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' },
@@ -235,7 +203,10 @@ describe('automationMethodsMixin', () => {
filteredAttributes
);
});
- it('getStandardAttributeInputType returns the resp default action model', () => {
+});
+
+describe('getStandardAttributeInputType', () => {
+ it('returns the resp default action model', () => {
expect(
helpers.getStandardAttributeInputType(
AUTOMATIONS,
@@ -258,7 +229,10 @@ describe('automationMethodsMixin', () => {
)
).toEqual('plain_text');
});
- it('generateAutomationPayload returns the resp default action model', () => {
+});
+
+describe('generateAutomationPayload', () => {
+ it('returns the resp default action model', () => {
const testPayload = {
name: 'Test',
description: 'This is a test',
@@ -300,7 +274,10 @@ describe('automationMethodsMixin', () => {
expectedPayload
);
});
- it('isCustomAttribute returns the resp default action model', () => {
+});
+
+describe('isCustomAttribute', () => {
+ it('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();
@@ -309,8 +286,10 @@ describe('automationMethodsMixin', () => {
expect(helpers.isCustomAttribute(attrs, 'prime_user')).toBeTruthy();
expect(helpers.isCustomAttribute(attrs, 'hello')).toBeFalsy();
});
+});
- it('generateCustomAttributes generates and returns correct condition attribute', () => {
+describe('generateCustomAttributes', () => {
+ it('generates and returns correct condition attribute', () => {
expect(
helpers.generateCustomAttributes(
conversationAttrs,
@@ -321,3 +300,116 @@ describe('automationMethodsMixin', () => {
).toEqual(expectedOutputForCustomAttributeGenerator);
});
});
+
+describe('getAttributes', () => {
+ it('returns the conditions for the given automation type', () => {
+ const result = helpers.getAttributes(AUTOMATIONS, 'message_created');
+ expect(result).toEqual(AUTOMATIONS.message_created.conditions);
+ });
+});
+
+describe('getAttributes', () => {
+ it('returns the conditions for the given automation type', () => {
+ const result = helpers.getAttributes(AUTOMATIONS, 'message_created');
+ expect(result).toEqual(AUTOMATIONS.message_created.conditions);
+ });
+});
+
+describe('getAutomationType', () => {
+ it('returns the automation type for the given key', () => {
+ const mockAutomation = { event_name: 'message_created' };
+ const result = helpers.getAutomationType(
+ AUTOMATIONS,
+ mockAutomation,
+ 'message_type'
+ );
+ expect(result).toEqual(
+ AUTOMATIONS.message_created.conditions.find(c => c.key === 'message_type')
+ );
+ });
+});
+
+describe('getInputType', () => {
+ it('returns the input type for a custom attribute', () => {
+ const mockAutomation = { event_name: 'message_created' };
+ const result = helpers.getInputType(
+ customAttributes,
+ AUTOMATIONS,
+ mockAutomation,
+ 'signed_up_at'
+ );
+ expect(result).toEqual('date');
+ });
+
+ it('returns the input type for a standard attribute', () => {
+ const mockAutomation = { event_name: 'message_created' };
+ const result = helpers.getInputType(
+ customAttributes,
+ AUTOMATIONS,
+ mockAutomation,
+ 'message_type'
+ );
+ expect(result).toEqual('search_select');
+ });
+});
+
+describe('getOperators', () => {
+ it('returns operators for a custom attribute in edit mode', () => {
+ const mockAutomation = { event_name: 'message_created' };
+ const result = helpers.getOperators(
+ customAttributes,
+ AUTOMATIONS,
+ mockAutomation,
+ 'edit',
+ 'signed_up_at'
+ );
+ expect(result).toEqual(OPERATOR_TYPES_4);
+ });
+
+ it('returns operators for a standard attribute', () => {
+ const mockAutomation = { event_name: 'message_created' };
+ const result = helpers.getOperators(
+ customAttributes,
+ AUTOMATIONS,
+ mockAutomation,
+ 'create',
+ 'message_type'
+ );
+ expect(result).toEqual(
+ AUTOMATIONS.message_created.conditions.find(c => c.key === 'message_type')
+ .filterOperators
+ );
+ });
+});
+
+describe('getCustomAttributeType', () => {
+ it('returns the custom attribute type for the given key', () => {
+ const mockAutomation = { event_name: 'message_created' };
+ const result = helpers.getCustomAttributeType(
+ AUTOMATIONS,
+ mockAutomation,
+ 'message_type'
+ );
+ expect(result).toEqual(
+ AUTOMATIONS.message_created.conditions.find(c => c.key === 'message_type')
+ .customAttributeType
+ );
+ });
+});
+
+describe('showActionInput', () => {
+ it('returns false for send_email_to_team and send_message actions', () => {
+ expect(helpers.showActionInput([], 'send_email_to_team')).toBe(false);
+ expect(helpers.showActionInput([], 'send_message')).toBe(false);
+ });
+
+ it('returns true if the action has an input type', () => {
+ const mockActionTypes = [{ key: 'add_label', inputType: 'select' }];
+ expect(helpers.showActionInput(mockActionTypes, 'add_label')).toBe(true);
+ });
+
+ it('returns false if the action does not have an input type', () => {
+ const mockActionTypes = [{ key: 'some_action', inputType: null }];
+ expect(helpers.showActionInput(mockActionTypes, 'some_action')).toBe(false);
+ });
+});
diff --git a/app/javascript/shared/mixins/specs/automationFixtures.js b/app/javascript/dashboard/helper/specs/fixtures/automationFixtures.js
similarity index 99%
rename from app/javascript/shared/mixins/specs/automationFixtures.js
rename to app/javascript/dashboard/helper/specs/fixtures/automationFixtures.js
index 1647f9004..1f642587b 100644
--- a/app/javascript/shared/mixins/specs/automationFixtures.js
+++ b/app/javascript/dashboard/helper/specs/fixtures/automationFixtures.js
@@ -1,6 +1,6 @@
-import allLanguages from '../../../dashboard/components/widgets/conversation/advancedFilterItems/languages.js';
+import allLanguages from 'dashboard/components/widgets/conversation/advancedFilterItems/languages';
-import allCountries from '../../../shared/constants/countries.js';
+import allCountries from 'shared/constants/countries.js';
export const customAttributes = [
{
diff --git a/app/javascript/dashboard/mixins/automations/methodsMixin.js b/app/javascript/dashboard/mixins/automations/methodsMixin.js
deleted file mode 100644
index 1e9863283..000000000
--- a/app/javascript/dashboard/mixins/automations/methodsMixin.js
+++ /dev/null
@@ -1,314 +0,0 @@
-import languages from 'dashboard/components/widgets/conversation/advancedFilterItems/languages';
-import countries from 'shared/constants/countries';
-import { validateAutomation } from 'dashboard/helper/validations';
-
-import {
- generateCustomAttributeTypes,
- getActionOptions,
- getConditionOptions,
- getCustomAttributeInputType,
- getOperatorTypes,
- isACustomAttribute,
- getFileName,
- getDefaultConditions,
- getDefaultActions,
- filterCustomAttributes,
- generateAutomationPayload,
- getStandardAttributeInputType,
- isCustomAttribute,
- generateCustomAttributes,
-} from 'dashboard/helper/automationHelper';
-import { mapGetters } from 'vuex';
-import { useAlert } from 'dashboard/composables';
-
-export default {
- computed: {
- ...mapGetters({
- agents: 'agents/getAgents',
- campaigns: 'campaigns/getAllCampaigns',
- contacts: 'contacts/getContacts',
- inboxes: 'inboxes/getInboxes',
- labels: 'labels/getLabels',
- teams: 'teams/getTeams',
- slaPolicies: 'sla/getSLA',
- }),
- 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) {
- useAlert(this.$t('AUTOMATION.CONDITION.DELETE_MESSAGE'));
- } else {
- this.automation.conditions.splice(index, 1);
- }
- },
- removeAction(index) {
- if (this.automation.actions.length <= 1) {
- useAlert(this.$t('AUTOMATION.ACTION.DELETE_MESSAGE'));
- } else {
- this.automation.actions.splice(index, 1);
- }
- },
- submitAutomation() {
- // we assign it to this.errors so that it can be accessed in the template
- // it is supposed to be declared in the data function
- this.errors = validateAutomation(this.automation);
- if (Object.keys(this.errors).length === 0) {
- 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],
- };
- }
- if (inputType === 'comma_separated_plain_text') {
- return {
- ...condition,
- values: condition.values.join(','),
- };
- }
- 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 { agents, labels, teams, slaPolicies } = this;
- return getActionOptions({
- agents,
- labels,
- teams,
- slaPolicies,
- languages,
- 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
- );
- this.automationTypes.conversation_opened.conditions.push(
- ...manifestedCustomAttributes
- );
- },
- },
-};
diff --git a/app/javascript/dashboard/routes/dashboard/settings/automation/AddAutomationRule.vue b/app/javascript/dashboard/routes/dashboard/settings/automation/AddAutomationRule.vue
index 8cb143a7e..63c352dbb 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/automation/AddAutomationRule.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/automation/AddAutomationRule.vue
@@ -1,9 +1,17 @@
@@ -121,7 +166,7 @@ export default {