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
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { ANALYTICS_ROUTES } from 'uiSrc/components/main-router/constants/sub-rou
import {
appFeaturePagesHighlightingSelector,
removeFeatureFromHighlighting,
appFeatureFlagsFeaturesSelector,
} from 'uiSrc/slices/app/features'
import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
import { connectedInstanceSelector as connectedRdiInstanceSelector } from 'uiSrc/slices/rdi/instances'
Expand Down Expand Up @@ -49,10 +48,6 @@ export function useNavigation() {
)
const highlightedPages = useSelector(appFeaturePagesHighlightingSelector)

const { [FeatureFlags.vectorSearch]: vectorSearchFeature } = useSelector(
appFeatureFlagsFeaturesSelector,
)

const isRdiWorkspace = workspace === AppWorkspace.RDI

useEffect(() => {
Expand Down Expand Up @@ -100,7 +95,7 @@ export function useNavigation() {
iconType: BrowserIcon,
onboard: ONBOARDING_FEATURES.BROWSER_PAGE,
},
vectorSearchFeature?.flag && {
{
tooltipText: 'Search',
pageName: PageNames.vectorSearch,
ariaLabel: 'Search',
Expand Down Expand Up @@ -144,7 +139,7 @@ export function useNavigation() {
onboard: ONBOARDING_FEATURES.PUB_SUB_PAGE,
featureFlag: FeatureFlags.envDependent,
},
].filter((tab) => !!tab) as INavigations[]
]

const privateRdiRoutes: INavigations[] = [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react'
import { cleanup, render, screen } from 'uiSrc/utils/test-utils'
import { appFeatureFlagsFeaturesSelector } from 'uiSrc/slices/app/features'
import NoDataMessage, { NoDataMessageProps } from './NoDataMessage'
import { NO_DATA_MESSAGES, NoDataMessageKeys } from './data'
import useRedisInstanceCompatibility from '../../create-index/hooks/useRedisInstanceCompatibility'
Expand All @@ -8,6 +9,11 @@ jest.mock('../../create-index/hooks/useRedisInstanceCompatibility', () =>
jest.fn(),
)

jest.mock('uiSrc/slices/app/features', () => ({
...jest.requireActual('uiSrc/slices/app/features'),
appFeatureFlagsFeaturesSelector: jest.fn(),
}))

const mockDefaultNoDataMessageVariant = NoDataMessageKeys.ManageIndexes

const renderNoDataMessageComponent = (props?: NoDataMessageProps) => {
Expand All @@ -22,9 +28,18 @@ describe('NoDataMessage', () => {
const mockUseRedisInstanceCompatibility =
useRedisInstanceCompatibility as jest.Mock

const mockAppFeatureFlagsFeaturesSelector =
appFeatureFlagsFeaturesSelector as jest.Mock

beforeEach(() => {
cleanup()

mockAppFeatureFlagsFeaturesSelector.mockReturnValue({
vectorSearch: {
flag: true,
},
})

mockUseRedisInstanceCompatibility.mockReturnValue({
loading: false,
hasRedisearch: true,
Expand All @@ -41,7 +56,7 @@ describe('NoDataMessage', () => {
const title = screen.getByText(
NO_DATA_MESSAGES[mockDefaultNoDataMessageVariant].title,
)
const description = screen.getByText(
const description = screen.queryByText(
NO_DATA_MESSAGES[mockDefaultNoDataMessageVariant].description,
)
const icon = screen.getByAltText(
Expand All @@ -57,6 +72,36 @@ describe('NoDataMessage', () => {
expect(gettingStartedButton).toBeInTheDocument()
})

it('should render without onboarding button and text when ff is off', () => {
mockAppFeatureFlagsFeaturesSelector.mockReturnValue({
vectorSearch: {
flag: false,
},
})
renderNoDataMessageComponent()

const container = screen.getByTestId('no-data-message')
expect(container).toBeInTheDocument()

const title = screen.getByText(
NO_DATA_MESSAGES[mockDefaultNoDataMessageVariant].title,
)
const description = screen.queryByText(
NO_DATA_MESSAGES[mockDefaultNoDataMessageVariant].description,
)
const icon = screen.getByAltText(
NO_DATA_MESSAGES[mockDefaultNoDataMessageVariant].title,
)
const gettingStartedButton = screen.queryByRole('button', {
name: /Get started/i,
})

expect(title).toBeInTheDocument()
expect(description).not.toBeInTheDocument()
expect(icon).toBeInTheDocument()
expect(gettingStartedButton).not.toBeInTheDocument()
})

it('should not render "Get started" button when Redis version is unsupported', () => {
mockUseRedisInstanceCompatibility.mockReturnValue({
loading: false,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import React from 'react'
import { useSelector } from 'react-redux'
import { Button } from 'uiSrc/components/base/forms/buttons'
import { Col } from 'uiSrc/components/base/layout/flex'
import { Text } from 'uiSrc/components/base/text'
import { FeatureFlags } from 'uiSrc/constants'
import { appFeatureFlagsFeaturesSelector } from 'uiSrc/slices/app/features'

import useStartWizard from '../../hooks/useStartWizard'
import { StyledContainer, StyledImage } from './NoDataMessage.styles'
Expand All @@ -13,24 +16,30 @@ export interface NoDataMessageProps {
}

const NoDataMessage = ({ variant }: NoDataMessageProps) => {
const { [FeatureFlags.vectorSearch]: vectorSearchFeature } = useSelector(
appFeatureFlagsFeaturesSelector,
)

const start = useStartWizard()
const { loading, hasSupportedVersion } = useRedisInstanceCompatibility()
const { title, description, icon } = NO_DATA_MESSAGES[variant]
const { title, description, icon, imgStyle } = NO_DATA_MESSAGES[variant]

return (
<StyledContainer gap="xxl" data-testid="no-data-message">
<StyledImage src={icon} alt={title} as="img" />
<StyledImage src={icon} alt={title} as="img" style={imgStyle} />

<Col gap="m">
<Text size="M">{title}</Text>
<Text size="S">{description}</Text>
{vectorSearchFeature?.flag && <Text size="S">{description}</Text>}
</Col>

{loading === false && hasSupportedVersion === true && (
<Button variant="secondary-invert" onClick={start}>
Get started
</Button>
)}
{vectorSearchFeature?.flag &&
loading === false &&
hasSupportedVersion === true && (
<Button variant="secondary-invert" onClick={start}>
Get started
</Button>
)}
</StyledContainer>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface NoDataMessageDetails {
title: string
description: string
icon: string
imgStyle?: Record<string, string>
}

export const NO_DATA_MESSAGES: Record<NoDataMessageKeys, NoDataMessageDetails> =
Expand All @@ -21,6 +22,9 @@ export const NO_DATA_MESSAGES: Record<NoDataMessageKeys, NoDataMessageDetails> =
description:
'Start with vector search onboarding to explore sample data, or create an index and write queries in the smart editor.',
icon: NoQueryResultsIcon,
imgStyle: {
marginRight: '30px'
}
},
[NoDataMessageKeys.ManageIndexes]: {
title: 'No indexes.',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
import { Pages } from 'uiSrc/constants'
import { addErrorNotification } from 'uiSrc/slices/app/notifications'
import { INSTANCE_ID_MOCK } from 'uiSrc/mocks/handlers/analytics/dbAnalysisHistoryHandlers'
import { VectorSearchOnboardingProvider } from 'uiSrc/pages/vector-search/context/VectorSearchOnboardingContext'
import {
VectorSearchCreateIndex,
VectorSearchCreateIndexProps,
Expand All @@ -38,6 +39,14 @@ const mockedUseCreateIndex = useCreateIndex as jest.MockedFunction<
typeof useCreateIndex
>

const mockRunSuccess = jest.fn(async (_params, onSuccess) => {
onSuccess?.()
})

const mockRunFail = jest.fn(async (_params, _onSuccess, onError) => {
onError?.()
})

const renderVectorSearchCreateIndexComponent = (
props?: VectorSearchCreateIndexProps,
) => {
Expand All @@ -59,7 +68,12 @@ const renderVectorSearchCreateIndexComponent = (
},
}
const store = mockStore(testState)
const utils = render(<VectorSearchCreateIndex {...props} />, { store })
const utils = render(
<VectorSearchOnboardingProvider>
<VectorSearchCreateIndex {...props} />
</VectorSearchOnboardingProvider>,
{ store },
)

return { ...utils, store }
}
Expand All @@ -68,7 +82,7 @@ describe('VectorSearchCreateIndex', () => {
beforeEach(() => {
jest.clearAllMocks()
mockedUseCreateIndex.mockReturnValue({
run: jest.fn(),
run: mockRunSuccess,
loading: false,
error: null,
success: false,
Expand Down Expand Up @@ -198,6 +212,33 @@ describe('VectorSearchCreateIndex', () => {
},
})
})

it('should send telemetry events on create index step failed', () => {
mockedUseCreateIndex.mockReturnValue({
run: mockRunFail,
loading: false,
error: { message: 'Some error' },
success: false,
} as any)

renderVectorSearchCreateIndexComponent()

// Simulate going to the index info step
const buttonNext = screen.getByTestId('proceed-to-index-button')
fireEvent.click(buttonNext)

// Simulate creating the index
const buttonCreateIndex = screen.getByTestId('create-index-button')
fireEvent.click(buttonCreateIndex)

expect(sendEventTelemetry).toHaveBeenCalledTimes(3)
expect(sendEventTelemetry).toHaveBeenNthCalledWith(3, {
event: TelemetryEvent.VECTOR_SEARCH_ONBOARDING_CREATE_INDEX_ERROR,
eventData: {
databaseId: INSTANCE_ID_MOCK,
},
})
})
})

it('should show disabled data editing banner when on final step with preset data', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
VectorSearchScreenWrapper,
} from '../styles'
import {
collectCreateIndexErrorStepTelemetry,
collectCreateIndexStepTelemetry,
collectCreateIndexWizardTelemetry,
} from '../telemetry'
Expand All @@ -36,6 +37,7 @@ import successMessages from 'uiSrc/components/notifications/success-messages'
import { parseCustomError } from 'uiSrc/utils'
import { Row } from 'uiSrc/components/base/layout/flex'
import { Banner } from 'uiSrc/components/base/display'
import { useVectorSearchOnboarding } from 'uiSrc/pages/vector-search/context/VectorSearchOnboardingContext'

const stepNextButton: IStepNextButton[] = [
{
Expand Down Expand Up @@ -74,6 +76,8 @@ export const VectorSearchCreateIndex = ({
const { instanceId } = useParams<{ instanceId: string }>()
const [step, setStep] = useState(initialStep)

const { setOnboardingSeen } = useVectorSearchOnboarding()

const [createSearchIndexParameters, setCreateSearchIndexParameters] =
useState<CreateSearchIndexParameters>({
instanceId,
Expand All @@ -99,8 +103,16 @@ export const VectorSearchCreateIndex = ({
const StepContent = stepContents[step]
const onNextClick = () => {
if (isFinalStep) {
createIndex(createSearchIndexParameters)
collectCreateIndexStepTelemetry(instanceId)
createIndex(
createSearchIndexParameters,
() => {
collectCreateIndexStepTelemetry(instanceId)
setOnboardingSeen()
},
() => {
collectCreateIndexErrorStepTelemetry(instanceId)
},
)
return
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ jest.mock('uiSrc/services/executeQuery', () => ({
default: jest.fn(),
}))
const mockExecute = executeQuery as jest.Mock
const mockOnSuccess = jest.fn()
const mockOnError = jest.fn()

describe('useCreateIndex', () => {
beforeEach(() => {
Expand All @@ -53,7 +55,7 @@ describe('useCreateIndex', () => {
const { result } = renderHook(() => useCreateIndex())

await act(async () => {
await result.current.run(defaultParams)
await result.current.run(defaultParams, mockOnSuccess, mockOnError)
})

expect(mockLoad).toHaveBeenCalledWith('test-instance-id', 'bikes')
Expand All @@ -65,6 +67,8 @@ describe('useCreateIndex', () => {
expect(result.current.success).toBe(true)
expect(result.current.error).toBeNull()
expect(result.current.loading).toBe(false)
expect(mockOnSuccess).toHaveBeenCalled()
expect(mockOnError).not.toHaveBeenCalled()
})

it('should handle error if instanceId is missing', async () => {
Expand All @@ -88,14 +92,16 @@ describe('useCreateIndex', () => {
const { result } = renderHook(() => useCreateIndex())

await act(async () => {
await result.current.run(defaultParams)
await result.current.run(defaultParams, mockOnSuccess, mockOnError)
})

expect(mockLoad).toHaveBeenCalled()
expect(result.current.success).toBe(false)
expect(result.current.error).toBe(error)
expect(result.current.loading).toBe(false)
expect(mockExecute).not.toHaveBeenCalled()
expect(mockOnSuccess).not.toHaveBeenCalled()
expect(mockOnError).toHaveBeenCalled()
})

it('should handle execution failure', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import { CreateSearchIndexParameters, SampleDataContent } from '../types'
import executeQuery from 'uiSrc/services/executeQuery'

interface UseCreateIndexResult {
run: (params: CreateSearchIndexParameters) => Promise<void>
run: (
params: CreateSearchIndexParameters,
onSuccess?: () => void,
onError?: () => void,
) => Promise<void>
loading: boolean
error: Error | null
success: boolean
Expand All @@ -26,11 +30,11 @@ export const useCreateIndex = (): UseCreateIndexResult => {
const { load } = useLoadData()

const run = useCallback(
async ({
instanceId,
indexName,
dataContent,
}: CreateSearchIndexParameters) => {
async (
{ instanceId, indexName, dataContent }: CreateSearchIndexParameters,
onSuccess: () => void,
onError: () => void,
) => {
setSuccess(false)
setError(null)
setLoading(true)
Expand Down Expand Up @@ -58,8 +62,10 @@ export const useCreateIndex = (): UseCreateIndexResult => {
}

setSuccess(true)
onSuccess?.()
} catch (e) {
setError(e instanceof Error ? e : new Error(String(e)))
onError?.()
} finally {
setLoading(false)
}
Expand Down
Loading
Loading