metricIssueChart.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import styled from '@emotion/styled';
  2. import {AreaChart} from 'sentry/components/charts/areaChart';
  3. import {useChartZoom} from 'sentry/components/charts/useChartZoom';
  4. import {Alert} from 'sentry/components/core/alert';
  5. import Placeholder from 'sentry/components/placeholder';
  6. import {t} from 'sentry/locale';
  7. import {space} from 'sentry/styles/space';
  8. import type {Group} from 'sentry/types/group';
  9. import type {Project} from 'sentry/types/project';
  10. import useOrganization from 'sentry/utils/useOrganization';
  11. import type {TimePeriodType} from 'sentry/views/alerts/rules/metric/details/constants';
  12. import type {MetricRule} from 'sentry/views/alerts/rules/metric/types';
  13. import {useMetricRule} from 'sentry/views/alerts/rules/metric/utils/useMetricRule';
  14. import type {Anomaly, Incident} from 'sentry/views/alerts/types';
  15. import {useMetricAnomalies} from 'sentry/views/issueDetails/metricIssues/useMetricAnomalies';
  16. import {useMetricIncidents} from 'sentry/views/issueDetails/metricIssues/useMetricIncidents';
  17. import {useMetricStatsChart} from 'sentry/views/issueDetails/metricIssues/useMetricStatsChart';
  18. import {
  19. useMetricIssueAlertId,
  20. useMetricIssueLegend,
  21. useMetricTimePeriod,
  22. } from 'sentry/views/issueDetails/metricIssues/utils';
  23. interface MetricIssueChartProps {
  24. group: Group;
  25. project: Project;
  26. }
  27. export function MetricIssueChart({group, project}: MetricIssueChartProps) {
  28. const organization = useOrganization();
  29. const ruleId = useMetricIssueAlertId({groupId: group.id});
  30. const timePeriod = useMetricTimePeriod();
  31. const {
  32. data: rule,
  33. isLoading: isRuleLoading,
  34. isError: isRuleError,
  35. } = useMetricRule(
  36. {
  37. orgSlug: organization.slug,
  38. ruleId: ruleId ?? '',
  39. query: {
  40. expand: 'latestIncident',
  41. },
  42. },
  43. {
  44. staleTime: Infinity,
  45. retry: false,
  46. enabled: !!ruleId,
  47. }
  48. );
  49. const {data: anomalies = [], isLoading: isAnomaliesLoading} = useMetricAnomalies(
  50. {
  51. orgSlug: organization.slug,
  52. ruleId: ruleId ?? '',
  53. query: {
  54. start: timePeriod.start,
  55. end: timePeriod.end,
  56. },
  57. },
  58. {
  59. enabled:
  60. !!ruleId && organization.features.includes('anomaly-detection-alerts-charts'),
  61. }
  62. );
  63. const {data: incidents = [], isLoading: isIncidentsLoading} = useMetricIncidents(
  64. {
  65. orgSlug: organization.slug,
  66. query: {
  67. alertRule: ruleId ?? '',
  68. start: timePeriod.start,
  69. end: timePeriod.end,
  70. project: project.id,
  71. },
  72. },
  73. {
  74. enabled: !!ruleId,
  75. }
  76. );
  77. if (isRuleLoading || isAnomaliesLoading || isIncidentsLoading || !rule) {
  78. return (
  79. <MetricChartSection>
  80. <MetricIssuePlaceholder type="loading" />
  81. </MetricChartSection>
  82. );
  83. }
  84. if (isRuleError) {
  85. return <MetricIssuePlaceholder type="error" />;
  86. }
  87. return (
  88. <MetricChartSection>
  89. <MetricIssueChartContent
  90. rule={rule}
  91. timePeriod={timePeriod}
  92. project={project}
  93. anomalies={anomalies}
  94. incidents={incidents}
  95. />
  96. </MetricChartSection>
  97. );
  98. }
  99. /**
  100. * This component is nested to avoid trying to fetch data without a rule or time period.
  101. */
  102. function MetricIssueChartContent({
  103. rule,
  104. timePeriod,
  105. project,
  106. anomalies,
  107. incidents,
  108. }: {
  109. project: Project;
  110. rule: MetricRule;
  111. timePeriod: TimePeriodType;
  112. anomalies?: Anomaly[];
  113. incidents?: Incident[];
  114. }) {
  115. const chartZoomProps = useChartZoom({saveOnZoom: true});
  116. const {chartProps, queryResult} = useMetricStatsChart({
  117. project,
  118. rule,
  119. timePeriod,
  120. anomalies,
  121. incidents,
  122. referrer: 'metric-issue-chart',
  123. });
  124. const {series = [], yAxis, ...otherChartProps} = chartProps;
  125. const legend = useMetricIssueLegend({rule, series});
  126. if (queryResult?.isLoading) {
  127. return <MetricIssuePlaceholder type="loading" />;
  128. }
  129. if (queryResult?.isError) {
  130. return <MetricIssuePlaceholder type="error" />;
  131. }
  132. return (
  133. <ChartContainer role="figure">
  134. <AreaChart
  135. {...otherChartProps}
  136. series={series}
  137. legend={{...legend, top: 4, right: 4}}
  138. height={100}
  139. grid={{
  140. top: 20,
  141. right: 0,
  142. left: 0,
  143. bottom: 0,
  144. }}
  145. yAxis={{
  146. ...yAxis,
  147. splitNumber: 2,
  148. }}
  149. {...chartZoomProps}
  150. />
  151. </ChartContainer>
  152. );
  153. }
  154. function MetricIssuePlaceholder({type}: {type: 'loading' | 'error'}) {
  155. return type === 'loading' ? (
  156. <PlaceholderContainer>
  157. <Placeholder height="96px" testId="metric-issue-chart-loading" />
  158. </PlaceholderContainer>
  159. ) : (
  160. <MetricChartAlert type="error" showIcon data-test-id="metric-issue-chart-error">
  161. {t('Unable to load the metric history')}
  162. </MetricChartAlert>
  163. );
  164. }
  165. const MetricChartSection = styled('div')`
  166. display: block;
  167. padding-right: ${space(1.5)};
  168. width: 100%;
  169. `;
  170. const PlaceholderContainer = styled('div')`
  171. padding: ${space(1)} 0;
  172. `;
  173. const ChartContainer = styled('div')`
  174. position: relative;
  175. padding: ${space(0.75)} 0;
  176. `;
  177. const MetricChartAlert = styled(Alert)`
  178. width: 100%;
  179. border: 0;
  180. border-radius: 0;
  181. `;