Skip to content

Commit

Permalink
Fixes #37718 - BuildModal error message not copyable
Browse files Browse the repository at this point in the history
- Replace TreeView component with Tree-Table
- Remove unneeded active element management from tree component
  • Loading branch information
Thorben-D authored and sbernhard committed Sep 17, 2024
1 parent 1a5c1b9 commit 9f02910
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 12 deletions.
2 changes: 2 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
"pficon",
"poolsurl",
"popstate",
"posinset",
"pqr",
"ptable",
"puppetclass",
Expand All @@ -141,6 +142,7 @@
"scrollable",
"scsi",
"securityfailure",
"setsize",
"sizex",
"sizey",
"Solaris",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import PropTypes from 'prop-types';
import React, { useState } from 'react';
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
Modal,
ModalVariant,
Button,
Alert,
TreeView,
Stack,
StackItem,
} from '@patternfly/react-core';
Expand All @@ -21,9 +20,9 @@ import { selectBuildErrorsTree, selectNoErrorState } from './Selectors';
import { buildHost } from './actions';
import StatusIcon from '../Status/StatusIcon';
import { ERROR_STATUS_STATE, OK_STATUS_STATE } from '../Status/Constants';
import { ErrorsTree } from './ErrorsTree/ErrorsTree';

const BuildModal = ({ isModalOpen, onClose, hostFriendlyId, hostName }) => {
const [activeErrors, setActiveErrors] = useState();
const errorsTree = useSelector(selectBuildErrorsTree);
const noErrors = useSelector(selectNoErrorState);
const dispach = useDispatch();
Expand All @@ -32,9 +31,6 @@ const BuildModal = ({ isModalOpen, onClose, hostFriendlyId, hostName }) => {
foremanUrl(`/hosts/${hostFriendlyId}/review_before_build`),
API_OPTIONS
);
const onSelectError = (evt, treeViewItem) => {
setActiveErrors([treeViewItem]);
};

return (
<Modal
Expand Down Expand Up @@ -107,12 +103,7 @@ const BuildModal = ({ isModalOpen, onClose, hostFriendlyId, hostName }) => {
statusNumber={ERROR_STATUS_STATE}
/>

<TreeView
data={errorsTree}
activeItems={activeErrors}
onSelect={onSelectError}
hasBadges
/>
<ErrorsTree data={errorsTree} />
</>
)}
</SkeletonLoader>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import React from 'react';
import { TableComposable, Tbody } from '@patternfly/react-table';
import PropTypes from 'prop-types';
import { ErrorsTreePrimaryRow } from './components/ErrorsTreePrimaryRow';
import { ErrorsTreeSecondaryRow } from './components/ErrorsTreeSecondaryRow';

export const ErrorsTree = props => {
const [expandedNodeNames, setExpandedNodeNames] = React.useState([]);
const [
expandedDetailsNodeNames,
setExpandedDetailsNodeNames,
] = React.useState([]);

const renderPrimaryRows = (
[node, ...remainingNodes],
level = 1,
posinset = 1,
rowIndex = 0,
isHidden = false
) => {
if (!node) {
return [];
}
const isExpanded = expandedNodeNames.includes(node.id);
const isDetailsExpanded = expandedDetailsNodeNames.includes(node.id);

const childRows =
node.children && node.children.length
? renderSecondaryRows(
node.children,
level + 1,
1,
rowIndex + 1,
!isExpanded || isHidden
)
: [];
return [
<ErrorsTreePrimaryRow
posinset={posinset}
key={node.id}
node={node}
isExpanded={isExpanded}
isDetailsExpanded={isDetailsExpanded}
setExpandedNodeNames={setExpandedNodeNames}
setExpandedDetailsNodeNames={setExpandedDetailsNodeNames}
/>,

...childRows,
...renderPrimaryRows(
remainingNodes,
level,
posinset + 1,
rowIndex + 1 + childRows.length,
isHidden
),
];
};

const renderSecondaryRows = (
[node, ...remainingNodes],
level,
posinset,
rowIndex,
isHidden = false
) => {
if (!node) {
return [];
}

return [
<ErrorsTreeSecondaryRow
posinset={posinset}
key={rowIndex}
node={node}
isHidden={isHidden}
/>,

...renderSecondaryRows(
remainingNodes,
level,
posinset + 1,
rowIndex + 1,
isHidden
),
];
};

return (
<TableComposable
ouiaId="errorsTreeTable"
isTreeTable
aria-label="Tree table"
variant="compact"
>
<Tbody>{renderPrimaryRows(props.data)}</Tbody>
</TableComposable>
);
};

ErrorsTree.propTypes = {
data: PropTypes.array,
};

ErrorsTree.defaultProps = {
data: [],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ClipboardCopy } from '@patternfly/react-core';
import { translate as __ } from '../../../../../../react_app/common/I18n';

export const ErrorPresenter = props => (
<ClipboardCopy
isReadOnly
isBlock
hoverTip={__('Copy')}
clickTip={__('Copied!')}
variant="inline-compact"
>
{props.errorMessage}
</ClipboardCopy>
);

ErrorPresenter.propTypes = {
errorMessage: PropTypes.string,
};

ErrorPresenter.defaultProps = {
errorMessage: '',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from 'react';
import { Td, TreeRowWrapper } from '@patternfly/react-table';
import { Badge } from '@patternfly/react-core';
import PropTypes from 'prop-types';

export const ErrorsTreePrimaryRow = props => {
const { isExpanded } = props;
const { isDetailsExpanded } = props;

const treeRow = {
onCollapse: () =>
props.setExpandedNodeNames(prevExpanded => {
const otherExpandedNodeNames = prevExpanded.filter(
name => name !== props.node.id
);
return props.isExpanded
? otherExpandedNodeNames
: [...otherExpandedNodeNames, props.node.id];
}),
onToggleRowDetails: () =>
props.setExpandedDetailsNodeNames(prevDetailsExpanded => {
const otherDetailsExpandedNodeNames = prevDetailsExpanded.filter(
name => name !== props.node.id
);
return isDetailsExpanded
? otherDetailsExpandedNodeNames
: [...otherDetailsExpandedNodeNames, props.node.id];
}),
props: {
isExpanded,
isDetailsExpanded,
'aria-level': 1,
'aria-posinset': props.posinset,
'aria-setsize': props.node.children ? props.node.children.length : 0,
},
};

return (
<TreeRowWrapper
row={{
props: treeRow.props,
}}
>
<Td treeRow={treeRow}>
<>
{props.node.name}{' '}
<Badge key={`${props.node.name}_badge`} isRead>
{props.node.children.length}
</Badge>
</>
</Td>
</TreeRowWrapper>
);
};

ErrorsTreePrimaryRow.propTypes = {
posinset: PropTypes.number,
isExpanded: PropTypes.bool,
isDetailsExpanded: PropTypes.bool,
setExpandedNodeNames: PropTypes.func,
setExpandedDetailsNodeNames: PropTypes.func,
node: PropTypes.object,
};

ErrorsTreePrimaryRow.defaultProps = {
posinset: 0,
isExpanded: false,
isDetailsExpanded: false,
setExpandedNodeNames: () => {},
setExpandedDetailsNodeNames: () => {},
node: {},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import { Td, TreeRowWrapper } from '@patternfly/react-table';
import PropTypes from 'prop-types';
import { ErrorPresenter } from './ErrorPresenter';

export const ErrorsTreeSecondaryRow = props => {
const { isHidden } = props;

const treeRow = {
props: {
isHidden,
'aria-level': 2,
'aria-posinset': props.posinset,
'aria-setsize': 0,
},
};

return (
<TreeRowWrapper
row={{
props: treeRow.props,
}}
>
<Td dataLabel="Variable" treeRow={treeRow}>
<ErrorPresenter errorMessage={props.node?.name} />
</Td>
</TreeRowWrapper>
);
};

ErrorsTreeSecondaryRow.propTypes = {
posinset: PropTypes.number,
isHidden: PropTypes.bool,
node: PropTypes.object,
};

ErrorsTreeSecondaryRow.defaultProps = {
posinset: 0,
isHidden: false,
node: {},
};

0 comments on commit 9f02910

Please sign in to comment.