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
commit 70b287490b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
91 changed files with 1149 additions and 1455 deletions

View File

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

View File

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

View File

@ -5,7 +5,9 @@ import AlertModal from './AlertModal';
describe('AlertModal', () => {
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);
});
});

View File

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

View File

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

View File

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

View File

@ -1,12 +1,11 @@
import React, { Fragment } from 'react';
import styled from 'styled-components';
import { Link, Redirect } from 'react-router-dom';
import { bool, instanceOf } from 'prop-types';
import { t } from '@lingui/macro';
import { withI18n } from '@lingui/react';
import {
Title,
EmptyState as PFEmptyState,
EmptyState,
EmptyStateIcon,
EmptyStateBody,
} from '@patternfly/react-core';
@ -14,11 +13,6 @@ import { ExclamationTriangleIcon } from '@patternfly/react-icons';
import { RootAPI } from '@api';
import ErrorDetail from '@components/ErrorDetail';
const EmptyState = styled(PFEmptyState)`
width: var(--pf-c-empty-state--m-lg--MaxWidth);
margin: 0 auto;
`;
async function logout() {
await RootAPI.logout();
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 {
DataToolbar,
DataToolbarContent,
DataToolbarGroup,
DataToolbarToggleGroup,
DataToolbarContent as _DataToolbarContent,
DataToolbarGroup as _DataToolbarGroup,
DataToolbarItem,
DataToolbarToggleGroup,
} from '@patternfly/react-core/dist/umd/experimental';
import ExpandCollapse from '../ExpandCollapse';
import Search from '../Search';
@ -18,27 +18,12 @@ import Sort from '../Sort';
import { SearchColumns, SortColumns, QSConfig } from '@types';
const AdditionalControlsWrapper = styled.div`
display: flex;
flex-grow: 1;
justify-content: flex-end;
align-items: center;
& > :not(:first-child) {
margin-left: 20px;
}
const DataToolbarContent = styled(_DataToolbarContent)`
--pf-c-data-toolbar__content--PaddingLeft: 24px;
--pf-c-data-toolbar__content--PaddingRight: 8px;
`;
const AdditionalControlsDataToolbarGroup = styled(DataToolbarGroup)`
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;
const DataToolbarGroup = styled(_DataToolbarGroup)`
--pf-c-data-toolbar__group--spacer: 24px;
`;
class DataListToolbar extends React.Component {
@ -80,7 +65,6 @@ class DataListToolbar extends React.Component {
id="select-all"
/>
</DataToolbarItem>
<DataToolbarSeparator variant="separator" />
</DataToolbarGroup>
)}
<DataToolbarToggleGroup toggleIcon={<SearchIcon />} breakpoint="lg">
@ -110,13 +94,11 @@ class DataListToolbar extends React.Component {
</Fragment>
)}
</DataToolbarGroup>
<AdditionalControlsDataToolbarGroup>
<DataToolbarItem>
<AdditionalControlsWrapper>
{additionalControls}
</AdditionalControlsWrapper>
</DataToolbarItem>
</AdditionalControlsDataToolbarGroup>
<DataToolbarGroup css="margin-left: auto">
{additionalControls.map(control => (
<DataToolbarItem key={control.key}>{control}</DataToolbarItem>
))}
</DataToolbarGroup>
</DataToolbarContent>
</DataToolbar>
);

View File

@ -3,7 +3,6 @@ import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { Button } from '@patternfly/react-core';
import AlertModal from '@components/AlertModal';
import { CardActionsRow } from '@components/Card';
function DeleteButton({
onConfirm,
@ -29,26 +28,28 @@ function DeleteButton({
title={modalTitle}
variant="danger"
onClose={() => setIsOpen(false)}
>
{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>
actions={[
<Button
key="delete"
variant="danger"
aria-label={i18n._(t`Delete`)}
onClick={onConfirm}
>
{i18n._(t`Delete`)}
</Button>
</CardActionsRow>
</Button>,
<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>
</>
);

View File

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

View File

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

View File

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

View File

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

View File

@ -42,7 +42,7 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
<DataListItemCells
dataListCells={
Array [
<ForwardRef>
<DataListCell>
<ForwardRef
to={
Object {
@ -56,41 +56,10 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
Foo
</b>
</ForwardRef>
</ForwardRef>,
<ForwardRef>
</DataListCell>,
<DataListCell>
Slack
</ForwardRef>,
<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>,
</DataListCell>,
]
}
key=".0"
@ -99,453 +68,324 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
<div
className="pf-c-data-list__item-content"
>
<NotificationListItem__DataListCell
<DataListCell
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}
<div
className="pf-c-data-list__cell"
>
<DataListCell
className="NotificationListItem__DataListCell-w674ng-0 faYgxF"
<Link
to={
Object {
"pathname": "/foo",
}
}
>
<div
className="pf-c-data-list__cell NotificationListItem__DataListCell-w674ng-0 faYgxF"
<LinkAnchor
href="/foo"
navigate={[Function]}
>
<Styled(Link)
to={
Object {
"pathname": "/foo",
}
}
<a
href="/foo"
onClick={[Function]}
>
<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",
}
}
<b
id="items-list-item-9000"
>
<Link
className="sc-bdVaJa eBseNd"
to={
Object {
"pathname": "/foo",
}
}
>
<LinkAnchor
className="sc-bdVaJa eBseNd"
href="/foo"
navigate={[Function]}
>
<a
className="sc-bdVaJa eBseNd"
href="/foo"
onClick={[Function]}
>
<b
id="items-list-item-9000"
>
Foo
</b>
</a>
</LinkAnchor>
</Link>
</StyledComponent>
</Styled(Link)>
</div>
</DataListCell>
</StyledComponent>
</NotificationListItem__DataListCell>
<NotificationListItem__DataListCell
Foo
</b>
</a>
</LinkAnchor>
</Link>
</div>
</DataListCell>
<DataListCell
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}
<div
className="pf-c-data-list__cell"
>
<DataListCell
className="NotificationListItem__DataListCell-w674ng-0 faYgxF"
>
<div
className="pf-c-data-list__cell NotificationListItem__DataListCell-w674ng-0 faYgxF"
>
Slack
</div>
</DataListCell>
</StyledComponent>
</NotificationListItem__DataListCell>
<NotificationListItem__DataListCell
key="toggles"
righthalf="true"
Slack
</div>
</DataListCell>
</div>
</DataListItemCells>
<NotificationListItem__DataListAction
aria-label="actions"
aria-labelledby="items-list-item-9000"
id="items-list-item-9000"
key=".1"
rowid="items-list-item-9000"
>
<StyledComponent
aria-label="actions"
aria-labelledby="items-list-item-9000"
forwardedComponent={
Object {
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
"componentId": "NotificationListItem__DataListAction-w674ng-0",
"isStatic": true,
"lastClassName": "hhZchj",
"rules": Array [
"align-items:center;display:grid;grid-gap:16px;grid-template-columns:repeat(3,max-content);",
],
},
"displayName": "NotificationListItem__DataListAction",
"foldedComponentIds": Array [],
"render": [Function],
"styledComponentId": "NotificationListItem__DataListAction-w674ng-0",
"target": [Function],
"toString": [Function],
"warnTooManyClasses": [Function],
"withComponent": [Function],
}
}
forwardedRef={null}
id="items-list-item-9000"
rowid="items-list-item-9000"
>
<DataListAction
aria-label="actions"
aria-labelledby="items-list-item-9000"
className="NotificationListItem__DataListAction-w674ng-0 hhZchj"
id="items-list-item-9000"
rowid="items-list-item-9000"
>
<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}
righthalf="true"
<div
className="pf-c-data-list__item-action NotificationListItem__DataListAction-w674ng-0 hhZchj"
rowid="items-list-item-9000"
>
<DataListCell
className="NotificationListItem__DataListCell-w674ng-0 dXsFLF"
righthalf="true"
<Component
aria-label="Toggle notification start"
id="notification-9000-started-toggle"
isChecked={false}
isDisabled={false}
label="Start"
labelOff="Start"
onChange={[Function]}
>
<div
className="pf-c-data-list__cell NotificationListItem__DataListCell-w674ng-0 dXsFLF"
righthalf="true"
<ComponentWithOuia
component={[Function]}
componentProps={
Object {
"aria-label": "Toggle notification start",
"id": "notification-9000-started-toggle",
"isChecked": false,
"isDisabled": false,
"label": "Start",
"labelOff": "Start",
"onChange": [Function],
}
}
consumerContext={null}
>
<Component
<Switch
aria-label="Toggle notification start"
className=""
id="notification-9000-started-toggle"
isChecked={false}
isDisabled={false}
label="Start"
labelOff="Start"
onChange={[Function]}
>
<ComponentWithOuia
component={[Function]}
componentProps={
Object {
"aria-label": "Toggle notification start",
"id": "notification-9000-started-toggle",
"isChecked": false,
"isDisabled": false,
"label": "Start",
"labelOff": "Start",
"onChange": [Function],
}
ouiaContext={
Object {
"isOuia": false,
"ouiaId": null,
}
consumerContext={null}
}
>
<label
className="pf-c-switch"
htmlFor="notification-9000-started-toggle"
>
<Switch
<input
aria-label="Toggle notification start"
className=""
aria-labelledby={null}
checked={false}
className="pf-c-switch__input"
disabled={false}
id="notification-9000-started-toggle"
isChecked={false}
isDisabled={false}
label="Start"
labelOff="Start"
onChange={[Function]}
ouiaContext={
Object {
"isOuia": false,
"ouiaId": null,
}
}
type="checkbox"
/>
<span
className="pf-c-switch__toggle"
/>
<span
aria-hidden="true"
className="pf-c-switch__label pf-m-on"
id={null}
>
<label
className="pf-c-switch"
htmlFor="notification-9000-started-toggle"
>
<input
aria-label="Toggle notification start"
aria-labelledby={null}
checked={false}
className="pf-c-switch__input"
disabled={false}
id="notification-9000-started-toggle"
onChange={[Function]}
type="checkbox"
/>
<span
className="pf-c-switch__toggle"
/>
<span
aria-hidden="true"
className="pf-c-switch__label pf-m-on"
id={null}
>
Start
</span>
<span
aria-hidden="true"
className="pf-c-switch__label pf-m-off"
id={null}
>
Start
</span>
</label>
</Switch>
</ComponentWithOuia>
</Component>
<Component
Start
</span>
<span
aria-hidden="true"
className="pf-c-switch__label pf-m-off"
id={null}
>
Start
</span>
</label>
</Switch>
</ComponentWithOuia>
</Component>
<Component
aria-label="Toggle notification success"
id="notification-9000-success-toggle"
isChecked={false}
isDisabled={false}
label="Success"
labelOff="Success"
onChange={[Function]}
>
<ComponentWithOuia
component={[Function]}
componentProps={
Object {
"aria-label": "Toggle notification success",
"id": "notification-9000-success-toggle",
"isChecked": false,
"isDisabled": false,
"label": "Success",
"labelOff": "Success",
"onChange": [Function],
}
}
consumerContext={null}
>
<Switch
aria-label="Toggle notification success"
className=""
id="notification-9000-success-toggle"
isChecked={false}
isDisabled={false}
label="Success"
labelOff="Success"
onChange={[Function]}
>
<ComponentWithOuia
component={[Function]}
componentProps={
Object {
"aria-label": "Toggle notification success",
"id": "notification-9000-success-toggle",
"isChecked": false,
"isDisabled": false,
"label": "Success",
"labelOff": "Success",
"onChange": [Function],
}
ouiaContext={
Object {
"isOuia": false,
"ouiaId": null,
}
consumerContext={null}
}
>
<label
className="pf-c-switch"
htmlFor="notification-9000-success-toggle"
>
<Switch
<input
aria-label="Toggle notification success"
className=""
aria-labelledby={null}
checked={false}
className="pf-c-switch__input"
disabled={false}
id="notification-9000-success-toggle"
isChecked={false}
isDisabled={false}
label="Success"
labelOff="Success"
onChange={[Function]}
ouiaContext={
Object {
"isOuia": false,
"ouiaId": null,
}
}
type="checkbox"
/>
<span
className="pf-c-switch__toggle"
/>
<span
aria-hidden="true"
className="pf-c-switch__label pf-m-on"
id={null}
>
<label
className="pf-c-switch"
htmlFor="notification-9000-success-toggle"
>
<input
aria-label="Toggle notification success"
aria-labelledby={null}
checked={false}
className="pf-c-switch__input"
disabled={false}
id="notification-9000-success-toggle"
onChange={[Function]}
type="checkbox"
/>
<span
className="pf-c-switch__toggle"
/>
<span
aria-hidden="true"
className="pf-c-switch__label pf-m-on"
id={null}
>
Success
</span>
<span
aria-hidden="true"
className="pf-c-switch__label pf-m-off"
id={null}
>
Success
</span>
</label>
</Switch>
</ComponentWithOuia>
</Component>
<Component
Success
</span>
<span
aria-hidden="true"
className="pf-c-switch__label pf-m-off"
id={null}
>
Success
</span>
</label>
</Switch>
</ComponentWithOuia>
</Component>
<Component
aria-label="Toggle notification failure"
id="notification-9000-error-toggle"
isChecked={false}
isDisabled={false}
label="Failure"
labelOff="Failure"
onChange={[Function]}
>
<ComponentWithOuia
component={[Function]}
componentProps={
Object {
"aria-label": "Toggle notification failure",
"id": "notification-9000-error-toggle",
"isChecked": false,
"isDisabled": false,
"label": "Failure",
"labelOff": "Failure",
"onChange": [Function],
}
}
consumerContext={null}
>
<Switch
aria-label="Toggle notification failure"
className=""
id="notification-9000-error-toggle"
isChecked={false}
isDisabled={false}
label="Failure"
labelOff="Failure"
onChange={[Function]}
>
<ComponentWithOuia
component={[Function]}
componentProps={
Object {
"aria-label": "Toggle notification failure",
"id": "notification-9000-error-toggle",
"isChecked": false,
"isDisabled": false,
"label": "Failure",
"labelOff": "Failure",
"onChange": [Function],
}
ouiaContext={
Object {
"isOuia": false,
"ouiaId": null,
}
consumerContext={null}
}
>
<label
className="pf-c-switch"
htmlFor="notification-9000-error-toggle"
>
<Switch
<input
aria-label="Toggle notification failure"
className=""
aria-labelledby={null}
checked={false}
className="pf-c-switch__input"
disabled={false}
id="notification-9000-error-toggle"
isChecked={false}
isDisabled={false}
label="Failure"
labelOff="Failure"
onChange={[Function]}
ouiaContext={
Object {
"isOuia": false,
"ouiaId": null,
}
}
type="checkbox"
/>
<span
className="pf-c-switch__toggle"
/>
<span
aria-hidden="true"
className="pf-c-switch__label pf-m-on"
id={null}
>
<label
className="pf-c-switch"
htmlFor="notification-9000-error-toggle"
>
<input
aria-label="Toggle notification failure"
aria-labelledby={null}
checked={false}
className="pf-c-switch__input"
disabled={false}
id="notification-9000-error-toggle"
onChange={[Function]}
type="checkbox"
/>
<span
className="pf-c-switch__toggle"
/>
<span
aria-hidden="true"
className="pf-c-switch__label pf-m-on"
id={null}
>
Failure
</span>
<span
aria-hidden="true"
className="pf-c-switch__label pf-m-off"
id={null}
>
Failure
</span>
</label>
</Switch>
</ComponentWithOuia>
</Component>
</div>
</DataListCell>
</StyledComponent>
</NotificationListItem__DataListCell>
</div>
</DataListItemCells>
Failure
</span>
<span
aria-hidden="true"
className="pf-c-switch__label pf-m-off"
id={null}
>
Failure
</span>
</label>
</Switch>
</ComponentWithOuia>
</Component>
</div>
</DataListAction>
</StyledComponent>
</NotificationListItem__DataListAction>
</div>
</DataListItemRow>
</li>

View File

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

View File

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

View File

@ -73,7 +73,7 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
aria-describedby="pf-modal-0"
aria-label="Remove {0} Access"
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"
>
<button
@ -96,13 +96,34 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
/>
</svg>
</button>
<h1
class="pf-c-title pf-m-2xl"
<div
class="pf-c-title"
>
Remove {0} Access
</h1>
<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
class="pf-c-title pf-m-2xl"
>
Remove {0} Access
</h1>
</div>
</div>
<div
class="pf-c-modal-box__body"
id="pf-modal-0"
@ -111,24 +132,9 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
<br />
<br />
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
class="pf-c-modal-box__footer"
class="pf-c-modal-box__footer pf-m-align-left"
>
<button
aria-label="Confirm delete"
@ -151,12 +157,24 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
</body>
}
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}
isFooterLeftAligned={false}
isLarge={true}
isFooterLeftAligned={true}
isLarge={false}
isOpen={true}
isSmall={false}
isSmall={true}
onClose={[Function]}
showClose={true}
title="Remove {0} Access"
@ -174,7 +192,7 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
aria-describedby="pf-modal-0"
aria-label="Remove {0} Access"
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"
>
<button
@ -197,13 +215,34 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
/>
</svg>
</button>
<h1
class="pf-c-title pf-m-2xl"
<div
class="pf-c-title"
>
Remove {0} Access
</h1>
<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
class="pf-c-title pf-m-2xl"
>
Remove {0} Access
</h1>
</div>
</div>
<div
class="pf-c-modal-box__body"
id="pf-modal-0"
@ -212,24 +251,9 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
<br />
<br />
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
class="pf-c-modal-box__footer"
class="pf-c-modal-box__footer pf-m-align-left"
>
<button
aria-label="Confirm delete"
@ -270,13 +294,25 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
]
}
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}
id="pf-modal-0"
isFooterLeftAligned={false}
isLarge={true}
isFooterLeftAligned={true}
isLarge={false}
isOpen={true}
isSmall={false}
isSmall={true}
onClose={[Function]}
showClose={true}
title="Remove {0} Access"
@ -301,10 +337,10 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
className="pf-l-bullseye"
>
<ModalBox
className="awx-c-modal at-c-alertModal at-c-alertModal--danger"
className=""
id="pf-modal-0"
isLarge={true}
isSmall={false}
isLarge={false}
isSmall={true}
style={Object {}}
title="Remove {0} Access"
>
@ -312,7 +348,7 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
aria-describedby="pf-modal-0"
aria-label="Remove {0} Access"
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"
style={Object {}}
>
@ -395,23 +431,110 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
</ComponentWithOuia>
</Component>
</ModalBoxCloseButton>
<ModalBoxHeader
hideTitle={false}
<div
className="pf-c-title"
>
<Title
className=""
headingLevel="h1"
size="2xl"
>
<h1
className="pf-c-title pf-m-2xl"
<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}
>
Remove {0} Access
</h1>
</Title>
</ModalBoxHeader>
<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
size="2xl"
>
<h1
className="pf-c-title pf-m-2xl"
>
Remove {0} Access
</h1>
</Title>
</div>
</StyledComponent>
</AlertModal__Header>
</div>
<ModalBoxBody
id="pf-modal-0"
>
@ -423,41 +546,13 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
<br />
<br />
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>
</ModalBoxBody>
<ModalBoxFooter
isLeftAligned={false}
isLeftAligned={true}
>
<div
className="pf-c-modal-box__footer"
className="pf-c-modal-box__footer pf-m-align-left"
>
<Component
aria-label="Confirm delete"

View File

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

View File

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

View File

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

View File

@ -12,8 +12,12 @@ const Pulse = keyframes`
`;
const Wrapper = styled.div`
width: 14px;
align-items: center;
display: flex;
flex-flow: column nowrap;
height: 14px;
margin: 5px 0;
width: 14px;
`;
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', () => {
test('successfully mounts', () => {
const wrapper = mount(<WorkflowActionTooltipItem />);
const wrapper = mount(<WorkflowActionTooltipItem id="node" />);
expect(wrapper).toHaveLength(1);
});
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,6 +4,9 @@ import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import {
Button,
DataListAction as _DataListAction,
DataListCell,
DataListCheck,
DataListItem,
DataListItemRow,
DataListItemCells,
@ -13,11 +16,16 @@ import {
import { Link } from 'react-router-dom';
import { PencilAltIcon } from '@patternfly/react-icons';
import DataListCell from '@components/DataListCell';
import DataListCheck from '@components/DataListCheck';
import { Sparkline } from '@components/Sparkline';
import VerticalSeparator from '@components/VerticalSeparator';
import Sparkline from '@components/Sparkline';
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 {
static propTypes = {
@ -56,7 +64,6 @@ class HostListItem extends React.Component {
<DataListItemCells
dataListCells={[
<DataListCell key="name">
<VerticalSeparator />
<Link to={`${detailUrl}`}>
<b>{host.name}</b>
</Link>
@ -67,9 +74,7 @@ class HostListItem extends React.Component {
<DataListCell key="inventory">
{host.summary_fields.inventory && (
<Fragment>
<b style={{ marginRight: '20px' }}>
{i18n._(t`Inventory`)}
</b>
<b css="margin-right: 24px">{i18n._(t`Inventory`)}</b>
<Link
to={`/inventories/${
host.summary_fields.inventory.kind === 'smart'
@ -82,43 +87,44 @@ class HostListItem extends React.Component {
</Fragment>
)}
</DataListCell>,
<DataListCell key="enable" alignRight isFilled={false}>
<Tooltip
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.`
)}
position="top"
>
<Switch
css="display: inline-flex;"
id={`host-${host.id}-toggle`}
label={i18n._(t`On`)}
labelOff={i18n._(t`Off`)}
isChecked={host.enabled}
isDisabled={
toggleLoading ||
!host.summary_fields.user_capabilities.edit
}
onChange={() => onToggleHost(host)}
aria-label={i18n._(t`Toggle host`)}
/>
</Tooltip>
</DataListCell>,
<DataListCell key="edit" alignRight isFilled={false}>
{host.summary_fields.user_capabilities.edit && (
<Tooltip content={i18n._(t`Edit Host`)} position="top">
<Button
variant="plain"
component={Link}
to={`/hosts/${host.id}/edit`}
>
<PencilAltIcon />
</Button>
</Tooltip>
)}
</DataListCell>,
]}
/>
<DataListAction
aria-label="actions"
aria-labelledby={labelId}
id={labelId}
>
<Tooltip
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.`
)}
position="top"
>
<Switch
css="display: inline-flex;"
id={`host-${host.id}-toggle`}
label={i18n._(t`On`)}
labelOff={i18n._(t`Off`)}
isChecked={host.enabled}
isDisabled={
toggleLoading || !host.summary_fields.user_capabilities.edit
}
onChange={() => onToggleHost(host)}
aria-label={i18n._(t`Toggle host`)}
/>
</Tooltip>
{host.summary_fields.user_capabilities.edit && (
<Tooltip content={i18n._(t`Edit Host`)} position="top">
<Button
variant="plain"
component={Link}
to={`/hosts/${host.id}/edit`}
>
<PencilAltIcon />
</Button>
</Tooltip>
)}
</DataListAction>
</DataListItemRow>
</DataListItem>
);

View File

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

View File

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

View File

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

View File

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

View File

@ -4,20 +4,27 @@ import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import {
Button,
DataListAction as _DataListAction,
DataListCell,
DataListCheck,
DataListItem,
DataListItemRow,
DataListItemCells,
DataListItemRow,
Switch,
Tooltip,
} from '@patternfly/react-core';
import { Link } from 'react-router-dom';
import { PencilAltIcon } from '@patternfly/react-icons';
import DataListCell from '@components/DataListCell';
import DataListCheck from '@components/DataListCheck';
import { Sparkline } from '@components/Sparkline';
import VerticalSeparator from '@components/VerticalSeparator';
import Sparkline from '@components/Sparkline';
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) {
const {
@ -50,7 +57,6 @@ function InventoryHostItem(props) {
<DataListItemCells
dataListCells={[
<DataListCell key="divider">
<VerticalSeparator />
<Link to={`${detailUrl}`}>
<b>{host.name}</b>
</Link>
@ -58,41 +64,42 @@ function InventoryHostItem(props) {
<DataListCell key="recentJobs">
<Sparkline jobs={recentPlaybookJobs} />
</DataListCell>,
<DataListCell key="enable" alignRight isFilled={false}>
<Tooltip
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.`
)}
position="top"
>
<Switch
css="display: inline-flex;"
id={`host-${host.id}-toggle`}
label={i18n._(t`On`)}
labelOff={i18n._(t`Off`)}
isChecked={host.enabled}
isDisabled={
toggleLoading ||
!host.summary_fields.user_capabilities?.edit
}
onChange={() => toggleHost(host)}
aria-label={i18n._(t`Toggle host`)}
/>
</Tooltip>
</DataListCell>,
<DataListCell key="edit" alignRight isFilled={false}>
{host.summary_fields.user_capabilities?.edit && (
<Tooltip content={i18n._(t`Edit Host`)} position="top">
<Button variant="plain" component={Link} to={`${editUrl}`}>
<PencilAltIcon />
</Button>
</Tooltip>
)}
</DataListCell>,
]}
/>
<DataListAction
aria-label="actions"
aria-labelledby={labelId}
id={labelId}
>
<Tooltip
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.`
)}
position="top"
>
<Switch
css="display: inline-flex;"
id={`host-${host.id}-toggle`}
label={i18n._(t`On`)}
labelOff={i18n._(t`Off`)}
isChecked={host.enabled}
isDisabled={
toggleLoading || !host.summary_fields.user_capabilities?.edit
}
onChange={() => toggleHost(host)}
aria-label={i18n._(t`Toggle host`)}
/>
</Tooltip>
{host.summary_fields.user_capabilities?.edit && (
<Tooltip content={i18n._(t`Edit Host`)} position="top">
<Button variant="plain" component={Link} to={`${editUrl}`}>
<PencilAltIcon />
</Button>
</Tooltip>
)}
</DataListAction>
</DataListItemRow>
</DataListItem>
);

View File

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

View File

@ -104,48 +104,48 @@ describe('<InventoryHostList />', () => {
test('should check and uncheck the row item', async () => {
expect(
wrapper.find('PFDataListCheck[id="select-host-1"]').props().checked
wrapper.find('DataListCheck[id="select-host-1"]').props().checked
).toBe(false);
await act(async () => {
wrapper.find('PFDataListCheck[id="select-host-1"]').invoke('onChange')(
wrapper.find('DataListCheck[id="select-host-1"]').invoke('onChange')(
true
);
});
wrapper.update();
expect(
wrapper.find('PFDataListCheck[id="select-host-1"]').props().checked
wrapper.find('DataListCheck[id="select-host-1"]').props().checked
).toBe(true);
await act(async () => {
wrapper.find('PFDataListCheck[id="select-host-1"]').invoke('onChange')(
wrapper.find('DataListCheck[id="select-host-1"]').invoke('onChange')(
false
);
});
wrapper.update();
expect(
wrapper.find('PFDataListCheck[id="select-host-1"]').props().checked
wrapper.find('DataListCheck[id="select-host-1"]').props().checked
).toBe(false);
});
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);
});
await act(async () => {
wrapper.find('Checkbox#select-all').invoke('onChange')(true);
});
wrapper.update();
wrapper.find('PFDataListCheck').forEach(el => {
wrapper.find('DataListCheck').forEach(el => {
expect(el.props().checked).toBe(true);
});
await act(async () => {
wrapper.find('Checkbox#select-all').invoke('onChange')(false);
});
wrapper.update();
wrapper.find('PFDataListCheck').forEach(el => {
wrapper.find('DataListCheck').forEach(el => {
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 () => {
await act(async () => {
wrapper.find('PFDataListCheck[id="select-host-3"]').invoke('onChange')();
wrapper.find('DataListCheck[id="select-host-3"]').invoke('onChange')();
});
wrapper.update();
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 () => {
HostsAPI.destroy = jest.fn();
await act(async () => {
wrapper.find('PFDataListCheck[id="select-host-1"]').invoke('onChange')();
wrapper.find('DataListCheck[id="select-host-1"]').invoke('onChange')();
});
wrapper.update();
await act(async () => {
@ -220,7 +220,7 @@ describe('<InventoryHostList />', () => {
})
);
await act(async () => {
wrapper.find('PFDataListCheck[id="select-host-1"]').invoke('onChange')();
wrapper.find('DataListCheck[id="select-host-1"]').invoke('onChange')();
});
wrapper.update();
await act(async () => {
@ -242,7 +242,7 @@ describe('<InventoryHostList />', () => {
Promise.reject(new Error())
);
await act(async () => {
wrapper.find('PFDataListCheck[id="select-host-1"]').invoke('onChange')();
wrapper.find('DataListCheck[id="select-host-1"]').invoke('onChange')();
});
wrapper.update();
await act(async () => {

View File

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

View File

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

View File

@ -27,7 +27,7 @@ const InventoryGroupsDeleteModal = ({
isOpen={isModalOpen}
variant="danger"
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}
actions={[
@ -60,7 +60,7 @@ const InventoryGroupsDeleteModal = ({
return <ListItem key={group.id}>{group.name}</ListItem>;
})}
</div>
<div css="padding-left: 1px;">
<div>
<Radio
id="radio-delete"
key="radio-delete"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -109,7 +109,7 @@ describe('NodeModal', () => {
wrapper.find('button#next-node-modal').simulate('click');
});
wrapper.update();
wrapper.find('DataListRadio').simulate('click');
wrapper.find('Radio').simulate('click');
await act(async () => {
wrapper.find('button#next-node-modal').simulate('click');
});
@ -136,7 +136,7 @@ describe('NodeModal', () => {
wrapper.find('AnsibleSelect').prop('onChange')(null, 'project_sync');
});
wrapper.update();
wrapper.find('DataListRadio').simulate('click');
wrapper.find('Radio').simulate('click');
await act(async () => {
wrapper.find('button#next-node-modal').simulate('click');
});
@ -166,7 +166,7 @@ describe('NodeModal', () => {
);
});
wrapper.update();
wrapper.find('DataListRadio').simulate('click');
wrapper.find('Radio').simulate('click');
await act(async () => {
wrapper.find('button#next-node-modal').simulate('click');
});
@ -193,7 +193,7 @@ describe('NodeModal', () => {
);
});
wrapper.update();
wrapper.find('DataListRadio').simulate('click');
wrapper.find('Radio').simulate('click');
await act(async () => {
wrapper.find('button#next-node-modal').simulate('click');
});
@ -396,7 +396,7 @@ describe('NodeModal', () => {
);
});
wrapper.update();
wrapper.find('DataListRadio').simulate('click');
wrapper.find('Radio').simulate('click');
await act(async () => {
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 FormRow from '@components/FormRow';
import AnsibleSelect from '@components/AnsibleSelect';
import VerticalSeperator from '@components/VerticalSeparator';
import InventorySourcesList from './InventorySourcesList';
import JobTemplatesList from './JobTemplatesList';
import ProjectsList from './ProjectsList';
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)`
width: 200px;
:not(:first-of-type) {
@ -47,9 +39,8 @@ function NodeTypeStep({
}) {
return (
<>
<div css=" display: flex; align-items: center; margin-bottom: 20px;">
<b>{i18n._(t`Node Type`)}</b>
<VerticalSeperator />
<div css="display: flex; align-items: center; margin-bottom: 20px;">
<b css="margin-right: 24px">{i18n._(t`Node Type`)}</b>
<div>
<AnsibleSelect
id="nodeResource-select"
@ -93,7 +84,6 @@ function NodeTypeStep({
/>
</div>
</div>
<Divider component="div" />
{nodeType === 'job_template' && (
<JobTemplatesList
nodeResource={nodeResource}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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