From 84444be047c3ca4eaa0347583216c2c6cd0a8a5a Mon Sep 17 00:00:00 2001 From: vineyardbovines Date: Wed, 2 Sep 2026 14:33:11 -0400 Subject: [PATCH] Track starter pack opt-out actions --- src/analytics/metrics/types.ts | 4 ++++ src/screens/StarterPack/StarterPackScreen.tsx | 6 ++++++ .../queries/__tests__/starter-packs.test.tsx | 15 +++++++++------ src/state/queries/starter-packs.ts | 5 ++++- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/analytics/metrics/types.ts b/src/analytics/metrics/types.ts index 78a4574f0f..1639c2a43d 100644 --- a/src/analytics/metrics/types.ts +++ b/src/analytics/metrics/types.ts @@ -715,6 +715,10 @@ export type Events = { count: number } 'starterPack:delete': {} + 'starterPack:optOut': { + starterPack: string + action: 'optOut' | 'undo' + } 'starterPack:create': { setName: boolean setDescription: boolean diff --git a/src/screens/StarterPack/StarterPackScreen.tsx b/src/screens/StarterPack/StarterPackScreen.tsx index 7d9a11d358..40bc6f31a3 100644 --- a/src/screens/StarterPack/StarterPackScreen.tsx +++ b/src/screens/StarterPack/StarterPackScreen.tsx @@ -552,6 +552,12 @@ function OverflowMenu({ const {mutate: setReferenceListOptOut, isPending: isOptOutPending} = useReferenceListOptOutMutation({ starterPack, + onSuccess: action => { + ax.metric('starterPack:optOut', { + starterPack: starterPack.uri, + action, + }) + }, onError: error => { logger.error('Failed to update starter pack opt-out', { safeMessage: error, diff --git a/src/state/queries/__tests__/starter-packs.test.tsx b/src/state/queries/__tests__/starter-packs.test.tsx index 5f958f7ed2..61a787658f 100644 --- a/src/state/queries/__tests__/starter-packs.test.tsx +++ b/src/state/queries/__tests__/starter-packs.test.tsx @@ -34,7 +34,7 @@ const createdOptOut = const indexedOptOut = 'at://did:plc:viewer/app.bsky.graph.referencelistoptout/indexed' -function setup() { +function setup({onSuccess = jest.fn()} = {}) { const queryClient = new QueryClient({ defaultOptions: { queries: {gcTime: Infinity, retry: false}, @@ -57,11 +57,11 @@ function setup() { {children} ) const hook = renderHook( - () => useReferenceListOptOutMutation({starterPack, onError}), + () => useReferenceListOptOutMutation({starterPack, onError, onSuccess}), {wrapper}, ) - return {appviewClient, hook, onError, pdsClient, queryClient} + return {appviewClient, hook, onError, onSuccess, pdsClient, queryClient} } beforeEach(() => { @@ -76,7 +76,7 @@ beforeAll(() => { describe('useReferenceListOptOutMutation', () => { it('keeps the successful PDS write optimistic when AppView has not caught up', async () => { - const {hook, pdsClient, queryClient} = setup() + const {hook, onSuccess, pdsClient, queryClient} = setup() pdsClient.create.mockResolvedValue({uri: createdOptOut}) jest.mocked(until).mockResolvedValue(false) @@ -92,6 +92,7 @@ describe('useReferenceListOptOutMutation', () => { queryClient.getQueryData(queryKey) ?.list?.viewer?.referenceListOptOut, ).toBe(createdOptOut) + expect(onSuccess).toHaveBeenCalledWith('optOut') }) it('uses the indexed viewer-state URI when AppView reports a duplicate', async () => { @@ -121,7 +122,7 @@ describe('useReferenceListOptOutMutation', () => { }) it('deletes the viewer-state record URI when undoing', async () => { - const {hook, pdsClient, queryClient} = setup() + const {hook, onSuccess, pdsClient, queryClient} = setup() queryClient.setQueryData(queryKey, { ...starterPack, list: { @@ -148,10 +149,11 @@ describe('useReferenceListOptOutMutation', () => { queryClient.getQueryData(queryKey) ?.list?.viewer?.referenceListOptOut, ).toBeUndefined() + expect(onSuccess).toHaveBeenCalledWith('undo') }) it('restores viewer state and surfaces PDS write failures', async () => { - const {hook, onError, pdsClient, queryClient} = setup() + const {hook, onError, onSuccess, pdsClient, queryClient} = setup() const error = new Error('write failed') pdsClient.create.mockRejectedValue(error) @@ -162,6 +164,7 @@ describe('useReferenceListOptOutMutation', () => { }) await waitFor(() => expect(onError).toHaveBeenCalledWith(error)) + expect(onSuccess).not.toHaveBeenCalled() expect( queryClient.getQueryData(queryKey) ?.list?.viewer?.referenceListOptOut, diff --git a/src/state/queries/starter-packs.ts b/src/state/queries/starter-packs.ts index 674fe7336d..bbc5d8b4e7 100644 --- a/src/state/queries/starter-packs.ts +++ b/src/state/queries/starter-packs.ts @@ -74,9 +74,11 @@ export function useStarterPackQuery({ export function useReferenceListOptOutMutation({ starterPack, onError, + onSuccess, }: { starterPack: app.bsky.graph.defs.StarterPackView onError: (error: Error) => void + onSuccess?: (action: 'optOut' | 'undo') => void }) { const queryClient = useQueryClient() const appviewClient = useAppviewClient() @@ -171,7 +173,7 @@ export function useReferenceListOptOutMutation({ ) return {previous} }, - onSuccess: ({referenceListOptOut}) => { + onSuccess: ({referenceListOptOut}, variables) => { queryClient.setQueryData( queryKey, current => @@ -192,6 +194,7 @@ export function useReferenceListOptOutMutation({ queryClient, uri: starterPack.list!.uri, }) + onSuccess?.(variables.referenceListOptOut ? 'undo' : 'optOut') }, onError: (error, _, context) => { if (context?.previous) {