github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/views/cluster/containers/events/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 moment from "moment"; 13 import React from "react"; 14 import { Helmet } from "react-helmet"; 15 import { Link, withRouter } from "react-router-dom"; 16 import { connect } from "react-redux"; 17 import * as protos from "src/js/protos"; 18 import { refreshEvents } from "src/redux/apiReducers"; 19 import { eventsSelector, eventsValidSelector } from "src/redux/events"; 20 import { LocalSetting } from "src/redux/localsettings"; 21 import { AdminUIState } from "src/redux/state"; 22 import { TimestampToMoment } from "src/util/convert"; 23 import { getEventDescription } from "src/util/events"; 24 import { DATE_FORMAT } from "src/util/format"; 25 import { SortSetting } from "src/views/shared/components/sortabletable"; 26 import { SortedTable } from "src/views/shared/components/sortedtable"; 27 import { ToolTipWrapper } from "src/views/shared/components/toolTip"; 28 import "./events.styl"; 29 30 type Event$Properties = protos.cockroach.server.serverpb.EventsResponse.IEvent; 31 32 // Number of events to show in the sidebar. 33 const EVENT_BOX_NUM_EVENTS = 5; 34 35 const eventsSortSetting = new LocalSetting<AdminUIState, SortSetting>( 36 "events/sort_setting", (s) => s.localSettings, 37 ); 38 39 export interface SimplifiedEvent { 40 // How long ago the event occurred (e.g. "10 minutes ago"). 41 fromNowString: string; 42 sortableTimestamp: moment.Moment; 43 content: React.ReactNode; 44 } 45 46 class EventSortedTable extends SortedTable<SimplifiedEvent> {} 47 48 export interface EventRowProps { 49 event: Event$Properties; 50 } 51 52 export function getEventInfo(e: Event$Properties): SimplifiedEvent { 53 return { 54 fromNowString: TimestampToMoment(e.timestamp).format(DATE_FORMAT) 55 .replace("second", "sec") 56 .replace("minute", "min"), 57 content: <span>{ getEventDescription(e) }</span>, 58 sortableTimestamp: TimestampToMoment(e.timestamp), 59 }; 60 } 61 62 export class EventRow extends React.Component<EventRowProps, {}> { 63 render() { 64 const { event } = this.props; 65 const e = getEventInfo(event); 66 return <tr> 67 <td> 68 <ToolTipWrapper 69 placement="left" 70 text={ e.content } 71 > 72 <div className="events__message"> 73 {e.content} 74 </div> 75 </ToolTipWrapper> 76 <div className="events__timestamp"> 77 {e.fromNowString} 78 </div> 79 </td> 80 </tr>; 81 } 82 } 83 84 export interface EventBoxProps { 85 events: Event$Properties[]; 86 // eventsValid is needed so that this component will re-render when the events 87 // data becomes invalid, and thus trigger a refresh. 88 eventsValid: boolean; 89 refreshEvents: typeof refreshEvents; 90 } 91 92 export class EventBoxUnconnected extends React.Component<EventBoxProps, {}> { 93 94 componentDidMount() { 95 // Refresh events when mounting. 96 this.props.refreshEvents(); 97 } 98 99 componentDidUpdate() { 100 // Refresh events when props change. 101 this.props.refreshEvents(); 102 } 103 104 render() { 105 const events = this.props.events; 106 return <div className="events"> 107 <table> 108 <tbody> 109 {_.map(_.take(events, EVENT_BOX_NUM_EVENTS), (e: Event$Properties, i: number) => { 110 return <EventRow event={e} key={i} />; 111 })} 112 <tr> 113 <td className="events__more-link" colSpan={2}><Link to="/events">View all events</Link></td> 114 </tr> 115 </tbody> 116 </table> 117 </div>; 118 } 119 } 120 121 export interface EventPageProps { 122 events: Event$Properties[]; 123 // eventsValid is needed so that this component will re-render when the events 124 // data becomes invalid, and thus trigger a refresh. 125 eventsValid: boolean; 126 refreshEvents: typeof refreshEvents; 127 sortSetting: SortSetting; 128 setSort: typeof eventsSortSetting.set; 129 } 130 131 export class EventPageUnconnected extends React.Component<EventPageProps, {}> { 132 componentDidMount() { 133 // Refresh events when mounting. 134 this.props.refreshEvents(); 135 } 136 137 componentDidUpdate() { 138 // Refresh events when props change. 139 this.props.refreshEvents(); 140 } 141 142 render() { 143 const { events, sortSetting } = this.props; 144 145 const simplifiedEvents = _.map(events, getEventInfo); 146 147 return <div> 148 <Helmet title="Events" /> 149 <section className="section section--heading"> 150 <h1 className="base-heading">Events</h1> 151 </section> 152 <section className="section l-columns"> 153 <div className="l-columns__left events-table"> 154 <EventSortedTable 155 data={simplifiedEvents} 156 sortSetting={sortSetting} 157 onChangeSortSetting={(setting) => this.props.setSort(setting)} 158 columns={[ 159 { 160 title: "Event", 161 cell: (e) => e.content, 162 }, 163 { 164 title: "Timestamp", 165 cell: (e) => e.fromNowString, 166 sort: (e) => e.sortableTimestamp, 167 }, 168 ]} 169 /> 170 </div> 171 </section> 172 </div>; 173 } 174 } 175 176 // Connect the EventsList class with our redux store. 177 const eventBoxConnected = withRouter(connect( 178 (state: AdminUIState) => { 179 return { 180 events: eventsSelector(state), 181 eventsValid: eventsValidSelector(state), 182 }; 183 }, 184 { 185 refreshEvents, 186 }, 187 )(EventBoxUnconnected)); 188 189 // Connect the EventsList class with our redux store. 190 const eventPageConnected = withRouter(connect( 191 (state: AdminUIState) => { 192 return { 193 events: eventsSelector(state), 194 eventsValid: eventsValidSelector(state), 195 sortSetting: eventsSortSetting.selector(state), 196 }; 197 }, 198 { 199 refreshEvents, 200 setSort: eventsSortSetting.set, 201 }, 202 )(EventPageUnconnected)); 203 204 export { eventBoxConnected as EventBox }; 205 export { eventPageConnected as EventPage };