-
Notifications
You must be signed in to change notification settings - Fork 712
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2109 from weaveworks/node-details-multicolumn-table
Add support for generic multicolumn tables
- Loading branch information
Showing
20 changed files
with
982 additions
and
309 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
111 changes: 111 additions & 0 deletions
111
client/app/scripts/components/node-details/node-details-generic-table.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import React from 'react'; | ||
import sortBy from 'lodash/sortBy'; | ||
import { Map as makeMap } from 'immutable'; | ||
|
||
import { NODE_DETAILS_DATA_ROWS_DEFAULT_LIMIT } from '../../constants/limits'; | ||
|
||
import { | ||
isNumber, | ||
getTableColumnsStyles, | ||
genericTableEntryKey | ||
} from '../../utils/node-details-utils'; | ||
import NodeDetailsTableHeaders from './node-details-table-headers'; | ||
import MatchedText from '../matched-text'; | ||
import ShowMore from '../show-more'; | ||
|
||
|
||
function sortedRows(rows, columns, sortedBy, sortedDesc) { | ||
const column = columns.find(c => c.id === sortedBy); | ||
const sorted = sortBy(rows, (row) => { | ||
let value = row.entries[sortedBy]; | ||
if (isNumber(column)) { | ||
value = parseFloat(value); | ||
} | ||
return value; | ||
}); | ||
if (sortedDesc) { | ||
sorted.reverse(); | ||
} | ||
return sorted; | ||
} | ||
|
||
export default class NodeDetailsGenericTable extends React.Component { | ||
constructor(props, context) { | ||
super(props, context); | ||
this.state = { | ||
limit: NODE_DETAILS_DATA_ROWS_DEFAULT_LIMIT, | ||
sortedBy: props.columns && props.columns[0].id, | ||
sortedDesc: true | ||
}; | ||
this.handleLimitClick = this.handleLimitClick.bind(this); | ||
this.updateSorted = this.updateSorted.bind(this); | ||
} | ||
|
||
updateSorted(sortedBy, sortedDesc) { | ||
this.setState({ sortedBy, sortedDesc }); | ||
} | ||
|
||
handleLimitClick() { | ||
this.setState({ | ||
limit: this.state.limit ? 0 : NODE_DETAILS_DATA_ROWS_DEFAULT_LIMIT | ||
}); | ||
} | ||
|
||
render() { | ||
const { sortedBy, sortedDesc } = this.state; | ||
const { columns, matches = makeMap() } = this.props; | ||
const expanded = this.state.limit === 0; | ||
|
||
let rows = this.props.rows || []; | ||
let notShown = 0; | ||
|
||
// If there are rows that would be hidden behind 'show more', keep them | ||
// expanded if any of them match the search query; otherwise hide them. | ||
if (this.state.limit > 0 && rows.length > this.state.limit) { | ||
const hasHiddenMatch = rows.slice(this.state.limit).some(row => | ||
columns.some(column => matches.has(genericTableEntryKey(row, column))) | ||
); | ||
if (!hasHiddenMatch) { | ||
notShown = rows.length - NODE_DETAILS_DATA_ROWS_DEFAULT_LIMIT; | ||
rows = rows.slice(0, this.state.limit); | ||
} | ||
} | ||
|
||
const styles = getTableColumnsStyles(columns); | ||
return ( | ||
<div className="node-details-generic-table"> | ||
<table> | ||
<thead> | ||
<NodeDetailsTableHeaders | ||
headers={columns} | ||
sortedBy={sortedBy} | ||
sortedDesc={sortedDesc} | ||
onClick={this.updateSorted} | ||
/> | ||
</thead> | ||
<tbody> | ||
{sortedRows(rows, columns, sortedBy, sortedDesc).map(row => ( | ||
<tr className="node-details-generic-table-row" key={row.id}> | ||
{columns.map((column, index) => { | ||
const match = matches.get(genericTableEntryKey(row, column)); | ||
const value = row.entries[column.id]; | ||
return ( | ||
<td | ||
className="node-details-generic-table-value truncate" | ||
title={value} key={column.id} style={styles[index]}> | ||
<MatchedText text={value} match={match} /> | ||
</td> | ||
); | ||
})} | ||
</tr> | ||
))} | ||
</tbody> | ||
</table> | ||
<ShowMore | ||
handleClick={this.handleLimitClick} collection={this.props.rows} | ||
expanded={expanded} notShown={notShown} | ||
/> | ||
</div> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
55 changes: 55 additions & 0 deletions
55
client/app/scripts/components/node-details/node-details-table-headers.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import React from 'react'; | ||
import { defaultSortDesc, getTableColumnsStyles } from '../../utils/node-details-utils'; | ||
import { NODE_DETAILS_TABLE_CW, NODE_DETAILS_TABLE_XS_LABEL } from '../../constants/styles'; | ||
|
||
|
||
export default class NodeDetailsTableHeaders extends React.Component { | ||
handleClick(ev, headerId, currentSortedBy, currentSortedDesc) { | ||
ev.preventDefault(); | ||
const header = this.props.headers.find(h => h.id === headerId); | ||
const sortedBy = header.id; | ||
const sortedDesc = sortedBy === currentSortedBy | ||
? !currentSortedDesc : defaultSortDesc(header); | ||
this.props.onClick(sortedBy, sortedDesc); | ||
} | ||
|
||
render() { | ||
const { headers, sortedBy, sortedDesc } = this.props; | ||
const colStyles = getTableColumnsStyles(headers); | ||
return ( | ||
<tr> | ||
{headers.map((header, index) => { | ||
const headerClasses = ['node-details-table-header', 'truncate']; | ||
const onClick = (ev) => { | ||
this.handleClick(ev, header.id, sortedBy, sortedDesc); | ||
}; | ||
// sort by first metric by default | ||
const isSorted = header.id === sortedBy; | ||
const isSortedDesc = isSorted && sortedDesc; | ||
const isSortedAsc = isSorted && !isSortedDesc; | ||
|
||
if (isSorted) { | ||
headerClasses.push('node-details-table-header-sorted'); | ||
} | ||
|
||
const style = colStyles[index]; | ||
const label = | ||
(style.width === NODE_DETAILS_TABLE_CW.XS && NODE_DETAILS_TABLE_XS_LABEL[header.id]) ? | ||
NODE_DETAILS_TABLE_XS_LABEL[header.id] : header.label; | ||
|
||
return ( | ||
<td | ||
className={headerClasses.join(' ')} style={style} onClick={onClick} | ||
title={header.label} key={header.id}> | ||
{isSortedAsc | ||
&& <span className="node-details-table-header-sorter fa fa-caret-up" />} | ||
{isSortedDesc | ||
&& <span className="node-details-table-header-sorter fa fa-caret-down" />} | ||
{label} | ||
</td> | ||
); | ||
})} | ||
</tr> | ||
); | ||
} | ||
} |
Oops, something went wrong.