feat: expand idb cache to agents, canned responses, and custom attributes

This commit is contained in:
Shivam Mishra
2026-06-10 13:16:37 +05:30
parent dec5468cd5
commit 8218880424
22 changed files with 143 additions and 27 deletions
+3 -3
View File
@@ -1,10 +1,10 @@
/* global axios */
import ApiClient from './ApiClient';
import CacheEnabledApiClient from './CacheEnabledApiClient';
class Agents extends ApiClient {
class Agents extends CacheEnabledApiClient {
constructor() {
super('agents', { accountScoped: true });
super('agents', { accountScoped: true, cacheModel: 'account_user' });
}
bulkInvite({ emails }) {
+7 -5
View File
@@ -1,13 +1,15 @@
/* global axios */
import ApiClient from './ApiClient';
import CacheEnabledApiClient from './CacheEnabledApiClient';
class AttributeAPI extends ApiClient {
class AttributeAPI extends CacheEnabledApiClient {
constructor() {
super('custom_attribute_definitions', { accountScoped: true });
super('custom_attribute_definitions', {
accountScoped: true,
cacheModel: 'custom_attribute_definition',
});
}
getAttributesByModel() {
return axios.get(this.url);
return super.get(true);
}
}
+11 -6
View File
@@ -1,15 +1,20 @@
/* global axios */
import ApiClient from './ApiClient';
import CacheEnabledApiClient from './CacheEnabledApiClient';
class CannedResponse extends ApiClient {
class CannedResponse extends CacheEnabledApiClient {
constructor() {
super('canned_responses', { accountScoped: true });
super('canned_responses', {
accountScoped: true,
cacheModel: 'canned_response',
});
}
get({ searchKey }) {
const url = searchKey ? `${this.url}?search=${searchKey}` : this.url;
return axios.get(url);
get({ searchKey } = {}) {
if (searchKey) {
return axios.get(`${this.url}?search=${searchKey}`);
}
return super.get(true);
}
}
@@ -12,6 +12,12 @@ export const cacheableModels = [
{ name: 'inbox', setMutation: 'inboxes/SET_INBOXES' },
{ name: 'label', setMutation: 'labels/SET_LABELS' },
{ name: 'team', setMutation: 'teams/SET_TEAMS' },
{ name: 'canned_response', setMutation: 'SET_CANNED' },
{ name: 'account_user', setMutation: 'agents/SET_AGENTS' },
{
name: 'custom_attribute_definition',
setMutation: 'attributes/SET_CUSTOM_ATTRIBUTE',
},
];
export const cacheableModelNames = cacheableModels.map(model => model.name);
@@ -1,3 +1,6 @@
import AgentAPI from 'dashboard/api/agents';
import AttributeAPI from 'dashboard/api/attributes';
import CannedResponseAPI from 'dashboard/api/cannedResponse';
import InboxesAPI from 'dashboard/api/inboxes';
import LabelsAPI from 'dashboard/api/labels';
import TeamsAPI from 'dashboard/api/teams';
@@ -10,6 +13,9 @@ const apiByModel = {
inbox: InboxesAPI,
label: LabelsAPI,
team: TeamsAPI,
canned_response: CannedResponseAPI,
account_user: AgentAPI,
custom_attribute_definition: AttributeAPI,
};
const revalidateModel = async (store, model, newKey) => {
@@ -1,3 +1,9 @@
// Monday, 13 March 2023
// Change this version if you want to invalidate old data
export const DATA_VERSION = '1678706392';
// Bump DATA_VERSION to (a) add new object stores to the IDB schema or (b)
// flush bad/stale cache globally. The `upgrade()` callback in DataManager runs
// only when the stored DB version is less than the requested version; on any
// such bump it clears every existing store (a full cache reset) and then
// idempotently creates any missing stores. So bump this whenever a cached
// model's serializer shape changes, or to force all clients to refetch.
//
// Thursday, 28 May 2026 — bumped to add canned_response + account_user stores + custom_attribute_definition store
export const DATA_VERSION = '1748390400';
@@ -1,6 +1,7 @@
import { dispatchCacheRevalidations } from '../../CacheHelper/dispatchCacheRevalidations';
import InboxesAPI from 'dashboard/api/inboxes';
import LabelsAPI from 'dashboard/api/labels';
import CannedResponseAPI from 'dashboard/api/cannedResponse';
import TeamsAPI from 'dashboard/api/teams';
vi.mock('dashboard/api/inboxes', () => ({
@@ -24,6 +25,27 @@ vi.mock('dashboard/api/teams', () => ({
extractDataFromResponse: vi.fn(),
},
}));
vi.mock('dashboard/api/cannedResponse', () => ({
default: {
validateCacheKey: vi.fn(),
refetchAndCommit: vi.fn(),
extractDataFromResponse: vi.fn(),
},
}));
vi.mock('dashboard/api/agents', () => ({
default: {
validateCacheKey: vi.fn(),
refetchAndCommit: vi.fn(),
extractDataFromResponse: vi.fn(),
},
}));
vi.mock('dashboard/api/attributes', () => ({
default: {
validateCacheKey: vi.fn(),
refetchAndCommit: vi.fn(),
extractDataFromResponse: vi.fn(),
},
}));
describe('dispatchCacheRevalidations', () => {
let store;
@@ -71,16 +93,16 @@ describe('dispatchCacheRevalidations', () => {
it('swallows per-model errors so one failure does not block the rest', async () => {
InboxesAPI.validateCacheKey.mockResolvedValue(false);
InboxesAPI.refetchAndCommit.mockRejectedValue(new Error('network down'));
TeamsAPI.validateCacheKey.mockResolvedValue(false);
TeamsAPI.refetchAndCommit.mockResolvedValue({ data: [] });
TeamsAPI.extractDataFromResponse.mockReturnValue([{ id: 7 }]);
CannedResponseAPI.validateCacheKey.mockResolvedValue(false);
CannedResponseAPI.refetchAndCommit.mockResolvedValue({ data: [] });
CannedResponseAPI.extractDataFromResponse.mockReturnValue([{ id: 7 }]);
await dispatchCacheRevalidations(store, {
inbox: 'inbox-key',
team: 'team-key',
canned_response: 'canned-key',
});
expect(store.commit).toHaveBeenCalledWith('teams/SET_TEAMS', [{ id: 7 }]);
expect(store.commit).toHaveBeenCalledWith('SET_CANNED', [{ id: 7 }]);
expect(store.commit).toHaveBeenCalledTimes(1);
});
});
@@ -44,7 +44,7 @@ export const actions = {
get: async ({ commit }) => {
commit(types.default.SET_AGENT_FETCHING_STATUS, true);
try {
const response = await AgentAPI.get();
const response = await AgentAPI.get(true);
commit(types.default.SET_AGENT_FETCHING_STATUS, false);
commit(types.default.SET_AGENTS, response.data);
} catch (error) {
@@ -1,6 +1,7 @@
import axios from 'axios';
import { actions } from '../../agents';
import * as types from '../../../mutation-types';
import AgentAPI from '../../../../api/agents';
import agentList from './fixtures';
const commit = vi.fn();
@@ -8,6 +9,13 @@ const dispatch = vi.fn();
global.axios = axios;
vi.mock('axios');
// Clear the IDB-backed cache between tests so each case starts from a known
// empty state and isn't affected by data persisted by a previous test.
beforeEach(async () => {
await AgentAPI.dataManager.initDb();
await AgentAPI.dataManager.db.clear(AgentAPI.cacheModelName);
});
describe('#actions', () => {
describe('#get', () => {
it('sends correct actions if API is success', async () => {
@@ -1,12 +1,20 @@
import axios from 'axios';
import { actions } from '../../attributes';
import * as types from '../../../mutation-types';
import AttributeAPI from '../../../../api/attributes';
import attributesList from './fixtures';
const commit = vi.fn();
global.axios = axios;
vi.mock('axios');
// Clear the IDB-backed cache between tests so each case starts from a known
// empty state and isn't affected by data persisted by a previous test.
beforeEach(async () => {
await AttributeAPI.dataManager.initDb();
await AttributeAPI.dataManager.db.clear(AttributeAPI.cacheModelName);
});
describe('#actions', () => {
describe('#get', () => {
it('sends correct actions if API is success', async () => {