From 16185973387bcfa6f2691aadc8ef3fb7842a7611 Mon Sep 17 00:00:00 2001 From: Noley Holland Date: Mon, 16 Mar 2026 13:49:08 -0700 Subject: [PATCH 1/4] Convert vuex to pinia for expert ff agent --- frontend/src/store/modules/account/index.js | 4 +- frontend/src/stores/index.js | 1 + .../src/stores/product-expert-ff-agent.js | 27 ++++ .../stores/product-expert-ff-agent.spec.js | 150 ++++++++++++++++++ 4 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 frontend/src/stores/product-expert-ff-agent.js create mode 100644 test/unit/frontend/stores/product-expert-ff-agent.spec.js diff --git a/frontend/src/store/modules/account/index.js b/frontend/src/store/modules/account/index.js index 1ce72effe7..315d33ffff 100644 --- a/frontend/src/store/modules/account/index.js +++ b/frontend/src/store/modules/account/index.js @@ -7,10 +7,12 @@ import teamApi from '../../../api/team.js' import userApi from '../../../api/user.js' import { getTeamProperty } from '../../../composables/TeamProperties.js' import router from '../../../routes.js' + import product from '../../../services/product.js' import { useContextStore } from '@/stores/context.js' import { useProductAssistantStore } from '@/stores/product-assistant.js' +import { useProductExpertFfAgentStore } from '@/stores/product-expert-ff-agent.js' import { useUxDialogStore } from '@/stores/ux-dialog.js' import { useUxDrawersStore } from '@/stores/ux-drawers.js' import { useUxNavigationStore } from '@/stores/ux-navigation.js' @@ -528,7 +530,7 @@ const actions = { // Task 6: useProductTablesStore().$reset() // Task 7: useProductBrokersStore().$reset() useProductAssistantStore().$reset() - // Task 9: useProductExpertFfAgentStore().$reset() + useProductExpertFfAgentStore().$reset() // Task 10: useProductExpertOperatorAgentStore().$reset() // Task 11: useProductExpertStore().$reset() } diff --git a/frontend/src/stores/index.js b/frontend/src/stores/index.js index 3a8096bec8..1d0d547774 100644 --- a/frontend/src/stores/index.js +++ b/frontend/src/stores/index.js @@ -6,3 +6,4 @@ export { useUxNavigationStore } from './ux-navigation.js' export { useUxStore } from './ux.js' export { useUxDialogStore } from './ux-dialog.js' export { useUxToursStore } from './ux-tours.js' +export { useProductExpertFfAgentStore } from './product-expert-ff-agent.js' diff --git a/frontend/src/stores/product-expert-ff-agent.js b/frontend/src/stores/product-expert-ff-agent.js new file mode 100644 index 0000000000..66ddb8d6b4 --- /dev/null +++ b/frontend/src/stores/product-expert-ff-agent.js @@ -0,0 +1,27 @@ +import { defineStore } from 'pinia' +import { markRaw } from 'vue' + +export const useProductExpertFfAgentStore = defineStore('product-expert-ff-agent', { + state: () => ({ + context: null, + sessionId: null, + messages: [], + sessionStartTime: null, + sessionWarningShown: false, + sessionExpiredShown: false, + sessionCheckTimer: null + }), + actions: { + reset () { + if (this.sessionCheckTimer) clearInterval(this.sessionCheckTimer) + this.$reset() + }, + setSessionCheckTimer (timer) { + this.sessionCheckTimer = markRaw(timer) + } + }, + persist: { + pick: ['context', 'sessionId'], + storage: localStorage + } +}) diff --git a/test/unit/frontend/stores/product-expert-ff-agent.spec.js b/test/unit/frontend/stores/product-expert-ff-agent.spec.js new file mode 100644 index 0000000000..b6b3107237 --- /dev/null +++ b/test/unit/frontend/stores/product-expert-ff-agent.spec.js @@ -0,0 +1,150 @@ +import { createPinia, setActivePinia } from 'pinia' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { useProductExpertFfAgentStore } from '@/stores/product-expert-ff-agent.js' + +describe('useProductExpertFfAgentStore', () => { + beforeEach(() => { + setActivePinia(createPinia()) + }) + + describe('initial state', () => { + it('has null context', () => { + const store = useProductExpertFfAgentStore() + expect(store.context).toBeNull() + }) + + it('has null sessionId', () => { + const store = useProductExpertFfAgentStore() + expect(store.sessionId).toBeNull() + }) + + it('has empty messages array', () => { + const store = useProductExpertFfAgentStore() + expect(store.messages).toEqual([]) + }) + + it('has null sessionStartTime', () => { + const store = useProductExpertFfAgentStore() + expect(store.sessionStartTime).toBeNull() + }) + + it('has sessionWarningShown false', () => { + const store = useProductExpertFfAgentStore() + expect(store.sessionWarningShown).toBe(false) + }) + + it('has sessionExpiredShown false', () => { + const store = useProductExpertFfAgentStore() + expect(store.sessionExpiredShown).toBe(false) + }) + + it('has null sessionCheckTimer', () => { + const store = useProductExpertFfAgentStore() + expect(store.sessionCheckTimer).toBeNull() + }) + }) + + describe('setSessionCheckTimer', () => { + it('stores the timer reference', () => { + const store = useProductExpertFfAgentStore() + const fakeTimer = setInterval(() => {}, 10000) + store.setSessionCheckTimer(fakeTimer) + expect(store.sessionCheckTimer).toBe(fakeTimer) + clearInterval(fakeTimer) + }) + + it('replaces an existing timer', () => { + const store = useProductExpertFfAgentStore() + const timer1 = setInterval(() => {}, 10000) + const timer2 = setInterval(() => {}, 10000) + store.setSessionCheckTimer(timer1) + store.setSessionCheckTimer(timer2) + expect(store.sessionCheckTimer).toBe(timer2) + clearInterval(timer1) + clearInterval(timer2) + }) + }) + + describe('reset', () => { + it('clears all state back to initial values', () => { + const store = useProductExpertFfAgentStore() + store.context = { instanceId: 'abc' } + store.sessionId = 'session-123' + store.messages = [{ role: 'user', content: 'hello' }] + store.sessionStartTime = Date.now() + store.sessionWarningShown = true + store.sessionExpiredShown = true + + store.reset() + + expect(store.context).toBeNull() + expect(store.sessionId).toBeNull() + expect(store.messages).toEqual([]) + expect(store.sessionStartTime).toBeNull() + expect(store.sessionWarningShown).toBe(false) + expect(store.sessionExpiredShown).toBe(false) + expect(store.sessionCheckTimer).toBeNull() + }) + + it('calls clearInterval when a timer is active', () => { + const store = useProductExpertFfAgentStore() + const clearIntervalSpy = vi.spyOn(globalThis, 'clearInterval') + const fakeTimer = setInterval(() => {}, 10000) + store.setSessionCheckTimer(fakeTimer) + + store.reset() + + expect(clearIntervalSpy).toHaveBeenCalledWith(fakeTimer) + clearIntervalSpy.mockRestore() + }) + + it('does not throw when no timer is set', () => { + const store = useProductExpertFfAgentStore() + expect(() => store.reset()).not.toThrow() + }) + + it('clears sessionCheckTimer after reset', () => { + const store = useProductExpertFfAgentStore() + const fakeTimer = setInterval(() => {}, 10000) + store.setSessionCheckTimer(fakeTimer) + + store.reset() + + expect(store.sessionCheckTimer).toBeNull() + }) + }) + + describe('state mutations', () => { + it('allows direct state assignment for context', () => { + const store = useProductExpertFfAgentStore() + store.context = { instanceId: 'test-id' } + expect(store.context).toEqual({ instanceId: 'test-id' }) + }) + + it('allows direct state assignment for sessionId', () => { + const store = useProductExpertFfAgentStore() + store.sessionId = 'abc-123' + expect(store.sessionId).toBe('abc-123') + }) + + it('allows pushing to messages', () => { + const store = useProductExpertFfAgentStore() + store.messages.push({ role: 'user', content: 'hello' }) + store.messages.push({ role: 'assistant', content: 'hi' }) + expect(store.messages).toHaveLength(2) + }) + + it('allows setting sessionWarningShown', () => { + const store = useProductExpertFfAgentStore() + store.sessionWarningShown = true + expect(store.sessionWarningShown).toBe(true) + }) + + it('allows setting sessionExpiredShown', () => { + const store = useProductExpertFfAgentStore() + store.sessionExpiredShown = true + expect(store.sessionExpiredShown).toBe(true) + }) + }) +}) From ad7b43904c24746106a5e4686d0ac19bda1c7dd0 Mon Sep 17 00:00:00 2001 From: Noley Holland Date: Tue, 17 Mar 2026 12:51:05 -0700 Subject: [PATCH 2/4] Fix incorrect spelling within nav spec for account bridge file --- test/unit/frontend/stores/ux-navigation.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/frontend/stores/ux-navigation.spec.js b/test/unit/frontend/stores/ux-navigation.spec.js index c365cb48dc..ca6f0640b5 100644 --- a/test/unit/frontend/stores/ux-navigation.spec.js +++ b/test/unit/frontend/stores/ux-navigation.spec.js @@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { useUxNavigationStore } from '@/stores/ux-navigation.js' -// Prevent _account-bridge from importing the real Vuex store +// Prevent _account_bridge from importing the real Vuex store vi.mock('@/stores/_account_bridge.js', () => ({ useAccountBridge: vi.fn(() => ({ team: null, From 1e93690eb27e4e0ff285b8d72b45616db8d290b2 Mon Sep 17 00:00:00 2001 From: Noley Holland Date: Thu, 19 Mar 2026 09:15:17 -0700 Subject: [PATCH 3/4] Update store to include abortController & remove sessionId persistence --- frontend/src/stores/product-expert-ff-agent.js | 5 ++++- test/unit/frontend/stores/product-expert-ff-agent.spec.js | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/frontend/src/stores/product-expert-ff-agent.js b/frontend/src/stores/product-expert-ff-agent.js index 66ddb8d6b4..44c2636e8a 100644 --- a/frontend/src/stores/product-expert-ff-agent.js +++ b/frontend/src/stores/product-expert-ff-agent.js @@ -6,6 +6,9 @@ export const useProductExpertFfAgentStore = defineStore('product-expert-ff-agent context: null, sessionId: null, messages: [], + + // Session timing + abortController: null, sessionStartTime: null, sessionWarningShown: false, sessionExpiredShown: false, @@ -21,7 +24,7 @@ export const useProductExpertFfAgentStore = defineStore('product-expert-ff-agent } }, persist: { - pick: ['context', 'sessionId'], + pick: ['context'], storage: localStorage } }) diff --git a/test/unit/frontend/stores/product-expert-ff-agent.spec.js b/test/unit/frontend/stores/product-expert-ff-agent.spec.js index b6b3107237..b7f4fc2dfa 100644 --- a/test/unit/frontend/stores/product-expert-ff-agent.spec.js +++ b/test/unit/frontend/stores/product-expert-ff-agent.spec.js @@ -39,6 +39,11 @@ describe('useProductExpertFfAgentStore', () => { expect(store.sessionExpiredShown).toBe(false) }) + it('has null abortController', () => { + const store = useProductExpertFfAgentStore() + expect(store.abortController).toBeNull() + }) + it('has null sessionCheckTimer', () => { const store = useProductExpertFfAgentStore() expect(store.sessionCheckTimer).toBeNull() @@ -72,6 +77,7 @@ describe('useProductExpertFfAgentStore', () => { store.context = { instanceId: 'abc' } store.sessionId = 'session-123' store.messages = [{ role: 'user', content: 'hello' }] + store.abortController = new AbortController() store.sessionStartTime = Date.now() store.sessionWarningShown = true store.sessionExpiredShown = true @@ -81,6 +87,7 @@ describe('useProductExpertFfAgentStore', () => { expect(store.context).toBeNull() expect(store.sessionId).toBeNull() expect(store.messages).toEqual([]) + expect(store.abortController).toBeNull() expect(store.sessionStartTime).toBeNull() expect(store.sessionWarningShown).toBe(false) expect(store.sessionExpiredShown).toBe(false) From f8ec9844f3cdfb4a037e820da5f6f778bbb63b3f Mon Sep 17 00:00:00 2001 From: Noley Holland Date: Mon, 23 Mar 2026 08:46:41 -0700 Subject: [PATCH 4/4] Update name of store to insights --- frontend/src/store/modules/account/index.js | 4 +- frontend/src/stores/index.js | 2 +- ...nt.js => product-expert-insights-agent.js} | 2 +- ... => product-expert-insights-agent.spec.js} | 42 +++++++++---------- 4 files changed, 25 insertions(+), 25 deletions(-) rename frontend/src/stores/{product-expert-ff-agent.js => product-expert-insights-agent.js} (87%) rename test/unit/frontend/stores/{product-expert-ff-agent.spec.js => product-expert-insights-agent.spec.js} (77%) diff --git a/frontend/src/store/modules/account/index.js b/frontend/src/store/modules/account/index.js index 315d33ffff..f6907abf46 100644 --- a/frontend/src/store/modules/account/index.js +++ b/frontend/src/store/modules/account/index.js @@ -12,7 +12,7 @@ import product from '../../../services/product.js' import { useContextStore } from '@/stores/context.js' import { useProductAssistantStore } from '@/stores/product-assistant.js' -import { useProductExpertFfAgentStore } from '@/stores/product-expert-ff-agent.js' +import { useProductExpertInsightsAgentStore } from '@/stores/product-expert-insights-agent.js' import { useUxDialogStore } from '@/stores/ux-dialog.js' import { useUxDrawersStore } from '@/stores/ux-drawers.js' import { useUxNavigationStore } from '@/stores/ux-navigation.js' @@ -530,7 +530,7 @@ const actions = { // Task 6: useProductTablesStore().$reset() // Task 7: useProductBrokersStore().$reset() useProductAssistantStore().$reset() - useProductExpertFfAgentStore().$reset() + useProductExpertInsightsAgentStore().$reset() // Task 10: useProductExpertOperatorAgentStore().$reset() // Task 11: useProductExpertStore().$reset() } diff --git a/frontend/src/stores/index.js b/frontend/src/stores/index.js index 1d0d547774..3021099d86 100644 --- a/frontend/src/stores/index.js +++ b/frontend/src/stores/index.js @@ -6,4 +6,4 @@ export { useUxNavigationStore } from './ux-navigation.js' export { useUxStore } from './ux.js' export { useUxDialogStore } from './ux-dialog.js' export { useUxToursStore } from './ux-tours.js' -export { useProductExpertFfAgentStore } from './product-expert-ff-agent.js' +export { useProductExpertInsightsAgentStore } from './product-expert-insights-agent.js' diff --git a/frontend/src/stores/product-expert-ff-agent.js b/frontend/src/stores/product-expert-insights-agent.js similarity index 87% rename from frontend/src/stores/product-expert-ff-agent.js rename to frontend/src/stores/product-expert-insights-agent.js index 44c2636e8a..ff647f0302 100644 --- a/frontend/src/stores/product-expert-ff-agent.js +++ b/frontend/src/stores/product-expert-insights-agent.js @@ -1,7 +1,7 @@ import { defineStore } from 'pinia' import { markRaw } from 'vue' -export const useProductExpertFfAgentStore = defineStore('product-expert-ff-agent', { +export const useProductExpertInsightsAgentStore = defineStore('product-expert-insights-agent', { state: () => ({ context: null, sessionId: null, diff --git a/test/unit/frontend/stores/product-expert-ff-agent.spec.js b/test/unit/frontend/stores/product-expert-insights-agent.spec.js similarity index 77% rename from test/unit/frontend/stores/product-expert-ff-agent.spec.js rename to test/unit/frontend/stores/product-expert-insights-agent.spec.js index b7f4fc2dfa..80acccffdf 100644 --- a/test/unit/frontend/stores/product-expert-ff-agent.spec.js +++ b/test/unit/frontend/stores/product-expert-insights-agent.spec.js @@ -1,58 +1,58 @@ import { createPinia, setActivePinia } from 'pinia' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { useProductExpertFfAgentStore } from '@/stores/product-expert-ff-agent.js' +import { useProductExpertInsightsAgentStore } from '@/stores/product-expert-insights-agent.js' -describe('useProductExpertFfAgentStore', () => { +describe('useProductExpertInsightsAgentStore', () => { beforeEach(() => { setActivePinia(createPinia()) }) describe('initial state', () => { it('has null context', () => { - const store = useProductExpertFfAgentStore() + const store = useProductExpertInsightsAgentStore() expect(store.context).toBeNull() }) it('has null sessionId', () => { - const store = useProductExpertFfAgentStore() + const store = useProductExpertInsightsAgentStore() expect(store.sessionId).toBeNull() }) it('has empty messages array', () => { - const store = useProductExpertFfAgentStore() + const store = useProductExpertInsightsAgentStore() expect(store.messages).toEqual([]) }) it('has null sessionStartTime', () => { - const store = useProductExpertFfAgentStore() + const store = useProductExpertInsightsAgentStore() expect(store.sessionStartTime).toBeNull() }) it('has sessionWarningShown false', () => { - const store = useProductExpertFfAgentStore() + const store = useProductExpertInsightsAgentStore() expect(store.sessionWarningShown).toBe(false) }) it('has sessionExpiredShown false', () => { - const store = useProductExpertFfAgentStore() + const store = useProductExpertInsightsAgentStore() expect(store.sessionExpiredShown).toBe(false) }) it('has null abortController', () => { - const store = useProductExpertFfAgentStore() + const store = useProductExpertInsightsAgentStore() expect(store.abortController).toBeNull() }) it('has null sessionCheckTimer', () => { - const store = useProductExpertFfAgentStore() + const store = useProductExpertInsightsAgentStore() expect(store.sessionCheckTimer).toBeNull() }) }) describe('setSessionCheckTimer', () => { it('stores the timer reference', () => { - const store = useProductExpertFfAgentStore() + const store = useProductExpertInsightsAgentStore() const fakeTimer = setInterval(() => {}, 10000) store.setSessionCheckTimer(fakeTimer) expect(store.sessionCheckTimer).toBe(fakeTimer) @@ -60,7 +60,7 @@ describe('useProductExpertFfAgentStore', () => { }) it('replaces an existing timer', () => { - const store = useProductExpertFfAgentStore() + const store = useProductExpertInsightsAgentStore() const timer1 = setInterval(() => {}, 10000) const timer2 = setInterval(() => {}, 10000) store.setSessionCheckTimer(timer1) @@ -73,7 +73,7 @@ describe('useProductExpertFfAgentStore', () => { describe('reset', () => { it('clears all state back to initial values', () => { - const store = useProductExpertFfAgentStore() + const store = useProductExpertInsightsAgentStore() store.context = { instanceId: 'abc' } store.sessionId = 'session-123' store.messages = [{ role: 'user', content: 'hello' }] @@ -95,7 +95,7 @@ describe('useProductExpertFfAgentStore', () => { }) it('calls clearInterval when a timer is active', () => { - const store = useProductExpertFfAgentStore() + const store = useProductExpertInsightsAgentStore() const clearIntervalSpy = vi.spyOn(globalThis, 'clearInterval') const fakeTimer = setInterval(() => {}, 10000) store.setSessionCheckTimer(fakeTimer) @@ -107,12 +107,12 @@ describe('useProductExpertFfAgentStore', () => { }) it('does not throw when no timer is set', () => { - const store = useProductExpertFfAgentStore() + const store = useProductExpertInsightsAgentStore() expect(() => store.reset()).not.toThrow() }) it('clears sessionCheckTimer after reset', () => { - const store = useProductExpertFfAgentStore() + const store = useProductExpertInsightsAgentStore() const fakeTimer = setInterval(() => {}, 10000) store.setSessionCheckTimer(fakeTimer) @@ -124,32 +124,32 @@ describe('useProductExpertFfAgentStore', () => { describe('state mutations', () => { it('allows direct state assignment for context', () => { - const store = useProductExpertFfAgentStore() + const store = useProductExpertInsightsAgentStore() store.context = { instanceId: 'test-id' } expect(store.context).toEqual({ instanceId: 'test-id' }) }) it('allows direct state assignment for sessionId', () => { - const store = useProductExpertFfAgentStore() + const store = useProductExpertInsightsAgentStore() store.sessionId = 'abc-123' expect(store.sessionId).toBe('abc-123') }) it('allows pushing to messages', () => { - const store = useProductExpertFfAgentStore() + const store = useProductExpertInsightsAgentStore() store.messages.push({ role: 'user', content: 'hello' }) store.messages.push({ role: 'assistant', content: 'hi' }) expect(store.messages).toHaveLength(2) }) it('allows setting sessionWarningShown', () => { - const store = useProductExpertFfAgentStore() + const store = useProductExpertInsightsAgentStore() store.sessionWarningShown = true expect(store.sessionWarningShown).toBe(true) }) it('allows setting sessionExpiredShown', () => { - const store = useProductExpertFfAgentStore() + const store = useProductExpertInsightsAgentStore() store.sessionExpiredShown = true expect(store.sessionExpiredShown).toBe(true) })