Skip to content
Merged
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 @@ -998,6 +998,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 | null
unique_events_count: number | null
}

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/',
// 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/',
// 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/',

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/',
// 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,36 @@ 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 flex-nowrap 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 && !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 @@ -126,6 +126,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,39 @@
import { FC } from 'react'
import Skeleton from 'components/Skeleton'
import './WarehouseSetup.scss'
import './SelectableCard.scss'

// Mirrors the WarehouseSetup layout (warehouse-type selector + Flagsmith enable
// card) so the loading state has the same shape as the empty state it resolves
// to, avoiding a layout shift when the connections query settles.
const TYPE_CARD_COUNT = 4

const WarehouseSetupSkeleton: FC = () => (
<div className='warehouse-setup' aria-hidden>
<div>
<div className='warehouse-setup__type-row'>
{Array.from({ length: TYPE_CARD_COUNT }).map((_, index) => (
<div className='warehouse-setup__type-card' key={index}>
<div className='selectable-card'>
<div className='selectable-card__content'>
<div className='selectable-card__icon'>
<Skeleton variant='circle' width={20} height={20} />
</div>
<Skeleton width={80} height={16} />
<Skeleton width={120} height={12} />
</div>
</div>
</div>
))}
</div>
</div>

<div className='warehouse-setup__flagsmith-card'>
<Skeleton width='60%' height={16} />
<Skeleton variant='badge' width={88} height={32} />
</div>
</div>
)

WarehouseSetupSkeleton.displayName = 'WarehouseSetupSkeleton'
export default WarehouseSetupSkeleton
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import Icon from 'components/icons/Icon'
type WarehouseStatsProps = {
errored: boolean
lastEventReceived: string
totalEventsReceived: number
uniqueEventsCount: number
totalEventsReceived: number | null
uniqueEventsCount: number | null
}

const formatCount = (value: number | null): string =>
value !== null ? value.toLocaleString() : '-'

const WarehouseStats: FC<WarehouseStatsProps> = ({
errored,
lastEventReceived,
Expand All @@ -30,13 +33,13 @@ const WarehouseStats: FC<WarehouseStatsProps> = ({
<div className='d-flex flex-row gap-2'>
<span className='text-muted'>Total events received:</span>
<span className='font-weight-medium'>
{totalEventsReceived.toLocaleString()}
{formatCount(totalEventsReceived)}
</span>
</div>
<div className='d-flex flex-row gap-2'>
<span className='text-muted'>Number of unique events:</span>
<span className='font-weight-medium'>
{uniqueEventsCount.toLocaleString()}
{formatCount(uniqueEventsCount)}
</span>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import sendWarehouseTestEvent from 'components/pages/environment-settings/tabs/warehouse-tab/sendWarehouseTestEvent'

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

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

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

describe('sendWarehouseTestEvent', () => {
beforeEach(() => {
init.mockClear()
trackEvent.mockClear()
flushEvents.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)
})

it('flushes events so the tracked event is sent immediately', async () => {
await sendWarehouseTestEvent('env-key-123')

expect(flushEvents).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 minute while pending_connection', () => {
expect(getWarehousePollingInterval('pending_connection')).toBe(60000)
})

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)
})
})
Loading
Loading