Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
11 changes: 11 additions & 0 deletions frontend/common/services/useWarehouseConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ export const warehouseConnectionService = service
url: `environments/${environmentId}/warehouse-connections/`,
}),
}),
testWarehouseConnection: builder.mutation<
Res['warehouseConnections'][number],
Req['testWarehouseConnection']
>({
invalidatesTags: [{ id: 'LIST', type: 'WarehouseConnection' }],
query: ({ environmentId, id }) => ({
method: 'POST',
url: `environments/${environmentId}/warehouse-connections/${id}/test-warehouse-connection/`,
}),
}),
updateWarehouseConnection: builder.mutation<
Res['warehouseConnections'][number],
Req['updateWarehouseConnection']
Expand All @@ -54,5 +64,6 @@ export const {
useCreateWarehouseConnectionMutation,
useDeleteWarehouseConnectionMutation,
useGetWarehouseConnectionsQuery,
useTestWarehouseConnectionMutation,
useUpdateWarehouseConnectionMutation,
} = warehouseConnectionService
1 change: 1 addition & 0 deletions frontend/common/types/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -981,6 +981,7 @@ export type Req = {
config?: Record<string, string>
}
deleteWarehouseConnection: { environmentId: string; id: number }
testWarehouseConnection: { environmentId: string; id: number }
updateWarehouseConnection: {
environmentId: string
id: number
Expand Down
2 changes: 2 additions & 0 deletions frontend/common/types/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1146,6 +1146,8 @@ export type WarehouseConnection = {
name: string
config: SnowflakeConfig | Record<string, never>
created_at: string
total_events_received: number
unique_events_count: number
}

export type Res = {
Expand Down
7 changes: 6 additions & 1 deletion frontend/env/project_dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@ const Project = {
flagsmithClientAPI: 'https://edge.api.flagsmith.com/api/v1/',

flagsmithClientEdgeAPI: 'https://edge.bullet-train-staging.win/api/v1/',

flagsmithClientEventsAPI: 'https://events.bullet-train-staging.win/v1/events',
// This is used for Sentry tracking
maintenance: false,
plans: {
scaleUp: { annual: 'Scale-Up-v4-USD-Yearly', monthly: 'Scale-Up-v4-USD-Monthly' },
scaleUp: {
annual: 'Scale-Up-v4-USD-Yearly',
monthly: 'Scale-Up-v4-USD-Monthly',
},
startup: { annual: 'startup-annual-v2', monthly: 'startup-v2' },
},
useSecureCookies: true,
Expand Down
7 changes: 6 additions & 1 deletion frontend/env/project_local.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@ const Project = {
flagsmithClientAPI: 'https://edge.api.flagsmith.com/api/v1/',

flagsmithClientEdgeAPI: 'https://edge.api.flagsmith.com/api/v1/',

flagsmithClientEventsAPI: 'https://events.bullet-train-staging.win/v1/events',
// This is used for Sentry tracking
maintenance: false,
plans: {
scaleUp: { annual: 'Scale-Up-v4-USD-Yearly', monthly: 'Scale-Up-v4-USD-Monthly' },
scaleUp: {
annual: 'Scale-Up-v4-USD-Yearly',
monthly: 'Scale-Up-v4-USD-Monthly',
},
startup: { annual: 'startup-annual-v2', monthly: 'startup-v2' },
},
useSecureCookies: false,
Expand Down
8 changes: 7 additions & 1 deletion frontend/env/project_prod.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// eslint-disable-next-line @dword-design/import-alias/prefer-alias
import { E2E_CHANGE_MAIL, E2E_SIGN_UP_USER, E2E_USER } from '../e2e/config'

const _globalThis = typeof window === 'undefined' ? global : window
Expand All @@ -21,13 +22,18 @@ const Project = {

flagsmithClientEdgeAPI: 'https://edge.api.flagsmith.com/api/v1/',

flagsmithClientEventsAPI: 'https://events.api.flagsmith.com/v1/events',

hubspot: '//js-eu1.hs-scripts.com/143451822.js',

linkedinConversionId: 16798338,
// This is used for Sentry tracking
maintenance: false,
plans: {
scaleUp: { annual: 'Scale-Up-v4-USD-Yearly', monthly: 'Scale-Up-v4-USD-Monthly' },
scaleUp: {
annual: 'Scale-Up-v4-USD-Yearly',
monthly: 'Scale-Up-v4-USD-Monthly',
},
startup: { annual: 'start-up-12-months-v2', monthly: 'startup-v2' },
},
useSecureCookies: true,
Expand Down
2 changes: 2 additions & 0 deletions frontend/env/project_staging.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const Project = {
flagsmithClientAPI: 'https://edge.api.flagsmith.com/api/v1/',

flagsmithClientEdgeAPI: 'https://edge.bullet-train-staging.win/api/v1/',

flagsmithClientEventsAPI: 'https://events.bullet-train-staging.win/v1/events',
// This is used for Sentry tracking
maintenance: false,
plans: {
Expand Down
9 changes: 7 additions & 2 deletions frontend/web/components/CodeHelp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Icon from './icons/Icon'
type Snippets = Record<string, string>

type CodeHelpProps = {
hideDocs?: boolean
hideHeader?: boolean
showInitially?: boolean
snippets: Snippets
Expand All @@ -22,6 +23,7 @@ type LanguageOption = {

type SnippetItemProps = {
code: string
hideDocs?: boolean
isVisible: boolean
language: string
languageKey: string
Expand Down Expand Up @@ -108,15 +110,16 @@ const getDocsLink = (key: string): string | null => {

const SnippetItem: FC<SnippetItemProps> = ({
code,
hideDocs,
isVisible,
language,
languageKey,
languageOptions,
onCopy,
onLanguageChange,
}) => {
const docs = getDocsLink(languageKey)
const github = getGithubLink(languageKey)
const docs = hideDocs ? null : getDocsLink(languageKey)
const github = hideDocs ? null : getGithubLink(languageKey)

return (
<div className={!isVisible ? 'd-none' : 'hljs-container mt-2 mb-2'}>
Expand Down Expand Up @@ -185,6 +188,7 @@ const SnippetItem: FC<SnippetItemProps> = ({
}

const CodeHelp: FC<CodeHelpProps> = ({
hideDocs,
hideHeader,
showInitially,
snippets,
Expand Down Expand Up @@ -251,6 +255,7 @@ const CodeHelp: FC<CodeHelpProps> = ({
<SnippetItem
key={key}
code={code}
hideDocs={hideDocs}
isVisible={key === language}
language={language}
languageOptions={languageOptions}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ type WarehouseConnectionCardProps = {
connection: WarehouseConnection
onDelete: () => void
onEdit?: () => void
onSendTestEvent: () => void
isSendingTestEvent: boolean
}

const STATUS_COLOUR: Record<WarehouseConnectionStatus, string> = {
Expand All @@ -38,14 +40,20 @@ const TYPE_LABEL: Partial<Record<WarehouseType, string>> = {

const WarehouseConnectionCard: FC<WarehouseConnectionCardProps> = ({
connection,
isSendingTestEvent,
onDelete,
onEdit,
onSendTestEvent,
}) => {
const typeLabel =
connection.warehouse_type !== 'flagsmith'
? TYPE_LABEL[connection.warehouse_type] ?? connection.warehouse_type
: null

const isFlagsmith = connection.warehouse_type === 'flagsmith'
const isPending = connection.status === 'pending_connection'
const isConnected = connection.status === 'connected'

const handleDelete = () => {
openConfirm({
body: 'Are you sure you want to remove this warehouse connection?',
Expand Down Expand Up @@ -106,21 +114,46 @@ const WarehouseConnectionCard: FC<WarehouseConnectionCardProps> = ({
<WarehouseStats
errored={connection.status === 'errored'}
lastEventReceived='-'
totalEventsReceived={0}
uniqueEventsCount={0}
totalEventsReceived={connection.total_events_received}
uniqueEventsCount={connection.unique_events_count}
/>
<hr className='my-4' />
{connection.status === 'pending_connection' && (
<div className='d-flex flex-row align-items-center gap-2 text-muted mb-2'>
<Icon name='info' width={14} fill='#656D7B' />
<span>
Your test event is on its way. It can take up to a few hours to
process the first event.
</span>
</div>
)}
<WarehouseEventCodeHelp />
<div className='d-flex justify-content-end mt-3'>
{connection.warehouse_type === 'flagsmith' ? (
<Button theme='primary' size='small' disabled>
Send your first event
</Button>
) : (
{!isFlagsmith && (
<Button theme='outline' size='small' disabled>
Test connection
</Button>
)}
{isFlagsmith && isPending && (
<Button
theme='outline'
size='small'
onClick={onSendTestEvent}
disabled={isSendingTestEvent}
>
Verify now
</Button>
)}
{isFlagsmith && !isPending && !isConnected && (
<Button
theme='primary'
size='small'
onClick={onSendTestEvent}
disabled={isSendingTestEvent}
>
{isSendingTestEvent ? 'Sending...' : 'Send your first event'}
</Button>
)}
</div>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ const WarehouseEventCodeHelp: FC = () => (
snippets={enabledSnippets}
showInitially
hideHeader
hideDocs
/>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import sendWarehouseTestEvent from 'components/pages/environment-settings/tabs/warehouse-tab/sendWarehouseTestEvent'

const init = jest.fn().mockResolvedValue(undefined)
const trackEvent = jest.fn()

jest.mock('@flagsmith/flagsmith/isomorphic', () => ({
createFlagsmithInstance: () => ({ init, trackEvent }),
}))

jest.mock('common/project', () => ({
__esModule: true,
default: { api: 'http://localhost:8000/api/v1/' },
}))

describe('sendWarehouseTestEvent', () => {
beforeEach(() => {
init.mockClear()
trackEvent.mockClear()
})

it('inits a per-environment instance with events enabled and no flag fetch', async () => {
await sendWarehouseTestEvent('env-key-123')

expect(init).toHaveBeenCalledWith(
expect.objectContaining({
defaultFlags: {},
enableEvents: true,
environmentID: 'env-key-123',
preventFetch: true,
}),
)
})
Comment thread
Zaimwa9 marked this conversation as resolved.

it('tracks the test_custom_event after init', async () => {
await sendWarehouseTestEvent('env-key-123')

expect(trackEvent).toHaveBeenCalledWith('test_custom_event')
expect(init).toHaveBeenCalledTimes(1)
expect(trackEvent).toHaveBeenCalledTimes(1)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { getWarehousePollingInterval } from 'components/pages/environment-settings/tabs/warehouse-tab/warehousePolling'

describe('getWarehousePollingInterval', () => {
it('polls every 30s while pending_connection', () => {
expect(getWarehousePollingInterval('pending_connection')).toBe(30000)
})

it('does not poll for connected', () => {
expect(getWarehousePollingInterval('connected')).toBe(0)
})

it('does not poll for created', () => {
expect(getWarehousePollingInterval('created')).toBe(0)
})

it('does not poll for errored', () => {
expect(getWarehousePollingInterval('errored')).toBe(0)
})

it('does not poll when status is undefined', () => {
expect(getWarehousePollingInterval(undefined)).toBe(0)
})
})
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { FC, useState } from 'react'
import { FC, useEffect, useState } from 'react'
import {
useCreateWarehouseConnectionMutation,
useDeleteWarehouseConnectionMutation,
useGetWarehouseConnectionsQuery,
useTestWarehouseConnectionMutation,
useUpdateWarehouseConnectionMutation,
} from 'common/services/useWarehouseConnection'
import { SnowflakeConfig } from 'common/types/responses'
import Loader from 'components/Loader'
import WarehouseConnectionCard from './WarehouseConnectionCard'
import WarehouseSetup from './WarehouseSetup'
import ConfigForm from './ConfigForm'
import sendWarehouseTestEvent from './sendWarehouseTestEvent'
import { getWarehousePollingInterval } from './warehousePolling'

type WarehouseTabProps = {
environmentId: string
Expand All @@ -18,13 +21,18 @@ type WarehouseTabProps = {
const WarehouseTab: FC<WarehouseTabProps> = ({ environmentId }) => {
const [editing, setEditing] = useState(false)

// Poll only while waiting for the first event to land in the warehouse. Held
// in state (updated by the effect below) because the interval depends on the
// query's own result, which isn't available when the hook is first called.
const [pollingInterval, setPollingInterval] = useState(0)

const {
data: connections,
isError,
isLoading,
} = useGetWarehouseConnectionsQuery(
{ environmentId },
{ skip: !environmentId },
{ pollingInterval, skip: !environmentId },
)
const [createConnection, { isLoading: isCreating }] =
useCreateWarehouseConnectionMutation()
Expand All @@ -33,6 +41,13 @@ const WarehouseTab: FC<WarehouseTabProps> = ({ environmentId }) => {

const connection = connections?.[0]

useEffect(() => {
setPollingInterval(getWarehousePollingInterval(connection?.status))
}, [connection?.status])

const [testConnection, { isLoading: isSendingTestEvent }] =
useTestWarehouseConnectionMutation()

const handleEnableFlagsmith = () => {
openConfirm({
body: 'This will enable a Flagsmith Warehouse connection for this environment. Are you sure you want to proceed?',
Expand Down Expand Up @@ -88,6 +103,18 @@ const WarehouseTab: FC<WarehouseTabProps> = ({ environmentId }) => {
.catch(() => toast('Failed to remove warehouse connection', 'danger'))
}

const handleSendTestEvent = () => {
if (!connection) return
sendWarehouseTestEvent(environmentId)
.then(() => testConnection({ environmentId, id: connection.id }).unwrap())
.then(() => toast('Test event sent'))
.catch((error) => {
// eslint-disable-next-line no-console
console.error('[warehouse] send test event failed:', error)
toast('Failed to send test event', 'danger')
})
}

if (isLoading) {
return (
<div className='mt-4 col-md-12'>
Expand Down Expand Up @@ -142,6 +169,8 @@ const WarehouseTab: FC<WarehouseTabProps> = ({ environmentId }) => {
? () => setEditing(true)
: undefined
}
onSendTestEvent={handleSendTestEvent}
isSendingTestEvent={isSendingTestEvent}
/>
</div>
)
Expand Down
Loading
Loading