diff --git a/__tests__/components/RoutedTabs.test.jsx b/__tests__/components/RoutedTabs.test.jsx
new file mode 100644
index 0000000000..9fcd896ef9
--- /dev/null
+++ b/__tests__/components/RoutedTabs.test.jsx
@@ -0,0 +1,62 @@
+import React from 'react';
+import { mount, shallow } from 'enzyme';
+import { Router } from 'react-router-dom';
+import { createMemoryHistory } from 'history';
+import RoutedTabs, { _RoutedTabs } from '../../src/components/Tabs/RoutedTabs';
+
+let wrapper;
+let history;
+
+const tabs = [
+ { name: 'Details', link: '/organizations/19/details', id: 1 },
+ { name: 'Access', link: '/organizations/19/access', id: 2 },
+ { name: 'Teams', link: '/organizations/19/teams', id: 3 },
+ { name: 'Notification', link: '/organizations/19/notification', id: 4 }
+];
+
+describe('', () => {
+ beforeEach(() => {
+ history = createMemoryHistory({
+ initialEntries: ['/organizations/19/teams'],
+ });
+ });
+
+ test('RoutedTabs renders successfully', () => {
+ wrapper = shallow(
+ <_RoutedTabs
+ tabsArray={tabs}
+ history={history}
+ />
+ );
+ expect(wrapper.find('Tab')).toHaveLength(4);
+ });
+
+ test('Given a URL the correct tab is active', async () => {
+ wrapper = mount(
+
+
+
+ );
+
+ expect(history.location.pathname).toEqual('/organizations/19/teams');
+ expect(wrapper.find('Tabs').prop('activeKey')).toBe(3);
+ });
+
+ test('should update history when new tab selected', async () => {
+ wrapper = mount(
+
+
+
+ );
+
+ wrapper.find('Tabs').prop('onSelect')({}, 2);
+ wrapper.update();
+
+ expect(history.location.pathname).toEqual('/organizations/19/access');
+ expect(wrapper.find('Tabs').prop('activeKey')).toBe(2);
+ });
+});
diff --git a/package-lock.json b/package-lock.json
index ad1c8266d0..8f85c50e78 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7027,7 +7027,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"aproba": {
"version": "1.2.0",
@@ -7048,12 +7049,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -7068,17 +7071,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"core-util-is": {
"version": "1.0.2",
@@ -7195,7 +7201,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"ini": {
"version": "1.3.5",
@@ -7207,6 +7214,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -7221,6 +7229,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -7228,12 +7237,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -7252,6 +7263,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -7332,7 +7344,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"object-assign": {
"version": "4.1.1",
@@ -7344,6 +7357,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"wrappy": "1"
}
@@ -7429,7 +7443,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -7465,6 +7480,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -7484,6 +7500,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -7527,12 +7544,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
}
}
},
diff --git a/package.json b/package.json
index 4900ec1de0..015865fda1 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,8 @@
"main": "index.jsx",
"scripts": {
"start": "webpack-dev-server --config ./webpack.config.js --mode development",
- "test": "jest --watch --coverage",
+ "test": "jest --coverage",
+ "test-watch": "jest --watch",
"lint": "eslint --ext .js --ext .jsx .",
"add-locale": "lingui add-locale",
"extract-strings": "lingui extract",
diff --git a/src/app.scss b/src/app.scss
index 20df8aa24b..7f05497dc5 100644
--- a/src/app.scss
+++ b/src/app.scss
@@ -283,6 +283,14 @@
margin-bottom: 10px;
}
+.OrgsTab-closeButton {
+ color: black;
+ float: right;
+ position: relative;
+ top: -25px;
+ margin: 0 10px;
+ right: 10px;
+}
.awx-c-form-action-group {
float: right;
display: block;
diff --git a/src/components/Tabs/RoutedTabs.jsx b/src/components/Tabs/RoutedTabs.jsx
new file mode 100644
index 0000000000..4100e766b6
--- /dev/null
+++ b/src/components/Tabs/RoutedTabs.jsx
@@ -0,0 +1,56 @@
+import React from 'react';
+import { shape, string, number, arrayOf } from 'prop-types';
+import { Tab, Tabs } from '@patternfly/react-core';
+import { withRouter } from 'react-router-dom';
+
+function RoutedTabs (props) {
+ const { history, tabsArray } = props;
+
+ const getActiveTabId = () => {
+ const match = tabsArray.find(tab => tab.link === history.location.pathname);
+ if (match) {
+ return match.id;
+ }
+ return 0;
+ };
+
+ function handleTabSelect (event, eventKey) {
+ const match = tabsArray.find(tab => tab.id === eventKey);
+ if (match) {
+ history.push(match.link);
+ }
+ }
+
+ return (
+
+ {tabsArray.map(tab => (
+
+ ))}
+
+ );
+}
+RoutedTabs.propTypes = {
+ history: shape({
+ location: shape({
+ pathname: string.isRequired
+ }).isRequired,
+ }).isRequired,
+ tabsArray: arrayOf(shape({
+ id: number.isRequired,
+ link: string.isRequired,
+ name: string.isRequired,
+ })).isRequired,
+};
+
+export { RoutedTabs as _RoutedTabs };
+export default withRouter(RoutedTabs);
diff --git a/src/components/Tabs/Tab.jsx b/src/components/Tabs/Tab.jsx
deleted file mode 100644
index 6dd9409391..0000000000
--- a/src/components/Tabs/Tab.jsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { NavLink } from 'react-router-dom';
-import './tabs.scss';
-
-const Tab = ({ children, link, replace }) => (
-
-
- {children}
-
-
-);
-
-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
deleted file mode 100644
index abea04f8b8..0000000000
--- a/src/components/Tabs/Tabs.jsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { Link } from 'react-router-dom';
-import { Button, Tooltip } from '@patternfly/react-core';
-import { TimesIcon } from '@patternfly/react-icons';
-import './tabs.scss';
-
-const Tabs = ({ children, labelText, closeButton }) => (
-
-
- {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/Tabs/tabs.scss b/src/components/Tabs/tabs.scss
deleted file mode 100644
index 651be6acba..0000000000
--- a/src/components/Tabs/tabs.scss
+++ /dev/null
@@ -1,71 +0,0 @@
-.pf-c-card__header {
- --pf-c-card__header--PaddingBottom: 0;
- --pf-c-card__header--PaddingX: 0;
- --pf-c-card__header--PaddingRight: 0;
- --pf-c-card__header--PaddingLeft: 0;
- --pf-c-card__header--PaddingTop: 0;
-}
-
-.pf-c-tabs {
- --pf-global--link--Color: #484848;
- --pf-global--link--Color--hover: #484848;
- --pf-global--link--TextDecoration--hover: none;
-
- align-items: center;
- flex-direction: row;
- justify-content: space-between;
-
- &:before {
- border-bottom: 1px solid var(--pf-c-tabs__item--BorderColor);
- border-top: 1px solid var(--pf-c-tabs__item--BorderColor);
- bottom: 0;
- content: " ";
- left: 0;
- position: absolute;
- right: 0;
- top: 0;
- }
-
- .pf-c-tabs__button {
- --pf-c-tabs__button--PaddingLeft: 20px;
- --pf-c-tabs__button--PaddingRight: 20px;
- display: block;
- font-weight: 700;
-
- &:after {
- content: '';
- bottom: 0;
- left: 0;
- position: absolute;
- right: 0;
- top: 0;
- }
- }
-
- .pf-c-tabs__item:first-child .pf-c-tabs__button:before {
- border-left: 0;
- }
-
- .pf-c-tabs__item:not(.pf-m-current):hover
- .pf-c-tabs__button::after {
- border-top: none;
- }
-
- .pf-c-tabs__item:hover
- .pf-c-tabs__button:not(.pf-m-current)::after {
- border-bottom: 3px solid var(--pf-global--Color--dark-200);
- border-top: none;
- }
-
- .pf-c-tabs__button.pf-m-current {
- color: var(--pf-c-tabs__item--m-current--Color);
- }
-
- .pf-c-tabs__button.pf-m-current::after {
- content: '';
- border-bottom: 3px solid var(--pf-c-tabs__item--m-current--Color);
- border-top: none;
- margin-left: 1px;
- }
-}
-
diff --git a/src/pages/Organizations/screens/Organization/Organization.jsx b/src/pages/Organizations/screens/Organization/Organization.jsx
index 2ff186e3db..ada25966eb 100644
--- a/src/pages/Organizations/screens/Organization/Organization.jsx
+++ b/src/pages/Organizations/screens/Organization/Organization.jsx
@@ -5,25 +5,25 @@ import {
Switch,
Route,
withRouter,
- Redirect
+ Redirect,
+ Link
} from 'react-router-dom';
import {
Card,
CardHeader,
- PageSection
+ PageSection,
} from '@patternfly/react-core';
-
+import {
+ TimesIcon
+} from '@patternfly/react-icons';
import { withNetwork } from '../../../../contexts/Network';
-
-import Tabs from '../../../../components/Tabs/Tabs';
-import Tab from '../../../../components/Tabs/Tab';
import NotifyAndRedirect from '../../../../components/NotifyAndRedirect';
-
import OrganizationAccess from './OrganizationAccess';
import OrganizationDetail from './OrganizationDetail';
import OrganizationEdit from './OrganizationEdit';
import OrganizationNotifications from './OrganizationNotifications';
import OrganizationTeams from './OrganizationTeams';
+import RoutedTabs from '../../../../components/Tabs/RoutedTabs';
class Organization extends Component {
constructor (props) {
@@ -79,35 +79,45 @@ class Organization extends Component {
loading
} = this.state;
- const tabElements = [
- { name: i18nMark('Details'), link: `${match.url}/details` },
- { name: i18nMark('Access'), link: `${match.url}/access` },
- { name: i18nMark('Teams'), link: `${match.url}/teams` },
- { name: i18nMark('Notifications'), link: `${match.url}/notifications` },
- ];
+ const tabsPaddingOverride = {
+ padding: '0'
+ };
let cardHeader = (
-
-
- {({ i18n }) => (
-
- {tabElements.map(tabElement => (
-
- {tabElement.name}
-
- ))}
-
- )}
-
-
- );
+ loading ? ''
+ : (
+
+
+ {({ i18n }) => (
+
+
+
+
+
+
+ )}
+
+
+ ));
+ if (!match) {
+ cardHeader = null;
+ }
if (location.pathname.endsWith('edit')) {
cardHeader = null;
@@ -181,5 +191,5 @@ class Organization extends Component {
);
}
}
-
export default withNetwork(withRouter(Organization));
+export { Organization as _Organization };