mirror of
https://github.com/ansible/awx.git
synced 2024-11-01 08:21:15 +03:00
Merge pull request #28 from jlmitch5/orgUrls
add routes and breadcrumbs for org detail/edit/related routes
This commit is contained in:
commit
8f54ec681d
@ -4,8 +4,6 @@ import DataListToolbar from '../../src/components/DataListToolbar';
|
|||||||
|
|
||||||
describe('<DataListToolbar />', () => {
|
describe('<DataListToolbar />', () => {
|
||||||
const columns = [{ name: 'Name', key: 'name', isSortable: true }];
|
const columns = [{ name: 'Name', key: 'name', isSortable: true }];
|
||||||
const noop = () => {};
|
|
||||||
|
|
||||||
let toolbar;
|
let toolbar;
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
55
__tests__/components/Tooltip.test.jsx
Normal file
55
__tests__/components/Tooltip.test.jsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import Tooltip from '../../src/components/Tooltip';
|
||||||
|
|
||||||
|
describe('<Tooltip />', () => {
|
||||||
|
let elem;
|
||||||
|
let content;
|
||||||
|
let mouseOverHandler;
|
||||||
|
let mouseOutHandler;
|
||||||
|
|
||||||
|
test('initially renders without crashing', () => {
|
||||||
|
elem = mount(<Tooltip />);
|
||||||
|
expect(elem.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('shows/hides with mouse over and leave', () => {
|
||||||
|
elem = mount(<Tooltip />);
|
||||||
|
mouseOverHandler = elem.find('.mouseOverHandler');
|
||||||
|
mouseOutHandler = elem.find('.mouseOutHandler');
|
||||||
|
expect(elem.state().isDisplayed).toBe(false);
|
||||||
|
elem.update();
|
||||||
|
content = elem.find('.pf-c-tooltip__content');
|
||||||
|
expect(content.length).toBe(0);
|
||||||
|
mouseOverHandler.props().onMouseOver();
|
||||||
|
expect(elem.state().isDisplayed).toBe(true);
|
||||||
|
elem.update();
|
||||||
|
content = elem.find('.pf-c-tooltip__content');
|
||||||
|
expect(content.length).toBe(1);
|
||||||
|
mouseOutHandler.props().onMouseLeave();
|
||||||
|
expect(elem.state().isDisplayed).toBe(false);
|
||||||
|
elem.update();
|
||||||
|
content = elem.find('.pf-c-tooltip__content');
|
||||||
|
expect(content.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('shows/hides with focus and blur', () => {
|
||||||
|
elem = mount(<Tooltip />);
|
||||||
|
mouseOverHandler = elem.find('.mouseOverHandler');
|
||||||
|
mouseOutHandler = elem.find('.mouseOutHandler');
|
||||||
|
expect(elem.state().isDisplayed).toBe(false);
|
||||||
|
elem.update();
|
||||||
|
content = elem.find('.pf-c-tooltip__content');
|
||||||
|
expect(content.length).toBe(0);
|
||||||
|
mouseOverHandler.props().onFocus();
|
||||||
|
expect(elem.state().isDisplayed).toBe(true);
|
||||||
|
elem.update();
|
||||||
|
content = elem.find('.pf-c-tooltip__content');
|
||||||
|
expect(content.length).toBe(1);
|
||||||
|
mouseOutHandler.props().onBlur();
|
||||||
|
expect(elem.state().isDisplayed).toBe(false);
|
||||||
|
elem.update();
|
||||||
|
content = elem.find('.pf-c-tooltip__content');
|
||||||
|
expect(content.length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
@ -1,90 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { HashRouter } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { mount } from 'enzyme';
|
|
||||||
|
|
||||||
import api from '../../src/api';
|
|
||||||
import { API_ORGANIZATIONS } from '../../src/endpoints';
|
|
||||||
import Organizations from '../../src/pages/Organizations';
|
|
||||||
|
|
||||||
describe('<Organizations />', () => {
|
|
||||||
let pageWrapper;
|
|
||||||
|
|
||||||
const results = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
name: 'org 1',
|
|
||||||
summary_fields: {
|
|
||||||
related_field_counts: {
|
|
||||||
users: 1,
|
|
||||||
teams: 1,
|
|
||||||
admins: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: 'org 2',
|
|
||||||
summary_fields: {
|
|
||||||
related_field_counts: {
|
|
||||||
users: 1,
|
|
||||||
teams: 1,
|
|
||||||
admins: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
name: 'org 3',
|
|
||||||
summary_fields: {
|
|
||||||
related_field_counts: {
|
|
||||||
users: 1,
|
|
||||||
teams: 1,
|
|
||||||
admins: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const count = results.length;
|
|
||||||
const response = { data: { count, results } };
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
api.get = jest.fn().mockImplementation(() => Promise.resolve(response));
|
|
||||||
pageWrapper = mount(<HashRouter><Organizations /></HashRouter>);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
pageWrapper.unmount();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('it renders expected content', () => {
|
|
||||||
const pageSections = pageWrapper.find('PageSection');
|
|
||||||
const title = pageWrapper.find('Title');
|
|
||||||
|
|
||||||
expect(pageWrapper.length).toBe(1);
|
|
||||||
expect(pageSections.length).toBe(2);
|
|
||||||
expect(title.length).toBe(1);
|
|
||||||
expect(title.props().size).toBe('2xl');
|
|
||||||
pageSections.forEach(section => {
|
|
||||||
expect(section.props().variant).toBeDefined();
|
|
||||||
});
|
|
||||||
expect(pageWrapper.find('ul').length).toBe(1);
|
|
||||||
expect(pageWrapper.find('ul li').length).toBe(0);
|
|
||||||
// will render all list items on update
|
|
||||||
pageWrapper.update();
|
|
||||||
expect(pageWrapper.find('ul li').length).toBe(count);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('API Organization endpoint is valid', () => {
|
|
||||||
expect(API_ORGANIZATIONS).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('it displays a tooltip on delete hover', () => {
|
|
||||||
const tooltip = '.pf-c-tooltip__content';
|
|
||||||
const deleteButton = 'button[aria-label="Delete"]';
|
|
||||||
|
|
||||||
expect(pageWrapper.find(tooltip).length).toBe(0);
|
|
||||||
pageWrapper.find(deleteButton).simulate('mouseover');
|
|
||||||
expect(pageWrapper.find(tooltip).length).toBe(1);
|
|
||||||
});
|
|
||||||
});
|
|
@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import OrganizationBreadcrumb from '../../../../src/pages/Organizations/components/OrganizationBreadcrumb';
|
||||||
|
|
||||||
|
describe('<OrganizationBreadcrumb />', () => {
|
||||||
|
test('initially renders succesfully', () => {
|
||||||
|
mount(
|
||||||
|
<MemoryRouter initialEntries={['/organizations']} initialIndex={0}>
|
||||||
|
<OrganizationBreadcrumb
|
||||||
|
match={{ path: '/organizations', url: '/organizations' }}
|
||||||
|
location={{ search: '', pathname: '/organizations' }}
|
||||||
|
parentObj={[{ name: 'Organizations', url: '/organizations' }]}
|
||||||
|
/>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import OrganizationDetail from '../../../../src/pages/Organizations/components/OrganizationDetail';
|
||||||
|
|
||||||
|
describe('<OrganizationDetail />', () => {
|
||||||
|
test('initially renders succesfully', () => {
|
||||||
|
mount(
|
||||||
|
<MemoryRouter initialEntries={['/organizations/1']} initialIndex={0}>
|
||||||
|
<OrganizationDetail
|
||||||
|
match={{ path: '/organizations/:id', url: '/organizations/1' }}
|
||||||
|
location={{ search: '', pathname: '/organizations/1' }}
|
||||||
|
/>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import OrganizationEdit from '../../../../src/pages/Organizations/components/OrganizationEdit';
|
||||||
|
|
||||||
|
describe('<OrganizationEdit />', () => {
|
||||||
|
test('initially renders succesfully', () => {
|
||||||
|
mount(
|
||||||
|
<MemoryRouter initialEntries={['/organizations/1/edit']} initialIndex={0}>
|
||||||
|
<OrganizationEdit
|
||||||
|
match={{ path: '/organizations/:id/edit', url: '/organizations/1/edit', params: { id: 1 } }}
|
||||||
|
/>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import OrganizationListItem from '../../../../src/pages/Organizations/components/OrganizationListItem';
|
||||||
|
|
||||||
|
describe('<OrganizationListItem />', () => {
|
||||||
|
test('initially renders succesfully', () => {
|
||||||
|
mount(
|
||||||
|
<MemoryRouter initialEntries={['/organizations']} initialIndex={0}>
|
||||||
|
<OrganizationListItem />
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
17
__tests__/pages/Organizations/index.test.jsx
Normal file
17
__tests__/pages/Organizations/index.test.jsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import Organizations from '../../../src/pages/Organizations/index';
|
||||||
|
|
||||||
|
describe('<Organizations />', () => {
|
||||||
|
test('initially renders succesfully', () => {
|
||||||
|
mount(
|
||||||
|
<MemoryRouter initialEntries={['/organizations']} initialIndex={0}>
|
||||||
|
<Organizations
|
||||||
|
match={{ path: '/organizations', url: '/organizations' }}
|
||||||
|
location={{ search: '', pathname: '/organizations' }}
|
||||||
|
/>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
13
__tests__/pages/Organizations/utils.test.jsx
Normal file
13
__tests__/pages/Organizations/utils.test.jsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import getTabName from '../../../src/pages/Organizations/utils';
|
||||||
|
|
||||||
|
describe('getTabName', () => {
|
||||||
|
test('returns tab name', () => {
|
||||||
|
expect(getTabName('details')).toBe('Details');
|
||||||
|
expect(getTabName('users')).toBe('Users');
|
||||||
|
expect(getTabName('teams')).toBe('Teams');
|
||||||
|
expect(getTabName('admins')).toBe('Admins');
|
||||||
|
expect(getTabName('notifications')).toBe('Notifications');
|
||||||
|
expect(getTabName('unknown')).toBe('');
|
||||||
|
expect(getTabName()).toBe('');
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import OrganizationAdd from '../../../../src/pages/Organizations/views/Organization.add';
|
||||||
|
|
||||||
|
describe('<OrganizationAdd />', () => {
|
||||||
|
test('initially renders succesfully', () => {
|
||||||
|
mount(
|
||||||
|
<OrganizationAdd
|
||||||
|
match={{ path: '/organizations/add', url: '/organizations/add' }}
|
||||||
|
location={{ search: '', pathname: '/organizations/add' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import OrganizationView from '../../../../src/pages/Organizations/views/Organization.view';
|
||||||
|
|
||||||
|
describe('<OrganizationView />', () => {
|
||||||
|
test('initially renders succesfully', () => {
|
||||||
|
mount(
|
||||||
|
<MemoryRouter initialEntries={['/organizations/1']} initialIndex={0}>
|
||||||
|
<OrganizationView
|
||||||
|
match={{ path: '/organizations/:id', url: '/organizations/1' }}
|
||||||
|
location={{ search: '', pathname: '/organizations/1' }}
|
||||||
|
/>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import OrganizationsList from '../../../../src/pages/Organizations/views/Organizations.list';
|
||||||
|
|
||||||
|
describe('<OrganizationsList />', () => {
|
||||||
|
test('initially renders succesfully', () => {
|
||||||
|
mount(
|
||||||
|
<MemoryRouter initialEntries={['/organizations']} initialIndex={0}>
|
||||||
|
<OrganizationsList
|
||||||
|
match={{ path: '/organizations', url: '/organizations' }}
|
||||||
|
location={{ search: '', pathname: '/organizations' }}
|
||||||
|
/>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@ -5,7 +5,7 @@
|
|||||||
"main": "index.jsx",
|
"main": "index.jsx",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "webpack-dev-server --config ./webpack.config.js --mode development",
|
"start": "webpack-dev-server --config ./webpack.config.js --mode development",
|
||||||
"test": "jest --watchAll --coverage",
|
"test": "jest --watch --coverage",
|
||||||
"lint": "./node_modules/eslint/bin/eslint.js src/**/*.js src/**/*.jsx"
|
"lint": "./node_modules/eslint/bin/eslint.js src/**/*.js src/**/*.jsx"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
|
@ -6,8 +6,6 @@ import {
|
|||||||
DropdownPosition,
|
DropdownPosition,
|
||||||
DropdownToggle,
|
DropdownToggle,
|
||||||
DropdownItem,
|
DropdownItem,
|
||||||
FormGroup,
|
|
||||||
KebabToggle,
|
|
||||||
Level,
|
Level,
|
||||||
LevelItem,
|
LevelItem,
|
||||||
TextInput,
|
TextInput,
|
||||||
@ -24,11 +22,14 @@ import {
|
|||||||
SortNumericUpIcon,
|
SortNumericUpIcon,
|
||||||
TrashAltIcon,
|
TrashAltIcon,
|
||||||
} from '@patternfly/react-icons';
|
} from '@patternfly/react-icons';
|
||||||
|
import {
|
||||||
|
Link
|
||||||
|
} from 'react-router-dom';
|
||||||
|
|
||||||
import Tooltip from '../Tooltip';
|
import Tooltip from '../Tooltip';
|
||||||
|
|
||||||
class DataListToolbar extends React.Component {
|
class DataListToolbar extends React.Component {
|
||||||
constructor(props) {
|
constructor (props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
const { sortedColumnKey } = this.props;
|
const { sortedColumnKey } = this.props;
|
||||||
@ -72,15 +73,7 @@ class DataListToolbar extends React.Component {
|
|||||||
this.setState({ isSearchDropdownOpen: false, searchKey: key });
|
this.setState({ isSearchDropdownOpen: false, searchKey: key });
|
||||||
};
|
};
|
||||||
|
|
||||||
onActionToggle = isActionDropdownOpen => {
|
render () {
|
||||||
this.setState({ isActionDropdownOpen });
|
|
||||||
};
|
|
||||||
|
|
||||||
onActionSelect = ({ target }) => {
|
|
||||||
this.setState({ isActionDropdownOpen: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { up } = DropdownPosition;
|
const { up } = DropdownPosition;
|
||||||
const {
|
const {
|
||||||
columns,
|
columns,
|
||||||
@ -90,9 +83,10 @@ class DataListToolbar extends React.Component {
|
|||||||
onSort,
|
onSort,
|
||||||
sortedColumnKey,
|
sortedColumnKey,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
|
addUrl
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const {
|
const {
|
||||||
isActionDropdownOpen,
|
// isActionDropdownOpen,
|
||||||
isSearchDropdownOpen,
|
isSearchDropdownOpen,
|
||||||
isSortDropdownOpen,
|
isSortDropdownOpen,
|
||||||
searchKey,
|
searchKey,
|
||||||
@ -107,19 +101,29 @@ class DataListToolbar extends React.Component {
|
|||||||
.filter(({ key }) => key === sortedColumnKey);
|
.filter(({ key }) => key === sortedColumnKey);
|
||||||
const sortedColumnName = sortedColumn.name;
|
const sortedColumnName = sortedColumn.name;
|
||||||
const isSortNumeric = sortedColumn.isNumeric;
|
const isSortNumeric = sortedColumn.isNumeric;
|
||||||
|
const displayedSortIcon = () => {
|
||||||
|
let icon;
|
||||||
|
if (sortOrder === 'ascending') {
|
||||||
|
icon = isSortNumeric ? (<SortNumericUpIcon />) : (<SortAlphaUpIcon />);
|
||||||
|
} else {
|
||||||
|
icon = isSortNumeric ? (<SortNumericDownIcon />) : (<SortAlphaDownIcon />);
|
||||||
|
}
|
||||||
|
return icon;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="awx-toolbar">
|
<div className="awx-toolbar">
|
||||||
<Level>
|
<Level>
|
||||||
<LevelItem>
|
<LevelItem>
|
||||||
<Toolbar style={{ marginLeft: "20px" }}>
|
<Toolbar style={{ marginLeft: '20px' }}>
|
||||||
<ToolbarGroup>
|
<ToolbarGroup>
|
||||||
<ToolbarItem>
|
<ToolbarItem>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={isAllSelected}
|
checked={isAllSelected}
|
||||||
onChange={onSelectAll}
|
onChange={onSelectAll}
|
||||||
aria-label="Select all"
|
aria-label="Select all"
|
||||||
id="select-all"/>
|
id="select-all"
|
||||||
|
/>
|
||||||
</ToolbarItem>
|
</ToolbarItem>
|
||||||
</ToolbarGroup>
|
</ToolbarGroup>
|
||||||
<ToolbarGroup>
|
<ToolbarGroup>
|
||||||
@ -132,10 +136,12 @@ class DataListToolbar extends React.Component {
|
|||||||
isOpen={isSearchDropdownOpen}
|
isOpen={isSearchDropdownOpen}
|
||||||
toggle={(
|
toggle={(
|
||||||
<DropdownToggle
|
<DropdownToggle
|
||||||
onToggle={this.onSearchDropdownToggle}>
|
onToggle={this.onSearchDropdownToggle}
|
||||||
|
>
|
||||||
{ searchColumnName }
|
{ searchColumnName }
|
||||||
</DropdownToggle>
|
</DropdownToggle>
|
||||||
)}>
|
)}
|
||||||
|
>
|
||||||
{columns.filter(({ key }) => key !== searchKey).map(({ key, name }) => (
|
{columns.filter(({ key }) => key !== searchKey).map(({ key, name }) => (
|
||||||
<DropdownItem key={key} component="button">
|
<DropdownItem key={key} component="button">
|
||||||
{ name }
|
{ name }
|
||||||
@ -146,12 +152,14 @@ class DataListToolbar extends React.Component {
|
|||||||
type="search"
|
type="search"
|
||||||
aria-label="search text input"
|
aria-label="search text input"
|
||||||
value={searchValue}
|
value={searchValue}
|
||||||
onChange={this.handleSearchInputChange}/>
|
onChange={this.handleSearchInputChange}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="tertiary"
|
variant="tertiary"
|
||||||
aria-label="Search"
|
aria-label="Search"
|
||||||
onClick={() => onSearch(searchValue)}>
|
onClick={() => onSearch(searchValue)}
|
||||||
<i className="fas fa-search" aria-hidden="true"></i>
|
>
|
||||||
|
<i className="fas fa-search" aria-hidden="true" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</ToolbarItem>
|
</ToolbarItem>
|
||||||
@ -165,31 +173,28 @@ class DataListToolbar extends React.Component {
|
|||||||
isOpen={isSortDropdownOpen}
|
isOpen={isSortDropdownOpen}
|
||||||
toggle={(
|
toggle={(
|
||||||
<DropdownToggle
|
<DropdownToggle
|
||||||
onToggle={this.onSortDropdownToggle}>
|
onToggle={this.onSortDropdownToggle}
|
||||||
|
>
|
||||||
{ sortedColumnName }
|
{ sortedColumnName }
|
||||||
</DropdownToggle>
|
</DropdownToggle>
|
||||||
)}>
|
)}
|
||||||
|
>
|
||||||
{columns
|
{columns
|
||||||
.filter(({ key, isSortable }) => isSortable && key !== sortedColumnKey)
|
.filter(({ key, isSortable }) => isSortable && key !== sortedColumnKey)
|
||||||
.map(({ key, name }) => (
|
.map(({ key, name }) => (
|
||||||
<DropdownItem key={key} component="button">
|
<DropdownItem key={key} component="button">
|
||||||
{ name }
|
{ name }
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
))}
|
))}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</ToolbarItem>
|
</ToolbarItem>
|
||||||
<ToolbarItem>
|
<ToolbarItem>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => onSort(sortedColumnKey, sortOrder === "ascending" ? "descending" : "ascending")}
|
onClick={() => onSort(sortedColumnKey, sortOrder === 'ascending' ? 'descending' : 'ascending')}
|
||||||
variant="plain"
|
variant="plain"
|
||||||
aria-label="Sort">
|
aria-label="Sort"
|
||||||
{
|
>
|
||||||
isSortNumeric ? (
|
{displayedSortIcon()}
|
||||||
sortOrder === "ascending" ? <SortNumericUpIcon /> : <SortNumericDownIcon />
|
|
||||||
) : (
|
|
||||||
sortOrder === "ascending" ? <SortAlphaUpIcon /> : <SortAlphaDownIcon />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</Button>
|
</Button>
|
||||||
</ToolbarItem>
|
</ToolbarItem>
|
||||||
</ToolbarGroup>
|
</ToolbarGroup>
|
||||||
@ -210,12 +215,16 @@ class DataListToolbar extends React.Component {
|
|||||||
<LevelItem>
|
<LevelItem>
|
||||||
<Tooltip message="Delete" position="top">
|
<Tooltip message="Delete" position="top">
|
||||||
<Button variant="plain" aria-label="Delete">
|
<Button variant="plain" aria-label="Delete">
|
||||||
<TrashAltIcon/>
|
<TrashAltIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Button variant="primary" aria-label="Add">
|
{addUrl && (
|
||||||
Add
|
<Link to={addUrl}>
|
||||||
</Button>
|
<Button variant="primary" aria-label="Add">
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
</LevelItem>
|
</LevelItem>
|
||||||
</Level>
|
</Level>
|
||||||
</div>
|
</div>
|
||||||
@ -223,4 +232,4 @@ class DataListToolbar extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DataListToolbar;
|
export default DataListToolbar;
|
||||||
|
@ -3,36 +3,36 @@ import React from 'react';
|
|||||||
class Tooltip extends React.Component {
|
class Tooltip extends React.Component {
|
||||||
transforms = {
|
transforms = {
|
||||||
top: {
|
top: {
|
||||||
bottom: "100%",
|
bottom: '100%',
|
||||||
left: "50%",
|
left: '50%',
|
||||||
transform: "translate(-50%, -25%)"
|
transform: 'translate(-50%, -25%)'
|
||||||
},
|
},
|
||||||
bottom: {
|
bottom: {
|
||||||
top: "100%",
|
top: '100%',
|
||||||
left: "50%",
|
left: '50%',
|
||||||
transform: "translate(-50%, 25%)"
|
transform: 'translate(-50%, 25%)'
|
||||||
},
|
},
|
||||||
left: {
|
left: {
|
||||||
top: "50%",
|
top: '50%',
|
||||||
right: "100%",
|
right: '100%',
|
||||||
transform: "translate(-25%, -50%)"
|
transform: 'translate(-25%, -50%)'
|
||||||
},
|
},
|
||||||
right: {
|
right: {
|
||||||
bottom: "100%",
|
bottom: '100%',
|
||||||
left: "50%",
|
left: '50%',
|
||||||
transform: "translate(25%, 50%)"
|
transform: 'translate(25%, 50%)'
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isDisplayed: false
|
isDisplayed: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render () {
|
||||||
const {
|
const {
|
||||||
children,
|
children,
|
||||||
message,
|
message,
|
||||||
@ -44,24 +44,33 @@ class Tooltip extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
style={{ position: "relative"}}
|
style={{ position: 'relative' }}
|
||||||
onMouseLeave={() => this.setState({ isDisplayed: false })}>
|
className="mouseOutHandler"
|
||||||
{ isDisplayed &&
|
onMouseLeave={() => this.setState({ isDisplayed: false })}
|
||||||
<div
|
onBlur={() => this.setState({ isDisplayed: false })}
|
||||||
style={{ position: "absolute", zIndex: "10", ...this.transforms[position] }}
|
>
|
||||||
className={`pf-c-tooltip pf-m-${position}`}>
|
{ isDisplayed
|
||||||
<div className="pf-c-tooltip__arrow"></div>
|
&& (
|
||||||
<div className="pf-c-tooltip__content">
|
<div
|
||||||
{ message }
|
style={{ position: 'absolute', zIndex: '10', ...this.transforms[position] }}
|
||||||
|
className={`pf-c-tooltip pf-m-${position}`}
|
||||||
|
>
|
||||||
|
<div className="pf-c-tooltip__arrow" />
|
||||||
|
<div className="pf-c-tooltip__content">
|
||||||
|
{ message }
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)
|
||||||
}
|
}
|
||||||
<span
|
<span
|
||||||
onMouseOver={() => this.setState({ isDisplayed: true })}>
|
className="mouseOverHandler"
|
||||||
|
onMouseOver={() => this.setState({ isDisplayed: true })}
|
||||||
|
onFocus={() => this.setState({ isDisplayed: true })}
|
||||||
|
>
|
||||||
{ children }
|
{ children }
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import {
|
||||||
|
PageSection,
|
||||||
|
PageSectionVariants,
|
||||||
|
Title,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
import {
|
||||||
|
Link
|
||||||
|
} from 'react-router-dom';
|
||||||
|
|
||||||
|
import getTabName from '../utils';
|
||||||
|
|
||||||
|
const OrganizationBreadcrumb = ({ parentObj, organization, currentTab, location }) => {
|
||||||
|
const { light } = PageSectionVariants;
|
||||||
|
let breadcrumb = '';
|
||||||
|
if (parentObj !== 'loading') {
|
||||||
|
const generateCrumb = (noLastLink = false) => (
|
||||||
|
<Fragment>
|
||||||
|
{parentObj
|
||||||
|
.map(({ url, name }, index) => {
|
||||||
|
let elem;
|
||||||
|
if (noLastLink && parentObj.length - 1 === index) {
|
||||||
|
elem = (<Fragment key={name}>{name}</Fragment>);
|
||||||
|
} else {
|
||||||
|
elem = (
|
||||||
|
<Link
|
||||||
|
key={name}
|
||||||
|
to={{ pathname: url, state: { breadcrumb: parentObj, organization } }}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return elem;
|
||||||
|
})
|
||||||
|
.reduce((prev, curr) => [prev, ' > ', curr])}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (currentTab && currentTab !== 'details') {
|
||||||
|
breadcrumb = (
|
||||||
|
<Fragment>
|
||||||
|
{generateCrumb()}
|
||||||
|
{' > '}
|
||||||
|
{getTabName(currentTab)}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
} else if (location.pathname.indexOf('edit') > -1) {
|
||||||
|
breadcrumb = (
|
||||||
|
<Fragment>
|
||||||
|
{generateCrumb()}
|
||||||
|
{' > edit'}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
} else if (location.pathname.indexOf('add') > -1) {
|
||||||
|
breadcrumb = (
|
||||||
|
<Fragment>
|
||||||
|
{generateCrumb()}
|
||||||
|
{' > add'}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
breadcrumb = (
|
||||||
|
<Fragment>
|
||||||
|
{generateCrumb(true)}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageSection variant={light} className="pf-m-condensed">
|
||||||
|
<Title size="2xl">{breadcrumb}</Title>
|
||||||
|
</PageSection>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OrganizationBreadcrumb;
|
140
src/pages/Organizations/components/OrganizationDetail.jsx
Normal file
140
src/pages/Organizations/components/OrganizationDetail.jsx
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardHeader,
|
||||||
|
CardBody,
|
||||||
|
PageSection,
|
||||||
|
PageSectionVariants,
|
||||||
|
ToolbarGroup,
|
||||||
|
ToolbarItem,
|
||||||
|
ToolbarSection,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
import {
|
||||||
|
Switch,
|
||||||
|
Link,
|
||||||
|
Route
|
||||||
|
} from 'react-router-dom';
|
||||||
|
|
||||||
|
import getTabName from '../utils';
|
||||||
|
|
||||||
|
import '../tabs.scss';
|
||||||
|
|
||||||
|
const DetailTab = ({ location, match, tab, currentTab, children, breadcrumb }) => {
|
||||||
|
const tabClasses = () => {
|
||||||
|
let classes = 'at-c-tabs__tab';
|
||||||
|
if (tab === currentTab) {
|
||||||
|
classes += ' at-m-selected';
|
||||||
|
}
|
||||||
|
|
||||||
|
return classes;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateTab = () => {
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
if (params.get('tab') !== undefined) {
|
||||||
|
params.set('tab', tab);
|
||||||
|
} else {
|
||||||
|
params.append('tab', tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `?${params.toString()}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolbarItem className={tabClasses()}>
|
||||||
|
<Link to={{ pathname: `${match.url}`, search: updateTab(), state: { breadcrumb } }} replace={tab === currentTab}>
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
</ToolbarItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const OrganizationDetail = ({
|
||||||
|
location,
|
||||||
|
match,
|
||||||
|
parentBreadcrumbObj,
|
||||||
|
organization,
|
||||||
|
params,
|
||||||
|
currentTab
|
||||||
|
}) => {
|
||||||
|
// TODO: set objectName by param or through grabbing org detail get from api
|
||||||
|
const { medium } = PageSectionVariants;
|
||||||
|
|
||||||
|
const deleteResourceView = () => (
|
||||||
|
<Fragment>
|
||||||
|
{`deleting ${currentTab} association with orgs `}
|
||||||
|
<Link to={{ pathname: `${match.url}`, search: `?${params.toString()}`, state: { breadcrumb: parentBreadcrumbObj, organization } }}>
|
||||||
|
{`confirm removal of ${currentTab}/cancel and go back to ${currentTab} view.`}
|
||||||
|
</Link>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
|
||||||
|
const addResourceView = () => (
|
||||||
|
<Fragment>
|
||||||
|
{`adding ${currentTab} `}
|
||||||
|
<Link to={{ pathname: `${match.url}`, search: `?${params.toString()}`, state: { breadcrumb: parentBreadcrumbObj, organization } }}>
|
||||||
|
{`save/cancel and go back to ${currentTab} view`}
|
||||||
|
</Link>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
|
||||||
|
const resourceView = () => (
|
||||||
|
<Fragment>
|
||||||
|
{`${currentTab} detail view `}
|
||||||
|
<Link to={{ pathname: `${match.url}/add-resource`, search: `?${params.toString()}`, state: { breadcrumb: parentBreadcrumbObj, organization } }}>
|
||||||
|
{`add ${currentTab}`}
|
||||||
|
</Link>
|
||||||
|
{' '}
|
||||||
|
<Link to={{ pathname: `${match.url}/delete-resources`, search: `?${params.toString()}`, state: { breadcrumb: parentBreadcrumbObj, organization } }}>
|
||||||
|
{`delete ${currentTab}`}
|
||||||
|
</Link>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
|
||||||
|
const detailTabs = (tabs) => (
|
||||||
|
<ToolbarSection aria-label="Organization detail tabs">
|
||||||
|
<ToolbarGroup className="at-c-tabs">
|
||||||
|
{tabs.map(tab => (
|
||||||
|
<DetailTab
|
||||||
|
key={tab}
|
||||||
|
tab={tab}
|
||||||
|
location={location}
|
||||||
|
match={match}
|
||||||
|
currentTab={currentTab}
|
||||||
|
breadcrumb={parentBreadcrumbObj}
|
||||||
|
>
|
||||||
|
{getTabName(tab)}
|
||||||
|
</DetailTab>
|
||||||
|
))}
|
||||||
|
</ToolbarGroup>
|
||||||
|
</ToolbarSection>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageSection variant={medium}>
|
||||||
|
<Card className="at-c-orgPane">
|
||||||
|
<CardHeader>
|
||||||
|
{detailTabs(['details', 'users', 'teams', 'admins', 'notifications'])}
|
||||||
|
</CardHeader>
|
||||||
|
<CardBody>
|
||||||
|
{(currentTab && currentTab !== 'details') ? (
|
||||||
|
<Switch>
|
||||||
|
<Route path={`${match.path}/delete-resources`} component={() => deleteResourceView()} />
|
||||||
|
<Route path={`${match.path}/add-resource`} component={() => addResourceView()} />
|
||||||
|
<Route path={`${match.path}`} component={() => resourceView()} />
|
||||||
|
</Switch>
|
||||||
|
) : (
|
||||||
|
<Fragment>
|
||||||
|
{'detail view '}
|
||||||
|
<Link to={{ pathname: `${match.url}/edit`, state: { breadcrumb: parentBreadcrumbObj, organization } }}>
|
||||||
|
{'edit'}
|
||||||
|
</Link>
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
</PageSection>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OrganizationDetail;
|
29
src/pages/Organizations/components/OrganizationEdit.jsx
Normal file
29
src/pages/Organizations/components/OrganizationEdit.jsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardBody,
|
||||||
|
PageSection,
|
||||||
|
PageSectionVariants
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
import {
|
||||||
|
Link
|
||||||
|
} from 'react-router-dom';
|
||||||
|
|
||||||
|
const OrganizationEdit = ({ match, parentBreadcrumbObj, organization }) => {
|
||||||
|
const { medium } = PageSectionVariants;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageSection variant={medium}>
|
||||||
|
<Card className="at-c-orgPane">
|
||||||
|
<CardBody>
|
||||||
|
{'edit view '}
|
||||||
|
<Link to={{ pathname: `/organizations/${match.params.id}`, state: { breadcrumb: parentBreadcrumbObj, organization } }}>
|
||||||
|
{'save/cancel and go back to view'}
|
||||||
|
</Link>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
</PageSection>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OrganizationEdit;
|
@ -3,8 +3,21 @@ import {
|
|||||||
Badge,
|
Badge,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
|
import {
|
||||||
|
Link
|
||||||
|
} from 'react-router-dom';
|
||||||
|
|
||||||
export default ({ itemId, name, userCount, teamCount, adminCount, isSelected, onSelect }) => (
|
export default ({
|
||||||
|
itemId,
|
||||||
|
name,
|
||||||
|
userCount,
|
||||||
|
teamCount,
|
||||||
|
adminCount,
|
||||||
|
isSelected,
|
||||||
|
onSelect,
|
||||||
|
detailUrl,
|
||||||
|
parentBreadcrumb
|
||||||
|
}) => (
|
||||||
<li key={itemId} className="pf-c-data-list__item" aria-labelledby="check-action-item1">
|
<li key={itemId} className="pf-c-data-list__item" aria-labelledby="check-action-item1">
|
||||||
<div className="pf-c-data-list__check">
|
<div className="pf-c-data-list__check">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@ -16,23 +29,36 @@ export default ({ itemId, name, userCount, teamCount, adminCount, isSelected, on
|
|||||||
</div>
|
</div>
|
||||||
<div className="pf-c-data-list__cell">
|
<div className="pf-c-data-list__cell">
|
||||||
<span id="check-action-item1">
|
<span id="check-action-item1">
|
||||||
<a href={`#/organizations/${itemId}`}>{ name }</a>
|
<Link
|
||||||
|
to={{
|
||||||
|
pathname: detailUrl,
|
||||||
|
state: { breadcrumb: [parentBreadcrumb, { name, url: detailUrl }] }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</Link>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="pf-c-data-list__cell">
|
<div className="pf-c-data-list__cell">
|
||||||
<a href="#/dashboard"> Users </a>
|
<Link to={`${detailUrl}?tab=users`}>
|
||||||
|
Users
|
||||||
|
</Link>
|
||||||
<Badge isRead>
|
<Badge isRead>
|
||||||
{' '}
|
{' '}
|
||||||
{userCount}
|
{userCount}
|
||||||
{' '}
|
{' '}
|
||||||
</Badge>
|
</Badge>
|
||||||
<a href="#/dashboard"> Teams </a>
|
<Link to={`${detailUrl}?tab=teams`}>
|
||||||
|
Teams
|
||||||
|
</Link>
|
||||||
<Badge isRead>
|
<Badge isRead>
|
||||||
{' '}
|
{' '}
|
||||||
{teamCount}
|
{teamCount}
|
||||||
{' '}
|
{' '}
|
||||||
</Badge>
|
</Badge>
|
||||||
<a href="#/dashboard"> Admins </a>
|
<Link to={`${detailUrl}?tab=admins`}>
|
||||||
|
Admins
|
||||||
|
</Link>
|
||||||
<Badge isRead>
|
<Badge isRead>
|
||||||
{' '}
|
{' '}
|
||||||
{adminCount}
|
{adminCount}
|
16
src/pages/Organizations/index.jsx
Normal file
16
src/pages/Organizations/index.jsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Route, Switch } from 'react-router-dom';
|
||||||
|
|
||||||
|
import OrganizationAdd from './views/Organization.add';
|
||||||
|
import OrganizationView from './views/Organization.view';
|
||||||
|
import OrganizationsList from './views/Organizations.list';
|
||||||
|
|
||||||
|
const Organizations = ({ match }) => (
|
||||||
|
<Switch>
|
||||||
|
<Route path={`${match.path}/add`} component={OrganizationAdd} />
|
||||||
|
<Route path={`${match.path}/:id`} component={OrganizationView} />
|
||||||
|
<Route path={`${match.path}`} component={OrganizationsList} />
|
||||||
|
</Switch>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Organizations;
|
18
src/pages/Organizations/tabs.scss
Normal file
18
src/pages/Organizations/tabs.scss
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
.at-c-tabs {
|
||||||
|
padding: 0 5px !important;
|
||||||
|
margin: 0 -10px !important;
|
||||||
|
|
||||||
|
.at-c-tabs__tab {
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.at-c-tabs__tab.at-m-selected {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.at-c-orgPane {
|
||||||
|
a {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
17
src/pages/Organizations/utils.jsx
Normal file
17
src/pages/Organizations/utils.jsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
const getTabName = (tab) => {
|
||||||
|
let tabName = '';
|
||||||
|
if (tab === 'details') {
|
||||||
|
tabName = 'Details';
|
||||||
|
} else if (tab === 'users') {
|
||||||
|
tabName = 'Users';
|
||||||
|
} else if (tab === 'teams') {
|
||||||
|
tabName = 'Teams';
|
||||||
|
} else if (tab === 'admins') {
|
||||||
|
tabName = 'Admins';
|
||||||
|
} else if (tab === 'notifications') {
|
||||||
|
tabName = 'Notifications';
|
||||||
|
}
|
||||||
|
return tabName;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getTabName;
|
21
src/pages/Organizations/views/Organization.add.jsx
Normal file
21
src/pages/Organizations/views/Organization.add.jsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import {
|
||||||
|
PageSection,
|
||||||
|
PageSectionVariants,
|
||||||
|
Title,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
|
||||||
|
const { light, medium } = PageSectionVariants;
|
||||||
|
|
||||||
|
const OrganizationView = () => (
|
||||||
|
<Fragment>
|
||||||
|
<PageSection variant={light} className="pf-m-condensed">
|
||||||
|
<Title size="2xl">Organization Add</Title>
|
||||||
|
</PageSection>
|
||||||
|
<PageSection variant={medium}>
|
||||||
|
This is the add view
|
||||||
|
</PageSection>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default OrganizationView;
|
120
src/pages/Organizations/views/Organization.view.jsx
Normal file
120
src/pages/Organizations/views/Organization.view.jsx
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import React, { Component, Fragment } from 'react';
|
||||||
|
import {
|
||||||
|
Switch,
|
||||||
|
Route
|
||||||
|
} from 'react-router-dom';
|
||||||
|
|
||||||
|
import OrganizationBreadcrumb from '../components/OrganizationBreadcrumb';
|
||||||
|
import OrganizationDetail from '../components/OrganizationDetail';
|
||||||
|
import OrganizationEdit from '../components/OrganizationEdit';
|
||||||
|
|
||||||
|
import api from '../../../api';
|
||||||
|
import { API_ORGANIZATIONS } from '../../../endpoints';
|
||||||
|
|
||||||
|
class OrganizationView extends Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
let { breadcrumb: parentBreadcrumbObj, organization } = props.location.state || {};
|
||||||
|
if (!parentBreadcrumbObj) {
|
||||||
|
parentBreadcrumbObj = 'loading';
|
||||||
|
}
|
||||||
|
if (!organization) {
|
||||||
|
organization = 'loading';
|
||||||
|
}
|
||||||
|
this.state = {
|
||||||
|
parentBreadcrumbObj,
|
||||||
|
organization,
|
||||||
|
error: false,
|
||||||
|
loading: false,
|
||||||
|
mounted: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.setState({ mounted: true }, () => {
|
||||||
|
const { organization } = this.state;
|
||||||
|
if (organization === 'loading') {
|
||||||
|
this.fetchOrganization();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
this.setState({ mounted: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchOrganization () {
|
||||||
|
const { mounted } = this.state;
|
||||||
|
if (mounted) {
|
||||||
|
this.setState({ error: false, loading: true });
|
||||||
|
|
||||||
|
const { match } = this.props;
|
||||||
|
const { parentBreadcrumbObj, organization } = this.state;
|
||||||
|
try {
|
||||||
|
const { data } = await api.get(`${API_ORGANIZATIONS}${match.params.id}/`);
|
||||||
|
if (organization === 'loading') {
|
||||||
|
this.setState({ organization: data });
|
||||||
|
}
|
||||||
|
const { name } = data;
|
||||||
|
if (parentBreadcrumbObj === 'loading') {
|
||||||
|
this.setState({ parentBreadcrumbObj: [{ name: 'Organizations', url: '/organizations' }, { name, url: match.url }] });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.setState({ error: true });
|
||||||
|
} finally {
|
||||||
|
this.setState({ loading: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { location, match } = this.props;
|
||||||
|
const { parentBreadcrumbObj, organization, error, loading } = this.state;
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
const currentTab = params.get('tab') || 'details';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<OrganizationBreadcrumb
|
||||||
|
parentObj={parentBreadcrumbObj}
|
||||||
|
currentTab={currentTab}
|
||||||
|
location={location}
|
||||||
|
organization={organization}
|
||||||
|
/>
|
||||||
|
<Switch>
|
||||||
|
<Route
|
||||||
|
path={`${match.path}/edit`}
|
||||||
|
component={() => (
|
||||||
|
<OrganizationEdit
|
||||||
|
location={location}
|
||||||
|
match={match}
|
||||||
|
parentBreadcrumbObj={parentBreadcrumbObj}
|
||||||
|
organization={organization}
|
||||||
|
params={params}
|
||||||
|
currentTab={currentTab}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={`${match.path}`}
|
||||||
|
component={() => (
|
||||||
|
<OrganizationDetail
|
||||||
|
location={location}
|
||||||
|
match={match}
|
||||||
|
parentBreadcrumbObj={parentBreadcrumbObj}
|
||||||
|
organization={organization}
|
||||||
|
params={params}
|
||||||
|
currentTab={currentTab}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Switch>
|
||||||
|
{error ? 'error!' : ''}
|
||||||
|
{loading ? 'loading...' : ''}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrganizationView;
|
@ -11,17 +11,17 @@ import {
|
|||||||
Title,
|
Title,
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
|
|
||||||
import DataListToolbar from '../components/DataListToolbar';
|
import DataListToolbar from '../../../components/DataListToolbar';
|
||||||
import OrganizationListItem from '../components/OrganizationListItem';
|
import OrganizationListItem from '../components/OrganizationListItem';
|
||||||
import Pagination from '../components/Pagination';
|
import Pagination from '../../../components/Pagination';
|
||||||
|
|
||||||
import api from '../api';
|
import api from '../../../api';
|
||||||
import { API_ORGANIZATIONS } from '../endpoints';
|
import { API_ORGANIZATIONS } from '../../../endpoints';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
encodeQueryString,
|
encodeQueryString,
|
||||||
parseQueryString,
|
parseQueryString,
|
||||||
} from '../qs';
|
} from '../../../qs';
|
||||||
|
|
||||||
class Organizations extends Component {
|
class Organizations extends Component {
|
||||||
columns = [
|
columns = [
|
||||||
@ -58,7 +58,6 @@ class Organizations extends Component {
|
|||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const queryParams = this.getQueryParams();
|
const queryParams = this.getQueryParams();
|
||||||
|
|
||||||
this.fetchOrganizations(queryParams);
|
this.fetchOrganizations(queryParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,7 +121,6 @@ class Organizations extends Component {
|
|||||||
|
|
||||||
updateUrl (queryParams) {
|
updateUrl (queryParams) {
|
||||||
const { history, location } = this.props;
|
const { history, location } = this.props;
|
||||||
|
|
||||||
const pathname = '/organizations';
|
const pathname = '/organizations';
|
||||||
const search = `?${encodeQueryString(queryParams)}`;
|
const search = `?${encodeQueryString(queryParams)}`;
|
||||||
|
|
||||||
@ -185,6 +183,8 @@ class Organizations extends Component {
|
|||||||
results,
|
results,
|
||||||
selected,
|
selected,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
const { match } = this.props;
|
||||||
|
const parentBreadcrumb = { name: 'Organizations', url: match.url };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
@ -193,6 +193,7 @@ class Organizations extends Component {
|
|||||||
</PageSection>
|
</PageSection>
|
||||||
<PageSection variant={medium}>
|
<PageSection variant={medium}>
|
||||||
<DataListToolbar
|
<DataListToolbar
|
||||||
|
addUrl={`${match.url}/add`}
|
||||||
isAllSelected={selected.length === results.length}
|
isAllSelected={selected.length === results.length}
|
||||||
sortedColumnKey={sortedColumnKey}
|
sortedColumnKey={sortedColumnKey}
|
||||||
sortOrder={sortOrder}
|
sortOrder={sortOrder}
|
||||||
@ -207,6 +208,8 @@ class Organizations extends Component {
|
|||||||
key={o.id}
|
key={o.id}
|
||||||
itemId={o.id}
|
itemId={o.id}
|
||||||
name={o.name}
|
name={o.name}
|
||||||
|
detailUrl={`${match.url}/${o.id}`}
|
||||||
|
parentBreadcrumb={parentBreadcrumb}
|
||||||
userCount={o.summary_fields.related_field_counts.users}
|
userCount={o.summary_fields.related_field_counts.users}
|
||||||
teamCount={o.summary_fields.related_field_counts.teams}
|
teamCount={o.summary_fields.related_field_counts.teams}
|
||||||
adminCount={o.summary_fields.related_field_counts.admins}
|
adminCount={o.summary_fields.related_field_counts.admins}
|
Loading…
Reference in New Issue
Block a user