## Description Adds the account-level `unread_count_for_filters` feature flag as the dark-launch gate for filtered sidebar unread counts. This reuses the deprecated `quoted_email_reply` flag slot, resets the reused bit for existing accounts, and removes stale defaults so new accounts do not reference the old flag. This also adds the feature where we are now calculating the unread counts for built in filters like mentions, participating and unattended along with unread count for saved filters/folders. Closes [CW-7262](https://linear.app/chatwoot/issue/CW-7262/unread-counts-for-filters-folders) ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update
206 lines
6.3 KiB
JavaScript
206 lines
6.3 KiB
JavaScript
import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
|
|
import types from '../mutation-types';
|
|
import CustomViewsAPI from '../../api/customViews';
|
|
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
|
|
|
const VIEW_TYPES = {
|
|
CONVERSATION: 'conversation',
|
|
CONTACT: 'contact',
|
|
};
|
|
const FILTERED_UNREAD_COUNTS_REFRESH_RETRY_MS = 30000;
|
|
const FILTERED_UNREAD_COUNTS_REFRESH_RETRY_JITTER_MS = 15000;
|
|
const getFilteredUnreadCountsRefreshRetryDelay = () =>
|
|
FILTERED_UNREAD_COUNTS_REFRESH_RETRY_MS +
|
|
Math.random() * FILTERED_UNREAD_COUNTS_REFRESH_RETRY_JITTER_MS;
|
|
|
|
// use to normalize the filter type
|
|
const FILTER_KEYS = {
|
|
0: VIEW_TYPES.CONVERSATION,
|
|
1: VIEW_TYPES.CONTACT,
|
|
[VIEW_TYPES.CONVERSATION]: VIEW_TYPES.CONVERSATION,
|
|
[VIEW_TYPES.CONTACT]: VIEW_TYPES.CONTACT,
|
|
};
|
|
|
|
// a folder's contact_id filter stores only the id, extract it so the
|
|
// contact can be fetched and its name shown in the edit folder modal
|
|
const getFolderContactId = folder =>
|
|
folder?.query?.payload?.find(filter => filter.attribute_key === 'contact_id')
|
|
?.values?.[0];
|
|
|
|
const hasFeatureEnabled = (rootGetters, featureFlag) => {
|
|
const accountId = rootGetters?.getCurrentAccountId;
|
|
const isFeatureEnabled = rootGetters?.['accounts/isFeatureEnabledonAccount'];
|
|
|
|
return Boolean(accountId && isFeatureEnabled?.(accountId, featureFlag));
|
|
};
|
|
|
|
const shouldRefreshConversationUnreadCounts = (filterType, rootGetters) => {
|
|
return (
|
|
FILTER_KEYS[filterType] === VIEW_TYPES.CONVERSATION &&
|
|
hasFeatureEnabled(rootGetters, FEATURE_FLAGS.CONVERSATION_UNREAD_COUNTS) &&
|
|
hasFeatureEnabled(rootGetters, FEATURE_FLAGS.UNREAD_COUNT_FOR_FILTERS)
|
|
);
|
|
};
|
|
|
|
const dispatchConversationUnreadCounts = dispatch => {
|
|
dispatch('conversationUnreadCounts/get', {}, { root: true });
|
|
};
|
|
|
|
const refreshConversationUnreadCounts = (
|
|
{ dispatch, rootGetters },
|
|
filterType
|
|
) => {
|
|
if (!shouldRefreshConversationUnreadCounts(filterType, rootGetters)) return;
|
|
|
|
dispatchConversationUnreadCounts(dispatch);
|
|
setTimeout(
|
|
() => dispatchConversationUnreadCounts(dispatch),
|
|
getFilteredUnreadCountsRefreshRetryDelay()
|
|
);
|
|
};
|
|
|
|
export const state = {
|
|
[VIEW_TYPES.CONVERSATION]: {
|
|
records: [],
|
|
},
|
|
[VIEW_TYPES.CONTACT]: {
|
|
records: [],
|
|
},
|
|
uiFlags: {
|
|
isFetching: false,
|
|
isCreating: false,
|
|
isDeleting: false,
|
|
},
|
|
activeConversationFolder: null,
|
|
};
|
|
|
|
export const getters = {
|
|
getUIFlags(_state) {
|
|
return _state.uiFlags;
|
|
},
|
|
getCustomViewsByFilterType: _state => key => {
|
|
const filterType = FILTER_KEYS[key];
|
|
return _state[filterType].records;
|
|
},
|
|
getConversationCustomViews(_state) {
|
|
return _state[VIEW_TYPES.CONVERSATION].records;
|
|
},
|
|
getContactCustomViews(_state) {
|
|
return _state[VIEW_TYPES.CONTACT].records;
|
|
},
|
|
getActiveConversationFolder(_state) {
|
|
return _state.activeConversationFolder;
|
|
},
|
|
getActiveFolderContactId(_state) {
|
|
return getFolderContactId(_state.activeConversationFolder);
|
|
},
|
|
};
|
|
|
|
export const actions = {
|
|
get: async function getCustomViews({ commit }, filterType) {
|
|
commit(types.SET_CUSTOM_VIEW_UI_FLAG, { isFetching: true });
|
|
try {
|
|
const response =
|
|
await CustomViewsAPI.getCustomViewsByFilterType(filterType);
|
|
commit(types.SET_CUSTOM_VIEW, { data: response.data, filterType });
|
|
} catch (error) {
|
|
// Ignore error
|
|
} finally {
|
|
commit(types.SET_CUSTOM_VIEW_UI_FLAG, { isFetching: false });
|
|
}
|
|
},
|
|
create: async function createCustomViews(
|
|
{ commit, dispatch, rootGetters },
|
|
obj
|
|
) {
|
|
commit(types.SET_CUSTOM_VIEW_UI_FLAG, { isCreating: true });
|
|
try {
|
|
const response = await CustomViewsAPI.create(obj);
|
|
const filterType = FILTER_KEYS[obj.filter_type];
|
|
commit(types.ADD_CUSTOM_VIEW, {
|
|
data: response.data,
|
|
filterType,
|
|
});
|
|
refreshConversationUnreadCounts({ dispatch, rootGetters }, filterType);
|
|
return response;
|
|
} catch (error) {
|
|
const errorMessage = error?.response?.data?.message;
|
|
throw new Error(errorMessage);
|
|
} finally {
|
|
commit(types.SET_CUSTOM_VIEW_UI_FLAG, { isCreating: false });
|
|
}
|
|
},
|
|
update: async function updateCustomViews(
|
|
{ commit, dispatch, rootGetters },
|
|
obj
|
|
) {
|
|
commit(types.SET_CUSTOM_VIEW_UI_FLAG, { isCreating: true });
|
|
try {
|
|
const response = await CustomViewsAPI.update(obj.id, obj);
|
|
const filterType = FILTER_KEYS[obj.filter_type];
|
|
commit(types.UPDATE_CUSTOM_VIEW, {
|
|
data: response.data,
|
|
filterType,
|
|
});
|
|
refreshConversationUnreadCounts({ dispatch, rootGetters }, filterType);
|
|
} catch (error) {
|
|
const errorMessage = error?.response?.data?.message;
|
|
throw new Error(errorMessage);
|
|
} finally {
|
|
commit(types.SET_CUSTOM_VIEW_UI_FLAG, { isCreating: false });
|
|
}
|
|
},
|
|
delete: async ({ commit, dispatch, rootGetters }, { id, filterType }) => {
|
|
commit(types.SET_CUSTOM_VIEW_UI_FLAG, { isDeleting: true });
|
|
try {
|
|
await CustomViewsAPI.deleteCustomViews(id, filterType);
|
|
commit(types.DELETE_CUSTOM_VIEW, { data: id, filterType });
|
|
refreshConversationUnreadCounts({ dispatch, rootGetters }, filterType);
|
|
} catch (error) {
|
|
throw new Error(error);
|
|
} finally {
|
|
commit(types.SET_CUSTOM_VIEW_UI_FLAG, { isDeleting: false });
|
|
}
|
|
},
|
|
setActiveConversationFolder({ commit, dispatch }, data) {
|
|
commit(types.SET_ACTIVE_CONVERSATION_FOLDER, data);
|
|
// prefetch the contact of a contact filter so the UI can show its name
|
|
const contactId = getFolderContactId(data);
|
|
if (contactId) dispatch('contacts/show', { id: contactId }, { root: true });
|
|
},
|
|
};
|
|
|
|
export const mutations = {
|
|
[types.SET_CUSTOM_VIEW_UI_FLAG](_state, data) {
|
|
_state.uiFlags = {
|
|
..._state.uiFlags,
|
|
...data,
|
|
};
|
|
},
|
|
|
|
[types.ADD_CUSTOM_VIEW]: (_state, { data, filterType }) => {
|
|
MutationHelpers.create(_state[filterType], data);
|
|
},
|
|
[types.SET_CUSTOM_VIEW]: (_state, { data, filterType }) => {
|
|
MutationHelpers.set(_state[filterType], data);
|
|
},
|
|
[types.UPDATE_CUSTOM_VIEW]: (_state, { data, filterType }) => {
|
|
MutationHelpers.update(_state[filterType], data);
|
|
},
|
|
[types.DELETE_CUSTOM_VIEW]: (_state, { data, filterType }) => {
|
|
MutationHelpers.destroy(_state[filterType], data);
|
|
},
|
|
|
|
[types.SET_ACTIVE_CONVERSATION_FOLDER](_state, folder) {
|
|
_state.activeConversationFolder = folder;
|
|
},
|
|
};
|
|
|
|
export default {
|
|
namespaced: true,
|
|
actions,
|
|
state,
|
|
getters,
|
|
mutations,
|
|
};
|