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
3 changes: 3 additions & 0 deletions redisinsight/ui/src/config/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ export const defaultConfig = {
localResourcesBaseUrl: process.env.RI_LOCAL_RESOURCES_BASE_URL,
useLocalResources: booleanEnv('RI_USE_LOCAL_RESOURCES', false),
indexedDbName: process.env.RI_INDEXED_DB_NAME || 'RI_LOCAL_STORAGE',
vectorSearchIndexedDbName:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a separate IndexedDB for the Search page (as advised here).

It will use the same interface like the Workbench, but the Commands History related to these pages will live independently.

process.env.RI_VECTOR_SEARCH_INDEXED_DB_NAME ||
'RI_VECTOR_SEARCH_STORAGE',
truncatedStringPrefix:
process.env.RI_CLIENTS_TRUNCATED_STRING_PREFIX ||
'[Truncated due to length]',
Expand Down
32 changes: 32 additions & 0 deletions redisinsight/ui/src/mocks/handlers/workbench/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,45 @@
import { INSTANCE_ID_MOCK } from '../instances/instancesHandlers'

const handlers: RestHandler[] = [
rest.get<CommandExecution[]>(
getMswURL(
getUrl(INSTANCE_ID_MOCK, ApiEndpoints.WORKBENCH_COMMAND_EXECUTIONS),
),
async (_req, res, ctx) =>
res(ctx.status(200), ctx.json(commandExecutionFactory.buildList(1))),
),
rest.get<CommandExecution>(
getMswURL(
getUrl(
INSTANCE_ID_MOCK,
`${ApiEndpoints.WORKBENCH_COMMAND_EXECUTIONS}/:commandId`,
),
),
async (_req, res, ctx) =>

Check warning on line 24 in redisinsight/ui/src/mocks/handlers/workbench/commands.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🕹️ Function is not covered

Warning! Not covered function
res(ctx.status(200), ctx.json(commandExecutionFactory.build())),

Check warning on line 25 in redisinsight/ui/src/mocks/handlers/workbench/commands.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
),
rest.post<CommandExecution[]>(
getMswURL(
getUrl(INSTANCE_ID_MOCK, ApiEndpoints.WORKBENCH_COMMAND_EXECUTIONS),
),
async (_req, res, ctx) =>
res(ctx.status(200), ctx.json(commandExecutionFactory.buildList(1))),
),
rest.delete(
getMswURL(
getUrl(
INSTANCE_ID_MOCK,
`${ApiEndpoints.WORKBENCH_COMMAND_EXECUTIONS}/:commandId`,
),
),
async (_req, res, ctx) => res(ctx.status(200)),
),
rest.delete(
getMswURL(
getUrl(INSTANCE_ID_MOCK, ApiEndpoints.WORKBENCH_COMMAND_EXECUTIONS),
),
async (_req, res, ctx) => res(ctx.status(200)),
),
]

export default handlers
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { renderHook, act } from '@testing-library/react-hooks'
import executeQuery from 'uiSrc/services/executeQuery'
import {
CreateSearchIndexParameters,
SampleDataContent,
Expand All @@ -9,27 +8,25 @@ import {
import { useCreateIndex } from './useCreateIndex'

const mockLoad = jest.fn()
const mockAddCommands = jest.fn()
const mockAddCommandsToHistory = jest.fn()

jest.mock('uiSrc/services/hooks', () => ({
useLoadData: () => ({
load: mockLoad,
}),
}))

jest.mock('uiSrc/services/workbenchStorage', () => ({
addCommands: (...args: any[]) => mockAddCommands(...args),
}))

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

jest.mock('uiSrc/services/executeQuery', () => ({
jest.mock('uiSrc/services/commands-history/commandsHistoryService', () => ({
__esModule: true,
default: jest.fn(),
default: jest.fn().mockImplementation(() => ({
addCommandsToHistory: mockAddCommandsToHistory,
})),
}))
const mockExecute = executeQuery as jest.Mock

const mockOnSuccess = jest.fn()
const mockOnError = jest.fn()

Expand All @@ -50,7 +47,7 @@ describe('useCreateIndex', () => {

it('should complete flow successfully', async () => {
mockLoad.mockResolvedValue(undefined)
mockExecute.mockResolvedValue([{ id: '1', databaseId: 'test-instance-id' }])
mockAddCommandsToHistory.mockResolvedValue([])

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

Expand All @@ -59,11 +56,14 @@ describe('useCreateIndex', () => {
})

expect(mockLoad).toHaveBeenCalledWith('test-instance-id', 'bikes')
expect(mockExecute).toHaveBeenCalledWith(
expect(mockAddCommandsToHistory).toHaveBeenCalledWith(
'test-instance-id',
'FT.CREATE idx:bikes_vss ...',
['FT.CREATE idx:bikes_vss ...'],
{
activeRunQueryMode: 'RAW',
resultsMode: 'DEFAULT',
},
)
expect(mockAddCommands).toHaveBeenCalled()
expect(result.current.success).toBe(true)
expect(result.current.error).toBeNull()
expect(result.current.loading).toBe(false)
Expand All @@ -82,7 +82,7 @@ describe('useCreateIndex', () => {
expect(result.current.error?.message).toMatch(/Instance ID is required/)
expect(result.current.loading).toBe(false)
expect(mockLoad).not.toHaveBeenCalled()
expect(mockExecute).not.toHaveBeenCalled()
expect(mockAddCommandsToHistory).not.toHaveBeenCalled()
})

it('should handle failure in data loading', async () => {
Expand All @@ -99,32 +99,33 @@ describe('useCreateIndex', () => {
expect(result.current.success).toBe(false)
expect(result.current.error).toBe(error)
expect(result.current.loading).toBe(false)
expect(mockExecute).not.toHaveBeenCalled()
expect(mockAddCommandsToHistory).not.toHaveBeenCalled()
expect(mockOnSuccess).not.toHaveBeenCalled()
expect(mockOnError).toHaveBeenCalled()
})

it('should handle execution failure', async () => {
it('should handle command history service failure', async () => {
mockLoad.mockResolvedValue(undefined)
mockExecute.mockRejectedValue(new Error('Execution failed'))
mockAddCommandsToHistory.mockRejectedValue(
new Error('Command history service failed'),
)

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

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

expect(mockExecute).toHaveBeenCalled()
expect(mockAddCommandsToHistory).toHaveBeenCalled()
expect(result.current.success).toBe(false)
expect(result.current.error).toBeInstanceOf(Error)
expect(result.current.error?.message).toBe('Execution failed')
expect(result.current.error?.message).toBe('Command history service failed')
expect(result.current.loading).toBe(false)
})

it('should handle movies data content correctly', async () => {
const mockData = [{ id: '1', databaseId: 'test-instance-id' }]
mockLoad.mockResolvedValue(undefined)
mockExecute.mockResolvedValue(mockData)
mockAddCommandsToHistory.mockResolvedValue([])

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

Expand All @@ -138,6 +139,14 @@ describe('useCreateIndex', () => {
})

expect(mockLoad).toHaveBeenCalledWith('test-instance-id', 'movies')
expect(mockAddCommandsToHistory).toHaveBeenCalledWith(
'test-instance-id',
['FT.CREATE idx:bikes_vss ...'],
{
activeRunQueryMode: 'RAW',
resultsMode: 'DEFAULT',
},
)
expect(result.current.success).toBe(true)
})
})
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { useCallback, useState } from 'react'
import { reverse } from 'lodash'
import { useCallback, useRef, useState } from 'react'
import { useLoadData } from 'uiSrc/services/hooks'
import { addCommands } from 'uiSrc/services/workbenchStorage'
import { generateFtCreateCommand } from 'uiSrc/utils/index/generateFtCreateCommand'
import { CreateSearchIndexParameters, SampleDataContent } from '../types'
import executeQuery from 'uiSrc/services/executeQuery'
import {
CommandExecutionType,
ResultsMode,
RunQueryMode,
} from 'uiSrc/slices/interfaces'
import CommandsHistoryService from 'uiSrc/services/commands-history/commandsHistoryService'

interface UseCreateIndexResult {
run: (
Expand All @@ -27,6 +31,10 @@ export const useCreateIndex = (): UseCreateIndexResult => {
const [loading, setLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)

const commandsHistoryService = useRef(
new CommandsHistoryService(CommandExecutionType.Search),
).current

const { load } = useLoadData()

const run = useCallback(
Expand Down Expand Up @@ -54,12 +62,12 @@ export const useCreateIndex = (): UseCreateIndexResult => {
indexName,
dataContent,
})
const data = await executeQuery(instanceId, cmd)

// Step 3: Persist results locally so Vector Search history (CommandsView) shows it
if (Array.isArray(data) && data.length) {
await addCommands(reverse(data))
}
// Step 3: Persist results so Vector Search history (CommandsView) shows it
await commandsHistoryService.addCommandsToHistory(instanceId, [cmd], {
activeRunQueryMode: RunQueryMode.Raw,
resultsMode: ResultsMode.Default,
})

setSuccess(true)
onSuccess?.()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { TelemetryEvent } from 'uiSrc/telemetry/events'
import { sendEventTelemetry } from 'uiSrc/telemetry'
import { INSTANCE_ID_MOCK } from 'uiSrc/mocks/handlers/instances/instancesHandlers'
import { VectorSearchQuery, VectorSearchQueryProps } from './VectorSearchQuery'
import * as utils from './utils'

// Mock the telemetry module, so we don't send actual telemetry data during tests
jest.mock('uiSrc/telemetry', () => ({
Expand All @@ -25,10 +24,19 @@ jest.mock('uiSrc/slices/browser/redisearch', () => ({
.mockReturnValue({ type: 'FETCH_REDISEARCH_LIST' }),
}))

// Mock the utils module to control loadHistoryData behavior
jest.mock('./utils', () => ({
...jest.requireActual('./utils'),
loadHistoryData: jest.fn(),
// Mock the CommandsHistoryService
const mockGetCommandsHistory = jest.fn()
const mockAddCommandsToHistory = jest.fn()
const mockDeleteCommandFromHistory = jest.fn()
const mockClearCommandsHistory = jest.fn()

jest.mock('uiSrc/services/commands-history/commandsHistoryService', () => ({
CommandsHistoryService: jest.fn().mockImplementation(() => ({
getCommandsHistory: mockGetCommandsHistory,
addCommandsToHistory: mockAddCommandsToHistory,
deleteCommandFromHistory: mockDeleteCommandFromHistory,
clearCommandsHistory: mockClearCommandsHistory,
})),
}))

const DEFAULT_PROPS: VectorSearchQueryProps = {
Expand All @@ -43,6 +51,11 @@ const renderVectorSearchQueryComponent = (
describe('VectorSearchQuery', () => {
beforeEach(() => {
jest.clearAllMocks()
// Reset the mock functions
mockGetCommandsHistory.mockResolvedValue([])
mockAddCommandsToHistory.mockResolvedValue([])
mockDeleteCommandFromHistory.mockResolvedValue(undefined)
mockClearCommandsHistory.mockResolvedValue(undefined)
})

it('should render correctly', () => {
Expand Down Expand Up @@ -79,10 +92,9 @@ describe('VectorSearchQuery', () => {
})

it('should render "No query results" message if there are no results', async () => {
// Mock loadHistoryData specifically for this test to return empty array
// Mock getCommandsHistory to return empty array
// This ensures isResultsLoaded becomes true and items remains empty
const mockedUtils = utils as jest.Mocked<typeof utils>
mockedUtils.loadHistoryData.mockResolvedValueOnce([])
mockGetCommandsHistory.mockResolvedValueOnce([])

renderVectorSearchQueryComponent()

Expand Down
Loading
Loading