diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c4031e2572..22f0ad8ec6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -15,6 +15,7 @@ Have questions about this document or anything not covered here? Feel free to re
* [Working with React](#working-with-react)
* [Class constructors vs Class properties](#class-constructors-vs-class-properties)
* [Binding](#binding)
+ * [Typechecking with PropTypes](#typechecking-with-proptypes)
* [Testing](#testing)
* [Jest](#jest)
* [Enzyme](#enzyme)
@@ -90,35 +91,25 @@ It is good practice to bind our class methods within our class constructor metho
2. [Performance advantages](https://stackoverflow.com/a/44844916).
3. Ease of [testing](https://github.com/airbnb/enzyme/issues/365).
-### Component Lifecycle
+### Typechecking with PropTypes
+Shared components should have their prop values typechecked. This will help catch bugs when components get refactored/renamed.
+```javascript
+About.propTypes = {
+ ansible_version: PropTypes.string,
+ isOpen: PropTypes.bool,
+ onClose: PropTypes.func.isRequired,
+ version: PropTypes.string,
+};
-A React Component has various [lifecylce methods](http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/). Understanding the basic lifecycle of a Component will help you determine which method to utilize for your specific need. Below are some general guidelines:
+About.defaultProps = {
+ ansible_version: null,
+ isOpen: false,
+ version: null,
+};
+```
-BAD:
- Use `render` method to make asynchronous calls.
- ```javascript
- render () {
- const { data } = await api.get(API_CONFIG);
- return(
`Hello, ${data.usename}!`
);
- }
- ```
-GOOD:
- Use `componentDidMount()` method to make asynchronous calls to retrieve data a Componenet may need.
- ```javascript
- async componentDidMount () {
- try {
- const { data } = await api.get(API_CONFIG);
- this.setState({ data });
- } catch (error) {
- this.setState({ error });
- }
- }
- render() {
- return(`Hello, ${this.state.data.usename}!`
)
- }
- ```
## Testing
All code, new or otherwise, should have at least 80% test coverage.
### Jest
diff --git a/__tests__/components/About.test.jsx b/__tests__/components/About.test.jsx
index c6d322a55d..86a4dab4c1 100644
--- a/__tests__/components/About.test.jsx
+++ b/__tests__/components/About.test.jsx
@@ -6,11 +6,11 @@ import About from '../../src/components/About';
describe(' ', () => {
let aboutWrapper;
let closeButton;
-
+ const onClose = jest.fn();
test('initially renders without crashing', () => {
aboutWrapper = mount(
-
+
);
expect(aboutWrapper.length).toBe(1);
@@ -18,7 +18,6 @@ describe(' ', () => {
});
test('close button calls onClose handler', () => {
- const onClose = jest.fn();
aboutWrapper = mount(
diff --git a/__tests__/components/AnsibleSelect.test.jsx b/__tests__/components/AnsibleSelect.test.jsx
index 8f54b0d522..c80207cc9d 100644
--- a/__tests__/components/AnsibleSelect.test.jsx
+++ b/__tests__/components/AnsibleSelect.test.jsx
@@ -33,19 +33,6 @@ describe(' ', () => {
expect(spy).toHaveBeenCalled();
});
- test('content not rendered when data property is falsey', () => {
- const wrapper = mount(
- { }}
- label={label}
- data={null}
- />
- );
- expect(wrapper.find('FormGroup')).toHaveLength(0);
- expect(wrapper.find('Select')).toHaveLength(0);
- });
test('Returns correct select options if defaultSelected props is passed', () => {
const wrapper = mount(
', () => {
test('initially renders succesfully', () => {
mount(
@@ -85,7 +85,7 @@ describe(' ', () => {
});
test('calls "toggleSelected" when remove icon is clicked', () => {
const spy = jest.spyOn(Lookup.prototype, 'toggleSelected');
- mockData = [{ name: 'foo', id: 1, isChecked: false }, { name: 'bar', id: 2, isChecked: true }];
+ mockData = [{ name: 'foo', id: 1 }, { name: 'bar', id: 2 }];
const wrapper = mount(
', () => {
});
test('"wrapTags" method properly handles data', () => {
const spy = jest.spyOn(Lookup.prototype, 'wrapTags');
- mockData = [{ name: 'foo', id: 0, isChecked: false }, { name: 'bar', id: 1, isChecked: false }];
+ mockData = [{ name: 'foo', id: 0 }, { name: 'bar', id: 1 }];
const wrapper = mount(
{ }}
- data={mockData}
+ value={mockData}
selected={[]}
+ getItems={() => { }}
/>
);
expect(spy).toHaveBeenCalled();
const pill = wrapper.find('span.awx-c-tag--pill');
- expect(pill).toHaveLength(0);
+ expect(pill).toHaveLength(2);
});
test('toggleSelected successfully adds/removes row from lookupSelectedItems state', () => {
mockData = [{ name: 'foo', id: 1 }];
@@ -125,8 +126,9 @@ describe(' ', () => {
{ }}
- data={mockData}
+ value={mockData}
selected={[]}
+ getItems={() => { }}
/>
).find('Lookup');
diff --git a/__tests__/components/NotificationList.test.jsx b/__tests__/components/NotificationList.test.jsx
index 893f7a58a0..0b53f3c7e9 100644
--- a/__tests__/components/NotificationList.test.jsx
+++ b/__tests__/components/NotificationList.test.jsx
@@ -112,9 +112,9 @@ describe(' ', () => {
const getNotificationsFn = jest.fn().mockResolvedValue({
data: {
results: [
- { id: 1 },
- { id: 2 },
- { id: 3 }
+ { id: 1, notification_type: 'slack' },
+ { id: 2, notification_type: 'email' },
+ { id: 3, notification_type: 'github' }
]
}
});
diff --git a/__tests__/components/NotificationListItem.test.jsx b/__tests__/components/NotificationListItem.test.jsx
index f162782338..dd823eab50 100644
--- a/__tests__/components/NotificationListItem.test.jsx
+++ b/__tests__/components/NotificationListItem.test.jsx
@@ -6,6 +6,7 @@ import NotificationListItem from '../../src/components/NotificationsList/Notific
describe(' ', () => {
let wrapper;
+ const toggleNotification = jest.fn();
afterEach(() => {
if (wrapper) {
@@ -18,7 +19,12 @@ describe(' ', () => {
wrapper = mount(
-
+
);
@@ -26,7 +32,6 @@ describe(' ', () => {
});
test('handles success click when toggle is on', () => {
- const toggleNotification = jest.fn();
wrapper = mount(
@@ -34,6 +39,8 @@ describe(' ', () => {
itemId={9000}
successTurnedOn
toggleNotification={toggleNotification}
+ detailUrl="/foo"
+ notificationType="slack"
/>
@@ -43,7 +50,6 @@ describe(' ', () => {
});
test('handles success click when toggle is off', () => {
- const toggleNotification = jest.fn();
wrapper = mount(
@@ -51,6 +57,8 @@ describe(' ', () => {
itemId={9000}
successTurnedOn={false}
toggleNotification={toggleNotification}
+ detailUrl="/foo"
+ notificationType="slack"
/>
@@ -60,7 +68,6 @@ describe(' ', () => {
});
test('handles error click when toggle is on', () => {
- const toggleNotification = jest.fn();
wrapper = mount(
@@ -68,6 +75,8 @@ describe(' ', () => {
itemId={9000}
errorTurnedOn
toggleNotification={toggleNotification}
+ detailUrl="/foo"
+ notificationType="slack"
/>
@@ -77,7 +86,6 @@ describe(' ', () => {
});
test('handles error click when toggle is off', () => {
- const toggleNotification = jest.fn();
wrapper = mount(
@@ -85,6 +93,8 @@ describe(' ', () => {
itemId={9000}
errorTurnedOn={false}
toggleNotification={toggleNotification}
+ detailUrl="/foo"
+ notificationType="slack"
/>
diff --git a/__tests__/components/PageHeaderToolbar.test.jsx b/__tests__/components/PageHeaderToolbar.test.jsx
index b37e791c89..49d023e0b7 100644
--- a/__tests__/components/PageHeaderToolbar.test.jsx
+++ b/__tests__/components/PageHeaderToolbar.test.jsx
@@ -8,17 +8,24 @@ import PageHeaderToolbar from '../../src/components/PageHeaderToolbar';
describe('PageHeaderToolbar', () => {
const pageHelpDropdownSelector = 'Dropdown QuestionCircleIcon';
const pageUserDropdownSelector = 'Dropdown UserIcon';
+ const onAboutClick = jest.fn();
+ const onLogoutClick = jest.fn();
test('expected content is rendered on initialization', () => {
- const wrapper = mount( );
+ const wrapper = mount(
+
+
+
+ );
expect(wrapper.find(pageHelpDropdownSelector)).toHaveLength(1);
expect(wrapper.find(pageUserDropdownSelector)).toHaveLength(1);
});
test('dropdowns have expected items and callbacks', () => {
- const onAboutClick = jest.fn();
- const onLogoutClick = jest.fn();
const wrapper = mount(
diff --git a/__tests__/components/Tooltip.test.jsx b/__tests__/components/Tooltip.test.jsx
index 7f09c5eaec..9a78ae5fa0 100644
--- a/__tests__/components/Tooltip.test.jsx
+++ b/__tests__/components/Tooltip.test.jsx
@@ -7,14 +7,24 @@ describe(' ', () => {
let content;
let mouseOverHandler;
let mouseOutHandler;
+ const child = (foo );
+ const message = 'hi';
test('initially renders without crashing', () => {
- elem = mount( );
+ elem = mount(
+
+ {child}
+
+ );
expect(elem.length).toBe(1);
});
test('shows/hides with mouse over and leave', () => {
- elem = mount( );
+ elem = mount(
+
+ {child}
+
+ );
mouseOverHandler = elem.find('.mouseOverHandler');
mouseOutHandler = elem.find('.mouseOutHandler');
expect(elem.state().isDisplayed).toBe(false);
@@ -34,7 +44,11 @@ describe(' ', () => {
});
test('shows/hides with focus and blur', () => {
- elem = mount( );
+ elem = mount(
+
+ {child}
+
+ );
mouseOverHandler = elem.find('.mouseOverHandler');
mouseOutHandler = elem.find('.mouseOutHandler');
expect(elem.state().isDisplayed).toBe(false);
diff --git a/__tests__/pages/Organizations/screens/OrganizationAdd.test.jsx b/__tests__/pages/Organizations/screens/OrganizationAdd.test.jsx
index 6a41d1d1ff..c9ea5414b4 100644
--- a/__tests__/pages/Organizations/screens/OrganizationAdd.test.jsx
+++ b/__tests__/pages/Organizations/screens/OrganizationAdd.test.jsx
@@ -2,6 +2,7 @@ import React from 'react';
import { mount } from 'enzyme';
import { MemoryRouter } from 'react-router-dom';
import { I18nProvider } from '@lingui/react';
+import { ConfigContext } from '../../../../src/context';
import OrganizationAdd from '../../../../src/pages/Organizations/screens/OrganizationAdd';
describe(' ', () => {
@@ -157,4 +158,37 @@ describe(' ', () => {
});
expect(createInstanceGroupsFn).toHaveBeenCalledWith('/api/v2/organizations/1/instance_groups', 1);
});
+
+ test('AnsibleSelect component renders if there are virtual environments', () => {
+ const config = {
+ custom_virtualenvs: ['foo', 'bar'],
+ };
+ const wrapper = mount(
+
+
+
+
+
+
+
+ ).find('OrganizationAdd').find('AnsibleSelect');
+ expect(wrapper.find('Select')).toHaveLength(1);
+ expect(wrapper.find('SelectOption')).toHaveLength(2);
+ });
+
+ test('AnsibleSelect component does not render if there are 0 virtual environments', () => {
+ const config = {
+ custom_virtualenvs: [],
+ };
+ const wrapper = mount(
+
+
+
+
+
+
+
+ ).find('OrganizationAdd').find('AnsibleSelect');
+ expect(wrapper.find('Select')).toHaveLength(0);
+ });
});
diff --git a/src/components/About.jsx b/src/components/About.jsx
index 0e8a07f09e..a0f0750e85 100644
--- a/src/components/About.jsx
+++ b/src/components/About.jsx
@@ -1,4 +1,5 @@
import React from 'react';
+import PropTypes from 'prop-types';
import { I18n } from '@lingui/react';
import { Trans, t } from '@lingui/macro';
import {
@@ -86,4 +87,17 @@ class About extends React.Component {
}
}
+About.propTypes = {
+ ansible_version: PropTypes.string,
+ isOpen: PropTypes.bool,
+ onClose: PropTypes.func.isRequired,
+ version: PropTypes.string,
+};
+
+About.defaultProps = {
+ ansible_version: null,
+ isOpen: false,
+ version: null,
+};
+
export default About;
diff --git a/src/components/AnsibleSelect/AnsibleSelect.jsx b/src/components/AnsibleSelect/AnsibleSelect.jsx
index 28563eefc9..807bd9b73f 100644
--- a/src/components/AnsibleSelect/AnsibleSelect.jsx
+++ b/src/components/AnsibleSelect/AnsibleSelect.jsx
@@ -1,4 +1,5 @@
import React from 'react';
+import PropTypes from 'prop-types';
import {
Select,
@@ -11,19 +12,6 @@ class AnsibleSelect extends React.Component {
this.onSelectChange = this.onSelectChange.bind(this);
}
- state = {
- count: 1,
- }
-
- static getDerivedStateFromProps (nexProps) {
- if (nexProps.data) {
- return {
- count: nexProps.data.length,
- };
- }
- return null;
- }
-
onSelectChange (val, event) {
const { onChange, name } = this.props;
event.target.name = name;
@@ -31,21 +19,30 @@ class AnsibleSelect extends React.Component {
}
render () {
- const { count } = this.state;
- const { label = '', value, data, defaultSelected } = this.props;
- let elem;
- if (count > 1) {
- elem = (
-
- {data.map((datum) => (datum === defaultSelected
- ? ( ) : ( )))
- }
-
- );
- } else {
- elem = null;
- }
- return elem;
+ const { label, value, data, defaultSelected } = this.props;
+ return (
+
+ {data.map((datum) => (datum === defaultSelected
+ ? ( ) : ( )))
+ }
+
+ );
}
}
+
+AnsibleSelect.defaultProps = {
+ data: [],
+ label: 'Ansible Select',
+ defaultSelected: null,
+};
+
+AnsibleSelect.propTypes = {
+ data: PropTypes.arrayOf(PropTypes.string),
+ defaultSelected: PropTypes.string,
+ label: PropTypes.string,
+ name: PropTypes.string.isRequired,
+ onChange: PropTypes.func.isRequired,
+ value: PropTypes.string.isRequired,
+};
+
export default AnsibleSelect;
diff --git a/src/components/BasicChip/BasicChip.jsx b/src/components/BasicChip/BasicChip.jsx
index a1c2d55950..753922bbb0 100644
--- a/src/components/BasicChip/BasicChip.jsx
+++ b/src/components/BasicChip/BasicChip.jsx
@@ -1,4 +1,6 @@
import React from 'react';
+import PropTypes from 'prop-types';
+
import { Chip } from '@patternfly/react-core';
import './basicChip.scss';
@@ -10,4 +12,8 @@ const BasicChip = ({ text }) => (
);
+BasicChip.propTypes = {
+ text: PropTypes.string.isRequired,
+};
+
export default BasicChip;
diff --git a/src/components/Breadcrumbs/Breadcrumbs.jsx b/src/components/Breadcrumbs/Breadcrumbs.jsx
index e63525a335..9722b79b80 100644
--- a/src/components/Breadcrumbs/Breadcrumbs.jsx
+++ b/src/components/Breadcrumbs/Breadcrumbs.jsx
@@ -1,4 +1,5 @@
import React, { Fragment } from 'react';
+import PropTypes from 'prop-types';
import {
PageSection,
PageSectionVariants,
@@ -68,4 +69,12 @@ const Crumb = ({ breadcrumbConfig, match }) => {
);
};
+Breadcrumbs.propTypes = {
+ breadcrumbConfig: PropTypes.objectOf(PropTypes.string).isRequired,
+};
+
+Crumb.propTypes = {
+ breadcrumbConfig: PropTypes.objectOf(PropTypes.string).isRequired,
+};
+
export default withRouter(Breadcrumbs);
diff --git a/src/components/DataListToolbar/DataListToolbar.jsx b/src/components/DataListToolbar/DataListToolbar.jsx
index e296203da6..b6a468c550 100644
--- a/src/components/DataListToolbar/DataListToolbar.jsx
+++ b/src/components/DataListToolbar/DataListToolbar.jsx
@@ -1,4 +1,5 @@
import React from 'react';
+import PropTypes from 'prop-types';
import { I18n } from '@lingui/react';
import { t } from '@lingui/macro';
import {
@@ -36,7 +37,6 @@ class DataListToolbar extends React.Component {
super(props);
const { sortedColumnKey } = this.props;
-
this.state = {
isSearchDropdownOpen: false,
isSortDropdownOpen: false,
@@ -282,4 +282,29 @@ class DataListToolbar extends React.Component {
}
}
+DataListToolbar.propTypes = {
+ addUrl: PropTypes.string,
+ columns: PropTypes.arrayOf(PropTypes.object).isRequired,
+ isAllSelected: PropTypes.bool,
+ onSearch: PropTypes.func,
+ onSelectAll: PropTypes.func,
+ onSort: PropTypes.func,
+ showDelete: PropTypes.bool,
+ showSelectAll: PropTypes.bool,
+ sortOrder: PropTypes.string,
+ sortedColumnKey: PropTypes.string,
+};
+
+DataListToolbar.defaultProps = {
+ addUrl: null,
+ onSearch: null,
+ onSelectAll: null,
+ onSort: null,
+ showDelete: false,
+ showSelectAll: false,
+ sortOrder: 'ascending',
+ sortedColumnKey: 'name',
+ isAllSelected: false,
+};
+
export default DataListToolbar;
diff --git a/src/components/FormActionGroup.jsx b/src/components/FormActionGroup.jsx
index 3da293fe62..451987e847 100644
--- a/src/components/FormActionGroup.jsx
+++ b/src/components/FormActionGroup.jsx
@@ -1,4 +1,5 @@
import React from 'react';
+import PropTypes from 'prop-types';
import { I18n } from '@lingui/react';
import { t } from '@lingui/macro';
@@ -21,7 +22,7 @@ const buttonGroupStyle = {
marginRight: '20px'
};
-export default ({ onSubmit, submitDisabled, onCancel }) => (
+const FormActionGroup = ({ onSubmit, submitDisabled, onCancel }) => (
{({ i18n }) => (
@@ -37,3 +38,15 @@ export default ({ onSubmit, submitDisabled, onCancel }) => (
)}
);
+
+FormActionGroup.propTypes = {
+ onCancel: PropTypes.func.isRequired,
+ onSubmit: PropTypes.func.isRequired,
+ submitDisabled: PropTypes.bool,
+};
+
+FormActionGroup.defaultProps = {
+ submitDisabled: false,
+};
+
+export default FormActionGroup;
diff --git a/src/components/ListItem/CheckboxListItem.jsx b/src/components/ListItem/CheckboxListItem.jsx
index efd8c78912..238891179b 100644
--- a/src/components/ListItem/CheckboxListItem.jsx
+++ b/src/components/ListItem/CheckboxListItem.jsx
@@ -1,11 +1,12 @@
import React from 'react';
+import PropTypes from 'prop-types';
import { I18n } from '@lingui/react';
import { t } from '@lingui/macro';
import {
Checkbox,
} from '@patternfly/react-core';
-export default ({
+const CheckboxListItem = ({
itemId,
name,
isSelected,
@@ -32,3 +33,12 @@ export default ({
);
+
+CheckboxListItem.propTypes = {
+ itemId: PropTypes.number.isRequired,
+ name: PropTypes.string.isRequired,
+ isSelected: PropTypes.bool.isRequired,
+ onSelect: PropTypes.func.isRequired,
+};
+
+export default CheckboxListItem;
diff --git a/src/components/Lookup/Lookup.jsx b/src/components/Lookup/Lookup.jsx
index 729325192f..0dbfc59813 100644
--- a/src/components/Lookup/Lookup.jsx
+++ b/src/components/Lookup/Lookup.jsx
@@ -1,5 +1,5 @@
import React, { Fragment } from 'react';
-
+import PropTypes from 'prop-types';
import { SearchIcon, CubesIcon } from '@patternfly/react-icons';
import {
Modal,
@@ -125,7 +125,7 @@ class Lookup extends React.Component {
render () {
const { isModalOpen, lookupSelectedItems, error, results, count, page, page_size } = this.state;
- const { lookupHeader = 'items', value } = this.props;
+ const { lookupHeader, value } = this.props;
return (
@@ -174,6 +174,7 @@ class Lookup extends React.Component {
pageCount={Math.ceil(count / page_size)}
page_size={page_size}
onSetPage={this.onSetPage}
+ pageSizeOptions={null}
style={paginationStyling}
/>
@@ -194,4 +195,18 @@ class Lookup extends React.Component {
);
}
}
+
+Lookup.propTypes = {
+ getItems: PropTypes.func.isRequired,
+ lookupHeader: PropTypes.string,
+ name: PropTypes.string,
+ onLookupSave: PropTypes.func.isRequired,
+ value: PropTypes.arrayOf(PropTypes.object).isRequired,
+};
+
+Lookup.defaultProps = {
+ lookupHeader: 'items',
+ name: null,
+};
+
export default Lookup;
diff --git a/src/components/NavExpandableGroup.jsx b/src/components/NavExpandableGroup.jsx
index 8a5562fcfa..8901d069ee 100644
--- a/src/components/NavExpandableGroup.jsx
+++ b/src/components/NavExpandableGroup.jsx
@@ -1,4 +1,5 @@
import React, { Component } from 'react';
+import PropTypes from 'prop-types';
import {
withRouter
} from 'react-router-dom';
@@ -55,4 +56,10 @@ class NavExpandableGroup extends Component {
}
}
+NavExpandableGroup.propTypes = {
+ groupId: PropTypes.string.isRequired,
+ groupTitle: PropTypes.string.isRequired,
+ routes: PropTypes.arrayOf(PropTypes.object).isRequired,
+};
+
export default withRouter(NavExpandableGroup);
diff --git a/src/components/NotificationsList/NotificationListItem.jsx b/src/components/NotificationsList/NotificationListItem.jsx
index 9535a4c1e4..b4faa2563f 100644
--- a/src/components/NotificationsList/NotificationListItem.jsx
+++ b/src/components/NotificationsList/NotificationListItem.jsx
@@ -1,4 +1,5 @@
import React from 'react';
+import PropTypes from 'prop-types';
import { I18n } from '@lingui/react';
import { t } from '@lingui/macro';
import {
@@ -20,7 +21,6 @@ class NotificationListItem extends React.Component {
errorTurnedOn,
toggleNotification
} = this.props;
-
const capText = {
textTransform: 'capitalize'
};
@@ -69,5 +69,21 @@ class NotificationListItem extends React.Component {
}
}
+NotificationListItem.propTypes = {
+ detailUrl: PropTypes.string.isRequired,
+ errorTurnedOn: PropTypes.bool,
+ itemId: PropTypes.number.isRequired,
+ name: PropTypes.string,
+ notificationType: PropTypes.string.isRequired,
+ successTurnedOn: PropTypes.bool,
+ toggleNotification: PropTypes.func.isRequired,
+};
+
+NotificationListItem.defaultProps = {
+ errorTurnedOn: false,
+ name: null,
+ successTurnedOn: false,
+};
+
export default NotificationListItem;
diff --git a/src/components/NotificationsList/Notifications.list.jsx b/src/components/NotificationsList/Notifications.list.jsx
index 7aca6b0db8..38f37044ce 100644
--- a/src/components/NotificationsList/Notifications.list.jsx
+++ b/src/components/NotificationsList/Notifications.list.jsx
@@ -2,6 +2,7 @@ import React, {
Component,
Fragment
} from 'react';
+import PropTypes from 'prop-types';
import { Title, EmptyState, EmptyStateIcon, EmptyStateBody } from '@patternfly/react-core';
import { CubesIcon } from '@patternfly/react-icons';
import { I18n, i18nMark } from '@lingui/react';
@@ -29,8 +30,6 @@ class Notifications extends Component {
order_by: 'name',
};
- pageSizeOptions = [5, 10, 25, 50];
-
constructor (props) {
super(props);
@@ -55,7 +54,6 @@ class Notifications extends Component {
this.onSort = this.onSort.bind(this);
this.onSetPage = this.onSetPage.bind(this);
this.onSelectAll = this.onSelectAll.bind(this);
- this.onSelect = this.onSelect.bind(this);
this.toggleNotification = this.toggleNotification.bind(this);
this.updateUrl = this.updateUrl.bind(this);
this.postToError = this.postToError.bind(this);
@@ -114,18 +112,6 @@ class Notifications extends Component {
this.setState({ selected });
};
- onSelect = id => {
- const { selected } = this.state;
-
- const isSelected = selected.includes(id);
-
- if (isSelected) {
- this.setState({ selected: selected.filter(s => s !== id) });
- } else {
- this.setState({ selected: selected.concat(id) });
- }
- };
-
toggleNotification = (id, isCurrentlyOn, status) => {
if (status === 'success') {
this.postToSuccess(id, isCurrentlyOn);
@@ -286,7 +272,6 @@ class Notifications extends Component {
successTemplateIds,
errorTemplateIds
} = this.state;
-
return (
{noInitialResults && (
@@ -326,8 +311,6 @@ class Notifications extends Component {
name={o.name}
notificationType={o.notification_type}
detailUrl={`/notifications/${o.id}`}
- isSelected={selected.includes(o.id)}
- onSelect={() => this.onSelect(o.id)}
toggleNotification={this.toggleNotification}
errorTurnedOn={errorTemplateIds.includes(o.id)}
successTurnedOn={successTemplateIds.includes(o.id)}
@@ -341,7 +324,6 @@ class Notifications extends Component {
page={page}
pageCount={pageCount}
page_size={page_size}
- pageSizeOptions={this.pageSizeOptions}
onSetPage={this.onSetPage}
/>
@@ -353,4 +335,12 @@ class Notifications extends Component {
}
}
+Notifications.propType = {
+ getError: PropTypes.func.isRequired,
+ getNotifications: PropTypes.func.isRequired,
+ getSuccess: PropTypes.func.isRequired,
+ postError: PropTypes.func.isRequired,
+ postSuccess: PropTypes.func.isRequired,
+};
+
export default Notifications;
diff --git a/src/components/PageHeaderToolbar.jsx b/src/components/PageHeaderToolbar.jsx
index e6d62ad366..8e1ee1078f 100644
--- a/src/components/PageHeaderToolbar.jsx
+++ b/src/components/PageHeaderToolbar.jsx
@@ -1,4 +1,5 @@
import React, { Component } from 'react';
+import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import { t } from '@lingui/macro';
@@ -126,4 +127,14 @@ class PageHeaderToolbar extends Component {
}
}
+PageHeaderToolbar.propTypes = {
+ isAboutDisabled: PropTypes.bool,
+ onAboutClick: PropTypes.func.isRequired,
+ onLogoutClick: PropTypes.func.isRequired,
+};
+
+PageHeaderToolbar.defaultProps = {
+ isAboutDisabled: false,
+};
+
export default PageHeaderToolbar;
diff --git a/src/components/Pagination/Pagination.jsx b/src/components/Pagination/Pagination.jsx
index c96e9c94a1..98a03b8e8c 100644
--- a/src/components/Pagination/Pagination.jsx
+++ b/src/components/Pagination/Pagination.jsx
@@ -1,4 +1,5 @@
import React, { Component } from 'react';
+import PropTypes from 'prop-types';
import { I18n } from '@lingui/react';
import { Trans, t } from '@lingui/macro';
import {
@@ -224,4 +225,19 @@ class Pagination extends Component {
}
}
+Pagination.propTypes = {
+ count: PropTypes.number,
+ onSetPage: PropTypes.func.isRequired,
+ page: PropTypes.number.isRequired,
+ pageCount: PropTypes.number,
+ pageSizeOptions: PropTypes.arrayOf(PropTypes.number),
+ page_size: PropTypes.number.isRequired,
+};
+
+Pagination.defaultProps = {
+ count: null,
+ pageCount: null,
+ pageSizeOptions: [5, 10, 25, 50],
+};
+
export default Pagination;
diff --git a/src/components/SelectedList/SelectedList.jsx b/src/components/SelectedList/SelectedList.jsx
index 1a82e45ef9..7dfd71a6b4 100644
--- a/src/components/SelectedList/SelectedList.jsx
+++ b/src/components/SelectedList/SelectedList.jsx
@@ -1,4 +1,5 @@
import React, { Component } from 'react';
+import PropTypes from 'prop-types';
import {
Chip
} from '@patternfly/react-core';
@@ -70,4 +71,16 @@ class SelectedList extends Component {
}
}
+SelectedList.propTypes = {
+ label: PropTypes.string,
+ onRemove: PropTypes.func.isRequired,
+ selected: PropTypes.arrayOf(PropTypes.object).isRequired,
+ showOverflowAfter: PropTypes.number,
+};
+
+SelectedList.defaultProps = {
+ label: 'Selected',
+ showOverflowAfter: 5,
+};
+
export default SelectedList;
diff --git a/src/components/Tabs/Tab.jsx b/src/components/Tabs/Tab.jsx
index 162c740312..6dd9409391 100644
--- a/src/components/Tabs/Tab.jsx
+++ b/src/components/Tabs/Tab.jsx
@@ -1,4 +1,5 @@
import React from 'react';
+import PropTypes from 'prop-types';
import { NavLink } from 'react-router-dom';
import './tabs.scss';
@@ -15,4 +16,18 @@ const Tab = ({ children, link, replace }) => (
);
+Tab.propTypes = {
+ children: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node
+ ]).isRequired,
+ link: PropTypes.string,
+ replace: PropTypes.bool,
+};
+
+Tab.defaultProps = {
+ link: null,
+ replace: false,
+};
+
export default Tab;
diff --git a/src/components/Tabs/Tabs.jsx b/src/components/Tabs/Tabs.jsx
index 3a308ccdc4..590d171cf8 100644
--- a/src/components/Tabs/Tabs.jsx
+++ b/src/components/Tabs/Tabs.jsx
@@ -1,4 +1,5 @@
import React from 'react';
+import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import { Button } from '@patternfly/react-core';
import { TimesIcon } from '@patternfly/react-icons';
@@ -35,4 +36,21 @@ const Tabs = ({ children, labelText, closeButton }) => (
);
+Tabs.propTypes = {
+ children: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node
+ ]).isRequired,
+ labelText: PropTypes.string,
+ closeButton: PropTypes.shape({
+ text: PropTypes.string,
+ link: PropTypes.string,
+ }),
+};
+
+Tabs.defaultProps = {
+ labelText: null,
+ closeButton: null,
+};
+
export default Tabs;
diff --git a/src/components/Tooltip/Tooltip.jsx b/src/components/Tooltip/Tooltip.jsx
index 107ee0674e..2ecb1ce832 100644
--- a/src/components/Tooltip/Tooltip.jsx
+++ b/src/components/Tooltip/Tooltip.jsx
@@ -1,4 +1,5 @@
import React from 'react';
+import PropTypes from 'prop-types';
class Tooltip extends React.Component {
transforms = {
@@ -74,4 +75,14 @@ class Tooltip extends React.Component {
}
}
+Tooltip.propTypes = {
+ children: PropTypes.element.isRequired,
+ message: PropTypes.string.isRequired,
+ position: PropTypes.string,
+};
+
+Tooltip.defaultProps = {
+ position: 'top',
+};
+
export default Tooltip;
diff --git a/src/components/TowerLogo/TowerLogo.jsx b/src/components/TowerLogo/TowerLogo.jsx
index 402adbaa0e..74d278a69c 100644
--- a/src/components/TowerLogo/TowerLogo.jsx
+++ b/src/components/TowerLogo/TowerLogo.jsx
@@ -1,4 +1,5 @@
import React, { Component } from 'react';
+import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
import { I18n } from '@lingui/react';
import { t } from '@lingui/macro';
@@ -58,4 +59,12 @@ class TowerLogo extends Component {
}
}
+TowerLogo.propTypes = {
+ linkTo: PropTypes.string,
+};
+
+TowerLogo.defaultProps = {
+ linkTo: null,
+};
+
export default withRouter(TowerLogo);
diff --git a/src/pages/Organizations/screens/OrganizationAdd.jsx b/src/pages/Organizations/screens/OrganizationAdd.jsx
index 6b31536b10..a42a381857 100644
--- a/src/pages/Organizations/screens/OrganizationAdd.jsx
+++ b/src/pages/Organizations/screens/OrganizationAdd.jsx
@@ -137,16 +137,18 @@ class OrganizationAdd extends React.Component {
{({ custom_virtualenvs }) => (
-
-
-
+ custom_virtualenvs && custom_virtualenvs.length > 1 && (
+
+
+
+ )
)}
diff --git a/src/pages/Organizations/screens/OrganizationsList.jsx b/src/pages/Organizations/screens/OrganizationsList.jsx
index d19078b575..19c553aa67 100644
--- a/src/pages/Organizations/screens/OrganizationsList.jsx
+++ b/src/pages/Organizations/screens/OrganizationsList.jsx
@@ -39,8 +39,6 @@ class OrganizationsList extends Component {
order_by: 'name',
};
- pageSizeOptions = [5, 10, 25, 50];
-
constructor (props) {
super(props);
@@ -207,7 +205,6 @@ class OrganizationsList extends Component {
selected,
} = this.state;
const { match } = this.props;
-
return (
@@ -258,7 +255,6 @@ class OrganizationsList extends Component {
page={page}
pageCount={pageCount}
page_size={page_size}
- pageSizeOptions={this.pageSizeOptions}
onSetPage={this.onSetPage}
/>
{ loading ? loading...
: '' }