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

Added search functonality to dashboard so user can search with Txn ha… #357

Merged
Show file tree
Hide file tree
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
144 changes: 144 additions & 0 deletions client/src/components/Lists/SearchByQuery.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@

import React, { useEffect, useState } from 'react'
import { txnListType } from '../types';
import { IconButton, TextField, Select, MenuItem, InputAdornment, makeStyles } from '@material-ui/core';
import SearchIcon from '@material-ui/icons/Search';
import { withRouter } from 'react-router-dom';
import Dialog from '@material-ui/core/Dialog';
import TransactionView from '../View/TransactionView';
import BlockView from '../View/BlockView';

const useStyles = makeStyles((theme) => ({
searchField: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
'& .MuiOutlinedInput-input': {
padding: '16px 14px'
}
},
searchInput: {
marginRight: theme.spacing(0),
'& > div': {
paddingRight: '24px !important',
}
},
iconButton: {
height: 40,
width: 40,
color: '#21295c',
backgroundColor: '#b9d6e1',
borderRadius: 15
}
}));


const SearchByQuery = (props) => {
let { txnList } = props;
let { blockSearch } = props;
const classes = useStyles();
const options = ["Txn Hash", "Block No"]
const [search, setSearch] = useState("")
const [selectedOption, setSelectedOption] = useState("Txn Hash")
const [dialogOpen, setDialogOpen] = useState(false)
const [error, setError] = useState(props.searchError)
const [searchClick, setSearchClick] = useState(false);

useEffect(() => {
if (props.searchError || searchClick) {
setSearchClick(false); setError(props.searchError) }
}, [props.searchError, searchClick])

const searchData = async () => {
if (selectedOption === "Txn Hash") {
await props.getTxnList(props.currentChannel, search)
} else if (selectedOption === "Block No") {
await props.getBlockSearch(props.currentChannel, search)
}
handleDialogOpen();
setSearchClick(true)
}

const handleSubmit = async (e) => {
e.preventDefault();
if (!search || (selectedOption === "Block No" && (isNaN(search) || search.length > 9))) {
setError("Please enter valid txn hash/block no")
return
}
searchData();

}

const handleDialogOpen = () => {
setDialogOpen(true)

}

const handleDialogClose = () => {
setDialogOpen(false)
}

return (
<div className={classes.searchField}>
<form onSubmit={handleSubmit}>
<Select
value={selectedOption}
onChange={(e) => { setSelectedOption(e.target.value); if (error) { setDialogOpen(false); setError('') } }}
className={classes.searchInput}
displayEmpty
variant='outlined'
style={{ width: 110 }}
MenuProps={{
anchorOrigin: {
vertical: "bottom",
horizontal: "left"
},
getContentAnchorEl: null
}}
>
{options.map((option) => (
<MenuItem key={option} value={option}>
{option}
</MenuItem>

))}
</Select>
<TextField
value={search}
onChange={(e) => { setSearch(e.target.value); if (error) { setDialogOpen(false); setError(''); } }}
onKeyPress={(e) => e.key === 'Enter' && handleSubmit(e)}
label=" Search by Txn Hash / Block"
variant='outlined'
style={{ width: 550 }}
error={error}
helperText={error}
InputProps={{
endAdornment: (
<InputAdornment position='end'>
<IconButton onClick={handleSubmit} className={classes.iconButton}>
<SearchIcon />
</IconButton>
</InputAdornment>
)
}}
/>
</form>
<Dialog
open={dialogOpen && !error}
onClose={handleDialogClose}
fullWidth
maxWidth="md"
>
{!error && selectedOption === 'Block No' ? <BlockView blockHash={blockSearch} onClose={handleDialogClose} />
: <TransactionView transaction={txnList} onClose={handleDialogClose} />
}
</Dialog>
</div>
)
}
SearchByQuery.propTypes = {
txnList: txnListType.isRequired
};


export default withRouter(SearchByQuery)
12 changes: 12 additions & 0 deletions client/src/components/Main.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
dashStatsType,
getTransactionType,
peerListType,
txnListType,
blockSearchType,
peerStatusType,
blockRangeSearchType,
blockListSearchType,
Expand All @@ -46,6 +48,8 @@ const {
chaincodeListSelector,
channelsSelector,
peerListSelector,
txnListSelector,
blockSearchSelector,
transactionSelector,
transactionListSelector,
blockRangeSearchSelector,
Expand Down Expand Up @@ -81,6 +85,8 @@ export const Main = props => {
dashStats,
getTransaction,
peerList,
txnList,
blockSearch,
peerStatus,
txnList,//s
transaction,
Expand Down Expand Up @@ -131,6 +137,8 @@ export const Main = props => {
blockListSearch,
dashStats,
peerStatus,
txnList,
blockSearch,
transactionByOrg,
blockActivity
};
Expand Down Expand Up @@ -242,6 +250,8 @@ Main.propTypes = {
dashStats: dashStatsType.isRequired,
getTransaction: getTransactionType.isRequired,
peerList: peerListType.isRequired,
txnList: txnListType.isRequired,
blockSearch: blockSearchType.isRequired,
peerStatus: peerStatusType.isRequired,
transaction: transactionType.isRequired,
transactionByOrg: transactionByOrgType.isRequired,
Expand All @@ -256,6 +266,8 @@ const connectedComponent = connect(
currentChannel: currentChannelSelector(state),
dashStats: dashStatsSelector(state),
peerList: peerListSelector(state),
txnList: txnListSelector(state),
blockSearch: blockSearchSelector(state),
peerStatus: peerStatusSelector(state),
transaction: transactionSelector(state),
transactionByOrg: transactionByOrgSelector(state),
Expand Down
45 changes: 43 additions & 2 deletions client/src/components/View/DashboardView.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,16 @@ import {
blockListSearchType,
dashStatsType,
peerStatusType,
txnListType,
blockSearchType,
transactionByOrgType
} from '../types';
import SearchByQuery from '../Lists/SearchByQuery';
import { connect } from 'react-redux';
import { currentChannelSelector } from '../../state/redux/charts/selectors';
import { tableOperations } from '../../state/redux/tables';

const {txnList, blockSearch} =tableOperations

/* istanbul ignore next */
const styles = theme => {
Expand All @@ -34,6 +42,13 @@ const styles = theme => {
marginLeft: '10%',
marginRight: '10%'
},
dashboardSearch:{
position: 'absolute',
width: '80%'
},
search :{
marginLeft:'10px'
},
blocks: {
height: 175,
marginBottom: 20,
Expand Down Expand Up @@ -155,8 +170,11 @@ export class DashboardView extends Component {
};

render() {
const { dashStats, peerStatus, blockActivity, transactionByOrg } = this.props;
const { dashStats, peerStatus, txnList, blockSearch, blockActivity, transactionByOrg } = this.props;
const { hasDbError, notifications } = this.state;
var searchError = ''
if(typeof txnList==='string'){searchError='Txn not found'; }
else if(typeof blockSearch==='string'){searchError='Block not found'}
if (hasDbError) {
return (
<div
Expand All @@ -177,6 +195,14 @@ export class DashboardView extends Component {
const { classes } = this.props;
return (
<div className={classes.background}>
<div className={classes.view}>
<div className={classes.dashboardSearch}>
<SearchByQuery getTxnList={this.props.getTxnList} getBlockSearch={this.props.getBlockSearch}
currentChannel={this.props.currentChannel}
txnList={txnList} blockSearch={blockSearch}
searchError={searchError} />
</div>
</div>
<div className={classes.view}>
<Row>
<Col sm="12">
Expand Down Expand Up @@ -269,7 +295,22 @@ DashboardView.propTypes = {
blockListSearch: blockListSearchType.isRequired,
dashStats: dashStatsType.isRequired,
peerStatus: peerStatusType.isRequired,
txnList: txnListType.isRequired,
blockSearch: blockSearchType.isRequired,
transactionByOrg: transactionByOrgType.isRequired
};

export default withStyles(styles)(DashboardView);
const mapStateToProps = state => {
return {
currentChannel: currentChannelSelector(state)
}
}
const mapDispatchToProps = {
getTxnList: txnList,
getBlockSearch: blockSearch
};
const connectedComponent = connect(
mapStateToProps,
mapDispatchToProps
)(DashboardView)
export default withStyles(styles)(connectedComponent);
6 changes: 6 additions & 0 deletions client/src/components/types/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ export const getChannelListType = func;
export const getChannelsType = func;
export const getDashStatsType = func;
export const getPeerListType = func;
export const getTxnListType = func;
export const getBlockSearchType = func;
export const getPeerStatusType = func;
export const getTransactionInfoType = func;
export const getTransactionListType = func;
Expand Down Expand Up @@ -153,6 +155,10 @@ export const peerListType = arrayOf(
})
);

export const txnListType = any;

export const blockSearchType = any;

export const peerStatusType = arrayOf(
shape({
server_hostname: string,
Expand Down
12 changes: 12 additions & 0 deletions client/src/state/redux/tables/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ const getBlockRangeSearch = resp => ({
payload: resp.data
});

const getTxnList = resp => ({
type: types.TXN_LIST,
payload: resp.data,
});

const getBlockSearch = resp => ({
type: types.BLOCK_SEARCH,
payload: resp.data,
});

const getTransaction = transaction => ({
type: types.TRANSACTION,
payload: transaction,
Expand All @@ -53,6 +63,8 @@ export default {
getChannels,
getPeerList,
getBlockRangeSearch,
getTxnList,
getBlockSearch,
getTransaction,
getTransactionList,
getBlockListSearch,
Expand Down
42 changes: 42 additions & 0 deletions client/src/state/redux/tables/operations.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,46 @@ const peerList = channel => dispatch =>
console.error(error);
});

const txnList = (channel, query) => dispatch =>
get(`/api/fetchDataByTxnId/${channel}/${query}`)
.then(resp => {
if (resp.status === 500) {
dispatch(
actions.getErroMessage(
'500 Internal Server Error: The server has encountered an internal error and unable to complete your request'
)
);
} else if (resp.status === 400) {
dispatch(actions.getErroMessage(resp.error));
} else {
dispatch(actions.getTxnList(resp));
}
dispatch(actions.getBlockSearch({ data: {} }));
})
.catch(error => {
console.error(error);
});

const blockSearch = (channel, query) => dispatch =>
get(`/api/fetchDataByBlockNo/${channel}/${query}`)
.then(resp => {
if (resp.status === 500) {
dispatch(
actions.getErroMessage(
'500 Internal Server Error: The server has encountered an internal error and unable to complete your request'
)
);
} else if (resp.status === 400) {
dispatch(actions.getErroMessage(resp.error));
} else {
dispatch(actions.getBlockSearch(resp));
}
dispatch(actions.getTxnList({ data: {} }));
})
.catch(error => {
console.error(error);
});

/* istanbul ignore next */
const transaction = (channel, transactionId) => dispatch =>
get(`/api/transaction/${channel}/${transactionId}`)
Expand Down Expand Up @@ -163,6 +203,8 @@ export default {
chaincodeList,
channels,
peerList,
txnList,
blockSearch,
transaction,
transactionList,
transactionListSearch,
Expand Down
Loading