Skip to content

Commit 4474f35

Browse files
authored
Merge pull request #247 from RedisInsight/feature/RI-6514_Support_multiple_key_name_delimiter
#RI-6514 - Support multiple key name delimiters
2 parents 15d2c01 + c82b46b commit 4474f35

File tree

42 files changed

+675
-201
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+675
-201
lines changed

.eslintrc.js

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ module.exports = {
4848
code: 140,
4949
},
5050
],
51+
'jsx-quotes': ['error', 'prefer-double'],
5152
'class-methods-use-this': 'off',
5253
// https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#consistent-components-exports
5354
'react-refresh/only-export-components': ['warn'],

l10n/bundle.l10n.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"Recommended": "Recommended",
1818
"Page was not found": "Page was not found",
1919
"Settings": "Settings",
20-
"Delimiter to separate namespaces": "Delimiter to separate namespaces",
20+
"Delimiter": "Delimiter",
2121
"key(s)": "key(s)",
2222
"({0}{1} Scanned)": "({0}{1} Scanned)",
2323
"All Key Types": "All Key Types",

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@
145145
"dev:key": "cross-env RI_DATA_ROUTE=main/key vite dev",
146146
"dev:database": "cross-env RI_DATA_ROUTE=main/add_database vite dev",
147147
"dev:sidebar": "cross-env RI_DATA_ROUTE=sidebar vite dev",
148+
"dev:settings": "cross-env RI_DATA_ROUTE=main/settings vite dev",
148149
"l10n:collect": "npx @vscode/l10n-dev export -o ./l10n ./src",
149150
"watch": "tsc -watch -p ./",
150151
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",

src/webviews/src/components/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ export { NoDatabases } from './no-databases/NoDatabases'
1111
export { MonacoLanguages } from './monaco-languages/MonacoLanguages'
1212
export { UploadFile } from './upload-file/UploadFile'
1313
export { SuperSelect } from './super-select/SuperSelect'
14+
export { MultiSelect } from './multi-select/MultiSelect'
1415
export { SuperSelectRemovableOption } from './super-select/components/removable-option/RemovableOption'
1516
export { AutoRefresh } from './auto-refresh/AutoRefresh'
1617
export * from './database-form'
1718
export * from './consents-option'
1819
export * from './consents-privacy'
1920

2021
export type { SuperSelectOption } from './super-select/SuperSelect'
22+
export type { MultiSelectOption } from './multi-select/MultiSelect'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from 'react'
2+
import { instance, mock } from 'ts-mockito'
3+
4+
import { render, constants } from 'testSrc/helpers'
5+
import { MultiSelect, Props } from './MultiSelect'
6+
7+
const mockedProps = mock<Props>()
8+
9+
describe('MultiSelect', () => {
10+
it('should render', async () => {
11+
expect(render(
12+
<MultiSelect {...instance(mockedProps)} options={constants.SUPER_SELECT_OPTIONS} />),
13+
).toBeTruthy()
14+
})
15+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React, { FC } from 'react'
2+
import cx from 'classnames'
3+
import Select, { CreatableProps } from 'react-select/creatable'
4+
import { GroupBase } from 'react-select'
5+
6+
import { Maybe } from 'uiSrc/interfaces'
7+
import styles from './styles.module.scss'
8+
9+
export interface MultiSelectOption {
10+
label: string
11+
value: string
12+
testid?: string
13+
}
14+
15+
export interface Props extends CreatableProps<MultiSelectOption, true, GroupBase<MultiSelectOption>> {
16+
options?: Maybe<MultiSelectOption[]>
17+
selectedOption?: Maybe<MultiSelectOption>
18+
containerClassName?: string
19+
itemClassName?: string
20+
testid?: string
21+
}
22+
23+
const components = {
24+
DropdownIndicator: null,
25+
}
26+
27+
const MultiSelect: FC<Props> = (props) => {
28+
const {
29+
containerClassName,
30+
testid,
31+
} = props
32+
33+
return (
34+
<div className={cx(styles.container, containerClassName)}>
35+
<Select<MultiSelectOption, true>
36+
{...props}
37+
isMulti
38+
isClearable
39+
components={components}
40+
menuIsOpen={false}
41+
inputId={testid}
42+
classNames={{
43+
container: () => styles.selectContainer,
44+
control: () => styles.control,
45+
multiValue: () => styles.multiValue,
46+
multiValueRemove: () => styles.multiValueRemove,
47+
menu: () => '!hidden',
48+
input: () => styles.input,
49+
indicatorsContainer: () => '!hidden',
50+
indicatorSeparator: () => '!hidden',
51+
}}
52+
/>
53+
</div>
54+
)
55+
}
56+
57+
export { MultiSelect }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
.container {
2+
@apply min-w-[210px] #{!important};
3+
}
4+
5+
.selectContainer {
6+
@apply min-h-8 #{!important};
7+
}
8+
9+
.control {
10+
@apply bg-vscode-dropdown-background border-solid border border-vscode-input-border rounded-none min-h-8 max-h-40 cursor-pointer overflow-auto #{!important};
11+
12+
scroll-padding-bottom: 8px;
13+
}
14+
15+
.multiValue {
16+
background-color: var(--vscode-banner-background) !important;
17+
18+
div {
19+
color: var(--vscode-banner-iconForeground) !important;
20+
}
21+
}
22+
23+
.multiValueRemove {
24+
@apply h-[14px] w-[14px] pl-[2px] mt-1 ml-0.5 mr-1 #{!important};
25+
border-radius: 50% !important;
26+
background-color: var(--vscode-banner-iconForeground) !important;
27+
transition: transform 0.1s ease;
28+
29+
&:hover {
30+
transform: translateY(-1px);
31+
}
32+
33+
svg {
34+
@apply h-[10px] w-[10px] min-w-[10px] #{!important};
35+
path {
36+
fill: var(--vscode-inlineChat-background) #{!important};
37+
}
38+
}
39+
}
40+
41+
.input {
42+
@apply text-vscode-foreground max-w-full truncate #{!important};
43+
}
44+

src/webviews/src/components/super-select/components/removable-option/RemovableOption.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ const SuperSelectRemovableOption = (props: Props) => {
4747
text={l10n.t('will be removed from Redis for VS Code.')}
4848
item={(options[i] as SuperSelectOption).value}
4949
suffix={suffix}
50-
triggerClassName='absolute right-2.5'
51-
position='right center'
50+
triggerClassName="absolute right-2.5"
51+
position="right center"
5252
deleting={activeOptionId}
5353
showPopover={showPopover}
5454
handleDeleteItem={handleRemoveOption}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export enum EventKeys {
2+
ENTER = 'Enter',
3+
SPACE = ' ',
4+
ESCAPE = 'Escape',
5+
TAB = 'Tab',
6+
}

src/webviews/src/constants/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export * from './core/apiErrors'
44
export * from './core/app'
55
export * from './core/storage'
66
export * from './core/commands'
7+
export * from './core/eventKeys'
78
export * from './connections/databases'
89
export * from './keys/types'
910
export * from './keys/tree'

src/webviews/src/index.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Config } from 'uiSrc/modules'
1212
import { AppRoutes } from 'uiSrc/Routes'
1313
import { PostMessage, SelectKeyAction, SetDatabaseAction } from 'uiSrc/interfaces'
1414
import { VscodeMessageAction } from 'uiSrc/constants'
15+
import { migrateLocalStorageData } from 'uiSrc/services'
1516
import { useAppInfoStore } from './store/hooks/use-app-info-store/useAppInfoStore'
1617
import {
1718
processCliAction,
@@ -26,6 +27,8 @@ import { MonacoLanguages } from './components'
2627
import './styles/main.scss'
2728
import '../vscode.css'
2829

30+
migrateLocalStorageData()
31+
2932
document.addEventListener('DOMContentLoaded', () => {
3033
window.addEventListener('message', handleMessage)
3134

@@ -71,7 +74,7 @@ document.addEventListener('DOMContentLoaded', () => {
7174
useAppInfoStore.getState().updateUserConfigSettingsSuccess(message.data)
7275
break
7376
case VscodeMessageAction.UpdateSettingsDelimiter:
74-
useAppInfoStore.getState().setDelimiter(message.data)
77+
useAppInfoStore.getState().setDelimiters(message.data)
7578
break
7679

7780
// CLI

src/webviews/src/interfaces/vscode/api.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ export interface UpdateSettingsAction {
9191

9292
export interface UpdateSettingsDelimiterAction {
9393
action: VscodeMessageAction.UpdateSettingsDelimiter
94-
data: string
94+
data: string[]
9595
}
9696

9797
export interface SaveAppInfoAction {

src/webviews/src/modules/add-key/components/AddKeyList/AddKeyList.tsx

+10-10
Original file line numberDiff line numberDiff line change
@@ -91,17 +91,17 @@ export const AddKeyList = (props: Props) => {
9191
<>
9292
<form onSubmit={onFormSubmit} className="key-footer-items-container pl-0 h-full">
9393
<h3 className="font-bold uppercase pb-3">{l10n.t('Element')}</h3>
94-
<div className="w-1/3 mr-2 mb-3">
95-
<Select
96-
position="below"
97-
options={optionsDestinations}
98-
containerClassName={styles.select}
99-
itemClassName={styles.selectOption}
100-
idSelected={destination}
101-
onChange={(value) => setDestination(value as ListElementDestination)}
102-
testid="destination-select"
94+
<div className="w-1/3 mr-2 mb-3">
95+
<Select
96+
position="below"
97+
options={optionsDestinations}
98+
containerClassName={styles.select}
99+
itemClassName={styles.selectOption}
100+
idSelected={destination}
101+
onChange={(value) => setDestination(value as ListElementDestination)}
102+
testid="destination-select"
103103
/>
104-
</div>
104+
</div>
105105
{elements.map((item, index) => (
106106
<div key={index}>
107107
<div className="flex items-center mb-3">

src/webviews/src/modules/key-details-header/KeyDetailsHeader.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ const KeyDetailsHeader = ({
123123
<KeyDetailsHeaderTTL onEditTTL={handleEditTTL} />
124124
<div className="flex ml-auto">
125125
<div className={styles.subtitleActionBtns}>
126-
<Actions width={width} key='auto-refresh'>
126+
<Actions width={width} key="auto-refresh">
127127
<AutoRefresh
128128
postfix={type}
129129
disabled={refreshing || refreshDisabled}

src/webviews/src/modules/keys-tree/KeysTree.tsx

+17-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useEffect, useState } from 'react'
22
import cx from 'classnames'
3-
import { isString, isUndefined } from 'lodash'
3+
import { isUndefined, escapeRegExp } from 'lodash'
44

55
import { KeyInfo, Nullable, RedisString } from 'uiSrc/interfaces'
66
import { AllKeyTypes, VscodeMessageAction } from 'uiSrc/constants'
@@ -26,12 +26,14 @@ export interface Props {
2626
}
2727

2828
export const KeysTree = ({ database }: Props) => {
29-
const delimiter = useAppInfoStore((state) => state.delimiter)
29+
const delimiters = useAppInfoStore((state) => state.delimiters)
3030
const openNodes = useContextInContext((state) => state.keys.tree.openNodes)
3131
const sorting = useContextInContext((state) => state.dbConfig.treeViewSort)
3232

33-
const selectedKeyName = useSelectedKeyStore((state) => state.data?.nameString) || ''
34-
const selectedKey = useSelectedKeyStore((state) => state.data?.name) || null
33+
const { selectedKeyName, selectedKey } = useSelectedKeyStore((state) => ({
34+
selectedKeyName: state.data?.nameString || '',
35+
selectedKey: state.data?.name || null,
36+
}))
3537

3638
const keysState = useKeysInContext((state) => state.data)
3739
const loading = useKeysInContext((state) => state.loading)
@@ -45,6 +47,11 @@ export const KeysTree = ({ database }: Props) => {
4547
const [firstDataLoaded, setFirstDataLoaded] = useState<boolean>(!!keysState.keys?.length)
4648
const [items, setItems] = useState<KeyInfo[]>(parseKeyNames(keysState.keys ?? []))
4749

50+
// escape regexp symbols and join and transform to regexp
51+
const delimiterPattern = delimiters
52+
.map(escapeRegExp)
53+
.join('|')
54+
4855
useEffect(() => {
4956
if (!firstDataLoaded) {
5057
keysApi.fetchPatternKeysAction()
@@ -58,9 +65,9 @@ export const KeysTree = ({ database }: Props) => {
5865

5966
// open all parents for selected key
6067
const openSelectedKey = (selectedKeyName: Nullable<string> = '') => {
61-
if (selectedKeyName && isString(selectedKeyName)) {
62-
const parts = selectedKeyName?.split(delimiter)
63-
const parents = parts.map((_, index) => parts.slice(0, index + 1).join(delimiter) + delimiter)
68+
if (selectedKeyName) {
69+
const parts = selectedKeyName?.split(delimiterPattern)
70+
const parents = parts.map((_, index) => parts.slice(0, index + 1).join(delimiterPattern) + delimiterPattern)
6471

6572
// remove key name from parents
6673
parents.pop()
@@ -80,7 +87,7 @@ export const KeysTree = ({ database }: Props) => {
8087
useEffect(() => {
8188
setFirstDataLoaded(true)
8289
setItems(parseKeyNames(keysState.keys))
83-
}, [sorting, delimiter, keysState.lastRefreshTime])
90+
}, [sorting, delimiterPattern, keysState.lastRefreshTime])
8491

8592
useEffect(() => {
8693
openSelectedKey(selectedKeyName)
@@ -167,7 +174,8 @@ export const KeysTree = ({ database }: Props) => {
167174
>
168175
<VirtualTree
169176
items={items}
170-
delimiter={delimiter}
177+
delimiters={delimiters}
178+
delimiterPattern={delimiterPattern}
171179
sorting={sorting}
172180
database={database}
173181
statusSelected={selectedKeyName}

src/webviews/src/modules/keys-tree/components/key-row-name/KeyRowName.tsx

+5-3
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,19 @@ export const KeyRowName = (props: Props) => {
2929

3030
return (
3131
<Tooltip
32+
repositionOnResize
33+
keepTooltipInside={false}
34+
position="bottom center"
3235
title={nameTooltipTitle}
3336
content={nameTooltipContent}
3437
mouseEnterDelay={300}
35-
className={cx(styles.keyNameContainer)}
3638
>
37-
<>
39+
<div className="flex flex-row truncate">
3840
<VscKey className={cx(styles.icon)} />
3941
<div className={cx(styles.keyName)} data-testid={`key-${shortString}`}>
4042
{nameContent}
4143
</div>
42-
</>
44+
</div>
4345
</Tooltip>
4446
)
4547
}

src/webviews/src/modules/keys-tree/components/key-row-name/styles.module.scss

-4
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,6 @@
33
margin-top: 7px;
44
}
55

6-
.keyNameContainer {
7-
@apply flex truncate h-full;
8-
}
9-
106
.keyName {
117
@apply inline-block truncate;
128
padding-left: 10px;

0 commit comments

Comments
 (0)