Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import mockRouter from 'next-router-mock'

import { useAuth } from '../../../../libs/auth'

import { GoogleSheetsSyncDialog } from './GoogleSheetsSyncDialog'

jest.mock('../../../../libs/auth', () => ({
useAuth: jest.fn()
}))

const mockUseAuth = useAuth as jest.MockedFunction<typeof useAuth>

jest.mock('next-i18next', () => ({
useTranslation: () => ({
t: (value: string) => value
Expand Down Expand Up @@ -50,7 +58,8 @@ const defaultIntegrationsData = {
{
__typename: 'IntegrationGoogle',
id: 'integration1',
accountEmail: '[email protected]'
accountEmail: '[email protected]',
user: { __typename: 'AuthenticatedUser', id: 'user1' }
}
]
}
Expand Down Expand Up @@ -100,9 +109,14 @@ describe('GoogleSheetsSyncDialog', () => {
mockUseLazyQuery.mockReset()
mockUseMutation.mockReset()
mockUseIntegrationQuery.mockReset()
mockUseAuth.mockReset()
mockEnqueueSnackbar.mockClear()
mockRouter.setCurrentUrl('/journeys')

mockUseAuth.mockReturnValue({
user: { id: 'user1' }
} as unknown as ReturnType<typeof useAuth>)

mockUseQuery.mockReturnValue({ data: defaultJourneyData })
mockUseIntegrationQuery.mockReturnValue({
data: defaultIntegrationsData
Expand Down Expand Up @@ -218,4 +232,70 @@ describe('GoogleSheetsSyncDialog', () => {

expect(onClose).toHaveBeenCalled()
})

it('only shows current user Google integrations in the dropdown', async () => {
mockUseIntegrationQuery.mockReturnValue({
data: {
integrations: [
{
__typename: 'IntegrationGoogle',
id: 'integration1',
accountEmail: '[email protected]',
user: { __typename: 'AuthenticatedUser', id: 'user1' }
},
{
__typename: 'IntegrationGoogle',
id: 'integration2',
accountEmail: '[email protected]',
user: { __typename: 'AuthenticatedUser', id: 'otherUser' }
}
]
}
})
setupApolloMocks()

render(
<GoogleSheetsSyncDialog open journeyId="journey1" onClose={onClose} />
)

await screen.findByRole('button', { name: 'Create Sync' })

// Open the dropdown
fireEvent.mouseDown(screen.getByRole('combobox', { name: 'Integration account' }))

const options = screen.getAllByRole('option')
const optionTexts = options.map((o) => o.textContent)

expect(optionTexts).toContain('[email protected]')
expect(optionTexts).not.toContain('[email protected]')
})

it('shows no integration options when none belong to current user', async () => {
mockUseIntegrationQuery.mockReturnValue({
data: {
integrations: [
{
__typename: 'IntegrationGoogle',
id: 'integration2',
accountEmail: '[email protected]',
user: { __typename: 'AuthenticatedUser', id: 'otherUser' }
}
]
}
})
setupApolloMocks()

render(
<GoogleSheetsSyncDialog open journeyId="journey1" onClose={onClose} />
)

await screen.findByRole('button', { name: 'Create Sync' })

fireEvent.mouseDown(screen.getByRole('combobox', { name: 'Integration account' }))

const options = screen.getAllByRole('option')
// Only the disabled placeholder option should exist
expect(options).toHaveLength(1)
expect(options[0]).toHaveTextContent('Select integration account')
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import Plus2Icon from '@core/shared/ui/icons/Plus2'
import Trash2Icon from '@core/shared/ui/icons/Trash2'

import { useAuth } from '../../../../libs/auth'
import { getGoogleOAuthUrl } from '../../../../libs/googleOAuthUrl'
import { useIntegrationQuery } from '../../../../libs/useIntegrationQuery/useIntegrationQuery'

Expand Down Expand Up @@ -158,6 +159,7 @@
const router = useRouter()
const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.down('sm'))
const { user } = useAuth()

const { data: journeyData } = useQuery(GET_JOURNEY_CREATED_AT, {
variables: { id: journeyId }
Expand All @@ -166,6 +168,12 @@
teamId: journeyData?.journey?.team?.id as string
})

const myGoogleIntegrations = integrationsData?.integrations?.filter(
(integration) =>
integration.__typename === 'IntegrationGoogle' &&
integration.user?.id === user?.id
)

const [googleDialogOpen, setGoogleDialogOpen] = useState(false)
const [pickerActive, setPickerActive] = useState(false)

Expand Down Expand Up @@ -1189,7 +1197,7 @@
renderValue={(selected) => {
if (selected === '')
return t('Select integration account')
const found = integrationsData?.integrations.find(
const found = myGoogleIntegrations?.find(
(integration) => integration.id === selected
)
if (found?.__typename === 'IntegrationGoogle') {
Expand All @@ -1201,16 +1209,11 @@
<MenuItem value="" disabled>
{t('Select integration account')}
</MenuItem>
{integrationsData?.integrations
?.filter(
(integration) =>
integration.__typename === 'IntegrationGoogle'
)
.map((integration) => (
<MenuItem key={integration.id} value={integration.id}>
{integration.accountEmail ?? t('Unknown email')}
</MenuItem>
))}
{myGoogleIntegrations?.map((integration) => (
<MenuItem key={integration.id} value={integration.id}>
{integration.accountEmail ?? t('Unknown email')}

Check failure on line 1214 in apps/journeys-admin/src/components/JourneyVisitorsList/FilterDrawer/GoogleSheetsSyncDialog/GoogleSheetsSyncDialog.tsx

View workflow job for this annotation

GitHub Actions / lint (22)

Property 'accountEmail' does not exist on type 'GetIntegration_integrations'.

Check failure on line 1214 in apps/journeys-admin/src/components/JourneyVisitorsList/FilterDrawer/GoogleSheetsSyncDialog/GoogleSheetsSyncDialog.tsx

View workflow job for this annotation

GitHub Actions / lint (22)

Property 'accountEmail' does not exist on type 'GetIntegration_integrations'.
</MenuItem>
))}
</Select>
{touched.integrationId != null &&
errors.integrationId != null && (
Expand Down
57 changes: 57 additions & 0 deletions docs/nes-1492-fix-google-sync-dropdown.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Fix: Google Sync Dropdown Shows Another Manager's Gmail Account (NES-1492)

## Context
The Google Account dropdown in the Google Sheets Sync Dialog shows ALL Google integrations for a team, including other managers' Gmail accounts. A user should only see their own connected Google account(s) in the dropdown, not accounts connected by other team members.

## Root Cause
`useIntegrationQuery` fetches all integrations for a `teamId`, and the dropdown only filters by `__typename === 'IntegrationGoogle'` — it does not filter by the current user's ID.

## Approach: Frontend filter using `useAuth()`
Filter the integrations in the dropdown to only show those where `integration.user.id` matches the current user's ID. Using `useAuth()` (Firebase context) is the simplest approach — it's synchronous, already available, and avoids an extra GraphQL query.

This is consistent with existing patterns (e.g., `GoogleIntegrationDetails` compares user IDs).

## Files to Modify

### 1. `apps/journeys-admin/src/components/JourneyVisitorsList/FilterDrawer/GoogleSheetsSyncDialog/GoogleSheetsSyncDialog.tsx`
- Import `useAuth` from `../../../../libs/auth`
- Add `const { user } = useAuth()` in the component
- Create a filtered list of Google integrations belonging to the current user:
```ts
const myGoogleIntegrations = integrationsData?.integrations?.filter(
(integration) =>
integration.__typename === 'IntegrationGoogle' &&
integration.user?.id === user?.id
)
```
- Use `myGoogleIntegrations` in both the dropdown `renderValue` (line ~1192) and the `MenuItem` map (line ~1204)

### 2. `apps/journeys-admin/src/components/JourneyVisitorsList/FilterDrawer/GoogleSheetsSyncDialog/GoogleSheetsSyncDialog.spec.tsx`
- Add mock for `useAuth` from `../../../../libs/auth` (pattern: `jest.mock('../../../../libs/auth', () => ({ useAuth: jest.fn() }))`)
- Configure `mockUseAuth.mockReturnValue({ user: { id: 'user1' } })` in `beforeEach`
- Update `defaultIntegrationsData` to include `user` field on the integration:
```ts
{
__typename: 'IntegrationGoogle',
id: 'integration1',
accountEmail: '[email protected]',
user: { __typename: 'AuthenticatedUser', id: 'user1' }
}
```
- Update existing tests that reference the integration data to include the `user` field
- **New test: "only shows current user's Google integrations in dropdown"**
- Mock integrations with two entries: one with `user.id: 'user1'` (current user) and one with `user.id: 'otherUser'`
- Open the dropdown and assert only the current user's email appears
- Assert the other user's email does NOT appear
- **New test: "shows no integrations when none belong to current user"**
- Mock integrations with only other users' accounts
- Assert the dropdown shows no account options

## Verification
1. Run existing tests:
```bash
npx jest --config apps/journeys-admin/jest.config.ts --no-coverage 'apps/journeys-admin/src/components/JourneyVisitorsList/FilterDrawer/GoogleSheetsSyncDialog/GoogleSheetsSyncDialog.spec.tsx'
```
2. Verify the dropdown only shows the current user's accounts
3. Verify the "Add New Google Account" button still works
4. Verify `renderValue` still displays the correct email for selected integration
Loading