basic/dataSegment/DataSegment.js
/**
* Created by pawelposel on 17/11/2016.
*/
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Form, Icon, Message } from 'semantic-ui-react';
import TableSearch from '../dataTable/TableSearch';
import Pagination from '../pagination/Pagination';
import SegmentAction from './SegmentAction';
import SegmentItem from './SegmentItem';
/**
* DataSegment component enables fetching data using predefined function and showing segmented data in a simple manner.
*
* It delivers alternative way of presenting fetched data to {@link DataTable}.
*
* ## Access
* `Stage.Basic.DataSegment`
*
* ## Features
* - data pagination
* - selectable segments
*
* ## Usage
*
* ## Simple example
* ![DataSegment_0](manual/asset/dataSegment/DataSegment_0.png)
* ```
* <DataSegment>
*
* <DataSegment.Action>
* <Button icon="rocket" />
* </DataSegment.Action>
*
* <DataSegment.Item>
* <h2>SegmentItem 1</h2>
* </DataSegment.Item>
*
* <DataSegment.Item>
* <h2>SegmentItem 2</h2>
* </DataSegment.Item>
*
* <DataSegment.Item>
* <h2>SegmentItem 3</h2>
* </DataSegment.Item>
*
* <DataSegment.Item>
* <h2>SegmentItem 4</h2>
* </DataSegment.Item>
*
* </DataSegment>
* ```
*
* ## Deployments list example
* ![DataSegment_1](manual/asset/dataSegment/DataSegment_1.png)
*
* ```
* <DataSegment totalSize={this.props.data.total}
* pageSize={this.props.widget.configuration.pageSize}
* fetchData={this.props.fetchData}>
* {
* this.props.data.items.map((item) => {
* return (
* <DataSegment.Item key={item.id} selected={item.isSelected} className={item.id}
* onClick={()=>this.props.onSelectDeployment(item)}>
* <div className="ui grid">
* <div className="three wide center aligned column rightDivider">
* <h3 className="ui icon header verticalCenter">{item.id}</h3>
* <ResourceVisibility visibility={item.visibility} className="rightFloated"/>
* </div>
* <div className="two wide column">
* <h5 className="ui icon header">Blueprint</h5>
* <p>{item.blueprint_id}</p>
* </div>
* <div className="two wide column">
* <h5 className="ui icon header">Created</h5>
* <p>{item.created_at}</p>
* </div>
* <div className="two wide column">
* <h5 className="ui icon header">Updated</h5>
* <p>{item.updated_at}</p>
* </div>
* <div className="two wide column">
* <h5 className="ui icon header">Creator</h5>
* <p>{item.created_by}</p>
* </div>
* <div className="four wide column">
* <h5 className="ui icon header">Nodes ({item.nodeSize})</h5>
* <div className="ui four column grid">
* <div className="column center aligned">
* <NodeState icon="checkmark" title="running" state="started" color="green"
* value={item.nodeStates.started}/>
* </div>
* <div className="column center aligned">
* <NodeState icon="spinner" title="in progress" state="uninitialized or created" color="yellow"
* value={_.add(item.nodeStates.uninitialized, item.nodeStates.created)}/>
* </div>
* <div className="column center aligned">
* <NodeState icon="exclamation" title="warning" state="undefined" color="orange"
* value={0}/>
* </div>
* <div className="column center aligned">
* <NodeState icon="remove" title="error" state="deleted or stopped" color="red"
* value={_.add(item.nodeStates.deleted, item.nodeStates.stopped)}/>
* </div>
* </div>
* </div>
*
* <div className="column action">
* {
* _.isEmpty(item.executions)
* ?
* <MenuAction item={item} onSelectAction={this.props.onMenuAction}/>
* :
* <ActiveExecutionStatus item={item.executions[0]} onCancelExecution={this.props.onCancelExecution}/>
* }
* </div>
* </div>
* </DataSegment.Item>
* );
* })
* }
* </DataSegment>
* ```
*/
export default class DataSegment extends Component {
/**
* Segment item, see {@link SegmentItem}
*/
static Item = SegmentItem;
/**
* Segment action, see {@link SegmentAction}
*/
static Action = SegmentAction;
constructor(props, context) {
super(props, context);
this.paginationRef = React.createRef();
this.state = {
searchText: '',
searching: false
};
this.debouncedSearch = _.debounce(
() => {
this.paginationRef.current.reset(() => {
return Promise.resolve(this._fetchData()).then(() => this.setState({ searching: false }));
});
},
300,
{ maxWait: 2000 }
);
}
/**
* propTypes
*
* @property {object[]} children - primary content
* @property {Function} [fetchData] - used to fetch data
* @property {number} [totalSize=-1] - total number of data segments, if not specified pagination will not be set. It is used to calculate pagination pages.
* @property {Function} [fetchSize=-1] - if total number is unknown size of fetched data can be provided.
* Pagination pages will be added dynamically until fetchSize is not equal to page size
* @property {number} [pageSize=0] - number of displayed rows on page
* @property {number} [sizeMultiplier=3] - param related to pagination.
* List of page sizes is generated as multiplication of basic fixed values [1, 2, 3, 5, 10] by this param
* @property {string} [className=''] - CSS classname
* @property {boolean} [searchable=false] - if true filtering and searching input to be added
*/
static propTypes = {
children: PropTypes.any.isRequired,
fetchData: PropTypes.func.isRequired,
totalSize: PropTypes.number,
fetchSize: PropTypes.number,
pageSize: PropTypes.number,
className: PropTypes.string,
sizeMultiplier: PropTypes.number,
searchable: PropTypes.bool,
noDataMessage: PropTypes.string
};
static defaultProps = {
className: '',
fetchData: () => {},
totalSize: -1,
fetchSize: -1,
pageSize: 0,
sizeMultiplier: 3,
searchable: false,
noDataMessage: 'No data available'
};
_fetchData() {
return this.props.fetchData({
gridParams: {
_search: this.state.searchText,
currentPage: this.paginationRef.current.state.currentPage,
pageSize: this.paginationRef.current.state.pageSize
}
});
}
render() {
let segmentAction = null;
const children = [];
React.Children.forEach(this.props.children, function(child) {
if (child && child.type) {
if (child.type === SegmentAction) {
segmentAction = child;
} else {
children.push(child);
}
}
});
return (
<div className={`segmentList ${this.props.className}`}>
{(segmentAction || this.props.searchable) && (
<Form size="small" as="div">
<Form.Group inline>
{this.props.searchable && (
<TableSearch
search={this.state.searchText}
searching={this.state.searching}
onSearch={searchText =>
this.setState({ searchText, searching: true }, this.debouncedSearch)
}
/>
)}
{segmentAction}
</Form.Group>
</Form>
)}
<Pagination
totalSize={this.props.totalSize}
pageSize={this.props.pageSize}
sizeMultiplier={this.props.sizeMultiplier}
fetchData={this._fetchData.bind(this)}
fetchSize={this.props.fetchSize}
ref={this.paginationRef}
>
{this.props.totalSize <= 0 &&
this.props.fetchSize <= 0 &&
(this.props.totalSize === 0 || this.props.fetchSize === 0) ? (
<Message icon>
<Icon name="ban" />
{this.props.fetchSize === 0 &&
this.paginationRef.current &&
this.paginationRef.current.state.currentPage > 1 ? (
<span>No more data available</span>
) : (
<span>{this.props.noDataMessage}</span>
)}
</Message>
) : (
children
)}
</Pagination>
</div>
);
}
}