Merge branch 'develop' into feat/ui-lib
This commit is contained in:
@@ -63,8 +63,11 @@ export const actions = {
|
||||
});
|
||||
}
|
||||
},
|
||||
update: async ({ commit }, updateObj) => {
|
||||
commit(types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: true });
|
||||
update: async ({ commit }, { options, ...updateObj }) => {
|
||||
if (options?.silent !== true) {
|
||||
commit(types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: true });
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await AccountAPI.update('', updateObj);
|
||||
commit(types.default.EDIT_ACCOUNT, response.data);
|
||||
|
||||
@@ -172,6 +172,17 @@ export const actions = {
|
||||
commit(types.SET_AGENT_BOT_UI_FLAG, { isDisconnecting: false });
|
||||
}
|
||||
},
|
||||
|
||||
resetAccessToken: async ({ commit }, botId) => {
|
||||
try {
|
||||
const response = await AgentBotsAPI.resetAccessToken(botId);
|
||||
commit(types.EDIT_AGENT_BOT, response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throwErrorMessage(error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export const mutations = {
|
||||
|
||||
@@ -2,6 +2,8 @@ import types from '../mutation-types';
|
||||
import authAPI from '../../api/auth';
|
||||
|
||||
import { setUser, clearCookiesOnLogout } from '../utils/api';
|
||||
import SessionStorage from 'shared/helpers/sessionStorage';
|
||||
import { SESSION_STORAGE_KEYS } from 'dashboard/constants/sessionStorage';
|
||||
|
||||
const initialState = {
|
||||
currentUser: {
|
||||
@@ -148,8 +150,15 @@ export const actions = {
|
||||
updateUISettings: async ({ commit }, params) => {
|
||||
try {
|
||||
commit(types.SET_CURRENT_USER_UI_SETTINGS, params);
|
||||
const response = await authAPI.updateUISettings(params);
|
||||
commit(types.SET_CURRENT_USER, response.data);
|
||||
|
||||
const isImpersonating = SessionStorage.get(
|
||||
SESSION_STORAGE_KEYS.IMPERSONATION_USER
|
||||
);
|
||||
|
||||
if (!isImpersonating) {
|
||||
const response = await authAPI.updateUISettings(params);
|
||||
commit(types.SET_CURRENT_USER, response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore error
|
||||
}
|
||||
@@ -207,6 +216,16 @@ export const actions = {
|
||||
}
|
||||
},
|
||||
|
||||
resetAccessToken: async ({ commit }) => {
|
||||
try {
|
||||
const response = await authAPI.resetAccessToken();
|
||||
commit(types.SET_CURRENT_USER, response.data);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
resendConfirmation: async () => {
|
||||
try {
|
||||
await authAPI.resendConfirmation();
|
||||
|
||||
@@ -5,12 +5,14 @@ export const initialState = {
|
||||
contactRecords: [],
|
||||
conversationRecords: [],
|
||||
messageRecords: [],
|
||||
articleRecords: [],
|
||||
uiFlags: {
|
||||
isFetching: false,
|
||||
isSearchCompleted: false,
|
||||
contact: { isFetching: false },
|
||||
conversation: { isFetching: false },
|
||||
message: { isFetching: false },
|
||||
article: { isFetching: false },
|
||||
},
|
||||
};
|
||||
|
||||
@@ -27,6 +29,9 @@ export const getters = {
|
||||
getMessageRecords(state) {
|
||||
return state.messageRecords;
|
||||
},
|
||||
getArticleRecords(state) {
|
||||
return state.articleRecords;
|
||||
},
|
||||
getUIFlags(state) {
|
||||
return state.uiFlags;
|
||||
},
|
||||
@@ -65,6 +70,7 @@ export const actions = {
|
||||
dispatch('contactSearch', { q }),
|
||||
dispatch('conversationSearch', { q }),
|
||||
dispatch('messageSearch', { q }),
|
||||
dispatch('articleSearch', { q }),
|
||||
]);
|
||||
} catch (error) {
|
||||
// Ignore error
|
||||
@@ -108,6 +114,17 @@ export const actions = {
|
||||
commit(types.MESSAGE_SEARCH_SET_UI_FLAG, { isFetching: false });
|
||||
}
|
||||
},
|
||||
async articleSearch({ commit }, { q, page = 1 }) {
|
||||
commit(types.ARTICLE_SEARCH_SET_UI_FLAG, { isFetching: true });
|
||||
try {
|
||||
const { data } = await SearchAPI.articles({ q, page });
|
||||
commit(types.ARTICLE_SEARCH_SET, data.payload.articles);
|
||||
} catch (error) {
|
||||
// Ignore error
|
||||
} finally {
|
||||
commit(types.ARTICLE_SEARCH_SET_UI_FLAG, { isFetching: false });
|
||||
}
|
||||
},
|
||||
async clearSearchResults({ commit }) {
|
||||
commit(types.CLEAR_SEARCH_RESULTS);
|
||||
},
|
||||
@@ -126,6 +143,9 @@ export const mutations = {
|
||||
[types.MESSAGE_SEARCH_SET](state, records) {
|
||||
state.messageRecords = [...state.messageRecords, ...records];
|
||||
},
|
||||
[types.ARTICLE_SEARCH_SET](state, records) {
|
||||
state.articleRecords = [...state.articleRecords, ...records];
|
||||
},
|
||||
[types.SEARCH_CONVERSATIONS_SET_UI_FLAG](state, uiFlags) {
|
||||
state.uiFlags = { ...state.uiFlags, ...uiFlags };
|
||||
},
|
||||
@@ -141,10 +161,14 @@ export const mutations = {
|
||||
[types.MESSAGE_SEARCH_SET_UI_FLAG](state, uiFlags) {
|
||||
state.uiFlags.message = { ...state.uiFlags.message, ...uiFlags };
|
||||
},
|
||||
[types.ARTICLE_SEARCH_SET_UI_FLAG](state, uiFlags) {
|
||||
state.uiFlags.article = { ...state.uiFlags.article, ...uiFlags };
|
||||
},
|
||||
[types.CLEAR_SEARCH_RESULTS](state) {
|
||||
state.contactRecords = [];
|
||||
state.conversationRecords = [];
|
||||
state.messageRecords = [];
|
||||
state.articleRecords = [];
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -18,13 +18,34 @@ const getters = {
|
||||
getAllConversations: ({ allConversations, chatSortFilter: sortKey }) => {
|
||||
return allConversations.sort((a, b) => sortComparator(a, b, sortKey));
|
||||
},
|
||||
getFilteredConversations: ({
|
||||
allConversations,
|
||||
chatSortFilter,
|
||||
appliedFilters,
|
||||
}) => {
|
||||
getFilteredConversations: (
|
||||
{ allConversations, chatSortFilter, appliedFilters },
|
||||
_,
|
||||
__,
|
||||
rootGetters
|
||||
) => {
|
||||
const currentUser = rootGetters.getCurrentUser;
|
||||
const currentUserId = rootGetters.getCurrentUser.id;
|
||||
const currentAccountId = rootGetters.getCurrentAccountId;
|
||||
|
||||
const permissions = getUserPermissions(currentUser, currentAccountId);
|
||||
const userRole = getUserRole(currentUser, currentAccountId);
|
||||
|
||||
return allConversations
|
||||
.filter(conversation => matchesFilters(conversation, appliedFilters))
|
||||
.filter(conversation => {
|
||||
const matchesFilterResult = matchesFilters(
|
||||
conversation,
|
||||
appliedFilters
|
||||
);
|
||||
const allowedForRole = applyRoleFilter(
|
||||
conversation,
|
||||
userRole,
|
||||
permissions,
|
||||
currentUserId
|
||||
);
|
||||
|
||||
return matchesFilterResult && allowedForRole;
|
||||
})
|
||||
.sort((a, b) => sortComparator(a, b, chatSortFilter));
|
||||
},
|
||||
getSelectedChat: ({ selectedChatId, allConversations }) => {
|
||||
|
||||
@@ -78,10 +78,7 @@ export const mutations = {
|
||||
}
|
||||
},
|
||||
[types.SET_ALL_ATTACHMENTS](_state, { id, data }) {
|
||||
const attachments = _state.attachments[id] || [];
|
||||
|
||||
attachments.push(...data);
|
||||
_state.attachments[id] = [...attachments];
|
||||
_state.attachments[id] = [...data];
|
||||
},
|
||||
[types.SET_MISSING_MESSAGES](_state, { id, data }) {
|
||||
const [chat] = _state.allConversations.filter(c => c.id === id);
|
||||
|
||||
@@ -170,4 +170,21 @@ describe('#actions', () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe('#resetAccessToken', () => {
|
||||
it('sends correct actions if API is success', async () => {
|
||||
const mockResponse = {
|
||||
data: { ...agentBotRecords[0], access_token: 'new_token_123' },
|
||||
};
|
||||
axios.post.mockResolvedValue(mockResponse);
|
||||
const result = await actions.resetAccessToken(
|
||||
{ commit },
|
||||
agentBotRecords[0].id
|
||||
);
|
||||
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[types.EDIT_AGENT_BOT, mockResponse.data],
|
||||
]);
|
||||
expect(result).toBe(mockResponse.data);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -228,4 +228,20 @@ describe('#actions', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#resetAccessToken', () => {
|
||||
it('sends correct actions if API is success', async () => {
|
||||
const mockResponse = {
|
||||
data: { id: 1, name: 'John', access_token: 'new_token_123' },
|
||||
headers: { expiry: 581842904 },
|
||||
};
|
||||
axios.post.mockResolvedValue(mockResponse);
|
||||
const result = await actions.resetAccessToken({ commit });
|
||||
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[types.SET_CURRENT_USER, mockResponse.data],
|
||||
]);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -75,6 +75,7 @@ describe('#actions', () => {
|
||||
q: 'test',
|
||||
});
|
||||
expect(dispatch).toHaveBeenCalledWith('messageSearch', { q: 'test' });
|
||||
expect(dispatch).toHaveBeenCalledWith('articleSearch', { q: 'test' });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -150,6 +151,30 @@ describe('#actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#articleSearch', () => {
|
||||
it('should handle successful article search', async () => {
|
||||
axios.get.mockResolvedValue({
|
||||
data: { payload: { articles: [{ id: 1 }] } },
|
||||
});
|
||||
|
||||
await actions.articleSearch({ commit }, { q: 'test', page: 1 });
|
||||
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' });
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[types.ARTICLE_SEARCH_SET_UI_FLAG, { isFetching: true }],
|
||||
[types.ARTICLE_SEARCH_SET_UI_FLAG, { isFetching: false }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#clearSearchResults', () => {
|
||||
it('should commit clear search results mutation', () => {
|
||||
actions.clearSearchResults({ commit });
|
||||
|
||||
@@ -37,6 +37,15 @@ describe('#getters', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('getArticleRecords', () => {
|
||||
const state = {
|
||||
articleRecords: [{ id: 1, title: 'Article 1' }],
|
||||
};
|
||||
expect(getters.getArticleRecords(state)).toEqual([
|
||||
{ id: 1, title: 'Article 1' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('getUIFlags', () => {
|
||||
const state = {
|
||||
uiFlags: {
|
||||
@@ -45,6 +54,7 @@ describe('#getters', () => {
|
||||
contact: { isFetching: true },
|
||||
message: { isFetching: false },
|
||||
conversation: { isFetching: false },
|
||||
article: { isFetching: false },
|
||||
},
|
||||
};
|
||||
expect(getters.getUIFlags(state)).toEqual({
|
||||
@@ -53,6 +63,7 @@ describe('#getters', () => {
|
||||
contact: { isFetching: true },
|
||||
message: { isFetching: false },
|
||||
conversation: { isFetching: false },
|
||||
article: { isFetching: false },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -101,17 +101,39 @@ describe('#mutations', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#ARTICLE_SEARCH_SET', () => {
|
||||
it('should append new article records to existing ones', () => {
|
||||
const state = { articleRecords: [{ id: 1 }] };
|
||||
mutations[types.ARTICLE_SEARCH_SET](state, [{ id: 2 }]);
|
||||
expect(state.articleRecords).toEqual([{ id: 1 }, { id: 2 }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#ARTICLE_SEARCH_SET_UI_FLAG', () => {
|
||||
it('set article search UI flags correctly', () => {
|
||||
const state = {
|
||||
uiFlags: {
|
||||
article: { isFetching: true },
|
||||
},
|
||||
};
|
||||
mutations[types.ARTICLE_SEARCH_SET_UI_FLAG](state, { isFetching: false });
|
||||
expect(state.uiFlags.article).toEqual({ isFetching: false });
|
||||
});
|
||||
});
|
||||
|
||||
describe('#CLEAR_SEARCH_RESULTS', () => {
|
||||
it('should clear all search records', () => {
|
||||
const state = {
|
||||
contactRecords: [{ id: 1 }],
|
||||
conversationRecords: [{ id: 1 }],
|
||||
messageRecords: [{ id: 1 }],
|
||||
articleRecords: [{ id: 1 }],
|
||||
};
|
||||
mutations[types.CLEAR_SEARCH_RESULTS](state);
|
||||
expect(state.contactRecords).toEqual([]);
|
||||
expect(state.conversationRecords).toEqual([]);
|
||||
expect(state.messageRecords).toEqual([]);
|
||||
expect(state.articleRecords).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -325,4 +325,308 @@ describe('#getters', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getFilteredConversations', () => {
|
||||
const mockConversations = [
|
||||
{
|
||||
id: 1,
|
||||
status: 'open',
|
||||
meta: { assignee: { id: 1 } },
|
||||
last_activity_at: 1000,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
status: 'open',
|
||||
meta: {},
|
||||
last_activity_at: 2000,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
status: 'resolved',
|
||||
meta: { assignee: { id: 2 } },
|
||||
last_activity_at: 3000,
|
||||
},
|
||||
];
|
||||
|
||||
const mockRootGetters = {
|
||||
getCurrentUser: {
|
||||
id: 1,
|
||||
accounts: [{ id: 1, role: 'agent', permissions: [] }],
|
||||
},
|
||||
getCurrentAccountId: 1,
|
||||
};
|
||||
|
||||
it('filters conversations based on role permissions for administrator', () => {
|
||||
const state = {
|
||||
allConversations: mockConversations,
|
||||
chatSortFilter: 'last_activity_at_desc',
|
||||
appliedFilters: [],
|
||||
};
|
||||
|
||||
const rootGetters = {
|
||||
...mockRootGetters,
|
||||
getCurrentUser: {
|
||||
...mockRootGetters.getCurrentUser,
|
||||
accounts: [{ id: 1, role: 'administrator', permissions: [] }],
|
||||
},
|
||||
};
|
||||
|
||||
const result = getters.getFilteredConversations(
|
||||
state,
|
||||
{},
|
||||
{},
|
||||
rootGetters
|
||||
);
|
||||
|
||||
expect(result).toEqual([
|
||||
mockConversations[2],
|
||||
mockConversations[1],
|
||||
mockConversations[0],
|
||||
]);
|
||||
});
|
||||
|
||||
it('filters conversations based on role permissions for agent', () => {
|
||||
const state = {
|
||||
allConversations: mockConversations,
|
||||
chatSortFilter: 'last_activity_at_desc',
|
||||
appliedFilters: [],
|
||||
};
|
||||
|
||||
const rootGetters = {
|
||||
...mockRootGetters,
|
||||
getCurrentUser: {
|
||||
...mockRootGetters.getCurrentUser,
|
||||
accounts: [{ id: 1, role: 'agent', permissions: [] }],
|
||||
},
|
||||
};
|
||||
|
||||
const result = getters.getFilteredConversations(
|
||||
state,
|
||||
{},
|
||||
{},
|
||||
rootGetters
|
||||
);
|
||||
|
||||
expect(result).toEqual([
|
||||
mockConversations[2],
|
||||
mockConversations[1],
|
||||
mockConversations[0],
|
||||
]);
|
||||
});
|
||||
|
||||
it('filters conversations for custom role with conversation_manage permission', () => {
|
||||
const state = {
|
||||
allConversations: mockConversations,
|
||||
chatSortFilter: 'last_activity_at_desc',
|
||||
appliedFilters: [],
|
||||
};
|
||||
|
||||
const rootGetters = {
|
||||
...mockRootGetters,
|
||||
getCurrentUser: {
|
||||
...mockRootGetters.getCurrentUser,
|
||||
accounts: [
|
||||
{
|
||||
id: 1,
|
||||
custom_role_id: 5,
|
||||
permissions: ['conversation_manage'],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = getters.getFilteredConversations(
|
||||
state,
|
||||
{},
|
||||
{},
|
||||
rootGetters
|
||||
);
|
||||
|
||||
expect(result).toEqual([
|
||||
mockConversations[2],
|
||||
mockConversations[1],
|
||||
mockConversations[0],
|
||||
]);
|
||||
});
|
||||
|
||||
it('filters conversations for custom role with conversation_unassigned_manage permission', () => {
|
||||
const state = {
|
||||
allConversations: mockConversations,
|
||||
chatSortFilter: 'last_activity_at_desc',
|
||||
appliedFilters: [],
|
||||
};
|
||||
|
||||
const rootGetters = {
|
||||
...mockRootGetters,
|
||||
getCurrentUser: {
|
||||
...mockRootGetters.getCurrentUser,
|
||||
accounts: [
|
||||
{
|
||||
id: 1,
|
||||
custom_role_id: 5,
|
||||
permissions: ['conversation_unassigned_manage'],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = getters.getFilteredConversations(
|
||||
state,
|
||||
{},
|
||||
{},
|
||||
rootGetters
|
||||
);
|
||||
|
||||
// Should include conversation assigned to user (id: 1) and unassigned conversation
|
||||
expect(result).toEqual([mockConversations[1], mockConversations[0]]);
|
||||
});
|
||||
|
||||
it('filters conversations for custom role with conversation_participating_manage permission', () => {
|
||||
const state = {
|
||||
allConversations: mockConversations,
|
||||
chatSortFilter: 'last_activity_at_desc',
|
||||
appliedFilters: [],
|
||||
};
|
||||
|
||||
const rootGetters = {
|
||||
...mockRootGetters,
|
||||
getCurrentUser: {
|
||||
...mockRootGetters.getCurrentUser,
|
||||
accounts: [
|
||||
{
|
||||
id: 1,
|
||||
custom_role_id: 5,
|
||||
permissions: ['conversation_participating_manage'],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = getters.getFilteredConversations(
|
||||
state,
|
||||
{},
|
||||
{},
|
||||
rootGetters
|
||||
);
|
||||
|
||||
// Should only include conversation assigned to user (id: 1)
|
||||
expect(result).toEqual([mockConversations[0]]);
|
||||
});
|
||||
|
||||
it('filters conversations for custom role with no permissions', () => {
|
||||
const state = {
|
||||
allConversations: mockConversations,
|
||||
chatSortFilter: 'last_activity_at_desc',
|
||||
appliedFilters: [],
|
||||
};
|
||||
|
||||
const rootGetters = {
|
||||
...mockRootGetters,
|
||||
getCurrentUser: {
|
||||
...mockRootGetters.getCurrentUser,
|
||||
accounts: [
|
||||
{
|
||||
id: 1,
|
||||
custom_role_id: 5,
|
||||
permissions: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = getters.getFilteredConversations(
|
||||
state,
|
||||
{},
|
||||
{},
|
||||
rootGetters
|
||||
);
|
||||
|
||||
// Should return empty array as user has no permissions
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('applies filters and role permissions together', () => {
|
||||
const state = {
|
||||
allConversations: mockConversations,
|
||||
chatSortFilter: 'last_activity_at_desc',
|
||||
appliedFilters: [
|
||||
{
|
||||
attribute_key: 'status',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['open'],
|
||||
query_operator: 'and',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const rootGetters = {
|
||||
...mockRootGetters,
|
||||
getCurrentUser: {
|
||||
...mockRootGetters.getCurrentUser,
|
||||
accounts: [
|
||||
{
|
||||
id: 1,
|
||||
custom_role_id: 5,
|
||||
permissions: ['conversation_participating_manage'],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = getters.getFilteredConversations(
|
||||
state,
|
||||
{},
|
||||
{},
|
||||
rootGetters
|
||||
);
|
||||
|
||||
// Should only include open conversation assigned to user (id: 1)
|
||||
expect(result).toEqual([mockConversations[0]]);
|
||||
});
|
||||
|
||||
it('returns empty array when no conversations match filters', () => {
|
||||
const state = {
|
||||
allConversations: mockConversations,
|
||||
chatSortFilter: 'last_activity_at_desc',
|
||||
appliedFilters: [
|
||||
{
|
||||
attribute_key: 'status',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['pending'],
|
||||
query_operator: 'and',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = getters.getFilteredConversations(
|
||||
state,
|
||||
{},
|
||||
{},
|
||||
mockRootGetters
|
||||
);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('sorts filtered conversations according to chatSortFilter', () => {
|
||||
const state = {
|
||||
allConversations: mockConversations,
|
||||
chatSortFilter: 'last_activity_at_asc',
|
||||
appliedFilters: [],
|
||||
};
|
||||
|
||||
const result = getters.getFilteredConversations(
|
||||
state,
|
||||
{},
|
||||
{},
|
||||
mockRootGetters
|
||||
);
|
||||
|
||||
expect(result).toEqual([
|
||||
mockConversations[0],
|
||||
mockConversations[1],
|
||||
mockConversations[2],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user