diff --git a/__tests__/components/DataListToolbar.test.jsx b/__tests__/components/DataListToolbar.test.jsx index 8f966ec739..b07c4fb5e8 100644 --- a/__tests__/components/DataListToolbar.test.jsx +++ b/__tests__/components/DataListToolbar.test.jsx @@ -221,4 +221,32 @@ describe('', () => { const upAlphaIcon = toolbar.find(upAlphaIconSelector); expect(upAlphaIcon.length).toBe(1); }); + + test('trash can button triggers correct function', () => { + const columns = [{ name: 'Name', key: 'name', isSortable: true }]; + const onOpenDeleteModal = jest.fn(); + const deleteModal = 'button.pf-c-button.pf-m-plain.awx-ToolBarBtn'; + const onSearch = jest.fn(); + const onSort = jest.fn(); + const onSelectAll = jest.fn(); + + toolbar = mount( + + ([1, 2, 3, 4])} + sortedColumnKey="name" + sortOrder="ascending" + columns={columns} + onSearch={onSearch} + onSort={onSort} + onSelectAll={onSelectAll} + onOpenDeleteModal={() => {}} + /> + + ); + + toolbar.find(deleteModal).simulate('click'); + expect(onOpenDeleteModal).toBeCalled(); + }); }); diff --git a/src/api.js b/src/api.js index f87c51703e..d448fe3425 100644 --- a/src/api.js +++ b/src/api.js @@ -56,6 +56,11 @@ class APIClient { return this.http.get(API_CONFIG); } + destroyOrganization (id) { + const endpoint = `${API_ORGANIZATIONS}${id}/`; + return (this.http.delete(endpoint)); + } + getOrganizations (params = {}) { return this.http.get(API_ORGANIZATIONS, { params }); } diff --git a/src/app.scss b/src/app.scss index 1b4a0e45b3..6a6ef32afe 100644 --- a/src/app.scss +++ b/src/app.scss @@ -279,6 +279,10 @@ margin-bottom: 10px; } +.orgListAlert-actionBtn{ + margin:0 10px; +} + .awx-c-form-action-group { float: right; display: block; @@ -287,4 +291,4 @@ margin-top: 20px; margin-right: 20px; } -} \ No newline at end of file +} diff --git a/src/components/DataListToolbar/DataListToolbar.jsx b/src/components/DataListToolbar/DataListToolbar.jsx index 00f3381458..7912c74ba3 100644 --- a/src/components/DataListToolbar/DataListToolbar.jsx +++ b/src/components/DataListToolbar/DataListToolbar.jsx @@ -24,29 +24,31 @@ import ExpandCollapse from '../ExpandCollapse'; import Search from '../Search'; import Sort from '../Sort'; import VerticalSeparator from '../VerticalSeparator'; +// import SelectedList from '../SelectedList'; class DataListToolbar extends React.Component { render () { const { + addUrl, columns, - isAllSelected, + disableTrashCanIcon, onSelectAll, sortedColumnKey, sortOrder, - addUrl, showDelete, showSelectAll, + isAllSelected, isLookup, isCompact, onSort, onSearch, onCompact, onExpand, - add + add, + onOpenDeleteModal } = this.props; const showExpandCollapse = (onCompact && onExpand); - return ( {({ i18n }) => ( @@ -115,10 +117,13 @@ class DataListToolbar extends React.Component { position="top" > )} diff --git a/src/components/DataListToolbar/styles.scss b/src/components/DataListToolbar/styles.scss index e8d989da3c..c91bdb727c 100644 --- a/src/components/DataListToolbar/styles.scss +++ b/src/components/DataListToolbar/styles.scss @@ -80,3 +80,16 @@ .awx-toolbar .pf-l-toolbar__item .pf-c-button.pf-m-plain { font-size: 18px; } +.pf-c-button--disabled--BackgroundColor{ + background-color: #b7b7b7; +} +.awx-ToolBarBtn{ + width: 30px; +} +.awx-ToolBarBtn:hover{ + .awx-ToolBarTrashCanIcon { + color:white; + } + background-color:#d9534f; +} + diff --git a/src/pages/Organizations/components/OrganizationListItem.jsx b/src/pages/Organizations/components/OrganizationListItem.jsx index 19adbd98df..55eea410d9 100644 --- a/src/pages/Organizations/components/OrganizationListItem.jsx +++ b/src/pages/Organizations/components/OrganizationListItem.jsx @@ -4,61 +4,72 @@ import { Trans, t } from '@lingui/macro'; import { Badge, Checkbox, + Button, } from '@patternfly/react-core'; +import { + TrashAltIcon, +} from '@patternfly/react-icons'; import { Link } from 'react-router-dom'; import VerticalSeparator from '../../../components/VerticalSeparator'; -export default ({ - itemId, - name, - userCount, - teamCount, - isSelected, - onSelect, - detailUrl, -}) => ( -
  • - - {({ i18n }) => ( - - )} - - -
    - - - {name} - - -
    -
    - - Users - - - {' '} - {userCount} - {' '} - - - Teams - - - {' '} - {teamCount} - {' '} - -
    -
    -
  • -); +class OrganizationListItem extends React.Component { + render () { + const { + itemId, + name, + userCount, + teamCount, + isSelected, + onSelect, + detailUrl, + } = this.props; + return ( +
  • + + {({ i18n }) => ( + + )} + + +
    + + + {name} + + +
    +
    + + Users + + + {' '} + {userCount} + {' '} + + + Teams + + + {' '} + {teamCount} + {' '} + +
    +
    +
  • + ); + } +} +export default OrganizationListItem; + diff --git a/src/pages/Organizations/screens/OrganizationsList.jsx b/src/pages/Organizations/screens/OrganizationsList.jsx index 19c553aa67..9da3b1f85e 100644 --- a/src/pages/Organizations/screens/OrganizationsList.jsx +++ b/src/pages/Organizations/screens/OrganizationsList.jsx @@ -12,9 +12,11 @@ import { EmptyState, EmptyStateIcon, EmptyStateBody, + Modal, PageSection, PageSectionVariants, - Title + Title, + Button } from '@patternfly/react-core'; import { CubesIcon } from '@patternfly/react-icons'; import DataListToolbar from '../../../components/DataListToolbar'; @@ -54,6 +56,8 @@ class OrganizationsList extends Component { loading: true, results: [], selected: [], + isModalOpen: false, + orgsToDelete: [] }; this.onSearch = this.onSearch.bind(this); @@ -64,6 +68,9 @@ class OrganizationsList extends Component { this.onSelect = this.onSelect.bind(this); this.updateUrl = this.updateUrl.bind(this); this.fetchOrganizations = this.fetchOrganizations.bind(this); + this.handleOrgDelete = this.handleOrgDelete.bind(this); + this.handleOpenOrgDeleteModal = this.handleOpenOrgDeleteModal.bind(this); + this.handleClearOrgsToDelete = this.handleClearOrgsToDelete.bind(this); } componentDidMount () { @@ -129,6 +136,50 @@ class OrganizationsList extends Component { return Object.assign({}, this.defaultParams, searchParams, overrides); } + handleClearOrgsToDelete () { + this.setState(({ isModalOpen }) => ({ isModalOpen: !isModalOpen, orgsToDelete: [] })); + this.onSelectAll(); + } + + handleOpenOrgDeleteModal () { + const { results, selected } = this.state; + const warningTitle = i18nMark('Delete Organization'); + const warningMsg = i18nMark('Are you sure you want to delete:'); + + const orgsToDelete = []; + results.forEach((result) => { + selected.forEach((selectedOrg) => { + if (result.id === selectedOrg) { + orgsToDelete.push({ name: result.name, id: selectedOrg }); + } + }); + }); + this.setState({ + orgsToDelete, + isModalOpen: true, + warningTitle, + warningMsg, + loading: false }); + } + + async handleOrgDelete (event) { + const { orgsToDelete } = this.state; + const { api } = this.props; + try { + const deleteOrgsApiCalls = []; + + orgsToDelete.forEach((org) => { + deleteOrgsApiCalls.push(api.destroyOrganization(org.id)); + }); + await Promise.all(deleteOrgsApiCalls); + } finally { + this.handleClearOrgsToDelete(); + const queryParams = this.getQueryParams(); + this.fetchOrganizations(queryParams); + } + event.preventDefault(); + } + updateUrl (queryParams) { const { history, location } = this.props; const pathname = '/organizations'; @@ -196,18 +247,47 @@ class OrganizationsList extends Component { error, loading, noInitialResults, + orgsToDelete, page, pageCount, page_size, + selected, sortedColumnKey, sortOrder, results, - selected, + isModalOpen, + warningTitle, + warningMsg, } = this.state; const { match } = this.props; return ( + { isModalOpen && ( + + {warningMsg} +
    + {orgsToDelete.map((org) => ( + + {org.name} +
    +
    + ))} +
    + + + + +
    + )} {noInitialResults && ( @@ -229,6 +309,8 @@ class OrganizationsList extends Component { onSearch={this.onSearch} onSort={this.onSort} onSelectAll={this.onSelectAll} + onOpenDeleteModal={this.handleOpenOrgDeleteModal} + disableTrashCanIcon={selected.length === 0} showDelete showSelectAll /> @@ -244,7 +326,8 @@ class OrganizationsList extends Component { userCount={o.summary_fields.related_field_counts.users} teamCount={o.summary_fields.related_field_counts.teams} isSelected={selected.includes(o.id)} - onSelect={() => this.onSelect(o.id)} + onSelect={() => this.onSelect(o.id, o.name)} + onOpenOrgDeleteModal={this.handleOpenOrgDeleteModal} /> ))}