Skip to content

Commit 692adf1

Browse files
committed
Implements client and backend both cases.
1 parent 3d5b600 commit 692adf1

File tree

6 files changed

+148
-11
lines changed

6 files changed

+148
-11
lines changed

packages/ui/src/components/multi-select/multi-select.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ReactNode } from 'react'
1+
import { ReactNode, useEffect } from 'react'
22

33
import {
44
Button,
@@ -49,20 +49,28 @@ export const MultiSelect = <T = unknown,>({
4949
error,
5050
label
5151
}: MultiSelectProps<T>) => {
52-
const [optionsToDisplay, onOpen] = useSelectedFirstOptions(options, selectedItems)
52+
const { showSelectedFirstOnOpen, optionsToDisplay, setOptionsToDisplay } = useSelectedFirstOptions(
53+
options,
54+
selectedItems
55+
)
56+
5357
const { search, handleSearchChange } = useDebounceSearch({
5458
handleChangeSearchValue,
5559
searchValue
5660
})
5761

62+
useEffect(() => {
63+
setOptionsToDisplay(options)
64+
}, [search, options, setOptionsToDisplay])
65+
5866
return (
5967
<ControlGroup className={className}>
6068
{!!label && (
6169
<Label className="mb-2" htmlFor={''}>
6270
{label}
6371
</Label>
6472
)}
65-
<DropdownMenu.Root onOpenChange={onOpen}>
73+
<DropdownMenu.Root onOpenChange={showSelectedFirstOnOpen}>
6674
<DropdownMenu.Trigger className="data-[state=open]:border-cn-borders-8 border-cn-borders-2 bg-cn-background-2 flex h-9 w-full items-center justify-between rounded border px-3 transition-colors">
6775
{placeholder}
6876
<Icon name="chevron-down" className="chevron-down ml-auto" size={12} />

packages/ui/src/components/multi-select/use-selected-first-options.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1-
import { useCallback, useMemo, useState } from 'react'
1+
import { Dispatch, SetStateAction, useCallback, useMemo, useState } from 'react'
22

33
import { MultiSelectOptionType } from '@/components'
44

5+
type UseSelectedFirstOptionsReturnType<TOption> = {
6+
optionsToDisplay: MultiSelectOptionType<TOption>[]
7+
setOptionsToDisplay: Dispatch<SetStateAction<MultiSelectOptionType<TOption>[]>>
8+
showSelectedFirstOnOpen: (open: boolean) => void
9+
}
10+
511
export const useSelectedFirstOptions = <TOption>(
612
options: MultiSelectOptionType<TOption>[],
713
selectedItems: MultiSelectOptionType<Partial<TOption>>[]
8-
): readonly [MultiSelectOptionType<TOption>[], (open: boolean) => void] => {
14+
): UseSelectedFirstOptionsReturnType<TOption> => {
915
const [isOpen, setIsOpen] = useState(false)
1016
const [optionsToDisplay, setOptionsToDisplay] = useState<MultiSelectOptionType<TOption>[]>(options)
1117
const selectedIds = useMemo(() => new Set(selectedItems.map(item => item.id)), [selectedItems])
1218

13-
const onOpen = useCallback(
19+
const showSelectedFirstOnOpen = useCallback(
1420
open => {
1521
if (open) {
1622
const selectedFirst = [...options].sort((a, b) => {
@@ -30,5 +36,9 @@ export const useSelectedFirstOptions = <TOption>(
3036
[selectedIds, options]
3137
)
3238

33-
return [isOpen ? optionsToDisplay : options, onOpen] as const
39+
return {
40+
optionsToDisplay: isOpen ? optionsToDisplay : options,
41+
setOptionsToDisplay,
42+
showSelectedFirstOnOpen
43+
}
3444
}

packages/ui/src/views/delegates/delegate-selector/delegate-selector-form.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
import { SandboxLayout, TranslationStore } from '@/views'
1818
import { zodResolver } from '@hookform/resolvers/zod'
1919
import { RadioOption, RadioSelect } from '@views/components/RadioSelect'
20+
import { useBackendTagsSearch, useClientTagsSearch } from '@views/delegates/hooks'
2021
import { z } from 'zod'
2122

2223
import { DelegateConnectivityList } from '../components/delegate-connectivity-list'
@@ -78,7 +79,21 @@ export const DelegateSelectorForm = (props: DelegateSelectorFormProps): JSX.Elem
7879
disableAnyDelegate
7980
} = props
8081
const { t } = useTranslationStore()
81-
const [searchTag, setSearchTag] = useState('')
82+
const { searchTag, handleSearchChange, filteredTags } = useClientTagsSearch(tagsList, preSelectedTags)
83+
// const { searchTag, handleSearchChange, filteredTags } = useBackendTagsSearch(tagsList, preSelectedTags)
84+
85+
// [BACKEND-SIDE] If there is no tags but preSelectedTags, we need to show the preSelectedTags
86+
// const { searchTag, handleSearchChange, filteredTags } = useBackendTagsSearch(
87+
// [],
88+
// ['sanity-windows', 'eightfivetwoold', 'qa-automation', 'sanity']
89+
// )
90+
91+
// [CLIENT-SIDE] If there is no tags but preSelectedTags, we need to show the preSelectedTags
92+
// const { searchTag, handleSearchChange, filteredTags } = useClientTagsSearch(
93+
// [],
94+
// ['sanity-windows', 'eightfivetwoold', 'qa-automation', 'sanity']
95+
// )
96+
8297
const [matchedDelegates, setMatchedDelegates] = useState(0)
8398
const {
8499
register,
@@ -182,11 +197,11 @@ export const DelegateSelectorForm = (props: DelegateSelectorFormProps): JSX.Elem
182197
label="Tags"
183198
placeholder="Enter tags"
184199
handleChange={handleTagChange}
185-
options={tagsList.map(tag => {
200+
options={filteredTags.map(tag => {
186201
return { id: tag, label: tag }
187202
})}
188203
searchValue={searchTag}
189-
handleChangeSearchValue={setSearchTag}
204+
handleChangeSearchValue={handleSearchChange}
190205
error={errors.tags?.message?.toString()}
191206
/>
192207
</Fieldset>
@@ -202,7 +217,7 @@ export const DelegateSelectorForm = (props: DelegateSelectorFormProps): JSX.Elem
202217
</>
203218
)}
204219

205-
<div className="absolute inset-x-0 bottom-0 bg-cn-background-2 p-4 shadow-md">
220+
<div className="bg-cn-background-2 absolute inset-x-0 bottom-0 p-4 shadow-md">
206221
<ControlGroup>
207222
<ButtonGroup className="flex flex-row justify-between">
208223
<Button type="button" variant="ghost" onClick={onBack}>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './use-client-tags-search'
2+
export * from './use-backend-tags-search'
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { useCallback, useEffect, useRef, useState } from 'react'
2+
3+
import { defaultTo, isEqual } from 'lodash-es'
4+
5+
const TAGS_MOCK = [
6+
'myrunner',
7+
'macos-arm64',
8+
'west1-delegate-qa',
9+
'linux-amd64',
10+
'eightfivetwo',
11+
'automation-eks-delegate'
12+
]
13+
14+
export const useBackendTagsSearch = (tags: string[], selectedTags?: string[]) => {
15+
const [searchTag, setSearchTag] = useState<string>('')
16+
const [filteredTags, setFilteredTags] = useState<string[]>(() => defaultTo(selectedTags, []))
17+
const latestQueryRef = useRef<string>('')
18+
19+
const fetchTagsFromBackend = useCallback(
20+
(searchQuery: string): Promise<string[]> => {
21+
return new Promise(resolve => {
22+
console.log(`Fetching tags from backend... Query is ${searchQuery}`)
23+
const tagsToResolve = tags.length === 0 ? TAGS_MOCK : tags
24+
25+
setTimeout(
26+
() => {
27+
if (searchQuery === '') {
28+
resolve(tags)
29+
} else {
30+
const tagsToResolveFiltered = tagsToResolve.filter(tag =>
31+
tag.toLowerCase().includes(searchQuery.toLowerCase())
32+
)
33+
resolve(tagsToResolveFiltered)
34+
}
35+
},
36+
Math.floor(Math.random() * 3000)
37+
)
38+
})
39+
},
40+
[tags]
41+
)
42+
43+
useEffect(() => {
44+
const searchQuery = searchTag.trim()
45+
46+
if (searchQuery === latestQueryRef.current) {
47+
return
48+
}
49+
50+
latestQueryRef.current = searchQuery
51+
52+
fetchTagsFromBackend(searchQuery).then(resolvedTags => {
53+
setFilteredTags(prev => {
54+
const newFiltered = [...defaultTo(selectedTags, []), ...resolvedTags]
55+
56+
if (isEqual(prev, newFiltered)) {
57+
return prev
58+
}
59+
60+
return newFiltered
61+
})
62+
})
63+
}, [searchTag, tags, selectedTags, fetchTagsFromBackend])
64+
65+
const handleSearchChange = useCallback((value: string) => {
66+
setSearchTag(value)
67+
}, [])
68+
69+
return {
70+
searchTag,
71+
filteredTags,
72+
handleSearchChange
73+
}
74+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { useMemo, useState } from 'react'
2+
3+
import { defaultTo } from 'lodash-es'
4+
5+
export const useClientTagsSearch = (tags: string[], selectedTags?: string[]) => {
6+
const [searchTag, setSearchTag] = useState('')
7+
const filteredTags = useMemo(() => {
8+
if (!searchTag && tags.length === 0) {
9+
return defaultTo(selectedTags, [])
10+
}
11+
12+
if (!searchTag) return tags
13+
14+
const search = searchTag.toLowerCase()
15+
16+
return tags.filter(tag => tag.toLowerCase().includes(search))
17+
}, [searchTag, tags, selectedTags])
18+
19+
const handleSearchChange = (value: string) => {
20+
setSearchTag(value)
21+
}
22+
23+
return {
24+
searchTag,
25+
handleSearchChange,
26+
filteredTags
27+
}
28+
}

0 commit comments

Comments
 (0)