problems timeline: show acknowledges

This commit is contained in:
Alexander Zobnin
2018-12-26 20:57:38 +03:00
parent cf018b4876
commit dd7b2565cb
2 changed files with 215 additions and 2 deletions

View File

@@ -1,7 +1,7 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import _ from 'lodash'; import _ from 'lodash';
import moment from 'moment'; import moment from 'moment';
import { GFTimeRange, ZBXEvent } from 'panel-triggers/types'; import { GFTimeRange, ZBXEvent, ZBXAcknowledge } from 'panel-triggers/types';
const DEFAULT_OK_COLOR = 'rgb(56, 189, 113)'; const DEFAULT_OK_COLOR = 'rgb(56, 189, 113)';
const DEFAULT_PROBLEM_COLOR = 'rgb(215, 0, 0)'; const DEFAULT_PROBLEM_COLOR = 'rgb(215, 0, 0)';
@@ -28,7 +28,9 @@ interface ProblemTimelineState {
} }
interface EventInfo { interface EventInfo {
timestamp?: number;
duration?: number; duration?: number;
message?: string;
} }
export default class ProblemTimeline extends PureComponent<ProblemTimelineProps, ProblemTimelineState> { export default class ProblemTimeline extends PureComponent<ProblemTimelineProps, ProblemTimelineState> {
@@ -49,6 +51,7 @@ export default class ProblemTimeline extends PureComponent<ProblemTimelineProps,
highlightedEvent: null, highlightedEvent: null,
highlightedRegion: null, highlightedRegion: null,
showEventInfo: false, showEventInfo: false,
eventInfo: {}
}; };
} }
@@ -87,6 +90,20 @@ export default class ProblemTimeline extends PureComponent<ProblemTimelineProps,
this.setState({ showEventInfo: false, highlightedRegion: null }); this.setState({ showEventInfo: false, highlightedRegion: null });
} }
handleAckHighlight = (ack: ZBXAcknowledge, index: number) => {
this.setState({
showEventInfo: true,
eventInfo: {
timestamp: Number(ack.clock),
message: ack.message,
}
});
}
handleAckUnHighlight = () => {
this.setState({ showEventInfo: false });
}
showEventInfo = (event: ZBXEvent) => { showEventInfo = (event: ZBXEvent) => {
this.setState({ highlightedEvent: event, showEventInfo: true }); this.setState({ highlightedEvent: event, showEventInfo: true });
} }
@@ -111,6 +128,18 @@ export default class ProblemTimeline extends PureComponent<ProblemTimelineProps,
return events; return events;
} }
getEventAcks(events: ZBXEvent[]): ZBXAcknowledge[] {
const acks: ZBXAcknowledge[] = [];
for (const event of events) {
if (event.acknowledges) {
for (const ack of event.acknowledges) {
acks.push(ack);
}
}
}
return _.sortBy(acks, ack => Number(ack.clock));
}
render() { render() {
if (!this.rootRef) { if (!this.rootRef) {
return <div className="event-timeline" ref={this.setRootRef} />; return <div className="event-timeline" ref={this.setRootRef} />;
@@ -118,6 +147,7 @@ export default class ProblemTimeline extends PureComponent<ProblemTimelineProps,
const { timeRange, eventPointSize, eventRegionHeight } = this.props; const { timeRange, eventPointSize, eventRegionHeight } = this.props;
const events = this.sortEvents(); const events = this.sortEvents();
const acknowledges = this.getEventAcks(events);
const boxWidth = this.state.width + eventPointSize; const boxWidth = this.state.width + eventPointSize;
const boxHeight = Math.round(eventPointSize * 2.5); const boxHeight = Math.round(eventPointSize * 2.5);
const width = boxWidth - eventPointSize; const width = boxWidth - eventPointSize;
@@ -147,6 +177,16 @@ export default class ProblemTimeline extends PureComponent<ProblemTimelineProps,
highlightedRegion={this.state.highlightedRegion} highlightedRegion={this.state.highlightedRegion}
/> />
</g> </g>
<g className="timeline-acknowledges" transform={`translate(0, ${pointsYpos})`}>
<TimelineAcks
acknowledges={acknowledges}
timeRange={timeRange}
width={width}
size={eventPointSize}
onHighlight={this.handleAckHighlight}
onUnHighlight={this.handleAckUnHighlight}
/>
</g>
<g className="timeline-points" transform={`translate(0, ${pointsYpos})`}> <g className="timeline-points" transform={`translate(0, ${pointsYpos})`}>
<TimelinePoints <TimelinePoints
events={events} events={events}
@@ -239,6 +279,17 @@ class TimelineInfoContainer extends PureComponent<TimelineInfoContainerProps> {
</span> </span>
); );
} }
if (eventInfo && eventInfo.timestamp) {
const ts = moment(eventInfo.timestamp * 1000);
const tsFormatted = ts.format('HH:mm:ss');
infoItems = [
<span key="ts" className="event-timestamp">
<span className="event-timestamp-label">Time:&nbsp;</span>
<span className="event-timestamp-value">{tsFormatted}</span>
</span>
];
}
const containerStyle: React.CSSProperties = { left }; const containerStyle: React.CSSProperties = { left };
if (!show) { if (!show) {
containerStyle.opacity = 0; containerStyle.opacity = 0;
@@ -252,6 +303,14 @@ class TimelineInfoContainer extends PureComponent<TimelineInfoContainerProps> {
<div> <div>
{durationItem} {durationItem}
</div> </div>
{eventInfo && eventInfo.message &&
<div>
<span key="duration" className="event-timestamp">
<span className="event-timestamp-label">Message:&nbsp;</span>
<span className="event-timestamp-value">{eventInfo.message}</span>
</span>
</div>
}
</div> </div>
); );
} }
@@ -497,6 +556,134 @@ class TimelinePoint extends PureComponent<TimelinePointProps, TimelinePointState
} }
} }
interface TimelineAcksProps {
acknowledges: ZBXAcknowledge[];
timeRange: GFTimeRange;
width: number;
size: number;
onHighlight?: (ack: ZBXAcknowledge, index: number) => void;
onUnHighlight?: () => void;
}
interface TimelineAcksState {
order: number[];
highlighted: number;
}
class TimelineAcks extends PureComponent<TimelineAcksProps, TimelineAcksState> {
constructor(props) {
super(props);
this.state = { order: [], highlighted: null };
}
handleHighlight = (index: number) => () => {
if (this.props.onHighlight) {
const ack = this.props.acknowledges[index];
this.props.onHighlight(ack, index);
}
this.bringToFront(index, true);
}
handleUnHighlight = () => {
if (this.props.onUnHighlight) {
this.props.onUnHighlight();
}
const order = this.props.acknowledges.map((v, i) => i);
this.setState({ order, highlighted: null });
}
bringToFront = (index: number, highlight = false) => {
const { acknowledges } = this.props;
let order = acknowledges.map((v, i) => i);
order = moveToEnd(order, [index]);
const highlighted = highlight ? index : null;
this.setState({ order, highlighted });
}
render() {
const { acknowledges, timeRange, width, size } = this.props;
const { timeFrom, timeTo } = timeRange;
const range = timeTo - timeFrom;
const pointR = size / 2;
const eventsItems = acknowledges.map((ack, i) => {
const ts = Number(ack.clock);
const posLeft = Math.round((ts - timeFrom) / range * width - pointR);
const highlighted = this.state.highlighted === i;
return (
<TimelineAck
key={ack.eventid}
x={posLeft}
r={pointR}
highlighted={highlighted}
onHighlight={this.handleHighlight(i)}
onUnHighlight={this.handleUnHighlight}
/>
);
});
if (this.state.order.length) {
return this.state.order.map(i => eventsItems[i]);
}
return eventsItems;
}
}
interface TimelineAckProps {
x: number;
r: number;
highlighted?: boolean;
onHighlight?: () => void;
onUnHighlight?: () => void;
}
interface TimelineAckState {
highlighted?: boolean;
}
class TimelineAck extends PureComponent<TimelineAckProps, TimelineAckState> {
constructor(props) {
super(props);
this.state = { highlighted: false };
}
componentDidUpdate(prevProps: TimelineAckProps) {
// Update component after reordering to make animation working
if (prevProps.highlighted !== this.props.highlighted) {
this.setState({ highlighted: this.props.highlighted });
}
}
handleHighlight = () => {
if (this.props.onHighlight) {
this.props.onHighlight();
}
}
handleUnHighlight = () => {
if (this.props.onUnHighlight) {
this.props.onUnHighlight();
}
}
render() {
const { x } = this.props;
const r = this.state.highlighted ? Math.round(this.props.r * HIGHLIGHTED_POINT_SIZE) : this.props.r;
const cx = x + this.props.r;
const rInner = Math.round(r * INNER_POINT_SIZE);
const className = `problem-event-ack ${this.state.highlighted ? 'highlighted' : ''}`;
return (
<g className={className}
transform={`translate(${cx}, 0)`}
filter="url(#dropShadow)"
onMouseOver={this.handleHighlight}
onMouseLeave={this.handleUnHighlight}>
<circle cx={0} cy={0} r={r} className="point-border" />
<circle cx={0} cy={0} r={rInner} className="point-core" />
</g>
);
}
}
function moveToEnd<T>(array: T[], itemsToMove: number[]): T[] { function moveToEnd<T>(array: T[], itemsToMove: number[]): T[] {
const removed = _.pullAt(array, itemsToMove); const removed = _.pullAt(array, itemsToMove);
removed.reverse(); removed.reverse();

View File

@@ -399,7 +399,7 @@
.event-timeline { .event-timeline {
position: relative; position: relative;
margin: 0.6rem 0; margin: 0.2rem 0;
padding-top: 2.6rem; padding-top: 2.6rem;
// margin-top: auto; // margin-top: auto;
@@ -474,6 +474,32 @@
} }
} }
} }
.problem-event-ack {
circle {
transition: all 0.2s ease-out;
}
.point-border {
fill: $blue;
}
.point-core {
fill: $problem-event-core;
}
&:hover,
&.highlighted {
circle.point-border {
stroke: $problem-event-highlighted;
stroke-width: 1;
filter: url(#glowShadow);
}
.point-core {
fill: $problem-event-core-highlighted;
}
}
}
} }
} }