feat: Adds the ability to have custom view for conversations (#3666)

* feat: Adds the ability to save custom filters and display folders on the sidebar

* Minor fixes

* Review fixes

* Review fixes

* i18n fixes

* Shows conversations when the user click on the folder sidebar item

* Spacing fixes

* Review fixes

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
Sivin Varghese
2022-01-17 09:18:54 +05:30
committed by GitHub
parent 290196d43b
commit 4398734bdf
21 changed files with 594 additions and 23 deletions

View File

@@ -0,0 +1,79 @@
import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
import types from '../mutation-types';
import CustomViewsAPI from '../../api/customViews';
export const state = {
records: [],
uiFlags: {
isFetching: false,
isCreating: false,
isDeleting: false,
},
};
export const getters = {
getUIFlags(_state) {
return _state.uiFlags;
},
getCustomViews(_state) {
return _state.records;
},
};
export const actions = {
get: async function getCustomViews({ commit }) {
commit(types.SET_CUSTOM_VIEW_UI_FLAG, { isFetching: true });
try {
const response = await CustomViewsAPI.getCustomViews();
commit(types.SET_CUSTOM_VIEW, response.data);
} catch (error) {
// Ignore error
} finally {
commit(types.SET_CUSTOM_VIEW_UI_FLAG, { isFetching: false });
}
},
create: async function createCustomViews({ commit }, obj) {
commit(types.SET_CUSTOM_VIEW_UI_FLAG, { isCreating: true });
try {
const response = await CustomViewsAPI.create(obj);
commit(types.ADD_CUSTOM_VIEW, response.data);
} 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 }, id) => {
commit(types.SET_CUSTOM_VIEW_UI_FLAG, { isDeleting: true });
try {
await CustomViewsAPI.delete(id);
commit(types.DELETE_CUSTOM_VIEW, id);
} catch (error) {
throw new Error(error);
} finally {
commit(types.SET_CUSTOM_VIEW_UI_FLAG, { isDeleting: false });
}
},
};
export const mutations = {
[types.SET_CUSTOM_VIEW_UI_FLAG](_state, data) {
_state.uiFlags = {
..._state.uiFlags,
...data,
};
},
[types.ADD_CUSTOM_VIEW]: MutationHelpers.create,
[types.SET_CUSTOM_VIEW]: MutationHelpers.set,
[types.DELETE_CUSTOM_VIEW]: MutationHelpers.destroy,
};
export default {
namespaced: true,
actions,
state,
getters,
mutations,
};

View File

@@ -0,0 +1,72 @@
import axios from 'axios';
import { actions } from '../../customViews';
import * as types from '../../../mutation-types';
import customViewList from './fixtures';
const commit = jest.fn();
global.axios = axios;
jest.mock('axios');
describe('#actions', () => {
describe('#get', () => {
it('sends correct actions if API is success', async () => {
axios.get.mockResolvedValue({ data: customViewList });
await actions.get({ commit });
expect(commit.mock.calls).toEqual([
[types.default.SET_CUSTOM_VIEW_UI_FLAG, { isFetching: true }],
[types.default.SET_CUSTOM_VIEW, customViewList],
[types.default.SET_CUSTOM_VIEW_UI_FLAG, { isFetching: false }],
]);
});
it('sends correct actions if API is error', async () => {
axios.get.mockRejectedValue({ message: 'Incorrect header' });
await actions.get({ commit });
expect(commit.mock.calls).toEqual([
[types.default.SET_CUSTOM_VIEW_UI_FLAG, { isFetching: true }],
[types.default.SET_CUSTOM_VIEW_UI_FLAG, { isFetching: false }],
]);
});
});
describe('#create', () => {
it('sends correct actions if API is success', async () => {
axios.post.mockResolvedValue({ data: customViewList[0] });
await actions.create({ commit }, customViewList[0]);
expect(commit.mock.calls).toEqual([
[types.default.SET_CUSTOM_VIEW_UI_FLAG, { isCreating: true }],
[types.default.ADD_CUSTOM_VIEW, customViewList[0]],
[types.default.SET_CUSTOM_VIEW_UI_FLAG, { isCreating: false }],
]);
});
it('sends correct actions if API is error', async () => {
axios.post.mockRejectedValue({ message: 'Incorrect header' });
await expect(actions.create({ commit })).rejects.toThrow(Error);
expect(commit.mock.calls).toEqual([
[types.default.SET_CUSTOM_VIEW_UI_FLAG, { isCreating: true }],
[types.default.SET_CUSTOM_VIEW_UI_FLAG, { isCreating: false }],
]);
});
});
describe('#delete', () => {
it('sends correct actions if API is success', async () => {
axios.delete.mockResolvedValue({ data: customViewList[0] });
await actions.delete({ commit }, customViewList[0].id);
expect(commit.mock.calls).toEqual([
[types.default.SET_CUSTOM_VIEW_UI_FLAG, { isDeleting: true }],
[types.default.DELETE_CUSTOM_VIEW, customViewList[0].id],
[types.default.SET_CUSTOM_VIEW_UI_FLAG, { isDeleting: false }],
]);
});
it('sends correct actions if API is error', async () => {
axios.delete.mockRejectedValue({ message: 'Incorrect header' });
await expect(
actions.delete({ commit }, customViewList[0].id)
).rejects.toThrow(Error);
expect(commit.mock.calls).toEqual([
[types.default.SET_CUSTOM_VIEW_UI_FLAG, { isDeleting: true }],
[types.default.SET_CUSTOM_VIEW_UI_FLAG, { isDeleting: false }],
]);
});
});
});

View File

@@ -0,0 +1,42 @@
export default [
{
name: 'Custom view',
filter_type: 'conversation',
query: {
payload: [
{
attribute_key: 'assignee_id',
filter_operator: 'equal_to',
values: [45],
query_operator: 'and',
},
{
attribute_key: 'inbox_id',
filter_operator: 'equal_to',
values: [144],
query_operator: 'and',
},
],
},
},
{
name: 'Custom view 1',
filter_type: 'conversation',
query: {
payload: [
{
attribute_key: 'browser_language',
filter_operator: 'equal_to',
values: ['eng'],
query_operator: 'or',
},
{
attribute_key: 'campaign_id',
filter_operator: 'equal_to',
values: [15],
query_operator: 'and',
},
],
},
},
];

View File

@@ -0,0 +1,65 @@
import { getters } from '../../customViews';
import customViewList from './fixtures';
describe('#getters', () => {
it('getCustomViews', () => {
const state = { records: customViewList };
expect(getters.getCustomViews(state)).toEqual([
{
name: 'Custom view',
filter_type: 'conversation',
query: {
payload: [
{
attribute_key: 'assignee_id',
filter_operator: 'equal_to',
values: [45],
query_operator: 'and',
},
{
attribute_key: 'inbox_id',
filter_operator: 'equal_to',
values: [144],
query_operator: 'and',
},
],
},
},
{
name: 'Custom view 1',
filter_type: 'conversation',
query: {
payload: [
{
attribute_key: 'browser_language',
filter_operator: 'equal_to',
values: ['eng'],
query_operator: 'or',
},
{
attribute_key: 'campaign_id',
filter_operator: 'equal_to',
values: [15],
query_operator: 'and',
},
],
},
},
]);
});
it('getUIFlags', () => {
const state = {
uiFlags: {
isFetching: true,
isCreating: false,
isDeleting: false,
},
};
expect(getters.getUIFlags(state)).toEqual({
isFetching: true,
isCreating: false,
isDeleting: false,
});
});
});

View File

@@ -0,0 +1,29 @@
import types from '../../../mutation-types';
import { mutations } from '../../customViews';
import customViewList from './fixtures';
describe('#mutations', () => {
describe('#SET_CUSTOM_VIEW', () => {
it('set custom view records', () => {
const state = { records: [] };
mutations[types.SET_CUSTOM_VIEW](state, customViewList);
expect(state.records).toEqual(customViewList);
});
});
describe('#ADD_CUSTOM_VIEW', () => {
it('push newly created custom views to the store', () => {
const state = { records: [customViewList] };
mutations[types.ADD_CUSTOM_VIEW](state, customViewList[0]);
expect(state.records).toEqual([customViewList, customViewList[0]]);
});
});
describe('#DELETE_CUSTOM_VIEW', () => {
it('delete custom view record', () => {
const state = { records: [customViewList[0]] };
mutations[types.DELETE_CUSTOM_VIEW](state, customViewList[0]);
expect(state.records).toEqual([customViewList[0]]);
});
});
});