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/constants/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ enum ApiEndpoints {

NOTIFICATIONS = 'notifications',
NOTIFICATIONS_READ = 'notifications/read',

REDISEARCH = 'redisearch',
REDISEARCH_SEARCH = 'redisearch/search',
}

export const DEFAULT_SEARCH_MATCH = '*'
Expand Down
8 changes: 8 additions & 0 deletions redisinsight/ui/src/mocks/handlers/browser/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { DefaultBodyType, MockedRequest, RestHandler } from 'msw'

import redisearch from './redisearchHandlers'

const handlers: RestHandler<MockedRequest<DefaultBodyType>>[] = [].concat(
redisearch,
)
export default handlers
21 changes: 21 additions & 0 deletions redisinsight/ui/src/mocks/handlers/browser/redisearchHandlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { rest, RestHandler } from 'msw'
import { ApiEndpoints } from 'uiSrc/constants'
import { getMswURL } from 'uiSrc/utils/test-utils'
import { getUrl, stringToBuffer } from 'uiSrc/utils'
import { RedisResponseBuffer } from 'uiSrc/slices/interfaces'
import { INSTANCE_ID_MOCK } from '../instances/instancesHandlers'

export const REDISEARCH_LIST_DATA_MOCK_UTF8 = ['idx: 1', 'idx:2']
export const REDISEARCH_LIST_DATA_MOCK = [...REDISEARCH_LIST_DATA_MOCK_UTF8].map((str) => stringToBuffer(str))

const handlers: RestHandler[] = [
// fetchRedisearchListAction
rest.get<RedisResponseBuffer[]>(getMswURL(
getUrl(INSTANCE_ID_MOCK, ApiEndpoints.REDISEARCH)
), async (req, res, ctx) => res(
ctx.status(200),
ctx.json(REDISEARCH_LIST_DATA_MOCK),
))
]

export default handlers
9 changes: 8 additions & 1 deletion redisinsight/ui/src/mocks/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ import instances from './instances'
import content from './content'
import app from './app'
import analytics from './analytics'
import browser from './browser'

// @ts-ignore
export const handlers: RestHandler<MockedRequest>[] = [].concat(instances, content, app, analytics)
export const handlers: RestHandler<MockedRequest>[] = [].concat(
instances,
content,
app,
analytics,
browser,
)
309 changes: 309 additions & 0 deletions redisinsight/ui/src/slices/browser/redisearch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
import { AxiosError } from 'axios'
import { createSlice, PayloadAction } from '@reduxjs/toolkit'

import { ApiEndpoints } from 'uiSrc/constants'
import { apiService } from 'uiSrc/services'
import { getApiErrorMessage, getUrl, isStatusSuccessful } from 'uiSrc/utils'
import { DEFAULT_SEARCH_MATCH } from 'uiSrc/constants/api'

import { GetKeysWithDetailsResponse } from 'apiSrc/modules/browser/dto'
import { CreateRedisearchIndexDto } from 'apiSrc/modules/browser/dto/redisearch'

import { AppDispatch, RootState } from '../store'
import { RedisResponseBuffer, StateRedisearch } from '../interfaces'
import { addErrorNotification } from '../app/notifications'

export const initialState: StateRedisearch = {
loading: false,
error: '',
search: '',
selectedIndex: null,
data: {
total: 0,
scanned: 0,
nextCursor: '0',
keys: [],
shardsMeta: {},
previousResultCount: 0,
lastRefreshTime: null,
},
list: {
loading: false,
error: '',
data: []
},
createIndex: {
loading: false,
error: '',
},
}

// A slice for recipes
const redisearchSlice = createSlice({
name: 'redisearch',
initialState,
reducers: {
setRedisearchInitialState: () => initialState,

// load redisearch keys
loadKeys: (state) => {
state.list = {
...state.list,
loading: true,
error: '',
}
},
loadKeysSuccess: (state, { payload }: PayloadAction<GetKeysWithDetailsResponse>) => {
state.data = {
...payload
}
},
loadKeysFailure: (state, { payload }) => {
state.list = {
...state.list,
loading: false,
error: payload,
}
},

// load more redisearch keys
loadMoreKeys: (state) => {
state.list = {
...state.list,
loading: true,
error: '',
}
},
loadMoreKeysSuccess: (state, { payload }: PayloadAction<GetKeysWithDetailsResponse>) => {
state.list = {
...state.list,
loading: false,
data: payload,
}
},
loadMoreKeysFailure: (state, { payload }) => {
state.list = {
...state.list,
loading: false,
error: payload,
}
},

// load list of indexes
loadList: (state) => {
state.list = {
...state.list,
loading: true,
error: '',
}
},
loadListSuccess: (state, { payload }: PayloadAction<RedisResponseBuffer[]>) => {
state.list = {
...state.list,
loading: false,
data: payload,
}
},
loadListFailure: (state, { payload }) => {
state.list = {
...state.list,
loading: false,
error: payload,
}
},

// create an index
createIndex: (state) => {
state.createIndex = {
...state.createIndex,
loading: true,
error: '',
}
},
createIndexSuccess: (state) => {
state.createIndex = {
...state.createIndex,
loading: false,
}
},
createIndexFailure: (state, { payload }: PayloadAction<string>) => {
state.createIndex = {
...state.createIndex,
loading: false,
error: payload,
}
},
},
})

// Actions generated from the slice
export const {
loadKeys,
loadKeysSuccess,
loadKeysFailure,
loadMoreKeys,
loadMoreKeysSuccess,
loadMoreKeysFailure,
loadList,
loadListSuccess,
loadListFailure,
createIndex,
createIndexSuccess,
createIndexFailure,
setRedisearchInitialState,
} = redisearchSlice.actions

// Selectors
export const redisearchSelector = (state: RootState) => state.browser.redisearch
export const redisearchDataSelector = (state: RootState) => state.browser.redisearch.data
export const redisearchListSelector = (state: RootState) => state.browser.redisearch.list
export const createIndexStateSelector = (state: RootState) => state.browser.redisearch.createIndex

// The reducer
export default redisearchSlice.reducer

// Asynchronous thunk action
export function fetchRedisearchKeysAction(
cursor: string,
count: number,
onSuccess?: (value: GetKeysWithDetailsResponse) => void,
onFailed?: () => void,
) {
return async (dispatch: AppDispatch, stateInit: () => RootState) => {
dispatch(loadKeys())

try {
const state = stateInit()
const { encoding } = state.app.info
const { search: query } = state.browser.keys
const { data, status } = await apiService.post<GetKeysWithDetailsResponse>(
getUrl(
state.connections.instances.connectedInstance?.id,
ApiEndpoints.REDISEARCH_SEARCH
),
{
limit: count, offset: cursor, query: query || DEFAULT_SEARCH_MATCH, keysInfo: false,
},
{
params: { encoding },
}
)

if (isStatusSuccessful(status)) {
dispatch(loadKeysSuccess(data))
onSuccess?.(data)
}
} catch (_err) {
const error = _err as AxiosError
const errorMessage = getApiErrorMessage(error)
dispatch(addErrorNotification(error))
dispatch(loadKeysFailure(errorMessage))
onFailed?.()
}
}
}

// export function fetchMoreRedisearchKeysAction(
// onSuccess?: (value: RedisResponseBuffer[]) => void,
// onFailed?: () => void,
// ) {
// return async (dispatch: AppDispatch, stateInit: () => RootState) => {
// dispatch(loadMoreKeys())

// try {
// const state = stateInit()
// const { encoding } = state.app.info
// const { data, status } = await apiService.get<GetKeysWithDetailsResponse>(
// getUrl(
// state.connections.instances.connectedInstance?.id,
// ApiEndpoints.REDISEARCH_SEARCH
// ),
// {
// params: { encoding },
// }
// )

// if (isStatusSuccessful(status)) {
// dispatch(loadMoreKeysSuccess(data))
// onSuccess?.(data)
// }
// } catch (_err) {
// const error = _err as AxiosError
// const errorMessage = getApiErrorMessage(error)
// dispatch(addErrorNotification(error))
// dispatch(loadMoreKeysFailure(errorMessage))
// onFailed?.()
// }
// }
// }

export function fetchRedisearchListAction(
onSuccess?: (value: RedisResponseBuffer[]) => void,
onFailed?: () => void,
) {
return async (dispatch: AppDispatch, stateInit: () => RootState) => {
dispatch(loadList())

try {
const state = stateInit()
const { encoding } = state.app.info
const { data, status } = await apiService.get<RedisResponseBuffer[]>(
getUrl(
state.connections.instances.connectedInstance?.id,
ApiEndpoints.REDISEARCH
),
{
params: { encoding },
}
)

if (isStatusSuccessful(status)) {
dispatch(loadListSuccess(data))
onSuccess?.(data)
}
} catch (_err) {
const error = _err as AxiosError
const errorMessage = getApiErrorMessage(error)
dispatch(addErrorNotification(error))
dispatch(loadListFailure(errorMessage))
onFailed?.()
}
}
}
export function createRedisearchIndexAction(
data: CreateRedisearchIndexDto,
onSuccess?: () => void,
onFailed?: () => void,
) {
return async (dispatch: AppDispatch, stateInit: () => RootState) => {
dispatch(createIndex())

try {
const state = stateInit()
const { encoding } = state.app.info
const { status } = await apiService.post<void>(
getUrl(
state.connections.instances.connectedInstance?.id,
ApiEndpoints.REDISEARCH
),
{
...data
},
{
params: { encoding },
}
)

if (isStatusSuccessful(status)) {
dispatch(createIndexSuccess())
onSuccess?.()
}
} catch (_err) {
const error = _err as AxiosError
const errorMessage = getApiErrorMessage(error)
dispatch(addErrorNotification(error))
dispatch(createIndexFailure(errorMessage))
onFailed?.()
}
}
}
1 change: 1 addition & 0 deletions redisinsight/ui/src/slices/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './workbench'
export * from './monitor'
export * from './api'
export * from './bulkActions'
export * from './redisearch'
20 changes: 20 additions & 0 deletions redisinsight/ui/src/slices/interfaces/redisearch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Nullable } from 'uiSrc/utils'
import { RedisResponseBuffer } from './app'
import { KeysStoreData } from './keys'

export interface StateRedisearch {
loading: boolean
error: string
search: string
data: KeysStoreData
selectedIndex: Nullable<RedisResponseBuffer>
list: {
loading: boolean
error: string
data: RedisResponseBuffer[]
}
createIndex: {
loading: boolean
error: string
}
}
Loading