Merge branch 'develop' into feat/ui-lib
This commit is contained in:
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user