diff --git a/app/javascript/dashboard/helper/ConversationMetaThrottleManager.js b/app/javascript/dashboard/helper/ConversationMetaThrottleManager.js new file mode 100644 index 000000000..4f7c7a610 --- /dev/null +++ b/app/javascript/dashboard/helper/ConversationMetaThrottleManager.js @@ -0,0 +1,29 @@ +class ConversationMetaThrottleManager { + constructor() { + this.lastUpdatedTime = null; + } + + shouldThrottle(threshold = 10000) { + if (!this.lastUpdatedTime) { + return false; + } + + const currentTime = new Date().getTime(); + const lastUpdatedTime = new Date(this.lastUpdatedTime).getTime(); + + if (currentTime - lastUpdatedTime < threshold) { + return true; + } + return false; + } + + markUpdate() { + this.lastUpdatedTime = new Date(); + } + + reset() { + this.lastUpdatedTime = null; + } +} + +export default new ConversationMetaThrottleManager(); diff --git a/app/javascript/dashboard/helper/specs/ConversationMetaThrottleManager.spec.js b/app/javascript/dashboard/helper/specs/ConversationMetaThrottleManager.spec.js new file mode 100644 index 000000000..b0c67ffaa --- /dev/null +++ b/app/javascript/dashboard/helper/specs/ConversationMetaThrottleManager.spec.js @@ -0,0 +1,34 @@ +import ConversationMetaThrottleManager from '../ConversationMetaThrottleManager'; + +describe('ConversationMetaThrottleManager', () => { + beforeEach(() => { + // Reset the lastUpdatedTime before each test + ConversationMetaThrottleManager.lastUpdatedTime = null; + }); + + describe('shouldThrottle', () => { + it('returns false when lastUpdatedTime is not set', () => { + expect(ConversationMetaThrottleManager.shouldThrottle()).toBe(false); + }); + + it('returns true when time difference is less than threshold', () => { + ConversationMetaThrottleManager.markUpdate(); + expect(ConversationMetaThrottleManager.shouldThrottle()).toBe(true); + }); + + it('returns false when time difference is more than threshold', () => { + ConversationMetaThrottleManager.lastUpdatedTime = new Date( + Date.now() - 11000 + ); + expect(ConversationMetaThrottleManager.shouldThrottle()).toBe(false); + }); + + it('respects custom threshold value', () => { + ConversationMetaThrottleManager.lastUpdatedTime = new Date( + Date.now() - 5000 + ); + expect(ConversationMetaThrottleManager.shouldThrottle(3000)).toBe(false); + expect(ConversationMetaThrottleManager.shouldThrottle(6000)).toBe(true); + }); + }); +}); diff --git a/app/javascript/dashboard/store/modules/conversationStats.js b/app/javascript/dashboard/store/modules/conversationStats.js index 35fc1c6ab..8de20370b 100644 --- a/app/javascript/dashboard/store/modules/conversationStats.js +++ b/app/javascript/dashboard/store/modules/conversationStats.js @@ -1,28 +1,40 @@ import types from '../mutation-types'; import ConversationApi from '../../api/inbox/conversation'; +import ConversationMetaThrottleManager from 'dashboard/helper/ConversationMetaThrottleManager'; + const state = { mineCount: 0, unAssignedCount: 0, allCount: 0, - updatedOn: null, }; export const getters = { getStats: $state => $state, }; +export const shouldThrottle = conversationCount => { + // The threshold for throttling is different for normal users and large accounts + // Normal users: 2 seconds + // Large accounts: 10 seconds + // We would only update the conversation stats based on the threshold above. + // This is done to reduce the number of /meta request made to the server. + const NORMAL_USER_THRESHOLD = 2000; + const LARGE_ACCOUNT_THRESHOLD = 10000; + + const threshold = + conversationCount > 100 ? LARGE_ACCOUNT_THRESHOLD : NORMAL_USER_THRESHOLD; + return ConversationMetaThrottleManager.shouldThrottle(threshold); +}; + export const actions = { get: async ({ commit, state: $state }, params) => { - const currentTime = new Date(); - const lastUpdatedTime = new Date($state.updatedOn); - - // Skip large accounts from making too many requests - if (currentTime - lastUpdatedTime < 10000 && $state.allCount > 100) { + if (shouldThrottle($state.allCount)) { // eslint-disable-next-line no-console - console.warn('Skipping conversation meta fetch'); + console.warn('Throttle /meta fetch, will resume after threshold'); return; } + ConversationMetaThrottleManager.markUpdate(); try { const response = await ConversationApi.meta(params); diff --git a/app/javascript/dashboard/store/modules/specs/conversationStats/helper.spec.js b/app/javascript/dashboard/store/modules/specs/conversationStats/helper.spec.js new file mode 100644 index 000000000..eb07fd1c8 --- /dev/null +++ b/app/javascript/dashboard/store/modules/specs/conversationStats/helper.spec.js @@ -0,0 +1,37 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import ConversationMetaThrottleManager from 'dashboard/helper/ConversationMetaThrottleManager'; +import { shouldThrottle } from '../../conversationStats'; + +vi.mock('dashboard/helper/ConversationMetaThrottleManager', () => ({ + default: { + shouldThrottle: vi.fn(), + }, +})); + +describe('shouldThrottle', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('uses normal threshold for accounts with 100 or fewer conversations', () => { + shouldThrottle(100); + expect(ConversationMetaThrottleManager.shouldThrottle).toHaveBeenCalledWith( + 2000 + ); + }); + + it('uses large account threshold for accounts with more than 100 conversations', () => { + shouldThrottle(101); + expect(ConversationMetaThrottleManager.shouldThrottle).toHaveBeenCalledWith( + 10000 + ); + }); + + it('returns the throttle value from ConversationMetaThrottleManager', () => { + ConversationMetaThrottleManager.shouldThrottle.mockReturnValue(true); + expect(shouldThrottle(50)).toBe(true); + + ConversationMetaThrottleManager.shouldThrottle.mockReturnValue(false); + expect(shouldThrottle(150)).toBe(false); + }); +}); diff --git a/app/javascript/widget/assets/scss/woot.scss b/app/javascript/widget/assets/scss/woot.scss index 61f432ab5..3a8496d70 100755 --- a/app/javascript/widget/assets/scss/woot.scss +++ b/app/javascript/widget/assets/scss/woot.scss @@ -10,7 +10,7 @@ html, body { - @apply antialiased h-full bg-n-background; + @apply antialiased h-full; } .is-mobile {