Merge branch 'develop' into feat/ui-lib

This commit is contained in:
Shivam Mishra
2026-02-09 16:51:26 +05:30
committed by GitHub
969 changed files with 30544 additions and 7332 deletions
@@ -45,14 +45,19 @@ export const handleContactOperationErrors = error => {
};
export const actions = {
search: async ({ commit }, { search, page, sortAttr, label }) => {
search: async (
{ commit },
{ search, page, sortAttr, label, append = false }
) => {
commit(types.SET_CONTACT_UI_FLAG, { isFetching: true });
try {
const {
data: { payload, meta },
} = await ContactAPI.search(search, page, sortAttr, label);
commit(types.CLEAR_CONTACTS);
commit(types.SET_CONTACTS, payload);
if (!append) {
commit(types.CLEAR_CONTACTS);
}
commit(append ? types.APPEND_CONTACTS : types.SET_CONTACTS, payload);
commit(types.SET_CONTACT_META, meta);
commit(types.SET_CONTACT_UI_FLAG, { isFetching: false });
} catch (error) {
@@ -6,6 +6,7 @@ const state = {
meta: {
count: 0,
currentPage: 1,
hasMore: false,
},
records: {},
uiFlags: {
@@ -15,9 +15,24 @@ export const mutations = {
},
[types.SET_CONTACT_META]: ($state, data) => {
const { count, current_page: currentPage } = data;
const { count, current_page: currentPage, has_more: hasMore } = data;
$state.meta.count = count;
$state.meta.currentPage = currentPage;
if (hasMore !== undefined) {
$state.meta.hasMore = hasMore;
}
},
[types.APPEND_CONTACTS]: ($state, data) => {
data.forEach(contact => {
$state.records[contact.id] = {
...($state.records[contact.id] || {}),
...contact,
};
if (!$state.sortOrder.includes(contact.id)) {
$state.sortOrder.push(contact.id);
}
});
},
[types.SET_CONTACTS]: ($state, data) => {
@@ -57,8 +57,9 @@ export const actions = {
});
}
},
async fullSearch({ commit, dispatch }, { q }) {
if (!q) {
async fullSearch({ commit, dispatch }, payload) {
const { q, ...filters } = payload;
if (!q && !Object.keys(filters).length) {
return;
}
commit(types.FULL_SEARCH_SET_UI_FLAG, {
@@ -67,10 +68,10 @@ export const actions = {
});
try {
await Promise.all([
dispatch('contactSearch', { q }),
dispatch('conversationSearch', { q }),
dispatch('messageSearch', { q }),
dispatch('articleSearch', { q }),
dispatch('contactSearch', { q, ...filters }),
dispatch('conversationSearch', { q, ...filters }),
dispatch('messageSearch', { q, ...filters }),
dispatch('articleSearch', { q, ...filters }),
]);
} catch (error) {
// Ignore error
@@ -81,10 +82,11 @@ export const actions = {
});
}
},
async contactSearch({ commit }, { q, page = 1 }) {
async contactSearch({ commit }, payload) {
const { page = 1, ...searchParams } = payload;
commit(types.CONTACT_SEARCH_SET_UI_FLAG, { isFetching: true });
try {
const { data } = await SearchAPI.contacts({ q, page });
const { data } = await SearchAPI.contacts({ ...searchParams, page });
commit(types.CONTACT_SEARCH_SET, data.payload.contacts);
} catch (error) {
// Ignore error
@@ -92,10 +94,11 @@ export const actions = {
commit(types.CONTACT_SEARCH_SET_UI_FLAG, { isFetching: false });
}
},
async conversationSearch({ commit }, { q, page = 1 }) {
async conversationSearch({ commit }, payload) {
const { page = 1, ...searchParams } = payload;
commit(types.CONVERSATION_SEARCH_SET_UI_FLAG, { isFetching: true });
try {
const { data } = await SearchAPI.conversations({ q, page });
const { data } = await SearchAPI.conversations({ ...searchParams, page });
commit(types.CONVERSATION_SEARCH_SET, data.payload.conversations);
} catch (error) {
// Ignore error
@@ -103,10 +106,11 @@ export const actions = {
commit(types.CONVERSATION_SEARCH_SET_UI_FLAG, { isFetching: false });
}
},
async messageSearch({ commit }, { q, page = 1 }) {
async messageSearch({ commit }, payload) {
const { page = 1, ...searchParams } = payload;
commit(types.MESSAGE_SEARCH_SET_UI_FLAG, { isFetching: true });
try {
const { data } = await SearchAPI.messages({ q, page });
const { data } = await SearchAPI.messages({ ...searchParams, page });
commit(types.MESSAGE_SEARCH_SET, data.payload.messages);
} catch (error) {
// Ignore error
@@ -114,10 +118,11 @@ export const actions = {
commit(types.MESSAGE_SEARCH_SET_UI_FLAG, { isFetching: false });
}
},
async articleSearch({ commit }, { q, page = 1 }) {
async articleSearch({ commit }, payload) {
const { page = 1, ...searchParams } = payload;
commit(types.ARTICLE_SEARCH_SET_UI_FLAG, { isFetching: true });
try {
const { data } = await SearchAPI.articles({ q, page });
const { data } = await SearchAPI.articles({ ...searchParams, page });
commit(types.ARTICLE_SEARCH_SET, data.payload.articles);
} catch (error) {
// Ignore error
@@ -240,9 +240,21 @@ const actions = {
toggleStatus: async (
{ commit },
{ conversationId, status, snoozedUntil = null }
{ conversationId, status, snoozedUntil = null, customAttributes = null }
) => {
try {
// Update custom attributes first if provided
if (customAttributes) {
await ConversationApi.updateCustomAttributes({
conversationId,
customAttributes,
});
commit(types.UPDATE_CONVERSATION_CUSTOM_ATTRIBUTES, {
conversationId,
customAttributes,
});
}
const {
data: {
payload: {
@@ -459,7 +471,10 @@ const actions = {
customAttributes,
});
const { custom_attributes } = response.data;
commit(types.UPDATE_CONVERSATION_CUSTOM_ATTRIBUTES, custom_attributes);
commit(types.UPDATE_CONVERSATION_CUSTOM_ATTRIBUTES, {
conversationId,
customAttributes: custom_attributes,
});
} catch (error) {
// Handle error
}
@@ -78,7 +78,6 @@ const getValueFromConversation = (conversation, attributeKey) => {
case 'team_id':
return conversation.meta?.team?.id;
case 'browser_language':
case 'country_code':
case 'referer':
return conversation.additional_attributes?.[attributeKey];
default:
@@ -121,9 +121,19 @@ export const mutations = {
chat.priority = priority;
},
[types.UPDATE_CONVERSATION_CUSTOM_ATTRIBUTES](_state, custom_attributes) {
const [chat] = getSelectedChatConversation(_state);
chat.custom_attributes = custom_attributes;
[types.UPDATE_CONVERSATION_CUSTOM_ATTRIBUTES](
_state,
{ conversationId, customAttributes }
) {
const conversation = _state.allConversations.find(
c => c.id === conversationId
);
if (conversation) {
conversation.custom_attributes = {
...conversation.custom_attributes,
...customAttributes,
};
}
},
[types.CHANGE_CONVERSATION_STATUS](
@@ -82,6 +82,9 @@ export const getters = {
),
};
},
getRatingCount(_state) {
return _state.metrics.ratingsCount;
},
};
export const actions = {
@@ -115,6 +118,13 @@ export const actions = {
});
});
},
update: async ({ commit }, { id, reviewNotes }) => {
const response = await CSATReports.update(id, {
csat_review_notes: reviewNotes,
});
commit(types.UPDATE_CSAT_RESPONSE, response.data);
return response.data;
},
};
export const mutations = {
@@ -144,6 +154,7 @@ export const mutations = {
};
_state.metrics.totalSentMessagesCount = totalSentMessagesCount || 0;
},
[types.UPDATE_CSAT_RESPONSE]: MutationHelpers.update,
};
export default {
@@ -83,6 +83,14 @@ export const getters = {
return false;
}
// Filter out CSAT templates (customer_satisfaction_survey and its versions)
if (
template.name &&
template.name.startsWith('customer_satisfaction_survey')
) {
return false;
}
// Filter out interactive templates (LIST, PRODUCT, CATALOG), location templates, and call permission templates
const hasUnsupportedComponents = template.components.some(
component =>
@@ -344,6 +352,14 @@ export const actions = {
throw new Error(error);
}
},
createCSATTemplate: async (_, { inboxId, template }) => {
const response = await InboxesAPI.createCSATTemplate(inboxId, template);
return response.data;
},
getCSATTemplateStatus: async (_, { inboxId }) => {
const response = await InboxesAPI.getCSATTemplateStatus(inboxId);
return response.data;
},
};
export const mutations = {
@@ -27,7 +27,7 @@ export const getters = {
.sort((a, b) => a.title.localeCompare(b.title));
},
getLabelById: _state => id => {
return _state.records.find(record => record.id === Number(id));
return _state.records.find(record => record.id === Number(id)) || {};
},
};
@@ -234,6 +234,19 @@ export const actions = {
console.error(error);
});
},
downloadConversationsSummaryReports(_, reportObj) {
return Report.getConversationsSummaryReports(reportObj)
.then(response => {
downloadCsvFile(reportObj.fileName, response.data);
AnalyticsHelper.track(REPORTS_EVENTS.DOWNLOAD_REPORT, {
reportType: 'conversations_summary',
businessHours: reportObj?.businessHours,
});
})
.catch(error => {
console.error(error);
});
},
downloadLabelReports(_, reportObj) {
return Report.getLabelReports(reportObj)
.then(response => {
@@ -77,6 +77,16 @@ describe('#actions', () => {
expect(dispatch).toHaveBeenCalledWith('messageSearch', { q: 'test' });
expect(dispatch).toHaveBeenCalledWith('articleSearch', { q: 'test' });
});
it('should pass filters to all search actions including articleSearch', async () => {
const payload = { q: 'test', since: 1700000000, until: 1732000000 };
await actions.fullSearch({ commit, dispatch }, payload);
expect(dispatch).toHaveBeenCalledWith('contactSearch', payload);
expect(dispatch).toHaveBeenCalledWith('conversationSearch', payload);
expect(dispatch).toHaveBeenCalledWith('messageSearch', payload);
expect(dispatch).toHaveBeenCalledWith('articleSearch', payload);
});
});
describe('#contactSearch', () => {
@@ -165,6 +175,22 @@ describe('#actions', () => {
]);
});
it('should handle article search with date filters', async () => {
axios.get.mockResolvedValue({
data: { payload: { articles: [{ id: 1 }] } },
});
await actions.articleSearch(
{ commit },
{ q: 'test', page: 1, since: 1700000000, until: 1732000000 }
);
expect(commit.mock.calls).toEqual([
[types.ARTICLE_SEARCH_SET_UI_FLAG, { isFetching: true }],
[types.ARTICLE_SEARCH_SET, [{ id: 1 }]],
[types.ARTICLE_SEARCH_SET_UI_FLAG, { isFetching: false }],
]);
});
it('should handle failed article search', async () => {
axios.get.mockRejectedValue({});
await actions.articleSearch({ commit }, { q: 'test' });
@@ -548,7 +548,13 @@ describe('#deleteMessage', () => {
}
);
expect(commit.mock.calls).toEqual([
[types.UPDATE_CONVERSATION_CUSTOM_ATTRIBUTES, { order_d: '1001' }],
[
types.UPDATE_CONVERSATION_CUSTOM_ATTRIBUTES,
{
conversationId: 1,
customAttributes: { order_d: '1001' },
},
],
]);
});
});
@@ -239,14 +239,14 @@ describe('#mutations', () => {
describe('#UPDATE_CONVERSATION_CUSTOM_ATTRIBUTES', () => {
it('update conversation custom attributes', () => {
const custom_attributes = { order_id: 1001 };
const state = { allConversations: [{ id: 1 }], selectedChatId: 1 };
const state = { allConversations: [{ id: 1, custom_attributes: {} }] };
mutations[types.UPDATE_CONVERSATION_CUSTOM_ATTRIBUTES](state, {
conversationId: 1,
custom_attributes,
customAttributes: custom_attributes,
});
expect(
state.allConversations[0].custom_attributes.custom_attributes
).toEqual(custom_attributes);
expect(state.allConversations[0].custom_attributes).toEqual(
custom_attributes
);
});
});
});
@@ -86,4 +86,19 @@ describe('#getters', () => {
})
).toEqual('50.00');
});
it('getRatingCount', () => {
const state = {
metrics: {
ratingsCount: { 1: 10, 2: 20, 3: 15, 4: 3, 5: 2 },
},
};
expect(getters.getRatingCount(state)).toEqual({
1: 10,
2: 20,
3: 15,
4: 3,
5: 2,
});
});
});
@@ -201,4 +201,24 @@ describe('#actions', () => {
);
});
});
describe('#downloadConversationsSummaryReports', () => {
it('open CSV download prompt if API is success', async () => {
const data = `Conversations,Messages received,Messages sent,Avg first response time,Avg resolution time,Resolution count,Avg customer waiting time
217,323,623,23 hours 22 minutes,179 days 18 hours,30,48 days 4 hours`;
axios.get.mockResolvedValue({ data });
const param = {
from: 1631039400,
to: 1635013800,
fileName: 'conversations-summary-report-24-10-2021.csv',
};
actions.downloadConversationsSummaryReports(1, param);
await flushPromises();
expect(DownloadHelper.downloadCsvFile).toBeCalledWith(
param.fileName,
data
);
});
});
});
@@ -141,12 +141,14 @@ describe('Summary Reports Store', () => {
});
});
it('should handle errors gracefully', async () => {
it('should reset uiFlags and rethrow error on failure', async () => {
SummaryReportsAPI.getInboxReports.mockRejectedValue(
new Error('API Error')
);
await store.actions.fetchInboxSummaryReports({ commit }, {});
await expect(
store.actions.fetchInboxSummaryReports({ commit }, {})
).rejects.toThrow('API Error');
expect(commit).toHaveBeenCalledWith('setUIFlags', {
isFetchingInboxSummaryReports: false,
@@ -28,15 +28,17 @@ async function fetchSummaryReports(type, params, { commit }) {
const config = typeMap[type];
if (!config) return;
let error = null;
try {
commit('setUIFlags', { [config.flagKey]: true });
const response = await SummaryReportsAPI[config.apiMethod](params);
commit(config.mutationKey, camelcaseKeys(response.data, { deep: true }));
} catch (error) {
// Ignore error
} catch (e) {
error = e;
} finally {
commit('setUIFlags', { [config.flagKey]: false });
}
if (error) throw error;
}
export const initialState = {