diff --git a/app/javascript/dashboard/components/widgets/conversation/linear/LinkIssue.vue b/app/javascript/dashboard/components/widgets/conversation/linear/LinkIssue.vue
index 87386f681..c2fb15b21 100644
--- a/app/javascript/dashboard/components/widgets/conversation/linear/LinkIssue.vue
+++ b/app/javascript/dashboard/components/widgets/conversation/linear/LinkIssue.vue
@@ -62,6 +62,8 @@ const onSearch = async value => {
issues.value = response.data.map(issue => ({
id: issue.id,
name: `${issue.identifier} ${issue.title}`,
+ icon: 'status',
+ iconColor: issue.state.color,
}));
} catch (error) {
const errorMessage = parseLinearAPIErrorResponse(
diff --git a/app/javascript/dashboard/composables/spec/useMacros.spec.js b/app/javascript/dashboard/composables/spec/useMacros.spec.js
new file mode 100644
index 000000000..d268547a5
--- /dev/null
+++ b/app/javascript/dashboard/composables/spec/useMacros.spec.js
@@ -0,0 +1,175 @@
+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';
+
+vi.mock('dashboard/composables/store');
+vi.mock('dashboard/helper/automationHelper.js');
+
+describe('useMacros', () => {
+ const mockLabels = [
+ {
+ id: 6,
+ title: 'sales',
+ description: 'sales team',
+ color: '#8EA20F',
+ show_on_sidebar: true,
+ },
+ {
+ id: 2,
+ title: 'billing',
+ description: 'billing',
+ color: '#4077DA',
+ show_on_sidebar: true,
+ },
+ {
+ id: 1,
+ title: 'snoozed',
+ description: 'Items marked for later',
+ color: '#D12F42',
+ show_on_sidebar: true,
+ },
+ {
+ id: 5,
+ title: 'mobile-app',
+ description: 'tech team',
+ color: '#2DB1CC',
+ show_on_sidebar: true,
+ },
+ {
+ id: 14,
+ title: 'human-resources-department-with-long-title',
+ description: 'Test',
+ color: '#FF6E09',
+ show_on_sidebar: true,
+ },
+ {
+ id: 22,
+ title: 'priority',
+ description: 'For important sales leads',
+ color: '#7E7CED',
+ show_on_sidebar: true,
+ },
+ ];
+ const mockTeams = [
+ {
+ id: 1,
+ name: '⚙️ sales team',
+ description: 'This is our internal sales team',
+ allow_auto_assign: true,
+ account_id: 1,
+ is_member: true,
+ },
+ {
+ id: 2,
+ name: '🤷♂️ fayaz',
+ description: 'Test',
+ allow_auto_assign: true,
+ account_id: 1,
+ is_member: true,
+ },
+ {
+ id: 3,
+ name: '🇮🇳 apac sales',
+ description: 'Sales team for France Territory',
+ allow_auto_assign: true,
+ account_id: 1,
+ is_member: true,
+ },
+ ];
+ const mockAgents = [
+ {
+ id: 1,
+ account_id: 1,
+ availability_status: 'offline',
+ auto_offline: true,
+ confirmed: true,
+ email: 'john@doe.com',
+ available_name: 'John Doe',
+ name: 'John Doe',
+ role: 'agent',
+ thumbnail: 'https://example.com/image.png',
+ },
+ {
+ id: 9,
+ account_id: 1,
+ availability_status: 'offline',
+ auto_offline: true,
+ confirmed: true,
+ email: 'clark@kent.com',
+ available_name: 'Clark Kent',
+ name: 'Clark Kent',
+ role: 'agent',
+ thumbnail: '',
+ },
+ ];
+
+ beforeEach(() => {
+ useStoreGetters.mockReturnValue({
+ 'labels/getLabels': { value: mockLabels },
+ 'teams/getTeams': { value: mockTeams },
+ 'agents/getAgents': { value: mockAgents },
+ });
+ });
+
+ it('initializes computed properties correctly', () => {
+ const { getMacroDropdownValues } = useMacros();
+ expect(getMacroDropdownValues('add_label')).toHaveLength(mockLabels.length);
+ expect(getMacroDropdownValues('assign_team')).toHaveLength(
+ mockTeams.length
+ );
+ expect(getMacroDropdownValues('assign_agent')).toHaveLength(
+ mockAgents.length + 1
+ ); // +1 for "Self"
+ });
+
+ it('returns teams for assign_team and send_email_to_team types', () => {
+ const { getMacroDropdownValues } = useMacros();
+ expect(getMacroDropdownValues('assign_team')).toEqual(mockTeams);
+ expect(getMacroDropdownValues('send_email_to_team')).toEqual(mockTeams);
+ });
+
+ it('returns agents with "Self" option for assign_agent type', () => {
+ const { getMacroDropdownValues } = useMacros();
+ const result = getMacroDropdownValues('assign_agent');
+ expect(result[0]).toEqual({ id: 'self', name: 'Self' });
+ expect(result.slice(1)).toEqual(mockAgents);
+ });
+
+ it('returns formatted labels for add_label and remove_label types', () => {
+ const { getMacroDropdownValues } = useMacros();
+ const expectedLabels = mockLabels.map(i => ({
+ id: i.title,
+ name: i.title,
+ }));
+ expect(getMacroDropdownValues('add_label')).toEqual(expectedLabels);
+ expect(getMacroDropdownValues('remove_label')).toEqual(expectedLabels);
+ });
+
+ it('returns PRIORITY_CONDITION_VALUES for change_priority type', () => {
+ const { getMacroDropdownValues } = useMacros();
+ expect(getMacroDropdownValues('change_priority')).toEqual(
+ PRIORITY_CONDITION_VALUES
+ );
+ });
+
+ it('returns an empty array for unknown types', () => {
+ const { getMacroDropdownValues } = useMacros();
+ expect(getMacroDropdownValues('unknown_type')).toEqual([]);
+ });
+
+ it('handles empty data correctly', () => {
+ useStoreGetters.mockReturnValue({
+ 'labels/getLabels': { value: [] },
+ 'teams/getTeams': { value: [] },
+ 'agents/getAgents': { value: [] },
+ });
+
+ const { getMacroDropdownValues } = useMacros();
+ expect(getMacroDropdownValues('add_label')).toEqual([]);
+ expect(getMacroDropdownValues('assign_team')).toEqual([]);
+ expect(getMacroDropdownValues('assign_agent')).toEqual([
+ { id: 'self', name: 'Self' },
+ ]);
+ });
+});
diff --git a/app/javascript/dashboard/composables/useMacros.js b/app/javascript/dashboard/composables/useMacros.js
new file mode 100644
index 000000000..6967331ea
--- /dev/null
+++ b/app/javascript/dashboard/composables/useMacros.js
@@ -0,0 +1,44 @@
+import { computed } from 'vue';
+import { useStoreGetters } from 'dashboard/composables/store';
+import { PRIORITY_CONDITION_VALUES } from 'dashboard/helper/automationHelper.js';
+
+/**
+ * Composable for handling macro-related functionality
+ * @returns {Object} An object containing the getMacroDropdownValues function
+ */
+export const useMacros = () => {
+ const getters = useStoreGetters();
+
+ const labels = computed(() => getters['labels/getLabels'].value);
+ const teams = computed(() => getters['teams/getTeams'].value);
+ const agents = computed(() => getters['agents/getAgents'].value);
+
+ /**
+ * Get dropdown values based on the specified type
+ * @param {string} type - The type of dropdown values to retrieve
+ * @returns {Array} An array of dropdown values
+ */
+ const getMacroDropdownValues = type => {
+ switch (type) {
+ case 'assign_team':
+ case 'send_email_to_team':
+ return teams.value;
+ case 'assign_agent':
+ return [{ id: 'self', name: 'Self' }, ...agents.value];
+ case 'add_label':
+ case 'remove_label':
+ return labels.value.map(i => ({
+ id: i.title,
+ name: i.title,
+ }));
+ case 'change_priority':
+ return PRIORITY_CONDITION_VALUES;
+ default:
+ return [];
+ }
+ };
+
+ return {
+ getMacroDropdownValues,
+ };
+};
diff --git a/app/javascript/dashboard/mixins/customAttributeMixin.js b/app/javascript/dashboard/mixins/customAttributeMixin.js
deleted file mode 100644
index a0617685d..000000000
--- a/app/javascript/dashboard/mixins/customAttributeMixin.js
+++ /dev/null
@@ -1,11 +0,0 @@
-export default {
- methods: {
- getRegexp(regexPatternValue) {
- let lastSlash = regexPatternValue.lastIndexOf('/');
- return new RegExp(
- regexPatternValue.slice(1, lastSlash),
- regexPatternValue.slice(lastSlash + 1)
- );
- },
- },
-};
diff --git a/app/javascript/dashboard/mixins/macrosMixin.js b/app/javascript/dashboard/mixins/macrosMixin.js
deleted file mode 100644
index 4aea89227..000000000
--- a/app/javascript/dashboard/mixins/macrosMixin.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import { mapGetters } from 'vuex';
-import { PRIORITY_CONDITION_VALUES } from 'dashboard/helper/automationHelper.js';
-export default {
- computed: {
- ...mapGetters({
- labels: 'labels/getLabels',
- teams: 'teams/getTeams',
- agents: 'agents/getAgents',
- }),
- },
- methods: {
- getDropdownValues(type) {
- switch (type) {
- case 'assign_team':
- case 'send_email_to_team':
- return this.teams;
- case 'assign_agent':
- return [{ id: 'self', name: 'Self' }, ...this.agents];
- case 'add_label':
- case 'remove_label':
- return this.labels.map(i => {
- return {
- id: i.title,
- name: i.title,
- };
- });
- case 'change_priority':
- return PRIORITY_CONDITION_VALUES;
- default:
- return [];
- }
- },
- },
-};
diff --git a/app/javascript/dashboard/mixins/specs/macros.spec.js b/app/javascript/dashboard/mixins/specs/macros.spec.js
deleted file mode 100644
index a8b3ee2b1..000000000
--- a/app/javascript/dashboard/mixins/specs/macros.spec.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import { createWrapper } from '@vue/test-utils';
-import macrosMixin from '../macrosMixin';
-import Vue from 'vue';
-import { teams, labels, agents } from '../../helper/specs/macrosFixtures';
-import { PRIORITY_CONDITION_VALUES } from 'dashboard/helper/automationHelper.js';
-describe('webhookMixin', () => {
- describe('#getEventLabel', () => {
- it('returns correct i18n translation:', () => {
- const Component = {
- render() {},
- title: 'MyComponent',
- mixins: [macrosMixin],
- data: () => {
- return {
- teams,
- labels,
- agents,
- };
- },
- methods: {
- $t(text) {
- return text;
- },
- },
- };
-
- const resolvedLabels = labels.map(i => {
- return {
- id: i.title,
- name: i.title,
- };
- });
-
- const Constructor = Vue.extend(Component);
- const vm = new Constructor().$mount();
- const wrapper = createWrapper(vm);
- expect(wrapper.vm.getDropdownValues('assign_team')).toEqual(teams);
- expect(wrapper.vm.getDropdownValues('send_email_to_team')).toEqual(teams);
- expect(wrapper.vm.getDropdownValues('add_label')).toEqual(resolvedLabels);
- expect(wrapper.vm.getDropdownValues('assign_agent')).toEqual([
- { id: 'self', name: 'Self' },
- ...agents,
- ]);
- expect(wrapper.vm.getDropdownValues('change_priority')).toEqual(
- PRIORITY_CONDITION_VALUES
- );
- expect(wrapper.vm.getDropdownValues()).toEqual([]);
- });
- });
-});
diff --git a/app/javascript/dashboard/modules/notes/components/AddNote.vue b/app/javascript/dashboard/modules/notes/components/AddNote.vue
index 2b325759b..f3f82e8a3 100644
--- a/app/javascript/dashboard/modules/notes/components/AddNote.vue
+++ b/app/javascript/dashboard/modules/notes/components/AddNote.vue
@@ -1,43 +1,34 @@
-
+import { ref } from 'vue';
import { mapGetters } from 'vuex';
import { useAdmin } from 'dashboard/composables/useAdmin';
+import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
import Spinner from 'shared/components/Spinner.vue';
import LabelDropdown from 'shared/components/ui/label/LabelDropdown.vue';
import AddLabel from 'shared/components/ui/dropdown/AddLabel.vue';
-import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins';
import conversationLabelMixin from 'dashboard/mixins/conversation/labelMixin';
export default {
@@ -14,7 +15,7 @@ export default {
AddLabel,
},
- mixins: [conversationLabelMixin, keyboardEventListenerMixins],
+ mixins: [conversationLabelMixin],
props: {
// conversationId prop is used in /conversation/labelMixin,
// remove this props when refactoring to composable if not needed
@@ -26,14 +27,46 @@ export default {
},
setup() {
const { isAdmin } = useAdmin();
+
+ const conversationLabelBoxRef = ref(null);
+ const showSearchDropdownLabel = ref(false);
+
+ const toggleLabels = () => {
+ showSearchDropdownLabel.value = !showSearchDropdownLabel.value;
+ };
+
+ const closeDropdownLabel = () => {
+ showSearchDropdownLabel.value = false;
+ };
+
+ const keyboardEvents = {
+ KeyL: {
+ action: e => {
+ e.preventDefault();
+ toggleLabels();
+ },
+ },
+ Escape: {
+ action: () => {
+ if (showSearchDropdownLabel.value) {
+ toggleLabels();
+ }
+ },
+ allowOnFocusedInput: true,
+ },
+ };
+ useKeyboardEvents(keyboardEvents, conversationLabelBoxRef);
return {
isAdmin,
+ conversationLabelBoxRef,
+ showSearchDropdownLabel,
+ closeDropdownLabel,
+ toggleLabels,
};
},
data() {
return {
selectedLabels: [],
- showSearchDropdownLabel: false,
};
},
@@ -42,37 +75,11 @@ export default {
conversationUiFlags: 'conversationLabels/getUIFlags',
}),
},
- methods: {
- toggleLabels() {
- this.showSearchDropdownLabel = !this.showSearchDropdownLabel;
- },
- closeDropdownLabel() {
- this.showSearchDropdownLabel = false;
- },
- getKeyboardEvents() {
- return {
- KeyL: {
- action: e => {
- e.preventDefault();
- this.toggleLabels();
- },
- },
- Escape: {
- action: () => {
- if (this.showSearchDropdownLabel) {
- this.toggleLabels();
- }
- },
- allowOnFocusedInput: true,
- },
- };
- },
- },
};
-