mirror of
https://github.com/ansible/awx.git
synced 2024-11-01 08:21:15 +03:00
Merge remote-tracking branch 'origin' into access-list-remove-role-functionality
This commit is contained in:
commit
21156d1409
@ -119,6 +119,63 @@ describe('<Pagination />', () => {
|
||||
pageSizeDropdownItems.at(1).simulate('click');
|
||||
});
|
||||
|
||||
test('itemCount displays correctly', () => {
|
||||
const onSetPage = jest.fn();
|
||||
|
||||
pagination = mount(
|
||||
<I18nProvider>
|
||||
<Pagination
|
||||
count={7}
|
||||
page={1}
|
||||
pageCount={2}
|
||||
page_size={5}
|
||||
pageSizeOptions={[5, 10, 25, 50]}
|
||||
onSetPage={onSetPage}
|
||||
/>
|
||||
</I18nProvider>
|
||||
);
|
||||
const itemCount = pagination.find('.awx-pagination__item-count');
|
||||
expect(itemCount.text()).toEqual('Items 1 – 5 of 7');
|
||||
});
|
||||
|
||||
test('itemCount matching pageSize displays correctly', () => {
|
||||
const onSetPage = jest.fn();
|
||||
|
||||
pagination = mount(
|
||||
<I18nProvider>
|
||||
<Pagination
|
||||
count={5}
|
||||
page={1}
|
||||
pageCount={1}
|
||||
page_size={5}
|
||||
pageSizeOptions={[5, 10, 25, 50]}
|
||||
onSetPage={onSetPage}
|
||||
/>
|
||||
</I18nProvider>
|
||||
);
|
||||
const itemCount = pagination.find('.awx-pagination__item-count');
|
||||
expect(itemCount.text()).toEqual('Items 1 – 5 of 5');
|
||||
});
|
||||
|
||||
test('itemCount less than pageSize displays correctly', () => {
|
||||
const onSetPage = jest.fn();
|
||||
|
||||
pagination = mount(
|
||||
<I18nProvider>
|
||||
<Pagination
|
||||
count={3}
|
||||
page={1}
|
||||
pageCount={1}
|
||||
page_size={5}
|
||||
pageSizeOptions={[5, 10, 25, 50]}
|
||||
onSetPage={onSetPage}
|
||||
/>
|
||||
</I18nProvider>
|
||||
);
|
||||
const itemCount = pagination.find('.awx-pagination__item-count');
|
||||
expect(itemCount.text()).toEqual('Items 1 – 3 of 3');
|
||||
});
|
||||
|
||||
test('submit a new page by typing in input works', () => {
|
||||
const textInputSelector = '.awx-pagination__page-input.pf-c-form-control';
|
||||
const submitFormSelector = '.awx-pagination__page-input-form';
|
||||
|
@ -1,69 +0,0 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import Tooltip from '../../src/components/Tooltip';
|
||||
|
||||
describe('<Tooltip />', () => {
|
||||
let elem;
|
||||
let content;
|
||||
let mouseOverHandler;
|
||||
let mouseOutHandler;
|
||||
const child = (<span>foo</span>);
|
||||
const message = 'hi';
|
||||
|
||||
test('initially renders without crashing', () => {
|
||||
elem = mount(
|
||||
<Tooltip message={message}>
|
||||
{child}
|
||||
</Tooltip>
|
||||
);
|
||||
expect(elem.length).toBe(1);
|
||||
});
|
||||
|
||||
test('shows/hides with mouse over and leave', () => {
|
||||
elem = mount(
|
||||
<Tooltip message={message}>
|
||||
{child}
|
||||
</Tooltip>
|
||||
);
|
||||
mouseOverHandler = elem.find('.mouseOverHandler');
|
||||
mouseOutHandler = elem.find('.mouseOutHandler');
|
||||
expect(elem.state().isDisplayed).toBe(false);
|
||||
elem.update();
|
||||
content = elem.find('.pf-c-tooltip__content');
|
||||
expect(content.length).toBe(0);
|
||||
mouseOverHandler.props().onMouseOver();
|
||||
expect(elem.state().isDisplayed).toBe(true);
|
||||
elem.update();
|
||||
content = elem.find('.pf-c-tooltip__content');
|
||||
expect(content.length).toBe(1);
|
||||
mouseOutHandler.props().onMouseLeave();
|
||||
expect(elem.state().isDisplayed).toBe(false);
|
||||
elem.update();
|
||||
content = elem.find('.pf-c-tooltip__content');
|
||||
expect(content.length).toBe(0);
|
||||
});
|
||||
|
||||
test('shows/hides with focus and blur', () => {
|
||||
elem = mount(
|
||||
<Tooltip message={message}>
|
||||
{child}
|
||||
</Tooltip>
|
||||
);
|
||||
mouseOverHandler = elem.find('.mouseOverHandler');
|
||||
mouseOutHandler = elem.find('.mouseOutHandler');
|
||||
expect(elem.state().isDisplayed).toBe(false);
|
||||
elem.update();
|
||||
content = elem.find('.pf-c-tooltip__content');
|
||||
expect(content.length).toBe(0);
|
||||
mouseOverHandler.props().onFocus();
|
||||
expect(elem.state().isDisplayed).toBe(true);
|
||||
elem.update();
|
||||
content = elem.find('.pf-c-tooltip__content');
|
||||
expect(content.length).toBe(1);
|
||||
mouseOutHandler.props().onBlur();
|
||||
expect(elem.state().isDisplayed).toBe(false);
|
||||
elem.update();
|
||||
content = elem.find('.pf-c-tooltip__content');
|
||||
expect(content.length).toBe(0);
|
||||
});
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { MemoryRouter, Router } from 'react-router-dom';
|
||||
import { I18nProvider } from '@lingui/react';
|
||||
import { ConfigContext } from '../../../../src/context';
|
||||
import OrganizationAdd from '../../../../src/pages/Organizations/screens/OrganizationAdd';
|
||||
@ -18,6 +18,7 @@ describe('<OrganizationAdd />', () => {
|
||||
</MemoryRouter>
|
||||
);
|
||||
});
|
||||
|
||||
test('calls "onFieldChange" when input values change', () => {
|
||||
const spy = jest.spyOn(OrganizationAdd.WrappedComponent.prototype, 'onFieldChange');
|
||||
const wrapper = mount(
|
||||
@ -35,6 +36,7 @@ describe('<OrganizationAdd />', () => {
|
||||
wrapper.find('input#add-org-form-description').simulate('change', { target: { value: 'bar' } });
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('calls "onSubmit" when Save button is clicked', () => {
|
||||
const spy = jest.spyOn(OrganizationAdd.WrappedComponent.prototype, 'onSubmit');
|
||||
const wrapper = mount(
|
||||
@ -51,6 +53,7 @@ describe('<OrganizationAdd />', () => {
|
||||
wrapper.find('button[aria-label="Save"]').prop('onClick')();
|
||||
expect(spy).toBeCalled();
|
||||
});
|
||||
|
||||
test('calls "onCancel" when Cancel button is clicked', () => {
|
||||
const spy = jest.spyOn(OrganizationAdd.WrappedComponent.prototype, 'onCancel');
|
||||
const wrapper = mount(
|
||||
@ -68,6 +71,25 @@ describe('<OrganizationAdd />', () => {
|
||||
expect(spy).toBeCalled();
|
||||
});
|
||||
|
||||
test('calls "onCancel" when close button (x) is clicked', () => {
|
||||
const wrapper = mount(
|
||||
<MemoryRouter initialEntries={['/organizations/add']} initialIndex={0}>
|
||||
<I18nProvider>
|
||||
<OrganizationAdd
|
||||
match={{ path: '/organizations/add', url: '/organizations/add' }}
|
||||
location={{ search: '', pathname: '/organizations/add' }}
|
||||
/>
|
||||
</I18nProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
const history = wrapper.find(Router).prop('history');
|
||||
expect(history.length).toBe(1);
|
||||
expect(history.location.pathname).toEqual('/organizations/add');
|
||||
wrapper.find('button[aria-label="Close"]').prop('onClick')();
|
||||
expect(history.length).toBe(2);
|
||||
expect(history.location.pathname).toEqual('/organizations');
|
||||
});
|
||||
|
||||
test('Successful form submission triggers redirect', (done) => {
|
||||
const onSuccess = jest.spyOn(OrganizationAdd.WrappedComponent.prototype, 'onSuccess');
|
||||
const mockedResp = { data: { id: 1, related: { instance_groups: '/bar' } } };
|
||||
|
42
src/app.scss
42
src/app.scss
@ -206,6 +206,21 @@
|
||||
--pf-c-card__body--PaddingTop: 24px;
|
||||
}
|
||||
|
||||
//
|
||||
// pf tooltip overrides
|
||||
//
|
||||
|
||||
.pf-c-tooltip__content {
|
||||
--pf-c-tooltip__content--PaddingTop: 0.71rem;
|
||||
--pf-c-tooltip__content--PaddingRight: 0.71rem;
|
||||
--pf-c-tooltip__content--PaddingBottom: 0.71rem;
|
||||
--pf-c-tooltip__content--PaddingLeft: 0.71rem;
|
||||
}
|
||||
// higher specificity needed to override PF styles added dynamically to page
|
||||
.pf-c-tooltip .pf-c-tooltip__content {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
//
|
||||
// pf empty state overrides
|
||||
//
|
||||
@ -219,6 +234,13 @@
|
||||
// note that these should be given a consistent prefix
|
||||
// and bem style, as well as moved into component-based scss files
|
||||
//
|
||||
.awx-lookup {
|
||||
min-height: 36px;
|
||||
|
||||
.pf-c-form-control {
|
||||
--pf-c-form-control--Height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.awx-c-list {
|
||||
border-bottom: 1px solid #d7d7d7;
|
||||
@ -236,22 +258,20 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.awx-c-alert {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pf-c-alert {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 20;
|
||||
|
||||
& button {
|
||||
button {
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.pf-c-alert__icon > svg {
|
||||
fill: white;
|
||||
}
|
||||
.pf-c-alert__icon {
|
||||
--pf-c-alert__icon--Color: white;
|
||||
}
|
||||
|
||||
.at-u-textRight {
|
||||
text-align: right;
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import {
|
||||
Toolbar,
|
||||
ToolbarGroup,
|
||||
ToolbarItem,
|
||||
Tooltip,
|
||||
} from '@patternfly/react-core';
|
||||
import {
|
||||
BarsIcon,
|
||||
@ -30,7 +31,6 @@ import {
|
||||
Link
|
||||
} from 'react-router-dom';
|
||||
|
||||
import Tooltip from '../Tooltip';
|
||||
import VerticalSeparator from '../VerticalSeparator';
|
||||
|
||||
const flexGrowStyling = {
|
||||
@ -291,7 +291,7 @@ class DataListToolbar extends React.Component {
|
||||
<LevelItem>
|
||||
{ showDelete && (
|
||||
<Tooltip
|
||||
message={i18n._(t`Delete`)}
|
||||
content={i18n._(t`Delete`)}
|
||||
position="top"
|
||||
>
|
||||
<Button
|
||||
|
@ -34,6 +34,7 @@ class NotificationListItem extends React.Component {
|
||||
to={{
|
||||
pathname: detailUrl
|
||||
}}
|
||||
style={{ marginRight: '1.5em' }}
|
||||
>
|
||||
<b>{name}</b>
|
||||
</Link>
|
||||
@ -82,4 +83,3 @@ NotificationListItem.defaultProps = {
|
||||
};
|
||||
|
||||
export default NotificationListItem;
|
||||
|
||||
|
@ -116,7 +116,12 @@ class Pagination extends Component {
|
||||
const isOnFirst = page === 1;
|
||||
const isOnLast = page === pageCount;
|
||||
|
||||
const itemCount = isOnLast ? count % page_size : page_size;
|
||||
let itemCount;
|
||||
if (!isOnLast || count === page_size) {
|
||||
itemCount = page_size;
|
||||
} else {
|
||||
itemCount = count % page_size;
|
||||
}
|
||||
const itemMin = ((page - 1) * page_size) + 1;
|
||||
const itemMax = itemMin + itemCount - 1;
|
||||
|
||||
@ -154,7 +159,7 @@ class Pagination extends Component {
|
||||
)}
|
||||
<div className="awx-pagination__counts">
|
||||
<div className="awx-pagination__item-count">
|
||||
<Trans>{`Items ${itemMin} - ${itemMax} of ${count}`}</Trans>
|
||||
<Trans>{`Items ${itemMin} – ${itemMax} of ${count}`}</Trans>
|
||||
</div>
|
||||
{pageCount !== 1 && (
|
||||
<div className="awx-pagination__page-count">
|
||||
|
@ -1,9 +1,8 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Button } from '@patternfly/react-core';
|
||||
import { Button, Tooltip } from '@patternfly/react-core';
|
||||
import { TimesIcon } from '@patternfly/react-icons';
|
||||
import Tooltip from '../Tooltip';
|
||||
import './tabs.scss';
|
||||
|
||||
const Tabs = ({ children, labelText, closeButton }) => (
|
||||
@ -17,7 +16,7 @@ const Tabs = ({ children, labelText, closeButton }) => (
|
||||
{closeButton
|
||||
&& (
|
||||
<Tooltip
|
||||
message={closeButton.text}
|
||||
content={closeButton.text}
|
||||
position="top"
|
||||
>
|
||||
<Link to={closeButton.link}>
|
||||
|
@ -1,96 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const toolTipContent = {
|
||||
padding: '10px',
|
||||
minWidth: '100px',
|
||||
};
|
||||
|
||||
class Tooltip extends React.Component {
|
||||
transforms = {
|
||||
top: {
|
||||
bottom: '100%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -25%)'
|
||||
},
|
||||
bottom: {
|
||||
top: '100%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, 25%)'
|
||||
},
|
||||
left: {
|
||||
top: '50%',
|
||||
right: '100%',
|
||||
transform: 'translate(-25%, -50%)'
|
||||
},
|
||||
right: {
|
||||
bottom: '100%',
|
||||
left: '50%',
|
||||
transform: 'translate(25%, 50%)'
|
||||
},
|
||||
};
|
||||
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isDisplayed: false
|
||||
};
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
children,
|
||||
message,
|
||||
position,
|
||||
} = this.props;
|
||||
const {
|
||||
isDisplayed
|
||||
} = this.state;
|
||||
|
||||
if (message === '') {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<span
|
||||
style={{ position: 'relative' }}
|
||||
className="mouseOutHandler"
|
||||
onMouseLeave={() => this.setState({ isDisplayed: false })}
|
||||
onBlur={() => this.setState({ isDisplayed: false })}
|
||||
>
|
||||
{ isDisplayed
|
||||
&& (
|
||||
<div
|
||||
style={{ position: 'absolute', zIndex: '10', ...this.transforms[position] }}
|
||||
className={`pf-c-tooltip pf-m-${position}`}
|
||||
>
|
||||
<div className="pf-c-tooltip__arrow" />
|
||||
<div className="pf-c-tooltip__content" style={toolTipContent}>
|
||||
{ message }
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<span
|
||||
className="mouseOverHandler"
|
||||
onMouseOver={() => this.setState({ isDisplayed: true })}
|
||||
onFocus={() => this.setState({ isDisplayed: true })}
|
||||
>
|
||||
{ children }
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Tooltip.propTypes = {
|
||||
children: PropTypes.element.isRequired,
|
||||
message: PropTypes.string.isRequired,
|
||||
position: PropTypes.string,
|
||||
};
|
||||
|
||||
Tooltip.defaultProps = {
|
||||
position: 'top',
|
||||
};
|
||||
|
||||
export default Tooltip;
|
@ -1,3 +0,0 @@
|
||||
import Tooltip from './Tooltip';
|
||||
|
||||
export default Tooltip;
|
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { I18n, i18nMark } from '@lingui/react';
|
||||
@ -10,8 +10,12 @@ import {
|
||||
TextInput,
|
||||
Gallery,
|
||||
Card,
|
||||
CardHeader,
|
||||
CardBody,
|
||||
Button,
|
||||
Tooltip,
|
||||
} from '@patternfly/react-core';
|
||||
import { QuestionCircleIcon, TimesIcon } from '@patternfly/react-icons';
|
||||
|
||||
import { ConfigContext } from '../../../context';
|
||||
import Lookup from '../../../components/Lookup';
|
||||
@ -108,11 +112,25 @@ class OrganizationAdd extends React.Component {
|
||||
|
||||
return (
|
||||
<PageSection>
|
||||
<Card>
|
||||
<CardBody>
|
||||
<Form autoComplete="off">
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<Card>
|
||||
<CardHeader className="at-u-textRight">
|
||||
<Tooltip
|
||||
content={i18n._(t`Close`)}
|
||||
position="top"
|
||||
>
|
||||
<Button
|
||||
variant="plain"
|
||||
aria-label={i18n._(t`Close`)}
|
||||
onClick={this.onCancel}
|
||||
>
|
||||
<TimesIcon />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<Form autoComplete="off">
|
||||
<Gallery gutter="md">
|
||||
<FormGroup
|
||||
label={i18n._(t`Name`)}
|
||||
@ -135,7 +153,21 @@ class OrganizationAdd extends React.Component {
|
||||
onChange={this.onFieldChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup label={i18n._(t`Instance Groups`)} fieldId="add-org-form-instance-groups">
|
||||
<FormGroup
|
||||
label={(
|
||||
<Fragment>
|
||||
{i18n._(t`Instance Groups`)}
|
||||
{' '}
|
||||
<Tooltip
|
||||
position="right"
|
||||
content={i18n._(t`Select the Instance Groups for this Organization to run on.`)}
|
||||
>
|
||||
<QuestionCircleIcon />
|
||||
</Tooltip>
|
||||
</Fragment>
|
||||
)}
|
||||
fieldId="add-org-form-instance-groups"
|
||||
>
|
||||
<Lookup
|
||||
lookupHeader={i18n._(t`Instance Groups`)}
|
||||
name="instanceGroups"
|
||||
@ -149,7 +181,21 @@ class OrganizationAdd extends React.Component {
|
||||
<ConfigContext.Consumer>
|
||||
{({ custom_virtualenvs }) => (
|
||||
custom_virtualenvs && custom_virtualenvs.length > 1 && (
|
||||
<FormGroup label={i18n._(t`Ansible Environment`)} fieldId="add-org-custom-virtualenv">
|
||||
<FormGroup
|
||||
label={(
|
||||
<Fragment>
|
||||
{i18n._(t`Ansible Environment`)}
|
||||
{' '}
|
||||
<Tooltip
|
||||
position="right"
|
||||
content={i18n._(t`Select the custom Python virtual environment for this organization to run on.`)}
|
||||
>
|
||||
<QuestionCircleIcon />
|
||||
</Tooltip>
|
||||
</Fragment>
|
||||
)}
|
||||
fieldId="add-org-custom-virtualenv"
|
||||
>
|
||||
<AnsibleSelect
|
||||
label={i18n._(t`Ansible Environment`)}
|
||||
name="custom_virtualenv"
|
||||
@ -163,17 +209,17 @@ class OrganizationAdd extends React.Component {
|
||||
)}
|
||||
</ConfigContext.Consumer>
|
||||
</Gallery>
|
||||
)}
|
||||
</I18n>
|
||||
<FormActionGroup
|
||||
onSubmit={this.onSubmit}
|
||||
submitDisabled={!enabled}
|
||||
onCancel={this.onCancel}
|
||||
/>
|
||||
{error ? <div>error</div> : ''}
|
||||
</Form>
|
||||
</CardBody>
|
||||
</Card>
|
||||
<FormActionGroup
|
||||
onSubmit={this.onSubmit}
|
||||
submitDisabled={!enabled}
|
||||
onCancel={this.onCancel}
|
||||
/>
|
||||
{error ? <div>error</div> : ''}
|
||||
</Form>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)}
|
||||
</I18n>
|
||||
</PageSection>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user