diff --git a/src/components/molecules/NavigatorResourceRow/NavigatorResourceRow.tsx b/src/components/molecules/NavigatorResourceRow/NavigatorResourceRow.tsx index 50178dff41..ba3b0f7f7c 100644 --- a/src/components/molecules/NavigatorResourceRow/NavigatorResourceRow.tsx +++ b/src/components/molecules/NavigatorResourceRow/NavigatorResourceRow.tsx @@ -1,10 +1,9 @@ import React from 'react'; import {Col, Row} from 'antd'; import styled from 'styled-components'; - import Colors, {FontColors} from '@styles/Colors'; - import NavigatorRowLabel from '@molecules/NavigatorRowLabel'; +import {ResourceValidationError} from '@models/k8sresource'; export type NavigatorResourceRowProps = { rowKey: React.Key; @@ -15,6 +14,7 @@ export type NavigatorResourceRowProps = { hasOutgoingRefs: boolean; hasUnsatisfiedRefs: boolean; onClickResource?: React.MouseEventHandler; + showErrorsModal?: (errors: ResourceValidationError[]) => void; }; const ItemRow = styled(Row)` @@ -71,6 +71,7 @@ const NavigatorResourceRow = (props: NavigatorResourceRowProps) => { hasOutgoingRefs, hasUnsatisfiedRefs, onClickResource, + showErrorsModal, } = props; // Parent needs to make sure disabled and selected arent active at the same time. @@ -93,6 +94,7 @@ const NavigatorResourceRow = (props: NavigatorResourceRowProps) => { hasOutgoingRefs={hasOutgoingRefs} hasUnsatisfiedRefs={hasUnsatisfiedRefs} onClickLabel={onClickResource} + showErrorsModal={showErrorsModal} /> diff --git a/src/components/molecules/NavigatorRowLabel/NavigatorRowLabel.tsx b/src/components/molecules/NavigatorRowLabel/NavigatorRowLabel.tsx index 5762687c42..5166cecb9c 100644 --- a/src/components/molecules/NavigatorRowLabel/NavigatorRowLabel.tsx +++ b/src/components/molecules/NavigatorRowLabel/NavigatorRowLabel.tsx @@ -9,7 +9,7 @@ import {NAVIGATOR_HEIGHT_OFFSET} from '@constants/constants'; import {useAppDispatch, useAppSelector} from '@redux/hooks'; import {selectK8sResource} from '@redux/reducers/main'; -import {ResourceRef, K8sResource} from '@models/k8sresource'; +import {ResourceRef, K8sResource, ResourceValidationError} from '@models/k8sresource'; import {ResourceMapType} from '@models/appstate'; import {isOutgoingRef, isIncomingRef, isUnsatisfiedRef} from '@redux/services/resourceRefs'; import ScrollIntoView from '@molecules/ScrollIntoView'; @@ -30,6 +30,7 @@ type NavigatorRowLabelProps = { hasOutgoingRefs: boolean; hasUnsatisfiedRefs?: boolean; onClickLabel?: React.MouseEventHandler; + showErrorsModal?: (errors: ResourceValidationError[]) => void; }; const StyledDivider = styled(Divider)` @@ -64,6 +65,7 @@ const StyledLabelContainer = styled.div` const StyledIconsContainer = styled.span` display: flex; align-items: center; + cursor: pointer; `; const StyledSpan = styled.span<{isSelected: boolean; isHighlighted: boolean}>` @@ -183,6 +185,7 @@ const NavigatorRowLabel = (props: NavigatorRowLabelProps) => { hasOutgoingRefs, hasUnsatisfiedRefs, onClickLabel, + showErrorsModal, } = props; const dispatch = useAppDispatch(); @@ -206,6 +209,12 @@ const NavigatorRowLabel = (props: NavigatorRowLabelProps) => { return elementTop < navigatorHeight && elementBottom >= 0; }, [navigatorHeight]); + const onClickErrorIcon = () => { + if (showErrorsModal && resource?.validation?.errors && resource.validation.errors.length > 0) { + showErrorsModal(resource.validation.errors); + } + }; + useEffect(() => { setResource(resourceMap[resourceId]); }, [resourceId, resourceMap]); @@ -295,7 +304,20 @@ const NavigatorRowLabel = (props: NavigatorRowLabelProps) => { )} {resource && resource.validation && !resource.validation.isValid && ( - + + + {resource.validation.errors.length} error{resource.validation.errors.length !== 1 && 's'} + + + } + > + + + + )} ); diff --git a/src/components/molecules/ValidationErrorsModal/ValidationErrorsModal.tsx b/src/components/molecules/ValidationErrorsModal/ValidationErrorsModal.tsx new file mode 100644 index 0000000000..29e0844fb4 --- /dev/null +++ b/src/components/molecules/ValidationErrorsModal/ValidationErrorsModal.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import {Modal} from 'antd'; +import {ResourceValidationError} from '@models/k8sresource'; +import styled from 'styled-components'; + +const StyledContainer = styled.div` + margin: 20px 10px; + overflow-y: auto; + display: block; + height: 100%; + max-height: 500px; +`; + +const StyledErrorList = styled.ul` + margin: 0; + padding: 5px; + padding-left: 15px; + height: 100%; + width: 100%; + display: block; +`; + +const StyledErrorProperty = styled.span` + font-weight: 600; + display: block; +`; +const StyledErrorMessage = styled.span` + margin-left: 5px; + font-style: italic; + display: block; +`; + +const ValidationErrorsModal = (props: {errors: ResourceValidationError[]; isVisible: boolean; onClose: () => void}) => { + const {errors, isVisible, onClose} = props; + return ( + onClose()} footer={null}> + + + {errors.map(error => { + return ( +
  • + {error.property} + {error.message} +
  • + ); + })} +
    +
    +
    + ); +}; + +export default ValidationErrorsModal; diff --git a/src/components/molecules/ValidationErrorsModal/index.ts b/src/components/molecules/ValidationErrorsModal/index.ts new file mode 100644 index 0000000000..15bbd43aff --- /dev/null +++ b/src/components/molecules/ValidationErrorsModal/index.ts @@ -0,0 +1 @@ +export {default} from './ValidationErrorsModal'; diff --git a/src/components/organisms/NavigatorPane/NavigatorPane.tsx b/src/components/organisms/NavigatorPane/NavigatorPane.tsx index fd63e97aed..cbc07a9134 100644 --- a/src/components/organisms/NavigatorPane/NavigatorPane.tsx +++ b/src/components/organisms/NavigatorPane/NavigatorPane.tsx @@ -2,7 +2,12 @@ import React, {useState, useContext, useEffect} from 'react'; import {Row, Skeleton} from 'antd'; import styled from 'styled-components'; import {useSelector} from 'react-redux'; -import {isInClusterModeSelector, helmChartsSelector, helmValuesSelector, kustomizationsSelector} from '@redux/selectors'; +import { + isInClusterModeSelector, + helmChartsSelector, + helmValuesSelector, + kustomizationsSelector, +} from '@redux/selectors'; import {HelmValuesFile} from '@models/helm'; import Colors, {BackgroundColors} from '@styles/Colors'; @@ -14,6 +19,9 @@ import {NAVIGATOR_HEIGHT_OFFSET} from '@constants/constants'; import AppContext from '@src/AppContext'; +import ValidationErrorsModal from '@components/molecules/ValidationErrorsModal'; +import {ResourceValidationError} from '@models/k8sresource'; + import HelmChartsSection from './components/HelmChartsSection'; import KustomizationsSection from './components/KustomizationsSection'; import ResourcesSection from './components/ResourcesSection'; @@ -131,6 +139,8 @@ const NavigatorPane = () => { const kustomizations = useSelector(kustomizationsSelector); const isInClusterMode = useSelector(isInClusterModeSelector); + const [isValidationsErrorsModalVisible, setValidationsErrorsVisible] = useState(false); + const [currentValidationErrors, setCurrentValidationErrors] = useState([]); const [expandedSections, setExpandedSections] = useState(['kustomizations', 'helmcharts']); const expandSection = (sectionName: string) => { @@ -153,8 +163,23 @@ const NavigatorPane = () => { } }, [selectedResourceId]); + const showValidationsErrorsModal = (errors: ResourceValidationError[]) => { + setValidationsErrorsVisible(true); + setCurrentValidationErrors(errors); + }; + + const hideValidationsErrorsModal = () => { + setValidationsErrorsVisible(false); + setCurrentValidationErrors([]); + }; + return ( <> + @@ -186,7 +211,9 @@ const NavigatorPane = () => { isSelected={ !isSectionExpanded('helmcharts') && Object.values(helmCharts).some(h => - h.valueFileIds.map(v => helmValues[v]).some((valuesFile: HelmValuesFile) => valuesFile.isSelected) + h.valueFileIds + .map(v => helmValues[v]) + .some((valuesFile: HelmValuesFile) => valuesFile.isSelected) ) } /> @@ -216,7 +243,11 @@ const NavigatorPane = () => { )} - {uiState.isFolderLoading || previewLoader.isLoading ? : } + {uiState.isFolderLoading || previewLoader.isLoading ? ( + + ) : ( + + )} ); diff --git a/src/components/organisms/NavigatorPane/components/ResourcesSection.tsx b/src/components/organisms/NavigatorPane/components/ResourcesSection.tsx index 1ac1eeaee7..8687cbb5e9 100644 --- a/src/components/organisms/NavigatorPane/components/ResourcesSection.tsx +++ b/src/components/organisms/NavigatorPane/components/ResourcesSection.tsx @@ -5,7 +5,7 @@ import {useSelector} from 'react-redux'; import {useAppDispatch, useAppSelector} from '@redux/hooks'; import {MonoSectionTitle} from '@components/atoms'; -import {K8sResource} from '@models/k8sresource'; +import {K8sResource, ResourceValidationError} from '@models/k8sresource'; import {NavigatorSection, NavigatorSubSection} from '@models/navigator'; import {activeResourcesSelector} from '@redux/selectors'; import {selectK8sResource} from '@redux/reducers/main'; @@ -20,7 +20,8 @@ import Section from './Section'; import {ALL_NAMESPACES} from '../constants'; -const ResourcesSection = () => { +const ResourcesSection = (props: {showErrorsModal: (errors: ResourceValidationError[]) => void}) => { + const {showErrorsModal} = props; const dispatch = useAppDispatch(); const appConfig = useAppSelector(state => state.config); const resourceMap = useAppSelector(state => state.main.resourceMap); @@ -172,6 +173,7 @@ const ResourcesSection = () => { shouldSubsectionBeVisible={shouldSubsectionBeVisible} resources={activeResources} selectResource={selectResource} + showErrorsModal={showErrorsModal} /> ); diff --git a/src/components/organisms/NavigatorPane/components/Section.tsx b/src/components/organisms/NavigatorPane/components/Section.tsx index df7af20c7d..5c877257d3 100644 --- a/src/components/organisms/NavigatorPane/components/Section.tsx +++ b/src/components/organisms/NavigatorPane/components/Section.tsx @@ -3,7 +3,7 @@ import {Collapse} from 'antd'; import styled from 'styled-components'; -import {K8sResource} from '@models/k8sresource'; +import {K8sResource, ResourceValidationError} from '@models/k8sresource'; import {NavigatorSubSection, NavigatorSection} from '@models/navigator'; import {hasIncomingRefs, hasOutgoingRefs, hasUnsatisfiedRefs} from '@redux/services/resourceRefs'; @@ -42,6 +42,7 @@ const Section = (props: { shouldResourceBeVisible: (resource: K8sResource, subsection: NavigatorSubSection) => boolean; shouldSubsectionBeVisible: (subsection: NavigatorSubSection) => boolean; selectResource: (resourceId: string) => void; + showErrorsModal: (errors: ResourceValidationError[]) => void; }) => { const { expandedSubsections, @@ -52,6 +53,7 @@ const Section = (props: { shouldResourceBeVisible, shouldSubsectionBeVisible, selectResource, + showErrorsModal, } = props; const isSubsectionExpanded = (subsectionName: string) => { @@ -72,7 +74,9 @@ const Section = (props: { header={ r.isHighlighted)} + isHighlighted={ + !isSubsectionExpanded(subsection.name) && visibleResources.some(r => r.isHighlighted) + } isSelected={!isSubsectionExpanded(subsection.name) && visibleResources.some(r => r.isSelected)} onExpand={() => onSubsectionExpand(section.name, subsection.name)} onCollapse={() => onSubsectionCollapse(section.name, subsection.name)} @@ -93,6 +97,7 @@ const Section = (props: { hasOutgoingRefs={Boolean(hasOutgoingRefs(resource))} hasUnsatisfiedRefs={Boolean(hasUnsatisfiedRefs(resource))} onClickResource={() => selectResource(resource.id)} + showErrorsModal={showErrorsModal} /> ))}