diff --git a/__tests__/components/DataListToolbar.test.jsx b/__tests__/components/DataListToolbar.test.jsx
index 98920bc02c..8f966ec739 100644
--- a/__tests__/components/DataListToolbar.test.jsx
+++ b/__tests__/components/DataListToolbar.test.jsx
@@ -162,6 +162,7 @@ describe('', () => {
onSearch={onSearch}
onSort={onSort}
onSelectAll={onSelectAll}
+ showDelete
/>
);
diff --git a/__tests__/components/Lookup.test.jsx b/__tests__/components/Lookup.test.jsx
index a331d4a7d1..9402dcbbcc 100644
--- a/__tests__/components/Lookup.test.jsx
+++ b/__tests__/components/Lookup.test.jsx
@@ -3,7 +3,10 @@ import { mount } from 'enzyme';
import { I18nProvider } from '@lingui/react';
import Lookup from '../../src/components/Lookup';
-let mockData = [{ name: 'foo', id: 1 }];
+let mockData = [{ name: 'foo', id: 1, isChecked: false }];
+const mockColumns = [
+ { name: 'Name', key: 'name', isSortable: true }
+];
describe('', () => {
test('initially renders succesfully', () => {
mount(
@@ -14,6 +17,8 @@ describe('', () => {
value={mockData}
onLookupSave={() => { }}
getItems={() => { }}
+ columns={mockColumns}
+ sortedColumnKey="name"
/>
);
@@ -27,6 +32,8 @@ describe('', () => {
value={mockData}
onLookupSave={() => { }}
getItems={() => ({ data: { results: [{ name: 'test instance', id: 1 }] } })}
+ columns={mockColumns}
+ sortedColumnKey="name"
/>
).find('Lookup');
@@ -47,6 +54,8 @@ describe('', () => {
value={mockSelected}
onLookupSave={() => { }}
getItems={() => { }}
+ columns={mockColumns}
+ sortedColumnKey="name"
/>
).find('Lookup');
@@ -72,6 +81,8 @@ describe('', () => {
value={mockSelected}
onLookupSave={() => { }}
getItems={() => ({ data: { results: [{ name: 'test instance', id: 1 }] } })}
+ columns={mockColumns}
+ sortedColumnKey="name"
/>
);
@@ -94,6 +105,8 @@ describe('', () => {
value={mockData}
onLookupSave={() => { }}
getItems={() => { }}
+ columns={mockColumns}
+ sortedColumnKey="name"
/>
);
@@ -112,6 +125,8 @@ describe('', () => {
value={mockData}
selected={[]}
getItems={() => { }}
+ columns={mockColumns}
+ sortedColumnKey="name"
/>
);
@@ -129,6 +144,8 @@ describe('', () => {
value={mockData}
selected={[]}
getItems={() => { }}
+ columns={mockColumns}
+ sortedColumnKey="name"
/>
).find('Lookup');
@@ -174,4 +191,42 @@ describe('', () => {
name: 'foo'
}], 'fooBar');
});
+ test('onSort sets state and calls getData ', () => {
+ const spy = jest.spyOn(Lookup.prototype, 'getData');
+ const wrapper = mount(
+
+ { }}
+ data={mockData}
+ selected={[]}
+ columns={mockColumns}
+ sortedColumnKey="name"
+ />
+
+ ).find('Lookup');
+ wrapper.instance().onSort('id', 'descending');
+ expect(wrapper.state('sortedColumnKey')).toEqual('id');
+ expect(wrapper.state('sortOrder')).toEqual('descending');
+ expect(spy).toHaveBeenCalled();
+ });
+ test('onSetPage sets state and calls getData ', () => {
+ const spy = jest.spyOn(Lookup.prototype, 'getData');
+ const wrapper = mount(
+
+ { }}
+ data={mockData}
+ selected={[]}
+ columns={mockColumns}
+ sortedColumnKey="name"
+ />
+
+ ).find('Lookup');
+ wrapper.instance().onSetPage(2, 10);
+ expect(wrapper.state('page')).toEqual(2);
+ expect(wrapper.state('page_size')).toEqual(10);
+ expect(spy).toHaveBeenCalled();
+ });
});
diff --git a/__tests__/components/VerticalSeparator.test.jsx b/__tests__/components/VerticalSeparator.test.jsx
new file mode 100644
index 0000000000..158ad9a09d
--- /dev/null
+++ b/__tests__/components/VerticalSeparator.test.jsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import { mount } from 'enzyme';
+
+import VerticalSeparator from '../../src/components/VerticalSeparator';
+
+describe('VerticalSeparator', () => {
+ test('renders the expected content', () => {
+ const wrapper = mount();
+ expect(wrapper).toHaveLength(1);
+ });
+});
diff --git a/build/locales/en/messages.js b/build/locales/en/messages.js
index 7b6fb177b6..47ec354bb5 100644
--- a/build/locales/en/messages.js
+++ b/build/locales/en/messages.js
@@ -1 +1 @@
-/* eslint-disable */module.exports={languageData:{"plurals":function(n,ord){var s=String(n).split("."),v0=!s[1],t0=Number(s[0])==n,n10=t0&&s[0].slice(-1),n100=t0&&s[0].slice(-2);if(ord)return n10==1&&n100!=11?"one":n10==2&&n100!=12?"two":n10==3&&n100!=13?"few":"other";return n==1&&v0?"one":"other"}},messages:{"> add":"> add","> edit":"> edit","About":"About","AboutModal Logo":"AboutModal Logo","Access":"Access","Add":"Add","Administration":"Administration","Admins":"Admins","Ansible Version":"Ansible Version","Applications":"Applications","Authentication":"Authentication","Authentication Settings":"Authentication Settings","Brand Image":"Brand Image","Collapse":"Collapse","Copyright 2018 Red Hat, Inc.":"Copyright 2018 Red Hat, Inc.","Created":"Created","Credential Types":"Credential Types","Credentials":"Credentials","Dashboard":"Dashboard","Delete":"Delete","Edit":"Edit","Expand":"Expand","First":"First","Help":"Help","Instance Groups":"Instance Groups","Integrations":"Integrations","Invalid username or password. Please try again.":"Invalid username or password. Please try again.","Inventories":"Inventories","Inventory Scripts":"Inventory Scripts","Jobs":"Jobs","Jobs Settings":"Jobs Settings","Last":"Last","License":"License","Logout":"Logout","Management Jobs":"Management Jobs","Modified":"Modified","My View":"Foo","Name":"Name","Next":"Next","Notification Templates":"Notification Templates","Notifications":"Notifications","Organization Add":"Organization Add","Organization detail tabs":"Organization detail tabs","Organizations":"Organizations","Organizations List":"Organizations List","Page <0/> of {pageCount}":function(a){return["Page <0/> of ",a("pageCount")]},"Page Number":"Page Number","Password":"Password","Per Page":"Per Page","Portal Mode":"Portal Mode","Previous":"Previous","Primary Navigation":"Primary Navigation","Projects":"Projects","Resources":"Resources","Schedules":"Schedules","Search":"Search","Search text input":"Search text input","Select all":"Select all","Settings":"Settings","Sort":"Sort","System":"System","System Settings":"System Settings","Teams":"Teams","Templates":"Templates","Tower Brand Image":"Tower Brand Image","User Details":"User Details","User Interface":"User Interface","User Interface Settings":"User Interface Settings","Username":"Username","Users":"Users","Views":"Views","Welcome to Ansible Tower! Please Sign In.":"Welcome to Ansible Tower! Please Sign In.","add {currentTab}":function(a){return["add ",a("currentTab")]},"adding {currentTab}":function(a){return["adding ",a("currentTab")]},"confirm removal of {currentTab}/cancel and go back to {currentTab} view.":function(a){return["confirm removal of ",a("currentTab"),"/cancel and go back to ",a("currentTab")," view."]},"delete {currentTab}":function(a){return["delete ",a("currentTab")]},"deleting {currentTab} association with orgs":function(a){return["deleting ",a("currentTab")," association with orgs"]},"edit view":"edit view","save/cancel and go back to view":"save/cancel and go back to view","save/cancel and go back to {currentTab} view":function(a){return["save/cancel and go back to ",a("currentTab")," view"]},"select organization {itemId}":function(a){return["select organization ",a("itemId")]},"{0}":function(a){return[a("0")]},"{currentTab} detail view":function(a){return[a("currentTab")," detail view"]},"{itemMin} - {itemMax} of {count}":function(a){return[a("itemMin")," - ",a("itemMax")," of ",a("count")]}}};
\ No newline at end of file
+/* eslint-disable */module.exports={languageData:{"plurals":function(n,ord){var s=String(n).split("."),v0=!s[1],t0=Number(s[0])==n,n10=t0&&s[0].slice(-1),n100=t0&&s[0].slice(-2);if(ord)return n10==1&&n100!=11?"one":n10==2&&n100!=12?"two":n10==3&&n100!=13?"few":"other";return n==1&&v0?"one":"other"}},messages:{"> add":"> add","> edit":"> edit","About":"About","AboutModal Logo":"AboutModal Logo","Access":"Access","Add":"Add","Administration":"Administration","Admins":"Admins","Ansible Version":"Ansible Version","Applications":"Applications","Authentication":"Authentication","Authentication Settings":"Authentication Settings","Brand Image":"Brand Image","Collapse":"Collapse","Copyright 2018 Red Hat, Inc.":"Copyright 2018 Red Hat, Inc.","Created":"Created","Credential Types":"Credential Types","Credentials":"Credentials","Dashboard":"Dashboard","Delete":"Delete","Edit":"Edit","Expand":"Expand","First":"First","Help":"Help","Instance Groups":"Instance Groups","Integrations":"Integrations","Invalid username or password. Please try again.":"Invalid username or password. Please try again.","Inventories":"Inventories","Inventory Scripts":"Inventory Scripts","Jobs":"Jobs","Jobs Settings":"Jobs Settings","Last":"Last","License":"License","Logout":"Logout","Management Jobs":"Management Jobs","Modified":"Modified","My View":"My View","Name":"Name","Next":"Next","Notification Templates":"Notification Templates","Notifications":"Notifications","Organization Add":"Organization Add","Organization detail tabs":"Organization detail tabs","Organizations":"Organizations","Organizations List":"Organizations List","Page <0/> of {pageCount}":function(a){return["Page <0/> of ",a("pageCount")]},"Page Number":"Page Number","Password":"Password","Per Page":"Per Page","Portal Mode":"Portal Mode","Previous":"Previous","Primary Navigation":"Primary Navigation","Projects":"Projects","Resources":"Resources","Schedules":"Schedules","Search":"Search","Search text input":"Search text input","Select all":"Select all","Settings":"Settings","Sort":"Sort","System":"System","System Settings":"System Settings","Teams":"Teams","Templates":"Templates","Tower Brand Image":"Tower Brand Image","User Details":"User Details","User Interface":"User Interface","User Interface Settings":"User Interface Settings","Username":"Username","Users":"Users","Views":"Views","Welcome to Ansible Tower! Please Sign In.":"Welcome to Ansible Tower! Please Sign In.","add {currentTab}":function(a){return["add ",a("currentTab")]},"adding {currentTab}":function(a){return["adding ",a("currentTab")]},"confirm removal of {currentTab}/cancel and go back to {currentTab} view.":function(a){return["confirm removal of ",a("currentTab"),"/cancel and go back to ",a("currentTab")," view."]},"delete {currentTab}":function(a){return["delete ",a("currentTab")]},"deleting {currentTab} association with orgs":function(a){return["deleting ",a("currentTab")," association with orgs"]},"edit view":"edit view","save/cancel and go back to view":"save/cancel and go back to view","save/cancel and go back to {currentTab} view":function(a){return["save/cancel and go back to ",a("currentTab")," view"]},"select organization {itemId}":function(a){return["select organization ",a("itemId")]},"{0}":function(a){return[a("0")]},"{currentTab} detail view":function(a){return[a("currentTab")," detail view"]},"{itemMin} - {itemMax} of {count}":function(a){return[a("itemMin")," - ",a("itemMax")," of ",a("count")]}}};
\ No newline at end of file
diff --git a/src/app.scss b/src/app.scss
index 09ce2c5863..0a1e430ded 100644
--- a/src/app.scss
+++ b/src/app.scss
@@ -158,10 +158,13 @@
.awx-c-modal.pf-c-modal-box {
margin: 0;
- padding: 20px;
- width: 550px;
+ width: 600px;
- .pf-c-button:not(:last-child) {
+ .pf-c-modal-box__body {
+ overflow: visible;
+ }
+
+ .pf-c-modal-box__footer > .pf-c-button:not(:last-child) {
margin-right: 20px;
}
}
@@ -233,7 +236,6 @@
}
.awx-c-list {
- border-top: 1px solid #d7d7d7;
border-bottom: 1px solid #d7d7d7;
}
diff --git a/src/components/DataListToolbar/DataListToolbar.jsx b/src/components/DataListToolbar/DataListToolbar.jsx
index b6a468c550..a84f086a00 100644
--- a/src/components/DataListToolbar/DataListToolbar.jsx
+++ b/src/components/DataListToolbar/DataListToolbar.jsx
@@ -31,6 +31,11 @@ import {
} from 'react-router-dom';
import Tooltip from '../Tooltip';
+import VerticalSeparator from '../VerticalSeparator';
+
+const flexGrowStyling = {
+ flexGrow: '1'
+};
class DataListToolbar extends React.Component {
constructor (props) {
@@ -108,10 +113,10 @@ class DataListToolbar extends React.Component {
addUrl,
showExpandCollapse,
showDelete,
- showSelectAll
+ showSelectAll,
+ isLookup
} = this.props;
const {
- // isActionDropdownOpen,
isSearchDropdownOpen,
isSortDropdownOpen,
searchKey,
@@ -150,8 +155,8 @@ class DataListToolbar extends React.Component {
{({ i18n }) => (
-
-
+
+
{ showSelectAll && (
@@ -162,10 +167,11 @@ class DataListToolbar extends React.Component {
id="select-all"
/>
+
)}
-
-
+
+
+
-
-
- {sortedColumnName}
-
- )}
- dropdownItems={sortDropdownItems}
- />
-
+ { sortDropdownItems.length > 1 && (
+
+
+ {sortedColumnName}
+
+ )}
+ dropdownItems={sortDropdownItems}
+ />
+
+ )}
+ { (showExpandCollapse || showDelete || addUrl) && (
+
+ )}
{showExpandCollapse && (
@@ -245,6 +258,9 @@ class DataListToolbar extends React.Component {
+ { (showDelete || addUrl) && (
+
+ )}
)}
diff --git a/src/components/DataListToolbar/styles.scss b/src/components/DataListToolbar/styles.scss
index 23ec65dba8..e8d989da3c 100644
--- a/src/components/DataListToolbar/styles.scss
+++ b/src/components/DataListToolbar/styles.scss
@@ -28,16 +28,6 @@
--pf-l-toolbar__group--MarginLeft: 0px;
}
-.awx-toolbar .pf-l-toolbar__group:after {
- content: "";
- background-color: #d7d7d7;
- width: 1px;
- height: 30px;
- display: block;
- margin-left: 20px;
- margin-right: 20px;
-}
-
.awx-toolbar button.pf-c-button {
height: 30px;
padding: 0px;
@@ -47,12 +37,6 @@
min-height: 0px;
height: 30px;
- input {
- height: 30px;
- padding: 0 10px;
- width: 300px;
- }
-
.pf-m-tertiary {
width: 34px;
padding: 0px;
diff --git a/src/components/Lookup/Lookup.jsx b/src/components/Lookup/Lookup.jsx
index 0dbfc59813..54fb3dbebf 100644
--- a/src/components/Lookup/Lookup.jsx
+++ b/src/components/Lookup/Lookup.jsx
@@ -13,6 +13,7 @@ import { I18n } from '@lingui/react';
import { Trans, t } from '@lingui/macro';
import CheckboxListItem from '../ListItem';
+import DataListToolbar from '../DataListToolbar';
import SelectedList from '../SelectedList';
import Pagination from '../Pagination';
@@ -34,7 +35,9 @@ class Lookup extends React.Component {
count: 0,
page: 1,
page_size: 5,
- error: null
+ error: null,
+ sortOrder: 'ascending',
+ sortedColumnKey: props.sortedColumnKey
};
this.onSetPage = this.onSetPage.bind(this);
this.handleModalToggle = this.handleModalToggle.bind(this);
@@ -42,6 +45,8 @@ class Lookup extends React.Component {
this.toggleSelected = this.toggleSelected.bind(this);
this.saveModal = this.saveModal.bind(this);
this.getData = this.getData.bind(this);
+ this.onSearch = this.onSearch.bind(this);
+ this.onSort = this.onSort.bind(this);
}
componentDidMount () {
@@ -49,18 +54,35 @@ class Lookup extends React.Component {
this.getData({ page_size, page });
}
- async getData (queryParams) {
+ onSearch () {
+ const { sortedColumnKey, sortOrder } = this.state;
+ this.onSort(sortedColumnKey, sortOrder);
+ }
+
+ onSort (sortedColumnKey, sortOrder) {
+ this.setState({ page: 1, sortedColumnKey, sortOrder }, this.getData);
+ }
+
+ async getData () {
const { getItems } = this.props;
- const { page } = queryParams;
+ const { page, page_size, sortedColumnKey, sortOrder } = this.state;
this.setState({ error: false });
+ const queryParams = {
+ page,
+ page_size
+ };
+
+ if (sortedColumnKey) {
+ queryParams.order_by = sortOrder === 'descending' ? `-${sortedColumnKey}` : sortedColumnKey;
+ }
+
try {
const { data } = await getItems(queryParams);
const { results, count } = data;
const stateToUpdate = {
- page,
results,
count
};
@@ -74,7 +96,7 @@ class Lookup extends React.Component {
onSetPage = async (pageNumber, pageSize) => {
const page = parseInt(pageNumber, 10);
const page_size = parseInt(pageSize, 10);
- this.getData({ page_size, page });
+ this.setState({ page, page_size }, this.getData);
};
toggleSelected (row) {
@@ -124,8 +146,18 @@ class Lookup extends React.Component {
}
render () {
- const { isModalOpen, lookupSelectedItems, error, results, count, page, page_size } = this.state;
- const { lookupHeader, value } = this.props;
+ const {
+ isModalOpen,
+ lookupSelectedItems,
+ error,
+ results,
+ count,
+ page,
+ page_size,
+ sortedColumnKey,
+ sortOrder
+ } = this.state;
+ const { lookupHeader = 'items', value, columns } = this.props;
return (
@@ -157,6 +189,14 @@ class Lookup extends React.Component {
) : (
+
{results.map(i => (
{label}
+
{selected
diff --git a/src/components/SelectedList/styles.scss b/src/components/SelectedList/styles.scss
index 4bf49ecfa7..7db18e2c3f 100644
--- a/src/components/SelectedList/styles.scss
+++ b/src/components/SelectedList/styles.scss
@@ -15,15 +15,6 @@
white-space: nowrap;
height: 30px;
}
- .pf-l-split__item:not(:last-child):after {
- content: "";
- background-color: var(--awx-selectedList--BorderColor);
- width: 1px;
- height: 30px;
- display: block;
- margin-left: 20px;
- margin-right: 20px;
- }
.pf-c-chip {
margin-right: 10px;
margin-bottom: 10px;
diff --git a/src/components/VerticalSeparator/VerticalSeparator.jsx b/src/components/VerticalSeparator/VerticalSeparator.jsx
new file mode 100644
index 0000000000..b86a94537b
--- /dev/null
+++ b/src/components/VerticalSeparator/VerticalSeparator.jsx
@@ -0,0 +1,16 @@
+import React from 'react';
+
+const VerticalSeparator = () => (
+
+);
+
+export default VerticalSeparator;
diff --git a/src/components/VerticalSeparator/index.js b/src/components/VerticalSeparator/index.js
new file mode 100644
index 0000000000..737c49a099
--- /dev/null
+++ b/src/components/VerticalSeparator/index.js
@@ -0,0 +1,3 @@
+import VerticalSeparator from './VerticalSeparator';
+
+export default VerticalSeparator;
diff --git a/src/pages/Organizations/screens/OrganizationAdd.jsx b/src/pages/Organizations/screens/OrganizationAdd.jsx
index a42a381857..6dbd224781 100644
--- a/src/pages/Organizations/screens/OrganizationAdd.jsx
+++ b/src/pages/Organizations/screens/OrganizationAdd.jsx
@@ -1,6 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
+import { I18n, i18nMark } from '@lingui/react';
+import { t } from '@lingui/macro';
import {
PageSection,
Form,
@@ -98,60 +100,71 @@ class OrganizationAdd extends React.Component {
error
} = this.state;
const enabled = name.length > 0; // TODO: add better form validation
+ const instanceGroupsLookupColumns = [
+ { name: i18nMark('Name'), key: 'name', isSortable: true },
+ { name: i18nMark('Modified'), key: 'modified', isSortable: false, isNumeric: true },
+ { name: i18nMark('Created'), key: 'created', isSortable: false, isNumeric: true }
+ ];
return (