Shivam Mishra
2024-10-02 13:06:30 +05:30
committed by GitHub
parent e0bf2bd9d4
commit 42f6621afb
661 changed files with 15939 additions and 31194 deletions

View File

@@ -0,0 +1,148 @@
import { ref, unref } from 'vue';
import { useStore } from 'vuex';
import { useAlert } from 'dashboard/composables';
import { useI18n } from 'vue-i18n';
import { useMapGetter } from 'dashboard/composables/store.js';
export function useBulkActions() {
const store = useStore();
const { t } = useI18n();
const selectedConversations = useMapGetter(
'bulkActions/getSelectedConversationIds'
);
const selectedInboxes = ref([]);
function selectConversation(conversationId, inboxId) {
store.dispatch('bulkActions/setSelectedConversationIds', conversationId);
selectedInboxes.value = [...selectedInboxes.value, inboxId];
}
function deSelectConversation(conversationId, inboxId) {
store.dispatch('bulkActions/removeSelectedConversationIds', conversationId);
selectedInboxes.value = selectedInboxes.value.filter(
item => item !== inboxId
);
}
function resetBulkActions() {
store.dispatch('bulkActions/clearSelectedConversationIds');
selectedInboxes.value = [];
}
function selectAllConversations(check, conversationList) {
const availableConversations = unref(conversationList);
if (check) {
store.dispatch(
'bulkActions/setSelectedConversationIds',
availableConversations.map(item => item.id)
);
selectedInboxes.value = availableConversations.map(item => item.inbox_id);
} else {
resetBulkActions();
}
}
function isConversationSelected(id) {
return selectedConversations.value.includes(id);
}
// Same method used in context menu, conversationId being passed from there.
async function onAssignAgent(agent, conversationId = null) {
try {
await store.dispatch('bulkActions/process', {
type: 'Conversation',
ids: conversationId || selectedConversations.value,
fields: {
assignee_id: agent.id,
},
});
store.dispatch('bulkActions/clearSelectedConversationIds');
if (conversationId) {
useAlert(
t('CONVERSATION.CARD_CONTEXT_MENU.API.AGENT_ASSIGNMENT.SUCCESFUL', {
agentName: agent.name,
conversationId,
})
);
} else {
useAlert(t('BULK_ACTION.ASSIGN_SUCCESFUL'));
}
} catch (err) {
useAlert(t('BULK_ACTION.ASSIGN_FAILED'));
}
}
// Same method used in context menu, conversationId being passed from there.
async function onAssignLabels(newLabels, conversationId = null) {
try {
await store.dispatch('bulkActions/process', {
type: 'Conversation',
ids: conversationId || selectedConversations.value,
labels: {
add: newLabels,
},
});
store.dispatch('bulkActions/clearSelectedConversationIds');
if (conversationId) {
useAlert(
t('CONVERSATION.CARD_CONTEXT_MENU.API.LABEL_ASSIGNMENT.SUCCESFUL', {
labelName: newLabels[0],
conversationId,
})
);
} else {
useAlert(t('BULK_ACTION.LABELS.ASSIGN_SUCCESFUL'));
}
} catch (err) {
useAlert(t('BULK_ACTION.LABELS.ASSIGN_FAILED'));
}
}
async function onAssignTeamsForBulk(team) {
try {
await store.dispatch('bulkActions/process', {
type: 'Conversation',
ids: selectedConversations.value,
fields: {
team_id: team.id,
},
});
store.dispatch('bulkActions/clearSelectedConversationIds');
useAlert(t('BULK_ACTION.TEAMS.ASSIGN_SUCCESFUL'));
} catch (err) {
useAlert(t('BULK_ACTION.TEAMS.ASSIGN_FAILED'));
}
}
async function onUpdateConversations(status, snoozedUntil) {
try {
await store.dispatch('bulkActions/process', {
type: 'Conversation',
ids: selectedConversations.value,
fields: {
status,
},
snoozed_until: snoozedUntil,
});
store.dispatch('bulkActions/clearSelectedConversationIds');
useAlert(t('BULK_ACTION.UPDATE.UPDATE_SUCCESFUL'));
} catch (err) {
useAlert(t('BULK_ACTION.UPDATE.UPDATE_FAILED'));
}
}
return {
selectedConversations,
selectedInboxes,
selectConversation,
deSelectConversation,
selectAllConversations,
resetBulkActions,
isConversationSelected,
onAssignAgent,
onAssignLabels,
onAssignTeamsForBulk,
onUpdateConversations,
};
}

View File

@@ -0,0 +1,59 @@
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
export function useChatListKeyboardEvents({ listRef }) {
const getKeyboardListenerParams = () => {
const allConversations = listRef.value.querySelectorAll(
'div.conversations-list div.conversation'
);
const activeConversation = listRef.value.querySelector(
'div.conversations-list div.conversation.active'
);
const activeConversationIndex = [...allConversations].indexOf(
activeConversation
);
const lastConversationIndex = allConversations.length - 1;
return {
allConversations,
activeConversation,
activeConversationIndex,
lastConversationIndex,
};
};
const handleConversationNavigation = direction => {
const { allConversations, activeConversationIndex, lastConversationIndex } =
getKeyboardListenerParams();
// Determine the new index based on the direction
const newIndex =
direction === 'previous'
? activeConversationIndex - 1
: activeConversationIndex + 1;
// Check if the new index is within the valid range
if (
allConversations.length > 0 &&
newIndex >= 0 &&
newIndex <= lastConversationIndex
) {
// Click the conversation at the new index
allConversations[newIndex].click();
} else if (allConversations.length > 0) {
// If the new index is out of range, click the first or last conversation based on the direction
const fallbackIndex =
direction === 'previous' ? 0 : lastConversationIndex;
allConversations[fallbackIndex].click();
}
};
const keyboardEvents = {
'Alt+KeyJ': {
action: () => handleConversationNavigation('previous'),
allowOnFocusedInput: true,
},
'Alt+KeyK': {
action: () => handleConversationNavigation('next'),
allowOnFocusedInput: true,
},
};
useKeyboardEvents(keyboardEvents);
}

View File

@@ -1,10 +1,10 @@
import { useAppearanceHotKeys } from '../useAppearanceHotKeys';
import { useI18n } from 'dashboard/composables/useI18n';
import { useI18n } from 'vue-i18n';
import { LocalStorage } from 'shared/helpers/localStorage';
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
import { setColorTheme } from 'dashboard/helper/themeHelper.js';
vi.mock('dashboard/composables/useI18n');
vi.mock('vue-i18n');
vi.mock('shared/helpers/localStorage');
vi.mock('dashboard/helper/themeHelper.js');

View File

@@ -1,11 +1,11 @@
import { useBulkActionsHotKeys } from '../useBulkActionsHotKeys';
import { useStore, useMapGetter } from 'dashboard/composables/store';
import { useI18n } from 'dashboard/composables/useI18n';
import { useI18n } from 'vue-i18n';
import wootConstants from 'dashboard/constants/globals';
import { emitter } from 'shared/helpers/mitt';
vi.mock('dashboard/composables/store');
vi.mock('dashboard/composables/useI18n');
vi.mock('vue-i18n');
vi.mock('shared/helpers/mitt');
describe('useBulkActionsHotKeys', () => {

View File

@@ -1,7 +1,7 @@
import { useConversationHotKeys } from '../useConversationHotKeys';
import { useStore, useMapGetter } from 'dashboard/composables/store';
import { useI18n } from 'dashboard/composables/useI18n';
import { useRoute } from 'dashboard/composables/route';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { useConversationLabels } from 'dashboard/composables/useConversationLabels';
import { useAI } from 'dashboard/composables/useAI';
import { useAgentsList } from 'dashboard/composables/useAgentsList';
@@ -15,8 +15,8 @@ import {
} from './fixtures';
vi.mock('dashboard/composables/store');
vi.mock('dashboard/composables/useI18n');
vi.mock('dashboard/composables/route');
vi.mock('vue-i18n');
vi.mock('vue-router');
vi.mock('dashboard/composables/useConversationLabels');
vi.mock('dashboard/composables/useAI');
vi.mock('dashboard/composables/useAgentsList');

View File

@@ -1,14 +1,14 @@
import { useGoToCommandHotKeys } from '../useGoToCommandHotKeys';
import { useStore, useMapGetter } from 'dashboard/composables/store';
import { useI18n } from 'dashboard/composables/useI18n';
import { useRouter } from 'dashboard/composables/route';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { useAdmin } from 'dashboard/composables/useAdmin';
import { frontendURL } from 'dashboard/helper/URLHelper';
import { MOCK_FEATURE_FLAGS } from './fixtures';
vi.mock('dashboard/composables/store');
vi.mock('dashboard/composables/useI18n');
vi.mock('dashboard/composables/route');
vi.mock('vue-i18n');
vi.mock('vue-router');
vi.mock('dashboard/composables/useAdmin');
vi.mock('dashboard/helper/URLHelper');

View File

@@ -1,10 +1,10 @@
import { useInboxHotKeys } from '../useInboxHotKeys';
import { useI18n } from 'dashboard/composables/useI18n';
import { useRoute } from 'dashboard/composables/route';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { isAInboxViewRoute } from 'dashboard/helper/routeHelpers';
vi.mock('dashboard/composables/useI18n');
vi.mock('dashboard/composables/route');
vi.mock('vue-i18n');
vi.mock('vue-router');
vi.mock('dashboard/helper/routeHelpers');
vi.mock('shared/helpers/mitt');

View File

@@ -1,5 +1,5 @@
import { computed } from 'vue';
import { useI18n } from 'dashboard/composables/useI18n';
import { useI18n } from 'vue-i18n';
import {
ICON_APPEARANCE,
ICON_LIGHT_MODE,

View File

@@ -1,5 +1,5 @@
import { computed } from 'vue';
import { useI18n } from 'dashboard/composables/useI18n';
import { useI18n } from 'vue-i18n';
import { useMapGetter } from 'dashboard/composables/store';
import wootConstants from 'dashboard/constants/globals';

View File

@@ -1,7 +1,7 @@
import { computed } from 'vue';
import { useI18n } from 'dashboard/composables/useI18n';
import { useI18n } from 'vue-i18n';
import { useStore, useMapGetter } from 'dashboard/composables/store';
import { useRoute } from 'dashboard/composables/route';
import { useRoute } from 'vue-router';
import { emitter } from 'shared/helpers/mitt';
import { useConversationLabels } from 'dashboard/composables/useConversationLabels';
import { useAI } from 'dashboard/composables/useAI';

View File

@@ -1,7 +1,7 @@
import { computed } from 'vue';
import { useI18n } from 'dashboard/composables/useI18n';
import { useI18n } from 'vue-i18n';
import { useMapGetter } from 'dashboard/composables/store';
import { useRouter } from 'dashboard/composables/route';
import { useRouter } from 'vue-router';
import { useAdmin } from 'dashboard/composables/useAdmin';
import {
ICON_ACCOUNT_SETTINGS,

View File

@@ -1,6 +1,6 @@
import { computed } from 'vue';
import { useI18n } from 'dashboard/composables/useI18n';
import { useRoute } from 'dashboard/composables/route';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import wootConstants from 'dashboard/constants/globals';
import { CMD_SNOOZE_NOTIFICATION } from 'dashboard/helper/commandbar/events';

View File

@@ -1,15 +1,17 @@
import { getCurrentInstance } from 'vue';
import { emitter } from 'shared/helpers/mitt';
import analyticsHelper from '/dashboard/helper/AnalyticsHelper/index';
/**
* Custom hook to track events
* @returns {Function} The track function
*/
export const useTrack = () => {
const vm = getCurrentInstance();
if (!vm) throw new Error('must be called in setup');
export const useTrack = (...args) => {
try {
return analyticsHelper.track(...args);
} catch (error) {
// Ignore this, tracking is not mission critical
}
return vm.proxy.$track;
return null;
};
/**

View File

@@ -1,30 +0,0 @@
import { getCurrentInstance, reactive, watchEffect } from 'vue';
/**
* Returns the current route location. Equivalent to using `$route` inside
* templates.
*/
export function useRoute() {
const instance = getCurrentInstance();
const route = reactive(Object.assign({}, instance.proxy.$root.$route));
watchEffect(() => {
Object.assign(route, instance.proxy.$root.$route);
});
return route;
}
/**
* Returns the router instance. Equivalent to using `$router` inside
* templates.
*/
export function useRouter() {
const instance = getCurrentInstance();
const router = instance.proxy.$root.$router;
watchEffect(() => {
if (router) {
Object.assign(router, instance.proxy.$root.$router);
}
});
return router;
}

View File

@@ -12,16 +12,9 @@ vi.mock('shared/helpers/mitt', () => ({
}));
describe('useTrack', () => {
it('should return $track from the current instance proxy', () => {
const mockProxy = { $track: vi.fn() };
getCurrentInstance.mockReturnValue({ proxy: mockProxy });
it('should return a function', () => {
const track = useTrack();
expect(track).toBe(mockProxy.$track);
});
it('should throw an error if called outside of setup', () => {
getCurrentInstance.mockReturnValue(null);
expect(useTrack).toThrowError('must be called in setup');
expect(typeof track).toBe('function');
});
});

View File

@@ -5,12 +5,12 @@ import {
useMapGetter,
} from 'dashboard/composables/store';
import { useAlert, useTrack } from 'dashboard/composables';
import { useI18n } from '../useI18n';
import { useI18n } from 'vue-i18n';
import OpenAPI from 'dashboard/api/integrations/openapi';
vi.mock('dashboard/composables/store');
vi.mock('dashboard/composables');
vi.mock('../useI18n');
vi.mock('vue-i18n');
vi.mock('dashboard/api/integrations/openapi');
vi.mock('dashboard/helper/AnalyticsHelper/events', () => ({
OPEN_AI_EVENTS: {

View File

@@ -1,4 +1,4 @@
import { computed } from 'vue';
import { computed, unref } from 'vue';
import { getCurrentInstance } from 'vue';
export const useStore = () => {
@@ -21,3 +21,11 @@ export const useMapGetter = key => {
const store = useStore();
return computed(() => store.getters[key]);
};
export const useFunctionGetter = (key, ...args) => {
const store = useStore();
return computed(() => {
const unrefedArgs = args.map(arg => unref(arg));
return store.getters[key](...unrefedArgs);
});
};

View File

@@ -5,7 +5,7 @@ import {
useMapGetter,
} from 'dashboard/composables/store';
import { useAlert, useTrack } from 'dashboard/composables';
import { useI18n } from './useI18n';
import { useI18n } from 'vue-i18n';
import { OPEN_AI_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
import OpenAPI from 'dashboard/api/integrations/openapi';
@@ -30,7 +30,6 @@ const cleanLabels = labels => {
export function useAI() {
const store = useStore();
const getters = useStoreGetters();
const track = useTrack();
const { t } = useI18n();
/**
@@ -125,7 +124,7 @@ export function useAI() {
const recordAnalytics = async (type, payload) => {
const event = OPEN_AI_EVENTS[type.toUpperCase()];
if (event) {
track(event, {
useTrack(event, {
type,
...payload,
});

View File

@@ -1,7 +1,7 @@
import { computed } from 'vue';
import { useStoreGetters, useMapGetter } from 'dashboard/composables/store';
import { useAlert } from 'dashboard/composables';
import { useI18n } from './useI18n';
import { useI18n } from 'vue-i18n';
import languages from 'dashboard/components/widgets/conversation/advancedFilterItems/languages';
import countries from 'shared/constants/countries';
import {
@@ -38,8 +38,28 @@ export function useAutomation() {
{ id: false, name: t('FILTER.ATTRIBUTE_LABELS.FALSE') },
]);
const statusFilterItems = computed(() => {
return {
open: {
TEXT: t('CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.open.TEXT'),
},
resolved: {
TEXT: t('CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.resolved.TEXT'),
},
pending: {
TEXT: t('CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.pending.TEXT'),
},
snoozed: {
TEXT: t('CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.snoozed.TEXT'),
},
all: {
TEXT: t('CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.all.TEXT'),
},
};
});
const statusFilterOptions = computed(() => {
const statusFilters = t('CHAT_LIST.CHAT_STATUS_FILTER_ITEMS');
const statusFilters = statusFilterItems.value;
return [
...Object.keys(statusFilters).map(status => ({
id: status,

View File

@@ -1,32 +0,0 @@
import { computed, getCurrentInstance } from 'vue';
import Vue from 'vue';
import VueI18n from 'vue-i18n';
let i18nInstance = VueI18n;
export function useI18n() {
if (!i18nInstance) throw new Error('vue-i18n not initialized');
const i18n = i18nInstance;
const instance = getCurrentInstance();
const vm = instance?.proxy || instance || new Vue({});
const locale = computed({
get() {
return i18n.locale;
},
set(v) {
i18n.locale = v;
},
});
return {
locale,
t: vm.$t.bind(vm),
tc: vm.$tc.bind(vm),
d: vm.$d.bind(vm),
te: vm.$te.bind(vm),
n: vm.$n.bind(vm),
};
}