Skip to content

Commit ce67571

Browse files
committed
feat: return parsed pluginData from getPluginDetail service
1 parent 123c7ea commit ce67571

File tree

6 files changed

+125
-85
lines changed

6 files changed

+125
-85
lines changed

src/Shared/Components/Plugin/PluginTagSelect.tsx

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { useCallback, useMemo, useState } from 'react'
2-
import ReactSelect, { MenuListProps } from 'react-select'
2+
import ReactSelect, { MenuListProps, ValueContainerProps } from 'react-select'
33
import { Option, OptionType } from '../../../Common'
4-
import { LoadingIndicator, MenuListWithApplyButton } from '../ReactSelect'
4+
import { LoadingIndicator, MenuListWithApplyButton, MultiSelectValueContainer } from '../ReactSelect'
55
import { GenericSectionErrorState } from '../GenericSectionErrorState'
66
import { PluginTagSelectProps } from './types'
7-
import { PluginTagSelectValueContainer, pluginTagSelectStyles } from './utils'
7+
import { pluginTagSelectStyles } from './utils'
88

99
const PluginTagSelect = ({
1010
availableTags,
@@ -14,6 +14,7 @@ const PluginTagSelect = ({
1414
hasError,
1515
reloadTags,
1616
}: PluginTagSelectProps) => {
17+
const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false)
1718
const [localSelectedOptions, setLocalSelectedOptions] = useState<OptionType[]>(
1819
selectedTags?.map((tag) => ({ label: tag, value: tag })) ?? [],
1920
)
@@ -27,7 +28,12 @@ const PluginTagSelect = ({
2728
setLocalSelectedOptions(selectedOptions)
2829
}
2930

31+
const handleMenuOpen = () => {
32+
setIsMenuOpen(true)
33+
}
34+
3035
const handleMenuClose = () => {
36+
setIsMenuOpen(false)
3137
setLocalSelectedOptions(selectedTags?.map((tag) => ({ label: tag, value: tag })) ?? [])
3238
}
3339

@@ -38,20 +44,24 @@ const PluginTagSelect = ({
3844
return <p className="m-0 cn-7 fs-13 fw-4 lh-20 py-6 px-8">No tags found</p>
3945
}
4046

41-
// TODO: Should i add handleUpdateSelectedTags as dependency?
42-
const renderMenuList = useCallback((props: MenuListProps) => {
43-
const selectedOptions: any = props.selectProps.value
47+
const renderMenuList = useCallback((props: MenuListProps<OptionType, true>) => {
48+
const selectedOptions = props.getValue() || []
4449

45-
// TODO: Should we create function here or inside MenuListWithApplyButton?
4650
const handleApplyFilters = () => {
51+
handleMenuClose()
4752
handleUpdateSelectedTags(selectedOptions.map((option) => option.value))
4853
}
4954

5055
return <MenuListWithApplyButton {...props} handleApplyFilter={handleApplyFilters} />
5156
}, [])
5257

58+
const renderValueContainer = useCallback(
59+
(props: ValueContainerProps<OptionType, true>) => <MultiSelectValueContainer {...props} title="Category" />,
60+
[],
61+
)
62+
5363
return (
54-
<ReactSelect
64+
<ReactSelect<OptionType, true>
5565
styles={pluginTagSelectStyles}
5666
options={tagOptions}
5767
isMulti
@@ -63,8 +73,11 @@ const PluginTagSelect = ({
6373
NoOptionsMessage: renderNoOptionsMessage,
6474
MenuList: renderMenuList,
6575
Option,
66-
ValueContainer: PluginTagSelectValueContainer,
76+
ValueContainer: renderValueContainer,
6777
}}
78+
menuIsOpen={isMenuOpen}
79+
onMenuOpen={handleMenuOpen}
80+
onMenuClose={handleMenuClose}
6881
isLoading={isLoading}
6982
onChange={handleLocalSelection}
7083
backspaceRemovesValue={false}
@@ -73,7 +86,6 @@ const PluginTagSelect = ({
7386
hideSelectedOptions={false}
7487
blurInputOnSelect={false}
7588
maxMenuHeight={250}
76-
onMenuClose={handleMenuClose}
7789
placeholder="Select tags"
7890
inputId="plugin-tag-select"
7991
className="w-150"

src/Shared/Components/Plugin/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ export const DEFAULT_PLUGIN_LIST_FILTERS: PluginListFiltersType = {
99
searchKey: '',
1010
selectedTags: [],
1111
}
12+
13+
export const DEFAULT_PLUGIN_CREATED_BY = 'Devtron'

src/Shared/Components/Plugin/service.tsx

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import {
44
GetPluginListPayloadType,
55
GetPluginStoreDataReturnType,
66
GetPluginStoreDataServiceParamsType,
7+
GetPluginTagsPayloadType,
78
PluginDetailDTO,
89
PluginDetailPayloadType,
10+
PluginDetailServiceParamsType,
911
PluginTagNamesDTO,
1012
} from './types'
1113
import { parsePluginDetailsDTOIntoPluginStore } from './utils'
@@ -14,18 +16,23 @@ export const getPluginsDetail = async ({
1416
appId,
1517
parentPluginId,
1618
pluginId,
17-
}: PluginDetailPayloadType): Promise<PluginDetailDTO> => {
19+
}: PluginDetailServiceParamsType): Promise<Pick<GetPluginStoreDataReturnType, 'pluginStore'>> => {
1820
try {
19-
// TODO: Add type for payload as well and return parsed data itself
20-
// Can parse the response here itself
21+
const payload: PluginDetailPayloadType = {
22+
appId,
23+
parentPluginId,
24+
pluginId,
25+
}
26+
2127
const { result } = (await get(
22-
getUrlWithSearchParams(ROUTES.PLUGIN_GLOBAL_LIST_DETAIL_V2, {
23-
appId,
24-
parentPluginId,
25-
pluginId,
26-
}),
28+
getUrlWithSearchParams(ROUTES.PLUGIN_GLOBAL_LIST_DETAIL_V2, payload),
2729
)) as ResponseType<PluginDetailDTO>
28-
return result
30+
31+
const pluginStore = parsePluginDetailsDTOIntoPluginStore(result?.parentPlugins)
32+
33+
return {
34+
pluginStore,
35+
}
2936
} catch (error) {
3037
showError(error)
3138
throw error
@@ -50,7 +57,7 @@ export const getPluginStoreData = async ({
5057
getUrlWithSearchParams(ROUTES.PLUGIN_GLOBAL_LIST_V2, payload),
5158
)) as ResponseType<PluginDetailDTO>
5259

53-
const pluginStore = parsePluginDetailsDTOIntoPluginStore(result.parentPlugins)
60+
const pluginStore = parsePluginDetailsDTOIntoPluginStore(result?.parentPlugins)
5461
return {
5562
totalCount: result.totalCount,
5663
pluginStore,
@@ -63,13 +70,20 @@ export const getPluginStoreData = async ({
6370

6471
export const getAvailablePluginTags = async (appId: number): Promise<string[]> => {
6572
try {
73+
const payload: GetPluginTagsPayloadType = {
74+
appId,
75+
}
76+
6677
const { result } = (await get(
67-
getUrlWithSearchParams(ROUTES.PLUGIN_GLOBAL_LIST_TAGS, { appId }),
78+
getUrlWithSearchParams(ROUTES.PLUGIN_GLOBAL_LIST_TAGS, payload),
6879
)) as ResponseType<PluginTagNamesDTO>
80+
6981
if (!result?.tagNames) {
7082
return []
7183
}
72-
return result.tagNames.sort(stringComparatorBySortOrder)
84+
85+
const uniqueTags = new Set(result.tagNames)
86+
return Array.from(uniqueTags).sort(stringComparatorBySortOrder)
7387
} catch (error) {
7488
showError(error)
7589
throw error

src/Shared/Components/Plugin/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ export enum PluginCreationType {
88
PRESET = 'PRESET',
99
}
1010

11+
export interface GetPluginTagsPayloadType {
12+
appId: number
13+
}
14+
1115
export interface PluginTagNamesDTO {
1216
tagNames: string[]
1317
}
@@ -51,12 +55,14 @@ export interface PluginDetailDTO {
5155
totalCount: number
5256
}
5357

54-
export interface PluginDetailPayloadType {
58+
export interface PluginDetailServiceParamsType {
5559
appId: number
5660
pluginId?: number[]
5761
parentPluginId?: number[]
5862
}
5963

64+
export interface PluginDetailPayloadType extends PluginDetailServiceParamsType {}
65+
6066
export interface PluginListFiltersType extends Pick<BaseFilterQueryParams<unknown>, 'searchKey'> {
6167
selectedTags: string[]
6268
}

src/Shared/Components/Plugin/utils.tsx

Lines changed: 14 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1-
import { cloneElement } from 'react'
2-
import { components, ValueContainerProps } from 'react-select'
31
import { ParentPluginDTO, PluginCreationType, PluginDataStoreType } from './types'
4-
import { ReactComponent as ICSearch } from '../../../Assets/Icon/ic-search.svg'
5-
import { ReactComponent as ICFilter } from '../../../Assets/Icon/ic-filter.svg'
6-
import { ReactComponent as ICFilterApplied } from '../../../Assets/Icon/ic-filter-applied.svg'
7-
import { OptionType } from '../../../Common'
82
import { commonSelectStyles } from '../ReactSelect'
3+
import { stringComparatorBySortOrder } from '../../Helpers'
4+
import { DEFAULT_PLUGIN_CREATED_BY } from './constants'
95

106
const parseMinimalPluginVersionsDTO = (
117
pluginVersionData: ParentPluginDTO['pluginVersions']['minimalPluginVersionData'],
@@ -14,12 +10,12 @@ const parseMinimalPluginVersionsDTO = (
1410
return []
1511
}
1612

17-
return pluginVersionData.map((pluginVersion) => ({
18-
id: pluginVersion.id,
19-
description: pluginVersion.description || '',
20-
name: pluginVersion.name || '',
21-
pluginVersion: pluginVersion.pluginVersion || '',
22-
isLatest: pluginVersion.isLatest || false,
13+
return pluginVersionData.map(({ id, description, name, pluginVersion, isLatest }) => ({
14+
id,
15+
description: description || '',
16+
name: name || '',
17+
pluginVersion: pluginVersion || '',
18+
isLatest: isLatest || false,
2319
}))
2420
}
2521

@@ -50,18 +46,21 @@ export const parsePluginDetailsDTOIntoPluginStore = (pluginData: ParentPluginDTO
5046
}
5147

5248
plugin.pluginVersions.detailedPluginVersionData.forEach((pluginVersionData) => {
49+
const targetTags = pluginVersionData.tags || []
50+
const sortedUniqueTags = Array.from(new Set(targetTags)).sort(stringComparatorBySortOrder)
51+
5352
pluginVersionStore[pluginVersionData.id] = {
5453
id: pluginVersionData.id,
5554
name: pluginVersionData.name || '',
5655
description: pluginVersionData.description || '',
5756
pluginVersion: pluginVersionData.pluginVersion || '',
5857
docLink: pluginVersionData.docLink || '',
59-
updatedBy: plugin.type === PluginCreationType.SHARED ? pluginVersionData.updatedBy : 'Devtron',
58+
updatedBy:
59+
plugin.type === PluginCreationType.SHARED ? pluginVersionData.updatedBy : DEFAULT_PLUGIN_CREATED_BY,
6060
outputVariables: pluginVersionData.outputVariables || [],
6161
inputVariables: pluginVersionData.inputVariables || [],
6262
isLatest: pluginVersionData.isLatest || false,
63-
// TODO: can add unique check and sort them alphabetically
64-
tags: pluginVersionData.tags || [],
63+
tags: sortedUniqueTags,
6564
parentPluginId: plugin.id,
6665
icon: plugin.icon || '',
6766
type: plugin.type,
@@ -75,51 +74,6 @@ export const parsePluginDetailsDTOIntoPluginStore = (pluginData: ParentPluginDTO
7574
}
7675
}
7776

78-
export const PluginTagSelectValueContainer = (props: ValueContainerProps<OptionType[]>) => {
79-
const { children, selectProps } = props
80-
81-
const renderContainer = () => {
82-
if (selectProps.menuIsOpen) {
83-
if (!selectProps.inputValue) {
84-
return (
85-
<>
86-
<ICSearch className="icon-dim-16 dc__no-shrink mr-4 mw-18" />
87-
<span className="dc__position-abs dc__left-35 cn-5 ml-2">{selectProps.placeholder}</span>
88-
</>
89-
)
90-
}
91-
92-
return <ICSearch className="icon-dim-16 dc__no-shrink mr-4 mw-18" />
93-
}
94-
95-
if (selectProps.value.length) {
96-
return (
97-
<>
98-
<ICFilterApplied className="icon-dim-16 dc__no-shrink mr-4 mw-18" />
99-
<span className="dc__position-abs dc__left-35 cn-9 fs-13 fw-4 lh-20">Category</span>
100-
</>
101-
)
102-
}
103-
104-
return (
105-
<>
106-
<ICFilter className="icon-dim-16 dc__no-shrink mr-4 mw-18" />
107-
<span className="dc__position-abs dc__left-35 cn-5 fs-13 fw-4 lh-20">Category</span>
108-
</>
109-
)
110-
}
111-
112-
return (
113-
<components.ValueContainer {...props}>
114-
<div className="flexbox dc__align-items-center">
115-
{renderContainer()}
116-
{cloneElement(children[1])}
117-
</div>
118-
</components.ValueContainer>
119-
)
120-
}
121-
122-
// TODO: Should it be in constants?
12377
export const pluginTagSelectStyles = {
12478
...commonSelectStyles,
12579
option: (base, state) => ({

src/Shared/Components/ReactSelect/utils.tsx

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@
1515
*/
1616

1717
import Tippy from '@tippyjs/react'
18-
import { components, MenuListProps } from 'react-select'
19-
import { Progressing, stopPropagation } from '../../../Common'
18+
import { components, MenuListProps, ValueContainerProps } from 'react-select'
19+
import { cloneElement } from 'react'
20+
import { OptionType, Progressing, stopPropagation } from '../../../Common'
21+
import { ReactComponent as ICSearch } from '../../../Assets/Icon/ic-search.svg'
22+
import { ReactComponent as ICFilter } from '../../../Assets/Icon/ic-filter.svg'
23+
import { ReactComponent as ICFilterApplied } from '../../../Assets/Icon/ic-filter-applied.svg'
2024

2125
export const getCommonSelectStyle = (styleOverrides = {}) => ({
2226
container: (base, state) => ({
@@ -205,3 +209,51 @@ export const MenuListWithApplyButton = ({
205209
)}
206210
</>
207211
)
212+
213+
export const MultiSelectValueContainer = ({
214+
title,
215+
...props
216+
}: ValueContainerProps<OptionType, true> & { title: string }) => {
217+
const { children, selectProps, getValue } = props
218+
const value = getValue() || []
219+
220+
const renderContainer = () => {
221+
if (selectProps.menuIsOpen) {
222+
if (!selectProps.inputValue) {
223+
return (
224+
<>
225+
<ICSearch className="icon-dim-16 dc__no-shrink mr-4 mw-18" />
226+
<span className="dc__position-abs dc__left-35 cn-5 ml-2">{selectProps.placeholder}</span>
227+
</>
228+
)
229+
}
230+
231+
return <ICSearch className="icon-dim-16 dc__no-shrink mr-4 mw-18" />
232+
}
233+
234+
if (value.length) {
235+
return (
236+
<>
237+
<ICFilterApplied className="icon-dim-16 dc__no-shrink mr-4 mw-18" />
238+
<span className="dc__position-abs dc__left-35 cn-9 fs-13 fw-4 lh-20">{title}</span>
239+
</>
240+
)
241+
}
242+
243+
return (
244+
<>
245+
<ICFilter className="icon-dim-16 dc__no-shrink mr-4 mw-18" />
246+
<span className="dc__position-abs dc__left-35 cn-5 fs-13 fw-4 lh-20">{title}</span>
247+
</>
248+
)
249+
}
250+
251+
return (
252+
<components.ValueContainer {...props}>
253+
<div className="flexbox dc__align-items-center">
254+
{renderContainer()}
255+
{cloneElement(children[1])}
256+
</div>
257+
</components.ValueContainer>
258+
)
259+
}

0 commit comments

Comments
 (0)