From 625302ac3cd3b78c7fe263e67502a4c646b623dd Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 8 Jun 2026 18:39:40 +0530 Subject: [PATCH] refactor: deduplicate cache revalidation actions --- .../dashboard/store/modules/agents.js | 16 ++-- .../dashboard/store/modules/attributes.js | 16 ++-- .../dashboard/store/modules/cannedResponse.js | 20 ++--- .../dashboard/store/modules/inboxes.js | 17 ++-- .../dashboard/store/modules/labels.js | 17 ++-- .../dashboard/store/modules/teams/actions.js | 18 ++-- .../dashboard/store/utils/cacheRevalidate.js | 14 ++++ .../store/utils/specs/cacheRevalidate.spec.js | 82 +++++++++++++++++++ 8 files changed, 129 insertions(+), 71 deletions(-) create mode 100644 app/javascript/dashboard/store/utils/cacheRevalidate.js create mode 100644 app/javascript/dashboard/store/utils/specs/cacheRevalidate.spec.js diff --git a/app/javascript/dashboard/store/modules/agents.js b/app/javascript/dashboard/store/modules/agents.js index 1e3200c77..5dd567b5d 100644 --- a/app/javascript/dashboard/store/modules/agents.js +++ b/app/javascript/dashboard/store/modules/agents.js @@ -1,6 +1,7 @@ import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers'; import * as types from '../mutation-types'; import AgentAPI from '../../api/agents'; +import { createCacheRevalidateAction } from '../utils/cacheRevalidate'; export const state = { records: [], @@ -51,17 +52,10 @@ export const actions = { commit(types.default.SET_AGENT_FETCHING_STATUS, false); } }, - revalidate: async ({ commit }, { newKey }) => { - try { - const isExistingKeyValid = await AgentAPI.validateCacheKey(newKey); - if (!isExistingKeyValid) { - const response = await AgentAPI.refetchAndCommit(newKey); - commit(types.default.SET_AGENTS, response.data); - } - } catch (error) { - // Ignore error - } - }, + revalidate: createCacheRevalidateAction({ + api: AgentAPI, + mutation: types.default.SET_AGENTS, + }), create: async ({ commit }, agentInfo) => { commit(types.default.SET_AGENT_CREATING_STATUS, true); try { diff --git a/app/javascript/dashboard/store/modules/attributes.js b/app/javascript/dashboard/store/modules/attributes.js index 84a440618..d90966d4d 100644 --- a/app/javascript/dashboard/store/modules/attributes.js +++ b/app/javascript/dashboard/store/modules/attributes.js @@ -2,6 +2,7 @@ import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers'; import types from '../mutation-types'; import AttributeAPI from '../../api/attributes'; import camelcaseKeys from 'camelcase-keys'; +import { createCacheRevalidateAction } from '../utils/cacheRevalidate'; export const state = { records: [], @@ -54,17 +55,10 @@ export const actions = { commit(types.SET_CUSTOM_ATTRIBUTE_UI_FLAG, { isFetching: false }); } }, - revalidate: async ({ commit }, { newKey }) => { - try { - const isExistingKeyValid = await AttributeAPI.validateCacheKey(newKey); - if (!isExistingKeyValid) { - const response = await AttributeAPI.refetchAndCommit(newKey); - commit(types.SET_CUSTOM_ATTRIBUTE, response.data); - } - } catch (error) { - // Ignore error - } - }, + revalidate: createCacheRevalidateAction({ + api: AttributeAPI, + mutation: types.SET_CUSTOM_ATTRIBUTE, + }), create: async function createAttribute({ commit }, attributeObj) { commit(types.SET_CUSTOM_ATTRIBUTE_UI_FLAG, { isCreating: true }); try { diff --git a/app/javascript/dashboard/store/modules/cannedResponse.js b/app/javascript/dashboard/store/modules/cannedResponse.js index f8301467f..c06c50b80 100644 --- a/app/javascript/dashboard/store/modules/cannedResponse.js +++ b/app/javascript/dashboard/store/modules/cannedResponse.js @@ -1,4 +1,5 @@ import { throwErrorMessage } from 'dashboard/store/utils/api'; +import { createCacheRevalidateAction } from 'dashboard/store/utils/cacheRevalidate'; import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers'; import * as types from '../mutation-types'; import CannedResponseAPI from '../../api/cannedResponse'; @@ -47,21 +48,10 @@ const actions = { } }, - revalidateCannedResponses: async function revalidateCannedResponses( - { commit }, - { newKey } - ) { - try { - const isExistingKeyValid = - await CannedResponseAPI.validateCacheKey(newKey); - if (!isExistingKeyValid) { - const response = await CannedResponseAPI.refetchAndCommit(newKey); - commit(types.default.SET_CANNED, response.data); - } - } catch (error) { - // Ignore error - } - }, + revalidateCannedResponses: createCacheRevalidateAction({ + api: CannedResponseAPI, + mutation: types.default.SET_CANNED, + }), createCannedResponse: async function createCannedResponse( { commit }, diff --git a/app/javascript/dashboard/store/modules/inboxes.js b/app/javascript/dashboard/store/modules/inboxes.js index ce24ef653..aa2f7d69a 100644 --- a/app/javascript/dashboard/store/modules/inboxes.js +++ b/app/javascript/dashboard/store/modules/inboxes.js @@ -7,6 +7,7 @@ import FBChannel from '../../api/channel/fbChannel'; import TwilioChannel from '../../api/channel/twilioChannel'; import WhatsappChannel from '../../api/channel/whatsappChannel'; import { throwErrorMessage } from '../utils/api'; +import { createCacheRevalidateAction } from '../utils/cacheRevalidate'; import AnalyticsHelper from '../../helper/AnalyticsHelper'; import camelcaseKeys from 'camelcase-keys'; import { ACCOUNT_EVENTS } from '../../helper/AnalyticsHelper/events'; @@ -189,17 +190,11 @@ const sendAnalyticsEvent = channelType => { }; export const actions = { - revalidate: async ({ commit }, { newKey }) => { - try { - const isExistingKeyValid = await InboxesAPI.validateCacheKey(newKey); - if (!isExistingKeyValid) { - const response = await InboxesAPI.refetchAndCommit(newKey); - commit(types.default.SET_INBOXES, response.data.payload); - } - } catch (error) { - // Ignore error - } - }, + revalidate: createCacheRevalidateAction({ + api: InboxesAPI, + mutation: types.default.SET_INBOXES, + getData: response => response.data.payload, + }), get: async ({ commit }) => { commit(types.default.SET_INBOXES_UI_FLAG, { isFetching: true }); try { diff --git a/app/javascript/dashboard/store/modules/labels.js b/app/javascript/dashboard/store/modules/labels.js index 658f212fd..b06bce998 100644 --- a/app/javascript/dashboard/store/modules/labels.js +++ b/app/javascript/dashboard/store/modules/labels.js @@ -3,6 +3,7 @@ import types from '../mutation-types'; import LabelsAPI from '../../api/labels'; import AnalyticsHelper from '../../helper/AnalyticsHelper'; import { LABEL_EVENTS } from '../../helper/AnalyticsHelper/events'; +import { createCacheRevalidateAction } from '../utils/cacheRevalidate'; export const state = { records: [], @@ -32,17 +33,11 @@ export const getters = { }; export const actions = { - revalidate: async function revalidate({ commit }, { newKey }) { - try { - const isExistingKeyValid = await LabelsAPI.validateCacheKey(newKey); - if (!isExistingKeyValid) { - const response = await LabelsAPI.refetchAndCommit(newKey); - commit(types.SET_LABELS, response.data.payload); - } - } catch (error) { - // Ignore error - } - }, + revalidate: createCacheRevalidateAction({ + api: LabelsAPI, + mutation: types.SET_LABELS, + getData: response => response.data.payload, + }), get: async function getLabels({ commit }) { commit(types.SET_LABEL_UI_FLAG, { isFetching: true }); diff --git a/app/javascript/dashboard/store/modules/teams/actions.js b/app/javascript/dashboard/store/modules/teams/actions.js index 3b8209017..baf285822 100644 --- a/app/javascript/dashboard/store/modules/teams/actions.js +++ b/app/javascript/dashboard/store/modules/teams/actions.js @@ -7,6 +7,7 @@ import { DELETE_TEAM, } from './types'; import TeamsAPI from '../../../api/teams'; +import { createCacheRevalidateAction } from '../../utils/cacheRevalidate'; export const actions = { create: async ({ commit }, teamInfo) => { @@ -22,18 +23,11 @@ export const actions = { commit(SET_TEAM_UI_FLAG, { isCreating: false }); } }, - revalidate: async ({ commit }, { newKey }) => { - try { - const isExistingKeyValid = await TeamsAPI.validateCacheKey(newKey); - if (!isExistingKeyValid) { - const response = await TeamsAPI.refetchAndCommit(newKey); - commit(CLEAR_TEAMS); - commit(SET_TEAMS, response.data); - } - } catch (error) { - // Ignore error - } - }, + revalidate: createCacheRevalidateAction({ + api: TeamsAPI, + mutation: SET_TEAMS, + clearMutation: CLEAR_TEAMS, + }), get: async ({ commit }) => { commit(SET_TEAM_UI_FLAG, { isFetching: true }); try { diff --git a/app/javascript/dashboard/store/utils/cacheRevalidate.js b/app/javascript/dashboard/store/utils/cacheRevalidate.js new file mode 100644 index 000000000..68847696e --- /dev/null +++ b/app/javascript/dashboard/store/utils/cacheRevalidate.js @@ -0,0 +1,14 @@ +export const createCacheRevalidateAction = + ({ api, mutation, clearMutation, getData = response => response.data }) => + async ({ commit }, { newKey }) => { + try { + const isExistingKeyValid = await api.validateCacheKey(newKey); + if (isExistingKeyValid) return; + + const response = await api.refetchAndCommit(newKey); + if (clearMutation) commit(clearMutation); + commit(mutation, getData(response)); + } catch (error) { + // Ignore error + } + }; diff --git a/app/javascript/dashboard/store/utils/specs/cacheRevalidate.spec.js b/app/javascript/dashboard/store/utils/specs/cacheRevalidate.spec.js new file mode 100644 index 000000000..b9b9624c8 --- /dev/null +++ b/app/javascript/dashboard/store/utils/specs/cacheRevalidate.spec.js @@ -0,0 +1,82 @@ +import { createCacheRevalidateAction } from '../cacheRevalidate'; + +describe('#createCacheRevalidateAction', () => { + const commit = vi.fn(); + + beforeEach(() => { + commit.mockReset(); + }); + + it('refetches and commits data when the cache key is stale', async () => { + const api = { + validateCacheKey: vi.fn().mockResolvedValue(false), + refetchAndCommit: vi.fn().mockResolvedValue({ data: [{ id: 1 }] }), + }; + + const action = createCacheRevalidateAction({ + api, + mutation: 'SET_RECORDS', + }); + + await action({ commit }, { newKey: 'new-key' }); + + expect(api.validateCacheKey).toHaveBeenCalledWith('new-key'); + expect(api.refetchAndCommit).toHaveBeenCalledWith('new-key'); + expect(commit).toHaveBeenCalledWith('SET_RECORDS', [{ id: 1 }]); + }); + + it('skips refetch when the cache key is current', async () => { + const api = { + validateCacheKey: vi.fn().mockResolvedValue(true), + refetchAndCommit: vi.fn(), + }; + + const action = createCacheRevalidateAction({ + api, + mutation: 'SET_RECORDS', + }); + + await action({ commit }, { newKey: 'new-key' }); + + expect(api.refetchAndCommit).not.toHaveBeenCalled(); + expect(commit).not.toHaveBeenCalled(); + }); + + it('supports clear-before-set and custom response data extraction', async () => { + const api = { + validateCacheKey: vi.fn().mockResolvedValue(false), + refetchAndCommit: vi + .fn() + .mockResolvedValue({ data: { payload: [{ id: 1 }] } }), + }; + + const action = createCacheRevalidateAction({ + api, + mutation: 'SET_RECORDS', + clearMutation: 'CLEAR_RECORDS', + getData: response => response.data.payload, + }); + + await action({ commit }, { newKey: 'new-key' }); + + expect(commit.mock.calls).toEqual([ + ['CLEAR_RECORDS'], + ['SET_RECORDS', [{ id: 1 }]], + ]); + }); + + it('ignores revalidation errors', async () => { + const api = { + validateCacheKey: vi.fn().mockRejectedValue(new Error('boom')), + refetchAndCommit: vi.fn(), + }; + + const action = createCacheRevalidateAction({ + api, + mutation: 'SET_RECORDS', + }); + + await expect(action({ commit }, { newKey: 'new-key' })).resolves.toBe(); + expect(commit).not.toHaveBeenCalled(); + }); +});