prometheus

Форк
0
286 строк · 9.5 Кб
1
import $ from 'jquery';
2
import React, { PureComponent } from 'react';
3
import ReactResizeDetector from 'react-resize-detector';
4

5
import { Legend } from './Legend';
6
import { ExemplarData, Histogram, Metric, QueryParams } from '../../types/types';
7
import { isPresent } from '../../utils';
8
import { getOptions, normalizeData, toHoverColor } from './GraphHelpers';
9
import { Button } from 'reactstrap';
10
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
11
import { faTimes } from '@fortawesome/free-solid-svg-icons';
12
import { GraphDisplayMode } from './Panel';
13

14
require('../../vendor/flot/jquery.flot');
15
require('../../vendor/flot/jquery.flot.stack');
16
require('../../vendor/flot/jquery.flot.time');
17
require('../../vendor/flot/jquery.flot.crosshair');
18
require('../../vendor/flot/jquery.flot.selection');
19
require('../../vendor/flot/jquery.flot.heatmap');
20
require('jquery.flot.tooltip');
21

22
export interface GraphProps {
23
  data: {
24
    resultType: string;
25
    result: Array<{ metric: Metric; values?: [number, string][]; histograms?: [number, Histogram][] }>;
26
  };
27
  exemplars: ExemplarData;
28
  displayMode: GraphDisplayMode;
29
  useLocalTime: boolean;
30
  showExemplars: boolean;
31
  handleTimeRangeSelection: (startTime: number, endTime: number) => void;
32
  queryParams: QueryParams | null;
33
  id: string;
34
}
35

36
export interface GraphSeries {
37
  labels: { [key: string]: string };
38
  color: string;
39
  data: (number | null)[][]; // [x,y][]
40
  index: number;
41
}
42

43
export interface GraphExemplar {
44
  seriesLabels: { [key: string]: string };
45
  labels: { [key: string]: string };
46
  data: number[][];
47
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
48
  points: any; // This is used to specify the symbol.
49
  color: string;
50
}
51

52
export interface GraphData {
53
  series: GraphSeries[];
54
  exemplars: GraphExemplar[];
55
}
56

57
interface GraphState {
58
  chartData: GraphData;
59
  selectedExemplarLabels: { exemplar: { [key: string]: string }; series: { [key: string]: string } };
60
}
61

62
class Graph extends PureComponent<GraphProps, GraphState> {
63
  private chartRef = React.createRef<HTMLDivElement>();
64
  private $chart?: jquery.flot.plot;
65
  private rafID = 0;
66
  private selectedSeriesIndexes: number[] = [];
67

68
  state = {
69
    chartData: normalizeData(this.props),
70
    selectedExemplarLabels: { exemplar: {}, series: {} },
71
  };
72

73
  componentDidUpdate(prevProps: GraphProps): void {
74
    const { data, displayMode, useLocalTime, showExemplars } = this.props;
75
    if (prevProps.data !== data) {
76
      this.selectedSeriesIndexes = [];
77
      this.setState({ chartData: normalizeData(this.props) }, this.plot);
78
    } else if (prevProps.displayMode !== displayMode) {
79
      this.setState({ chartData: normalizeData(this.props) }, () => {
80
        if (this.selectedSeriesIndexes.length === 0) {
81
          this.plot();
82
        } else {
83
          this.plot([
84
            ...this.state.chartData.series.filter((_, i) => this.selectedSeriesIndexes.includes(i)),
85
            ...this.state.chartData.exemplars,
86
          ]);
87
        }
88
      });
89
    }
90

91
    if (prevProps.useLocalTime !== useLocalTime) {
92
      this.plot();
93
    }
94

95
    if (prevProps.showExemplars !== showExemplars && !showExemplars) {
96
      this.setState(
97
        {
98
          chartData: { series: this.state.chartData.series, exemplars: [] },
99
          selectedExemplarLabels: { exemplar: {}, series: {} },
100
        },
101
        () => {
102
          this.plot();
103
        }
104
      );
105
    }
106
  }
107

108
  componentDidMount(): void {
109
    this.plot();
110

111
    $(`.graph-${this.props.id}`).bind('plotclick', (event, pos, item) => {
112
      // If an item has the series label property that means it's an exemplar.
113
      if (item && 'seriesLabels' in item.series) {
114
        this.setState({
115
          selectedExemplarLabels: { exemplar: item.series.labels, series: item.series.seriesLabels },
116
          chartData: this.state.chartData,
117
        });
118
      } else {
119
        this.setState({
120
          chartData: this.state.chartData,
121
          selectedExemplarLabels: { exemplar: {}, series: {} },
122
        });
123
      }
124
    });
125

126
    $(`.graph-${this.props.id}`).bind('plotselected', (_, ranges) => {
127
      if (isPresent(this.$chart)) {
128
        // eslint-disable-next-line
129
        // @ts-ignore Typescript doesn't think this method exists although it actually does.
130
        this.$chart.clearSelection();
131
        this.props.handleTimeRangeSelection(ranges.xaxis.from, ranges.xaxis.to);
132
      }
133
    });
134
  }
135

136
  componentWillUnmount(): void {
137
    this.destroyPlot();
138
  }
139

140
  plot = (
141
    data: (GraphSeries | GraphExemplar)[] = [...this.state.chartData.series, ...this.state.chartData.exemplars]
142
  ): void => {
143
    if (!this.chartRef.current) {
144
      return;
145
    }
146
    this.destroyPlot();
147

148
    const options = getOptions(this.props.displayMode === GraphDisplayMode.Stacked, this.props.useLocalTime);
149
    const isHeatmap = this.props.displayMode === GraphDisplayMode.Heatmap;
150
    options.series.heatmap = isHeatmap;
151

152
    if (options.yaxis && isHeatmap) {
153
      options.yaxis.ticks = () => new Array(data.length + 1).fill(0).map((_el, i) => i);
154
      options.yaxis.tickFormatter = (val) => `${val ? data[val - 1].labels.le : ''}`;
155
      options.yaxis.min = 0;
156
      options.yaxis.max = data.length;
157
      options.series.lines = { show: false };
158
    }
159
    this.$chart = $.plot($(this.chartRef.current), data, options);
160
  };
161

162
  destroyPlot = (): void => {
163
    if (isPresent(this.$chart)) {
164
      this.$chart.destroy();
165
    }
166
  };
167

168
  plotSetAndDraw(
169
    data: (GraphSeries | GraphExemplar)[] = [...this.state.chartData.series, ...this.state.chartData.exemplars]
170
  ): void {
171
    if (isPresent(this.$chart)) {
172
      this.$chart.setData(data);
173
      this.$chart.draw();
174
    }
175
  }
176

177
  handleSeriesSelect = (selected: number[], selectedIndex: number): void => {
178
    const { chartData } = this.state;
179
    this.plot(
180
      this.selectedSeriesIndexes.length === 1 && this.selectedSeriesIndexes.includes(selectedIndex)
181
        ? [
182
            ...chartData.series.map(toHoverColor(selectedIndex, this.props.displayMode === GraphDisplayMode.Stacked)),
183
            ...chartData.exemplars,
184
          ]
185
        : [
186
            ...chartData.series.filter((_, i) => selected.includes(i)),
187
            ...chartData.exemplars.filter((exemplar) => {
188
              series: for (const i in selected) {
189
                for (const name in chartData.series[selected[i]].labels) {
190
                  if (exemplar.seriesLabels[name] !== chartData.series[selected[i]].labels[name]) {
191
                    continue series;
192
                  }
193
                }
194
                return true;
195
              }
196
              return false;
197
            }),
198
          ] // draw only selected
199
    );
200
    this.selectedSeriesIndexes = selected;
201
  };
202

203
  handleSeriesHover = (index: number) => (): void => {
204
    if (this.rafID) {
205
      cancelAnimationFrame(this.rafID);
206
    }
207
    this.rafID = requestAnimationFrame(() => {
208
      this.plotSetAndDraw([
209
        ...this.state.chartData.series.map(toHoverColor(index, this.props.displayMode === GraphDisplayMode.Stacked)),
210
        ...this.state.chartData.exemplars,
211
      ]);
212
    });
213
  };
214

215
  handleLegendMouseOut = (): void => {
216
    cancelAnimationFrame(this.rafID);
217
    this.plotSetAndDraw();
218
  };
219

220
  handleResize = (): void => {
221
    if (isPresent(this.$chart)) {
222
      this.plot(this.$chart.getData() as (GraphSeries | GraphExemplar)[]);
223
    }
224
  };
225

226
  render(): JSX.Element {
227
    const { chartData, selectedExemplarLabels } = this.state;
228
    const selectedLabels = selectedExemplarLabels as {
229
      exemplar: { [key: string]: string };
230
      series: { [key: string]: string };
231
    };
232
    return (
233
      <div className={`graph-${this.props.id}`}>
234
        <ReactResizeDetector handleWidth onResize={this.handleResize} skipOnMount />
235
        <div className="graph-chart" ref={this.chartRef} />
236
        {Object.keys(selectedLabels.exemplar).length > 0 ? (
237
          <div className="graph-selected-exemplar">
238
            <div className="font-weight-bold">Selected exemplar labels:</div>
239
            <div className="labels mt-1 ml-3">
240
              {Object.keys(selectedLabels.exemplar).map((k, i) => (
241
                <div key={i}>
242
                  <strong>{k}</strong>: {selectedLabels.exemplar[k]}
243
                </div>
244
              ))}
245
            </div>
246
            <div className="font-weight-bold mt-3">Associated series labels:</div>
247
            <div className="labels mt-1 ml-3">
248
              {Object.keys(selectedLabels.series).map((k, i) => (
249
                <div key={i}>
250
                  <strong>{k}</strong>: {selectedLabels.series[k]}
251
                </div>
252
              ))}
253
            </div>
254
            <Button
255
              size="small"
256
              color="light"
257
              style={{ position: 'absolute', top: 5, right: 5 }}
258
              title="Hide selected exemplar details"
259
              onClick={() =>
260
                this.setState({
261
                  chartData: this.state.chartData,
262
                  selectedExemplarLabels: { exemplar: {}, series: {} },
263
                })
264
              }
265
            >
266
              <FontAwesomeIcon icon={faTimes} />
267
            </Button>
268
          </div>
269
        ) : null}
270
        {this.props.displayMode !== GraphDisplayMode.Heatmap && (
271
          <Legend
272
            shouldReset={this.selectedSeriesIndexes.length === 0}
273
            chartData={chartData.series}
274
            onHover={this.handleSeriesHover}
275
            onLegendMouseOut={this.handleLegendMouseOut}
276
            onSeriesToggle={this.handleSeriesSelect}
277
          />
278
        )}
279
        {/* This is to make sure the graph box expands when the selected exemplar info pops up. */}
280
        <br style={{ clear: 'both' }} />
281
      </div>
282
    );
283
  }
284
}
285

286
export default Graph;
287

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.