Merge branch 'develop' into fix/conversation-tabs-limit
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
import CaptainCustomTools from 'dashboard/api/captain/customTools';
|
||||
import { createStore } from './storeFactory';
|
||||
import { throwErrorMessage } from 'dashboard/store/utils/api';
|
||||
|
||||
export default createStore({
|
||||
name: 'CaptainCustomTool',
|
||||
API: CaptainCustomTools,
|
||||
actions: mutations => ({
|
||||
update: async ({ commit }, { id, ...updateObj }) => {
|
||||
commit(mutations.SET_UI_FLAG, { updatingItem: true });
|
||||
try {
|
||||
const response = await CaptainCustomTools.update(id, updateObj);
|
||||
commit(mutations.EDIT, response.data);
|
||||
commit(mutations.SET_UI_FLAG, { updatingItem: false });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
commit(mutations.SET_UI_FLAG, { updatingItem: false });
|
||||
return throwErrorMessage(error);
|
||||
}
|
||||
},
|
||||
|
||||
delete: async ({ commit }, id) => {
|
||||
commit(mutations.SET_UI_FLAG, { deletingItem: true });
|
||||
try {
|
||||
await CaptainCustomTools.delete(id);
|
||||
commit(mutations.DELETE, id);
|
||||
commit(mutations.SET_UI_FLAG, { deletingItem: false });
|
||||
return id;
|
||||
} catch (error) {
|
||||
commit(mutations.SET_UI_FLAG, { deletingItem: false });
|
||||
return throwErrorMessage(error);
|
||||
}
|
||||
},
|
||||
}),
|
||||
});
|
||||
@@ -1,9 +1,22 @@
|
||||
import CaptainResponseAPI from 'dashboard/api/captain/response';
|
||||
import { createStore } from './storeFactory';
|
||||
|
||||
const SET_PENDING_COUNT = 'SET_PENDING_COUNT';
|
||||
|
||||
export default createStore({
|
||||
name: 'CaptainResponse',
|
||||
API: CaptainResponseAPI,
|
||||
getters: {
|
||||
getPendingCount: state => state.meta.pendingCount || 0,
|
||||
},
|
||||
mutations: {
|
||||
[SET_PENDING_COUNT](state, count) {
|
||||
state.meta = {
|
||||
...state.meta,
|
||||
pendingCount: Number(count),
|
||||
};
|
||||
},
|
||||
},
|
||||
actions: mutations => ({
|
||||
removeBulkResponses: ({ commit, state }, ids) => {
|
||||
const updatedRecords = state.records.filter(
|
||||
@@ -28,5 +41,18 @@ export default createStore({
|
||||
|
||||
commit(mutations.SET, updatedRecords);
|
||||
},
|
||||
fetchPendingCount: async ({ commit }, assistantId) => {
|
||||
try {
|
||||
const response = await CaptainResponseAPI.get({
|
||||
status: 'pending',
|
||||
page: 1,
|
||||
assistantId,
|
||||
});
|
||||
const count = response.data?.meta?.total_count || 0;
|
||||
commit(SET_PENDING_COUNT, count);
|
||||
} catch (error) {
|
||||
commit(SET_PENDING_COUNT, 0);
|
||||
}
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -49,6 +49,7 @@ export const createMutations = mutationTypes => ({
|
||||
},
|
||||
[mutationTypes.SET_META](state, meta) {
|
||||
state.meta = {
|
||||
...state.meta,
|
||||
totalCount: Number(meta.total_count),
|
||||
page: Number(meta.page),
|
||||
};
|
||||
@@ -69,7 +70,7 @@ export const createCrudActions = (API, mutationTypes) => ({
|
||||
});
|
||||
|
||||
export const createStore = options => {
|
||||
const { name, API, actions, getters } = options;
|
||||
const { name, API, actions, getters, mutations } = options;
|
||||
const mutationTypes = generateMutationTypes(name);
|
||||
|
||||
const customActions = actions ? actions(mutationTypes) : {};
|
||||
@@ -81,7 +82,10 @@ export const createStore = options => {
|
||||
...createGetters(),
|
||||
...(getters || {}),
|
||||
},
|
||||
mutations: createMutations(mutationTypes),
|
||||
mutations: {
|
||||
...createMutations(mutationTypes),
|
||||
...(mutations || {}),
|
||||
},
|
||||
actions: {
|
||||
...createCrudActions(API, mutationTypes),
|
||||
...customActions,
|
||||
|
||||
@@ -3,7 +3,7 @@ import CaptainToolsAPI from '../../api/captain/tools';
|
||||
import { throwErrorMessage } from 'dashboard/store/utils/api';
|
||||
|
||||
const toolsStore = createStore({
|
||||
name: 'captainTool',
|
||||
name: 'Tools',
|
||||
API: CaptainToolsAPI,
|
||||
actions: mutations => ({
|
||||
getTools: async ({ commit }) => {
|
||||
|
||||
@@ -57,6 +57,7 @@ import copilotThreads from './captain/copilotThreads';
|
||||
import copilotMessages from './captain/copilotMessages';
|
||||
import captainScenarios from './captain/scenarios';
|
||||
import captainTools from './captain/tools';
|
||||
import captainCustomTools from './captain/customTools';
|
||||
|
||||
const plugins = [];
|
||||
|
||||
@@ -119,6 +120,7 @@ export default createStore({
|
||||
copilotMessages,
|
||||
captainScenarios,
|
||||
captainTools,
|
||||
captainCustomTools,
|
||||
},
|
||||
plugins,
|
||||
});
|
||||
|
||||
@@ -25,18 +25,18 @@ const fetchMetaData = async (commit, params) => {
|
||||
}
|
||||
};
|
||||
|
||||
const debouncedFetchMetaData = debounce(fetchMetaData, 500, false, 1000);
|
||||
const longDebouncedFetchMetaData = debounce(fetchMetaData, 500, false, 5000);
|
||||
const debouncedFetchMetaData = debounce(fetchMetaData, 500, false, 1500);
|
||||
const longDebouncedFetchMetaData = debounce(fetchMetaData, 5000, false, 10000);
|
||||
const superLongDebouncedFetchMetaData = debounce(
|
||||
fetchMetaData,
|
||||
1500,
|
||||
10000,
|
||||
false,
|
||||
10000
|
||||
20000
|
||||
);
|
||||
|
||||
export const actions = {
|
||||
get: async ({ commit, state: $state }, params) => {
|
||||
if ($state.allCount > 10000) {
|
||||
if ($state.allCount > 5000) {
|
||||
superLongDebouncedFetchMetaData(commit, params);
|
||||
} else if ($state.allCount > 100) {
|
||||
longDebouncedFetchMetaData(commit, params);
|
||||
|
||||
@@ -154,7 +154,10 @@ const equalTo = (filterValue, conversationValue) => {
|
||||
* It only works with string values and returns false for non-string types.
|
||||
*/
|
||||
const contains = (filterValue, conversationValue) => {
|
||||
if (typeof conversationValue === 'string') {
|
||||
if (
|
||||
typeof conversationValue === 'string' &&
|
||||
typeof filterValue === 'string'
|
||||
) {
|
||||
return conversationValue.toLowerCase().includes(filterValue.toLowerCase());
|
||||
}
|
||||
return false;
|
||||
@@ -190,10 +193,8 @@ const compareDates = (conversationValue, filterValue, compareFn) => {
|
||||
const matchesCondition = (conversationValue, filter) => {
|
||||
const { filter_operator: filterOperator, values } = filter;
|
||||
|
||||
// Handle null/undefined values
|
||||
if (conversationValue === null || conversationValue === undefined) {
|
||||
return filterOperator === 'is_not_present';
|
||||
}
|
||||
const isNullish =
|
||||
conversationValue === null || conversationValue === undefined;
|
||||
|
||||
const filterValue = Array.isArray(values)
|
||||
? values.map(resolveValue)
|
||||
@@ -213,10 +214,10 @@ const matchesCondition = (conversationValue, filter) => {
|
||||
return !contains(filterValue, conversationValue);
|
||||
|
||||
case 'is_present':
|
||||
return true; // We already handled null/undefined above
|
||||
return !isNullish;
|
||||
|
||||
case 'is_not_present':
|
||||
return false; // We already handled null/undefined above
|
||||
return isNullish;
|
||||
|
||||
case 'is_greater_than':
|
||||
return compareDates(conversationValue, filterValue, (a, b) => a > b);
|
||||
@@ -225,6 +226,10 @@ const matchesCondition = (conversationValue, filter) => {
|
||||
return compareDates(conversationValue, filterValue, (a, b) => a < b);
|
||||
|
||||
case 'days_before': {
|
||||
if (isNullish) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const today = new Date();
|
||||
const daysInMilliseconds = filterValue * 24 * 60 * 60 * 1000;
|
||||
const targetDate = new Date(today.getTime() - daysInMilliseconds);
|
||||
|
||||
+78
@@ -192,6 +192,32 @@ describe('filterHelpers', () => {
|
||||
expect(matchesFilters(conversation, filters)).toBe(true);
|
||||
});
|
||||
|
||||
it('should not match conversation with equal_to operator when assignee is null', () => {
|
||||
const conversation = { meta: { assignee: null } };
|
||||
const filters = [
|
||||
{
|
||||
attribute_key: 'assignee_id',
|
||||
filter_operator: 'equal_to',
|
||||
values: { id: 1, name: 'John Doe' },
|
||||
query_operator: 'and',
|
||||
},
|
||||
];
|
||||
expect(matchesFilters(conversation, filters)).toBe(false);
|
||||
});
|
||||
|
||||
it('should match conversation with not_equal_to operator when assignee is null', () => {
|
||||
const conversation = { meta: { assignee: null } };
|
||||
const filters = [
|
||||
{
|
||||
attribute_key: 'assignee_id',
|
||||
filter_operator: 'not_equal_to',
|
||||
values: { id: 1, name: 'John Doe' },
|
||||
query_operator: 'and',
|
||||
},
|
||||
];
|
||||
expect(matchesFilters(conversation, filters)).toBe(true);
|
||||
});
|
||||
|
||||
it('should match conversation with is_not_present operator for assignee_id', () => {
|
||||
const conversation = { meta: { assignee: null } };
|
||||
const filters = [
|
||||
@@ -285,6 +311,58 @@ describe('filterHelpers', () => {
|
||||
expect(matchesFilters(conversation, filters)).toBe(false);
|
||||
});
|
||||
|
||||
it('should not match contains operator when display_id is null', () => {
|
||||
const conversation = { id: null };
|
||||
const filters = [
|
||||
{
|
||||
attribute_key: 'display_id',
|
||||
filter_operator: 'contains',
|
||||
values: '234',
|
||||
query_operator: 'and',
|
||||
},
|
||||
];
|
||||
expect(matchesFilters(conversation, filters)).toBe(false);
|
||||
});
|
||||
|
||||
it('should not match contains operator when filter value is null', () => {
|
||||
const conversation = { id: '12345' };
|
||||
const filters = [
|
||||
{
|
||||
attribute_key: 'display_id',
|
||||
filter_operator: 'contains',
|
||||
values: null,
|
||||
query_operator: 'and',
|
||||
},
|
||||
];
|
||||
expect(matchesFilters(conversation, filters)).toBe(false);
|
||||
});
|
||||
|
||||
it('should match does_not_contain operator when display_id is null', () => {
|
||||
const conversation = { id: null };
|
||||
const filters = [
|
||||
{
|
||||
attribute_key: 'display_id',
|
||||
filter_operator: 'does_not_contain',
|
||||
values: '234',
|
||||
query_operator: 'and',
|
||||
},
|
||||
];
|
||||
expect(matchesFilters(conversation, filters)).toBe(true);
|
||||
});
|
||||
|
||||
it('should match does_not_contain operator when filter value is null', () => {
|
||||
const conversation = { id: '12345' };
|
||||
const filters = [
|
||||
{
|
||||
attribute_key: 'display_id',
|
||||
filter_operator: 'does_not_contain',
|
||||
values: null,
|
||||
query_operator: 'and',
|
||||
},
|
||||
];
|
||||
expect(matchesFilters(conversation, filters)).toBe(true);
|
||||
});
|
||||
|
||||
it('should match conversation with does_not_contain operator when value is not present', () => {
|
||||
const conversation = { id: '12345' };
|
||||
const filters = [
|
||||
|
||||
@@ -78,6 +78,11 @@ export const getters = {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filter out authentication templates
|
||||
if (template.category === 'AUTHENTICATION') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filter out interactive templates (LIST, PRODUCT, CATALOG), location templates, and call permission templates
|
||||
const hasUnsupportedComponents = template.components.some(
|
||||
component =>
|
||||
|
||||
@@ -57,11 +57,13 @@ const state = {
|
||||
uiFlags: {
|
||||
isFetchingAccountConversationMetric: false,
|
||||
isFetchingAccountConversationsHeatmap: false,
|
||||
isFetchingAccountResolutionsHeatmap: false,
|
||||
isFetchingAgentConversationMetric: false,
|
||||
isFetchingTeamConversationMetric: false,
|
||||
},
|
||||
accountConversationMetric: {},
|
||||
accountConversationHeatmap: [],
|
||||
accountResolutionHeatmap: [],
|
||||
agentConversationMetric: [],
|
||||
teamConversationMetric: [],
|
||||
},
|
||||
@@ -89,6 +91,9 @@ const getters = {
|
||||
getAccountConversationHeatmapData(_state) {
|
||||
return _state.overview.accountConversationHeatmap;
|
||||
},
|
||||
getAccountResolutionHeatmapData(_state) {
|
||||
return _state.overview.accountResolutionHeatmap;
|
||||
},
|
||||
getAgentConversationMetric(_state) {
|
||||
return _state.overview.agentConversationMetric;
|
||||
},
|
||||
@@ -130,6 +135,16 @@ export const actions = {
|
||||
commit(types.default.TOGGLE_HEATMAP_LOADING, false);
|
||||
});
|
||||
},
|
||||
fetchAccountResolutionHeatmap({ commit }, reportObj) {
|
||||
commit(types.default.TOGGLE_RESOLUTION_HEATMAP_LOADING, true);
|
||||
Report.getReports({ ...reportObj, groupBy: 'hour' }).then(heatmapData => {
|
||||
let { data } = heatmapData;
|
||||
data = clampDataBetweenTimeline(data, reportObj.from, reportObj.to);
|
||||
|
||||
commit(types.default.SET_RESOLUTION_HEATMAP_DATA, data);
|
||||
commit(types.default.TOGGLE_RESOLUTION_HEATMAP_LOADING, false);
|
||||
});
|
||||
},
|
||||
fetchAccountSummary({ commit }, reportObj) {
|
||||
commit(types.default.SET_ACCOUNT_SUMMARY_STATUS, STATUS.FETCHING);
|
||||
Report.getSummary(
|
||||
@@ -287,6 +302,9 @@ const mutations = {
|
||||
[types.default.SET_HEATMAP_DATA](_state, heatmapData) {
|
||||
_state.overview.accountConversationHeatmap = heatmapData;
|
||||
},
|
||||
[types.default.SET_RESOLUTION_HEATMAP_DATA](_state, heatmapData) {
|
||||
_state.overview.accountResolutionHeatmap = heatmapData;
|
||||
},
|
||||
[types.default.TOGGLE_ACCOUNT_REPORT_LOADING](_state, { metric, value }) {
|
||||
_state.accountReport.isFetching[metric] = value;
|
||||
},
|
||||
@@ -299,6 +317,9 @@ const mutations = {
|
||||
[types.default.TOGGLE_HEATMAP_LOADING](_state, flag) {
|
||||
_state.overview.uiFlags.isFetchingAccountConversationsHeatmap = flag;
|
||||
},
|
||||
[types.default.TOGGLE_RESOLUTION_HEATMAP_LOADING](_state, flag) {
|
||||
_state.overview.uiFlags.isFetchingAccountResolutionsHeatmap = flag;
|
||||
},
|
||||
[types.default.SET_ACCOUNT_SUMMARY](_state, summaryData) {
|
||||
_state.accountSummary = summaryData;
|
||||
},
|
||||
|
||||
@@ -265,6 +265,39 @@ describe('#getters', () => {
|
||||
expect(result[0].name).toBe('regular_template');
|
||||
});
|
||||
|
||||
it('filters out authentication templates', () => {
|
||||
const authenticationTemplates = [
|
||||
{
|
||||
name: 'auth_template',
|
||||
status: 'approved',
|
||||
category: 'AUTHENTICATION',
|
||||
components: [
|
||||
{ type: 'BODY', text: 'Your verification code is {{1}}' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'regular_template',
|
||||
status: 'approved',
|
||||
category: 'MARKETING',
|
||||
components: [{ type: 'BODY', text: 'Regular message' }],
|
||||
},
|
||||
];
|
||||
|
||||
const state = {
|
||||
records: [
|
||||
{
|
||||
id: 1,
|
||||
channel_type: 'Channel::Whatsapp',
|
||||
message_templates: authenticationTemplates,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = getters.getFilteredWhatsAppTemplates(state)(1);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe('regular_template');
|
||||
});
|
||||
|
||||
it('returns valid templates from fixture data', () => {
|
||||
const state = {
|
||||
records: [
|
||||
|
||||
@@ -188,6 +188,8 @@ export default {
|
||||
SET_ACCOUNT_REPORTS: 'SET_ACCOUNT_REPORTS',
|
||||
SET_HEATMAP_DATA: 'SET_HEATMAP_DATA',
|
||||
TOGGLE_HEATMAP_LOADING: 'TOGGLE_HEATMAP_LOADING',
|
||||
SET_RESOLUTION_HEATMAP_DATA: 'SET_RESOLUTION_HEATMAP_DATA',
|
||||
TOGGLE_RESOLUTION_HEATMAP_LOADING: 'TOGGLE_RESOLUTION_HEATMAP_LOADING',
|
||||
SET_ACCOUNT_SUMMARY: 'SET_ACCOUNT_SUMMARY',
|
||||
SET_BOT_SUMMARY: 'SET_BOT_SUMMARY',
|
||||
TOGGLE_ACCOUNT_REPORT_LOADING: 'TOGGLE_ACCOUNT_REPORT_LOADING',
|
||||
|
||||
Reference in New Issue
Block a user