spikeProtectionUsageChart.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import {Component} from 'react';
  2. import type {Theme} from '@emotion/react';
  3. import {withTheme} from '@emotion/react';
  4. import type {
  5. LineSeriesOption,
  6. MarkAreaComponentOption,
  7. MarkLineComponentOption,
  8. SeriesOption,
  9. } from 'echarts';
  10. import moment from 'moment-timezone';
  11. import MarkArea from 'sentry/components/charts/components/markArea';
  12. import MarkLine from 'sentry/components/charts/components/markLine';
  13. import AreaSeries from 'sentry/components/charts/series/areaSeries';
  14. import LineSeries from 'sentry/components/charts/series/lineSeries';
  15. import {t} from 'sentry/locale';
  16. import type {DataCategoryInfo} from 'sentry/types/core';
  17. import type {UsageChartProps} from 'sentry/views/organizationStats/usageChart';
  18. import UsageChart, {ChartDataTransform} from 'sentry/views/organizationStats/usageChart';
  19. import {getDateFromMoment} from 'sentry/views/organizationStats/usageChart/utils';
  20. import type {SpikeDetails, SpikeThresholds} from 'getsentry/views/spikeProtection/types';
  21. import {getDateFromString, getOngoingSpikeInterval, getSpikeInterval} from './utils';
  22. type SpikeProtectionUsageChartProps = {
  23. dataCategoryInfo: DataCategoryInfo;
  24. isLoading: boolean;
  25. spikeThresholds: SpikeThresholds;
  26. storedSpikes: SpikeDetails[];
  27. theme: Theme;
  28. } & UsageChartProps;
  29. class SpikeProtectionUsageChart extends Component<SpikeProtectionUsageChartProps> {
  30. get spikeThresholdSeries() {
  31. const {
  32. usageDateShowUtc,
  33. usageDateInterval,
  34. spikeThresholds,
  35. dataCategoryInfo,
  36. theme,
  37. } = this.props;
  38. const spikeThresholdData = spikeThresholds?.groups?.find(
  39. group => group.billing_metric === dataCategoryInfo?.uid
  40. )?.threshold;
  41. const seriesData: LineSeriesOption['data'] = spikeThresholds.intervals.map(
  42. (interval, i) => {
  43. const dateTime = moment(interval);
  44. const threshold = spikeThresholdData?.[i] ?? 0;
  45. return {
  46. value: [
  47. getDateFromMoment(dateTime, usageDateInterval, usageDateShowUtc),
  48. threshold === 0 ? '--' : threshold,
  49. ],
  50. };
  51. }
  52. );
  53. return LineSeries({
  54. name: t('Spike Protection Threshold'),
  55. lineStyle: {type: 'dotted'},
  56. color: theme.gray300,
  57. data: seriesData,
  58. legendHoverLink: false,
  59. zlevel: 2,
  60. tooltip: {
  61. show: false,
  62. },
  63. });
  64. }
  65. get spikeRegionSeries() {
  66. const {storedSpikes, dataCategoryInfo, spikeThresholds, theme} = this.props;
  67. const formattedStoredSpikes: SpikeDetails[] = [];
  68. if (storedSpikes) {
  69. // Format the stored spikes to show on the graph
  70. // Round down the start time and round up the end time
  71. storedSpikes.forEach(spike => {
  72. const spikeDates = {startDate: '', endDate: ''};
  73. if (spike.end) {
  74. const {startDate, endDate} = getSpikeInterval(
  75. spikeThresholds.intervals,
  76. getDateFromString(spike.start),
  77. getDateFromString(spike.end)
  78. );
  79. spikeDates.startDate = startDate!;
  80. spikeDates.endDate = endDate!;
  81. } else {
  82. // For an ongoing spike, assume the spike end is the end of the graph
  83. // for visualization purposes
  84. const {startDate, endDate} = getOngoingSpikeInterval(
  85. spikeThresholds.intervals,
  86. getDateFromString(spike.start)
  87. );
  88. spikeDates.startDate = startDate!;
  89. spikeDates.endDate = endDate!;
  90. }
  91. formattedStoredSpikes.push({
  92. start: spikeDates.startDate,
  93. end: spikeDates.endDate,
  94. dropped: spike.dropped,
  95. threshold: spike.threshold,
  96. dataCategory: spike.dataCategory,
  97. });
  98. });
  99. }
  100. const {usageDateShowUtc, usageDateInterval} = this.props;
  101. const categorySpikes = formattedStoredSpikes.filter(
  102. spike => spike.dataCategory === dataCategoryInfo.name
  103. );
  104. const spikeAreaData: MarkAreaComponentOption['data'] = categorySpikes.map(
  105. ({start, end}) => {
  106. return [
  107. {xAxis: getDateFromMoment(moment(start), usageDateInterval, usageDateShowUtc)},
  108. {xAxis: getDateFromMoment(moment(end), usageDateInterval, usageDateShowUtc)},
  109. ];
  110. }
  111. );
  112. const spikeLineData: MarkLineComponentOption['data'] = categorySpikes.map(
  113. ({start}) => ({
  114. xAxis: getDateFromMoment(moment(start), usageDateInterval, usageDateShowUtc),
  115. })
  116. );
  117. return AreaSeries({
  118. name: t('Spikes'),
  119. type: 'line',
  120. markArea: MarkArea({
  121. emphasis: {itemStyle: {color: theme.red300}},
  122. data: spikeAreaData,
  123. itemStyle: {color: theme.red200, opacity: 0.2},
  124. silent: true,
  125. }),
  126. markLine: MarkLine({
  127. label: {show: false},
  128. silent: true,
  129. data: spikeLineData,
  130. itemStyle: {color: theme.red200},
  131. lineStyle: {type: 'solid'},
  132. }),
  133. itemStyle: {
  134. color: theme.red100,
  135. borderColor: theme.red300,
  136. borderWidth: 0.1,
  137. },
  138. zlevel: 0,
  139. data: [],
  140. });
  141. }
  142. get chartSeries() {
  143. const chartSeries: SeriesOption[] = [];
  144. const {spikeThresholds, dataTransform} = this.props;
  145. if (dataTransform === ChartDataTransform.CUMULATIVE) {
  146. return chartSeries;
  147. }
  148. if (spikeThresholds) {
  149. chartSeries.push(this.spikeThresholdSeries);
  150. chartSeries.push(this.spikeRegionSeries);
  151. }
  152. return chartSeries;
  153. }
  154. render() {
  155. const {isLoading} = this.props;
  156. return (
  157. <UsageChart {...this.props} isLoading={isLoading} chartSeries={this.chartSeries} />
  158. );
  159. }
  160. }
  161. export default withTheme(SpikeProtectionUsageChart);