Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: peers table can be filtered #2181

Merged
merged 4 commits into from
Dec 1, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
274 changes: 150 additions & 124 deletions src/peers/PeersTable/PeersTable.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import classNames from 'classnames'
import ms from 'milliseconds'
import { connect } from 'redux-bundler-react'
Expand All @@ -11,166 +11,192 @@

import './PeersTable.css'

export class PeersTable extends React.Component {
/**
*
* @param {object} props
* @param {Promise<any[]>} props.peerLocationsForSwarm
* @param {string} props.className
* @param {import('i18next').TFunction} props.t
*/
constructor (props) {
super(props)

this.state = {
sortBy: 'latency',
sortDirection: SortDirection.ASC,
peerLocationsForSwarm: []
}

this.sort = this.sort.bind(this)
}

flagRenderer = (flagCode, isPrivate) => {
// Check if the OS is Windows to render the flags as SVGs
// Windows doesn't render the flags as emojis ¯\_(ツ)_/¯
const isWindows = window.navigator.appVersion.indexOf('Win') !== -1
return (
const flagRenderer = (flagCode, isPrivate) => {
// Check if the OS is Windows to render the flags as SVGs
// Windows doesn't render the flags as emojis ¯\_(ツ)_/¯
const isWindows = window.navigator.appVersion.indexOf('Win') !== -1
return (
<span className='f4 pr2'>
{isPrivate ? '🤝' : flagCode ? <CountryFlag code={flagCode} svg={isWindows} /> : '🌐'}
</span>
)
}
)
}

locationCellRenderer = ({ rowData }) => {
const ref = React.createRef()
const location = rowData.isPrivate
? this.props.t('localNetwork')
: rowData.location
? rowData.isNearby
? <span>{rowData.location} <span className='charcoal-muted'>({this.props.t('nearby')})</span></span>
: rowData.location
: <span className='charcoal-muted fw4'>{this.props.t('app:terms.unknown')}</span>
const value = rowData.location || this.props.t('app:terms.unknown')
return (
<CopyToClipboard text={value} onCopy={() => copyFeedback(ref, this.props.t)}>
const locationCellRenderer = (t) => ({ rowData }) => {
const ref = React.createRef()
const location = rowData.isPrivate
? t('localNetwork')
: rowData.location
? rowData.isNearby
? <span>{rowData.location} <span className='charcoal-muted'>({t('nearby')})</span></span>
: rowData.location
: <span className='charcoal-muted fw4'>{t('app:terms.unknown')}</span>
const value = rowData.location || t('app:terms.unknown')
return (
<CopyToClipboard text={value} onCopy={() => copyFeedback(ref, t)}>

Check warning on line 36 in src/peers/PeersTable/PeersTable.js

View check run for this annotation

Codecov / codecov/patch

src/peers/PeersTable/PeersTable.js#L36

Added line #L36 was not covered by tests
<span title={value} className='copyable' ref={ref}>
{ this.flagRenderer(rowData.flagCode, rowData.isPrivate) }
{ flagRenderer(rowData.flagCode, rowData.isPrivate) }
{ location }
</span>
</CopyToClipboard>
)
}
)
}

latencyCellRenderer = ({ cellData, rowData }) => {
const style = { width: '60px' }
const latency = `${cellData}ms`
if (cellData == null) return (<span className='dib o-40 no-select' style={style}>-</span>)
return (<span className='dib no-select'>{latency}</span>)
}
const latencyCellRenderer = ({ cellData }) => {
const style = { width: '60px' }
const latency = `${cellData}ms`
if (cellData == null) return (<span className='dib o-40 no-select' style={style}>-</span>)
return (<span className='dib no-select'>{latency}</span>)
}

peerIdCellRenderer = ({ cellData: peerId }) => {
const ref = React.createRef()
const p2pMultiaddr = `/p2p/${peerId}`
return (
<CopyToClipboard text={p2pMultiaddr} onCopy={() => copyFeedback(ref, this.props.t)}>
const peerIdCellRenderer = (t) => ({ cellData: peerId }) => {
const ref = React.createRef()
const p2pMultiaddr = `/p2p/${peerId}`
return (
<CopyToClipboard text={p2pMultiaddr} onCopy={() => copyFeedback(ref, t)}>

Check warning on line 56 in src/peers/PeersTable/PeersTable.js

View check run for this annotation

Codecov / codecov/patch

src/peers/PeersTable/PeersTable.js#L56

Added line #L56 was not covered by tests
<Cid value={peerId} identicon ref={ref} className='copyable' />
</CopyToClipboard>
)
}
)
}

protocolsCellRenderer = ({ rowData, cellData }) => {
const ref = React.createRef()
const { protocols } = rowData
const title = protocols.split(', ').join('\n')
return (
<CopyToClipboard text={protocols} onCopy={() => copyFeedback(ref, this.props.t)}>
const protocolsCellRenderer = (t) => ({ rowData }) => {
const ref = React.createRef()
const { protocols } = rowData
const title = protocols.split(', ').join('\n')
return (
<CopyToClipboard text={protocols} onCopy={() => copyFeedback(ref, t)}>

Check warning on line 67 in src/peers/PeersTable/PeersTable.js

View check run for this annotation

Codecov / codecov/patch

src/peers/PeersTable/PeersTable.js#L67

Added line #L67 was not covered by tests
<span
ref={ref}
className='copyable'
title={title}>
{ protocols.replaceAll('[unnamed]', '🤔') }
</span>
</CopyToClipboard>
)
}
)
}

connectionCellRenderer = ({ rowData }) => {
const ref = React.createRef()
const { address, direction, peerId } = rowData
const p2pMultiaddr = `${address}/p2p/${peerId}`
const title = direction != null
? `${address}\n(${renderDirection(direction, this.props.t)})`
: address
const connectionCellRenderer = (t) => ({ rowData }) => {
const ref = React.createRef()
const { address, direction, peerId } = rowData
const p2pMultiaddr = `${address}/p2p/${peerId}`
const title = direction != null
? `${address}\n(${renderDirection(direction, t)})`
: address

return (
<CopyToClipboard text={p2pMultiaddr} onCopy={() => copyFeedback(ref, this.props.t)}>
return (
<CopyToClipboard text={p2pMultiaddr} onCopy={() => copyFeedback(ref, t)}>

Check warning on line 87 in src/peers/PeersTable/PeersTable.js

View check run for this annotation

Codecov / codecov/patch

src/peers/PeersTable/PeersTable.js#L87

Added line #L87 was not covered by tests
<abbr
ref={ref}
className='copyable'
title={title}>
{rowData.connection}
</abbr>
</CopyToClipboard>
)
}

rowClassRenderer = ({ index }, peers = []) => {
const { selectedPeers } = this.props
const shouldAddHoverEffect = selectedPeers?.peerIds?.includes(peers[index]?.peerId)

return classNames('bb b--near-white peersTableItem', index === -1 && 'bg-near-white', shouldAddHoverEffect && 'bg-light-gray')
}

sort ({ sortBy, sortDirection }) {
this.setState({ sortBy, sortDirection })
}
)
}

componentWillReceiveProps (nextProps) {
if (nextProps.peerLocationsForSwarm) {
nextProps.peerLocationsForSwarm?.then?.((peerLocationsForSwarm) => {
if (peerLocationsForSwarm !== this.state.peerLocationsForSwarm) {
this.setState({ peerLocationsForSwarm })
}
})
}
}
const rowClassRenderer = ({ index }, peers = [], selectedPeers) => {
const shouldAddHoverEffect = selectedPeers?.peerIds?.includes(peers[index]?.peerId)

render () {
const { className, t } = this.props
const { sortBy, sortDirection, peerLocationsForSwarm } = this.state
return classNames('bb b--near-white peersTableItem', index === -1 && 'bg-near-white', shouldAddHoverEffect && 'bg-light-gray')
}

const sortedList = peerLocationsForSwarm.sort(sortByProperty(sortBy, sortDirection === SortDirection.ASC ? 1 : -1))
const tableHeight = 400
const FilterInput = ({ setFilter, t, filteredCount }) => {
return (
<div className='flex items-center justify-between pa2'>
<input
className='input-reset ba b--black-20 pa2 mb2 db w-100'
type='text'
placeholder='Filter peers'
onChange={(e) => setFilter(e.target.value)}

Check warning on line 111 in src/peers/PeersTable/PeersTable.js

View check run for this annotation

Codecov / codecov/patch

src/peers/PeersTable/PeersTable.js#L111

Added line #L111 was not covered by tests
/>
{/* Now to display the total number of peers filtered out on the right side of the inside of the input */}
<div className='f4 charcoal-muted absolute top-1 right-1'>{filteredCount}</div>
</div>
)
}

return (
<div className={`bg-white-70 center ${className}`} style={{ height: `${tableHeight}px`, maxWidth: 1764 }}>
{ peerLocationsForSwarm && <AutoSizer disableHeight>
export const PeersTable = ({ className, t, peerLocationsForSwarm, selectedPeers }) => {
const tableHeight = 400
const [awaitedPeerLocationsForSwarm, setAwaitedPeerLocationsForSwarm] = useState([])
const [sortBy, setSortBy] = useState('latency')
const [sortDirection, setSortDirection] = useState(SortDirection.ASC)
const [filter, setFilter] = useState('')

const sort = useCallback(({ sortBy, sortDirection }) => {
setSortBy(sortBy)
setSortDirection(sortDirection)

Check warning on line 128 in src/peers/PeersTable/PeersTable.js

View check run for this annotation

Codecov / codecov/patch

src/peers/PeersTable/PeersTable.js#L127-L128

Added lines #L127 - L128 were not covered by tests
}, [])
const filterCb = useCallback((value) => {
setFilter(value)

Check warning on line 131 in src/peers/PeersTable/PeersTable.js

View check run for this annotation

Codecov / codecov/patch

src/peers/PeersTable/PeersTable.js#L131

Added line #L131 was not covered by tests
}, [])

useEffect(() => {
peerLocationsForSwarm?.then?.((peerLocationsForSwarm) => {
setAwaitedPeerLocationsForSwarm(peerLocationsForSwarm)
})
}, [peerLocationsForSwarm])

const filteredPeerList = useMemo(() => {
const filterLower = filter.toLowerCase()
if (filterLower === '') return awaitedPeerLocationsForSwarm
return awaitedPeerLocationsForSwarm.filter(({ location, latency, peerId, connection, protocols }) => {
if (location != null && location.toLowerCase().includes(filterLower)) {
return true

Check warning on line 145 in src/peers/PeersTable/PeersTable.js

View check run for this annotation

Codecov / codecov/patch

src/peers/PeersTable/PeersTable.js#L143-L145

Added lines #L143 - L145 were not covered by tests
}
if (latency != null && [latency, `${latency}ms`].some((str) => str.toString().includes(filterLower))) {
return true

Check warning on line 148 in src/peers/PeersTable/PeersTable.js

View check run for this annotation

Codecov / codecov/patch

src/peers/PeersTable/PeersTable.js#L147-L148

Added lines #L147 - L148 were not covered by tests
}
if (peerId != null && peerId.toString().includes(filter)) {
return true

Check warning on line 151 in src/peers/PeersTable/PeersTable.js

View check run for this annotation

Codecov / codecov/patch

src/peers/PeersTable/PeersTable.js#L150-L151

Added lines #L150 - L151 were not covered by tests
}
console.log('connection: ', connection)
if (connection != null && connection.toLowerCase().includes(filterLower)) {
return true

Check warning on line 155 in src/peers/PeersTable/PeersTable.js

View check run for this annotation

Codecov / codecov/patch

src/peers/PeersTable/PeersTable.js#L153-L155

Added lines #L153 - L155 were not covered by tests
}
if (protocols != null && protocols.toLowerCase().includes(filterLower)) {
return true

Check warning on line 158 in src/peers/PeersTable/PeersTable.js

View check run for this annotation

Codecov / codecov/patch

src/peers/PeersTable/PeersTable.js#L157-L158

Added lines #L157 - L158 were not covered by tests
}

return false

Check warning on line 161 in src/peers/PeersTable/PeersTable.js

View check run for this annotation

Codecov / codecov/patch

src/peers/PeersTable/PeersTable.js#L161

Added line #L161 was not covered by tests
})
}, [awaitedPeerLocationsForSwarm, filter])

const sortedList = useMemo(
() => filteredPeerList.sort(sortByProperty(sortBy, sortDirection === SortDirection.ASC ? 1 : -1)),
[filteredPeerList, sortBy, sortDirection]
)

return (
<div className={`bg-white-70 center ${className}`} style={{ height: `${tableHeight}px`, maxWidth: 1764 }}>
<FilterInput setFilter={filterCb} t={t} filteredCount={sortedList.length} />
{ awaitedPeerLocationsForSwarm && <AutoSizer disableHeight>
{({ width }) => (
<Table
className='tl fw4 w-100 f6'
headerClassName='teal fw2 ttu tracked ph2 no-select'
rowClassName={(rowInfo) => this.rowClassRenderer(rowInfo, peerLocationsForSwarm)}
width={width}
height={tableHeight}
headerHeight={32}
rowHeight={36}
rowCount={peerLocationsForSwarm.length}
rowGetter={({ index }) => sortedList[index]}
sort={this.sort}
sortBy={sortBy}
sortDirection={sortDirection}>
<Column label={t('app:terms.location')} cellRenderer={this.locationCellRenderer} dataKey='location' width={450} className='f6 charcoal truncate pl2' />
<Column label={t('app:terms.latency')} cellRenderer={this.latencyCellRenderer} dataKey='latency' width={200} className='f6 charcoal pl2' />
<Column label={t('app:terms.peerId')} cellRenderer={this.peerIdCellRenderer} dataKey='peerId' width={250} className='charcoal monospace truncate f6 pl2' />
<Column label={t('app:terms.connection')} cellRenderer={this.connectionCellRenderer} dataKey='connection' width={250} className='f6 charcoal truncate pl2' />
<Column label={t('protocols')} cellRenderer={this.protocolsCellRenderer} dataKey='protocols' width={520} className='charcoal monospace truncate f7 pl2' />
</Table>
<>

Check warning on line 175 in src/peers/PeersTable/PeersTable.js

View check run for this annotation

Codecov / codecov/patch

src/peers/PeersTable/PeersTable.js#L175

Added line #L175 was not covered by tests
<Table
className='tl fw4 w-100 f6'
headerClassName='teal fw2 ttu tracked ph2 no-select'
rowClassName={(rowInfo) => rowClassRenderer(rowInfo, awaitedPeerLocationsForSwarm, selectedPeers)}
width={width}
height={tableHeight}
headerHeight={32}
rowHeight={36}
rowCount={sortedList.length}
rowGetter={({ index }) => sortedList[index]}
sort={sort}
sortBy={sortBy}
sortDirection={sortDirection}>
<Column label={t('app:terms.location')} cellRenderer={locationCellRenderer(t)} dataKey='location' width={450} className='f6 charcoal truncate pl2' />
<Column label={t('app:terms.latency')} cellRenderer={latencyCellRenderer} dataKey='latency' width={200} className='f6 charcoal pl2' />
<Column label={t('app:terms.peerId')} cellRenderer={peerIdCellRenderer(t)} dataKey='peerId' width={250} className='charcoal monospace truncate f6 pl2' />
<Column label={t('app:terms.connection')} cellRenderer={connectionCellRenderer(t)} dataKey='connection' width={250} className='f6 charcoal truncate pl2' />
<Column label={t('protocols')} cellRenderer={protocolsCellRenderer(t)} dataKey='protocols' width={520} className='charcoal monospace truncate f7 pl2' />
</Table>
</>
)}
</AutoSizer> }
</div>
)
}
)
}

// API returns integer atm, but that may change in the future
Expand Down
Loading