eventChart.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import {Component} from 'react';
  2. import type {Theme} from '@emotion/react';
  3. import type {Client} from 'sentry/api';
  4. import MiniBarChart from 'sentry/components/charts/miniBarChart';
  5. import LoadingError from 'sentry/components/loadingError';
  6. import LoadingIndicator from 'sentry/components/loadingIndicator';
  7. import {t} from 'sentry/locale';
  8. import type {TimeseriesValue} from 'sentry/types/core';
  9. import type {SeriesDataUnit} from 'sentry/types/echarts';
  10. import withApi from 'sentry/utils/withApi';
  11. type Props = {
  12. api: Client;
  13. resolution: string;
  14. since: number;
  15. theme: Theme;
  16. };
  17. type State = {
  18. error: boolean;
  19. loading: boolean;
  20. rawData: Record<string, TimeseriesValue[]>;
  21. stats: Record<string, SeriesDataUnit[]>;
  22. };
  23. const initialState: State = {
  24. error: false,
  25. loading: true,
  26. rawData: {
  27. 'events.total': [],
  28. 'events.dropped': [],
  29. },
  30. stats: {received: [], rejected: []},
  31. };
  32. class EventChart extends Component<Props, State> {
  33. state: State = initialState;
  34. UNSAFE_componentWillMount() {
  35. this.fetchData();
  36. }
  37. UNSAFE_componentWillReceiveProps(nextProps: Props) {
  38. if (this.props.since !== nextProps.since) {
  39. this.setState(initialState, this.fetchData);
  40. }
  41. }
  42. fetchData = () => {
  43. const statNameList = ['events.total', 'events.dropped'];
  44. statNameList.forEach(statName => {
  45. // query the organization stats via a separate call as its possible the project stats
  46. // are too heavy
  47. this.props.api.request('/internal/stats/', {
  48. method: 'GET',
  49. data: {
  50. since: this.props.since,
  51. resolution: this.props.resolution,
  52. key: statName,
  53. },
  54. success: data => {
  55. this.setState(prevState => {
  56. const rawData = prevState.rawData;
  57. rawData[statName] = data;
  58. return {
  59. rawData,
  60. };
  61. }, this.requestFinished);
  62. },
  63. error: () => {
  64. this.setState({
  65. error: true,
  66. });
  67. },
  68. });
  69. });
  70. };
  71. requestFinished() {
  72. const {rawData} = this.state;
  73. if (rawData['events.total'] && rawData['events.dropped']) {
  74. this.processOrgData();
  75. }
  76. }
  77. processOrgData() {
  78. const {rawData} = this.state;
  79. const sReceived: Record<string, number> = {};
  80. const sRejected: Record<string, number> = {};
  81. const aReceived = [0, 0]; // received, points
  82. rawData['events.total']!.forEach((point, idx) => {
  83. const dReceived = point[1];
  84. const dRejected = rawData['events.dropped']![idx]?.[1];
  85. const ts = point[0];
  86. if (sReceived[ts] === undefined) {
  87. sReceived[ts] = dReceived;
  88. sRejected[ts] = dRejected!;
  89. } else {
  90. sReceived[ts] += dReceived;
  91. sRejected[ts]! += dRejected!;
  92. }
  93. if (dReceived > 0) {
  94. aReceived[0]! += dReceived;
  95. aReceived[1]! += 1;
  96. }
  97. });
  98. this.setState({
  99. stats: {
  100. rejected: Object.keys(sRejected).map(ts => ({
  101. name: parseInt(ts, 10) * 1000,
  102. value: sRejected[ts] || 0,
  103. })),
  104. accepted: Object.keys(sReceived).map(ts =>
  105. // total number of events accepted (received - rejected)
  106. ({name: parseInt(ts, 10) * 1000, value: sReceived[ts]! - sRejected[ts]!})
  107. ),
  108. },
  109. loading: false,
  110. });
  111. }
  112. getChartSeries() {
  113. const {stats} = this.state;
  114. return [
  115. {
  116. seriesName: t('Accepted'),
  117. data: stats.accepted!,
  118. color: this.props.theme.blue300,
  119. },
  120. {
  121. seriesName: t('Dropped'),
  122. data: stats.rejected!,
  123. color: this.props.theme.red200,
  124. },
  125. ];
  126. }
  127. render() {
  128. const {loading, error} = this.state;
  129. if (loading) {
  130. return <LoadingIndicator />;
  131. }
  132. if (error) {
  133. return <LoadingError onRetry={this.fetchData} />;
  134. }
  135. const series = this.getChartSeries();
  136. const colors = series.map(({color}) => color);
  137. return (
  138. <MiniBarChart
  139. series={series}
  140. colors={colors}
  141. height={110}
  142. stacked
  143. isGroupedByDate
  144. showTimeInTooltip
  145. labelYAxisExtents
  146. />
  147. );
  148. }
  149. }
  150. export default withApi(EventChart);