diff --git a/src/api.js b/src/api.js
index 1c9c03ff31..95e4520541 100644
--- a/src/api.js
+++ b/src/api.js
@@ -66,10 +66,10 @@ class APIClient {
return this.http.post(API_ORGANIZATIONS, data);
}
- getOrganzationAccessList (id) {
+ getOrganzationAccessList (id, params = {}) {
const endpoint = `${API_ORGANIZATIONS}${id}/access_list/`;
- return this.http.get(endpoint);
+ return this.http.get(endpoint, { params });
}
getOrganizationDetails (id) {
@@ -84,24 +84,6 @@ class APIClient {
return this.http.get(endpoint, { params });
}
- getOrganizationUserRoles (id) {
- const endpoint = `${API_USERS}${id}/roles/`;
-
- return this.http.get(endpoint);
- }
-
- getUserTeams (id) {
- const endpoint = `${API_USERS}${id}/teams/`;
-
- return this.http.get(endpoint);
- }
-
- getTeamRoles (id) {
- const endpoint = `${API_TEAMS}${id}/roles/`;
-
- return this.http.get(endpoint);
- }
-
getOrganizationNotifications (id, params = {}) {
const endpoint = `${API_ORGANIZATIONS}${id}/notification_templates/`;
@@ -139,6 +121,24 @@ class APIClient {
createInstanceGroups (url, id) {
return this.http.post(url, { id });
}
+
+ getUserRoles (id) {
+ const endpoint = `${API_USERS}${id}/roles/`;
+
+ return this.http.get(endpoint);
+ }
+
+ getUserTeams (id) {
+ const endpoint = `${API_USERS}${id}/teams/`;
+
+ return this.http.get(endpoint);
+ }
+
+ getTeamRoles (id) {
+ const endpoint = `${API_TEAMS}${id}/roles/`;
+
+ return this.http.get(endpoint);
+ }
}
export default APIClient;
diff --git a/src/components/AccessList/Access.list.jsx b/src/components/AccessList/Access.list.jsx
new file mode 100644
index 0000000000..dc9a53a592
--- /dev/null
+++ b/src/components/AccessList/Access.list.jsx
@@ -0,0 +1,343 @@
+import React, { Fragment } from 'react';
+import PropTypes from 'prop-types';
+
+import {
+ DataList, DataListItem, DataListCell, Text,
+ TextContent, TextVariants, Badge
+} from '@patternfly/react-core';
+
+import { I18n, i18nMark } from '@lingui/react';
+import { t } from '@lingui/macro';
+
+import {
+ Link
+} from 'react-router-dom';
+
+import BasicChip from '../BasicChip/BasicChip';
+import Pagination from '../Pagination/Pagination';
+import DataListToolbar from '../DataListToolbar/DataListToolbar';
+
+import {
+ parseQueryString,
+} from '../../qs';
+
+const userRolesWrapperStyle = {
+ display: 'flex',
+ flexWrap: 'wrap',
+};
+
+const detailWrapperStyle = {
+ display: 'grid',
+ gridTemplateColumns: 'minmax(70px, max-content) minmax(60px, max-content)',
+};
+
+const detailLabelStyle = {
+ fontWeight: '700',
+ lineHeight: '24px',
+ marginRight: '20px',
+};
+
+const detailValueStyle = {
+ lineHeight: '28px',
+ overflow: 'visible',
+};
+
+const hiddenStyle = {
+ display: 'none',
+};
+
+const Detail = ({ label, value, url, isBadge, customStyles }) => {
+ let detail = null;
+ if (value) {
+ detail = (
+
+ {url ? (
+
+ {label}
+ ) : ({label}
+ )}
+ {isBadge ? (
+
+ {value}
+
+ ) : (
+ {value}
+ )}
+
+ );
+ }
+ return detail;
+};
+
+class AccessList extends React.Component {
+ columns = [
+ { name: i18nMark('Username'), key: 'username', isSortable: true },
+ { name: i18nMark('First Name'), key: 'first_name', isSortable: true, isNumeric: true },
+ { name: i18nMark('Last Name'), key: 'last_name', isSortable: true, isNumeric: true },
+ ];
+
+ defaultParams = {
+ page: 1,
+ page_size: 5,
+ order_by: 'username',
+ };
+
+ constructor (props) {
+ super(props);
+
+ const { page, page_size } = this.getQueryParams();
+
+ this.state = {
+ page,
+ page_size,
+ count: null,
+ results: [],
+ sortOrder: 'ascending',
+ sortedColumnKey: 'username',
+ isCompact: true,
+ };
+
+ this.fetchOrgAccessList = this.fetchOrgAccessList.bind(this);
+ this.onSetPage = this.onSetPage.bind(this);
+ this.onExpand = this.onExpand.bind(this);
+ this.onCompact = this.onCompact.bind(this);
+ this.onSort = this.onSort.bind(this);
+ this.getQueryParams = this.getQueryParams.bind(this);
+ }
+
+ componentDidMount () {
+ const queryParams = this.getQueryParams();
+
+ this.fetchOrgAccessList(queryParams);
+ }
+
+ onExpand = () => {
+ this.setState({ isCompact: false });
+ }
+
+ onCompact = () => {
+ this.setState({ isCompact: true });
+ }
+
+ onSetPage = (pageNumber, pageSize) => {
+ const { sortOrder, sortedColumnKey } = this.state;
+ const page = parseInt(pageNumber, 10);
+ const page_size = parseInt(pageSize, 10);
+ let order_by = sortedColumnKey;
+
+ if (sortOrder === 'descending') {
+ order_by = `-${order_by}`;
+ }
+
+ const queryParams = this.getQueryParams({ page, page_size, order_by });
+
+ this.fetchOrgAccessList(queryParams);
+ };
+
+ getQueryParams (overrides = {}) {
+ const { location } = this.props;
+ const { search } = location;
+
+ const searchParams = parseQueryString(search.substring(1));
+
+ return Object.assign({}, this.defaultParams, searchParams, overrides);
+ }
+
+ onSort = (sortedColumnKey, sortOrder) => {
+ const { page_size } = this.state;
+
+ let order_by = sortedColumnKey;
+
+ if (sortOrder === 'descending') {
+ order_by = `-${order_by}`;
+ }
+
+ const queryParams = this.getQueryParams({ order_by, page_size });
+
+ this.fetchOrgAccessList(queryParams);
+ };
+
+ async fetchOrgAccessList (queryParams) {
+ const { match, getAccessList, getUserRoles, getTeamRoles, getUserTeams } = this.props;
+
+ const { page, page_size, order_by } = queryParams;
+
+ let sortOrder = 'ascending';
+ let sortedColumnKey = order_by;
+
+ if (order_by.startsWith('-')) {
+ sortOrder = 'descending';
+ sortedColumnKey = order_by.substring(1);
+ }
+
+ try {
+ const { data:
+ { count = null, results = null }
+ } = await getAccessList(match.params.id, queryParams);
+ const pageCount = Math.ceil(count / page_size);
+
+ const stateToUpdate = {
+ count,
+ page,
+ pageCount,
+ page_size,
+ sortOrder,
+ sortedColumnKey,
+ results,
+ };
+
+ results.map(async result => {
+ result.user_roles = [];
+ result.team_roles = [];
+ // Grab each Role Type and set as a top-level value
+ Object.values(result.summary_fields).filter(value => value.length > 0).forEach(roleType => {
+ result.roleType = roleType[0].role.name;
+ });
+
+ // Grab User Roles and set as a top-level value
+ try {
+ const resp = await getUserRoles(result.id);
+ const roles = resp.data.results;
+ roles.forEach(role => {
+ result.user_roles.push(role);
+ });
+ this.setState(stateToUpdate);
+ } catch (error) {
+ this.setState({ error });
+ }
+
+ // Grab Team Roles and set as a top-level value
+ try {
+ const team_data = await getUserTeams(result.id);
+ const teams = team_data.data.results;
+ teams.forEach(async team => {
+ let team_roles = await getTeamRoles(team.id);
+ team_roles = team_roles.data.results;
+ team_roles.forEach(role => {
+ result.team_roles.push(role);
+ });
+ this.setState(stateToUpdate);
+ });
+ } catch (error) {
+ this.setState({ error });
+ }
+ });
+ } catch (error) {
+ this.setState({ error });
+ }
+ }
+
+ render () {
+ const {
+ results,
+ error,
+ count,
+ page_size,
+ pageCount,
+ page,
+ sortedColumnKey,
+ sortOrder,
+ isCompact,
+ } = this.state;
+
+ if (!results) {
+ return null;
+ }
+ return (
+
+ {({ i18n }) => (
+
+ {}}
+ onSort={this.onSort}
+ onCompact={this.onCompact}
+ onExpand={this.onExpand}
+ isCompact={isCompact}
+ showExpandCollapse
+ />
+
+
+ { error ? {error}
: '' }
+
+ )}
+
+ );
+ }
+}
+
+AccessList.propTypes = {
+ getAccessList: PropTypes.func.isRequired,
+ getUserRoles: PropTypes.func.isRequired,
+ getUserTeams: PropTypes.func.isRequired,
+ getTeamRoles: PropTypes.func.isRequired,
+};
+
+export default AccessList;
diff --git a/src/components/AccessList/index.js b/src/components/AccessList/index.js
new file mode 100644
index 0000000000..f435e8bb1f
--- /dev/null
+++ b/src/components/AccessList/index.js
@@ -0,0 +1,3 @@
+import AccessList from './Access.list';
+
+export default AccessList;
diff --git a/src/components/DataListToolbar/DataListToolbar.jsx b/src/components/DataListToolbar/DataListToolbar.jsx
index a84f086a00..074b804e58 100644
--- a/src/components/DataListToolbar/DataListToolbar.jsx
+++ b/src/components/DataListToolbar/DataListToolbar.jsx
@@ -24,7 +24,7 @@ import {
SortNumericDownIcon,
SortNumericUpIcon,
TrashAltIcon,
- PlusIcon
+ PlusIcon,
} from '@patternfly/react-icons';
import {
Link
@@ -37,6 +37,12 @@ const flexGrowStyling = {
flexGrow: '1'
};
+const ToolbarActiveStyle = {
+ backgroundColor: 'rgb(0, 123, 186)',
+ color: 'white',
+ padding: '0 5px',
+};
+
class DataListToolbar extends React.Component {
constructor (props) {
super(props);
@@ -56,6 +62,18 @@ class DataListToolbar extends React.Component {
this.onSearchDropdownSelect = this.onSearchDropdownSelect.bind(this);
this.onSearch = this.onSearch.bind(this);
this.onSort = this.onSort.bind(this);
+ this.onExpand = this.onExpand.bind(this);
+ this.onCompact = this.onCompact.bind(this);
+ }
+
+ onExpand () {
+ const { onExpand } = this.props;
+ onExpand();
+ }
+
+ onCompact () {
+ const { onCompact } = this.props;
+ onCompact();
}
onSortDropdownToggle (isSortDropdownOpen) {
@@ -114,7 +132,8 @@ class DataListToolbar extends React.Component {
showExpandCollapse,
showDelete,
showSelectAll,
- isLookup
+ isLookup,
+ isCompact,
} = this.props;
const {
isSearchDropdownOpen,
@@ -246,16 +265,20 @@ class DataListToolbar extends React.Component {
{ (showDelete || addUrl) && (
@@ -306,6 +329,7 @@ DataListToolbar.propTypes = {
onSelectAll: PropTypes.func,
onSort: PropTypes.func,
showDelete: PropTypes.bool,
+ showExpandCollapse: PropTypes.bool,
showSelectAll: PropTypes.bool,
sortOrder: PropTypes.string,
sortedColumnKey: PropTypes.string,
@@ -317,6 +341,7 @@ DataListToolbar.defaultProps = {
onSelectAll: null,
onSort: null,
showDelete: false,
+ showExpandCollapse: false,
showSelectAll: false,
sortOrder: 'ascending',
sortedColumnKey: 'name',
diff --git a/src/components/NotificationsList/Notifications.list.jsx b/src/components/NotificationsList/Notifications.list.jsx
index 38f37044ce..1fe385823d 100644
--- a/src/components/NotificationsList/Notifications.list.jsx
+++ b/src/components/NotificationsList/Notifications.list.jsx
@@ -335,7 +335,7 @@ class Notifications extends Component {
}
}
-Notifications.propType = {
+Notifications.propTypes = {
getError: PropTypes.func.isRequired,
getNotifications: PropTypes.func.isRequired,
getSuccess: PropTypes.func.isRequired,
diff --git a/src/pages/Organizations/screens/Organization/OrganizationAccess.jsx b/src/pages/Organizations/screens/Organization/OrganizationAccess.jsx
new file mode 100644
index 0000000000..969bb4a370
--- /dev/null
+++ b/src/pages/Organizations/screens/Organization/OrganizationAccess.jsx
@@ -0,0 +1,55 @@
+import React from 'react';
+import AccessList from '../../../../components/AccessList/Access.list';
+
+class OrganizationAccess extends React.Component {
+ constructor (props) {
+ super(props);
+
+ this.getOrgAccessList = this.getOrgAccessList.bind(this);
+ this.getUserRoles = this.getUserRoles.bind(this);
+ this.getUserTeams = this.getUserTeams.bind(this);
+ this.getTeamRoles = this.getTeamRoles.bind(this);
+ }
+
+ getOrgAccessList (id, params) {
+ const { api } = this.props;
+ return api.getOrganzationAccessList(id, params);
+ }
+
+ getUserRoles (id) {
+ const { api } = this.props;
+ return api.getUserRoles(id);
+ }
+
+ getUserTeams (id) {
+ const { api } = this.props;
+ return api.getUserTeams(id);
+ }
+
+ getTeamRoles (id) {
+ const { api } = this.props;
+ return api.getTeamRoles(id);
+ }
+
+ render () {
+ const {
+ location,
+ match,
+ history,
+ } = this.props;
+
+ return (
+
+ );
+ }
+}
+
+export default OrganizationAccess;