mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-03 12:37:56 +00:00
feat: Add stores for copilotMessages and copilotThreads (#11603)
- Set up stores for copilotThreads and copilotMessages. - Add support for upsert messages to the copilotMessages store on receiving ActionCable events. - Implement support for the upsert option.
This commit is contained in:
18
app/javascript/dashboard/api/captain/copilotMessages.js
Normal file
18
app/javascript/dashboard/api/captain/copilotMessages.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/* global axios */
|
||||||
|
import ApiClient from '../ApiClient';
|
||||||
|
|
||||||
|
class CopilotMessages extends ApiClient {
|
||||||
|
constructor() {
|
||||||
|
super('captain/copilot_threads', { accountScoped: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
get(threadId) {
|
||||||
|
return axios.get(`${this.url}/${threadId}/copilot_messages`);
|
||||||
|
}
|
||||||
|
|
||||||
|
create({ threadId, ...rest }) {
|
||||||
|
return axios.post(`${this.url}/${threadId}/copilot_messages`, rest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new CopilotMessages();
|
||||||
9
app/javascript/dashboard/api/captain/copilotThreads.js
Normal file
9
app/javascript/dashboard/api/captain/copilotThreads.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import ApiClient from '../ApiClient';
|
||||||
|
|
||||||
|
class CopilotThreads extends ApiClient {
|
||||||
|
constructor() {
|
||||||
|
super('captain/copilot_threads', { accountScoped: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new CopilotThreads();
|
||||||
@@ -33,6 +33,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
|||||||
'conversation.read': this.onConversationRead,
|
'conversation.read': this.onConversationRead,
|
||||||
'conversation.updated': this.onConversationUpdated,
|
'conversation.updated': this.onConversationUpdated,
|
||||||
'account.cache_invalidated': this.onCacheInvalidate,
|
'account.cache_invalidated': this.onCacheInvalidate,
|
||||||
|
'copilot.message.created': this.onCopilotMessageCreated,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,6 +190,10 @@ class ActionCableConnector extends BaseActionCableConnector {
|
|||||||
this.app.$store.dispatch('notifications/updateNotification', data);
|
this.app.$store.dispatch('notifications/updateNotification', data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onCopilotMessageCreated = data => {
|
||||||
|
this.app.$store.dispatch('copilotMessages/upsert', data);
|
||||||
|
};
|
||||||
|
|
||||||
onCacheInvalidate = data => {
|
onCacheInvalidate = data => {
|
||||||
const keys = data.cache_keys;
|
const keys = data.cache_keys;
|
||||||
this.app.$store.dispatch('labels/revalidate', { newKey: keys.label });
|
this.app.$store.dispatch('labels/revalidate', { newKey: keys.label });
|
||||||
|
|||||||
67
app/javascript/dashboard/helper/specs/actionCable.spec.js
Normal file
67
app/javascript/dashboard/helper/specs/actionCable.spec.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { describe, it, beforeEach, expect, vi } from 'vitest';
|
||||||
|
import ActionCableConnector from '../actionCable';
|
||||||
|
|
||||||
|
vi.mock('shared/helpers/mitt', () => ({
|
||||||
|
emitter: {
|
||||||
|
emit: vi.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('dashboard/composables/useImpersonation', () => ({
|
||||||
|
useImpersonation: () => ({
|
||||||
|
isImpersonating: { value: false },
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
global.chatwootConfig = {
|
||||||
|
websocketURL: 'wss://test.chatwoot.com',
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('ActionCableConnector - Copilot Tests', () => {
|
||||||
|
let store;
|
||||||
|
let actionCable;
|
||||||
|
let mockDispatch;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
mockDispatch = vi.fn();
|
||||||
|
store = {
|
||||||
|
$store: {
|
||||||
|
dispatch: mockDispatch,
|
||||||
|
getters: {
|
||||||
|
getCurrentAccountId: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
actionCable = ActionCableConnector.init(store.$store, 'test-token');
|
||||||
|
});
|
||||||
|
describe('copilot event handlers', () => {
|
||||||
|
it('should register the copilot.message.created event handler', () => {
|
||||||
|
expect(Object.keys(actionCable.events)).toContain(
|
||||||
|
'copilot.message.created'
|
||||||
|
);
|
||||||
|
expect(actionCable.events['copilot.message.created']).toBe(
|
||||||
|
actionCable.onCopilotMessageCreated
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle the copilot.message.created event through the ActionCable system', () => {
|
||||||
|
const copilotData = {
|
||||||
|
id: 2,
|
||||||
|
content: 'This is a copilot message from ActionCable',
|
||||||
|
conversation_id: 456,
|
||||||
|
created_at: '2025-05-27T15:58:04-06:00',
|
||||||
|
account_id: 1,
|
||||||
|
};
|
||||||
|
actionCable.onReceived({
|
||||||
|
event: 'copilot.message.created',
|
||||||
|
data: copilotData,
|
||||||
|
});
|
||||||
|
expect(mockDispatch).toHaveBeenCalledWith(
|
||||||
|
'copilotMessages/upsert',
|
||||||
|
copilotData
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
19
app/javascript/dashboard/store/captain/copilotMessages.js
Normal file
19
app/javascript/dashboard/store/captain/copilotMessages.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import CopilotMessagesAPI from 'dashboard/api/captain/copilotMessages';
|
||||||
|
import { createStore } from './storeFactory';
|
||||||
|
|
||||||
|
export default createStore({
|
||||||
|
name: 'CopilotMessages',
|
||||||
|
API: CopilotMessagesAPI,
|
||||||
|
getters: {
|
||||||
|
getMessagesByThreadId: state => copilotThreadId => {
|
||||||
|
return state.records.filter(
|
||||||
|
record => record.copilot_thread?.id === Number(copilotThreadId)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: mutationTypes => ({
|
||||||
|
upsert({ commit }, data) {
|
||||||
|
commit(mutationTypes.UPSERT, data);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
7
app/javascript/dashboard/store/captain/copilotThreads.js
Normal file
7
app/javascript/dashboard/store/captain/copilotThreads.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import CopilotThreadsAPI from 'dashboard/api/captain/copilotThreads';
|
||||||
|
import { createStore } from './storeFactory';
|
||||||
|
|
||||||
|
export default createStore({
|
||||||
|
name: 'CopilotThreads',
|
||||||
|
API: CopilotThreadsAPI,
|
||||||
|
});
|
||||||
@@ -1,5 +1,11 @@
|
|||||||
import { throwErrorMessage } from 'dashboard/store/utils/api';
|
|
||||||
import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
|
import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
|
||||||
|
import {
|
||||||
|
createRecord,
|
||||||
|
deleteRecord,
|
||||||
|
getRecords,
|
||||||
|
showRecord,
|
||||||
|
updateRecord,
|
||||||
|
} from './storeFactoryHelper';
|
||||||
|
|
||||||
export const generateMutationTypes = name => {
|
export const generateMutationTypes = name => {
|
||||||
const capitalizedName = name.toUpperCase();
|
const capitalizedName = name.toUpperCase();
|
||||||
@@ -10,6 +16,7 @@ export const generateMutationTypes = name => {
|
|||||||
EDIT: `EDIT_${capitalizedName}`,
|
EDIT: `EDIT_${capitalizedName}`,
|
||||||
DELETE: `DELETE_${capitalizedName}`,
|
DELETE: `DELETE_${capitalizedName}`,
|
||||||
SET_META: `SET_${capitalizedName}_META`,
|
SET_META: `SET_${capitalizedName}_META`,
|
||||||
|
UPSERT: `UPSERT_${capitalizedName}`,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -33,7 +40,6 @@ export const createGetters = () => ({
|
|||||||
getMeta: state => state.meta,
|
getMeta: state => state.meta,
|
||||||
});
|
});
|
||||||
|
|
||||||
// store/mutations.js
|
|
||||||
export const createMutations = mutationTypes => ({
|
export const createMutations = mutationTypes => ({
|
||||||
[mutationTypes.SET_UI_FLAG](state, data) {
|
[mutationTypes.SET_UI_FLAG](state, data) {
|
||||||
state.uiFlags = {
|
state.uiFlags = {
|
||||||
@@ -51,78 +57,19 @@ export const createMutations = mutationTypes => ({
|
|||||||
[mutationTypes.ADD]: MutationHelpers.create,
|
[mutationTypes.ADD]: MutationHelpers.create,
|
||||||
[mutationTypes.EDIT]: MutationHelpers.update,
|
[mutationTypes.EDIT]: MutationHelpers.update,
|
||||||
[mutationTypes.DELETE]: MutationHelpers.destroy,
|
[mutationTypes.DELETE]: MutationHelpers.destroy,
|
||||||
|
[mutationTypes.UPSERT]: MutationHelpers.setSingleRecord,
|
||||||
});
|
});
|
||||||
|
|
||||||
// store/actions/crud.js
|
|
||||||
export const createCrudActions = (API, mutationTypes) => ({
|
export const createCrudActions = (API, mutationTypes) => ({
|
||||||
async get({ commit }, params = {}) {
|
get: getRecords(mutationTypes, API),
|
||||||
commit(mutationTypes.SET_UI_FLAG, { fetchingList: true });
|
show: showRecord(mutationTypes, API),
|
||||||
try {
|
create: createRecord(mutationTypes, API),
|
||||||
const response = await API.get(params);
|
update: updateRecord(mutationTypes, API),
|
||||||
commit(mutationTypes.SET, response.data.payload);
|
delete: deleteRecord(mutationTypes, API),
|
||||||
commit(mutationTypes.SET_META, response.data.meta);
|
|
||||||
return response.data.payload;
|
|
||||||
} catch (error) {
|
|
||||||
return throwErrorMessage(error);
|
|
||||||
} finally {
|
|
||||||
commit(mutationTypes.SET_UI_FLAG, { fetchingList: false });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async show({ commit }, id) {
|
|
||||||
commit(mutationTypes.SET_UI_FLAG, { fetchingItem: true });
|
|
||||||
try {
|
|
||||||
const response = await API.show(id);
|
|
||||||
commit(mutationTypes.ADD, response.data);
|
|
||||||
return response.data;
|
|
||||||
} catch (error) {
|
|
||||||
return throwErrorMessage(error);
|
|
||||||
} finally {
|
|
||||||
commit(mutationTypes.SET_UI_FLAG, { fetchingItem: false });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async create({ commit }, dataObj) {
|
|
||||||
commit(mutationTypes.SET_UI_FLAG, { creatingItem: true });
|
|
||||||
try {
|
|
||||||
const response = await API.create(dataObj);
|
|
||||||
commit(mutationTypes.ADD, response.data);
|
|
||||||
return response.data;
|
|
||||||
} catch (error) {
|
|
||||||
return throwErrorMessage(error);
|
|
||||||
} finally {
|
|
||||||
commit(mutationTypes.SET_UI_FLAG, { creatingItem: false });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async update({ commit }, { id, ...updateObj }) {
|
|
||||||
commit(mutationTypes.SET_UI_FLAG, { updatingItem: true });
|
|
||||||
try {
|
|
||||||
const response = await API.update(id, updateObj);
|
|
||||||
commit(mutationTypes.EDIT, response.data);
|
|
||||||
return response.data;
|
|
||||||
} catch (error) {
|
|
||||||
return throwErrorMessage(error);
|
|
||||||
} finally {
|
|
||||||
commit(mutationTypes.SET_UI_FLAG, { updatingItem: false });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async delete({ commit }, id) {
|
|
||||||
commit(mutationTypes.SET_UI_FLAG, { deletingItem: true });
|
|
||||||
try {
|
|
||||||
await API.delete(id);
|
|
||||||
commit(mutationTypes.DELETE, id);
|
|
||||||
return id;
|
|
||||||
} catch (error) {
|
|
||||||
return throwErrorMessage(error);
|
|
||||||
} finally {
|
|
||||||
commit(mutationTypes.SET_UI_FLAG, { deletingItem: false });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const createStore = options => {
|
export const createStore = options => {
|
||||||
const { name, API, actions } = options;
|
const { name, API, actions, getters } = options;
|
||||||
const mutationTypes = generateMutationTypes(name);
|
const mutationTypes = generateMutationTypes(name);
|
||||||
|
|
||||||
const customActions = actions ? actions(mutationTypes) : {};
|
const customActions = actions ? actions(mutationTypes) : {};
|
||||||
@@ -130,7 +77,10 @@ export const createStore = options => {
|
|||||||
return {
|
return {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: createInitialState(),
|
state: createInitialState(),
|
||||||
getters: createGetters(),
|
getters: {
|
||||||
|
...createGetters(),
|
||||||
|
...(getters || {}),
|
||||||
|
},
|
||||||
mutations: createMutations(mutationTypes),
|
mutations: createMutations(mutationTypes),
|
||||||
actions: {
|
actions: {
|
||||||
...createCrudActions(API, mutationTypes),
|
...createCrudActions(API, mutationTypes),
|
||||||
|
|||||||
380
app/javascript/dashboard/store/captain/storeFactory.spec.js
Normal file
380
app/javascript/dashboard/store/captain/storeFactory.spec.js
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
import { throwErrorMessage } from 'dashboard/store/utils/api';
|
||||||
|
import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
|
||||||
|
import {
|
||||||
|
generateMutationTypes,
|
||||||
|
createInitialState,
|
||||||
|
createGetters,
|
||||||
|
createMutations,
|
||||||
|
createCrudActions,
|
||||||
|
createStore,
|
||||||
|
} from './storeFactory';
|
||||||
|
|
||||||
|
vi.mock('dashboard/store/utils/api', () => ({
|
||||||
|
throwErrorMessage: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('shared/helpers/vuex/mutationHelpers', () => ({
|
||||||
|
set: vi.fn(),
|
||||||
|
create: vi.fn(),
|
||||||
|
update: vi.fn(),
|
||||||
|
destroy: vi.fn(),
|
||||||
|
setSingleRecord: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('storeFactory', () => {
|
||||||
|
describe('generateMutationTypes', () => {
|
||||||
|
it('generates correct mutation types with capitalized name', () => {
|
||||||
|
const result = generateMutationTypes('test');
|
||||||
|
expect(result).toEqual({
|
||||||
|
SET_UI_FLAG: 'SET_TEST_UI_FLAG',
|
||||||
|
SET: 'SET_TEST',
|
||||||
|
ADD: 'ADD_TEST',
|
||||||
|
EDIT: 'EDIT_TEST',
|
||||||
|
DELETE: 'DELETE_TEST',
|
||||||
|
SET_META: 'SET_TEST_META',
|
||||||
|
UPSERT: 'UPSERT_TEST',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createInitialState', () => {
|
||||||
|
it('returns the correct initial state structure', () => {
|
||||||
|
const result = createInitialState();
|
||||||
|
expect(result).toEqual({
|
||||||
|
records: [],
|
||||||
|
meta: {},
|
||||||
|
uiFlags: {
|
||||||
|
fetchingList: false,
|
||||||
|
fetchingItem: false,
|
||||||
|
creatingItem: false,
|
||||||
|
updatingItem: false,
|
||||||
|
deletingItem: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createGetters', () => {
|
||||||
|
it('returns getters with correct implementations', () => {
|
||||||
|
const getters = createGetters();
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
records: [{ id: 2 }, { id: 1 }, { id: 3 }],
|
||||||
|
uiFlags: { fetchingList: true },
|
||||||
|
meta: { totalCount: 10, page: 1 },
|
||||||
|
};
|
||||||
|
expect(getters.getRecords(state)).toEqual([
|
||||||
|
{ id: 3 },
|
||||||
|
{ id: 2 },
|
||||||
|
{ id: 1 },
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(getters.getRecord(state)(2)).toEqual({ id: 2 });
|
||||||
|
expect(getters.getRecord(state)(4)).toEqual({});
|
||||||
|
|
||||||
|
expect(getters.getUIFlags(state)).toEqual({
|
||||||
|
fetchingList: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getters.getMeta(state)).toEqual({
|
||||||
|
totalCount: 10,
|
||||||
|
page: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createMutations', () => {
|
||||||
|
it('creates mutations with correct implementations', () => {
|
||||||
|
const mutationTypes = generateMutationTypes('test');
|
||||||
|
const mutations = createMutations(mutationTypes);
|
||||||
|
|
||||||
|
const state = { uiFlags: { fetchingList: false } };
|
||||||
|
mutations[mutationTypes.SET_UI_FLAG](state, { fetchingList: true });
|
||||||
|
expect(state.uiFlags).toEqual({ fetchingList: true });
|
||||||
|
|
||||||
|
const metaState = { meta: {} };
|
||||||
|
mutations[mutationTypes.SET_META](metaState, {
|
||||||
|
total_count: '10',
|
||||||
|
page: '2',
|
||||||
|
});
|
||||||
|
expect(metaState.meta).toEqual({ totalCount: 10, page: 2 });
|
||||||
|
|
||||||
|
expect(mutations[mutationTypes.SET]).toBe(MutationHelpers.set);
|
||||||
|
expect(mutations[mutationTypes.ADD]).toBe(MutationHelpers.create);
|
||||||
|
expect(mutations[mutationTypes.EDIT]).toBe(MutationHelpers.update);
|
||||||
|
expect(mutations[mutationTypes.DELETE]).toBe(MutationHelpers.destroy);
|
||||||
|
expect(mutations[mutationTypes.UPSERT]).toBe(
|
||||||
|
MutationHelpers.setSingleRecord
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createCrudActions', () => {
|
||||||
|
let API;
|
||||||
|
let commit;
|
||||||
|
let mutationTypes;
|
||||||
|
let actions;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
API = {
|
||||||
|
get: vi.fn(),
|
||||||
|
show: vi.fn(),
|
||||||
|
create: vi.fn(),
|
||||||
|
update: vi.fn(),
|
||||||
|
delete: vi.fn(),
|
||||||
|
};
|
||||||
|
commit = vi.fn();
|
||||||
|
mutationTypes = generateMutationTypes('test');
|
||||||
|
actions = createCrudActions(API, mutationTypes);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('get action', () => {
|
||||||
|
it('handles successful API response', async () => {
|
||||||
|
const payload = [{ id: 1 }];
|
||||||
|
const meta = { total_count: 10, page: 1 };
|
||||||
|
API.get.mockResolvedValue({ data: { payload, meta } });
|
||||||
|
|
||||||
|
const result = await actions.get({ commit });
|
||||||
|
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||||
|
fetchingList: true,
|
||||||
|
});
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.SET, payload);
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_META, meta);
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||||
|
fetchingList: false,
|
||||||
|
});
|
||||||
|
expect(result).toEqual(payload);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles API error', async () => {
|
||||||
|
const error = new Error('API Error');
|
||||||
|
API.get.mockRejectedValue(error);
|
||||||
|
throwErrorMessage.mockReturnValue('Error thrown');
|
||||||
|
|
||||||
|
const result = await actions.get({ commit });
|
||||||
|
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||||
|
fetchingList: true,
|
||||||
|
});
|
||||||
|
expect(throwErrorMessage).toHaveBeenCalledWith(error);
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||||
|
fetchingList: false,
|
||||||
|
});
|
||||||
|
expect(result).toEqual('Error thrown');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('show action', () => {
|
||||||
|
it('handles successful API response', async () => {
|
||||||
|
const data = { id: 1, name: 'Test' };
|
||||||
|
API.show.mockResolvedValue({ data });
|
||||||
|
|
||||||
|
const result = await actions.show({ commit }, 1);
|
||||||
|
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||||
|
fetchingItem: true,
|
||||||
|
});
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.ADD, data);
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||||
|
fetchingItem: false,
|
||||||
|
});
|
||||||
|
expect(result).toEqual(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles API error', async () => {
|
||||||
|
const error = new Error('API Error');
|
||||||
|
API.show.mockRejectedValue(error);
|
||||||
|
throwErrorMessage.mockReturnValue('Error thrown');
|
||||||
|
|
||||||
|
const result = await actions.show({ commit }, 1);
|
||||||
|
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||||
|
fetchingItem: true,
|
||||||
|
});
|
||||||
|
expect(throwErrorMessage).toHaveBeenCalledWith(error);
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||||
|
fetchingItem: false,
|
||||||
|
});
|
||||||
|
expect(result).toEqual('Error thrown');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('create action', () => {
|
||||||
|
it('handles successful API response', async () => {
|
||||||
|
const data = { id: 1, name: 'Test' };
|
||||||
|
API.create.mockResolvedValue({ data });
|
||||||
|
|
||||||
|
const result = await actions.create({ commit }, { name: 'Test' });
|
||||||
|
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||||
|
creatingItem: true,
|
||||||
|
});
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.UPSERT, data);
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||||
|
creatingItem: false,
|
||||||
|
});
|
||||||
|
expect(result).toEqual(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles API error', async () => {
|
||||||
|
const error = new Error('API Error');
|
||||||
|
API.create.mockRejectedValue(error);
|
||||||
|
throwErrorMessage.mockReturnValue('Error thrown');
|
||||||
|
|
||||||
|
const result = await actions.create({ commit }, { name: 'Test' });
|
||||||
|
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||||
|
creatingItem: true,
|
||||||
|
});
|
||||||
|
expect(throwErrorMessage).toHaveBeenCalledWith(error);
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||||
|
creatingItem: false,
|
||||||
|
});
|
||||||
|
expect(result).toEqual('Error thrown');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('update action', () => {
|
||||||
|
it('handles successful API response', async () => {
|
||||||
|
const data = { id: 1, name: 'Updated' };
|
||||||
|
API.update.mockResolvedValue({ data });
|
||||||
|
|
||||||
|
const result = await actions.update(
|
||||||
|
{ commit },
|
||||||
|
{ id: 1, name: 'Updated' }
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||||
|
updatingItem: true,
|
||||||
|
});
|
||||||
|
expect(API.update).toHaveBeenCalledWith(1, { name: 'Updated' });
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.EDIT, data);
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||||
|
updatingItem: false,
|
||||||
|
});
|
||||||
|
expect(result).toEqual(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles API error', async () => {
|
||||||
|
const error = new Error('API Error');
|
||||||
|
API.update.mockRejectedValue(error);
|
||||||
|
throwErrorMessage.mockReturnValue('Error thrown');
|
||||||
|
|
||||||
|
const result = await actions.update(
|
||||||
|
{ commit },
|
||||||
|
{ id: 1, name: 'Updated' }
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||||
|
updatingItem: true,
|
||||||
|
});
|
||||||
|
expect(throwErrorMessage).toHaveBeenCalledWith(error);
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||||
|
updatingItem: false,
|
||||||
|
});
|
||||||
|
expect(result).toEqual('Error thrown');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('delete action', () => {
|
||||||
|
it('handles successful API response', async () => {
|
||||||
|
API.delete.mockResolvedValue({});
|
||||||
|
|
||||||
|
const result = await actions.delete({ commit }, 1);
|
||||||
|
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||||
|
deletingItem: true,
|
||||||
|
});
|
||||||
|
expect(API.delete).toHaveBeenCalledWith(1);
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.DELETE, 1);
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||||
|
deletingItem: false,
|
||||||
|
});
|
||||||
|
expect(result).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles API error', async () => {
|
||||||
|
const error = new Error('API Error');
|
||||||
|
API.delete.mockRejectedValue(error);
|
||||||
|
throwErrorMessage.mockReturnValue('Error thrown');
|
||||||
|
|
||||||
|
const result = await actions.delete({ commit }, 1);
|
||||||
|
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||||
|
deletingItem: true,
|
||||||
|
});
|
||||||
|
expect(throwErrorMessage).toHaveBeenCalledWith(error);
|
||||||
|
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||||
|
deletingItem: false,
|
||||||
|
});
|
||||||
|
expect(result).toEqual('Error thrown');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createStore', () => {
|
||||||
|
it('creates a complete store with default options', () => {
|
||||||
|
const API = {};
|
||||||
|
const store = createStore({ name: 'test', API });
|
||||||
|
|
||||||
|
expect(store.namespaced).toBe(true);
|
||||||
|
expect(store.state).toEqual(createInitialState());
|
||||||
|
expect(Object.keys(store.getters)).toEqual([
|
||||||
|
'getRecords',
|
||||||
|
'getRecord',
|
||||||
|
'getUIFlags',
|
||||||
|
'getMeta',
|
||||||
|
]);
|
||||||
|
expect(Object.keys(store.mutations)).toEqual([
|
||||||
|
'SET_TEST_UI_FLAG',
|
||||||
|
'SET_TEST_META',
|
||||||
|
'SET_TEST',
|
||||||
|
'ADD_TEST',
|
||||||
|
'EDIT_TEST',
|
||||||
|
'DELETE_TEST',
|
||||||
|
'UPSERT_TEST',
|
||||||
|
]);
|
||||||
|
expect(Object.keys(store.actions)).toEqual([
|
||||||
|
'get',
|
||||||
|
'show',
|
||||||
|
'create',
|
||||||
|
'update',
|
||||||
|
'delete',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a store with custom actions and getters', () => {
|
||||||
|
const API = {};
|
||||||
|
const customGetters = { customGetter: () => 'custom' };
|
||||||
|
const customActions = () => ({
|
||||||
|
customAction: () => 'custom',
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = createStore({
|
||||||
|
name: 'test',
|
||||||
|
API,
|
||||||
|
getters: customGetters,
|
||||||
|
actions: customActions,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(store.getters).toHaveProperty('customGetter');
|
||||||
|
expect(store.actions).toHaveProperty('customAction');
|
||||||
|
expect(Object.keys(store.getters)).toEqual([
|
||||||
|
'getRecords',
|
||||||
|
'getRecord',
|
||||||
|
'getUIFlags',
|
||||||
|
'getMeta',
|
||||||
|
'customGetter',
|
||||||
|
]);
|
||||||
|
expect(Object.keys(store.actions)).toEqual([
|
||||||
|
'get',
|
||||||
|
'show',
|
||||||
|
'create',
|
||||||
|
'update',
|
||||||
|
'delete',
|
||||||
|
'customAction',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
77
app/javascript/dashboard/store/captain/storeFactoryHelper.js
Normal file
77
app/javascript/dashboard/store/captain/storeFactoryHelper.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { throwErrorMessage } from 'dashboard/store/utils/api';
|
||||||
|
|
||||||
|
export const getRecords =
|
||||||
|
(mutationTypes, API) =>
|
||||||
|
async ({ commit }, params = {}) => {
|
||||||
|
commit(mutationTypes.SET_UI_FLAG, { fetchingList: true });
|
||||||
|
try {
|
||||||
|
const response = await API.get(params);
|
||||||
|
commit(mutationTypes.SET, response.data.payload);
|
||||||
|
commit(mutationTypes.SET_META, response.data.meta);
|
||||||
|
return response.data.payload;
|
||||||
|
} catch (error) {
|
||||||
|
return throwErrorMessage(error);
|
||||||
|
} finally {
|
||||||
|
commit(mutationTypes.SET_UI_FLAG, { fetchingList: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const showRecord =
|
||||||
|
(mutationTypes, API) =>
|
||||||
|
async ({ commit }, id) => {
|
||||||
|
commit(mutationTypes.SET_UI_FLAG, { fetchingItem: true });
|
||||||
|
try {
|
||||||
|
const response = await API.show(id);
|
||||||
|
commit(mutationTypes.ADD, response.data);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
return throwErrorMessage(error);
|
||||||
|
} finally {
|
||||||
|
commit(mutationTypes.SET_UI_FLAG, { fetchingItem: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createRecord =
|
||||||
|
(mutationTypes, API) =>
|
||||||
|
async ({ commit }, dataObj) => {
|
||||||
|
commit(mutationTypes.SET_UI_FLAG, { creatingItem: true });
|
||||||
|
try {
|
||||||
|
const response = await API.create(dataObj);
|
||||||
|
commit(mutationTypes.UPSERT, response.data);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
return throwErrorMessage(error);
|
||||||
|
} finally {
|
||||||
|
commit(mutationTypes.SET_UI_FLAG, { creatingItem: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateRecord =
|
||||||
|
(mutationTypes, API) =>
|
||||||
|
async ({ commit }, { id, ...updateObj }) => {
|
||||||
|
commit(mutationTypes.SET_UI_FLAG, { updatingItem: true });
|
||||||
|
try {
|
||||||
|
const response = await API.update(id, updateObj);
|
||||||
|
commit(mutationTypes.EDIT, response.data);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
return throwErrorMessage(error);
|
||||||
|
} finally {
|
||||||
|
commit(mutationTypes.SET_UI_FLAG, { updatingItem: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteRecord =
|
||||||
|
(mutationTypes, API) =>
|
||||||
|
async ({ commit }, id) => {
|
||||||
|
commit(mutationTypes.SET_UI_FLAG, { deletingItem: true });
|
||||||
|
try {
|
||||||
|
await API.delete(id);
|
||||||
|
commit(mutationTypes.DELETE, id);
|
||||||
|
return id;
|
||||||
|
} catch (error) {
|
||||||
|
return throwErrorMessage(error);
|
||||||
|
} finally {
|
||||||
|
commit(mutationTypes.SET_UI_FLAG, { deletingItem: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -51,6 +51,9 @@ import captainDocuments from './captain/document';
|
|||||||
import captainResponses from './captain/response';
|
import captainResponses from './captain/response';
|
||||||
import captainInboxes from './captain/inboxes';
|
import captainInboxes from './captain/inboxes';
|
||||||
import captainBulkActions from './captain/bulkActions';
|
import captainBulkActions from './captain/bulkActions';
|
||||||
|
import copilotThreads from './captain/copilotThreads';
|
||||||
|
import copilotMessages from './captain/copilotMessages';
|
||||||
|
|
||||||
const plugins = [];
|
const plugins = [];
|
||||||
|
|
||||||
export default createStore({
|
export default createStore({
|
||||||
@@ -106,6 +109,8 @@ export default createStore({
|
|||||||
captainResponses,
|
captainResponses,
|
||||||
captainInboxes,
|
captainInboxes,
|
||||||
captainBulkActions,
|
captainBulkActions,
|
||||||
|
copilotThreads,
|
||||||
|
copilotMessages,
|
||||||
},
|
},
|
||||||
plugins,
|
plugins,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user