Skip to content

Commit a7a5798

Browse files
committed
fix: state management for selectable filters in plugin
1 parent 80bfa4b commit a7a5798

File tree

9 files changed

+111
-72
lines changed

9 files changed

+111
-72
lines changed

src/Common/Constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export const ROUTES = {
9292
CONFIG_CD_PIPELINE: 'config/cd-pipeline',
9393
MODULE_CONFIGURED: 'module/config',
9494
RESOURCE_HISTORY_DEPLOYMENT: 'resource/history/deployment',
95-
PLUGIN_GLOBAL_DETAIL_V2: 'plugin/global/detail/v2',
95+
PLUGIN_GLOBAL_LIST_DETAIL_V2: 'plugin/global/list/detail/v2',
9696
PLUGIN_GLOBAL_LIST_V2: 'plugin/global/list/v2',
9797
PLUGIN_GLOBAL_LIST_TAGS: 'plugin/global/list/tags',
9898
}

src/Shared/Components/Plugin/PluginCard.tsx

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Checkbox, CHECKBOX_VALUE, noop } from '../../../Common'
1+
import { Checkbox, CHECKBOX_VALUE } from '../../../Common'
22
import { ImageWithFallback } from '../ImageWithFallback'
33
import { PluginCardProps } from './types'
44
import { ReactComponent as ICLegoBlock } from '../../../Assets/Icon/ic-lego-block.svg'
@@ -9,6 +9,7 @@ const PluginCard = ({
99
handlePluginSelection,
1010
parentPluginId,
1111
isSelected,
12+
showCardBorder,
1213
}: PluginCardProps) => {
1314
const latestPluginId = pluginDataStore.parentPluginStore[parentPluginId].latestVersionId
1415
const { icon, name, description, tags, pluginVersion, updatedBy } =
@@ -19,26 +20,33 @@ const PluginCard = ({
1920
}
2021

2122
return (
22-
<div className="p-12 flexbox dc__gap-16 dc__tab-focus" role="button" tabIndex={0} onClick={handleSelection}>
23-
{isSelectable ? (
24-
<div className="dc__no-shrink icon-dim-40 p-8">
23+
<div
24+
className={`p-12 flexbox dc__gap-16 dc__tab-focus dc__visible-hover dc__visible-hover--parent ${showCardBorder ? 'dc__border br-4 dc__hover-n50' : ''}`}
25+
role="button"
26+
tabIndex={0}
27+
onClick={handleSelection}
28+
>
29+
{isSelectable && (
30+
<div className={`dc__no-shrink icon-dim-40 p-8 ${!isSelected ? 'dc__visible-hover--child' : ''}`}>
2531
<Checkbox
2632
isChecked={isSelected}
27-
onChange={noop}
33+
onChange={handleSelection}
2834
rootClassName="icon-dim-40 p-8 w-100 mb-0 dc__no-shrink"
2935
value={CHECKBOX_VALUE.CHECKED}
3036
/>
3137
</div>
32-
) : (
33-
// TODO: Test multiple cards with fallback since has if in LegoBlock
38+
)}
39+
40+
{/* TODO: Test multiple cards with fallback since has if in LegoBlock */}
41+
{!isSelected && (
3442
<ImageWithFallback
35-
fallbackImage={<ICLegoBlock className="dc__no-shrink icon-dim-40" />}
43+
fallbackImage={<ICLegoBlock className="dc__no-shrink dc__visible-hover--hide-child icon-dim-40" />}
3644
imageProps={{
3745
src: icon,
3846
alt: `${name} logo`,
3947
width: 40,
4048
height: 40,
41-
className: 'p-4 dc__no-shrink',
49+
className: 'p-4 dc__no-shrink dc__visible-hover--hide-child',
4250
}}
4351
/>
4452
)}
@@ -47,9 +55,9 @@ const PluginCard = ({
4755
<div className="flexbox-col dc__gap-8">
4856
<div className="flexbox-col dc__gap-4">
4957
<div className="flexbox dc__gap-4">
50-
<h4 className="m-0 dc__truncate--clamp-3 cn-9 fs-13 fw-6 lh-20">{name}</h4>
58+
<h4 className="m-0 dc__truncate cn-9 fs-13 fw-6 lh-20">{name}</h4>
5159
{!isSelectable && (
52-
<span className="dc__truncate--clamp-3 cn-7 fs-12 fw-4 lh-20">({pluginVersion})</span>
60+
<span className="dc__truncate cn-7 fs-12 fw-4 lh-20">({pluginVersion})</span>
5361
)}
5462
</div>
5563

src/Shared/Components/Plugin/PluginList.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const PluginList = ({
1616
selectedPluginsMap,
1717
isSelectable,
1818
handleClearFilters,
19+
showCardBorder,
1920
}: PluginListProps) => {
2021
const { appId } = useParams<PluginListParamsType>()
2122

@@ -49,17 +50,20 @@ const PluginList = ({
4950
)
5051
}
5152

52-
// selectedPluginsMap should always be present if s
53+
// selectedPluginsMap should always be present if isSelectable is true
5354
if (showSelectedPlugins) {
55+
// TODO: Ask with product, filters and search can cause conflicting state
56+
5457
return (
5558
<>
56-
{Object.keys(selectedPluginsMap).map((pluginId) => (
59+
{Object.keys(selectedPluginsMap).map((parentPluginId) => (
5760
<PluginCard
58-
key={pluginId}
59-
parentPluginId={+pluginId}
61+
key={parentPluginId}
62+
parentPluginId={+parentPluginId}
6063
isSelectable={isSelectable}
6164
pluginDataStore={pluginDataStore}
6265
handlePluginSelection={handlePluginSelection}
66+
showCardBorder={showCardBorder}
6367
isSelected
6468
/>
6569
))}
@@ -77,6 +81,7 @@ const PluginList = ({
7781
pluginDataStore={pluginDataStore}
7882
handlePluginSelection={handlePluginSelection}
7983
isSelected={!!selectedPluginsMap[plugin.parentPluginId]}
84+
showCardBorder={showCardBorder}
8085
/>
8186
))}
8287

src/Shared/Components/Plugin/PluginListContainer.tsx

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import {
1313
PluginListFiltersType,
1414
PluginListItemType,
1515
} from './types'
16+
import { DEFAULT_PLUGIN_LIST_FILTERS } from './constants'
1617
import { ReactComponent as ICCross } from '../../../Assets/Icon/ic-cross.svg'
1718
import { ReactComponent as ICVisibility } from '../../../Assets/Icon/ic-visibility-on.svg'
18-
import { DEFAULT_PLUGIN_LIST_FILTERS } from './constants'
1919

2020
const PluginListContainer = ({
2121
availableTags,
@@ -34,14 +34,19 @@ const PluginListContainer = ({
3434
handleParentTotalCount,
3535
parentFilters,
3636
handleUpdateParentFilters,
37+
rootClassName,
38+
showCardBorder,
3739
}: Readonly<PluginListContainerProps>) => {
3840
const { appId } = useParams<PluginListParamsType>()
3941

4042
const [pluginList, setPluginList] = useState<PluginListItemType[]>(parentPluginList || [])
4143
const [totalCount, setTotalCount] = useState<number>(parentTotalCount || 0)
44+
// TODO: Maybe structuredClone is not required
4245
const [filters, setFilters] = useState<PluginListFiltersType>(
4346
parentFilters || structuredClone(DEFAULT_PLUGIN_LIST_FILTERS),
4447
)
48+
// Have to make a state to trigger clear filters, since filters are not on URL
49+
const [clearFiltersTrigger, setClearFiltersTrigger] = useState<boolean>(false)
4550

4651
const handlePluginListUpdate = (updatedPluginList: PluginListItemType[]) => {
4752
setPluginList(updatedPluginList)
@@ -129,12 +134,22 @@ const PluginListContainer = ({
129134
}
130135
}, [isLoadingPluginData, pluginData, pluginDataError])
131136

137+
const handlePersistFiltersChange = () => {
138+
// Doing this since in case of persistence of filter we have should run as plugin length
139+
if (persistFilters) {
140+
handlePluginListUpdate([])
141+
}
142+
}
143+
132144
const handleClearFilters = () => {
133145
handleUpdateFilters({
134146
searchKey: '',
135147
selectedTags: [],
136148
showSelectedPlugins: false,
137149
})
150+
151+
setClearFiltersTrigger((prev) => !prev)
152+
handlePersistFiltersChange()
138153
}
139154

140155
const handleSearch = (searchText: string) => {
@@ -143,9 +158,7 @@ const PluginListContainer = ({
143158
searchKey: searchText,
144159
})
145160

146-
if (persistFilters) {
147-
handlePluginListUpdate([])
148-
}
161+
handlePersistFiltersChange()
149162
}
150163

151164
const handleUpdateSelectedTags = (updatedTags: string[]) => {
@@ -154,9 +167,16 @@ const PluginListContainer = ({
154167
selectedTags: updatedTags,
155168
})
156169

157-
if (persistFilters) {
158-
handlePluginListUpdate([])
159-
}
170+
handlePersistFiltersChange()
171+
}
172+
173+
const handleRemoveSelectedTag = (filterConfig: Pick<PluginListFiltersType, 'selectedTags'>) => {
174+
handleUpdateFilters({
175+
...filters,
176+
selectedTags: filterConfig.selectedTags,
177+
})
178+
179+
handlePersistFiltersChange()
160180
}
161181

162182
const handleShowSelectedPlugins = () => {
@@ -173,24 +193,26 @@ const PluginListContainer = ({
173193
})
174194
}
175195

176-
const handleRemoveSelectedTag = (filterConfig: Pick<PluginListFiltersType, 'selectedTags'>) => {
177-
handleUpdateFilters({
178-
...filters,
179-
selectedTags: filterConfig.selectedTags,
180-
})
196+
const handlePluginSelectionWrapper: PluginListProps['handlePluginSelection'] = (parentPluginId) => {
197+
const isCurrentPluginSelected = isSelectable && selectedPluginsMap[parentPluginId]
198+
if (isCurrentPluginSelected && Object.keys(selectedPluginsMap).length === 1) {
199+
handleHideSelectedPlugins()
200+
}
201+
202+
handlePluginSelection(parentPluginId)
181203
}
182204

183-
// TODO: Be remember to turn showSelectedPluginFilter as false if count becomes 0
184205
const showSelectedPluginFilter = useMemo(
185206
() => isSelectable && selectedPluginsMap && Object.keys(selectedPluginsMap).length > 0,
186207
[selectedPluginsMap, isSelectable],
187208
)
188209

189210
return (
190-
<div className="flexbox-col dc__overflow-scroll w-100 h-100">
211+
<div className={`flexbox-col w-100 ${rootClassName}`}>
191212
{/* Filters section */}
192-
<div className="w-100 flexbox dc__gap-12 py-12">
213+
<div className="w-100 flexbox dc__gap-12 py-12 dc__position-sticky dc__top-0 bcn-0 dc__zi-1">
193214
<SearchBar
215+
key={`search-bar-key-${Number(clearFiltersTrigger)}`}
194216
containerClassName="flex-grow-1"
195217
handleEnter={handleSearch}
196218
inputProps={{
@@ -199,6 +221,7 @@ const PluginListContainer = ({
199221
/>
200222

201223
<PluginTagSelect
224+
key={`plugin-tag-select-key-${Number(clearFiltersTrigger)}`}
202225
availableTags={availableTags}
203226
handleUpdateSelectedTags={handleUpdateSelectedTags}
204227
selectedTags={selectedTags}
@@ -209,8 +232,8 @@ const PluginListContainer = ({
209232

210233
{showSelectedPluginFilter && (
211234
<button
212-
className={`py-6 px-8 dc__gap-12 flex dc__no-border dc__no-background dc__outline-none-imp dc__tab-focus dc__tab-focus ${
213-
showSelectedPlugins ? 'bc-n50 dc__border-n1' : ''
235+
className={`py-6 px-8 dc__gap-12 flex dc__outline-none-imp dc__tab-focus dc__tab-focus ${
236+
showSelectedPlugins ? 'bc-n50 dc__border-n1' : 'dc__no-border dc__no-background'
214237
}`}
215238
data-testid="view-only-selected"
216239
type="button"
@@ -253,10 +276,11 @@ const PluginListContainer = ({
253276
totalCount={totalCount}
254277
handleDataUpdateForPluginResponse={handleDataUpdateForPluginResponse}
255278
filters={filters}
256-
handlePluginSelection={handlePluginSelection}
279+
handlePluginSelection={handlePluginSelectionWrapper}
257280
selectedPluginsMap={selectedPluginsMap}
258281
isSelectable={isSelectable}
259282
handleClearFilters={handleClearFilters}
283+
showCardBorder={showCardBorder}
260284
/>
261285
</APIResponseHandler>
262286
</div>

src/Shared/Components/Plugin/PluginTagSelect.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ const PluginTagSelect = ({
6666
closeMenuOnSelect={false}
6767
controlShouldRenderValue={false}
6868
hideSelectedOptions={false}
69-
maxMenuHeight={300}
69+
blurInputOnSelect={false}
70+
maxMenuHeight={250}
7071
onMenuClose={handleMenuClose}
7172
inputId="plugin-tag-select"
7273
className="w-200"

src/Shared/Components/Plugin/service.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { get, getUrlWithSearchParams, ResponseType, ROUTES, showError } from '../../../Common'
2+
import { stringComparatorBySortOrder } from '../../Helpers'
23
import {
34
GetPluginListPayloadType,
45
GetPluginStoreDataReturnType,
@@ -13,16 +14,15 @@ export const getPluginsDetail = async ({
1314
appId,
1415
parentPluginId,
1516
pluginId,
16-
// TODO: Can make it true by default
17-
fetchLatestVersionDetails,
1817
}: PluginDetailPayloadType): Promise<PluginDetailDTO> => {
1918
try {
19+
// TODO: Add type for payload as well and return parsed data itself
20+
// Can parse the response here itself
2021
const { result } = (await get(
21-
getUrlWithSearchParams(ROUTES.PLUGIN_GLOBAL_DETAIL, {
22+
getUrlWithSearchParams(ROUTES.PLUGIN_GLOBAL_LIST_DETAIL_V2, {
2223
appId,
2324
parentPluginId,
2425
pluginId,
25-
fetchLatestVersionDetails,
2626
}),
2727
)) as ResponseType<PluginDetailDTO>
2828
return result
@@ -34,17 +34,16 @@ export const getPluginsDetail = async ({
3434

3535
export const getPluginStoreData = async ({
3636
searchKey,
37-
offset = 0,
3837
selectedTags,
3938
appId,
39+
offset = 0,
4040
}: GetPluginStoreDataServiceParamsType): Promise<GetPluginStoreDataReturnType> => {
4141
try {
4242
const payload: GetPluginListPayloadType = {
4343
searchKey,
4444
offset,
4545
appId,
4646
size: 20,
47-
fetchLatestVersionDetails: true,
4847
tag: selectedTags,
4948
}
5049
const { result } = (await get(
@@ -62,13 +61,15 @@ export const getPluginStoreData = async ({
6261
}
6362
}
6463

65-
export const getAvailablePluginTags = async (): Promise<string[]> => {
64+
export const getAvailablePluginTags = async (appId: number): Promise<string[]> => {
6665
try {
67-
const { result } = (await get(ROUTES.PLUGIN_GLOBAL_LIST_TAGS)) as ResponseType<PluginTagNamesDTO>
66+
const { result } = (await get(
67+
getUrlWithSearchParams(ROUTES.PLUGIN_GLOBAL_LIST_TAGS, { appId }),
68+
)) as ResponseType<PluginTagNamesDTO>
6869
if (!result?.tagNames) {
6970
return []
7071
}
71-
return result.tagNames
72+
return result.tagNames.sort(stringComparatorBySortOrder)
7273
} catch (error) {
7374
showError(error)
7475
throw error

src/Shared/Components/Plugin/types.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ export interface PluginDetailDTO {
5252

5353
export interface PluginDetailPayloadType {
5454
appId: number
55-
fetchLatestVersionDetails: boolean
5655
pluginId?: number[]
5756
parentPluginId?: number[]
5857
}
@@ -92,6 +91,8 @@ interface BasePluginListContainerProps {
9291
handlePluginSelection: (parentPluginId: PluginDetailType['parentPluginId']) => void
9392
pluginDataStore: PluginDataStoreType
9493
handlePluginDataStoreUpdate: (pluginStore: PluginDataStoreType) => void
94+
rootClassName?: string
95+
showCardBorder?: boolean
9596
}
9697

9798
type PluginListType =
@@ -134,15 +135,14 @@ export type PluginListContainerProps = BasePluginListContainerProps &
134135
)
135136

136137
export interface GetPluginStoreDataServiceParamsType extends Pick<PluginListFiltersType, 'searchKey' | 'selectedTags'> {
137-
offset: number
138138
appId: number
139+
offset?: number
139140
}
140141

141142
export interface GetPluginListPayloadType
142143
extends Pick<GetPluginStoreDataServiceParamsType, 'searchKey' | 'offset' | 'appId'> {
143144
tag: PluginListFiltersType['selectedTags']
144145
size: number
145-
fetchLatestVersionDetails: boolean
146146
}
147147

148148
export interface GetPluginStoreDataReturnType {
@@ -165,7 +165,7 @@ export interface PluginTagSelectProps extends Pick<BasePluginListContainerProps,
165165
export interface PluginListProps
166166
extends Pick<
167167
PluginListContainerProps,
168-
'pluginDataStore' | 'handlePluginSelection' | 'selectedPluginsMap' | 'isSelectable'
168+
'pluginDataStore' | 'handlePluginSelection' | 'selectedPluginsMap' | 'isSelectable' | 'showCardBorder'
169169
> {
170170
handleDataUpdateForPluginResponse: (pluginResponse: Awaited<ReturnType<typeof getPluginStoreData>>) => void
171171
handleClearFilters: () => void
@@ -175,7 +175,7 @@ export interface PluginListProps
175175
}
176176

177177
export interface PluginCardProps
178-
extends Pick<PluginListProps, 'isSelectable' | 'pluginDataStore' | 'handlePluginSelection'> {
178+
extends Pick<PluginListProps, 'isSelectable' | 'pluginDataStore' | 'handlePluginSelection' | 'showCardBorder'> {
179179
parentPluginId: PluginDetailType['parentPluginId']
180180
isSelected: boolean
181181
}

0 commit comments

Comments
 (0)