Merge pull request #5982 from marshmalien/5866-remove-datalist-alert-modal

Remove DataList component overrides

Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
softwarefactory-project-zuul[bot]
2020-02-20 23:17:05 +00:00
committed by GitHub
91 changed files with 1149 additions and 1455 deletions

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<body> <body>
<div id="app"></div> <div id="app" style="height: 100%"></div>
<script src="/bundle.js"></script> <script src="/bundle.js"></script>
</body> </body>
</html> </html>

View File

@@ -204,7 +204,7 @@ class App extends Component {
/> />
<AlertModal <AlertModal
isOpen={configError} isOpen={configError}
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
onClose={this.handleConfigErrorClose} onClose={this.handleConfigErrorClose}
> >

View File

@@ -1,117 +0,0 @@
// https://github.com/patternfly/patternfly-react/issues/1294
#app {
height: 100%;
}
//
// data list overrides
//
.pf-c-data-list {
--pf-global--target-size--MinHeight: 32px;
--pf-global--target-size--MinWidth: 32px;
--pf-global--FontSize--md: 14px;
.pf-c-badge:not(:last-child),
.pf-c-switch:not(:last-child) {
margin-right: 18px;
}
}
.pf-c-data-list__item-row {
--pf-c-data-list__item-row--PaddingRight: 20px;
--pf-c-data-list__item-row--PaddingLeft: 20px;
}
.pf-c-data-list__item-content {
--pf-c-data-list__item-content--PaddingBottom: 16px;
min-height: 59px;
align-items: center;
}
.pf-c-data-list__item-control {
--pf-c-data-list__item-control--PaddingTop: 16px;
--pf-c-data-list__item-control--MarginRight: 8px;
--pf-c-data-list__item-control--PaddingBottom: 16px;
}
.pf-c-data-list__item {
--pf-c-data-list__item--PaddingLeft: 20px;
--pf-c-data-list__item--PaddingRight: 20px;
}
.pf-c-data-list__cell {
--pf-c-data-list__cell--PaddingTop: 16px;
--pf-c-data-list__cell-cell--PaddingTop: 16px;
&.pf-c-data-list__cell--divider {
--pf-c-data-list__cell-cell--MarginRight: 0;
--pf-c-data-list__cell--PaddingTop: 12px;
flex-grow: 0;
}
}
//
// AlertModal styles
//
.at-c-alertModal.pf-c-modal-box {
border: 0;
border-left: 56px solid black;
.at-c-alertModal__icon {
position: absolute;
font-size: 23px;
top: 28px;
left: -39px;
}
}
.at-c-alertModal--warning.pf-c-modal-box {
border-color: var(--pf-global--warning-color--100);
.pf-c-title {
color: var(--pf-global--warning-color--200);
}
.at-c-alertModal__icon {
color: var(--pf-global--warning-color--200);
}
}
.at-c-alertModal--danger.pf-c-modal-box {
border-color: var(--pf-global--danger-color--100);
.pf-c-title {
color: var(--pf-global--danger-color--200);
}
.at-c-alertModal__icon {
color: white;
}
}
.at-c-alertModal--info.pf-c-modal-box {
border-color: var(--pf-global--info-color--100);
.pf-c-title {
color: var(--pf-global--info-color--200);
}
.at-c-alertModal__icon {
color: var(--pf-global--info-color--200);
}
}
.at-c-alertModal--success.pf-c-modal-box {
border-color: var(--pf-global--success-color--100);
.pf-c-title {
color: var(--pf-global--success-color--200);
}
.at-c-alertModal__icon {
color: var(--pf-global--success-color--200);
}
}

View File

@@ -1,42 +1,47 @@
import React from 'react'; import React from 'react';
import { Modal, Title } from '@patternfly/react-core';
import { Modal } from '@patternfly/react-core';
import { import {
ExclamationTriangleIcon,
ExclamationCircleIcon,
InfoCircleIcon,
CheckCircleIcon, CheckCircleIcon,
ExclamationCircleIcon,
ExclamationTriangleIcon,
InfoCircleIcon,
TimesCircleIcon,
} from '@patternfly/react-icons'; } from '@patternfly/react-icons';
import styled from 'styled-components';
const getIcon = variant => { const Header = styled.div`
let icon; display: flex;
if (variant === 'warning') { svg {
icon = <ExclamationTriangleIcon className="at-c-alertModal__icon" />; margin-right: 16px;
} else if (variant === 'danger') {
icon = <ExclamationCircleIcon className="at-c-alertModal__icon" />;
} }
if (variant === 'info') { `;
icon = <InfoCircleIcon className="at-c-alertModal__icon" />;
} export default ({ isOpen = null, title, variant, children, ...props }) => {
if (variant === 'success') { const variantIcons = {
icon = <CheckCircleIcon className="at-c-alertModal__icon" />; danger: <ExclamationCircleIcon size="lg" css="color: #c9190b" />,
} error: <TimesCircleIcon size="lg" css="color: #c9190b" />,
return icon; info: <InfoCircleIcon size="lg" css="color: #73bcf7" />,
success: <CheckCircleIcon size="lg" css="color: #92d400" />,
warning: <ExclamationTriangleIcon size="lg" css="color: #f0ab00" />,
}; };
export default ({ variant, children, ...props }) => { const customHeader = (
const { isOpen = null } = props; <Header>
props.isOpen = Boolean(isOpen); {variant ? variantIcons[variant] : null}
<Title size="2xl">{title}</Title>
</Header>
);
return ( return (
<Modal <Modal
isLarge header={customHeader}
className={`awx-c-modal${variant && isFooterLeftAligned
` at-c-alertModal at-c-alertModal--${variant}`}`} isOpen={Boolean(isOpen)}
isSmall
title={title}
{...props} {...props}
> >
{children} {children}
{getIcon(variant)}
</Modal> </Modal>
); );
}; };

View File

@@ -5,7 +5,9 @@ import AlertModal from './AlertModal';
describe('AlertModal', () => { describe('AlertModal', () => {
test('renders the expected content', () => { test('renders the expected content', () => {
const wrapper = mount(<AlertModal title="Danger!" />); const wrapper = mount(
<AlertModal title="Danger!">Are you sure?</AlertModal>
);
expect(wrapper).toHaveLength(1); expect(wrapper).toHaveLength(1);
}); });
}); });

View File

@@ -6,19 +6,21 @@ import {
DataListItemCells, DataListItemCells,
DataListCell, DataListCell,
DataListCheck, DataListCheck,
Radio,
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import DataListRadio from '@components/DataListRadio';
const CheckboxListItem = ({ const CheckboxListItem = ({
isDisabled = false,
isRadio = false,
isSelected = false,
itemId, itemId,
name,
label, label,
isSelected, name,
onSelect,
onDeselect, onDeselect,
isRadio, onSelect,
}) => { }) => {
const CheckboxRadio = isRadio ? DataListRadio : DataListCheck; const CheckboxRadio = isRadio ? Radio : DataListCheck;
return ( return (
<DataListItem <DataListItem
key={itemId} key={itemId}
@@ -27,11 +29,14 @@ const CheckboxListItem = ({
> >
<DataListItemRow> <DataListItemRow>
<CheckboxRadio <CheckboxRadio
id={`selected-${itemId}`} aria-label={`check-action-item-${itemId}`}
checked={isSelected}
onChange={isSelected ? onDeselect : onSelect}
aria-labelledby={`check-action-item-${itemId}`} aria-labelledby={`check-action-item-${itemId}`}
checked={isSelected}
disabled={isDisabled}
id={`selected-${itemId}`}
isChecked={isSelected}
name={name} name={name}
onChange={isSelected ? onDeselect : onSelect}
value={itemId} value={itemId}
/> />
<DataListItemCells <DataListItemCells
@@ -53,12 +58,12 @@ const CheckboxListItem = ({
}; };
CheckboxListItem.propTypes = { CheckboxListItem.propTypes = {
itemId: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
isSelected: PropTypes.bool.isRequired, isSelected: PropTypes.bool.isRequired,
onSelect: PropTypes.func.isRequired, itemId: PropTypes.number.isRequired,
label: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
onDeselect: PropTypes.func.isRequired, onDeselect: PropTypes.func.isRequired,
onSelect: PropTypes.func.isRequired,
}; };
export default CheckboxListItem; export default CheckboxListItem;

View File

@@ -2,9 +2,9 @@ import React, { useState } from 'react';
import { string, func, bool, number } from 'prop-types'; import { string, func, bool, number } from 'prop-types';
import { Button, Split, SplitItem } from '@patternfly/react-core'; import { Button, Split, SplitItem } from '@patternfly/react-core';
import styled from 'styled-components'; import styled from 'styled-components';
import ButtonGroup from '@components/ButtonGroup';
import { yamlToJson, jsonToYaml, isJson } from '@util/yaml'; import { yamlToJson, jsonToYaml, isJson } from '@util/yaml';
import CodeMirrorInput from './CodeMirrorInput'; import CodeMirrorInput from './CodeMirrorInput';
import ButtonGroup from './ButtonGroup';
import { JSON_MODE, YAML_MODE } from './constants'; import { JSON_MODE, YAML_MODE } from './constants';
function formatJson(jsonString) { function formatJson(jsonString) {

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { oneOf, func } from 'prop-types'; import { oneOf, func } from 'prop-types';
import styled from 'styled-components'; import styled from 'styled-components';
import { Button } from '@patternfly/react-core'; import { Button } from '@patternfly/react-core';
import ButtonGroup from '../ButtonGroup'; import ButtonGroup from './ButtonGroup';
const SmallButton = styled(Button)` const SmallButton = styled(Button)`
padding: 3px 8px; padding: 3px 8px;

View File

@@ -1,12 +1,11 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import styled from 'styled-components';
import { Link, Redirect } from 'react-router-dom'; import { Link, Redirect } from 'react-router-dom';
import { bool, instanceOf } from 'prop-types'; import { bool, instanceOf } from 'prop-types';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { import {
Title, Title,
EmptyState as PFEmptyState, EmptyState,
EmptyStateIcon, EmptyStateIcon,
EmptyStateBody, EmptyStateBody,
} from '@patternfly/react-core'; } from '@patternfly/react-core';
@@ -14,11 +13,6 @@ import { ExclamationTriangleIcon } from '@patternfly/react-icons';
import { RootAPI } from '@api'; import { RootAPI } from '@api';
import ErrorDetail from '@components/ErrorDetail'; import ErrorDetail from '@components/ErrorDetail';
const EmptyState = styled(PFEmptyState)`
width: var(--pf-c-empty-state--m-lg--MaxWidth);
margin: 0 auto;
`;
async function logout() { async function logout() {
await RootAPI.logout(); await RootAPI.logout();
window.location.replace('/#/login'); window.location.replace('/#/login');

View File

@@ -1,14 +0,0 @@
import { DataListCell as PFDataListCell } from '@patternfly/react-core';
import styled from 'styled-components';
const DataListCell = styled(PFDataListCell)`
display: flex;
align-items: center;
padding-bottom: ${props => (props.righthalf ? '16px' : '8px')};
@media screen and (min-width: 768px) {
padding-bottom: 0;
justify-content: ${props => (props.lastcolumn ? 'flex-end' : 'inherit')};
}
`;
export default DataListCell;

View File

@@ -1,11 +0,0 @@
import React from 'react';
import { mountWithContexts } from '@testUtils/enzymeHelpers';
import DataListCell from './DataListCell';
describe('DataListCell', () => {
test('renders without failing', () => {
const wrapper = mountWithContexts(<DataListCell />);
expect(wrapper).toHaveLength(1);
});
});

View File

@@ -1 +0,0 @@
export { default } from './DataListCell';

View File

@@ -1,15 +0,0 @@
import { DataListCheck as PFDataListCheck } from '@patternfly/react-core';
import styled from 'styled-components';
PFDataListCheck.displayName = 'PFDataListCheck';
export default styled(PFDataListCheck)`
padding-top: 18px;
@media screen and (min-width: 768px) {
padding-top: 16px;
justify-content: ${props => (props.lastcolumn ? 'flex-end' : 'inherit')};
.pf-c-data-list__check {
display: flex;
align-items: center;
}
}
`;

View File

@@ -1,10 +0,0 @@
import React from 'react';
import { mount } from 'enzyme';
import DataListCheck from './DataListCheck';
describe('DataListCheck', () => {
test('renders the expected content', () => {
const wrapper = mount(<DataListCheck checked aria-labelledby="Checkbox" />);
expect(wrapper).toHaveLength(1);
});
});

View File

@@ -1 +0,0 @@
export { default } from './DataListCheck';

View File

@@ -1,47 +0,0 @@
import * as React from 'react';
import { string, bool, func } from 'prop-types';
function DataListRadio({
className = '',
onChange,
isValid = true,
isDisabled = false,
isChecked = null,
checked = null,
...props
}) {
return (
<div className={`pf-c-data-list__item-control ${className}`}>
<div className="pf-c-data-list__check">
<input
{...props}
type="radio"
onChange={event => onChange(event.currentTarget.checked, event)}
aria-invalid={!isValid}
disabled={isDisabled}
checked={isChecked || checked}
/>
</div>
</div>
);
}
DataListRadio.propTypes = {
className: string,
isValid: bool,
isDisabled: bool,
isChecked: bool,
checked: bool,
onChange: func,
'aria-labelledby': string,
};
DataListRadio.defaultProps = {
className: '',
isValid: true,
isDisabled: false,
isChecked: false,
checked: false,
onChange: () => {},
'aria-labelledby': '',
};
export default DataListRadio;

View File

@@ -1,36 +0,0 @@
import React from 'react';
import { mountWithContexts } from '@testUtils/enzymeHelpers';
import DataListRadio from './DataListRadio';
describe('DataListRadio', () => {
test('should call onChange', () => {
const onChange = jest.fn();
const wrapper = mountWithContexts(<DataListRadio onChange={onChange} />);
wrapper.find('input[type="radio"]').prop('onChange')({
currentTarget: { checked: true },
});
expect(onChange).toHaveBeenCalledWith(true, {
currentTarget: { checked: true },
});
});
test('should pass props to correct children', () => {
const onChange = jest.fn();
const wrapper = mountWithContexts(
<DataListRadio
onChange={onChange}
className="foo"
isValid
isDisabled
checked
/>
);
const div = wrapper.find('.pf-c-data-list__item-control');
const input = wrapper.find('input[type="radio"]');
expect(div.prop('className')).toEqual('pf-c-data-list__item-control foo');
expect(input.prop('disabled')).toBe(true);
expect(input.prop('checked')).toBe(true);
expect(input.prop('aria-invalid')).toBe(false);
});
});

View File

@@ -1 +0,0 @@
export { default } from './DataListRadio';

View File

@@ -7,10 +7,10 @@ import styled from 'styled-components';
import { SearchIcon } from '@patternfly/react-icons'; import { SearchIcon } from '@patternfly/react-icons';
import { import {
DataToolbar, DataToolbar,
DataToolbarContent, DataToolbarContent as _DataToolbarContent,
DataToolbarGroup, DataToolbarGroup as _DataToolbarGroup,
DataToolbarToggleGroup,
DataToolbarItem, DataToolbarItem,
DataToolbarToggleGroup,
} from '@patternfly/react-core/dist/umd/experimental'; } from '@patternfly/react-core/dist/umd/experimental';
import ExpandCollapse from '../ExpandCollapse'; import ExpandCollapse from '../ExpandCollapse';
import Search from '../Search'; import Search from '../Search';
@@ -18,27 +18,12 @@ import Sort from '../Sort';
import { SearchColumns, SortColumns, QSConfig } from '@types'; import { SearchColumns, SortColumns, QSConfig } from '@types';
const AdditionalControlsWrapper = styled.div` const DataToolbarContent = styled(_DataToolbarContent)`
display: flex; --pf-c-data-toolbar__content--PaddingLeft: 24px;
flex-grow: 1; --pf-c-data-toolbar__content--PaddingRight: 8px;
justify-content: flex-end;
align-items: center;
& > :not(:first-child) {
margin-left: 20px;
}
`; `;
const DataToolbarGroup = styled(_DataToolbarGroup)`
const AdditionalControlsDataToolbarGroup = styled(DataToolbarGroup)` --pf-c-data-toolbar__group--spacer: 24px;
margin-left: auto;
margin-right: 0 !important;
`;
const DataToolbarSeparator = styled(DataToolbarItem)`
width: 1px !important;
height: 30px !important;
margin-left: 3px !important;
margin-right: 10px !important;
`; `;
class DataListToolbar extends React.Component { class DataListToolbar extends React.Component {
@@ -80,7 +65,6 @@ class DataListToolbar extends React.Component {
id="select-all" id="select-all"
/> />
</DataToolbarItem> </DataToolbarItem>
<DataToolbarSeparator variant="separator" />
</DataToolbarGroup> </DataToolbarGroup>
)} )}
<DataToolbarToggleGroup toggleIcon={<SearchIcon />} breakpoint="lg"> <DataToolbarToggleGroup toggleIcon={<SearchIcon />} breakpoint="lg">
@@ -110,13 +94,11 @@ class DataListToolbar extends React.Component {
</Fragment> </Fragment>
)} )}
</DataToolbarGroup> </DataToolbarGroup>
<AdditionalControlsDataToolbarGroup> <DataToolbarGroup css="margin-left: auto">
<DataToolbarItem> {additionalControls.map(control => (
<AdditionalControlsWrapper> <DataToolbarItem key={control.key}>{control}</DataToolbarItem>
{additionalControls} ))}
</AdditionalControlsWrapper> </DataToolbarGroup>
</DataToolbarItem>
</AdditionalControlsDataToolbarGroup>
</DataToolbarContent> </DataToolbarContent>
</DataToolbar> </DataToolbar>
); );

View File

@@ -3,7 +3,6 @@ import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Button } from '@patternfly/react-core'; import { Button } from '@patternfly/react-core';
import AlertModal from '@components/AlertModal'; import AlertModal from '@components/AlertModal';
import { CardActionsRow } from '@components/Card';
function DeleteButton({ function DeleteButton({
onConfirm, onConfirm,
@@ -29,26 +28,28 @@ function DeleteButton({
title={modalTitle} title={modalTitle}
variant="danger" variant="danger"
onClose={() => setIsOpen(false)} onClose={() => setIsOpen(false)}
> actions={[
{i18n._(t`Are you sure you want to delete:`)}
<br />
<strong>{name}</strong>
<CardActionsRow>
<Button
variant="secondary"
aria-label={i18n._(t`Cancel`)}
onClick={() => setIsOpen(false)}
>
{i18n._(t`Cancel`)}
</Button>
<Button <Button
key="delete"
variant="danger" variant="danger"
aria-label={i18n._(t`Delete`)} aria-label={i18n._(t`Delete`)}
onClick={onConfirm} onClick={onConfirm}
> >
{i18n._(t`Delete`)} {i18n._(t`Delete`)}
</Button> </Button>,
</CardActionsRow> <Button
key="cancel"
variant="secondary"
aria-label={i18n._(t`Cancel`)}
onClick={() => setIsOpen(false)}
>
{i18n._(t`Cancel`)}
</Button>,
]}
>
{i18n._(t`Are you sure you want to delete:`)}
<br />
<strong>{name}</strong>
</AlertModal> </AlertModal>
</> </>
); );

View File

@@ -128,7 +128,7 @@ class LaunchButton extends React.Component {
{launchError && ( {launchError && (
<AlertModal <AlertModal
isOpen={launchError} isOpen={launchError}
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
onClose={this.handleLaunchErrorClose} onClose={this.handleLaunchErrorClose}
> >

View File

@@ -8,7 +8,6 @@ import { CredentialsAPI, CredentialTypesAPI } from '@api';
import AnsibleSelect from '@components/AnsibleSelect'; import AnsibleSelect from '@components/AnsibleSelect';
import { FieldTooltip } from '@components/FormField'; import { FieldTooltip } from '@components/FormField';
import CredentialChip from '@components/CredentialChip'; import CredentialChip from '@components/CredentialChip';
import VerticalSeperator from '@components/VerticalSeparator';
import { getQSConfig, parseQueryString } from '@util/qs'; import { getQSConfig, parseQueryString } from '@util/qs';
import Lookup from './Lookup'; import Lookup from './Lookup';
import OptionsList from './shared/OptionsList'; import OptionsList from './shared/OptionsList';
@@ -97,8 +96,9 @@ function MultiCredentialsLookup(props) {
<Fragment> <Fragment>
{credentialTypes && credentialTypes.length > 0 && ( {credentialTypes && credentialTypes.length > 0 && (
<ToolbarItem css=" display: flex; align-items: center;"> <ToolbarItem css=" display: flex; align-items: center;">
<div css="flex: 0 0 25%;">{i18n._(t`Selected Category`)}</div> <div css="flex: 0 0 25%; margin-right: 32px">
<VerticalSeperator /> {i18n._(t`Selected Category`)}
</div>
<AnsibleSelect <AnsibleSelect
css="flex: 1 1 75%;" css="flex: 1 1 75%;"
id="multiCredentialsLookUp-select" id="multiCredentialsLookUp-select"

View File

@@ -250,7 +250,7 @@ class NotificationList extends Component {
)} )}
/> />
<AlertModal <AlertModal
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
isOpen={toggleError && !toggleLoading} isOpen={toggleError && !toggleLoading}
onClose={this.handleNotificationErrorClose} onClose={this.handleNotificationErrorClose}

View File

@@ -4,24 +4,20 @@ import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { import {
DataListAction as _DataListAction,
DataListCell,
DataListItem, DataListItem,
DataListItemRow,
DataListItemCells, DataListItemCells,
DataListCell as PFDataListCell, DataListItemRow,
Switch, Switch,
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import styled from 'styled-components'; import styled from 'styled-components';
const DataListCell = styled(PFDataListCell)` const DataListAction = styled(_DataListAction)`
display: flex; align-items: center;
justify-content: ${props => (props.righthalf ? 'flex-start' : 'inherit')}; display: grid;
padding-bottom: ${props => (props.righthalf ? '16px' : '8px')}; grid-gap: 16px;
grid-template-columns: repeat(3, max-content);
@media screen and (min-width: 768px) {
justify-content: ${props => (props.righthalf ? 'flex-end' : 'inherit')};
padding-bottom: 0;
}
`; `;
function NotificationListItem(props) { function NotificationListItem(props) {
@@ -51,7 +47,6 @@ function NotificationListItem(props) {
to={{ to={{
pathname: detailUrl, pathname: detailUrl,
}} }}
css="margin-right: 1.5em;"
> >
<b id={`items-list-item-${notification.id}`}> <b id={`items-list-item-${notification.id}`}>
{notification.name} {notification.name}
@@ -61,7 +56,13 @@ function NotificationListItem(props) {
<DataListCell key="type"> <DataListCell key="type">
{typeLabels[notification.notification_type]} {typeLabels[notification.notification_type]}
</DataListCell>, </DataListCell>,
<DataListCell righthalf="true" key="toggles"> ]}
/>
<DataListAction
aria-label="actions"
aria-labelledby={`items-list-item-${notification.id}`}
id={`items-list-item-${notification.id}`}
>
<Switch <Switch
id={`notification-${notification.id}-started-toggle`} id={`notification-${notification.id}-started-toggle`}
label={i18n._(t`Start`)} label={i18n._(t`Start`)}
@@ -69,11 +70,7 @@ function NotificationListItem(props) {
isChecked={startedTurnedOn} isChecked={startedTurnedOn}
isDisabled={!canToggleNotifications} isDisabled={!canToggleNotifications}
onChange={() => onChange={() =>
toggleNotification( toggleNotification(notification.id, startedTurnedOn, 'started')
notification.id,
startedTurnedOn,
'started'
)
} }
aria-label={i18n._(t`Toggle notification start`)} aria-label={i18n._(t`Toggle notification start`)}
/> />
@@ -84,11 +81,7 @@ function NotificationListItem(props) {
isChecked={successTurnedOn} isChecked={successTurnedOn}
isDisabled={!canToggleNotifications} isDisabled={!canToggleNotifications}
onChange={() => onChange={() =>
toggleNotification( toggleNotification(notification.id, successTurnedOn, 'success')
notification.id,
successTurnedOn,
'success'
)
} }
aria-label={i18n._(t`Toggle notification success`)} aria-label={i18n._(t`Toggle notification success`)}
/> />
@@ -103,9 +96,7 @@ function NotificationListItem(props) {
} }
aria-label={i18n._(t`Toggle notification failure`)} aria-label={i18n._(t`Toggle notification failure`)}
/> />
</DataListCell>, </DataListAction>
]}
/>
</DataListItemRow> </DataListItemRow>
</DataListItem> </DataListItem>
); );

View File

@@ -42,7 +42,7 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
<DataListItemCells <DataListItemCells
dataListCells={ dataListCells={
Array [ Array [
<ForwardRef> <DataListCell>
<ForwardRef <ForwardRef
to={ to={
Object { Object {
@@ -56,41 +56,10 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
Foo Foo
</b> </b>
</ForwardRef> </ForwardRef>
</ForwardRef>, </DataListCell>,
<ForwardRef> <DataListCell>
Slack Slack
</ForwardRef>, </DataListCell>,
<ForwardRef
righthalf="true"
>
<Unknown
aria-label="Toggle notification start"
id="notification-9000-started-toggle"
isChecked={false}
isDisabled={false}
label="Start"
labelOff="Start"
onChange={[Function]}
/>
<Unknown
aria-label="Toggle notification success"
id="notification-9000-success-toggle"
isChecked={false}
isDisabled={false}
label="Success"
labelOff="Success"
onChange={[Function]}
/>
<Unknown
aria-label="Toggle notification failure"
id="notification-9000-error-toggle"
isChecked={false}
isDisabled={false}
label="Failure"
labelOff="Failure"
onChange={[Function]}
/>
</ForwardRef>,
] ]
} }
key=".0" key=".0"
@@ -99,103 +68,13 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
<div <div
className="pf-c-data-list__item-content" className="pf-c-data-list__item-content"
> >
<NotificationListItem__DataListCell <DataListCell
key="name" key="name"
>
<StyledComponent
forwardedComponent={
Object {
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
"componentId": "NotificationListItem__DataListCell-w674ng-0",
"isStatic": false,
"lastClassName": "dXsFLF",
"rules": Array [
"display:flex;justify-content:",
[Function],
";padding-bottom:",
[Function],
";@media screen and (min-width:768px){justify-content:",
[Function],
";padding-bottom:0;}",
],
},
"displayName": "NotificationListItem__DataListCell",
"foldedComponentIds": Array [],
"render": [Function],
"styledComponentId": "NotificationListItem__DataListCell-w674ng-0",
"target": [Function],
"toString": [Function],
"warnTooManyClasses": [Function],
"withComponent": [Function],
}
}
forwardedRef={null}
>
<DataListCell
className="NotificationListItem__DataListCell-w674ng-0 faYgxF"
> >
<div <div
className="pf-c-data-list__cell NotificationListItem__DataListCell-w674ng-0 faYgxF" className="pf-c-data-list__cell"
>
<Styled(Link)
to={
Object {
"pathname": "/foo",
}
}
>
<StyledComponent
forwardedComponent={
Object {
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
"componentId": "sc-bdVaJa",
"isStatic": true,
"lastClassName": "eBseNd",
"rules": Array [
"margin-right: 1.5em;",
],
},
"displayName": "Styled(Link)",
"foldedComponentIds": Array [],
"propTypes": Object {
"innerRef": [Function],
"onClick": [Function],
"replace": [Function],
"target": [Function],
"to": [Function],
},
"render": [Function],
"styledComponentId": "sc-bdVaJa",
"target": Object {
"$$typeof": Symbol(react.forward_ref),
"displayName": "Link",
"propTypes": Object {
"innerRef": [Function],
"onClick": [Function],
"replace": [Function],
"target": [Function],
"to": [Function],
},
"render": [Function],
},
"toString": [Function],
"warnTooManyClasses": [Function],
"withComponent": [Function],
}
}
forwardedRef={null}
to={
Object {
"pathname": "/foo",
}
}
> >
<Link <Link
className="sc-bdVaJa eBseNd"
to={ to={
Object { Object {
"pathname": "/foo", "pathname": "/foo",
@@ -203,12 +82,10 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
} }
> >
<LinkAnchor <LinkAnchor
className="sc-bdVaJa eBseNd"
href="/foo" href="/foo"
navigate={[Function]} navigate={[Function]}
> >
<a <a
className="sc-bdVaJa eBseNd"
href="/foo" href="/foo"
onClick={[Function]} onClick={[Function]}
> >
@@ -220,84 +97,45 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
</a> </a>
</LinkAnchor> </LinkAnchor>
</Link> </Link>
</StyledComponent>
</Styled(Link)>
</div> </div>
</DataListCell> </DataListCell>
</StyledComponent> <DataListCell
</NotificationListItem__DataListCell>
<NotificationListItem__DataListCell
key="type" key="type"
>
<StyledComponent
forwardedComponent={
Object {
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
"componentId": "NotificationListItem__DataListCell-w674ng-0",
"isStatic": false,
"lastClassName": "dXsFLF",
"rules": Array [
"display:flex;justify-content:",
[Function],
";padding-bottom:",
[Function],
";@media screen and (min-width:768px){justify-content:",
[Function],
";padding-bottom:0;}",
],
},
"displayName": "NotificationListItem__DataListCell",
"foldedComponentIds": Array [],
"render": [Function],
"styledComponentId": "NotificationListItem__DataListCell-w674ng-0",
"target": [Function],
"toString": [Function],
"warnTooManyClasses": [Function],
"withComponent": [Function],
}
}
forwardedRef={null}
>
<DataListCell
className="NotificationListItem__DataListCell-w674ng-0 faYgxF"
> >
<div <div
className="pf-c-data-list__cell NotificationListItem__DataListCell-w674ng-0 faYgxF" className="pf-c-data-list__cell"
> >
Slack Slack
</div> </div>
</DataListCell> </DataListCell>
</StyledComponent> </div>
</NotificationListItem__DataListCell> </DataListItemCells>
<NotificationListItem__DataListCell <NotificationListItem__DataListAction
key="toggles" aria-label="actions"
righthalf="true" aria-labelledby="items-list-item-9000"
id="items-list-item-9000"
key=".1"
rowid="items-list-item-9000"
> >
<StyledComponent <StyledComponent
aria-label="actions"
aria-labelledby="items-list-item-9000"
forwardedComponent={ forwardedComponent={
Object { Object {
"$$typeof": Symbol(react.forward_ref), "$$typeof": Symbol(react.forward_ref),
"attrs": Array [], "attrs": Array [],
"componentStyle": ComponentStyle { "componentStyle": ComponentStyle {
"componentId": "NotificationListItem__DataListCell-w674ng-0", "componentId": "NotificationListItem__DataListAction-w674ng-0",
"isStatic": false, "isStatic": true,
"lastClassName": "dXsFLF", "lastClassName": "hhZchj",
"rules": Array [ "rules": Array [
"display:flex;justify-content:", "align-items:center;display:grid;grid-gap:16px;grid-template-columns:repeat(3,max-content);",
[Function],
";padding-bottom:",
[Function],
";@media screen and (min-width:768px){justify-content:",
[Function],
";padding-bottom:0;}",
], ],
}, },
"displayName": "NotificationListItem__DataListCell", "displayName": "NotificationListItem__DataListAction",
"foldedComponentIds": Array [], "foldedComponentIds": Array [],
"render": [Function], "render": [Function],
"styledComponentId": "NotificationListItem__DataListCell-w674ng-0", "styledComponentId": "NotificationListItem__DataListAction-w674ng-0",
"target": [Function], "target": [Function],
"toString": [Function], "toString": [Function],
"warnTooManyClasses": [Function], "warnTooManyClasses": [Function],
@@ -305,15 +143,19 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
} }
} }
forwardedRef={null} forwardedRef={null}
righthalf="true" id="items-list-item-9000"
rowid="items-list-item-9000"
> >
<DataListCell <DataListAction
className="NotificationListItem__DataListCell-w674ng-0 dXsFLF" aria-label="actions"
righthalf="true" aria-labelledby="items-list-item-9000"
className="NotificationListItem__DataListAction-w674ng-0 hhZchj"
id="items-list-item-9000"
rowid="items-list-item-9000"
> >
<div <div
className="pf-c-data-list__cell NotificationListItem__DataListCell-w674ng-0 dXsFLF" className="pf-c-data-list__item-action NotificationListItem__DataListAction-w674ng-0 hhZchj"
righthalf="true" rowid="items-list-item-9000"
> >
<Component <Component
aria-label="Toggle notification start" aria-label="Toggle notification start"
@@ -541,11 +383,9 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
</ComponentWithOuia> </ComponentWithOuia>
</Component> </Component>
</div> </div>
</DataListCell> </DataListAction>
</StyledComponent> </StyledComponent>
</NotificationListItem__DataListCell> </NotificationListItem__DataListAction>
</div>
</DataListItemCells>
</div> </div>
</DataListItemRow> </DataListItemRow>
</li> </li>

View File

@@ -137,6 +137,7 @@ class ToolbarDeleteButton extends React.Component {
render() { render() {
const { itemsToDelete, pluralizedItemName, i18n } = this.props; const { itemsToDelete, pluralizedItemName, i18n } = this.props;
const { isModalOpen } = this.state; const { isModalOpen } = this.state;
const modalTitle = i18n._(t`Delete ${pluralizedItemName}?`);
const isDisabled = const isDisabled =
itemsToDelete.length === 0 || itemsToDelete.some(cannotDelete); itemsToDelete.length === 0 || itemsToDelete.some(cannotDelete);
@@ -161,7 +162,7 @@ class ToolbarDeleteButton extends React.Component {
{isModalOpen && ( {isModalOpen && (
<AlertModal <AlertModal
variant="danger" variant="danger"
title={pluralizedItemName} title={modalTitle}
isOpen={isModalOpen} isOpen={isModalOpen}
onClose={this.handleCancelDelete} onClose={this.handleCancelDelete}
actions={[ actions={[
@@ -183,15 +184,13 @@ class ToolbarDeleteButton extends React.Component {
</Button>, </Button>,
]} ]}
> >
{i18n._(t`Are you sure you want to delete:`)} <div>{i18n._(t`This action will delete the following:`)}</div>
<br />
{itemsToDelete.map(item => ( {itemsToDelete.map(item => (
<span key={item.id}> <span key={item.id}>
<strong>{item.name || item.username}</strong> <strong>{item.name || item.username}</strong>
<br /> <br />
</span> </span>
))} ))}
<br />
</AlertModal> </AlertModal>
)} )}
</Fragment> </Fragment>

View File

@@ -232,7 +232,7 @@ class ResourceAccessList extends React.Component {
)} )}
<AlertModal <AlertModal
isOpen={hasDeletionError} isOpen={hasDeletionError}
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
onClose={this.handleDeleteErrorClose} onClose={this.handleDeleteErrorClose}
> >

View File

@@ -73,7 +73,7 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
aria-describedby="pf-modal-0" aria-describedby="pf-modal-0"
aria-label="Remove {0} Access" aria-label="Remove {0} Access"
aria-modal="true" aria-modal="true"
class="pf-c-modal-box awx-c-modal at-c-alertModal at-c-alertModal--danger pf-m-lg" class="pf-c-modal-box pf-m-sm"
role="dialog" role="dialog"
> >
<button <button
@@ -96,13 +96,34 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
/> />
</svg> </svg>
</button> </button>
<div
class="pf-c-title"
>
<div
class="AlertModal__Header-sc-9waqvl-0 dYqVFx"
>
<svg
aria-hidden="true"
class="sc-bdVaJa bkDjEn"
fill="currentColor"
height="2em"
role="img"
style="vertical-align: -0.25em;"
viewBox="0 0 512 512"
width="2em"
>
<path
d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"
transform=""
/>
</svg>
<h1 <h1
class="pf-c-title pf-m-2xl" class="pf-c-title pf-m-2xl"
> >
Remove {0} Access Remove {0} Access
</h1> </h1>
</div>
</div>
<div <div
class="pf-c-modal-box__body" class="pf-c-modal-box__body"
id="pf-modal-0" id="pf-modal-0"
@@ -111,24 +132,9 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
<br /> <br />
<br /> <br />
If you only want to remove access for this particular user, please remove them from the team. If you only want to remove access for this particular user, please remove them from the team.
<svg
aria-hidden="true"
class="at-c-alertModal__icon"
fill="currentColor"
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 0 512 512"
width="1em"
>
<path
d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"
transform=""
/>
</svg>
</div> </div>
<div <div
class="pf-c-modal-box__footer" class="pf-c-modal-box__footer pf-m-align-left"
> >
<button <button
aria-label="Confirm delete" aria-label="Confirm delete"
@@ -151,12 +157,24 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
</body> </body>
} }
ariaDescribedById="" ariaDescribedById=""
className="awx-c-modal at-c-alertModal at-c-alertModal--danger" className=""
header={
<ForwardRef>
<ForwardRef
size="lg"
/>
<Title
size="2xl"
>
Remove {0} Access
</Title>
</ForwardRef>
}
hideTitle={false} hideTitle={false}
isFooterLeftAligned={false} isFooterLeftAligned={true}
isLarge={true} isLarge={false}
isOpen={true} isOpen={true}
isSmall={false} isSmall={true}
onClose={[Function]} onClose={[Function]}
showClose={true} showClose={true}
title="Remove {0} Access" title="Remove {0} Access"
@@ -174,7 +192,7 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
aria-describedby="pf-modal-0" aria-describedby="pf-modal-0"
aria-label="Remove {0} Access" aria-label="Remove {0} Access"
aria-modal="true" aria-modal="true"
class="pf-c-modal-box awx-c-modal at-c-alertModal at-c-alertModal--danger pf-m-lg" class="pf-c-modal-box pf-m-sm"
role="dialog" role="dialog"
> >
<button <button
@@ -197,13 +215,34 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
/> />
</svg> </svg>
</button> </button>
<div
class="pf-c-title"
>
<div
class="AlertModal__Header-sc-9waqvl-0 dYqVFx"
>
<svg
aria-hidden="true"
class="sc-bdVaJa bkDjEn"
fill="currentColor"
height="2em"
role="img"
style="vertical-align: -0.25em;"
viewBox="0 0 512 512"
width="2em"
>
<path
d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"
transform=""
/>
</svg>
<h1 <h1
class="pf-c-title pf-m-2xl" class="pf-c-title pf-m-2xl"
> >
Remove {0} Access Remove {0} Access
</h1> </h1>
</div>
</div>
<div <div
class="pf-c-modal-box__body" class="pf-c-modal-box__body"
id="pf-modal-0" id="pf-modal-0"
@@ -212,24 +251,9 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
<br /> <br />
<br /> <br />
If you only want to remove access for this particular user, please remove them from the team. If you only want to remove access for this particular user, please remove them from the team.
<svg
aria-hidden="true"
class="at-c-alertModal__icon"
fill="currentColor"
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 0 512 512"
width="1em"
>
<path
d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"
transform=""
/>
</svg>
</div> </div>
<div <div
class="pf-c-modal-box__footer" class="pf-c-modal-box__footer pf-m-align-left"
> >
<button <button
aria-label="Confirm delete" aria-label="Confirm delete"
@@ -270,13 +294,25 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
] ]
} }
ariaDescribedById="" ariaDescribedById=""
className="awx-c-modal at-c-alertModal at-c-alertModal--danger" className=""
header={
<ForwardRef>
<ForwardRef
size="lg"
/>
<Title
size="2xl"
>
Remove {0} Access
</Title>
</ForwardRef>
}
hideTitle={false} hideTitle={false}
id="pf-modal-0" id="pf-modal-0"
isFooterLeftAligned={false} isFooterLeftAligned={true}
isLarge={true} isLarge={false}
isOpen={true} isOpen={true}
isSmall={false} isSmall={true}
onClose={[Function]} onClose={[Function]}
showClose={true} showClose={true}
title="Remove {0} Access" title="Remove {0} Access"
@@ -301,10 +337,10 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
className="pf-l-bullseye" className="pf-l-bullseye"
> >
<ModalBox <ModalBox
className="awx-c-modal at-c-alertModal at-c-alertModal--danger" className=""
id="pf-modal-0" id="pf-modal-0"
isLarge={true} isLarge={false}
isSmall={false} isSmall={true}
style={Object {}} style={Object {}}
title="Remove {0} Access" title="Remove {0} Access"
> >
@@ -312,7 +348,7 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
aria-describedby="pf-modal-0" aria-describedby="pf-modal-0"
aria-label="Remove {0} Access" aria-label="Remove {0} Access"
aria-modal="true" aria-modal="true"
className="pf-c-modal-box awx-c-modal at-c-alertModal at-c-alertModal--danger pf-m-lg" className="pf-c-modal-box pf-m-sm"
role="dialog" role="dialog"
style={Object {}} style={Object {}}
> >
@@ -395,23 +431,110 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
</ComponentWithOuia> </ComponentWithOuia>
</Component> </Component>
</ModalBoxCloseButton> </ModalBoxCloseButton>
<ModalBoxHeader <div
hideTitle={false} className="pf-c-title"
> >
<AlertModal__Header>
<StyledComponent
forwardedComponent={
Object {
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
"componentId": "AlertModal__Header-sc-9waqvl-0",
"isStatic": true,
"lastClassName": "dYqVFx",
"rules": Array [
"display:flex;svg{margin-right:16px;}",
],
},
"displayName": "AlertModal__Header",
"foldedComponentIds": Array [],
"render": [Function],
"styledComponentId": "AlertModal__Header-sc-9waqvl-0",
"target": "div",
"toString": [Function],
"warnTooManyClasses": [Function],
"withComponent": [Function],
}
}
forwardedRef={null}
>
<div
className="AlertModal__Header-sc-9waqvl-0 dYqVFx"
>
<Styled(ExclamationCircleIcon)
size="lg"
>
<StyledComponent
forwardedComponent={
Object {
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
"componentId": "sc-bdVaJa",
"isStatic": true,
"lastClassName": "bkDjEn",
"rules": Array [
"color: #c9190b",
],
},
"displayName": "Styled(ExclamationCircleIcon)",
"foldedComponentIds": Array [],
"render": [Function],
"styledComponentId": "sc-bdVaJa",
"target": [Function],
"toString": [Function],
"warnTooManyClasses": [Function],
"withComponent": [Function],
}
}
forwardedRef={null}
size="lg"
>
<ExclamationCircleIcon
className="sc-bdVaJa bkDjEn"
color="currentColor"
noVerticalAlign={false}
size="lg"
title={null}
>
<svg
aria-hidden={true}
aria-labelledby={null}
className="sc-bdVaJa bkDjEn"
fill="currentColor"
height="2em"
role="img"
style={
Object {
"verticalAlign": "-0.25em",
}
}
viewBox="0 0 512 512"
width="2em"
>
<path
d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"
transform=""
/>
</svg>
</ExclamationCircleIcon>
</StyledComponent>
</Styled(ExclamationCircleIcon)>
<Title <Title
className=""
headingLevel="h1"
size="2xl" size="2xl"
> >
<h1 <h1
className="pf-c-title pf-m-2xl" className="pf-c-title pf-m-2xl"
> >
Remove {0} Access Remove {0} Access
</h1> </h1>
</Title> </Title>
</ModalBoxHeader> </div>
</StyledComponent>
</AlertModal__Header>
</div>
<ModalBoxBody <ModalBoxBody
id="pf-modal-0" id="pf-modal-0"
> >
@@ -423,41 +546,13 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
<br /> <br />
<br /> <br />
If you only want to remove access for this particular user, please remove them from the team. If you only want to remove access for this particular user, please remove them from the team.
<ExclamationCircleIcon
className="at-c-alertModal__icon"
color="currentColor"
noVerticalAlign={false}
size="sm"
title={null}
>
<svg
aria-hidden={true}
aria-labelledby={null}
className="at-c-alertModal__icon"
fill="currentColor"
height="1em"
role="img"
style={
Object {
"verticalAlign": "-0.125em",
}
}
viewBox="0 0 512 512"
width="1em"
>
<path
d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"
transform=""
/>
</svg>
</ExclamationCircleIcon>
</div> </div>
</ModalBoxBody> </ModalBoxBody>
<ModalBoxFooter <ModalBoxFooter
isLeftAligned={false} isLeftAligned={true}
> >
<div <div
className="pf-c-modal-box__footer" className="pf-c-modal-box__footer pf-m-align-left"
> >
<Component <Component
aria-label="Confirm delete" aria-label="Confirm delete"

View File

@@ -7,7 +7,6 @@ import {
SplitItem, SplitItem,
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import styled from 'styled-components'; import styled from 'styled-components';
import VerticalSeparator from '../VerticalSeparator';
const Split = styled(PFSplit)` const Split = styled(PFSplit)`
margin: 20px 0px; margin: 20px 0px;
@@ -16,6 +15,7 @@ const Split = styled(PFSplit)`
const SplitLabelItem = styled(SplitItem)` const SplitLabelItem = styled(SplitItem)`
font-weight: bold; font-weight: bold;
margin-right: 32px;
word-break: initial; word-break: initial;
`; `;
@@ -41,7 +41,6 @@ class SelectedList extends Component {
return ( return (
<Split> <Split>
<SplitLabelItem>{label}</SplitLabelItem> <SplitLabelItem>{label}</SplitLabelItem>
<VerticalSeparator />
<SplitItem> <SplitItem>
<ChipGroup numChips={5}> <ChipGroup numChips={5}>
{selected.map(item => {selected.map(item =>

View File

@@ -2,7 +2,7 @@ import React, { Fragment } from 'react';
import { arrayOf, object } from 'prop-types'; import { arrayOf, object } from 'prop-types';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { Link as _Link } from 'react-router-dom'; import { Link as _Link } from 'react-router-dom';
import { StatusIcon } from '@components/Sparkline'; import StatusIcon from '@components/StatusIcon';
import { Tooltip } from '@patternfly/react-core'; import { Tooltip } from '@patternfly/react-core';
import styled from 'styled-components'; import styled from 'styled-components';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
@@ -13,6 +13,10 @@ import { JOB_TYPE_URL_SEGMENTS } from '@constants';
const Link = styled(props => <_Link {...props} />)` const Link = styled(props => <_Link {...props} />)`
margin-right: 5px; margin-right: 5px;
`; `;
const Wrapper = styled.div`
display: inline-flex;
`;
/* eslint-enable react/jsx-pascal-case */ /* eslint-enable react/jsx-pascal-case */
const Sparkline = ({ i18n, jobs }) => { const Sparkline = ({ i18n, jobs }) => {
@@ -32,13 +36,15 @@ const Sparkline = ({ i18n, jobs }) => {
</Fragment> </Fragment>
); );
return jobs.map(job => ( const statusIcons = jobs.map(job => (
<Tooltip position="top" content={generateTooltip(job)} key={job.id}> <Tooltip position="top" content={generateTooltip(job)} key={job.id}>
<Link to={`/jobs/${JOB_TYPE_URL_SEGMENTS[job.type]}/${job.id}`}> <Link to={`/jobs/${JOB_TYPE_URL_SEGMENTS[job.type]}/${job.id}`}>
<StatusIcon status={job.status} /> <StatusIcon status={job.status} />
</Link> </Link>
</Tooltip> </Tooltip>
)); ));
return <Wrapper>{statusIcons}</Wrapper>;
}; };
Sparkline.propTypes = { Sparkline.propTypes = {

View File

@@ -1,2 +1 @@
export { default as Sparkline } from './Sparkline'; export { default } from './Sparkline';
export { default as StatusIcon } from './StatusIcon';

View File

@@ -12,8 +12,12 @@ const Pulse = keyframes`
`; `;
const Wrapper = styled.div` const Wrapper = styled.div`
width: 14px; align-items: center;
display: flex;
flex-flow: column nowrap;
height: 14px; height: 14px;
margin: 5px 0;
width: 14px;
`; `;
const WhiteTop = styled.div` const WhiteTop = styled.div`

View File

@@ -0,0 +1 @@
export { default } from './StatusIcon';

View File

@@ -1,20 +0,0 @@
import React from 'react';
import styled from 'styled-components';
const Separator = styled.span`
display: inline-block;
width: 1px;
height: 30px;
margin-right: 20px;
margin-left: 20px;
background-color: #d7d7d7;
vertical-align: middle;
`;
const VerticalSeparator = () => (
<div>
<Separator />
</div>
);
export default VerticalSeparator;

View File

@@ -1,11 +0,0 @@
import React from 'react';
import { mount } from 'enzyme';
import VerticalSeparator from './VerticalSeparator';
describe('VerticalSeparator', () => {
test('renders the expected content', () => {
const wrapper = mount(<VerticalSeparator />);
expect(wrapper).toHaveLength(1);
});
});

View File

@@ -1 +0,0 @@
export { default } from './VerticalSeparator';

View File

@@ -4,7 +4,7 @@ import WorkflowActionTooltipItem from './WorkflowActionTooltipItem';
describe('WorkflowActionTooltipItem', () => { describe('WorkflowActionTooltipItem', () => {
test('successfully mounts', () => { test('successfully mounts', () => {
const wrapper = mount(<WorkflowActionTooltipItem />); const wrapper = mount(<WorkflowActionTooltipItem id="node" />);
expect(wrapper).toHaveLength(1); expect(wrapper).toHaveLength(1);
}); });
}); });

View File

@@ -5,7 +5,6 @@ import { I18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import '@patternfly/react-core/dist/styles/base.css'; import '@patternfly/react-core/dist/styles/base.css';
import './app.scss';
import { isAuthenticated } from '@util/auth'; import { isAuthenticated } from '@util/auth';
import Background from '@components/Background'; import Background from '@components/Background';

View File

@@ -158,7 +158,7 @@ function CredentialDetail({ i18n, credential }) {
{deletionError && ( {deletionError && (
<AlertModal <AlertModal
isOpen={deletionError} isOpen={deletionError}
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
onClose={() => setDeletionError(null)} onClose={() => setDeletionError(null)}
> >

View File

@@ -125,9 +125,9 @@ function CredentialList({ i18n }) {
itemsToDelete={selected} itemsToDelete={selected}
pluralizedItemName={i18n._(t`Credentials`)} pluralizedItemName={i18n._(t`Credentials`)}
/>, />,
canAdd && ( ...(canAdd
<ToolbarAddButton key="add" linkTo="/credentials/add" /> ? [<ToolbarAddButton key="add" linkTo="/credentials/add" />]
), : []),
]} ]}
/> />
)} )}
@@ -135,7 +135,7 @@ function CredentialList({ i18n }) {
</Card> </Card>
<AlertModal <AlertModal
isOpen={deletionError} isOpen={deletionError}
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
onClose={clearDeletionError} onClose={clearDeletionError}
> >

View File

@@ -54,44 +54,44 @@ describe('<CredentialList />', () => {
test('should check and uncheck the row item', async () => { test('should check and uncheck the row item', async () => {
expect( expect(
wrapper.find('PFDataListCheck[id="select-credential-1"]').props().checked wrapper.find('DataListCheck[id="select-credential-1"]').props().checked
).toBe(false); ).toBe(false);
await act(async () => { await act(async () => {
wrapper wrapper
.find('PFDataListCheck[id="select-credential-1"]') .find('DataListCheck[id="select-credential-1"]')
.invoke('onChange')(true); .invoke('onChange')(true);
}); });
wrapper.update(); wrapper.update();
expect( expect(
wrapper.find('PFDataListCheck[id="select-credential-1"]').props().checked wrapper.find('DataListCheck[id="select-credential-1"]').props().checked
).toBe(true); ).toBe(true);
await act(async () => { await act(async () => {
wrapper wrapper
.find('PFDataListCheck[id="select-credential-1"]') .find('DataListCheck[id="select-credential-1"]')
.invoke('onChange')(false); .invoke('onChange')(false);
}); });
wrapper.update(); wrapper.update();
expect( expect(
wrapper.find('PFDataListCheck[id="select-credential-1"]').props().checked wrapper.find('DataListCheck[id="select-credential-1"]').props().checked
).toBe(false); ).toBe(false);
}); });
test('should check all row items when select all is checked', async () => { test('should check all row items when select all is checked', async () => {
wrapper.find('PFDataListCheck').forEach(el => { wrapper.find('DataListCheck').forEach(el => {
expect(el.props().checked).toBe(false); expect(el.props().checked).toBe(false);
}); });
await act(async () => { await act(async () => {
wrapper.find('Checkbox#select-all').invoke('onChange')(true); wrapper.find('Checkbox#select-all').invoke('onChange')(true);
}); });
wrapper.update(); wrapper.update();
wrapper.find('PFDataListCheck').forEach(el => { wrapper.find('DataListCheck').forEach(el => {
expect(el.props().checked).toBe(true); expect(el.props().checked).toBe(true);
}); });
await act(async () => { await act(async () => {
wrapper.find('Checkbox#select-all').invoke('onChange')(false); wrapper.find('Checkbox#select-all').invoke('onChange')(false);
}); });
wrapper.update(); wrapper.update();
wrapper.find('PFDataListCheck').forEach(el => { wrapper.find('DataListCheck').forEach(el => {
expect(el.props().checked).toBe(false); expect(el.props().checked).toBe(false);
}); });
}); });
@@ -102,7 +102,7 @@ describe('<CredentialList />', () => {
await act(async () => { await act(async () => {
wrapper wrapper
.find('PFDataListCheck[id="select-credential-3"]') .find('DataListCheck[id="select-credential-3"]')
.invoke('onChange')(); .invoke('onChange')();
}); });
wrapper.update(); wrapper.update();
@@ -119,7 +119,7 @@ describe('<CredentialList />', () => {
); );
await act(async () => { await act(async () => {
wrapper wrapper
.find('PFDataListCheck[id="select-credential-2"]') .find('DataListCheck[id="select-credential-2"]')
.invoke('onChange')(); .invoke('onChange')();
}); });
wrapper.update(); wrapper.update();

View File

@@ -5,25 +5,18 @@ import { t } from '@lingui/macro';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { import {
Button, Button,
DataListAction,
DataListCell,
DataListCheck,
DataListItem, DataListItem,
DataListItemRow, DataListItemRow,
DataListItemCells as _DataListItemCells, DataListItemCells,
Tooltip, Tooltip,
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import { PencilAltIcon } from '@patternfly/react-icons'; import { PencilAltIcon } from '@patternfly/react-icons';
import DataListCell from '@components/DataListCell';
import DataListCheck from '@components/DataListCheck';
import VerticalSeparator from '@components/VerticalSeparator';
import styled from 'styled-components';
import { Credential } from '@types'; import { Credential } from '@types';
const DataListItemCells = styled(_DataListItemCells)`
${DataListCell}:first-child {
flex-grow: 2;
}
`;
function CredentialListItem({ function CredentialListItem({
credential, credential,
detailUrl, detailUrl,
@@ -50,7 +43,6 @@ function CredentialListItem({
<DataListItemCells <DataListItemCells
dataListCells={[ dataListCells={[
<DataListCell key="name"> <DataListCell key="name">
<VerticalSeparator />
<Link to={`${detailUrl}`}> <Link to={`${detailUrl}`}>
<b>{credential.name}</b> <b>{credential.name}</b>
</Link> </Link>
@@ -58,7 +50,13 @@ function CredentialListItem({
<DataListCell key="type"> <DataListCell key="type">
{credential.summary_fields.credential_type.name} {credential.summary_fields.credential_type.name}
</DataListCell>, </DataListCell>,
<DataListCell key="edit" alignRight isFilled={false}> ]}
/>
<DataListAction
aria-label="actions"
aria-labelledby={labelId}
id={labelId}
>
{canEdit && ( {canEdit && (
<Tooltip content={i18n._(t`Edit Credential`)} position="top"> <Tooltip content={i18n._(t`Edit Credential`)} position="top">
<Button <Button
@@ -70,9 +68,7 @@ function CredentialListItem({
</Button> </Button>
</Tooltip> </Tooltip>
)} )}
</DataListCell>, </DataListAction>
]}
/>
</DataListItemRow> </DataListItemRow>
</DataListItem> </DataListItem>
); );

View File

@@ -9,7 +9,7 @@ import AlertModal from '@components/AlertModal';
import ErrorDetail from '@components/ErrorDetail'; import ErrorDetail from '@components/ErrorDetail';
import { DetailList, Detail, UserDateDetail } from '@components/DetailList'; import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
import { VariablesDetail } from '@components/CodeMirrorInput'; import { VariablesDetail } from '@components/CodeMirrorInput';
import { Sparkline } from '@components/Sparkline'; import Sparkline from '@components/Sparkline';
import DeleteButton from '@components/DeleteButton'; import DeleteButton from '@components/DeleteButton';
import { HostsAPI } from '@api'; import { HostsAPI } from '@api';
@@ -69,7 +69,7 @@ function HostDetail({ host, i18n, onUpdateHost }) {
if (toggleError && !toggleLoading) { if (toggleError && !toggleLoading) {
return ( return (
<AlertModal <AlertModal
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
isOpen={toggleError && !toggleLoading} isOpen={toggleError && !toggleLoading}
onClose={() => setToggleError(false)} onClose={() => setToggleError(false)}
@@ -83,7 +83,7 @@ function HostDetail({ host, i18n, onUpdateHost }) {
return ( return (
<AlertModal <AlertModal
isOpen={deletionError} isOpen={deletionError}
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
onClose={() => setDeletionError(false)} onClose={() => setDeletionError(false)}
> >
@@ -107,7 +107,6 @@ function HostDetail({ host, i18n, onUpdateHost }) {
<DetailList gutter="sm"> <DetailList gutter="sm">
<Detail label={i18n._(t`Name`)} value={name} /> <Detail label={i18n._(t`Name`)} value={name} />
<Detail <Detail
css="display: flex; flex: 1;"
value={<Sparkline jobs={recentPlaybookJobs} />} value={<Sparkline jobs={recentPlaybookJobs} />}
label={i18n._(t`Activity`)} label={i18n._(t`Activity`)}
/> />

View File

@@ -224,9 +224,14 @@ class HostsList extends Component {
itemsToDelete={selected} itemsToDelete={selected}
pluralizedItemName={i18n._(t`Hosts`)} pluralizedItemName={i18n._(t`Hosts`)}
/>, />,
canAdd ? ( ...(canAdd
<ToolbarAddButton key="add" linkTo={`${match.url}/add`} /> ? [
) : null, <ToolbarAddButton
key="add"
linkTo={`${match.url}/add`}
/>,
]
: []),
]} ]}
/> />
)} )}
@@ -250,7 +255,7 @@ class HostsList extends Component {
</Card> </Card>
{toggleError && !toggleLoading && ( {toggleError && !toggleLoading && (
<AlertModal <AlertModal
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
isOpen={toggleError && !toggleLoading} isOpen={toggleError && !toggleLoading}
onClose={this.handleHostToggleErrorClose} onClose={this.handleHostToggleErrorClose}
@@ -262,7 +267,7 @@ class HostsList extends Component {
{deletionError && ( {deletionError && (
<AlertModal <AlertModal
isOpen={deletionError} isOpen={deletionError}
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
onClose={this.handleDeleteErrorClose} onClose={this.handleDeleteErrorClose}
> >

View File

@@ -4,6 +4,9 @@ import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { import {
Button, Button,
DataListAction as _DataListAction,
DataListCell,
DataListCheck,
DataListItem, DataListItem,
DataListItemRow, DataListItemRow,
DataListItemCells, DataListItemCells,
@@ -13,11 +16,16 @@ import {
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { PencilAltIcon } from '@patternfly/react-icons'; import { PencilAltIcon } from '@patternfly/react-icons';
import DataListCell from '@components/DataListCell'; import Sparkline from '@components/Sparkline';
import DataListCheck from '@components/DataListCheck';
import { Sparkline } from '@components/Sparkline';
import VerticalSeparator from '@components/VerticalSeparator';
import { Host } from '@types'; import { Host } from '@types';
import styled from 'styled-components';
const DataListAction = styled(_DataListAction)`
align-items: center;
display: grid;
grid-gap: 24px;
grid-template-columns: min-content 40px;
`;
class HostListItem extends React.Component { class HostListItem extends React.Component {
static propTypes = { static propTypes = {
@@ -56,7 +64,6 @@ class HostListItem extends React.Component {
<DataListItemCells <DataListItemCells
dataListCells={[ dataListCells={[
<DataListCell key="name"> <DataListCell key="name">
<VerticalSeparator />
<Link to={`${detailUrl}`}> <Link to={`${detailUrl}`}>
<b>{host.name}</b> <b>{host.name}</b>
</Link> </Link>
@@ -67,9 +74,7 @@ class HostListItem extends React.Component {
<DataListCell key="inventory"> <DataListCell key="inventory">
{host.summary_fields.inventory && ( {host.summary_fields.inventory && (
<Fragment> <Fragment>
<b style={{ marginRight: '20px' }}> <b css="margin-right: 24px">{i18n._(t`Inventory`)}</b>
{i18n._(t`Inventory`)}
</b>
<Link <Link
to={`/inventories/${ to={`/inventories/${
host.summary_fields.inventory.kind === 'smart' host.summary_fields.inventory.kind === 'smart'
@@ -82,7 +87,13 @@ class HostListItem extends React.Component {
</Fragment> </Fragment>
)} )}
</DataListCell>, </DataListCell>,
<DataListCell key="enable" alignRight isFilled={false}> ]}
/>
<DataListAction
aria-label="actions"
aria-labelledby={labelId}
id={labelId}
>
<Tooltip <Tooltip
content={i18n._( content={i18n._(
t`Indicates if a host is available and should be included in running jobs. For hosts that are part of an external inventory, this may be reset by the inventory sync process.` t`Indicates if a host is available and should be included in running jobs. For hosts that are part of an external inventory, this may be reset by the inventory sync process.`
@@ -96,15 +107,12 @@ class HostListItem extends React.Component {
labelOff={i18n._(t`Off`)} labelOff={i18n._(t`Off`)}
isChecked={host.enabled} isChecked={host.enabled}
isDisabled={ isDisabled={
toggleLoading || toggleLoading || !host.summary_fields.user_capabilities.edit
!host.summary_fields.user_capabilities.edit
} }
onChange={() => onToggleHost(host)} onChange={() => onToggleHost(host)}
aria-label={i18n._(t`Toggle host`)} aria-label={i18n._(t`Toggle host`)}
/> />
</Tooltip> </Tooltip>
</DataListCell>,
<DataListCell key="edit" alignRight isFilled={false}>
{host.summary_fields.user_capabilities.edit && ( {host.summary_fields.user_capabilities.edit && (
<Tooltip content={i18n._(t`Edit Host`)} position="top"> <Tooltip content={i18n._(t`Edit Host`)} position="top">
<Button <Button
@@ -116,9 +124,7 @@ class HostListItem extends React.Component {
</Button> </Button>
</Tooltip> </Tooltip>
)} )}
</DataListCell>, </DataListAction>
]}
/>
</DataListItemRow> </DataListItemRow>
</DataListItem> </DataListItem>
); );

View File

@@ -98,7 +98,7 @@ function InventoryGroupDetail({ i18n, inventoryGroup }) {
)} )}
{error && ( {error && (
<AlertModal <AlertModal
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
isOpen={error} isOpen={error}
onClose={() => setError(false)} onClose={() => setError(false)}

View File

@@ -6,18 +6,17 @@ import { Group } from '@types';
import { import {
Button, Button,
DataListAction,
DataListCell,
DataListCheck,
DataListItem, DataListItem,
DataListItemRow,
DataListItemCells, DataListItemCells,
DataListItemRow,
Tooltip, Tooltip,
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { PencilAltIcon } from '@patternfly/react-icons'; import { PencilAltIcon } from '@patternfly/react-icons';
import DataListCell from '@components/DataListCell';
import DataListCheck from '@components/DataListCheck';
import VerticalSeparator from '@components/VerticalSeparator';
function InventoryGroupItem({ function InventoryGroupItem({
i18n, i18n,
group, group,
@@ -40,13 +39,18 @@ function InventoryGroupItem({
/> />
<DataListItemCells <DataListItemCells
dataListCells={[ dataListCells={[
<DataListCell key="divider"> <DataListCell key="name">
<VerticalSeparator />
<Link to={`${detailUrl}`} id={labelId}> <Link to={`${detailUrl}`} id={labelId}>
<b>{group.name}</b> <b>{group.name}</b>
</Link> </Link>
</DataListCell>, </DataListCell>,
<DataListCell key="edit" alignRight isFilled={false}> ]}
/>
<DataListAction
aria-label="actions"
aria-labelledby={labelId}
id={labelId}
>
{group.summary_fields.user_capabilities.edit && ( {group.summary_fields.user_capabilities.edit && (
<Tooltip content={i18n._(t`Edit Group`)} position="top"> <Tooltip content={i18n._(t`Edit Group`)} position="top">
<Button variant="plain" component={Link} to={editUrl}> <Button variant="plain" component={Link} to={editUrl}>
@@ -54,9 +58,7 @@ function InventoryGroupItem({
</Button> </Button>
</Tooltip> </Tooltip>
)} )}
</DataListCell>, </DataListAction>
]}
/>
</DataListItemRow> </DataListItemRow>
</DataListItem> </DataListItem>
); );

View File

@@ -238,12 +238,14 @@ function InventoryGroupsList({ i18n, location, match }) {
</DeleteButton> </DeleteButton>
</div> </div>
</Tooltip>, </Tooltip>,
canAdd && ( ...(canAdd
? [
<ToolbarAddButton <ToolbarAddButton
key="add" key="add"
linkTo={`/inventories/inventory/${inventoryId}/groups/add`} linkTo={`/inventories/inventory/${inventoryId}/groups/add`}
/> />,
), ]
: []),
]} ]}
/> />
)} )}
@@ -259,7 +261,7 @@ function InventoryGroupsList({ i18n, location, match }) {
{deletionError && ( {deletionError && (
<AlertModal <AlertModal
isOpen={deletionError} isOpen={deletionError}
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
onClose={() => setDeletionError(null)} onClose={() => setDeletionError(null)}
> >

View File

@@ -98,46 +98,46 @@ describe('<InventoryGroupsList />', () => {
test('should check and uncheck the row item', async () => { test('should check and uncheck the row item', async () => {
expect( expect(
wrapper.find('PFDataListCheck[id="select-group-1"]').props().checked wrapper.find('DataListCheck[id="select-group-1"]').props().checked
).toBe(false); ).toBe(false);
await act(async () => { await act(async () => {
wrapper.find('PFDataListCheck[id="select-group-1"]').invoke('onChange')( wrapper.find('DataListCheck[id="select-group-1"]').invoke('onChange')(
true true
); );
}); });
wrapper.update(); wrapper.update();
expect( expect(
wrapper.find('PFDataListCheck[id="select-group-1"]').props().checked wrapper.find('DataListCheck[id="select-group-1"]').props().checked
).toBe(true); ).toBe(true);
await act(async () => { await act(async () => {
wrapper.find('PFDataListCheck[id="select-group-1"]').invoke('onChange')( wrapper.find('DataListCheck[id="select-group-1"]').invoke('onChange')(
false false
); );
}); });
wrapper.update(); wrapper.update();
expect( expect(
wrapper.find('PFDataListCheck[id="select-group-1"]').props().checked wrapper.find('DataListCheck[id="select-group-1"]').props().checked
).toBe(false); ).toBe(false);
}); });
test('should check all row items when select all is checked', async () => { test('should check all row items when select all is checked', async () => {
wrapper.find('PFDataListCheck').forEach(el => { wrapper.find('DataListCheck').forEach(el => {
expect(el.props().checked).toBe(false); expect(el.props().checked).toBe(false);
}); });
await act(async () => { await act(async () => {
wrapper.find('Checkbox#select-all').invoke('onChange')(true); wrapper.find('Checkbox#select-all').invoke('onChange')(true);
}); });
wrapper.update(); wrapper.update();
wrapper.find('PFDataListCheck').forEach(el => { wrapper.find('DataListCheck').forEach(el => {
expect(el.props().checked).toBe(true); expect(el.props().checked).toBe(true);
}); });
await act(async () => { await act(async () => {
wrapper.find('Checkbox#select-all').invoke('onChange')(false); wrapper.find('Checkbox#select-all').invoke('onChange')(false);
}); });
wrapper.update(); wrapper.update();
wrapper.find('PFDataListCheck').forEach(el => { wrapper.find('DataListCheck').forEach(el => {
expect(el.props().checked).toBe(false); expect(el.props().checked).toBe(false);
}); });
}); });
@@ -157,7 +157,7 @@ describe('<InventoryGroupsList />', () => {
Promise.reject(new Error()) Promise.reject(new Error())
); );
await act(async () => { await act(async () => {
wrapper.find('PFDataListCheck[id="select-group-1"]').invoke('onChange')(); wrapper.find('DataListCheck[id="select-group-1"]').invoke('onChange')();
}); });
wrapper.update(); wrapper.update();
await act(async () => { await act(async () => {
@@ -191,7 +191,7 @@ describe('<InventoryGroupsList />', () => {
}) })
); );
await act(async () => { await act(async () => {
wrapper.find('PFDataListCheck[id="select-group-1"]').invoke('onChange')(); wrapper.find('DataListCheck[id="select-group-1"]').invoke('onChange')();
}); });
wrapper.update(); wrapper.update();
await act(async () => { await act(async () => {
@@ -213,7 +213,7 @@ describe('<InventoryGroupsList />', () => {
.find('ModalBoxFooter Button[aria-label="Delete"]') .find('ModalBoxFooter Button[aria-label="Delete"]')
.invoke('onClick')(); .invoke('onClick')();
}); });
await waitForElement(wrapper, { title: 'Error!', variant: 'danger' }); await waitForElement(wrapper, { title: 'Error!', variant: 'error' });
await act(async () => { await act(async () => {
wrapper.find('ModalBoxCloseButton').invoke('onClose')(); wrapper.find('ModalBoxCloseButton').invoke('onClose')();
}); });

View File

@@ -4,20 +4,27 @@ import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { import {
Button, Button,
DataListAction as _DataListAction,
DataListCell,
DataListCheck,
DataListItem, DataListItem,
DataListItemRow,
DataListItemCells, DataListItemCells,
DataListItemRow,
Switch, Switch,
Tooltip, Tooltip,
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { PencilAltIcon } from '@patternfly/react-icons'; import { PencilAltIcon } from '@patternfly/react-icons';
import Sparkline from '@components/Sparkline';
import DataListCell from '@components/DataListCell';
import DataListCheck from '@components/DataListCheck';
import { Sparkline } from '@components/Sparkline';
import VerticalSeparator from '@components/VerticalSeparator';
import { Host } from '@types'; import { Host } from '@types';
import styled from 'styled-components';
const DataListAction = styled(_DataListAction)`
align-items: center;
display: grid;
grid-gap: 24px;
grid-template-columns: min-content 40px;
`;
function InventoryHostItem(props) { function InventoryHostItem(props) {
const { const {
@@ -50,7 +57,6 @@ function InventoryHostItem(props) {
<DataListItemCells <DataListItemCells
dataListCells={[ dataListCells={[
<DataListCell key="divider"> <DataListCell key="divider">
<VerticalSeparator />
<Link to={`${detailUrl}`}> <Link to={`${detailUrl}`}>
<b>{host.name}</b> <b>{host.name}</b>
</Link> </Link>
@@ -58,7 +64,13 @@ function InventoryHostItem(props) {
<DataListCell key="recentJobs"> <DataListCell key="recentJobs">
<Sparkline jobs={recentPlaybookJobs} /> <Sparkline jobs={recentPlaybookJobs} />
</DataListCell>, </DataListCell>,
<DataListCell key="enable" alignRight isFilled={false}> ]}
/>
<DataListAction
aria-label="actions"
aria-labelledby={labelId}
id={labelId}
>
<Tooltip <Tooltip
content={i18n._( content={i18n._(
t`Indicates if a host is available and should be included t`Indicates if a host is available and should be included
@@ -74,15 +86,12 @@ function InventoryHostItem(props) {
labelOff={i18n._(t`Off`)} labelOff={i18n._(t`Off`)}
isChecked={host.enabled} isChecked={host.enabled}
isDisabled={ isDisabled={
toggleLoading || toggleLoading || !host.summary_fields.user_capabilities?.edit
!host.summary_fields.user_capabilities?.edit
} }
onChange={() => toggleHost(host)} onChange={() => toggleHost(host)}
aria-label={i18n._(t`Toggle host`)} aria-label={i18n._(t`Toggle host`)}
/> />
</Tooltip> </Tooltip>
</DataListCell>,
<DataListCell key="edit" alignRight isFilled={false}>
{host.summary_fields.user_capabilities?.edit && ( {host.summary_fields.user_capabilities?.edit && (
<Tooltip content={i18n._(t`Edit Host`)} position="top"> <Tooltip content={i18n._(t`Edit Host`)} position="top">
<Button variant="plain" component={Link} to={`${editUrl}`}> <Button variant="plain" component={Link} to={`${editUrl}`}>
@@ -90,9 +99,7 @@ function InventoryHostItem(props) {
</Button> </Button>
</Tooltip> </Tooltip>
)} )}
</DataListCell>, </DataListAction>
]}
/>
</DataListItemRow> </DataListItemRow>
</DataListItem> </DataListItem>
); );

View File

@@ -165,12 +165,14 @@ function InventoryHostList({ i18n, location, match }) {
itemsToDelete={selected} itemsToDelete={selected}
pluralizedItemName={i18n._(t`Hosts`)} pluralizedItemName={i18n._(t`Hosts`)}
/>, />,
canAdd && ( ...(canAdd
? [
<ToolbarAddButton <ToolbarAddButton
key="add" key="add"
linkTo={`/inventories/inventory/${match.params.id}/hosts/add`} linkTo={`/inventories/inventory/${match.params.id}/hosts/add`}
/> />,
), ]
: []),
]} ]}
/> />
)} )}
@@ -198,7 +200,7 @@ function InventoryHostList({ i18n, location, match }) {
{toggleError && !toggleLoading && ( {toggleError && !toggleLoading && (
<AlertModal <AlertModal
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
isOpen={toggleError && !toggleLoading} isOpen={toggleError && !toggleLoading}
onClose={() => setToggleError(false)} onClose={() => setToggleError(false)}
@@ -211,7 +213,7 @@ function InventoryHostList({ i18n, location, match }) {
{deletionError && ( {deletionError && (
<AlertModal <AlertModal
isOpen={deletionError} isOpen={deletionError}
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
onClose={() => setDeletionError(null)} onClose={() => setDeletionError(null)}
> >

View File

@@ -104,48 +104,48 @@ describe('<InventoryHostList />', () => {
test('should check and uncheck the row item', async () => { test('should check and uncheck the row item', async () => {
expect( expect(
wrapper.find('PFDataListCheck[id="select-host-1"]').props().checked wrapper.find('DataListCheck[id="select-host-1"]').props().checked
).toBe(false); ).toBe(false);
await act(async () => { await act(async () => {
wrapper.find('PFDataListCheck[id="select-host-1"]').invoke('onChange')( wrapper.find('DataListCheck[id="select-host-1"]').invoke('onChange')(
true true
); );
}); });
wrapper.update(); wrapper.update();
expect( expect(
wrapper.find('PFDataListCheck[id="select-host-1"]').props().checked wrapper.find('DataListCheck[id="select-host-1"]').props().checked
).toBe(true); ).toBe(true);
await act(async () => { await act(async () => {
wrapper.find('PFDataListCheck[id="select-host-1"]').invoke('onChange')( wrapper.find('DataListCheck[id="select-host-1"]').invoke('onChange')(
false false
); );
}); });
wrapper.update(); wrapper.update();
expect( expect(
wrapper.find('PFDataListCheck[id="select-host-1"]').props().checked wrapper.find('DataListCheck[id="select-host-1"]').props().checked
).toBe(false); ).toBe(false);
}); });
test('should check all row items when select all is checked', async () => { test('should check all row items when select all is checked', async () => {
wrapper.find('PFDataListCheck').forEach(el => { wrapper.find('DataListCheck').forEach(el => {
expect(el.props().checked).toBe(false); expect(el.props().checked).toBe(false);
}); });
await act(async () => { await act(async () => {
wrapper.find('Checkbox#select-all').invoke('onChange')(true); wrapper.find('Checkbox#select-all').invoke('onChange')(true);
}); });
wrapper.update(); wrapper.update();
wrapper.find('PFDataListCheck').forEach(el => { wrapper.find('DataListCheck').forEach(el => {
expect(el.props().checked).toBe(true); expect(el.props().checked).toBe(true);
}); });
await act(async () => { await act(async () => {
wrapper.find('Checkbox#select-all').invoke('onChange')(false); wrapper.find('Checkbox#select-all').invoke('onChange')(false);
}); });
wrapper.update(); wrapper.update();
wrapper.find('PFDataListCheck').forEach(el => { wrapper.find('DataListCheck').forEach(el => {
expect(el.props().checked).toBe(false); expect(el.props().checked).toBe(false);
}); });
}); });
@@ -186,7 +186,7 @@ describe('<InventoryHostList />', () => {
test('delete button is disabled if user does not have delete capabilities on a selected host', async () => { test('delete button is disabled if user does not have delete capabilities on a selected host', async () => {
await act(async () => { await act(async () => {
wrapper.find('PFDataListCheck[id="select-host-3"]').invoke('onChange')(); wrapper.find('DataListCheck[id="select-host-3"]').invoke('onChange')();
}); });
wrapper.update(); wrapper.update();
expect(wrapper.find('ToolbarDeleteButton button').props().disabled).toBe( expect(wrapper.find('ToolbarDeleteButton button').props().disabled).toBe(
@@ -197,7 +197,7 @@ describe('<InventoryHostList />', () => {
test('should call api delete hosts for each selected host', async () => { test('should call api delete hosts for each selected host', async () => {
HostsAPI.destroy = jest.fn(); HostsAPI.destroy = jest.fn();
await act(async () => { await act(async () => {
wrapper.find('PFDataListCheck[id="select-host-1"]').invoke('onChange')(); wrapper.find('DataListCheck[id="select-host-1"]').invoke('onChange')();
}); });
wrapper.update(); wrapper.update();
await act(async () => { await act(async () => {
@@ -220,7 +220,7 @@ describe('<InventoryHostList />', () => {
}) })
); );
await act(async () => { await act(async () => {
wrapper.find('PFDataListCheck[id="select-host-1"]').invoke('onChange')(); wrapper.find('DataListCheck[id="select-host-1"]').invoke('onChange')();
}); });
wrapper.update(); wrapper.update();
await act(async () => { await act(async () => {
@@ -242,7 +242,7 @@ describe('<InventoryHostList />', () => {
Promise.reject(new Error()) Promise.reject(new Error())
); );
await act(async () => { await act(async () => {
wrapper.find('PFDataListCheck[id="select-host-1"]').invoke('onChange')(); wrapper.find('DataListCheck[id="select-host-1"]').invoke('onChange')();
}); });
wrapper.update(); wrapper.update();
await act(async () => { await act(async () => {

View File

@@ -208,7 +208,7 @@ class InventoriesList extends Component {
itemsToDelete={selected} itemsToDelete={selected}
pluralizedItemName="Inventories" pluralizedItemName="Inventories"
/>, />,
canAdd && addButton, ...(canAdd ? [addButton] : []),
]} ]}
/> />
)} )}
@@ -231,7 +231,7 @@ class InventoriesList extends Component {
</Card> </Card>
<AlertModal <AlertModal
isOpen={deletionError} isOpen={deletionError}
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
onClose={this.handleDeleteErrorClose} onClose={this.handleDeleteErrorClose}
> >

View File

@@ -3,18 +3,18 @@ import { string, bool, func } from 'prop-types';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { import {
Button, Button,
DataListAction,
DataListCell,
DataListCheck,
DataListItem, DataListItem,
DataListItemRow,
DataListItemCells, DataListItemCells,
DataListItemRow,
Tooltip, Tooltip,
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { PencilAltIcon } from '@patternfly/react-icons'; import { PencilAltIcon } from '@patternfly/react-icons';
import DataListCell from '@components/DataListCell';
import DataListCheck from '@components/DataListCheck';
import VerticalSeparator from '@components/VerticalSeparator';
import { Inventory } from '@types'; import { Inventory } from '@types';
class InventoryListItem extends React.Component { class InventoryListItem extends React.Component {
@@ -44,7 +44,6 @@ class InventoryListItem extends React.Component {
<DataListItemCells <DataListItemCells
dataListCells={[ dataListCells={[
<DataListCell key="divider"> <DataListCell key="divider">
<VerticalSeparator />
<Link to={`${detailUrl}`}> <Link to={`${detailUrl}`}>
<b>{inventory.name}</b> <b>{inventory.name}</b>
</Link> </Link>
@@ -54,25 +53,27 @@ class InventoryListItem extends React.Component {
? i18n._(t`Smart Inventory`) ? i18n._(t`Smart Inventory`)
: i18n._(t`Inventory`)} : i18n._(t`Inventory`)}
</DataListCell>, </DataListCell>,
<DataListCell key="edit" alignRight isFilled={false}> ]}
/>
<DataListAction
aria-label="actions"
aria-labelledby={labelId}
id={labelId}
>
{inventory.summary_fields.user_capabilities.edit && ( {inventory.summary_fields.user_capabilities.edit && (
<Tooltip content={i18n._(t`Edit Inventory`)} position="top"> <Tooltip content={i18n._(t`Edit Inventory`)} position="top">
<Button <Button
variant="plain" variant="plain"
component={Link} component={Link}
to={`/inventories/${ to={`/inventories/${
inventory.kind === 'smart' inventory.kind === 'smart' ? 'smart_inventory' : 'inventory'
? 'smart_inventory'
: 'inventory'
}/${inventory.id}/edit`} }/${inventory.id}/edit`}
> >
<PencilAltIcon /> <PencilAltIcon />
</Button> </Button>
</Tooltip> </Tooltip>
)} )}
</DataListCell>, </DataListAction>
]}
/>
</DataListItemRow> </DataListItemRow>
</DataListItem> </DataListItem>
); );

View File

@@ -27,7 +27,7 @@ const InventoryGroupsDeleteModal = ({
isOpen={isModalOpen} isOpen={isModalOpen}
variant="danger" variant="danger"
title={ title={
groups.length > 1 ? i18n._(t`Delete Groups`) : i18n._(t`Delete Group`) groups.length > 1 ? i18n._(t`Delete Groups?`) : i18n._(t`Delete Group?`)
} }
onClose={onClose} onClose={onClose}
actions={[ actions={[
@@ -60,7 +60,7 @@ const InventoryGroupsDeleteModal = ({
return <ListItem key={group.id}>{group.name}</ListItem>; return <ListItem key={group.id}>{group.name}</ListItem>;
})} })}
</div> </div>
<div css="padding-left: 1px;"> <div>
<Radio <Radio
id="radio-delete" id="radio-delete"
key="radio-delete" key="radio-delete"

View File

@@ -13,7 +13,7 @@ import { VariablesInput as _VariablesInput } from '@components/CodeMirrorInput';
import DeleteButton from '@components/DeleteButton'; import DeleteButton from '@components/DeleteButton';
import ErrorDetail from '@components/ErrorDetail'; import ErrorDetail from '@components/ErrorDetail';
import LaunchButton from '@components/LaunchButton'; import LaunchButton from '@components/LaunchButton';
import { StatusIcon } from '@components/Sparkline'; import StatusIcon from '@components/StatusIcon';
import { toTitleCase } from '@util/strings'; import { toTitleCase } from '@util/strings';
import { formatDateString } from '@util/dates'; import { formatDateString } from '@util/dates';
import { Job } from '@types'; import { Job } from '@types';
@@ -275,7 +275,7 @@ function JobDetail({ job, i18n }) {
{errorMsg && ( {errorMsg && (
<AlertModal <AlertModal
isOpen={errorMsg} isOpen={errorMsg}
variant="danger" variant="error"
onClose={() => setErrorMsg()} onClose={() => setErrorMsg()}
title={i18n._(t`Job Delete Error`)} title={i18n._(t`Job Delete Error`)}
> >

View File

@@ -236,7 +236,7 @@ function JobList({ i18n }) {
</Card> </Card>
<AlertModal <AlertModal
isOpen={deletionError} isOpen={deletionError}
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
onClose={clearDeletionError} onClose={clearDeletionError}
> >

View File

@@ -23,6 +23,7 @@ const mockResults = [
summary_fields: { summary_fields: {
user_capabilities: { user_capabilities: {
delete: true, delete: true,
start: true,
}, },
}, },
}, },
@@ -34,6 +35,7 @@ const mockResults = [
summary_fields: { summary_fields: {
user_capabilities: { user_capabilities: {
delete: true, delete: true,
start: true,
}, },
}, },
}, },
@@ -45,6 +47,7 @@ const mockResults = [
summary_fields: { summary_fields: {
user_capabilities: { user_capabilities: {
delete: true, delete: true,
start: true,
}, },
}, },
}, },
@@ -56,6 +59,7 @@ const mockResults = [
summary_fields: { summary_fields: {
user_capabilities: { user_capabilities: {
delete: true, delete: true,
start: true,
}, },
}, },
}, },
@@ -67,6 +71,7 @@ const mockResults = [
summary_fields: { summary_fields: {
user_capabilities: { user_capabilities: {
delete: true, delete: true,
edit: true,
}, },
}, },
}, },
@@ -78,6 +83,7 @@ const mockResults = [
summary_fields: { summary_fields: {
user_capabilities: { user_capabilities: {
delete: true, delete: true,
edit: true,
}, },
}, },
}, },

View File

@@ -1,10 +1,10 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { import {
Button, Button,
DataListAction,
DataListCell, DataListCell,
DataListCheck, DataListCheck,
DataListItem, DataListItem,
@@ -14,32 +14,31 @@ import {
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import { RocketIcon } from '@patternfly/react-icons'; import { RocketIcon } from '@patternfly/react-icons';
import LaunchButton from '@components/LaunchButton'; import LaunchButton from '@components/LaunchButton';
import { StatusIcon } from '@components/Sparkline'; import StatusIcon from '@components/StatusIcon';
import { toTitleCase } from '@util/strings'; import { toTitleCase } from '@util/strings';
import { formatDateString } from '@util/dates'; import { formatDateString } from '@util/dates';
import { JOB_TYPE_URL_SEGMENTS } from '@constants'; import { JOB_TYPE_URL_SEGMENTS } from '@constants';
const PaddedIcon = styled(StatusIcon)`
margin-right: 20px;
`;
class JobListItem extends Component { class JobListItem extends Component {
render() { render() {
const { i18n, job, isSelected, onSelect } = this.props; const { i18n, job, isSelected, onSelect } = this.props;
const labelId = `check-action-${job.id}`;
return ( return (
<DataListItem aria-labelledby={`check-action-${job.id}`} id={`${job.id}`}> <DataListItem aria-labelledby={labelId} id={`${job.id}`}>
<DataListItemRow> <DataListItemRow>
<DataListCheck <DataListCheck
id={`select-job-${job.id}`} id={`select-job-${job.id}`}
checked={isSelected} checked={isSelected}
onChange={onSelect} onChange={onSelect}
aria-labelledby={`check-action-${job.id}`} aria-labelledby={labelId}
/> />
<DataListItemCells <DataListItemCells
dataListCells={[ dataListCells={[
<DataListCell key="divider"> <DataListCell key="status" isFilled={false}>
{job.status && <PaddedIcon status={job.status} />} {job.status && <StatusIcon status={job.status} />}
</DataListCell>,
<DataListCell key="name" css="display: inline-flex;">
<span> <span>
<Link <Link
to={`/jobs/${JOB_TYPE_URL_SEGMENTS[job.type]}/${job.id}`} to={`/jobs/${JOB_TYPE_URL_SEGMENTS[job.type]}/${job.id}`}
@@ -54,9 +53,15 @@ class JobListItem extends Component {
<DataListCell key="finished"> <DataListCell key="finished">
{formatDateString(job.finished)} {formatDateString(job.finished)}
</DataListCell>, </DataListCell>,
<DataListCell isFilled={false} alignRight key="relaunch"> ]}
/>
{job.type !== 'system_job' && {job.type !== 'system_job' &&
job.summary_fields.user_capabilities.start && ( job.summary_fields?.user_capabilities?.start && (
<DataListAction
aria-label="actions"
aria-labelledby={labelId}
id={labelId}
>
<Tooltip content={i18n._(t`Relaunch Job`)} position="top"> <Tooltip content={i18n._(t`Relaunch Job`)} position="top">
<LaunchButton resource={job}> <LaunchButton resource={job}>
{({ handleRelaunch }) => ( {({ handleRelaunch }) => (
@@ -66,10 +71,8 @@ class JobListItem extends Component {
)} )}
</LaunchButton> </LaunchButton>
</Tooltip> </Tooltip>
</DataListAction>
)} )}
</DataListCell>,
]}
/>
</DataListItemRow> </DataListItemRow>
</DataListItem> </DataListItem>
); );

View File

@@ -4,7 +4,7 @@ import CodeMirrorInput from '@components/CodeMirrorInput';
import ContentEmpty from '@components/ContentEmpty'; import ContentEmpty from '@components/ContentEmpty';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { DetailList, Detail } from '@components/DetailList'; import { DetailList, Detail } from '@components/DetailList';
import { StatusIcon } from '@components/Sparkline'; import StatusIcon from '@components/StatusIcon';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import styled from 'styled-components'; import styled from 'styled-components';

View File

@@ -16,7 +16,7 @@ import { CardBody } from '@components/Card';
import ContentError from '@components/ContentError'; import ContentError from '@components/ContentError';
import ContentLoading from '@components/ContentLoading'; import ContentLoading from '@components/ContentLoading';
import ErrorDetail from '@components/ErrorDetail'; import ErrorDetail from '@components/ErrorDetail';
import { StatusIcon } from '@components/Sparkline'; import StatusIcon from '@components/StatusIcon';
import JobEvent from './JobEvent'; import JobEvent from './JobEvent';
import JobEventSkeleton from './JobEventSkeleton'; import JobEventSkeleton from './JobEventSkeleton';

View File

@@ -9,7 +9,6 @@ import {
TrashAltIcon, TrashAltIcon,
} from '@patternfly/react-icons'; } from '@patternfly/react-icons';
import { Badge as PFBadge, Button, Tooltip } from '@patternfly/react-core'; import { Badge as PFBadge, Button, Tooltip } from '@patternfly/react-core';
import VerticalSeparator from '@components/VerticalSeparator';
import DeleteButton from '@components/DeleteButton'; import DeleteButton from '@components/DeleteButton';
import LaunchButton from '@components/LaunchButton'; import LaunchButton from '@components/LaunchButton';
@@ -123,8 +122,6 @@ const OutputToolbar = ({ i18n, job, onDelete }) => {
</Tooltip> </Tooltip>
</BadgeGroup> </BadgeGroup>
<VerticalSeparator />
{job.type !== 'system_job' && {job.type !== 'system_job' &&
job.summary_fields.user_capabilities?.start && ( job.summary_fields.user_capabilities?.start && (
<Tooltip content={i18n._(t`Relaunch Job`)}> <Tooltip content={i18n._(t`Relaunch Job`)}>

View File

@@ -5,7 +5,7 @@ import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import styled from 'styled-components'; import styled from 'styled-components';
import { func, shape } from 'prop-types'; import { func, shape } from 'prop-types';
import { StatusIcon } from '@components/Sparkline'; import StatusIcon from '@components/StatusIcon';
import { WorkflowNodeTypeLetter } from '@components/Workflow'; import { WorkflowNodeTypeLetter } from '@components/Workflow';
import { secondsToHHMMSS } from '@util/dates'; import { secondsToHHMMSS } from '@util/dates';
import { constants as wfConstants } from '@components/Workflow/WorkflowUtils'; import { constants as wfConstants } from '@components/Workflow/WorkflowUtils';

View File

@@ -8,12 +8,11 @@ import { t } from '@lingui/macro';
import { shape } from 'prop-types'; import { shape } from 'prop-types';
import { Badge as PFBadge, Button, Tooltip } from '@patternfly/react-core'; import { Badge as PFBadge, Button, Tooltip } from '@patternfly/react-core';
import { CompassIcon, WrenchIcon } from '@patternfly/react-icons'; import { CompassIcon, WrenchIcon } from '@patternfly/react-icons';
import { StatusIcon } from '@components/Sparkline'; import StatusIcon from '@components/StatusIcon';
import VerticalSeparator from '@components/VerticalSeparator';
import styled from 'styled-components'; import styled from 'styled-components';
const Toolbar = styled.div` const Toolbar = styled.div`
align-items: center align-items: center;
border-bottom: 1px solid grey; border-bottom: 1px solid grey;
display: flex; display: flex;
height: 56px; height: 56px;
@@ -73,7 +72,6 @@ function WorkflowOutputToolbar({ i18n, job }) {
<ToolbarActions> <ToolbarActions>
<div>{i18n._(t`Total Nodes`)}</div> <div>{i18n._(t`Total Nodes`)}</div>
<Badge isRead>{totalNodes}</Badge> <Badge isRead>{totalNodes}</Badge>
<VerticalSeparator />
<Tooltip content={i18n._(t`Toggle Legend`)} position="bottom"> <Tooltip content={i18n._(t`Toggle Legend`)} position="bottom">
<ActionButton <ActionButton
id="workflow-output-toggle-legend" id="workflow-output-toggle-legend"

View File

@@ -132,7 +132,7 @@ function OrganizationDetail({ i18n, organization }) {
{deletionError && ( {deletionError && (
<AlertModal <AlertModal
isOpen={deletionError} isOpen={deletionError}
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
onClose={() => setDeletionError(null)} onClose={() => setDeletionError(null)}
> >

View File

@@ -146,9 +146,9 @@ function OrganizationsList({ i18n }) {
itemsToDelete={selected} itemsToDelete={selected}
pluralizedItemName="Organizations" pluralizedItemName="Organizations"
/>, />,
canAdd ? ( ...(canAdd
<ToolbarAddButton key="add" linkTo={addUrl} /> ? [<ToolbarAddButton key="add" linkTo={addUrl} />]
) : null, : []),
]} ]}
/> />
)} )}
@@ -169,7 +169,7 @@ function OrganizationsList({ i18n }) {
</PageSection> </PageSection>
<AlertModal <AlertModal
isOpen={deletionError} isOpen={deletionError}
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
onClose={clearDeletionError} onClose={clearDeletionError}
> >

View File

@@ -23,6 +23,7 @@ const mockOrganizations = {
}, },
user_capabilities: { user_capabilities: {
delete: true, delete: true,
edit: true,
}, },
}, },
}, },
@@ -37,6 +38,7 @@ const mockOrganizations = {
}, },
user_capabilities: { user_capabilities: {
delete: true, delete: true,
edit: true,
}, },
}, },
}, },
@@ -51,6 +53,7 @@ const mockOrganizations = {
}, },
user_capabilities: { user_capabilities: {
delete: true, delete: true,
edit: true,
}, },
}, },
}, },

View File

@@ -5,38 +5,30 @@ import { t } from '@lingui/macro';
import { import {
Badge as PFBadge, Badge as PFBadge,
Button, Button,
DataListAction,
DataListCell,
DataListCheck,
DataListItem, DataListItem,
DataListItemRow,
DataListItemCells, DataListItemCells,
DataListItemRow,
Tooltip, Tooltip,
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import styled from 'styled-components'; import styled from 'styled-components';
import { PencilAltIcon } from '@patternfly/react-icons'; import { PencilAltIcon } from '@patternfly/react-icons';
import DataListCell from '@components/DataListCell';
import DataListCheck from '@components/DataListCheck';
import VerticalSeparator from '@components/VerticalSeparator';
import { Organization } from '@types'; import { Organization } from '@types';
const Badge = styled(PFBadge)` const Badge = styled(PFBadge)`
align-items: center; margin-left: 8px;
display: flex;
justify-content: center;
margin-left: 10px;
`; `;
const ListGroup = styled.span` const ListGroup = styled.span`
display: flex; margin-left: 24px;
margin-left: 40px;
@media screen and (min-width: 768px) {
margin-left: 20px;
&:first-of-type { &:first-of-type {
margin-left: 0; margin-left: 0;
} }
}
`; `;
function OrganizationListItem({ function OrganizationListItem({
@@ -63,7 +55,6 @@ function OrganizationListItem({
<DataListItemCells <DataListItemCells
dataListCells={[ dataListCells={[
<DataListCell key="divider"> <DataListCell key="divider">
<VerticalSeparator />
<span id={labelId}> <span id={labelId}>
<Link to={`${detailUrl}`}> <Link to={`${detailUrl}`}>
<b>{organization.name}</b> <b>{organization.name}</b>
@@ -84,7 +75,13 @@ function OrganizationListItem({
</Badge> </Badge>
</ListGroup> </ListGroup>
</DataListCell>, </DataListCell>,
<DataListCell key="edit" alignRight isFilled={false}> ]}
/>
<DataListAction
aria-label="actions"
aria-labelledby={labelId}
id={labelId}
>
{organization.summary_fields.user_capabilities.edit && ( {organization.summary_fields.user_capabilities.edit && (
<Tooltip content={i18n._(t`Edit Organization`)} position="top"> <Tooltip content={i18n._(t`Edit Organization`)} position="top">
<Button <Button
@@ -96,9 +93,7 @@ function OrganizationListItem({
</Button> </Button>
</Tooltip> </Tooltip>
)} )}
</DataListCell>, </DataListAction>
]}
/>
</DataListItemRow> </DataListItemRow>
</DataListItem> </DataListItem>
); );

View File

@@ -177,7 +177,7 @@ function ProjectDetail({ project, i18n }) {
{deletionError && ( {deletionError && (
<AlertModal <AlertModal
isOpen={deletionError} isOpen={deletionError}
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
onClose={() => setDeletionError(null)} onClose={() => setDeletionError(null)}
> >

View File

@@ -206,9 +206,14 @@ class ProjectsList extends Component {
itemsToDelete={selected} itemsToDelete={selected}
pluralizedItemName={i18n._(t`Projects`)} pluralizedItemName={i18n._(t`Projects`)}
/>, />,
canAdd ? ( ...(canAdd
<ToolbarAddButton key="add" linkTo={`${match.url}/add`} /> ? [
) : null, <ToolbarAddButton
key="add"
linkTo={`${match.url}/add`}
/>,
]
: []),
]} ]}
/> />
)} )}
@@ -231,7 +236,7 @@ class ProjectsList extends Component {
</PageSection> </PageSection>
<AlertModal <AlertModal
isOpen={deletionError} isOpen={deletionError}
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
onClose={this.handleDeleteErrorClose} onClose={this.handleDeleteErrorClose}
> >

View File

@@ -3,6 +3,9 @@ import { string, bool, func } from 'prop-types';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { import {
Button, Button,
DataListAction as _DataListAction,
DataListCell,
DataListCheck,
DataListItem, DataListItem,
DataListItemRow, DataListItemRow,
DataListItemCells, DataListItemCells,
@@ -11,16 +14,20 @@ import {
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { PencilAltIcon, SyncIcon } from '@patternfly/react-icons'; import { PencilAltIcon, SyncIcon } from '@patternfly/react-icons';
import styled from 'styled-components';
import ClipboardCopyButton from '@components/ClipboardCopyButton'; import ClipboardCopyButton from '@components/ClipboardCopyButton';
import DataListCell from '@components/DataListCell';
import DataListCheck from '@components/DataListCheck';
import ProjectSyncButton from '../shared/ProjectSyncButton'; import ProjectSyncButton from '../shared/ProjectSyncButton';
import { StatusIcon } from '@components/Sparkline'; import StatusIcon from '@components/StatusIcon';
import VerticalSeparator from '@components/VerticalSeparator';
import { toTitleCase } from '@util/strings'; import { toTitleCase } from '@util/strings';
import { Project } from '@types'; import { Project } from '@types';
const DataListAction = styled(_DataListAction)`
align-items: center;
display: grid;
grid-gap: 16px;
grid-template-columns: repeat(2, 40px);
`;
class ProjectListItem extends React.Component { class ProjectListItem extends React.Component {
static propTypes = { static propTypes = {
project: Project.isRequired, project: Project.isRequired,
@@ -73,8 +80,7 @@ class ProjectListItem extends React.Component {
/> />
<DataListItemCells <DataListItemCells
dataListCells={[ dataListCells={[
<DataListCell key="divider"> <DataListCell key="status" isFilled={false}>
<VerticalSeparator />
{project.summary_fields.last_job && ( {project.summary_fields.last_job && (
<Tooltip <Tooltip
position="top" position="top"
@@ -92,11 +98,9 @@ class ProjectListItem extends React.Component {
</Link> </Link>
</Tooltip> </Tooltip>
)} )}
<Link </DataListCell>,
id={labelId} <DataListCell key="name">
to={`${detailUrl}`} <Link id={labelId} to={`${detailUrl}`}>
css={{ marginLeft: '10px' }}
>
<b>{project.name}</b> <b>{project.name}</b>
</Link> </Link>
</DataListCell>, </DataListCell>,
@@ -105,7 +109,7 @@ class ProjectListItem extends React.Component {
? i18n._(t`Manual`) ? i18n._(t`Manual`)
: toTitleCase(project.scm_type)} : toTitleCase(project.scm_type)}
</DataListCell>, </DataListCell>,
<DataListCell alignRight isFilled={false} key="revision"> <DataListCell key="revision">
{project.scm_revision.substring(0, 7)} {project.scm_revision.substring(0, 7)}
{project.scm_revision ? ( {project.scm_revision ? (
<ClipboardCopyButton <ClipboardCopyButton
@@ -115,23 +119,32 @@ class ProjectListItem extends React.Component {
/> />
) : null} ) : null}
</DataListCell>, </DataListCell>,
<DataListCell alignRight isFilled={false} key="sync"> ]}
/>
<DataListAction
aria-label="actions"
aria-labelledby={labelId}
id={labelId}
>
{project.summary_fields.user_capabilities.start && ( {project.summary_fields.user_capabilities.start && (
<Tooltip content={i18n._(t`Sync Project`)} position="top"> <Tooltip content={i18n._(t`Sync Project`)} position="top">
<ProjectSyncButton projectId={project.id}> <ProjectSyncButton projectId={project.id}>
{handleSync => ( {handleSync => (
<Button variant="plain" onClick={handleSync}> <Button
css="grid-column: 1"
variant="plain"
onClick={handleSync}
>
<SyncIcon /> <SyncIcon />
</Button> </Button>
)} )}
</ProjectSyncButton> </ProjectSyncButton>
</Tooltip> </Tooltip>
)} )}
</DataListCell>,
<DataListCell key="edit" alignRight isFilled={false}>
{project.summary_fields.user_capabilities.edit && ( {project.summary_fields.user_capabilities.edit && (
<Tooltip content={i18n._(t`Edit Project`)} position="top"> <Tooltip content={i18n._(t`Edit Project`)} position="top">
<Button <Button
css="grid-column: 2"
variant="plain" variant="plain"
component={Link} component={Link}
to={`/projects/${project.id}/edit`} to={`/projects/${project.id}/edit`}
@@ -140,9 +153,7 @@ class ProjectListItem extends React.Component {
</Button> </Button>
</Tooltip> </Tooltip>
)} )}
</DataListCell>, </DataListAction>
]}
/>
</DataListItemRow> </DataListItemRow>
</DataListItem> </DataListItem>
); );

View File

@@ -54,7 +54,7 @@ class ProjectSyncButton extends React.Component {
{syncError && ( {syncError && (
<AlertModal <AlertModal
isOpen={syncError} isOpen={syncError}
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
onClose={this.handleSyncErrorClose} onClose={this.handleSyncErrorClose}
> >

View File

@@ -83,7 +83,7 @@ function TeamDetail({ team, i18n }) {
{deletionError && ( {deletionError && (
<AlertModal <AlertModal
isOpen={deletionError} isOpen={deletionError}
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
onClose={() => setDeletionError(null)} onClose={() => setDeletionError(null)}
> >

View File

@@ -193,9 +193,14 @@ class TeamsList extends Component {
itemsToDelete={selected} itemsToDelete={selected}
pluralizedItemName={i18n._(t`Teams`)} pluralizedItemName={i18n._(t`Teams`)}
/>, />,
canAdd ? ( ...(canAdd
<ToolbarAddButton key="add" linkTo={`${match.url}/add`} /> ? [
) : null, <ToolbarAddButton
key="add"
linkTo={`${match.url}/add`}
/>,
]
: []),
]} ]}
/> />
)} )}
@@ -218,7 +223,7 @@ class TeamsList extends Component {
</PageSection> </PageSection>
<AlertModal <AlertModal
isOpen={deletionError} isOpen={deletionError}
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
onClose={this.handleDeleteErrorClose} onClose={this.handleDeleteErrorClose}
> >

View File

@@ -17,6 +17,7 @@ const mockAPITeamsList = {
summary_fields: { summary_fields: {
user_capabilities: { user_capabilities: {
delete: true, delete: true,
edit: true,
}, },
}, },
}, },
@@ -27,6 +28,7 @@ const mockAPITeamsList = {
summary_fields: { summary_fields: {
user_capabilities: { user_capabilities: {
delete: true, delete: true,
edit: true,
}, },
}, },
}, },
@@ -37,6 +39,7 @@ const mockAPITeamsList = {
summary_fields: { summary_fields: {
user_capabilities: { user_capabilities: {
delete: true, delete: true,
edit: true,
}, },
}, },
}, },

View File

@@ -4,17 +4,17 @@ import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { import {
Button, Button,
DataListAction,
DataListCell,
DataListCheck,
DataListItem, DataListItem,
DataListItemRow,
DataListItemCells, DataListItemCells,
DataListItemRow,
Tooltip, Tooltip,
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { PencilAltIcon } from '@patternfly/react-icons'; import { PencilAltIcon } from '@patternfly/react-icons';
import DataListCell from '@components/DataListCell';
import DataListCheck from '@components/DataListCheck';
import VerticalSeparator from '@components/VerticalSeparator';
import { Team } from '@types'; import { Team } from '@types';
class TeamListItem extends React.Component { class TeamListItem extends React.Component {
@@ -28,6 +28,7 @@ class TeamListItem extends React.Component {
render() { render() {
const { team, isSelected, onSelect, detailUrl, i18n } = this.props; const { team, isSelected, onSelect, detailUrl, i18n } = this.props;
const labelId = `check-action-${team.id}`; const labelId = `check-action-${team.id}`;
return ( return (
<DataListItem key={team.id} aria-labelledby={labelId} id={`${team.id}`}> <DataListItem key={team.id} aria-labelledby={labelId} id={`${team.id}`}>
<DataListItemRow> <DataListItemRow>
@@ -39,8 +40,7 @@ class TeamListItem extends React.Component {
/> />
<DataListItemCells <DataListItemCells
dataListCells={[ dataListCells={[
<DataListCell key="divider"> <DataListCell key="name">
<VerticalSeparator />
<Link id={labelId} to={`${detailUrl}`}> <Link id={labelId} to={`${detailUrl}`}>
<b>{team.name}</b> <b>{team.name}</b>
</Link> </Link>
@@ -48,9 +48,7 @@ class TeamListItem extends React.Component {
<DataListCell key="organization"> <DataListCell key="organization">
{team.summary_fields.organization && ( {team.summary_fields.organization && (
<Fragment> <Fragment>
<b css={{ marginRight: '20px' }}> <b css="margin-right: 24px">{i18n._(t`Organization`)}</b>
{i18n._(t`Organization`)}
</b>
<Link <Link
to={`/organizations/${team.summary_fields.organization.id}/details`} to={`/organizations/${team.summary_fields.organization.id}/details`}
> >
@@ -59,7 +57,13 @@ class TeamListItem extends React.Component {
</Fragment> </Fragment>
)} )}
</DataListCell>, </DataListCell>,
<DataListCell key="edit" alignRight isFilled={false}> ]}
/>
<DataListAction
aria-label="actions"
aria-labelledby={labelId}
id={labelId}
>
{team.summary_fields.user_capabilities.edit && ( {team.summary_fields.user_capabilities.edit && (
<Tooltip content={i18n._(t`Edit Team`)} position="top"> <Tooltip content={i18n._(t`Edit Team`)} position="top">
<Button <Button
@@ -71,9 +75,7 @@ class TeamListItem extends React.Component {
</Button> </Button>
</Tooltip> </Tooltip>
)} )}
</DataListCell>, </DataListAction>
]}
/>
</DataListItemRow> </DataListItemRow>
</DataListItem> </DataListItem>
); );

View File

@@ -338,7 +338,7 @@ function JobTemplateDetail({ i18n, template }) {
{deletionError && ( {deletionError && (
<AlertModal <AlertModal
isOpen={deletionError} isOpen={deletionError}
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
onClose={() => setDeletionError(null)} onClose={() => setDeletionError(null)}
> >

View File

@@ -121,21 +121,25 @@ function TemplateList({ i18n }) {
const canAddWFJT = const canAddWFJT =
wfjtActions && Object.prototype.hasOwnProperty.call(wfjtActions, 'POST'); wfjtActions && Object.prototype.hasOwnProperty.call(wfjtActions, 'POST');
const addButtonOptions = []; const addButtonOptions = [];
if (canAddJT) { if (canAddJT) {
addButtonOptions.push({ addButtonOptions.push({
label: i18n._(t`Template`), label: i18n._(t`Template`),
url: `/templates/job_template/add/`, url: `/templates/job_template/add/`,
}); });
} }
if (canAddWFJT) { if (canAddWFJT) {
addButtonOptions.push({ addButtonOptions.push({
label: i18n._(t`Workflow Template`), label: i18n._(t`Workflow Template`),
url: `/templates/workflow_job_template/add/`, url: `/templates/workflow_job_template/add/`,
}); });
} }
const addButton = ( const addButton = (
<AddDropDownButton key="add" dropdownItems={addButtonOptions} /> <AddDropDownButton key="add" dropdownItems={addButtonOptions} />
); );
return ( return (
<> <>
<Card> <Card>
@@ -215,7 +219,7 @@ function TemplateList({ i18n }) {
itemsToDelete={selected} itemsToDelete={selected}
pluralizedItemName="Templates" pluralizedItemName="Templates"
/>, />,
(canAddJT || canAddWFJT) && addButton, ...(canAddJT || canAddWFJT ? [addButton] : []),
]} ]}
/> />
)} )}
@@ -234,7 +238,7 @@ function TemplateList({ i18n }) {
</Card> </Card>
<AlertModal <AlertModal
isOpen={deletionError} isOpen={deletionError}
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
onClose={clearDeletionError} onClose={clearDeletionError}
> >

View File

@@ -2,6 +2,9 @@ import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { import {
Button, Button,
DataListAction as _DataListAction,
DataListCell,
DataListCheck,
DataListItem, DataListItem,
DataListItemRow, DataListItemRow,
DataListItemCells, DataListItemCells,
@@ -15,14 +18,20 @@ import {
RocketIcon, RocketIcon,
} from '@patternfly/react-icons'; } from '@patternfly/react-icons';
import DataListCell from '@components/DataListCell';
import DataListCheck from '@components/DataListCheck';
import LaunchButton from '@components/LaunchButton'; import LaunchButton from '@components/LaunchButton';
import VerticalSeparator from '@components/VerticalSeparator'; import Sparkline from '@components/Sparkline';
import { Sparkline } from '@components/Sparkline';
import { toTitleCase } from '@util/strings'; import { toTitleCase } from '@util/strings';
import styled from 'styled-components';
const DataListAction = styled(_DataListAction)`
align-items: center;
display: grid;
grid-gap: 16px;
grid-template-columns: repeat(2, 40px);
`;
function TemplateListItem({ i18n, template, isSelected, onSelect, detailUrl }) { function TemplateListItem({ i18n, template, isSelected, onSelect, detailUrl }) {
const labelId = `check-action-${template.id}`;
const canLaunch = template.summary_fields.user_capabilities.start; const canLaunch = template.summary_fields.user_capabilities.start;
const missingResourceIcon = const missingResourceIcon =
@@ -32,21 +41,17 @@ function TemplateListItem({ i18n, template, isSelected, onSelect, detailUrl }) {
!template.ask_inventory_on_launch)); !template.ask_inventory_on_launch));
return ( return (
<DataListItem <DataListItem aria-labelledby={labelId} id={`${template.id}`}>
aria-labelledby={`check-action-${template.id}`}
id={`${template.id}`}
>
<DataListItemRow> <DataListItemRow>
<DataListCheck <DataListCheck
id={`select-jobTemplate-${template.id}`} id={`select-jobTemplate-${template.id}`}
checked={isSelected} checked={isSelected}
onChange={onSelect} onChange={onSelect}
aria-labelledby={`check-action-${template.id}`} aria-labelledby={labelId}
/> />
<DataListItemCells <DataListItemCells
dataListCells={[ dataListCells={[
<DataListCell key="divider"> <DataListCell key="divider">
<VerticalSeparator />
<span> <span>
<Link to={`${detailUrl}`}> <Link to={`${detailUrl}`}>
<b>{template.name}</b> <b>{template.name}</b>
@@ -71,23 +76,32 @@ function TemplateListItem({ i18n, template, isSelected, onSelect, detailUrl }) {
<DataListCell key="sparkline"> <DataListCell key="sparkline">
<Sparkline jobs={template.summary_fields.recent_jobs} /> <Sparkline jobs={template.summary_fields.recent_jobs} />
</DataListCell>, </DataListCell>,
<DataListCell alignRight isFilled={false} key="launch"> ]}
/>
<DataListAction
aria-label="actions"
aria-labelledby={labelId}
id={labelId}
>
{canLaunch && template.type === 'job_template' && ( {canLaunch && template.type === 'job_template' && (
<Tooltip content={i18n._(t`Launch Template`)} position="top"> <Tooltip content={i18n._(t`Launch Template`)} position="top">
<LaunchButton resource={template}> <LaunchButton resource={template}>
{({ handleLaunch }) => ( {({ handleLaunch }) => (
<Button variant="plain" onClick={handleLaunch}> <Button
css="grid-column: 1"
variant="plain"
onClick={handleLaunch}
>
<RocketIcon /> <RocketIcon />
</Button> </Button>
)} )}
</LaunchButton> </LaunchButton>
</Tooltip> </Tooltip>
)} )}
</DataListCell>,
<DataListCell key="edit" alignRight isFilled={false}>
{template.summary_fields.user_capabilities.edit && ( {template.summary_fields.user_capabilities.edit && (
<Tooltip content={i18n._(t`Edit Template`)} position="top"> <Tooltip content={i18n._(t`Edit Template`)} position="top">
<Button <Button
css="grid-column: 2"
variant="plain" variant="plain"
component={Link} component={Link}
to={`/templates/${template.type}/${template.id}/edit`} to={`/templates/${template.type}/${template.id}/edit`}
@@ -96,9 +110,7 @@ function TemplateListItem({ i18n, template, isSelected, onSelect, detailUrl }) {
</Button> </Button>
</Tooltip> </Tooltip>
)} )}
</DataListCell>, </DataListAction>
]}
/>
</DataListItemRow> </DataListItemRow>
</DataListItem> </DataListItem>
); );

View File

@@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { Link, useHistory } from 'react-router-dom'; import { Link, useHistory } from 'react-router-dom';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { WorkflowJobTemplatesAPI } from '@api';
import { import {
Chip, Chip,
ChipGroup, ChipGroup,
@@ -13,17 +14,16 @@ import {
Label, Label,
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import { CardBody, CardActionsRow } from '@components/Card';
import ContentLoading from '@components/ContentLoading';
import { WorkflowJobTemplatesAPI } from '@api';
import AlertModal from '@components/AlertModal'; import AlertModal from '@components/AlertModal';
import ErrorDetail from '@components/ErrorDetail'; import { CardBody, CardActionsRow } from '@components/Card';
import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
import { VariablesDetail } from '@components/CodeMirrorInput'; import { VariablesDetail } from '@components/CodeMirrorInput';
import LaunchButton from '@components/LaunchButton'; import ContentLoading from '@components/ContentLoading';
import DeleteButton from '@components/DeleteButton'; import DeleteButton from '@components/DeleteButton';
import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
import ErrorDetail from '@components/ErrorDetail';
import LaunchButton from '@components/LaunchButton';
import Sparkline from '@components/Sparkline';
import { toTitleCase } from '@util/strings'; import { toTitleCase } from '@util/strings';
import { Sparkline } from '@components/Sparkline';
function WorkflowJobTemplateDetail({ template, i18n, webHookKey }) { function WorkflowJobTemplateDetail({ template, i18n, webHookKey }) {
const { const {
@@ -104,7 +104,6 @@ function WorkflowJobTemplateDetail({ template, i18n, webHookKey }) {
<Detail label={i18n._(t`Description`)} value={description} /> <Detail label={i18n._(t`Description`)} value={description} />
{summary_fields.recent_jobs?.length > 0 && ( {summary_fields.recent_jobs?.length > 0 && (
<Detail <Detail
css="display: flex; flex: 1;"
value={<Sparkline jobs={recentPlaybookJobs} />} value={<Sparkline jobs={recentPlaybookJobs} />}
label={i18n._(t`Activity`)} label={i18n._(t`Activity`)}
/> />
@@ -223,7 +222,7 @@ function WorkflowJobTemplateDetail({ template, i18n, webHookKey }) {
{deletionError && ( {deletionError && (
<AlertModal <AlertModal
isOpen={deletionError} isOpen={deletionError}
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
onClose={() => setDeletionError(null)} onClose={() => setDeletionError(null)}
> >

View File

@@ -109,7 +109,7 @@ describe('NodeModal', () => {
wrapper.find('button#next-node-modal').simulate('click'); wrapper.find('button#next-node-modal').simulate('click');
}); });
wrapper.update(); wrapper.update();
wrapper.find('DataListRadio').simulate('click'); wrapper.find('Radio').simulate('click');
await act(async () => { await act(async () => {
wrapper.find('button#next-node-modal').simulate('click'); wrapper.find('button#next-node-modal').simulate('click');
}); });
@@ -136,7 +136,7 @@ describe('NodeModal', () => {
wrapper.find('AnsibleSelect').prop('onChange')(null, 'project_sync'); wrapper.find('AnsibleSelect').prop('onChange')(null, 'project_sync');
}); });
wrapper.update(); wrapper.update();
wrapper.find('DataListRadio').simulate('click'); wrapper.find('Radio').simulate('click');
await act(async () => { await act(async () => {
wrapper.find('button#next-node-modal').simulate('click'); wrapper.find('button#next-node-modal').simulate('click');
}); });
@@ -166,7 +166,7 @@ describe('NodeModal', () => {
); );
}); });
wrapper.update(); wrapper.update();
wrapper.find('DataListRadio').simulate('click'); wrapper.find('Radio').simulate('click');
await act(async () => { await act(async () => {
wrapper.find('button#next-node-modal').simulate('click'); wrapper.find('button#next-node-modal').simulate('click');
}); });
@@ -193,7 +193,7 @@ describe('NodeModal', () => {
); );
}); });
wrapper.update(); wrapper.update();
wrapper.find('DataListRadio').simulate('click'); wrapper.find('Radio').simulate('click');
await act(async () => { await act(async () => {
wrapper.find('button#next-node-modal').simulate('click'); wrapper.find('button#next-node-modal').simulate('click');
}); });
@@ -396,7 +396,7 @@ describe('NodeModal', () => {
); );
}); });
wrapper.update(); wrapper.update();
wrapper.find('DataListRadio').simulate('click'); wrapper.find('Radio').simulate('click');
await act(async () => { await act(async () => {
wrapper.find('button#next-node-modal').simulate('click'); wrapper.find('button#next-node-modal').simulate('click');
}); });

View File

@@ -7,19 +7,11 @@ import { Formik, Field } from 'formik';
import { Form, FormGroup, TextInput } from '@patternfly/react-core'; import { Form, FormGroup, TextInput } from '@patternfly/react-core';
import FormRow from '@components/FormRow'; import FormRow from '@components/FormRow';
import AnsibleSelect from '@components/AnsibleSelect'; import AnsibleSelect from '@components/AnsibleSelect';
import VerticalSeperator from '@components/VerticalSeparator';
import InventorySourcesList from './InventorySourcesList'; import InventorySourcesList from './InventorySourcesList';
import JobTemplatesList from './JobTemplatesList'; import JobTemplatesList from './JobTemplatesList';
import ProjectsList from './ProjectsList'; import ProjectsList from './ProjectsList';
import WorkflowJobTemplatesList from './WorkflowJobTemplatesList'; import WorkflowJobTemplatesList from './WorkflowJobTemplatesList';
const Divider = styled.div`
height: 1px;
background-color: var(--pf-global--Color--light-300);
border: 0;
flex-shrink: 0;
`;
const TimeoutInput = styled(TextInput)` const TimeoutInput = styled(TextInput)`
width: 200px; width: 200px;
:not(:first-of-type) { :not(:first-of-type) {
@@ -48,8 +40,7 @@ function NodeTypeStep({
return ( return (
<> <>
<div css="display: flex; align-items: center; margin-bottom: 20px;"> <div css="display: flex; align-items: center; margin-bottom: 20px;">
<b>{i18n._(t`Node Type`)}</b> <b css="margin-right: 24px">{i18n._(t`Node Type`)}</b>
<VerticalSeperator />
<div> <div>
<AnsibleSelect <AnsibleSelect
id="nodeResource-select" id="nodeResource-select"
@@ -93,7 +84,6 @@ function NodeTypeStep({
/> />
</div> </div>
</div> </div>
<Divider component="div" />
{nodeType === 'job_template' && ( {nodeType === 'job_template' && (
<JobTemplatesList <JobTemplatesList
nodeResource={nodeResource} nodeResource={nodeResource}

View File

@@ -94,7 +94,7 @@ describe('NodeTypeStep', () => {
wrapper.update(); wrapper.update();
expect(wrapper.find('AnsibleSelect').prop('value')).toBe('job_template'); expect(wrapper.find('AnsibleSelect').prop('value')).toBe('job_template');
expect(wrapper.find('JobTemplatesList').length).toBe(1); expect(wrapper.find('JobTemplatesList').length).toBe(1);
wrapper.find('DataListRadio').simulate('click'); wrapper.find('Radio').simulate('click');
expect(onUpdateNodeResource).toHaveBeenCalledWith({ expect(onUpdateNodeResource).toHaveBeenCalledWith({
id: 1, id: 1,
name: 'Test Job Template', name: 'Test Job Template',
@@ -119,7 +119,7 @@ describe('NodeTypeStep', () => {
wrapper.update(); wrapper.update();
expect(wrapper.find('AnsibleSelect').prop('value')).toBe('project_sync'); expect(wrapper.find('AnsibleSelect').prop('value')).toBe('project_sync');
expect(wrapper.find('ProjectsList').length).toBe(1); expect(wrapper.find('ProjectsList').length).toBe(1);
wrapper.find('DataListRadio').simulate('click'); wrapper.find('Radio').simulate('click');
expect(onUpdateNodeResource).toHaveBeenCalledWith({ expect(onUpdateNodeResource).toHaveBeenCalledWith({
id: 1, id: 1,
name: 'Test Project', name: 'Test Project',
@@ -146,7 +146,7 @@ describe('NodeTypeStep', () => {
'inventory_source_sync' 'inventory_source_sync'
); );
expect(wrapper.find('InventorySourcesList').length).toBe(1); expect(wrapper.find('InventorySourcesList').length).toBe(1);
wrapper.find('DataListRadio').simulate('click'); wrapper.find('Radio').simulate('click');
expect(onUpdateNodeResource).toHaveBeenCalledWith({ expect(onUpdateNodeResource).toHaveBeenCalledWith({
id: 1, id: 1,
name: 'Test Inventory Source', name: 'Test Inventory Source',
@@ -173,7 +173,7 @@ describe('NodeTypeStep', () => {
'workflow_job_template' 'workflow_job_template'
); );
expect(wrapper.find('WorkflowJobTemplatesList').length).toBe(1); expect(wrapper.find('WorkflowJobTemplatesList').length).toBe(1);
wrapper.find('DataListRadio').simulate('click'); wrapper.find('Radio').simulate('click');
expect(onUpdateNodeResource).toHaveBeenCalledWith({ expect(onUpdateNodeResource).toHaveBeenCalledWith({
id: 1, id: 1,
name: 'Test Workflow Job Template', name: 'Test Workflow Job Template',

View File

@@ -234,7 +234,7 @@ function VisualizerGraph({ i18n, readOnly }) {
{linkHelp && <WorkflowLinkHelp link={linkHelp} />} {linkHelp && <WorkflowLinkHelp link={linkHelp} />}
</WorkflowHelp> </WorkflowHelp>
)} )}
<WorkflowSVG id="workflow-svg" ref={svgRef} css=""> <WorkflowSVG id="workflow-svg" ref={svgRef}>
<defs> <defs>
<marker <marker
className="WorkflowChart-noPointerEvents" className="WorkflowChart-noPointerEvents"

View File

@@ -6,7 +6,12 @@ import {
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { func, shape } from 'prop-types'; import { func, shape } from 'prop-types';
import { Badge as PFBadge, Button, Tooltip } from '@patternfly/react-core'; import {
Badge as PFBadge,
Button,
Title,
Tooltip,
} from '@patternfly/react-core';
import { import {
BookIcon, BookIcon,
CompassIcon, CompassIcon,
@@ -15,7 +20,6 @@ import {
TrashAltIcon, TrashAltIcon,
WrenchIcon, WrenchIcon,
} from '@patternfly/react-icons'; } from '@patternfly/react-icons';
import VerticalSeparator from '@components/VerticalSeparator';
import styled from 'styled-components'; import styled from 'styled-components';
const Badge = styled(PFBadge)` const Badge = styled(PFBadge)`
@@ -51,15 +55,12 @@ function VisualizerToolbar({ i18n, onClose, onSave, template }) {
return ( return (
<div id="visualizer-toolbar"> <div id="visualizer-toolbar">
<div css="align-items: center; border-bottom: 1px solid grey; display: flex; height: 56px; padding: 0px 20px;"> <div css="align-items: center; border-bottom: 1px solid grey; display: flex; height: 56px; padding: 0px 20px;">
<div css="display: flex;" id="visualizer-toolbar-template-name"> <Title size="xl">{template.name}</Title>
<b>{template.name}</b>
</div>
<div css="align-items: center; display: flex; flex: 1; justify-content: flex-end"> <div css="align-items: center; display: flex; flex: 1; justify-content: flex-end">
<div>{i18n._(t`Total Nodes`)}</div> <div>{i18n._(t`Total Nodes`)}</div>
<Badge id="visualizer-total-nodes-badge" isRead> <Badge id="visualizer-total-nodes-badge" isRead>
{totalNodes} {totalNodes}
</Badge> </Badge>
<VerticalSeparator />
<Tooltip content={i18n._(t`Toggle Legend`)} position="bottom"> <Tooltip content={i18n._(t`Toggle Legend`)} position="bottom">
<ActionButton <ActionButton
id="visualizer-toggle-legend" id="visualizer-toggle-legend"
@@ -108,16 +109,15 @@ function VisualizerToolbar({ i18n, onClose, onSave, template }) {
<TrashAltIcon /> <TrashAltIcon />
</ActionButton> </ActionButton>
</Tooltip> </Tooltip>
<VerticalSeparator />
<Button <Button
id="visualizer-save" id="visualizer-save"
css="margin: 0 32px"
aria-label={i18n._(t`Save`)} aria-label={i18n._(t`Save`)}
variant="primary" variant="primary"
onClick={onSave} onClick={onSave}
> >
{i18n._(t`Save`)} {i18n._(t`Save`)}
</Button> </Button>
<VerticalSeparator />
<Button <Button
id="visualizer-close" id="visualizer-close"
aria-label={i18n._(t`Close`)} aria-label={i18n._(t`Close`)}

View File

@@ -99,7 +99,7 @@ function UserDetail({ user, i18n }) {
{deletionError && ( {deletionError && (
<AlertModal <AlertModal
isOpen={deletionError} isOpen={deletionError}
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
onClose={() => setDeletionError(null)} onClose={() => setDeletionError(null)}
> >

View File

@@ -197,9 +197,14 @@ class UsersList extends Component {
itemsToDelete={selected} itemsToDelete={selected}
pluralizedItemName="Users" pluralizedItemName="Users"
/>, />,
canAdd ? ( ...(canAdd
<ToolbarAddButton key="add" linkTo={`${match.url}/add`} /> ? [
) : null, <ToolbarAddButton
key="add"
linkTo={`${match.url}/add`}
/>,
]
: []),
]} ]}
/> />
)} )}
@@ -222,7 +227,7 @@ class UsersList extends Component {
</PageSection> </PageSection>
<AlertModal <AlertModal
isOpen={deletionError} isOpen={deletionError}
variant="danger" variant="error"
title={i18n._(t`Error!`)} title={i18n._(t`Error!`)}
onClose={this.handleDeleteErrorClose} onClose={this.handleDeleteErrorClose}
> >

View File

@@ -4,17 +4,17 @@ import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { import {
Button, Button,
DataListAction,
DataListCell,
DataListCheck,
DataListItem, DataListItem,
DataListItemRow,
DataListItemCells, DataListItemCells,
DataListItemRow,
Tooltip, Tooltip,
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { PencilAltIcon } from '@patternfly/react-icons'; import { PencilAltIcon } from '@patternfly/react-icons';
import DataListCell from '@components/DataListCell';
import DataListCheck from '@components/DataListCheck';
import VerticalSeparator from '@components/VerticalSeparator';
import { User } from '@types'; import { User } from '@types';
class UserListItem extends React.Component { class UserListItem extends React.Component {
@@ -39,8 +39,7 @@ class UserListItem extends React.Component {
/> />
<DataListItemCells <DataListItemCells
dataListCells={[ dataListCells={[
<DataListCell key="divider"> <DataListCell key="username">
<VerticalSeparator />
<Link to={`${detailUrl}`} id={labelId}> <Link to={`${detailUrl}`} id={labelId}>
<b>{user.username}</b> <b>{user.username}</b>
</Link> </Link>
@@ -48,7 +47,7 @@ class UserListItem extends React.Component {
<DataListCell key="first-name"> <DataListCell key="first-name">
{user.first_name && ( {user.first_name && (
<Fragment> <Fragment>
<b css={{ marginRight: '20px' }}>{i18n._(t`First Name`)}</b> <b css="margin-right: 24px">{i18n._(t`First Name`)}</b>
{user.first_name} {user.first_name}
</Fragment> </Fragment>
)} )}
@@ -56,12 +55,18 @@ class UserListItem extends React.Component {
<DataListCell key="last-name"> <DataListCell key="last-name">
{user.last_name && ( {user.last_name && (
<Fragment> <Fragment>
<b css={{ marginRight: '20px' }}>{i18n._(t`Last Name`)}</b> <b css="margin-right: 24px">{i18n._(t`Last Name`)}</b>
{user.last_name} {user.last_name}
</Fragment> </Fragment>
)} )}
</DataListCell>, </DataListCell>,
<DataListCell key="edit" alignRight isFilled={false}> ]}
/>
<DataListAction
aria-label="actions"
aria-labelledby={labelId}
id={labelId}
>
{user.summary_fields.user_capabilities.edit && ( {user.summary_fields.user_capabilities.edit && (
<Tooltip content={i18n._(t`Edit User`)} position="top"> <Tooltip content={i18n._(t`Edit User`)} position="top">
<Button <Button
@@ -73,9 +78,7 @@ class UserListItem extends React.Component {
</Button> </Button>
</Tooltip> </Tooltip>
)} )}
</DataListCell>, </DataListAction>
]}
/>
</DataListItemRow> </DataListItemRow>
</DataListItem> </DataListItem>
); );

View File

@@ -80,11 +80,7 @@ function UserForm({ user, handleCancel, handleSubmit, submitError, i18n }) {
onSubmit={handleValidateAndSubmit} onSubmit={handleValidateAndSubmit}
> >
{formik => ( {formik => (
<Form <Form autoComplete="off" onSubmit={formik.handleSubmit}>
autoComplete="off"
onSubmit={formik.handleSubmit}
css="padding: 0 24px"
>
<FormRow> <FormRow>
<FormField <FormField
id="user-username" id="user-username"