From f42fddd38e1d9563799619cfb3bd17e706bafc0c Mon Sep 17 00:00:00 2001 From: Pranav Date: Tue, 27 May 2025 18:36:32 -0600 Subject: [PATCH] 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. --- .../dashboard/api/captain/copilotMessages.js | 18 + .../dashboard/api/captain/copilotThreads.js | 9 + .../dashboard/helper/actionCable.js | 5 + .../helper/specs/actionCable.spec.js | 67 +++ .../store/captain/copilotMessages.js | 19 + .../dashboard/store/captain/copilotThreads.js | 7 + .../dashboard/store/captain/storeFactory.js | 90 +---- .../store/captain/storeFactory.spec.js | 380 ++++++++++++++++++ .../store/captain/storeFactoryHelper.js | 77 ++++ app/javascript/dashboard/store/index.js | 5 + 10 files changed, 607 insertions(+), 70 deletions(-) create mode 100644 app/javascript/dashboard/api/captain/copilotMessages.js create mode 100644 app/javascript/dashboard/api/captain/copilotThreads.js create mode 100644 app/javascript/dashboard/helper/specs/actionCable.spec.js create mode 100644 app/javascript/dashboard/store/captain/copilotMessages.js create mode 100644 app/javascript/dashboard/store/captain/copilotThreads.js create mode 100644 app/javascript/dashboard/store/captain/storeFactory.spec.js create mode 100644 app/javascript/dashboard/store/captain/storeFactoryHelper.js diff --git a/app/javascript/dashboard/api/captain/copilotMessages.js b/app/javascript/dashboard/api/captain/copilotMessages.js new file mode 100644 index 000000000..49e05398a --- /dev/null +++ b/app/javascript/dashboard/api/captain/copilotMessages.js @@ -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(); diff --git a/app/javascript/dashboard/api/captain/copilotThreads.js b/app/javascript/dashboard/api/captain/copilotThreads.js new file mode 100644 index 000000000..7fdce3b91 --- /dev/null +++ b/app/javascript/dashboard/api/captain/copilotThreads.js @@ -0,0 +1,9 @@ +import ApiClient from '../ApiClient'; + +class CopilotThreads extends ApiClient { + constructor() { + super('captain/copilot_threads', { accountScoped: true }); + } +} + +export default new CopilotThreads(); diff --git a/app/javascript/dashboard/helper/actionCable.js b/app/javascript/dashboard/helper/actionCable.js index 515806b31..991576e66 100644 --- a/app/javascript/dashboard/helper/actionCable.js +++ b/app/javascript/dashboard/helper/actionCable.js @@ -33,6 +33,7 @@ class ActionCableConnector extends BaseActionCableConnector { 'conversation.read': this.onConversationRead, 'conversation.updated': this.onConversationUpdated, '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); }; + onCopilotMessageCreated = data => { + this.app.$store.dispatch('copilotMessages/upsert', data); + }; + onCacheInvalidate = data => { const keys = data.cache_keys; this.app.$store.dispatch('labels/revalidate', { newKey: keys.label }); diff --git a/app/javascript/dashboard/helper/specs/actionCable.spec.js b/app/javascript/dashboard/helper/specs/actionCable.spec.js new file mode 100644 index 000000000..4ad8a52c6 --- /dev/null +++ b/app/javascript/dashboard/helper/specs/actionCable.spec.js @@ -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 + ); + }); + }); +}); diff --git a/app/javascript/dashboard/store/captain/copilotMessages.js b/app/javascript/dashboard/store/captain/copilotMessages.js new file mode 100644 index 000000000..83b7fddce --- /dev/null +++ b/app/javascript/dashboard/store/captain/copilotMessages.js @@ -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); + }, + }), +}); diff --git a/app/javascript/dashboard/store/captain/copilotThreads.js b/app/javascript/dashboard/store/captain/copilotThreads.js new file mode 100644 index 000000000..8d820f305 --- /dev/null +++ b/app/javascript/dashboard/store/captain/copilotThreads.js @@ -0,0 +1,7 @@ +import CopilotThreadsAPI from 'dashboard/api/captain/copilotThreads'; +import { createStore } from './storeFactory'; + +export default createStore({ + name: 'CopilotThreads', + API: CopilotThreadsAPI, +}); diff --git a/app/javascript/dashboard/store/captain/storeFactory.js b/app/javascript/dashboard/store/captain/storeFactory.js index a55522062..ad669f62b 100644 --- a/app/javascript/dashboard/store/captain/storeFactory.js +++ b/app/javascript/dashboard/store/captain/storeFactory.js @@ -1,5 +1,11 @@ -import { throwErrorMessage } from 'dashboard/store/utils/api'; import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers'; +import { + createRecord, + deleteRecord, + getRecords, + showRecord, + updateRecord, +} from './storeFactoryHelper'; export const generateMutationTypes = name => { const capitalizedName = name.toUpperCase(); @@ -10,6 +16,7 @@ export const generateMutationTypes = name => { EDIT: `EDIT_${capitalizedName}`, DELETE: `DELETE_${capitalizedName}`, SET_META: `SET_${capitalizedName}_META`, + UPSERT: `UPSERT_${capitalizedName}`, }; }; @@ -33,7 +40,6 @@ export const createGetters = () => ({ getMeta: state => state.meta, }); -// store/mutations.js export const createMutations = mutationTypes => ({ [mutationTypes.SET_UI_FLAG](state, data) { state.uiFlags = { @@ -51,78 +57,19 @@ export const createMutations = mutationTypes => ({ [mutationTypes.ADD]: MutationHelpers.create, [mutationTypes.EDIT]: MutationHelpers.update, [mutationTypes.DELETE]: MutationHelpers.destroy, + [mutationTypes.UPSERT]: MutationHelpers.setSingleRecord, }); -// store/actions/crud.js export const createCrudActions = (API, mutationTypes) => ({ - async get({ 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 }); - } - }, - - 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 }); - } - }, + get: getRecords(mutationTypes, API), + show: showRecord(mutationTypes, API), + create: createRecord(mutationTypes, API), + update: updateRecord(mutationTypes, API), + delete: deleteRecord(mutationTypes, API), }); + export const createStore = options => { - const { name, API, actions } = options; + const { name, API, actions, getters } = options; const mutationTypes = generateMutationTypes(name); const customActions = actions ? actions(mutationTypes) : {}; @@ -130,7 +77,10 @@ export const createStore = options => { return { namespaced: true, state: createInitialState(), - getters: createGetters(), + getters: { + ...createGetters(), + ...(getters || {}), + }, mutations: createMutations(mutationTypes), actions: { ...createCrudActions(API, mutationTypes), diff --git a/app/javascript/dashboard/store/captain/storeFactory.spec.js b/app/javascript/dashboard/store/captain/storeFactory.spec.js new file mode 100644 index 000000000..0aec26e40 --- /dev/null +++ b/app/javascript/dashboard/store/captain/storeFactory.spec.js @@ -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', + ]); + }); + }); +}); diff --git a/app/javascript/dashboard/store/captain/storeFactoryHelper.js b/app/javascript/dashboard/store/captain/storeFactoryHelper.js new file mode 100644 index 000000000..7a04d2e81 --- /dev/null +++ b/app/javascript/dashboard/store/captain/storeFactoryHelper.js @@ -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 }); + } + }; diff --git a/app/javascript/dashboard/store/index.js b/app/javascript/dashboard/store/index.js index 5daf73ae1..960285ebf 100755 --- a/app/javascript/dashboard/store/index.js +++ b/app/javascript/dashboard/store/index.js @@ -51,6 +51,9 @@ import captainDocuments from './captain/document'; import captainResponses from './captain/response'; import captainInboxes from './captain/inboxes'; import captainBulkActions from './captain/bulkActions'; +import copilotThreads from './captain/copilotThreads'; +import copilotMessages from './captain/copilotMessages'; + const plugins = []; export default createStore({ @@ -106,6 +109,8 @@ export default createStore({ captainResponses, captainInboxes, captainBulkActions, + copilotThreads, + copilotMessages, }, plugins, });