Skip to content

Commit

Permalink
feat: show resource validation errors in a scrollable modal when the …
Browse files Browse the repository at this point in the history
…error icon is pressed
  • Loading branch information
devcatalin committed Aug 26, 2021
1 parent 444bf97 commit 7541c9f
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -15,6 +14,7 @@ export type NavigatorResourceRowProps = {
hasOutgoingRefs: boolean;
hasUnsatisfiedRefs: boolean;
onClickResource?: React.MouseEventHandler<HTMLDivElement>;
showErrorsModal?: (errors: ResourceValidationError[]) => void;
};

const ItemRow = styled(Row)`
Expand Down Expand Up @@ -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.
Expand All @@ -93,6 +94,7 @@ const NavigatorResourceRow = (props: NavigatorResourceRowProps) => {
hasOutgoingRefs={hasOutgoingRefs}
hasUnsatisfiedRefs={hasUnsatisfiedRefs}
onClickLabel={onClickResource}
showErrorsModal={showErrorsModal}
/>
</div>
</SectionCol>
Expand Down
26 changes: 24 additions & 2 deletions src/components/molecules/NavigatorRowLabel/NavigatorRowLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -30,6 +30,7 @@ type NavigatorRowLabelProps = {
hasOutgoingRefs: boolean;
hasUnsatisfiedRefs?: boolean;
onClickLabel?: React.MouseEventHandler<HTMLDivElement>;
showErrorsModal?: (errors: ResourceValidationError[]) => void;
};

const StyledDivider = styled(Divider)`
Expand Down Expand Up @@ -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}>`
Expand Down Expand Up @@ -183,6 +185,7 @@ const NavigatorRowLabel = (props: NavigatorRowLabelProps) => {
hasOutgoingRefs,
hasUnsatisfiedRefs,
onClickLabel,
showErrorsModal,
} = props;

const dispatch = useAppDispatch();
Expand All @@ -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]);
Expand Down Expand Up @@ -295,7 +304,20 @@ const NavigatorRowLabel = (props: NavigatorRowLabelProps) => {
</Popover>
)}
{resource && resource.validation && !resource.validation.isValid && (
<MonoIcon type={MonoIconTypes.Error} style={{marginLeft: 5, color: Colors.redError}} />
<Popover
placement="right"
content={
<div>
<span>
{resource.validation.errors.length} error{resource.validation.errors.length !== 1 && 's'}
</span>
</div>
}
>
<StyledIconsContainer onClick={onClickErrorIcon}>
<MonoIcon type={MonoIconTypes.Error} style={{marginLeft: 5, color: Colors.redError}} />
</StyledIconsContainer>
</Popover>
)}
</StyledLabelContainer>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<Modal centered visible={isVisible} onCancel={() => onClose()} footer={null}>
<StyledContainer>
<StyledErrorList>
{errors.map(error => {
return (
<li>
<StyledErrorProperty>{error.property}</StyledErrorProperty>
<StyledErrorMessage>{error.message}</StyledErrorMessage>
</li>
);
})}
</StyledErrorList>
</StyledContainer>
</Modal>
);
};

export default ValidationErrorsModal;
1 change: 1 addition & 0 deletions src/components/molecules/ValidationErrorsModal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {default} from './ValidationErrorsModal';
37 changes: 34 additions & 3 deletions src/components/organisms/NavigatorPane/NavigatorPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -131,6 +139,8 @@ const NavigatorPane = () => {
const kustomizations = useSelector(kustomizationsSelector);
const isInClusterMode = useSelector(isInClusterModeSelector);

const [isValidationsErrorsModalVisible, setValidationsErrorsVisible] = useState<boolean>(false);
const [currentValidationErrors, setCurrentValidationErrors] = useState<ResourceValidationError[]>([]);
const [expandedSections, setExpandedSections] = useState<string[]>(['kustomizations', 'helmcharts']);

const expandSection = (sectionName: string) => {
Expand All @@ -153,8 +163,23 @@ const NavigatorPane = () => {
}
}, [selectedResourceId]);

const showValidationsErrorsModal = (errors: ResourceValidationError[]) => {
setValidationsErrorsVisible(true);
setCurrentValidationErrors(errors);
};

const hideValidationsErrorsModal = () => {
setValidationsErrorsVisible(false);
setCurrentValidationErrors([]);
};

return (
<>
<ValidationErrorsModal
errors={currentValidationErrors}
isVisible={isValidationsErrorsModalVisible}
onClose={hideValidationsErrorsModal}
/>
<TitleRow>
<MonoPaneTitleCol span={24}>
<MonoPaneTitle>
Expand Down Expand Up @@ -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)
)
}
/>
Expand Down Expand Up @@ -216,7 +243,11 @@ const NavigatorPane = () => {
</StyledCollapse>
)}

{uiState.isFolderLoading || previewLoader.isLoading ? <StyledSkeleton /> : <ResourcesSection />}
{uiState.isFolderLoading || previewLoader.isLoading ? (
<StyledSkeleton />
) : (
<ResourcesSection showErrorsModal={showValidationsErrorsModal} />
)}
</NavigatorPaneContainer>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);
Expand Down Expand Up @@ -172,6 +173,7 @@ const ResourcesSection = () => {
shouldSubsectionBeVisible={shouldSubsectionBeVisible}
resources={activeResources}
selectResource={selectResource}
showErrorsModal={showErrorsModal}
/>
</div>
);
Expand Down
9 changes: 7 additions & 2 deletions src/components/organisms/NavigatorPane/components/Section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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,
Expand All @@ -52,6 +53,7 @@ const Section = (props: {
shouldResourceBeVisible,
shouldSubsectionBeVisible,
selectResource,
showErrorsModal,
} = props;

const isSubsectionExpanded = (subsectionName: string) => {
Expand All @@ -72,7 +74,9 @@ const Section = (props: {
header={
<SubsectionHeader
isExpanded={isSubsectionExpanded(subsection.name)}
isHighlighted={!isSubsectionExpanded(subsection.name) && visibleResources.some(r => 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)}
Expand All @@ -93,6 +97,7 @@ const Section = (props: {
hasOutgoingRefs={Boolean(hasOutgoingRefs(resource))}
hasUnsatisfiedRefs={Boolean(hasUnsatisfiedRefs(resource))}
onClickResource={() => selectResource(resource.id)}
showErrorsModal={showErrorsModal}
/>
))}
</SectionCol>
Expand Down

0 comments on commit 7541c9f

Please sign in to comment.