Skip to content

Commit

Permalink
[fix] inventory bugs (bad effect, tooltip, cloud filters, remove netw…
Browse files Browse the repository at this point in the history
…ork throw, property filter) (#46)
  • Loading branch information
sijav authored Dec 1, 2023
1 parent 1b1e379 commit ff25e7c
Show file tree
Hide file tree
Showing 11 changed files with 102 additions and 71 deletions.
25 changes: 8 additions & 17 deletions src/pages/panel/inventory/InventoryAdvanceSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface InventoryAdvanceSearchConfig {

interface InventoryAdvanceSearchProps {
value: string
onChange: (value: string) => void
onChange: (value?: string, hide?: string) => void
hasError: boolean
}

Expand All @@ -37,9 +37,9 @@ const StyledArrowDropDownIcon = styled(ArrowDropDownIcon, { shouldForwardProp: s

export const InventoryAdvanceSearch = ({ value: searchCrit, onChange, hasError }: InventoryAdvanceSearchProps) => {
const initializedRef = useRef(false)
const [searchParams, setSearchParams] = useSearchParams()
const [searchParams] = useSearchParams()
const hideFilters = searchParams.get('hide') === 'true'
const [searchCritValue, setSearchCritValue] = useState(searchCrit)
const [searchCritValue, setSearchCritValue] = useState(searchCrit === 'all' || !searchCrit ? '' : searchCrit)
const [config, setConfig] = useState<InventoryAdvanceSearchConfig[]>([
{ id: Math.random(), property: null, op: null, value: null, fqn: null },
])
Expand All @@ -63,23 +63,14 @@ export const InventoryAdvanceSearch = ({ value: searchCrit, onChange, hasError }
)

const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
if (e.target.value !== searchCritValue && !hideFilters) {
setSearchParams((prev) => {
prev.set('hide', 'true')
return prev
})
if (e.target.value !== searchCritValue) {
onChange(undefined, !e.target.value || e.target.value === 'all' ? '' : 'true')
}
handleChangeValue(e.target.value)
}

useEffect(() => {
if (initializedRef.current && !hideFilters) {
setConfig([{ id: Math.random(), property: null, op: null, value: null, fqn: null }])
}
}, [kind, hideFilters])

useEffect(() => {
if (initializedRef.current && !hideFilters) {
if (initializedRef.current) {
const configJoined = config
.map((item) => {
if (typeof item === 'string' || !item) {
Expand All @@ -102,15 +93,15 @@ export const InventoryAdvanceSearch = ({ value: searchCrit, onChange, hasError }
handleChangeValue(result || 'all')
}
initializedRef.current = true
}, [hideFilters, config, handleChangeValue, kind])
}, [config, handleChangeValue, kind])

return (
<>
<Collapse in={!hideFilters}>
<NetworkErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
<Suspense fallback={<InventoryFormsSkeleton />}>
{hideFilters ? null : (
<InventoryForm config={config} setConfig={setConfig} searchCrit={searchCrit} kind={kind} setKind={setKind} />
<InventoryForm config={config} setConfig={setConfig} searchCrit={searchCrit || 'all'} kind={kind} setKind={setKind} />
)}
</Suspense>
</NetworkErrorBoundary>
Expand Down
1 change: 1 addition & 0 deletions src/pages/panel/inventory/InventoryForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const InventoryForm = ({ searchCrit, kind, setKind, config, setConfig }:
const { data: originalStartData } = useQuery({
queryKey: ['workspace-inventory-search-start', selectedWorkspace?.id],
queryFn: getWorkspaceInventorySearchStartQuery,
throwOnError: false,
enabled: !!selectedWorkspace?.id,
})
const startData = useMemo(() => originalStartData ?? { accounts: [], kinds: [], regions: [], severity: [] }, [originalStartData])
Expand Down
40 changes: 28 additions & 12 deletions src/pages/panel/inventory/InventoryFormFilterRowProperty.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,31 @@ export const InventoryFormFilterRowProperty = ({ selectedKind, defaultValue, kin
const [value, setValue] = useState<string | null>(defaultValue || null)
const { selectedWorkspace } = useUserProfile()
const isDictionary = fqn?.startsWith('dictionary') ?? false
const {
data = null,
isLoading,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
const propertyAttributes = useInfiniteQuery({
queryKey: [
'workspace-inventory-property-path-complete-query',
selectedWorkspace?.id,
path,
isDictionary ? fqn?.split(',')[1].split(']')[0].trim() ?? '' : prop,
prop,
selectedKind,
fqn?.split(',')[1]?.split(']')[0]?.trim() ?? '',
] as const,
initialPageParam: {
limit: ITEMS_PER_PAGE,
skip: 0,
},
getNextPageParam: (lastPage, _allPages, lastPageParam) =>
(lastPage?.length ?? 0) < ITEMS_PER_PAGE ? undefined : { ...lastPageParam, skip: lastPageParam.skip + ITEMS_PER_PAGE },
queryFn: getCustomedWorkspaceInventoryPropertyAttributesQuery,
throwOnError: false,
enabled: !!selectedWorkspace?.id && !!kinds.length && isDictionary,
})
const pathComplete = useInfiniteQuery({
queryKey: [
'workspace-inventory-property-path-complete-query',
selectedWorkspace?.id,
path,
prop,
selectedKind,
JSON.stringify(kinds),
] as const,
Expand All @@ -67,10 +80,11 @@ export const InventoryFormFilterRowProperty = ({ selectedKind, defaultValue, kin
},
getNextPageParam: (lastPage, _allPages, lastPageParam) =>
(lastPage?.length ?? 0) < ITEMS_PER_PAGE ? undefined : { ...lastPageParam, skip: lastPageParam.skip + ITEMS_PER_PAGE },
queryFn: isDictionary ? getCustomedWorkspaceInventoryPropertyAttributesQuery : getWorkspaceInventoryPropertyPathCompleteQuery,
queryFn: getWorkspaceInventoryPropertyPathCompleteQuery,
throwOnError: false,
enabled: !!selectedWorkspace?.id && !!kinds.length,
enabled: !!selectedWorkspace?.id && !!kinds.length && !isDictionary,
})
const { data = null, isLoading, fetchNextPage, hasNextPage, isFetchingNextPage } = isDictionary ? propertyAttributes : pathComplete
const flatData = useMemo(() => (data?.pages.flat().filter((i) => i) as Exclude<typeof data, null>['pages'][number]) ?? null, [data])
const highlightedOptionRef = useRef<Exclude<typeof flatData, null>[number] | null>(null)
const handleScroll = (e: ReactUIEvent<HTMLUListElement, UIEvent>) => {
Expand All @@ -90,7 +104,9 @@ export const InventoryFormFilterRowProperty = ({ selectedKind, defaultValue, kin
}
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
setFqn('object')
if (!isDictionary) {
setFqn('object')
}
setValue(null)
const separatedValue = value.split('.')
const newProp = separatedValue.splice(separatedValue.length - 1, 1)[0]
Expand All @@ -99,7 +115,7 @@ export const InventoryFormFilterRowProperty = ({ selectedKind, defaultValue, kin
setProp(newProp)
}
if (newPath !== path) {
setPath(newPath)
setPath(newPath[newPath.length - 1] === '.' ? newPath.substring(0, newPath.length - 1) : newPath)
}
}
const handleChange = (_: unknown, option: string | { key: string; label: string; value: string } | null) => {
Expand Down
26 changes: 9 additions & 17 deletions src/pages/panel/inventory/InventoryFormFilterRowStringValue.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,31 +79,23 @@ export function InventoryFormFilterRowStringValue<Multiple extends boolean, Netw
const rawOptions = (networkDisabled ? defaultOptions : flatData) ?? []

const optionsWithTyped =
typed &&
!(Array.isArray(value)
? value.find((i) => (typeof i === 'string' ? i : i.label) === typed)
: (typeof value === 'string' ? value : value?.label) === typed)
typed && typed.toLocaleLowerCase() !== 'null' && !(Array.isArray(value) ? value.find((i) => i.label === typed) : value?.label === typed)
? rawOptions.concat({ value: typed, label: typed })
: rawOptions

const optionsWithValues =
value && (!Array.isArray(value) || value.length)
? optionsWithTyped.concat(
typeof value === 'string'
? !optionsWithTyped.find((i) => i.label === value)
? [{ value, label: value } as AutoCompleteValue]
: []
: Array.isArray(value)
? value
.map((i) => (optionsWithTyped.find((j) => (typeof i === 'string' ? j.label === i : j === i)) ? undefined : i))
.filter((i) => i)
.map((i) => (typeof i === 'string' ? { label: i, value: i } : (i as AutoCompleteValue)))
: optionsWithTyped.indexOf(value) > -1
? [value]
: [],
Array.isArray(value)
? value
.map((i) => (optionsWithTyped.find((j) => (typeof i === 'string' ? j.label === i : j === i)) ? undefined : i))
.filter((i) => i && i.value !== 'null')
.map((i) => (typeof i === 'string' ? { label: i, value: i } : (i as AutoCompleteValue)))
: optionsWithTyped.indexOf(value) > -1
? []
: [value],
)
: optionsWithTyped

const options = optionsWithValues.find((i) => i.value === 'null')
? optionsWithValues
: optionsWithValues.concat({ label: 'Null', value: 'null' })
Expand Down
32 changes: 23 additions & 9 deletions src/pages/panel/inventory/InventoryPage.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { Suspense, useState } from 'react'
import { Suspense, useCallback, useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import { useAbsoluteNavigate } from 'src/shared/absolute-navigate'
import { ErrorBoundaryFallback, NetworkErrorBoundary } from 'src/shared/error-boundary-fallback'
import { LoadingSuspenseFallback } from 'src/shared/loading'
import { getLocationSearchValues, mergeLocationSearchValues } from 'src/shared/utils/windowLocationSearch'
import { InventoryAdvanceSearch } from './InventoryAdvanceSearch'
import { InventoryTable } from './InventoryTable'
import { InventoryTableError } from './InventoryTable.error'

export default function InventoryPage() {
const [searchParams, setSeachParams] = useSearchParams()
const [searchParams] = useSearchParams()
const navigate = useAbsoluteNavigate()
const [hasError, setHasError] = useState(false)
const searchCrit = searchParams.get('q') || 'all'
const history = {
Expand All @@ -16,14 +19,25 @@ export default function InventoryPage() {
before: searchParams.get('before'),
}

const setSearchCrit = (crit: string) => {
setSeachParams((prev) => {
if (crit !== prev.get('q')) {
prev.set('q', crit)
const setSearchCrit = useCallback(
(crit?: string, hide?: string) => {
if (crit === undefined && hide === undefined) {
return
}
return prev
})
}
const searchValues = getLocationSearchValues()
if (crit !== undefined) {
searchValues['q'] = window.encodeURIComponent(crit)
}
if (hide !== undefined) {
searchValues['hide'] = hide
}
const search = mergeLocationSearchValues(searchValues)
if (search !== window.location.search) {
navigate({ pathname: '/inventory', search })
}
},
[navigate],
)

return (
<NetworkErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
Expand Down
8 changes: 6 additions & 2 deletions src/pages/panel/inventory/InventoryTagAutoComplete.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Trans } from '@lingui/macro'
import { Autocomplete, CircularProgress, TextField } from '@mui/material'
import { useInfiniteQuery } from '@tanstack/react-query'
import { ReactNode, UIEvent as ReactUIEvent, useMemo } from 'react'
import { ReactNode, UIEvent as ReactUIEvent, useMemo, useState } from 'react'
import { useUserProfile } from 'src/core/auth'
import { getWorkspaceInventoryPropertyAttributesQuery } from 'src/pages/panel/shared/queries'
import { panelUI } from 'src/shared/constants'
Expand All @@ -15,6 +15,7 @@ interface InventoryTagAutoCompleteProps {
const ITEMS_PER_PAGE = 50

export const InventoryTagAutoComplete = ({ searchCrit, setSelectedTag }: InventoryTagAutoCompleteProps) => {
const [typed, setTyped] = useState('')
const { selectedWorkspace } = useUserProfile()
const {
data = null,
Expand All @@ -27,7 +28,7 @@ export const InventoryTagAutoComplete = ({ searchCrit, setSelectedTag }: Invento
'workspace-inventory-property-attributes',
selectedWorkspace?.id,
searchCrit.startsWith('is') ? searchCrit.split(' ')[0] : 'all',
'tags',
`tags${typed ? `=~${typed}` : ''}`,
] as const,
initialPageParam: {
limit: ITEMS_PER_PAGE,
Expand All @@ -36,6 +37,7 @@ export const InventoryTagAutoComplete = ({ searchCrit, setSelectedTag }: Invento
getNextPageParam: (lastPage, _allPages, lastPageParam) =>
(lastPage?.length ?? 0) < ITEMS_PER_PAGE ? undefined : { ...lastPageParam, skip: lastPageParam.skip + ITEMS_PER_PAGE },
queryFn: getWorkspaceInventoryPropertyAttributesQuery,
throwOnError: false,
enabled: !!selectedWorkspace?.id,
})
const flatData = useMemo(() => (data?.pages.flat().filter((i) => i) as Exclude<typeof data, null>['pages'][number]) ?? null, [data])
Expand All @@ -56,6 +58,7 @@ export const InventoryTagAutoComplete = ({ searchCrit, setSelectedTag }: Invento
loading={isLoading}
onChange={(_, value) => setSelectedTag(value ?? '')}
getOptionLabel={(option) => option ?? ''}
filterOptions={(option) => option}
options={flatData ?? []}
ListboxComponent={ListboxComponent}
ListboxProps={{
Expand All @@ -76,6 +79,7 @@ export const InventoryTagAutoComplete = ({ searchCrit, setSelectedTag }: Invento
</>
),
}}
onChange={(e) => setTyped(e.target.value)}
/>
)}
/>
Expand Down
1 change: 1 addition & 0 deletions src/pages/panel/inventory/ResourceDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export const ResourceDetail = ({ detail, onClose }: ResourceDetailProps) => {
const { data, isLoading } = useQuery({
queryKey: ['workspace-inventory-node', selectedWorkspace?.id, detail?.id],
queryFn: getWorkspaceInventoryNodeQuery,
throwOnError: false,
})
const [selectedRow, setSelectedRow] = useState(detail)

Expand Down
4 changes: 3 additions & 1 deletion src/pages/panel/inventory/utils/getAutoCompleteFromKey.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,16 @@ export const getAutoCompletePropsFromKey = (key: string) => {
) =>
option ? (
<ListItemButton component="li" {...props} {...state}>
<CloudAvatar cloud={option.value} />
{option.value !== null ? <CloudAvatar cloud={option.value} /> : null}
<Typography variant="overline" ml={2}>
{option.label}
</Typography>
</ListItemButton>
) : (
''
),
ListboxComponent: undefined,
ListboxProps: undefined,
renderInput: (params: AutocompleteRenderInputParams) => <TextField {...params} label={<Trans>Clouds</Trans>} />,
}
case '/ancestors.account.reported.id':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { getWorkspaceInventoryPropertyAttributesQuery } from 'src/pages/panel/sh

export const getCustomedWorkspaceInventoryPropertyAttributesQuery = ({
signal,
queryKey: [_, workspaceId, path, prop, kind],
queryKey: [_, workspaceId, path, prop, kind, type],
pageParam,
direction,
meta,
Expand All @@ -14,7 +14,7 @@ export const getCustomedWorkspaceInventoryPropertyAttributesQuery = ({
string, // path
string, // prop
string | null, // kind
string, // kinds
string, // type
],
{
skip: number | null
Expand All @@ -28,11 +28,11 @@ export const getCustomedWorkspaceInventoryPropertyAttributesQuery = ({
'workspace-inventory-property-attributes',
workspaceId,
kind ? `is(${kind})` : 'all',
path.split('.').slice(-1)[0],
`${path.split('.').slice(-1)[0]}${prop ? `=~${prop}` : ''}`,
] as const,
pageParam,
direction,
meta,
})?.then((item) => item.map((key) => ({ label: path ? `${path}.${key}` : key, key, value: prop }))) ?? null
})?.then((item) => item.map((key) => ({ label: path ? `${path}.${key}` : key, key, value: type }))) ?? null
)
}
10 changes: 1 addition & 9 deletions src/shared/react-window/ListboxComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,13 @@ function RenderRow({
}: ListChildComponentProps) {
const [props, option, { inputValue: _state, ...state }] = (data as DataItemType[])[index]

const theme = useTheme()

const inlineStyle = {
...style,
top: (style.top as number) + LIST_BOX_PADDING,
}

return (
<Tooltip
title={option}
arrow
enterDelay={400}
enterNextDelay={400}
slotProps={{ tooltip: { sx: { backgroundColor: theme.palette.common.black } } }}
>
<Tooltip title={option} arrow enterDelay={400} enterNextDelay={400}>
<ListItemButton component="li" {...props} {...state} style={inlineStyle}>
<Typography noWrap>{option}</Typography>
</ListItemButton>
Expand Down
18 changes: 18 additions & 0 deletions src/shared/utils/windowLocationSearch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export const getLocationSearchValues = () =>
window.location.search
.substring(1)
.split('&')
.reduce(
(prev, cur) => {
const splitted = cur.split('=')
prev[splitted[0]] = splitted[1]
return prev
},
{} as Record<string, string>,
)

export const mergeLocationSearchValues = (search: Record<string, string>) =>
`?${Object.entries(search)
.map(([key, value]) => (value ? `${key}=${value}` : null))
.filter((i) => i)
.join('&')}`

0 comments on commit ff25e7c

Please sign in to comment.