github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/views/reports/containers/problemRanges/index.tsx (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 import _ from "lodash"; 12 import Long from "long"; 13 import React from "react"; 14 import { Helmet } from "react-helmet"; 15 import { connect } from "react-redux"; 16 import { Link, RouteComponentProps, withRouter } from "react-router-dom"; 17 import * as protos from "src/js/protos"; 18 import { problemRangesRequestKey, refreshProblemRanges } from "src/redux/apiReducers"; 19 import { CachedDataReducerState } from "src/redux/cachedDataReducer"; 20 import { AdminUIState } from "src/redux/state"; 21 import { nodeIDAttr } from "src/util/constants"; 22 import { FixLong } from "src/util/fixLong"; 23 import ConnectionsTable from "src/views/reports/containers/problemRanges/connectionsTable"; 24 import Loading from "src/views/shared/components/loading"; 25 import { getMatchParamByName } from "src/util/query"; 26 27 type NodeProblems$Properties = protos.cockroach.server.serverpb.ProblemRangesResponse.INodeProblems; 28 29 interface ProblemRangesOwnProps { 30 problemRanges: CachedDataReducerState<protos.cockroach.server.serverpb.ProblemRangesResponse>; 31 refreshProblemRanges: typeof refreshProblemRanges; 32 } 33 34 type ProblemRangesProps = ProblemRangesOwnProps & RouteComponentProps; 35 36 function isLoading(state: CachedDataReducerState<any>) { 37 return _.isNil(state) || (_.isNil(state.data) && _.isNil(state.lastError)); 38 } 39 40 function ProblemRangeList(props: { 41 name: string, 42 problems: NodeProblems$Properties[], 43 extract: (p: NodeProblems$Properties) => Long[], 44 }) { 45 const ids = _.chain(props.problems) 46 .filter(problem => _.isEmpty(problem.error_message)) 47 .flatMap(problem => props.extract(problem)) 48 .map(id => FixLong(id)) 49 .sort((a, b) => a.compare(b)) 50 .map(id => id.toString()) 51 .sortedUniq() 52 .value(); 53 if (_.isEmpty(ids)) { 54 return null; 55 } 56 return ( 57 <div> 58 <h2 className="base-heading">{props.name}</h2> 59 <div className="problems-list"> 60 { 61 _.map(ids, id => { 62 return ( 63 <Link key={id} className="problems-link" to={`/reports/range/${id}`}> 64 {id} 65 </Link> 66 ); 67 }) 68 } 69 </div> 70 </div> 71 ); 72 } 73 74 function problemRangeRequestFromProps(props: ProblemRangesProps) { 75 return new protos.cockroach.server.serverpb.ProblemRangesRequest({ 76 node_id: getMatchParamByName(props.match, nodeIDAttr), 77 }); 78 } 79 80 /** 81 * Renders the Problem Ranges page. 82 * 83 * The problem ranges endpoint returns a list of known ranges with issues on a 84 * per node basis. This page aggregates those lists together and displays all 85 * unique range IDs that have problems. 86 */ 87 export class ProblemRanges extends React.Component<ProblemRangesProps, {}> { 88 refresh(props = this.props) { 89 props.refreshProblemRanges(problemRangeRequestFromProps(props)); 90 } 91 92 componentDidMount() { 93 // Refresh nodes status query when mounting. 94 this.refresh(); 95 } 96 97 componentDidUpdate(prevProps: ProblemRangesProps) { 98 if (!_.isEqual(this.props.location, prevProps.location)) { 99 this.refresh(this.props); 100 } 101 } 102 103 renderReportBody() { 104 const { problemRanges, match } = this.props; 105 const nodeId = getMatchParamByName(match, nodeIDAttr); 106 107 if (isLoading(this.props.problemRanges)) { 108 return null; 109 } 110 111 if (!_.isNil(problemRanges.lastError)) { 112 if (nodeId === null) { 113 return ( 114 <div> 115 <h2 className="base-heading">Error loading Problem Ranges for the Cluster</h2> 116 {problemRanges.lastError.toString()} 117 </div> 118 ); 119 } else { 120 return ( 121 <div> 122 <h2 className="base-heading">Error loading Problem Ranges for node n{nodeId}</h2> 123 {problemRanges.lastError.toString()} 124 </div> 125 ); 126 } 127 } 128 129 const { data } = problemRanges; 130 131 const validIDs = _.keys(_.pickBy(data.problems_by_node_id, d => { 132 return _.isEmpty(d.error_message); 133 })); 134 if (validIDs.length === 0) { 135 if (nodeId === null) { 136 return <h2 className="base-heading">No nodes returned any results</h2>; 137 } else { 138 return <h2 className="base-heading">No results reported for node n{nodeId}</h2>; 139 } 140 } 141 142 let titleText: string; // = "Problem Ranges for "; 143 if (validIDs.length === 1) { 144 const singleNodeID = _.keys(data.problems_by_node_id)[0]; 145 titleText = `Problem Ranges on Node n${singleNodeID}`; 146 } else { 147 titleText = "Problem Ranges on the Cluster"; 148 } 149 150 const problems = _.values(data.problems_by_node_id); 151 return ( 152 <div> 153 <h2 className="base-heading"> 154 {titleText} 155 </h2> 156 <ProblemRangeList 157 name="Unavailable" 158 problems={problems} 159 extract={(problem) => problem.unavailable_range_ids} 160 /> 161 <ProblemRangeList 162 name="No Raft Leader" 163 problems={problems} 164 extract={(problem) => problem.no_raft_leader_range_ids} 165 /> 166 <ProblemRangeList 167 name="Invalid Lease" 168 problems={problems} 169 extract={(problem) => problem.no_lease_range_ids} 170 /> 171 <ProblemRangeList 172 name="Raft Leader but not Lease Holder" 173 problems={problems} 174 extract={(problem) => problem.raft_leader_not_lease_holder_range_ids} 175 /> 176 <ProblemRangeList 177 name="Underreplicated (or slow)" 178 problems={problems} 179 extract={(problem) => problem.underreplicated_range_ids} 180 /> 181 <ProblemRangeList 182 name="Overreplicated" 183 problems={problems} 184 extract={(problem) => problem.overreplicated_range_ids} 185 /> 186 <ProblemRangeList 187 name="Quiescent equals ticking" 188 problems={problems} 189 extract={(problem) => problem.quiescent_equals_ticking_range_ids} 190 /> 191 </div> 192 ); 193 } 194 195 render() { 196 return ( 197 <div className="section"> 198 <Helmet title="Problem Ranges | Debug" /> 199 <h1 className="base-heading">Problem Ranges Report</h1> 200 <Loading 201 loading={isLoading(this.props.problemRanges)} 202 error={this.props.problemRanges && this.props.problemRanges.lastError} 203 render={() => ( 204 <div> 205 {this.renderReportBody()} 206 <ConnectionsTable problemRanges={this.props.problemRanges} /> 207 </div> 208 )} 209 /> 210 </div> 211 ); 212 } 213 } 214 215 const mapStateToProps = (state: AdminUIState, props: ProblemRangesProps) => { 216 const nodeIDKey = problemRangesRequestKey(problemRangeRequestFromProps(props)); 217 return { 218 problemRanges: state.cachedData.problemRanges[nodeIDKey], 219 }; 220 }; 221 222 const mapDispatchToProps = { 223 // actionCreators returns objects with type and payload 224 refreshProblemRanges, 225 }; 226 227 export default withRouter(connect( 228 mapStateToProps, 229 mapDispatchToProps, 230 )(ProblemRanges));