diff --git a/.eslintrc b/.eslintrc index 0de7f9bd00b..ddbf14fe09f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -119,6 +119,7 @@ "pficon", "poolsurl", "popstate", + "posinset", "pqr", "ptable", "puppetclass", @@ -141,6 +142,7 @@ "scrollable", "scsi", "securityfailure", + "setsize", "sizex", "sizey", "Solaris", diff --git a/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/BuildModal.js b/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/BuildModal.js index ede2dc23c28..4cdc39184a1 100644 --- a/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/BuildModal.js +++ b/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/BuildModal.js @@ -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'; @@ -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(); @@ -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 ( { statusNumber={ERROR_STATUS_STATE} /> - + )} diff --git a/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/ErrorsTree/ErrorsTree.js b/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/ErrorsTree/ErrorsTree.js new file mode 100644 index 00000000000..ddf8bbab16c --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/ErrorsTree/ErrorsTree.js @@ -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 [ + , + + ...childRows, + ...renderPrimaryRows( + remainingNodes, + level, + posinset + 1, + rowIndex + 1 + childRows.length, + isHidden + ), + ]; + }; + + const renderSecondaryRows = ( + [node, ...remainingNodes], + level, + posinset, + rowIndex, + isHidden = false + ) => { + if (!node) { + return []; + } + + return [ + , + + ...renderSecondaryRows( + remainingNodes, + level, + posinset + 1, + rowIndex + 1, + isHidden + ), + ]; + }; + + return ( + + {renderPrimaryRows(props.data)} + + ); +}; + +ErrorsTree.propTypes = { + data: PropTypes.array, +}; + +ErrorsTree.defaultProps = { + data: [], +}; diff --git a/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/ErrorsTree/components/ErrorPresenter.js b/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/ErrorsTree/components/ErrorPresenter.js new file mode 100644 index 00000000000..14a9877ba0d --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/ErrorsTree/components/ErrorPresenter.js @@ -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 => ( + + {props.errorMessage} + +); + +ErrorPresenter.propTypes = { + errorMessage: PropTypes.string, +}; + +ErrorPresenter.defaultProps = { + errorMessage: '', +}; diff --git a/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/ErrorsTree/components/ErrorsTreePrimaryRow.js b/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/ErrorsTree/components/ErrorsTreePrimaryRow.js new file mode 100644 index 00000000000..fb6849c0ce7 --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/ErrorsTree/components/ErrorsTreePrimaryRow.js @@ -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 ( + + + <> + {props.node.name}{' '} + + {props.node.children.length} + + + + + ); +}; + +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: {}, +}; diff --git a/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/ErrorsTree/components/ErrorsTreeSecondaryRow.js b/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/ErrorsTree/components/ErrorsTreeSecondaryRow.js new file mode 100644 index 00000000000..484a03caed2 --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/ErrorsTree/components/ErrorsTreeSecondaryRow.js @@ -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 ( + + + + + + ); +}; + +ErrorsTreeSecondaryRow.propTypes = { + posinset: PropTypes.number, + isHidden: PropTypes.bool, + node: PropTypes.object, +}; + +ErrorsTreeSecondaryRow.defaultProps = { + posinset: 0, + isHidden: false, + node: {}, +};