Skip to content

Commit 341fb9d

Browse files
authored
RI-7303: Store create index command in Search results (#4835)
1 parent cf34065 commit 341fb9d

File tree

7 files changed

+165
-145
lines changed

7 files changed

+165
-145
lines changed

redisinsight/ui/src/pages/vector-search/create-index/hooks/useCreateIndex.spec.ts

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,54 @@
11
import { renderHook, act } from '@testing-library/react-hooks'
2-
import { useCreateIndex } from './useCreateIndex'
2+
import executeQuery from 'uiSrc/services/executeQuery'
33
import {
44
CreateSearchIndexParameters,
5+
SampleDataContent,
56
SampleDataType,
67
SearchIndexType,
78
} from '../types'
9+
import { useCreateIndex } from './useCreateIndex'
810

911
const mockLoad = jest.fn()
10-
const mockDispatch = jest.fn()
12+
const mockAddCommands = jest.fn()
1113

1214
jest.mock('uiSrc/services/hooks', () => ({
1315
useLoadData: () => ({
1416
load: mockLoad,
1517
}),
16-
useDispatchWbQuery: () => mockDispatch,
18+
}))
19+
20+
jest.mock('uiSrc/services/workbenchStorage', () => ({
21+
addCommands: (...args: any[]) => mockAddCommands(...args),
1722
}))
1823

1924
jest.mock('uiSrc/utils/index/generateFtCreateCommand', () => ({
2025
generateFtCreateCommand: () => 'FT.CREATE idx:bikes_vss ...',
2126
}))
2227

28+
jest.mock('uiSrc/services/executeQuery', () => ({
29+
__esModule: true,
30+
default: jest.fn(),
31+
}))
32+
const mockExecute = executeQuery as jest.Mock
33+
2334
describe('useCreateIndex', () => {
2435
beforeEach(() => {
2536
jest.clearAllMocks()
2637
})
2738

2839
const defaultParams: CreateSearchIndexParameters = {
29-
dataContent: '',
30-
usePresetVectorIndex: true,
31-
presetVectorIndexName: '',
32-
tags: [],
3340
instanceId: 'test-instance-id',
34-
searchIndexType: SearchIndexType.REDIS_QUERY_ENGINE,
41+
dataContent: SampleDataContent.E_COMMERCE_DISCOVERY,
3542
sampleDataType: SampleDataType.PRESET_DATA,
43+
searchIndexType: SearchIndexType.REDIS_QUERY_ENGINE,
44+
usePresetVectorIndex: true,
45+
indexName: 'bikes',
46+
indexFields: [],
3647
}
3748

3849
it('should complete flow successfully', async () => {
3950
mockLoad.mockResolvedValue(undefined)
40-
mockDispatch.mockImplementation((_data, { afterAll }) => afterAll?.())
51+
mockExecute.mockResolvedValue([{ id: '1', databaseId: 'test-instance-id' }])
4152

4253
const { result } = renderHook(() => useCreateIndex())
4354

@@ -46,7 +57,11 @@ describe('useCreateIndex', () => {
4657
})
4758

4859
expect(mockLoad).toHaveBeenCalledWith('test-instance-id', 'bikes')
49-
expect(mockDispatch).toHaveBeenCalled()
60+
expect(mockExecute).toHaveBeenCalledWith(
61+
'test-instance-id',
62+
'FT.CREATE idx:bikes_vss ...',
63+
)
64+
expect(mockAddCommands).toHaveBeenCalled()
5065
expect(result.current.success).toBe(true)
5166
expect(result.current.error).toBeNull()
5267
expect(result.current.loading).toBe(false)
@@ -62,6 +77,8 @@ describe('useCreateIndex', () => {
6277
expect(result.current.success).toBe(false)
6378
expect(result.current.error?.message).toMatch(/Instance ID is required/)
6479
expect(result.current.loading).toBe(false)
80+
expect(mockLoad).not.toHaveBeenCalled()
81+
expect(mockExecute).not.toHaveBeenCalled()
6582
})
6683

6784
it('should handle failure in data loading', async () => {
@@ -78,24 +95,23 @@ describe('useCreateIndex', () => {
7895
expect(result.current.success).toBe(false)
7996
expect(result.current.error).toBe(error)
8097
expect(result.current.loading).toBe(false)
98+
expect(mockExecute).not.toHaveBeenCalled()
8199
})
82100

83-
it('should handle dispatch failure', async () => {
101+
it('should handle execution failure', async () => {
84102
mockLoad.mockResolvedValue(undefined)
85-
mockDispatch.mockImplementation((_data, { onFail }) =>
86-
onFail?.(new Error('Dispatch failed')),
87-
)
103+
mockExecute.mockRejectedValue(new Error('Execution failed'))
88104

89105
const { result } = renderHook(() => useCreateIndex())
90106

91107
await act(async () => {
92108
await result.current.run(defaultParams)
93109
})
94110

95-
expect(mockDispatch).toHaveBeenCalled()
111+
expect(mockExecute).toHaveBeenCalled()
96112
expect(result.current.success).toBe(false)
97113
expect(result.current.error).toBeInstanceOf(Error)
98-
expect(result.current.error?.message).toBe('Dispatch failed')
114+
expect(result.current.error?.message).toBe('Execution failed')
99115
expect(result.current.loading).toBe(false)
100116
})
101117
})

redisinsight/ui/src/pages/vector-search/create-index/hooks/useCreateIndex.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { useCallback, useState } from 'react'
2-
import { useLoadData, useDispatchWbQuery } from 'uiSrc/services/hooks'
2+
import { reverse } from 'lodash'
3+
import { useLoadData } from 'uiSrc/services/hooks'
4+
import { addCommands } from 'uiSrc/services/workbenchStorage'
35
import { generateFtCreateCommand } from 'uiSrc/utils/index/generateFtCreateCommand'
46
import { CreateSearchIndexParameters, PresetDataType } from '../types'
7+
import executeQuery from 'uiSrc/services/executeQuery'
58

69
interface UseCreateIndexResult {
710
run: (params: CreateSearchIndexParameters) => Promise<void>
@@ -20,7 +23,6 @@ export const useCreateIndex = (): UseCreateIndexResult => {
2023
const [error, setError] = useState<Error | null>(null)
2124

2225
const { load } = useLoadData()
23-
const dispatchCreateIndex = useDispatchWbQuery()
2426

2527
const run = useCallback(
2628
async ({ instanceId }: CreateSearchIndexParameters) => {
@@ -40,22 +42,22 @@ export const useCreateIndex = (): UseCreateIndexResult => {
4042
await load(instanceId, collectionName)
4143

4244
// Step 2: Create the search index
43-
await new Promise<void>((resolve, reject) => {
44-
dispatchCreateIndex(generateFtCreateCommand(), {
45-
afterAll: () => {
46-
setSuccess(true)
47-
resolve()
48-
},
49-
onFail: reject,
50-
})
51-
})
45+
const cmd = generateFtCreateCommand()
46+
const data = await executeQuery(instanceId, cmd)
47+
48+
// Step 3: Persist results locally so Vector Search history (CommandsView) shows it
49+
if (Array.isArray(data) && data.length) {
50+
await addCommands(reverse(data))
51+
}
52+
53+
setSuccess(true)
5254
} catch (e) {
5355
setError(e instanceof Error ? e : new Error(String(e)))
5456
} finally {
5557
setLoading(false)
5658
}
5759
},
58-
[load, dispatchCreateIndex],
60+
[load, executeQuery],
5961
)
6062

6163
return {
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { rest } from 'msw'
2+
import { ApiEndpoints } from 'uiSrc/constants'
3+
import { mswServer } from 'uiSrc/mocks/server'
4+
import { getMswURL } from 'uiSrc/utils/test-utils'
5+
import { getUrl } from 'uiSrc/utils'
6+
import { RunQueryMode, ResultsMode } from 'uiSrc/slices/interfaces'
7+
import executeQuery from './executeQuery'
8+
9+
describe('executeQuery', () => {
10+
const instanceId = 'test-instance-id'
11+
const command = 'FT.CREATE idx:bikes_vss ...'
12+
13+
beforeEach(() => {
14+
mswServer.resetHandlers()
15+
jest.clearAllMocks()
16+
})
17+
18+
it.each([null, undefined])(
19+
'returns empty array and does not call API when data is %s',
20+
async (data) => {
21+
const result = await executeQuery(instanceId, data as any)
22+
expect(result).toEqual([])
23+
},
24+
)
25+
26+
it('calls API with correct parameters and returns result', async () => {
27+
const mockResponse = [{ id: '1', databaseId: instanceId }]
28+
29+
mswServer.use(
30+
rest.post(
31+
getMswURL(
32+
getUrl(instanceId, ApiEndpoints.WORKBENCH_COMMAND_EXECUTIONS),
33+
),
34+
async (req, res, ctx) => {
35+
const body = await req.json()
36+
expect(body).toEqual({
37+
commands: [command],
38+
mode: RunQueryMode.ASCII,
39+
resultsMode: ResultsMode.Default,
40+
type: 'SEARCH',
41+
})
42+
return res(ctx.status(200), ctx.json(mockResponse))
43+
},
44+
),
45+
)
46+
47+
const returned = await executeQuery(instanceId, command)
48+
expect(returned).toEqual(mockResponse)
49+
})
50+
51+
it('invokes afterAll callback on success', async () => {
52+
const mockResponse = [{ id: '1', databaseId: instanceId }]
53+
54+
mswServer.use(
55+
rest.post(
56+
getMswURL(
57+
getUrl(instanceId, ApiEndpoints.WORKBENCH_COMMAND_EXECUTIONS),
58+
),
59+
async (_req, res, ctx) => res(ctx.status(200), ctx.json(mockResponse)),
60+
),
61+
)
62+
63+
const afterAll = jest.fn()
64+
65+
await executeQuery(instanceId, command, { afterAll })
66+
67+
expect(afterAll).toHaveBeenCalled()
68+
})
69+
70+
it('invokes onFail and rethrows on error', async () => {
71+
mswServer.use(
72+
rest.post(
73+
getMswURL(
74+
getUrl(instanceId, ApiEndpoints.WORKBENCH_COMMAND_EXECUTIONS),
75+
),
76+
async (_req, res, ctx) => res(ctx.status(500)),
77+
),
78+
)
79+
80+
const onFail = jest.fn()
81+
82+
await expect(
83+
executeQuery(instanceId, command, { onFail }),
84+
).rejects.toThrow()
85+
86+
expect(onFail).toHaveBeenCalled()
87+
})
88+
})
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { RunQueryMode, ResultsMode } from 'uiSrc/slices/interfaces'
2+
import { executeApiCall } from 'uiSrc/pages/vector-search/query/utils'
3+
4+
export interface ExecuteQueryOptions {
5+
afterAll?: () => void
6+
onFail?: (error?: unknown) => void
7+
}
8+
9+
const executeQuery = async (
10+
instanceId: string,
11+
data: string | null | undefined,
12+
options: ExecuteQueryOptions = {},
13+
): Promise<Awaited<ReturnType<typeof executeApiCall>>> => {
14+
if (!data) return [] as unknown as Awaited<ReturnType<typeof executeApiCall>>
15+
16+
try {
17+
const result = await executeApiCall(
18+
instanceId,
19+
[data],
20+
RunQueryMode.ASCII,
21+
ResultsMode.Default,
22+
)
23+
options.afterAll?.()
24+
return result
25+
} catch (e) {
26+
options.onFail?.(e)
27+
throw e
28+
}
29+
}
30+
31+
export default executeQuery

redisinsight/ui/src/services/hooks/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,4 @@ export * from './hooks'
22
export * from './useWebworkers'
33
export * from './useCabability'
44
export * from './useStateWithContext'
5-
export * from './useDispatchWbQuery'
65
export * from './useLoadData'

redisinsight/ui/src/services/hooks/useDispatchWbQuery.spec.ts

Lines changed: 0 additions & 83 deletions
This file was deleted.

0 commit comments

Comments
 (0)