mirror of
https://github.com/ansible/awx.git
synced 2026-02-15 02:00:01 -03:30
Convert last class components to functional components
Convert last class components to functional components
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Checkbox as PFCheckbox } from '@patternfly/react-core';
|
import { Checkbox as PFCheckbox } from '@patternfly/react-core';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@@ -17,27 +17,25 @@ const Checkbox = styled(PFCheckbox)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
class CheckboxCard extends Component {
|
function CheckboxCard(props) {
|
||||||
render() {
|
const { name, description, isSelected, onSelect, itemId } = props;
|
||||||
const { name, description, isSelected, onSelect, itemId } = this.props;
|
return (
|
||||||
return (
|
<CheckboxWrapper>
|
||||||
<CheckboxWrapper>
|
<Checkbox
|
||||||
<Checkbox
|
isChecked={isSelected}
|
||||||
isChecked={isSelected}
|
onChange={onSelect}
|
||||||
onChange={onSelect}
|
aria-label={name}
|
||||||
aria-label={name}
|
id={`checkbox-card-${itemId}`}
|
||||||
id={`checkbox-card-${itemId}`}
|
label={
|
||||||
label={
|
<>
|
||||||
<>
|
<div style={{ fontWeight: 'bold' }}>{name}</div>
|
||||||
<div style={{ fontWeight: 'bold' }}>{name}</div>
|
<div>{description}</div>
|
||||||
<div>{description}</div>
|
</>
|
||||||
</>
|
}
|
||||||
}
|
value={itemId}
|
||||||
value={itemId}
|
/>
|
||||||
/>
|
</CheckboxWrapper>
|
||||||
</CheckboxWrapper>
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckboxCard.propTypes = {
|
CheckboxCard.propTypes = {
|
||||||
|
|||||||
@@ -1,60 +1,45 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import PropTypes, { oneOfType, string, arrayOf } from 'prop-types';
|
import PropTypes, { oneOfType, string, arrayOf } from 'prop-types';
|
||||||
import { matchPath, Link, withRouter } from 'react-router-dom';
|
import { matchPath, Link, useHistory } from 'react-router-dom';
|
||||||
import { NavExpandable, NavItem } from '@patternfly/react-core';
|
import { NavExpandable, NavItem } from '@patternfly/react-core';
|
||||||
|
|
||||||
class NavExpandableGroup extends Component {
|
function NavExpandableGroup(props) {
|
||||||
constructor(props) {
|
const history = useHistory();
|
||||||
super(props);
|
const { groupId, groupTitle, routes } = props;
|
||||||
const { routes } = this.props;
|
|
||||||
|
|
||||||
// Extract a list of paths from the route params and store them for later. This creates
|
// Extract a list of paths from the route params and store them for later. This creates
|
||||||
// an array of url paths associated with any NavItem component rendered by this component.
|
// an array of url paths associated with any NavItem component rendered by this component.
|
||||||
this.navItemPaths = routes.map(({ path }) => path);
|
const navItemPaths = routes.map(({ path }) => path);
|
||||||
this.isActiveGroup = this.isActiveGroup.bind(this);
|
|
||||||
this.isActivePath = this.isActivePath.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
isActiveGroup() {
|
const isActive = navItemPaths.some(isActivePath);
|
||||||
return this.navItemPaths.some(this.isActivePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
isActivePath(path) {
|
function isActivePath(path) {
|
||||||
const { history } = this.props;
|
|
||||||
return Boolean(matchPath(history.location.pathname, { path }));
|
return Boolean(matchPath(history.location.pathname, { path }));
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
if (routes.length === 1 && groupId === 'settings') {
|
||||||
const { groupId, groupTitle, routes } = this.props;
|
const [{ path }] = routes;
|
||||||
|
|
||||||
if (routes.length === 1 && groupId === 'settings') {
|
|
||||||
const [{ path }] = routes;
|
|
||||||
return (
|
|
||||||
<NavItem itemId={groupId} isActive={this.isActivePath(path)} key={path}>
|
|
||||||
<Link to={path}>{groupTitle}</Link>
|
|
||||||
</NavItem>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavExpandable
|
<NavItem itemId={groupId} isActive={isActivePath(path)} key={path}>
|
||||||
isActive={this.isActiveGroup()}
|
<Link to={path}>{groupTitle}</Link>
|
||||||
isExpanded
|
</NavItem>
|
||||||
groupId={groupId}
|
|
||||||
title={groupTitle}
|
|
||||||
>
|
|
||||||
{routes.map(({ path, title }) => (
|
|
||||||
<NavItem
|
|
||||||
groupId={groupId}
|
|
||||||
isActive={this.isActivePath(path)}
|
|
||||||
key={path}
|
|
||||||
>
|
|
||||||
<Link to={path}>{title}</Link>
|
|
||||||
</NavItem>
|
|
||||||
))}
|
|
||||||
</NavExpandable>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NavExpandable
|
||||||
|
isActive={isActive}
|
||||||
|
isExpanded
|
||||||
|
groupId={groupId}
|
||||||
|
title={groupTitle}
|
||||||
|
>
|
||||||
|
{routes.map(({ path, title }) => (
|
||||||
|
<NavItem groupId={groupId} isActive={isActivePath(path)} key={path}>
|
||||||
|
<Link to={path}>{title}</Link>
|
||||||
|
</NavItem>
|
||||||
|
))}
|
||||||
|
</NavExpandable>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
NavExpandableGroup.propTypes = {
|
NavExpandableGroup.propTypes = {
|
||||||
@@ -63,4 +48,4 @@ NavExpandableGroup.propTypes = {
|
|||||||
routes: arrayOf(PropTypes.object).isRequired,
|
routes: arrayOf(PropTypes.object).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withRouter(NavExpandableGroup);
|
export default NavExpandableGroup;
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter, withRouter } from 'react-router-dom';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
|
|
||||||
import { Nav } from '@patternfly/react-core';
|
import { Nav } from '@patternfly/react-core';
|
||||||
import NavExpandableGroup from './NavExpandableGroup';
|
import _NavExpandableGroup from './NavExpandableGroup';
|
||||||
|
|
||||||
|
const NavExpandableGroup = withRouter(_NavExpandableGroup);
|
||||||
|
|
||||||
describe('NavExpandableGroup', () => {
|
describe('NavExpandableGroup', () => {
|
||||||
test('initialization and render', () => {
|
test('initialization and render', () => {
|
||||||
@@ -21,47 +23,88 @@ describe('NavExpandableGroup', () => {
|
|||||||
/>
|
/>
|
||||||
</Nav>
|
</Nav>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
)
|
).find('NavExpandableGroup');
|
||||||
.find('NavExpandableGroup')
|
|
||||||
.instance();
|
|
||||||
|
|
||||||
expect(component.navItemPaths).toEqual(['/foo', '/bar', '/fiz']);
|
expect(component.find('NavItem').length).toEqual(3);
|
||||||
expect(component.isActiveGroup()).toEqual(true);
|
let link = component.find('NavItem').at(0);
|
||||||
|
expect(component.find('NavItem').at(0).prop('isActive')).toBeTruthy();
|
||||||
|
expect(link.find('Link').prop('to')).toBe('/foo');
|
||||||
|
|
||||||
|
link = component.find('NavItem').at(1);
|
||||||
|
expect(link.prop('isActive')).toBeFalsy();
|
||||||
|
expect(link.find('Link').prop('to')).toBe('/bar');
|
||||||
|
|
||||||
|
link = component.find('NavItem').at(2);
|
||||||
|
expect(link.prop('isActive')).toBeFalsy();
|
||||||
|
expect(link.find('Link').prop('to')).toBe('/fiz');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('isActivePath', () => {
|
test('when location is /foo/1/bar/fiz isActive returns false', () => {
|
||||||
const params = [
|
const component = mount(
|
||||||
['/fo', '/foo', false],
|
<MemoryRouter initialEntries={['/foo/1/bar/fiz']}>
|
||||||
['/foo', '/foo', true],
|
<Nav aria-label="Test Navigation">
|
||||||
['/foo/1/bar/fiz', '/foo', true],
|
<NavExpandableGroup
|
||||||
['/foo/1/bar/fiz', 'foo', false],
|
groupId="test"
|
||||||
['/foo/1/bar/fiz', 'foo/', false],
|
groupTitle="Test"
|
||||||
['/foo/1/bar/fiz', '/bar', false],
|
routes={[
|
||||||
['/foo/1/bar/fiz', '/fiz', false],
|
{ path: '/foo', title: 'Foo' },
|
||||||
];
|
{ path: '/bar', title: 'Bar' },
|
||||||
|
{ path: '/fiz', title: 'Fiz' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Nav>
|
||||||
|
</MemoryRouter>
|
||||||
|
).find('NavExpandableGroup');
|
||||||
|
|
||||||
params.forEach(([location, path, expected]) => {
|
expect(component.find('NavItem').length).toEqual(3);
|
||||||
test(`when location is ${location}, isActivePath('${path}') returns ${expected} `, () => {
|
const link = component.find('NavItem').at(0);
|
||||||
const component = mount(
|
expect(component.find('NavItem').at(0).prop('isActive')).toBeTruthy();
|
||||||
<MemoryRouter initialEntries={[location]}>
|
expect(link.find('Link').prop('to')).toBe('/foo');
|
||||||
<Nav aria-label="Test Navigation">
|
});
|
||||||
<NavExpandableGroup
|
|
||||||
groupId="test"
|
|
||||||
groupTitle="Test"
|
|
||||||
routes={[
|
|
||||||
{ path: '/foo', title: 'Foo' },
|
|
||||||
{ path: '/bar', title: 'Bar' },
|
|
||||||
{ path: '/fiz', title: 'Fiz' },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</Nav>
|
|
||||||
</MemoryRouter>
|
|
||||||
)
|
|
||||||
.find('NavExpandableGroup')
|
|
||||||
.instance();
|
|
||||||
|
|
||||||
expect(component.isActivePath(path)).toEqual(expected);
|
test('when location is /fo isActive returns false', () => {
|
||||||
});
|
const component = mount(
|
||||||
});
|
<MemoryRouter initialEntries={['/fo']}>
|
||||||
|
<Nav aria-label="Test Navigation">
|
||||||
|
<NavExpandableGroup
|
||||||
|
groupId="test"
|
||||||
|
groupTitle="Test"
|
||||||
|
routes={[
|
||||||
|
{ path: '/foo', title: 'Foo' },
|
||||||
|
{ path: '/bar', title: 'Bar' },
|
||||||
|
{ path: '/fiz', title: 'Fiz' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Nav>
|
||||||
|
</MemoryRouter>
|
||||||
|
).find('NavExpandableGroup');
|
||||||
|
|
||||||
|
expect(component.find('NavItem').length).toEqual(3);
|
||||||
|
const link = component.find('NavItem').at(0);
|
||||||
|
expect(component.find('NavItem').at(0).prop('isActive')).toBeFalsy();
|
||||||
|
expect(link.find('Link').prop('to')).toBe('/foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('when location is /foo isActive returns true', () => {
|
||||||
|
const component = mount(
|
||||||
|
<MemoryRouter initialEntries={['/foo']}>
|
||||||
|
<Nav aria-label="Test Navigation">
|
||||||
|
<NavExpandableGroup
|
||||||
|
groupId="test"
|
||||||
|
groupTitle="Test"
|
||||||
|
routes={[
|
||||||
|
{ path: '/foo', title: 'Foo' },
|
||||||
|
{ path: '/bar', title: 'Bar' },
|
||||||
|
{ path: '/fiz', title: 'Fiz' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Nav>
|
||||||
|
</MemoryRouter>
|
||||||
|
).find('NavExpandableGroup');
|
||||||
|
|
||||||
|
expect(component.find('NavItem').length).toEqual(3);
|
||||||
|
const link = component.find('NavItem').at(0);
|
||||||
|
expect(component.find('NavItem').at(0).prop('isActive')).toBeTruthy();
|
||||||
|
expect(link.find('Link').prop('to')).toBe('/foo');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,24 +1,17 @@
|
|||||||
import { Component } from 'react';
|
import { useEffect } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
class AppendBody extends Component {
|
function AppendBody({ children }) {
|
||||||
constructor(props) {
|
const el = document.createElement('div');
|
||||||
super(props);
|
|
||||||
this.el = document.createElement('div');
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
useEffect(() => {
|
||||||
document.body.appendChild(this.el);
|
document.body.appendChild(el);
|
||||||
}
|
return () => {
|
||||||
|
document.body.removeChild(el);
|
||||||
|
};
|
||||||
|
}, [el]);
|
||||||
|
|
||||||
componentWillUnmount() {
|
return ReactDOM.createPortal(children, el);
|
||||||
document.body.removeChild(this.el);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { children } = this.props;
|
|
||||||
return ReactDOM.createPortal(children, this.el);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AppendBody;
|
export default AppendBody;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Toolbar, ToolbarContent } from '@patternfly/react-core';
|
import { Toolbar, ToolbarContent } from '@patternfly/react-core';
|
||||||
|
|
||||||
@@ -24,122 +24,105 @@ const EmptyStateControlsWrapper = styled.div`
|
|||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
class ListHeader extends React.Component {
|
function ListHeader(props) {
|
||||||
constructor(props) {
|
const { search, pathname } = useLocation();
|
||||||
super(props);
|
const history = useHistory();
|
||||||
|
const {
|
||||||
|
emptyStateControls,
|
||||||
|
itemCount,
|
||||||
|
pagination,
|
||||||
|
qsConfig,
|
||||||
|
relatedSearchableKeys,
|
||||||
|
renderToolbar,
|
||||||
|
searchColumns,
|
||||||
|
searchableKeys,
|
||||||
|
sortColumns,
|
||||||
|
} = props;
|
||||||
|
|
||||||
this.handleSearch = this.handleSearch.bind(this);
|
const handleSearch = (key, value) => {
|
||||||
this.handleReplaceSearch = this.handleReplaceSearch.bind(this);
|
const params = parseQueryString(qsConfig, search);
|
||||||
this.handleSort = this.handleSort.bind(this);
|
const qs = updateQueryString(qsConfig, search, {
|
||||||
this.handleRemove = this.handleRemove.bind(this);
|
|
||||||
this.handleRemoveAll = this.handleRemoveAll.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSearch(key, value) {
|
|
||||||
const { location, qsConfig } = this.props;
|
|
||||||
const params = parseQueryString(qsConfig, location.search);
|
|
||||||
const qs = updateQueryString(qsConfig, location.search, {
|
|
||||||
...mergeParams(params, { [key]: value }),
|
...mergeParams(params, { [key]: value }),
|
||||||
page: 1,
|
page: 1,
|
||||||
});
|
});
|
||||||
this.pushHistoryState(qs);
|
pushHistoryState(qs);
|
||||||
}
|
};
|
||||||
|
|
||||||
handleReplaceSearch(key, value) {
|
const handleReplaceSearch = (key, value) => {
|
||||||
const { location, qsConfig } = this.props;
|
const qs = updateQueryString(qsConfig, search, {
|
||||||
const qs = updateQueryString(qsConfig, location.search, {
|
|
||||||
[key]: value,
|
[key]: value,
|
||||||
});
|
});
|
||||||
this.pushHistoryState(qs);
|
pushHistoryState(qs);
|
||||||
}
|
};
|
||||||
|
|
||||||
handleRemove(key, value) {
|
const handleRemove = (key, value) => {
|
||||||
const { location, qsConfig } = this.props;
|
const oldParams = parseQueryString(qsConfig, search);
|
||||||
const oldParams = parseQueryString(qsConfig, location.search);
|
|
||||||
const updatedParams = removeParams(qsConfig, oldParams, {
|
const updatedParams = removeParams(qsConfig, oldParams, {
|
||||||
[key]: value,
|
[key]: value,
|
||||||
});
|
});
|
||||||
const qs = updateQueryString(qsConfig, location.search, updatedParams);
|
const qs = updateQueryString(qsConfig, search, updatedParams);
|
||||||
this.pushHistoryState(qs);
|
pushHistoryState(qs);
|
||||||
}
|
};
|
||||||
|
|
||||||
handleRemoveAll() {
|
const handleRemoveAll = () => {
|
||||||
const { location, qsConfig } = this.props;
|
const oldParams = parseQueryString(qsConfig, search);
|
||||||
const oldParams = parseQueryString(qsConfig, location.search);
|
|
||||||
Object.keys(oldParams).forEach((key) => {
|
Object.keys(oldParams).forEach((key) => {
|
||||||
oldParams[key] = null;
|
oldParams[key] = null;
|
||||||
});
|
});
|
||||||
delete oldParams.page_size;
|
delete oldParams.page_size;
|
||||||
delete oldParams.order_by;
|
delete oldParams.order_by;
|
||||||
const qs = updateQueryString(qsConfig, location.search, oldParams);
|
const qs = updateQueryString(qsConfig, search, oldParams);
|
||||||
this.pushHistoryState(qs);
|
pushHistoryState(qs);
|
||||||
}
|
};
|
||||||
|
|
||||||
handleSort(key, order) {
|
const handleSort = (key, order) => {
|
||||||
const { location, qsConfig } = this.props;
|
const qs = updateQueryString(qsConfig, search, {
|
||||||
const qs = updateQueryString(qsConfig, location.search, {
|
|
||||||
order_by: order === 'ascending' ? key : `-${key}`,
|
order_by: order === 'ascending' ? key : `-${key}`,
|
||||||
page: null,
|
page: null,
|
||||||
});
|
});
|
||||||
this.pushHistoryState(qs);
|
pushHistoryState(qs);
|
||||||
}
|
};
|
||||||
|
|
||||||
pushHistoryState(queryString) {
|
const pushHistoryState = (queryString) => {
|
||||||
const { history } = this.props;
|
|
||||||
const { pathname } = history.location;
|
|
||||||
history.push(queryString ? `${pathname}?${queryString}` : pathname);
|
history.push(queryString ? `${pathname}?${queryString}` : pathname);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
const params = parseQueryString(qsConfig, search);
|
||||||
const {
|
const isEmpty = itemCount === 0 && Object.keys(params).length === 0;
|
||||||
emptyStateControls,
|
return (
|
||||||
itemCount,
|
<>
|
||||||
searchColumns,
|
{isEmpty ? (
|
||||||
searchableKeys,
|
<Toolbar
|
||||||
relatedSearchableKeys,
|
id={`${qsConfig.namespace}-list-toolbar`}
|
||||||
sortColumns,
|
clearAllFilters={handleRemoveAll}
|
||||||
renderToolbar,
|
collapseListedFiltersBreakpoint="lg"
|
||||||
qsConfig,
|
>
|
||||||
location,
|
<ToolbarContent>
|
||||||
pagination,
|
<EmptyStateControlsWrapper>
|
||||||
} = this.props;
|
{emptyStateControls}
|
||||||
const params = parseQueryString(qsConfig, location.search);
|
</EmptyStateControlsWrapper>
|
||||||
const isEmpty = itemCount === 0 && Object.keys(params).length === 0;
|
</ToolbarContent>
|
||||||
return (
|
</Toolbar>
|
||||||
<>
|
) : (
|
||||||
{isEmpty ? (
|
<>
|
||||||
<Toolbar
|
{renderToolbar({
|
||||||
id={`${qsConfig.namespace}-list-toolbar`}
|
itemCount,
|
||||||
clearAllFilters={this.handleRemoveAll}
|
searchColumns,
|
||||||
collapseListedFiltersBreakpoint="lg"
|
sortColumns,
|
||||||
>
|
searchableKeys,
|
||||||
<ToolbarContent>
|
relatedSearchableKeys,
|
||||||
<EmptyStateControlsWrapper>
|
onSearch: handleSearch,
|
||||||
{emptyStateControls}
|
onReplaceSearch: handleReplaceSearch,
|
||||||
</EmptyStateControlsWrapper>
|
onSort: handleSort,
|
||||||
</ToolbarContent>
|
onRemove: handleRemove,
|
||||||
</Toolbar>
|
clearAllFilters: handleRemoveAll,
|
||||||
) : (
|
qsConfig,
|
||||||
<>
|
pagination,
|
||||||
{renderToolbar({
|
})}
|
||||||
itemCount,
|
</>
|
||||||
searchColumns,
|
)}
|
||||||
sortColumns,
|
</>
|
||||||
searchableKeys,
|
);
|
||||||
relatedSearchableKeys,
|
|
||||||
onSearch: this.handleSearch,
|
|
||||||
onReplaceSearch: this.handleReplaceSearch,
|
|
||||||
onSort: this.handleSort,
|
|
||||||
onRemove: this.handleRemove,
|
|
||||||
clearAllFilters: this.handleRemoveAll,
|
|
||||||
qsConfig,
|
|
||||||
pagination,
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ListHeader.propTypes = {
|
ListHeader.propTypes = {
|
||||||
@@ -159,4 +142,4 @@ ListHeader.defaultProps = {
|
|||||||
relatedSearchableKeys: [],
|
relatedSearchableKeys: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withRouter(ListHeader);
|
export default ListHeader;
|
||||||
|
|||||||
@@ -7,11 +7,15 @@ describe('ListHeader', () => {
|
|||||||
const qsConfig = {
|
const qsConfig = {
|
||||||
namespace: 'item',
|
namespace: 'item',
|
||||||
defaultParams: { page: 1, page_size: 5, order_by: 'foo' },
|
defaultParams: { page: 1, page_size: 5, order_by: 'foo' },
|
||||||
integerFields: [],
|
integerFields: ['id', 'page', 'page_size'],
|
||||||
|
dateFields: ['modified', 'created'],
|
||||||
};
|
};
|
||||||
const renderToolbarFn = jest.fn();
|
const renderToolbarFn = jest.fn();
|
||||||
|
|
||||||
test('initially renders without crashing', () => {
|
test('initially renders without crashing', () => {
|
||||||
|
const history = createMemoryHistory({
|
||||||
|
initialEntries: ['/organizations/1/teams'],
|
||||||
|
});
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<ListHeader
|
<ListHeader
|
||||||
itemCount={50}
|
itemCount={50}
|
||||||
@@ -21,7 +25,8 @@ describe('ListHeader', () => {
|
|||||||
]}
|
]}
|
||||||
sortColumns={[{ name: 'foo', key: 'foo' }]}
|
sortColumns={[{ name: 'foo', key: 'foo' }]}
|
||||||
renderToolbar={renderToolbarFn}
|
renderToolbar={renderToolbarFn}
|
||||||
/>
|
/>,
|
||||||
|
{ context: { router: { history } } }
|
||||||
);
|
);
|
||||||
expect(wrapper.length).toBe(1);
|
expect(wrapper.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -137,14 +137,14 @@ function PaginatedTable({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ListHeader
|
<ListHeader
|
||||||
itemCount={itemCount}
|
|
||||||
renderToolbar={renderToolbar}
|
|
||||||
emptyStateControls={emptyStateControls}
|
emptyStateControls={emptyStateControls}
|
||||||
|
itemCount={itemCount}
|
||||||
|
pagination={ToolbarPagination}
|
||||||
|
qsConfig={qsConfig}
|
||||||
|
relatedSearchableKeys={toolbarRelatedSearchableKeys}
|
||||||
|
renderToolbar={renderToolbar}
|
||||||
searchColumns={searchColumns}
|
searchColumns={searchColumns}
|
||||||
searchableKeys={toolbarSearchableKeys}
|
searchableKeys={toolbarSearchableKeys}
|
||||||
relatedSearchableKeys={toolbarRelatedSearchableKeys}
|
|
||||||
qsConfig={qsConfig}
|
|
||||||
pagination={ToolbarPagination}
|
|
||||||
/>
|
/>
|
||||||
{Content}
|
{Content}
|
||||||
{items.length ? (
|
{items.length ? (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Chip, Split as PFSplit, SplitItem } from '@patternfly/react-core';
|
import { Chip, Split as PFSplit, SplitItem } from '@patternfly/react-core';
|
||||||
|
|
||||||
@@ -16,42 +16,34 @@ const SplitLabelItem = styled(SplitItem)`
|
|||||||
word-break: initial;
|
word-break: initial;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
class SelectedList extends Component {
|
function SelectedList(props) {
|
||||||
render() {
|
const { label, selected, onRemove, displayKey, isReadOnly, renderItemChip } =
|
||||||
const {
|
props;
|
||||||
label,
|
|
||||||
selected,
|
|
||||||
onRemove,
|
|
||||||
displayKey,
|
|
||||||
isReadOnly,
|
|
||||||
renderItemChip,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const renderChip =
|
const renderChip =
|
||||||
renderItemChip ||
|
renderItemChip ||
|
||||||
(({ item, removeItem }) => (
|
(({ item, removeItem }) => (
|
||||||
<Chip key={item.id} onClick={removeItem} isReadOnly={isReadOnly}>
|
<Chip key={item.id} onClick={removeItem} isReadOnly={isReadOnly}>
|
||||||
{item[displayKey]}
|
{item[displayKey]}
|
||||||
</Chip>
|
</Chip>
|
||||||
));
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Split>
|
<Split>
|
||||||
<SplitLabelItem>{label}</SplitLabelItem>
|
<SplitLabelItem>{label}</SplitLabelItem>
|
||||||
<SplitItem>
|
<SplitItem>
|
||||||
<ChipGroup numChips={5} totalChips={selected.length}>
|
<ChipGroup numChips={5} totalChips={selected.length}>
|
||||||
{selected.map((item) =>
|
{selected.map((item) =>
|
||||||
renderChip({
|
renderChip({
|
||||||
item,
|
item,
|
||||||
removeItem: () => onRemove(item),
|
removeItem: () => onRemove(item),
|
||||||
canDelete: !isReadOnly,
|
canDelete: !isReadOnly,
|
||||||
})
|
})
|
||||||
)}
|
)}
|
||||||
</ChipGroup>
|
</ChipGroup>
|
||||||
</SplitItem>
|
</SplitItem>
|
||||||
</Split>
|
</Split>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectedList.propTypes = {
|
SelectedList.propTypes = {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { PageSection, Card } from '@patternfly/react-core';
|
import { PageSection, Card } from '@patternfly/react-core';
|
||||||
|
|
||||||
import { TeamsAPI } from 'api';
|
import { TeamsAPI } from 'api';
|
||||||
@@ -7,54 +7,48 @@ import { Config } from 'contexts/Config';
|
|||||||
import { CardBody } from 'components/Card';
|
import { CardBody } from 'components/Card';
|
||||||
import TeamForm from '../shared/TeamForm';
|
import TeamForm from '../shared/TeamForm';
|
||||||
|
|
||||||
class TeamAdd extends React.Component {
|
function TeamAdd() {
|
||||||
constructor(props) {
|
const [submitError, setSubmitError] = useState(null);
|
||||||
super(props);
|
const history = useHistory();
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
|
||||||
this.handleCancel = this.handleCancel.bind(this);
|
|
||||||
this.state = { error: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleSubmit(values) {
|
const handleSubmit = async (values) => {
|
||||||
const { history } = this.props;
|
|
||||||
try {
|
try {
|
||||||
const valuesToSend = { ...values };
|
const {
|
||||||
valuesToSend.organization = valuesToSend.organization.id;
|
name,
|
||||||
|
description,
|
||||||
|
organization: { id },
|
||||||
|
} = values;
|
||||||
|
const valuesToSend = { name, description, organization: id };
|
||||||
const { data: response } = await TeamsAPI.create(valuesToSend);
|
const { data: response } = await TeamsAPI.create(valuesToSend);
|
||||||
history.push(`/teams/${response.id}`);
|
history.push(`/teams/${response.id}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.setState({ error });
|
setSubmitError(error);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
handleCancel() {
|
const handleCancel = () => {
|
||||||
const { history } = this.props;
|
|
||||||
history.push('/teams');
|
history.push('/teams');
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
const { error } = this.state;
|
<PageSection>
|
||||||
|
<Card>
|
||||||
return (
|
<CardBody>
|
||||||
<PageSection>
|
<Config>
|
||||||
<Card>
|
{({ me }) => (
|
||||||
<CardBody>
|
<TeamForm
|
||||||
<Config>
|
handleSubmit={handleSubmit}
|
||||||
{({ me }) => (
|
handleCancel={handleCancel}
|
||||||
<TeamForm
|
me={me || {}}
|
||||||
handleSubmit={this.handleSubmit}
|
submitError={submitError}
|
||||||
handleCancel={this.handleCancel}
|
/>
|
||||||
me={me || {}}
|
)}
|
||||||
submitError={error}
|
</Config>
|
||||||
/>
|
</CardBody>
|
||||||
)}
|
</Card>
|
||||||
</Config>
|
</PageSection>
|
||||||
</CardBody>
|
);
|
||||||
</Card>
|
|
||||||
</PageSection>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { TeamAdd as _TeamAdd };
|
export { TeamAdd as _TeamAdd };
|
||||||
export default withRouter(TeamAdd);
|
export default TeamAdd;
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ jest.mock('../../../api');
|
|||||||
|
|
||||||
describe('<TeamAdd />', () => {
|
describe('<TeamAdd />', () => {
|
||||||
test('handleSubmit should post to api', async () => {
|
test('handleSubmit should post to api', async () => {
|
||||||
TeamsAPI.create.mockResolvedValueOnce({ data: {} });
|
const history = createMemoryHistory({});
|
||||||
const wrapper = mountWithContexts(<TeamAdd />);
|
|
||||||
const updatedTeamData = {
|
const updatedTeamData = {
|
||||||
name: 'new name',
|
name: 'new name',
|
||||||
description: 'new description',
|
description: 'new description',
|
||||||
@@ -22,6 +21,10 @@ describe('<TeamAdd />', () => {
|
|||||||
name: 'Default',
|
name: 'Default',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
TeamsAPI.create.mockResolvedValueOnce({ data: {} });
|
||||||
|
const wrapper = mountWithContexts(<TeamAdd />, {
|
||||||
|
context: { router: { history } },
|
||||||
|
});
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper.find('TeamForm').invoke('handleSubmit')(updatedTeamData);
|
wrapper.find('TeamForm').invoke('handleSubmit')(updatedTeamData);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ export function updateQueryString(config, queryString, newParams) {
|
|||||||
return encodeQueryString(allParams);
|
return encodeQueryString(allParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseFullQueryString(queryString) {
|
function parseFullQueryString(queryString = '') {
|
||||||
const allParams = {};
|
const allParams = {};
|
||||||
queryString
|
queryString
|
||||||
.replace(/^\?/, '')
|
.replace(/^\?/, '')
|
||||||
|
|||||||
Reference in New Issue
Block a user