Merge branch 'develop' into report-rewrite

This commit is contained in:
Pranav
2024-04-15 16:52:33 -07:00
182 changed files with 4452 additions and 1173 deletions
@@ -0,0 +1,107 @@
import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
import types from '../mutation-types';
import SLAReportsAPI from '../../api/slaReports';
import { downloadCsvFile } from 'dashboard/helper/downloadHelper';
export const state = {
records: [],
metrics: {
numberOfConversations: 0,
numberOfSLAMisses: 0,
hitRate: '0%',
},
uiFlags: {
isFetching: false,
isFetchingMetrics: false,
},
meta: {
count: 0,
currentPage: 1,
},
};
export const getters = {
getAll(_state) {
return _state.records;
},
getMeta(_state) {
return _state.meta;
},
getMetrics(_state) {
return _state.metrics;
},
getUIFlags(_state) {
return _state.uiFlags;
},
};
export const actions = {
get: async function getResponses({ commit }, params) {
commit(types.SET_SLA_REPORTS_UI_FLAG, { isFetching: true });
try {
const response = await SLAReportsAPI.get(params);
const { payload, meta } = response.data;
commit(types.SET_SLA_REPORTS, payload);
commit(types.SET_SLA_REPORTS_META, meta);
} catch (error) {
throw new Error(error);
} finally {
commit(types.SET_SLA_REPORTS_UI_FLAG, { isFetching: false });
}
},
getMetrics: async function getMetrics({ commit }, params) {
commit(types.SET_SLA_REPORTS_UI_FLAG, { isFetchingMetrics: true });
try {
const response = await SLAReportsAPI.getMetrics(params);
commit(types.SET_SLA_REPORTS_METRICS, response.data);
} catch (error) {
// Ignore error
} finally {
commit(types.SET_SLA_REPORTS_UI_FLAG, { isFetchingMetrics: false });
}
},
download(_, params) {
return SLAReportsAPI.download(params).then(response => {
downloadCsvFile(params.fileName, response.data);
});
},
};
export const mutations = {
[types.SET_SLA_REPORTS_UI_FLAG](_state, data) {
_state.uiFlags = {
..._state.uiFlags,
...data,
};
},
[types.SET_SLA_REPORTS]: MutationHelpers.set,
[types.SET_SLA_REPORTS_METRICS](
_state,
{
number_of_sla_misses: numberOfSLAMisses,
hit_rate: hitRate,
total_applied_slas: numberOfConversations,
}
) {
_state.metrics = {
numberOfSLAMisses,
hitRate,
numberOfConversations,
};
},
[types.SET_SLA_REPORTS_META](_state, { count, current_page: currentPage }) {
_state.meta = {
count,
currentPage,
};
},
};
export default {
namespaced: true,
state,
getters,
actions,
mutations,
};
@@ -0,0 +1,78 @@
import axios from 'axios';
import { actions } from '../../sla';
import * as types from '../../../mutation-types';
import SLAList from './fixtures';
const commit = jest.fn();
global.axios = axios;
jest.mock('axios');
describe('#actions', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('#get', () => {
it('sends correct actions if API is success', async () => {
axios.get.mockResolvedValue({
data: { payload: SLAList },
});
await actions.get({ commit });
expect(commit.mock.calls).toEqual([
[types.default.SET_SLA_UI_FLAG, { isFetching: true }],
[types.default.SET_SLA, SLAList],
[types.default.SET_SLA_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_SLA_UI_FLAG, { isFetching: true }],
[types.default.SET_SLA_UI_FLAG, { isFetching: false }],
]);
});
});
describe('#create', () => {
it('sends correct actions if API is success', async () => {
axios.post.mockResolvedValue({
data: { payload: SLAList[0] },
});
await actions.create({ commit }, SLAList[0]);
expect(commit.mock.calls).toEqual([
[types.default.SET_SLA_UI_FLAG, { isCreating: true }],
[types.default.ADD_SLA, SLAList[0]],
[types.default.SET_SLA_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_SLA_UI_FLAG, { isCreating: true }],
[types.default.SET_SLA_UI_FLAG, { isCreating: false }],
]);
});
});
describe('#delete', () => {
it('sends correct actions if API is success', async () => {
axios.delete.mockResolvedValue({});
await actions.delete({ commit }, 1);
expect(commit.mock.calls).toEqual([
[types.default.SET_SLA_UI_FLAG, { isDeleting: true }],
[types.default.DELETE_SLA, 1],
[types.default.SET_SLA_UI_FLAG, { isDeleting: false }],
]);
});
it('sends correct actions if API is error', async () => {
axios.delete.mockRejectedValue({ message: 'Incorrect header' });
await expect(actions.delete({ commit })).rejects.toThrow(Error);
expect(commit.mock.calls).toEqual([
[types.default.SET_SLA_UI_FLAG, { isDeleting: true }],
[types.default.SET_SLA_UI_FLAG, { isDeleting: false }],
]);
});
});
});
@@ -0,0 +1,95 @@
export default [
{
id: 1,
name: 'Premium SLA',
description:
'SLA for chatwoot cloud premium and self-hosted premium customers. SLA for chatwoot cloud premium and self-hosted premium customers',
first_response_time_threshold: 14400,
next_response_time_threshold: 18000,
resolution_time_threshold: 86400,
only_during_business_hours: true,
},
{
id: 2,
name: 'Enterprise SLA',
description:
'SLA for chatwoot enterprise and self-hosted enterprise customers.',
first_response_time_threshold: 600,
next_response_time_threshold: 2400,
resolution_time_threshold: 3600,
only_during_business_hours: false,
},
{
id: 3,
name: 'Business SLA',
description:
'Chatwoot cloud Business and self-hosted Business customers SLA',
first_response_time_threshold: null,
next_response_time_threshold: null,
resolution_time_threshold: null,
only_during_business_hours: false,
},
{
id: 4,
name: 'Hacker SLA',
description: '',
first_response_time_threshold: 60,
next_response_time_threshold: 120,
resolution_time_threshold: 180,
only_during_business_hours: false,
},
{
id: 5,
name: 'SLA',
description: '',
first_response_time_threshold: 120,
next_response_time_threshold: 300,
resolution_time_threshold: 21600,
only_during_business_hours: false,
},
{
id: 6,
name: 'ALla',
description: '',
first_response_time_threshold: 5400,
next_response_time_threshold: 9000,
resolution_time_threshold: 23040,
only_during_business_hours: false,
},
{
id: 7,
name: '10',
description: '',
first_response_time_threshold: 120,
next_response_time_threshold: null,
resolution_time_threshold: null,
only_during_business_hours: false,
},
{
id: 8,
name: '11',
description: '',
first_response_time_threshold: null,
next_response_time_threshold: 240,
resolution_time_threshold: null,
only_during_business_hours: false,
},
{
id: 9,
name: '12',
description: '',
first_response_time_threshold: null,
next_response_time_threshold: null,
resolution_time_threshold: 300,
only_during_business_hours: false,
},
{
id: 10,
name: '14',
description: '',
first_response_time_threshold: null,
next_response_time_threshold: null,
resolution_time_threshold: null,
only_during_business_hours: false,
},
];
@@ -0,0 +1,26 @@
import { getters } from '../../sla';
import SLAs from './fixtures';
describe('#getters', () => {
it('getSLA', () => {
const state = { records: SLAs };
expect(getters.getSLA(state)).toEqual(SLAs);
});
it('getUIFlags', () => {
const state = {
uiFlags: {
isFetching: true,
isCreating: false,
isUpdating: false,
isDeleting: false,
},
};
expect(getters.getUIFlags(state)).toEqual({
isFetching: true,
isCreating: false,
isUpdating: false,
isDeleting: false,
});
});
});
@@ -0,0 +1,34 @@
import types from '../../../mutation-types';
import { mutations } from '../../sla';
import SLAs from './fixtures';
describe('#mutations', () => {
describe('#SET_SLA_UI_FLAG', () => {
it('set sla ui flags', () => {
const state = { uiFlags: {} };
mutations[types.SET_SLA_UI_FLAG](state, { isFetching: true });
expect(state.uiFlags).toEqual({ isFetching: true });
});
});
describe('#SET_SLA', () => {
it('set sla records', () => {
const state = { records: [] };
mutations[types.SET_SLA](state, SLAs);
expect(state.records).toEqual(SLAs);
});
});
describe('#ADD_SLA', () => {
it('push newly created sla to the store', () => {
const state = { records: [SLAs[0]] };
mutations[types.ADD_SLA](state, SLAs[1]);
expect(state.records).toEqual([SLAs[0], SLAs[1]]);
});
});
describe('#DELETE_SLA', () => {
it('delete sla record', () => {
const state = { records: [SLAs[0]] };
mutations[types.DELETE_SLA](state, 1);
expect(state.records).toEqual([]);
});
});
});
@@ -0,0 +1,55 @@
import axios from 'axios';
import { actions } from '../../SLAReports';
import appliedSlas from './fixtures';
import types from '../../../mutation-types';
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: { payload: appliedSlas, meta: { count: 1 } },
});
await actions.get({ commit }, {});
expect(commit.mock.calls).toEqual([
[types.SET_SLA_REPORTS_UI_FLAG, { isFetching: true }],
[types.SET_SLA_REPORTS, appliedSlas],
[types.SET_SLA_REPORTS_META, { count: 1 }],
[types.SET_SLA_REPORTS_UI_FLAG, { isFetching: false }],
]);
});
it('sends correct actions if API is error', async () => {
axios.get.mockRejectedValue({ message: 'Incorrect header' });
await expect(actions.get({ commit }, { teamId: 1 })).rejects.toThrow(
Error
);
expect(commit.mock.calls).toEqual([
[types.SET_SLA_REPORTS_UI_FLAG, { isFetching: true }],
[types.SET_SLA_REPORTS_UI_FLAG, { isFetching: false }],
]);
});
});
describe('#getMetrics', () => {
it('sends correct actions if API is success', async () => {
axios.get.mockResolvedValue({ data: { metrics: { count: 1 } } });
await actions.getMetrics({ commit }, {});
expect(commit.mock.calls).toEqual([
[types.SET_SLA_REPORTS_UI_FLAG, { isFetchingMetrics: true }],
[types.SET_SLA_REPORTS_METRICS, { metrics: { count: 1 } }],
[types.SET_SLA_REPORTS_UI_FLAG, { isFetchingMetrics: false }],
]);
});
it('sends correct actions if API is error', async () => {
axios.get.mockRejectedValue({ message: 'Incorrect header' });
await actions.getMetrics({ commit }, { teamId: 1 });
expect(commit.mock.calls).toEqual([
[types.SET_SLA_REPORTS_UI_FLAG, { isFetchingMetrics: true }],
[types.SET_SLA_REPORTS_UI_FLAG, { isFetchingMetrics: false }],
]);
});
});
});
@@ -0,0 +1,52 @@
export default [
{
id: 23,
sla_policy_id: 7,
conversation_id: 152,
sla_status: 'active_with_misses',
created_at: '2024-03-31T07:50:53.518Z',
updated_at: '2024-03-31T07:55:06.451Z',
conversation: {
id: 152,
uuid: '2f9a988d-418f-47d9-b4dc-c441f28da7c2',
account_id: 1,
},
sla_events: [
{
id: 14,
event_type: 'frt',
meta: {},
updated_at: 1711871706,
created_at: 1711871706,
},
{
id: 15,
event_type: 'rt',
meta: {},
updated_at: 1711871706,
created_at: 1711871706,
},
],
},
{
id: 24,
sla_policy_id: 7,
conversation_id: 153,
sla_status: 'active_with_misses',
created_at: '2024-03-31T07:57:49.659Z',
updated_at: '2024-03-31T08:00:31.627Z',
conversation: {
id: 153,
uuid: 'd5d97961-4341-469e-accf-f13f25a14c3c',
},
sla_events: [
{
id: 16,
event_type: 'rt',
meta: {},
updated_at: 1711872031,
created_at: 1711872031,
},
],
},
];
@@ -0,0 +1,53 @@
import { getters } from '../../SLAReports';
import appliedSlas from './fixtures';
describe('#getters', () => {
it('getAppliedSlas', () => {
const state = {
records: [appliedSlas[0]],
};
expect(getters.getAll(state)).toEqual([appliedSlas[0]]);
});
it('getUIFlags', () => {
const state = {
uiFlags: {
isFetching: false,
isFetchingMetrics: false,
},
};
expect(getters.getUIFlags(state)).toEqual({
isFetching: false,
isFetchingMetrics: false,
});
});
it('getMeta', () => {
const state = {
meta: {
count: 0,
currentPage: 1,
},
};
expect(getters.getMeta(state)).toEqual({
count: 0,
currentPage: 1,
});
});
it('getMetrics', () => {
const state = {
metrics: {
numberOfConversations: 27,
numberOfSLAMisses: 25,
hitRate: '7.41%',
},
};
expect(getters.getMetrics(state)).toEqual({
numberOfConversations: 27,
numberOfSLAMisses: 25,
hitRate: '7.41%',
});
});
});
@@ -0,0 +1,51 @@
import { mutations } from '../../SLAReports';
import appliedSlas from './fixtures';
import types from '../../../mutation-types';
describe('#mutations', () => {
describe('#SET_SLA_REPORTS', () => {
it('Adds sla reports', () => {
const state = { records: {} };
mutations[types.SET_SLA_REPORTS](state, appliedSlas);
expect(state.records).toEqual(appliedSlas);
});
});
describe('#SET_SLA_REPORTS_UI_FLAG', () => {
it('set ui flags', () => {
const state = { uiFlags: {} };
mutations[types.SET_SLA_REPORTS_UI_FLAG](state, { isFetching: true });
expect(state.uiFlags).toEqual({ isFetching: true });
});
});
describe('#SET_SLA_REPORTS_METRICS', () => {
it('set metrics', () => {
const state = { metrics: {} };
mutations[types.SET_SLA_REPORTS_METRICS](state, {
number_of_sla_misses: 1,
hit_rate: '100%',
total_applied_slas: 1,
});
expect(state.metrics).toEqual({
numberOfSLAMisses: 1,
hitRate: '100%',
numberOfConversations: 1,
});
});
});
describe('#SET_SLA_REPORTS_META', () => {
it('set meta', () => {
const state = { meta: {} };
mutations[types.SET_SLA_REPORTS_META](state, {
count: 1,
current_page: 1,
});
expect(state.meta).toEqual({
count: 1,
currentPage: 1,
});
});
});
});