Skip to content

Commit

Permalink
TableList improvements (Graylog2#4579)
Browse files Browse the repository at this point in the history
* Show count of selected items in header

This provides some feedback when users select items in the list.

* Show header actions also if 1 element is selected

This is not different than the case when user selects two or more elements
and it makes easier to figure out what the UI does.

* Wrap TableList bulk actions inside component

Until now we positioned the individual actions inside the component but
not the bulk actions. Moving it inside the component makes it more
consistent.

* Update selection when filter changes

When some items are selected, changes in the filter should also affect
the selection, deselecting items that are filtered out from the list.
selection

* Improve select all selection

- Set the select all checkbox as indeterminate when some but not all
items are checked
- Remove the select all checked status from the component state

* Extract function recalculating selected items

Make clearer how we recalculate selected items on filter change by
extracting the code into a function.

* Correct data passed to TypeAheadDataFilter

We require the items prop to be an instance of `Immutable.List`, but the
code doesn't treat it like that. This commit ensures we convert the list
back to a JS array before using it in the filter.

* Refactor code in componentDidUpdate

Move code setting the "select all" checkbox status to indeterminate into
its own function.

* Disable "select all" checkbox if there's no data

* Do not hide filter and header when no data

Having no items to display should not hide UI elements from the view.

* Update filters and selections when items change

* Enable users to filter data from TableList header

When no elements are selected, display filter elements that may filter
the data outside of the component.

* Add prop to hide TableList filter

* Add ControlledTableList

This is meant to provide a more customizable UI, only providing the
basis to build a TableList.

* Move styling to a better place

* Wrap string headers

Ensure giving a string header is displayed properly. Consumers providing
their own nodes must do this on their own.

* Render TableList using ControlledTableList

* Remove unused ControlledTableList.css

* Remove headerFiltersFactory

We need this functionality but the `TableList` component is becoming
more complicated. In the end, consumers needing a fully customizable
`TableList` should use `ControlledTableList` and customize the
components as needed on a case by case basis.

* Rename headerActionsFactory with bulkActionsFactory

That makes clearer what the prop is for, in the end where elements are
rendered is an implementation detail.

* Add prop to disable bulk actions

This also removes checkboxes from items, since there is no need for
selecting them when actions can only affect an element.

* Use consistent naming for enable/disable flags

Rename `isFilterEnabled` to `enableFilter`.

* Use all space available for filter

In certain screen sizes the filter buttons cannot fit on its column. As
we currently don't use the space after the filter, here we take all
available space to ensure the filter buttons will be aligned with the
input.

* Add more specificity to group header style

* Adapt PermissionSelector to changes in TableList

* Correct description after problem with conflict

After merge-conflicts, the description was duplicated.

* Disable bulk actions in TokenList

* Update test snapshots

* Ensure we pass Immutable JS lists down

* Fix ImmutableJS proptypes checking
  • Loading branch information
edmundoa authored and Marius Sturm committed Feb 14, 2018
1 parent a889506 commit 60406c5
Show file tree
Hide file tree
Showing 16 changed files with 984 additions and 825 deletions.
1 change: 1 addition & 0 deletions graylog2-web-interface/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
"less": "^2.5.3",
"less-loader": "^4.0.5",
"phantomjs-prebuilt": ">=1.9",
"react-immutable-proptypes": "^2.1.0",
"react-proxy-loader": "^0.3.4",
"react-styleguidist": "^6.0.33",
"react-test-renderer": "^15.6.1",
Expand Down
9 changes: 0 additions & 9 deletions graylog2-web-interface/public/stylesheets/graylog2.less
Original file line number Diff line number Diff line change
Expand Up @@ -2515,15 +2515,6 @@ i.error-icon {
width: auto;
}

.list-group-header {
background-color: #F1F2F2;
padding: 0 15px;

.form-group {
margin: 0;
}
}

.form-group-inline {
display: inline-block;
margin: 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import { ListGroup } from 'react-bootstrap';

import ControlledTableListHeader from './ControlledTableListHeader';
import ControlledTableListItem from './ControlledTableListItem';

const ControlledTableList = createReactClass({
propTypes: {
children: PropTypes.node.isRequired,
},

render() {
const { children } = this.props;
let effectiveChildren;

if (children.length === 0) {
effectiveChildren = <ControlledTableListItem>No items to display</ControlledTableListItem>;
} else {
effectiveChildren = children;
}

return (
<div>
<ListGroup>
{effectiveChildren}
</ListGroup>
</div>
);
},
});

ControlledTableList.Header = ControlledTableListHeader;
ControlledTableList.Item = ControlledTableListItem;

export default ControlledTableList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
```js
const createReactClass = require('create-react-class');
const Immutable = require('immutable');
const { Col, Row } = require('react-bootstrap');

const ControlledTableListExample = createReactClass({
getInitialState() {
return {
items: Immutable.List([
{ id: '1', title: 'One', secret_key: 'uno', description: 'First number' },
{ id: '2', title: 'Two', secret_key: 'dos', description: 'Second number' },
{ id: '3', title: 'Three', secret_key: 'tres', description: 'Third number' },
{ id: '4', title: 'Four', secret_key: 'cuatro', description: 'Fourth number' },
{ id: '5', title: 'Five', secret_key: 'cinco', description: 'Fifth number' },
]),
};
},

formatItems(items) {
return items.map(item => {
return (
<ControlledTableList.Item key={item.id}>
<Row className="row-sm">
<Col md={12}>
<h5>{item.title} <small>{item.description}</small></h5>
</Col>
</Row>
<Row className="row-sm">
<Col md={12}>
#{item.id}
</Col>
</Row>
</ControlledTableList.Item>
)
});
},

render() {
const { items } = this.state;

return (
<ControlledTableList>
<ControlledTableList.Header>
Numbers
</ControlledTableList.Header>
{this.formatItems(items)}
</ControlledTableList>
);
},
});

<ControlledTableListExample />
```

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
:local(.listGroupHeader).list-group-item {
background-color: #F1F2F2;
font-size: 14px;
padding: 0 15px;
}

:local(.listGroupHeader) .form-group {
margin: 0;
}

:local(.headerWrapper) {
margin: 10px 0;
min-height: 20px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';

import { ListGroupItem } from 'react-bootstrap';

import style from './ControlledTableListHeader.css';

const ControlledTableListHeader = createReactClass({
propTypes: {
children: PropTypes.node,
},

getDefaultProps() {
return {
children: '',
};
},

// We wrap string children to ensure they are displayed properly in the header
wrapStringChildren(text) {
return <div className={style.headerWrapper}>{text}</div>;
},

render() {
const { children } = this.props;

const header = typeof children === 'string' ? this.wrapStringChildren(children) : children;
return <ListGroupItem className={style.listGroupHeader}>{header}</ListGroupItem>;
},
});

export default ControlledTableListHeader;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';

import { ListGroupItem } from 'react-bootstrap';

const ControlledTableListItem = createReactClass({
propTypes: {
children: PropTypes.node.isRequired,
},

render() {
return (
<ListGroupItem>
{this.props.children}
</ListGroupItem>
);
},
});

export default ControlledTableListItem;
27 changes: 27 additions & 0 deletions graylog2-web-interface/src/components/common/TableList.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
:local(.headerComponentsWrapper) {
float: right;
margin-top: 10px;
}

:local(.itemActionsWrapper) {
float: right;
margin-top: 10px;
}

:local(.itemWrapper) .header {
font-size: 14px;
padding: 10px 0;
min-height: 20px;
}

:local(.itemWrapper):not(:local(.itemWrapperStatic)) .description {
margin-left: 20px;
}

:local(.itemWrapper) .form-group {
margin-bottom: 0;
}

:local(.itemWrapperStatic) {
margin-left: 0;
}
Loading

0 comments on commit 60406c5

Please sign in to comment.