Skip to content

Commit 2fc7ad6

Browse files
#RI-5004 - add geoshape resisearch option (#2705)
* #RI-5004 - add geoshape resisearch option
1 parent 57a72eb commit 2fc7ad6

File tree

10 files changed

+129
-12
lines changed

10 files changed

+129
-12
lines changed

redisinsight/api/src/modules/browser/dto/redisearch.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export enum RedisearchIndexDataType {
1616
TAG = 'tag',
1717
NUMERIC = 'numeric',
1818
GEO = 'geo',
19+
GEOSHAPE = 'geoshape',
1920
VECTOR = 'vector',
2021
}
2122

redisinsight/ui/src/constants/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,5 @@ export * from './featureFlags'
3030
export * from './serverVersions'
3131
export * from './customErrorCodes'
3232
export * from './securityField'
33+
export * from './redisearch'
3334
export { ApiEndpoints, BrowserStorageItem, ApiStatusCode, apiErrors }
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const REDISEARCH_GEOSHAPE_SEMANTIC_VERSION = '2.8.4'
2+
3+
export const REDISEARCH_GEOSHAPE_VERSION = 20804

redisinsight/ui/src/pages/browser/components/create-redisearch-index/CreateRedisearchIndex.tsx

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
EuiLink,
1515
EuiPopover,
1616
EuiButtonIcon,
17+
EuiSuperSelectOption,
1718
} from '@elastic/eui'
1819
import { EuiComboBoxOptionOption } from '@elastic/eui/src/components/combo_box/types'
1920
import cx from 'classnames'
@@ -27,9 +28,10 @@ import { stringToBuffer } from 'uiSrc/utils'
2728
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
2829
import { keysSelector } from 'uiSrc/slices/browser/keys'
2930
import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
31+
import { getFieldTypeOptions } from 'uiSrc/utils/redisearch'
3032
import { CreateRedisearchIndexDto } from 'apiSrc/modules/browser/dto/redisearch'
3133

32-
import { FIELD_TYPE_OPTIONS, KEY_TYPE_OPTIONS, RedisearchIndexKeyType } from './constants'
34+
import { KEY_TYPE_OPTIONS, RedisearchIndexKeyType } from './constants'
3335

3436
import styles from './styles.module.scss'
3537

@@ -50,22 +52,19 @@ const keyTypeOptions = KEY_TYPE_OPTIONS.map((item) => {
5052
}
5153
})
5254

53-
const fieldTypeOptions = FIELD_TYPE_OPTIONS.map(({ value, text }) => ({
54-
value,
55-
inputDisplay: text,
56-
}))
57-
58-
const initialFieldValue = (id = 0) => ({ id, identifier: '', fieldType: fieldTypeOptions[0].value })
55+
const initialFieldValue = (fieldTypeOptions: EuiSuperSelectOption<string>[], id = 0) => ({ id, identifier: '', fieldType: fieldTypeOptions[0].value })
5956

6057
const CreateRedisearchIndex = ({ onClosePanel, onCreateIndex }: Props) => {
6158
const { viewType } = useSelector(keysSelector)
6259
const { loading } = useSelector(createIndexStateSelector)
63-
const { id: instanceId } = useSelector(connectedInstanceSelector)
60+
const { id: instanceId, modules } = useSelector(connectedInstanceSelector)
6461

6562
const [keyTypeSelected, setKeyTypeSelected] = useState<RedisearchIndexKeyType>(keyTypeOptions[0].value)
6663
const [prefixes, setPrefixes] = useState<EuiComboBoxOptionOption[]>([])
6764
const [indexName, setIndexName] = useState<string>('')
68-
const [fields, setFields] = useState<any[]>([initialFieldValue()])
65+
const [fieldTypeOptions, setFieldTypeOptions] = useState<EuiSuperSelectOption<string>[]>(getFieldTypeOptions(modules))
66+
const [fields, setFields] = useState<any[]>([initialFieldValue(fieldTypeOptions)])
67+
6968
const [isInfoPopoverOpen, setIsInfoPopoverOpen] = useState<boolean>(false)
7069

7170
const lastAddedIdentifier = useRef<HTMLInputElement>(null)
@@ -80,17 +79,21 @@ const CreateRedisearchIndex = ({ onClosePanel, onCreateIndex }: Props) => {
8079
prevCountFields.current = fields.length
8180
}, [fields.length])
8281

82+
useEffect(() => {
83+
setFieldTypeOptions(getFieldTypeOptions(modules))
84+
}, [modules])
85+
8386
const addField = () => {
8487
const lastFieldId = fields[fields.length - 1].id
85-
setFields([...fields, initialFieldValue(lastFieldId + 1)])
88+
setFields([...fields, initialFieldValue(fieldTypeOptions, lastFieldId + 1)])
8689
}
8790

8891
const removeField = (id: number) => {
8992
setFields((fields) => fields.filter((item) => item.id !== id))
9093
}
9194

9295
const clearFieldsValues = (id: number) => {
93-
setFields((fields) => fields.map((item) => (item.id === id ? initialFieldValue(id) : item)))
96+
setFields((fields) => fields.map((item) => (item.id === id ? initialFieldValue(fieldTypeOptions, id) : item)))
9497
}
9598

9699
const handleFieldChange = (formField: string, id: number, value: string) => {

redisinsight/ui/src/pages/browser/components/create-redisearch-index/CreateRedisearchIndexWrapper.spec.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
import { cloneDeep } from 'lodash'
22
import React from 'react'
33
import { createIndex } from 'uiSrc/slices/browser/redisearch'
4+
import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
45
import { render, screen, fireEvent, cleanup, mockedStore } from 'uiSrc/utils/test-utils'
56

67
import CreateRedisearchIndexWrapper from './CreateRedisearchIndexWrapper'
78

9+
jest.mock('uiSrc/slices/instances/instances', () => ({
10+
...jest.requireActual('uiSrc/slices/instances/instances'),
11+
connectedInstanceSelector: jest.fn().mockReturnValue({
12+
id: '1',
13+
modules: [],
14+
}),
15+
}))
16+
817
const onClose = jest.fn()
918

1019
let store: typeof mockedStore
@@ -106,4 +115,25 @@ describe('CreateRedisearchIndexWrapper', () => {
106115

107116
expect(screen.getByTestId('identifier-info-icon')).toBeInTheDocument()
108117
})
118+
119+
it('should not have geoshape option ', () => {
120+
const { queryByText } = render(<CreateRedisearchIndexWrapper onClosePanel={onClose} />)
121+
122+
fireEvent.click(screen.getByTestId('field-type-0'))
123+
124+
expect(queryByText('GEOSHAPE')).not.toBeInTheDocument()
125+
})
126+
127+
it('should have geoshape option ', () => {
128+
const connectedInstanceSelectorMock = jest.fn().mockReturnValueOnce({
129+
id: '1',
130+
modules: [{ name: 'search', semanticVersion: '2.8.4' }]
131+
})
132+
connectedInstanceSelector.mockImplementation(connectedInstanceSelectorMock)
133+
134+
const { queryByText } = render(<CreateRedisearchIndexWrapper onClosePanel={onClose} />)
135+
fireEvent.click(screen.getByTestId('field-type-0'))
136+
137+
expect(queryByText('GEOSHAPE')).toBeInTheDocument()
138+
})
109139
})

redisinsight/ui/src/pages/browser/components/create-redisearch-index/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export enum FieldTypes {
66
NUMERIC = 'numeric',
77
GEO = 'geo',
88
VECTOR = 'vector',
9+
GEOSHAPE = 'geoshape',
910
}
1011

1112
export enum RedisearchIndexKeyType {
@@ -43,6 +44,10 @@ export const FIELD_TYPE_OPTIONS = [
4344
text: 'GEO',
4445
value: FieldTypes.GEO,
4546
},
47+
{
48+
text: 'GEOSHAPE',
49+
value: FieldTypes.GEOSHAPE,
50+
},
4651
{
4752
text: 'VECTOR',
4853
value: FieldTypes.VECTOR,

redisinsight/ui/src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export * from './modules'
2727
export * from './events'
2828
export * from './telemetry'
2929
export * from './oauth'
30+
export * from './redisearch'
3031

3132
export {
3233
Maybe,
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { REDISEARCH_MODULES } from 'uiSrc/slices/interfaces'
2+
import { isVersionHigherOrEquals } from 'uiSrc/utils'
3+
import {
4+
REDISEARCH_GEOSHAPE_SEMANTIC_VERSION,
5+
REDISEARCH_GEOSHAPE_VERSION,
6+
} from 'uiSrc/constants'
7+
import { FIELD_TYPE_OPTIONS, FieldTypes } from 'uiSrc/pages/browser/components/create-redisearch-index/constants'
8+
import { AdditionalRedisModule } from 'apiSrc/modules/database/models/additional.redis.module'
9+
10+
const isGeoshapeOptionAvailable = (modules: AdditionalRedisModule[]): boolean =>
11+
modules?.some(({ name, semanticVersion, version }) =>
12+
REDISEARCH_MODULES
13+
.some((search) => (
14+
name === search && (
15+
isVersionHigherOrEquals(semanticVersion, REDISEARCH_GEOSHAPE_SEMANTIC_VERSION)
16+
|| (version && version >= REDISEARCH_GEOSHAPE_VERSION)
17+
))))
18+
19+
export const getFieldTypeOptions = (modules: AdditionalRedisModule[] = []) => FIELD_TYPE_OPTIONS
20+
.filter((option) => option.value !== FieldTypes.GEOSHAPE || isGeoshapeOptionAvailable(modules))
21+
.map(({ value, text }) => ({
22+
value,
23+
inputDisplay: text,
24+
}))

redisinsight/ui/src/utils/tests/modules.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ const getOutputForReJSONAvailable: any[] = [
7171
[['1', 'json', RedisDefaultModules.SearchLight, RedisDefaultModules.ReJSON].map(nameToModule), true],
7272
]
7373

74-
describe('isRedisearchAvailable', () => {
74+
describe('isContainJSONModule', () => {
7575
it.each(getOutputForReJSONAvailable)('for input: %s (reply), should be output: %s',
7676
(reply, expected) => {
7777
const result = isContainJSONModule(reply)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { getFieldTypeOptions } from 'uiSrc/utils'
2+
import { RedisDefaultModules } from 'uiSrc/slices/interfaces'
3+
import { FIELD_TYPE_OPTIONS, FieldTypes } from
4+
'uiSrc/pages/browser/components/create-redisearch-index/constants'
5+
6+
const nameAndVersionToModule = ([name, semanticVersion, version]: any[]) => (
7+
{ name, semanticVersion, version }
8+
)
9+
10+
const ALL_OPTIONS = FIELD_TYPE_OPTIONS.map(({ value, text }) => ({
11+
value,
12+
inputDisplay: text,
13+
}))
14+
15+
const WITHOUT_GEOSHAPE_OPTIONS = ALL_OPTIONS.filter(({ value }) => value !== FieldTypes.GEOSHAPE)
16+
17+
const getFieldTypeOptionsTests: any[] = [
18+
[[['1', '2.8.4'], [RedisDefaultModules.Search, '2.8.4']].map(nameAndVersionToModule), ALL_OPTIONS],
19+
[[['1', '2.8.4'], [RedisDefaultModules.Search, '2.8.3']].map(nameAndVersionToModule), WITHOUT_GEOSHAPE_OPTIONS],
20+
[[['1', '2.8.3'], [RedisDefaultModules.SearchLight, '2.8.4']].map(nameAndVersionToModule), ALL_OPTIONS],
21+
[[['1', '2.8.4'], [RedisDefaultModules.SearchLight, '2.8.3']].map(nameAndVersionToModule), WITHOUT_GEOSHAPE_OPTIONS],
22+
[[['1', '2.8.3'], [RedisDefaultModules.FT, '2.8.4']].map(nameAndVersionToModule), ALL_OPTIONS],
23+
[[['1', '2.8.4'], [RedisDefaultModules.FT, '2.8.3']].map(nameAndVersionToModule), WITHOUT_GEOSHAPE_OPTIONS],
24+
[[['1', '2.8.3'], [RedisDefaultModules.FTL, '2.8.4']].map(nameAndVersionToModule), ALL_OPTIONS],
25+
[[['1', '2.8.4'], [RedisDefaultModules.FTL, '2.8.3']].map(nameAndVersionToModule), WITHOUT_GEOSHAPE_OPTIONS],
26+
[[['1', '2.8.4'], [RedisDefaultModules.Gears, '2.8.4']].map(nameAndVersionToModule), WITHOUT_GEOSHAPE_OPTIONS],
27+
[[['1', '2.8.4'], [RedisDefaultModules.Search, undefined, 20804]].map(nameAndVersionToModule), ALL_OPTIONS],
28+
[[['1', '2.8.4'], [RedisDefaultModules.Search, undefined, 20803]].map(nameAndVersionToModule), WITHOUT_GEOSHAPE_OPTIONS],
29+
[[['1', '2.8.4'], [RedisDefaultModules.SearchLight, undefined, 20804]].map(nameAndVersionToModule), ALL_OPTIONS],
30+
[[['1', '2.8.4'], [RedisDefaultModules.SearchLight, undefined, 20803]].map(nameAndVersionToModule), WITHOUT_GEOSHAPE_OPTIONS],
31+
[[['1', '2.8.4'], [RedisDefaultModules.SearchLight, undefined, 20804]].map(nameAndVersionToModule), ALL_OPTIONS],
32+
[[['1', '2.8.4'], [RedisDefaultModules.SearchLight, undefined, 20803]].map(nameAndVersionToModule), WITHOUT_GEOSHAPE_OPTIONS],
33+
[[['1', '2.8.4'], [RedisDefaultModules.FT, undefined, 20804]].map(nameAndVersionToModule), ALL_OPTIONS],
34+
[[['1', '2.8.4'], [RedisDefaultModules.FT, undefined, 20803]].map(nameAndVersionToModule), WITHOUT_GEOSHAPE_OPTIONS],
35+
[[['1', '2.8.4'], [RedisDefaultModules.FTL, undefined, 20804]].map(nameAndVersionToModule), ALL_OPTIONS],
36+
[[['1', '2.8.4'], [RedisDefaultModules.FTL, undefined, 20803]].map(nameAndVersionToModule), WITHOUT_GEOSHAPE_OPTIONS],
37+
[[['1', '2.8.4'], [RedisDefaultModules.Gears, undefined, 20804]].map(nameAndVersionToModule), WITHOUT_GEOSHAPE_OPTIONS],
38+
[[['1', '2.8.4'], [RedisDefaultModules.Gears, undefined, 20803]].map(nameAndVersionToModule), WITHOUT_GEOSHAPE_OPTIONS],
39+
[[['1', '2.8.4'], [RedisDefaultModules.FTL, '2.8.3', 20803]].map(nameAndVersionToModule), WITHOUT_GEOSHAPE_OPTIONS],
40+
[[['1', '2.8.4'], [RedisDefaultModules.FTL, '2.8.4', 20804]].map(nameAndVersionToModule), ALL_OPTIONS],
41+
]
42+
43+
describe('getFieldTypeOptions', () => {
44+
it.each(getFieldTypeOptionsTests)('for input: %s (type), should be output: %s',
45+
(type, expected) => {
46+
const result = getFieldTypeOptions(type)
47+
expect(result).toEqual(expected)
48+
})
49+
})

0 commit comments

Comments
 (0)