Skip to content

Commit

Permalink
Merge pull request #2222 from devtron-labs/fix/node-details-list
Browse files Browse the repository at this point in the history
fix: add error icon for errors column & colors in status column; replace column selector with select picker
  • Loading branch information
Elessar1802 authored Nov 21, 2024
2 parents 2e3c6b8 + f7b05a7 commit 28a1cbc
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 193 deletions.
34 changes: 0 additions & 34 deletions src/components/CIPipelineN/ciPipeline.utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,40 +172,6 @@ export const outputFormatSelectStyle = {
}),
}

export const containerImageSelectStyles = {
...baseSelectStyles,
control: (base, state) => ({
...base,
border: '1px solid var(--N200)',
boxShadow: 'none',
minHeight: 'auto',
borderRadius: '4px',
height: '32px',
fontSize: '12px',
pointerEvents: 'auto',
cursor: state.isDisabled ? 'not-allowed' : 'pointer',
}),
valueContainer: (base, state) => ({
...base,
color: 'var(--N900)',
background: 'var(--N50) !important',
padding: '0px 10px',
display: 'flex',
height: '30px',
borderTopLeftRadius: '4px',
borderBottomLeftRadius: '4px',
fontSize: '12px',
width: '100px',
whiteSpace: 'nowrap',
}),
indicatorsContainer: (base, state) => ({
...base,
background: 'var(--N50) !important',
borderTopRightRadius: '4px',
borderBottomRightRadius: '4px',
}),
}

export const CiPipelineSourceTypeBaseOptions = [
{
label: 'Branch Fixed',
Expand Down
47 changes: 40 additions & 7 deletions src/components/ResourceBrowser/ResourceList/BaseResourceList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,14 @@ import searchWorker from '@Config/searchWorker'
import { URLS } from '@Config/routes'
import { deleteNodeCapacity } from '@Components/ClusterNodes/clusterNodes.service'
import NodeActionsMenu from '@Components/ResourceBrowser/ResourceList/NodeActionsMenu'
import { ReactComponent as ICErrorExclamation } from '@Icons/ic-error-exclamation.svg'
import ResourceListEmptyState from './ResourceListEmptyState'
import {
ALL_NAMESPACE_OPTION,
DEFAULT_K8SLIST_PAGE_SIZE,
K8S_EMPTY_GROUP,
MANDATORY_NODE_LIST_HEADERS,
NODE_LIST_HEADERS,
NODE_K8S_VERSION_FILTER_KEY,
NODE_LIST_HEADERS_TO_KEY_MAP,
NODE_SEARCH_KEYS_TO_OBJECT_KEYS,
Expand Down Expand Up @@ -131,7 +134,11 @@ const BaseResourceListContent = ({

const visibleColumnsSet = new Set(visibleColumns)

return list.filter((header) => visibleColumnsSet.has(header)) ?? []
return list.filter(
(header) =>
MANDATORY_NODE_LIST_HEADERS.includes(header as (typeof NODE_LIST_HEADERS)[number]) ||
visibleColumnsSet.has(header),
)
}, [resourceList, visibleColumns, isNodeListing])

const { gridTemplateColumns, handleResize } = useResizableTableConfig({
Expand Down Expand Up @@ -385,11 +392,14 @@ const BaseResourceListContent = ({
const getStatusClass = (status: string) => {
let statusPostfix = status?.toLowerCase()

if (statusPostfix && (statusPostfix.includes(':') || statusPostfix.includes('/'))) {
statusPostfix = statusPostfix.replace(':', '__').replace('/', '__')
if (
statusPostfix &&
(statusPostfix.includes(':') || statusPostfix.includes('/') || statusPostfix.includes(' '))
) {
statusPostfix = statusPostfix.replace(':', '__').replace('/', '__').replace(' ', '__')
}

return `f-${statusPostfix}`
return `f-${statusPostfix} ${isNodeListing ? 'dc__capitalize' : ''}`
}

const handleResourceClick = (e) => onResourceClick(e, shouldOverrideSelectedResourceKind)
Expand All @@ -408,6 +418,8 @@ const BaseResourceListContent = ({
const gvkFromRawData = lowercaseKindToResourceGroupMap[lowercaseKind]?.gvk ?? ({} as GVKType)
// Redirection and actions are not possible for Events since the required data for the same is not available
const shouldShowRedirectionAndActions = lowercaseKind !== Nodes.Event.toLowerCase()
const isNodeUnschedulable = isNodeListing && !!resourceData.unschedulable
const isNodeListingAndNodeHasErrors = isNodeListing && !!resourceData[NODE_LIST_HEADERS_TO_KEY_MAP.errors]

return (
<div
Expand Down Expand Up @@ -498,15 +510,28 @@ const BaseResourceListContent = ({
columnName === 'status'
? ` app-summary__status-name ${getStatusClass(String(resourceData[columnName]))}`
: ''
}`}
} ${columnName === 'errors' ? 'app-summary__status-name f-error' : ''}`}
>
<ConditionalWrap
condition={columnName === 'node'}
wrap={getRenderNodeButton(resourceData, columnName, handleNodeClick)}
>
<Tooltip content={resourceData[columnName]}>
{columnName === 'errors' && isNodeListingAndNodeHasErrors && (
<ICErrorExclamation className="icon-dim-16 dc__no-shrink mr-4" />
)}
<Tooltip
content={renderResourceValue(
resourceData[
isNodeListing ? NODE_LIST_HEADERS_TO_KEY_MAP[columnName] : columnName
]?.toString(),
)}
>
<span
className="dc__truncate"
className={
columnName === 'status' && isNodeUnschedulable
? 'dc__no-shrink'
: 'dc__truncate'
}
data-testid={`${columnName}-count`}
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
Expand All @@ -526,6 +551,14 @@ const BaseResourceListContent = ({
}}
/>
</Tooltip>
{columnName === 'status' && isNodeUnschedulable && (
<>
<span className="dc__bullet mr-4 ml-4 mw-4 bcn-4" />
<Tooltip content="Scheduling disabled">
<span className="cr-5 dc__truncate">SchedulingDisabled</span>
</Tooltip>
</>
)}
<span>
{columnName === 'restarts' &&
Number(resourceData.restarts) !== 0 &&
Expand Down
194 changes: 54 additions & 140 deletions src/components/ResourceBrowser/ResourceList/ColumnSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,82 +14,14 @@
* limitations under the License.
*/

import React, { useState, useMemo, useRef } from 'react'
import ReactSelect, { components, ValueContainerProps, MenuListProps } from 'react-select'
import { Option } from '@devtron-labs/devtron-fe-common-lib'
import { ReactComponent as Setting } from '@Icons/ic-nav-gear.svg'
import { containerImageSelectStyles } from '../../CIPipelineN/ciPipeline.utils'
import { useState, useMemo, useRef } from 'react'
import { MultiValue, SelectInstance } from 'react-select'
import { Button, ButtonVariantType, SelectPicker, SelectPickerOptionType } from '@devtron-labs/devtron-fe-common-lib'
import { ReactComponent as ICGears } from '@Icons/ic-nav-gear.svg'
import { OPTIONAL_NODE_LIST_HEADERS } from '../Constants'
import { ColumnFilterContextType, ColumnSelectorType } from '../Types'
import { ColumnSelectorType } from '../Types'
import { saveAppliedColumnsInLocalStorage } from './utils'

const ColumnFilterContext = React.createContext<ColumnFilterContextType>(null)

export function useColumnFilterContext() {
const context = React.useContext(ColumnFilterContext)

if (!context) {
throw new Error('cannot be rendered outside the component')
}

return context
}

const ValueContainer = (props: ValueContainerProps) => {
const { getValue, selectProps, children } = props
const { length } = getValue()

return (
<components.ValueContainer {...props}>
{length > 0 ? (
<>
{!selectProps.menuIsOpen && (
<>
<Setting className="icon-dim-16 scn-6 mr-5" />
Columns
</>
)}
{React.cloneElement(children[1])}
</>
) : (
children
)}
</components.ValueContainer>
)
}

const MenuList = (props: MenuListProps) => {
const { selectedColumns, setVisibleColumns, setIsMenuOpen, selectRef } = useColumnFilterContext()

const { children } = props

const handleApplySelectedColumns = (): void => {
setIsMenuOpen(false)

const newVisibleColumns = selectedColumns.map((option) => option.value)

if (typeof Storage !== 'undefined') {
saveAppliedColumnsInLocalStorage(newVisibleColumns)
}

selectRef.current?.blur()

setVisibleColumns(newVisibleColumns)
}

return (
<components.MenuList {...props}>
{children}

<div className="flex dc__react-select__bottom bcn-0 p-8">
<button type="button" className="flex cta apply-filter h-32 w-100" onClick={handleApplySelectedColumns}>
Apply
</button>
</div>
</components.MenuList>
)
}

const ColumnSelector = ({ setVisibleColumns, visibleColumns }: ColumnSelectorType) => {
const columnOptions = useMemo(
() =>
Expand All @@ -101,14 +33,16 @@ const ColumnSelector = ({ setVisibleColumns, visibleColumns }: ColumnSelectorTyp
)

const [isMenuOpen, setIsMenuOpen] = useState(false)
const [selectedColumns, setSelectedColumns] = useState<ColumnFilterContextType['selectedColumns']>(
const [selectedColumns, setSelectedColumns] = useState<MultiValue<SelectPickerOptionType<string>>>(
visibleColumns.map((column) => ({ value: column, label: column })),
)

const selectRef: ColumnFilterContextType['selectRef'] = useRef()
const selectRef = useRef<SelectInstance<SelectPickerOptionType<string>, true>>(null)

const handleMenuOpen = () => {
setIsMenuOpen(true)

selectRef.current?.focus()
}

const handleMenuClose = () => {
Expand All @@ -117,74 +51,54 @@ const ColumnSelector = ({ setVisibleColumns, visibleColumns }: ColumnSelectorTyp
selectRef.current?.blur()
}

const columnFilterProviderValue: ColumnFilterContextType = useMemo(
() => ({
isMenuOpen,
setIsMenuOpen,
selectedColumns,
setSelectedColumns,
selectRef,
setVisibleColumns,
}),
[isMenuOpen, selectedColumns],
const handleApplySelectedColumns = (): void => {
setIsMenuOpen(false)

const newVisibleColumns = selectedColumns.map((option) => option.value)

if (typeof Storage !== 'undefined') {
saveAppliedColumnsInLocalStorage(newVisibleColumns)
}

selectRef.current?.blur()

setVisibleColumns(newVisibleColumns)
}

const renderMenuListFooter = () => (
<div className="bcn-0 p-8 dc__border-top-n1 w-100">
<Button
text="Apply"
onClick={handleApplySelectedColumns}
variant={ButtonVariantType.primary}
fullWidth
dataTestId="apply-column-selector"
/>
</div>
)

return (
<ColumnFilterContext.Provider value={columnFilterProviderValue}>
<ReactSelect
classNamePrefix="node-column-list-filter"
ref={selectRef}
menuIsOpen={isMenuOpen}
name="columns"
value={selectedColumns}
options={columnOptions}
onChange={setSelectedColumns}
isMulti
autoFocus={false}
closeMenuOnSelect={false}
hideSelectedOptions={false}
blurInputOnSelect={false}
onMenuOpen={handleMenuOpen}
onMenuClose={handleMenuClose}
components={{
Option,
ValueContainer,
IndicatorSeparator: null,
ClearIndicator: null,
MenuList,
}}
styles={{
...containerImageSelectStyles,
menu: (base) => ({
...base,
zIndex: 6,
}),
menuList: (base) => ({
...base,
borderRadius: '4px',
paddingTop: 0,
paddingBottom: 0,
}),
option: (base, state) => ({
...base,
padding: '10px 0',
backgroundColor: state.isFocused ? 'var(--N100) !important' : 'var(--N0) !important',
color: 'var(--N900)',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
cursor: 'pointer',
}),
dropdownIndicator: (base, state) => ({
...base,
color: 'var(--N400)',
transition: 'all .2s ease',
transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : 'rotate(0deg)',
padding: '0 8px',
}),
}}
/>
</ColumnFilterContext.Provider>
<SelectPicker
selectRef={selectRef}
classNamePrefix="node-column-list-filter"
inputId="node-column-list-filter"
closeMenuOnSelect={false}
controlShouldRenderValue={false}
fullWidth
hideSelectedOptions={false}
icon={<ICGears className="dc__no-shrink icon-dim-16 scn-6" />}
isSearchable
menuIsOpen={isMenuOpen}
onMenuOpen={handleMenuOpen}
onMenuClose={handleMenuClose}
isMulti
onChange={setSelectedColumns}
placeholder="Column"
options={columnOptions}
value={selectedColumns}
renderMenuListFooter={renderMenuListFooter}
isClearable={false}
/>
)
}

Expand Down
Loading

0 comments on commit 28a1cbc

Please sign in to comment.