Skip to content

Commit c6b8e24

Browse files
committed
Implements both cases filtering: client and backend.
1 parent 08515f0 commit c6b8e24

File tree

7 files changed

+145
-14
lines changed

7 files changed

+145
-14
lines changed

apps/design-system/src/subjects/views/delegates/delegate-selector.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ const DelegateSelectorDrawer = ({ open, setOpen, preSelectedTags, onSubmit, disa
5959
<FormSeparator className="w-full" />
6060
<div className="flex">
6161
Haven&apos;t installed a delegate yet?
62-
<StyledLink className="flex flex-row items-center ml-1" variant="accent" to="#">
62+
<StyledLink className="ml-1 flex flex-row items-center" variant="accent" to="#">
6363
Install delegate <Icon name="attachment-link" className="ml-1" size={12} />
6464
</StyledLink>
6565
</div>
@@ -68,6 +68,7 @@ const DelegateSelectorDrawer = ({ open, setOpen, preSelectedTags, onSubmit, disa
6868

6969
<DelegateSelectorForm
7070
delegates={delegatesData}
71+
// tagsList={[]}
7172
tagsList={mockTagsList}
7273
useTranslationStore={useTranslationStore}
7374
isLoading={false}
@@ -76,6 +77,7 @@ const DelegateSelectorDrawer = ({ open, setOpen, preSelectedTags, onSubmit, disa
7677
isDelegateSelected={isDelegateSelected}
7778
getMatchedDelegatesCount={getMatchedDelegatesCount}
7879
preSelectedTags={preSelectedTags}
80+
// preSelectedTags={['sanity-windows', 'eightfivetwoold', 'qa-automation', 'sanity']}
7981
disableAnyDelegate={disableAnyDelegate}
8082
/>
8183
</Drawer.Content>
@@ -116,7 +118,7 @@ export const DelegateSelector = () => {
116118
onEdit={() => setOpenA(true)}
117119
onClear={() => setTagsA([])}
118120
renderValue={tag => tag}
119-
className="max-w-xs mb-8"
121+
className="mb-8 max-w-xs"
120122
/>
121123

122124
<DelegateSelectorDrawer open={openA} setOpen={setOpenA} preSelectedTags={tagsA} onSubmit={handleSubmitA} />
@@ -130,7 +132,7 @@ export const DelegateSelector = () => {
130132
onEdit={() => setOpenB(true)}
131133
onClear={() => setTagsB([])}
132134
renderValue={tag => tag}
133-
className="max-w-xs mb-8"
135+
className="mb-8 max-w-xs"
134136
/>
135137

136138
<DelegateSelectorDrawer

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: 7 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,9 @@ 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+
8285
const [matchedDelegates, setMatchedDelegates] = useState(0)
8386
const {
8487
register,
@@ -182,11 +185,11 @@ export const DelegateSelectorForm = (props: DelegateSelectorFormProps): JSX.Elem
182185
label="Tags"
183186
placeholder="Enter tags"
184187
handleChange={handleTagChange}
185-
options={tagsList.map(tag => {
188+
options={filteredTags.map(tag => {
186189
return { id: tag, label: tag }
187190
})}
188191
searchValue={searchTag}
189-
handleChangeSearchValue={setSearchTag}
192+
handleChangeSearchValue={handleSearchChange}
190193
error={errors.tags?.message?.toString()}
191194
/>
192195
</Fieldset>
@@ -202,7 +205,7 @@ export const DelegateSelectorForm = (props: DelegateSelectorFormProps): JSX.Elem
202205
</>
203206
)}
204207

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