vitalsPanel.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import {Component, Fragment} from 'react';
  2. import type {Location} from 'history';
  3. import Panel from 'sentry/components/panels/panel';
  4. import type {Organization} from 'sentry/types';
  5. import type EventView from 'sentry/utils/discover/eventView';
  6. import type {WebVital} from 'sentry/utils/fields';
  7. import HistogramQuery from 'sentry/utils/performance/histogram/histogramQuery';
  8. import type {DataFilter, HistogramData} from 'sentry/utils/performance/histogram/types';
  9. import {WEB_VITAL_DETAILS} from 'sentry/utils/performance/vitals/constants';
  10. import type {VitalGroup} from 'sentry/utils/performance/vitals/types';
  11. import type {VitalData} from 'sentry/utils/performance/vitals/vitalsCardsDiscoverQuery';
  12. import {decodeScalar} from 'sentry/utils/queryString';
  13. import {NUM_BUCKETS, VITAL_GROUPS} from './constants';
  14. import VitalCard from './vitalCard';
  15. type Props = {
  16. eventView: EventView;
  17. location: Location;
  18. organization: Organization;
  19. results: object;
  20. dataFilter?: DataFilter;
  21. };
  22. class VitalsPanel extends Component<Props> {
  23. renderVitalCard(
  24. vital: WebVital,
  25. isLoading: boolean,
  26. error: boolean,
  27. data: VitalData | null,
  28. histogram: HistogramData,
  29. color: [string],
  30. min?: number,
  31. max?: number,
  32. precision?: number
  33. ) {
  34. const {location, organization, eventView, dataFilter} = this.props;
  35. const vitalDetails = WEB_VITAL_DETAILS[vital];
  36. const zoomed = min !== undefined || max !== undefined;
  37. return (
  38. <HistogramQuery
  39. location={location}
  40. orgSlug={organization.slug}
  41. eventView={eventView}
  42. numBuckets={NUM_BUCKETS}
  43. fields={zoomed ? [vital] : []}
  44. min={min}
  45. max={max}
  46. precision={precision}
  47. dataFilter={dataFilter}
  48. >
  49. {results => {
  50. const loading = zoomed ? results.isLoading : isLoading;
  51. const errored = zoomed ? results.error !== null : error;
  52. const chartData = zoomed ? results.histograms?.[vital] ?? histogram : histogram;
  53. return (
  54. <VitalCard
  55. location={location}
  56. isLoading={loading}
  57. error={errored}
  58. vital={vital}
  59. vitalDetails={vitalDetails}
  60. summaryData={data}
  61. chartData={chartData}
  62. colors={color}
  63. eventView={eventView}
  64. organization={organization}
  65. min={min}
  66. max={max}
  67. precision={precision}
  68. dataFilter={dataFilter}
  69. />
  70. );
  71. }}
  72. </HistogramQuery>
  73. );
  74. }
  75. renderVitalGroup(group: VitalGroup, summaryResults) {
  76. const {location, organization, eventView, dataFilter} = this.props;
  77. const {vitals, colors, min, max, precision} = group;
  78. const bounds = vitals.reduce(
  79. (
  80. allBounds: Partial<
  81. Record<WebVital, {end: string | undefined; start: string | undefined}>
  82. >,
  83. vital: WebVital
  84. ) => {
  85. const slug = WEB_VITAL_DETAILS[vital].slug;
  86. allBounds[vital] = {
  87. start: decodeScalar(location.query[`${slug}Start`]),
  88. end: decodeScalar(location.query[`${slug}End`]),
  89. };
  90. return allBounds;
  91. },
  92. {}
  93. );
  94. return (
  95. <HistogramQuery
  96. location={location}
  97. orgSlug={organization.slug}
  98. eventView={eventView}
  99. numBuckets={NUM_BUCKETS}
  100. fields={vitals}
  101. min={min}
  102. max={max}
  103. precision={precision}
  104. dataFilter={dataFilter}
  105. >
  106. {multiHistogramResults => {
  107. const isLoading = summaryResults.isLoading || multiHistogramResults.isLoading;
  108. const error =
  109. summaryResults.error !== null || multiHistogramResults.error !== null;
  110. return (
  111. <Fragment>
  112. {vitals.map((vital, index) => {
  113. const data = summaryResults?.vitalsData?.[vital] ?? null;
  114. const histogram = multiHistogramResults.histograms?.[vital] ?? [];
  115. const {start, end} = bounds[vital] ?? {};
  116. return (
  117. <Fragment key={vital}>
  118. {this.renderVitalCard(
  119. vital,
  120. isLoading,
  121. error,
  122. data,
  123. histogram,
  124. [colors[index]],
  125. parseBound(start, precision),
  126. parseBound(end, precision),
  127. precision
  128. )}
  129. </Fragment>
  130. );
  131. })}
  132. </Fragment>
  133. );
  134. }}
  135. </HistogramQuery>
  136. );
  137. }
  138. render() {
  139. const {results} = this.props;
  140. return (
  141. <Panel>
  142. <Fragment>
  143. {VITAL_GROUPS.map(vitalGroup => (
  144. <Fragment key={vitalGroup.vitals.join('')}>
  145. {this.renderVitalGroup(vitalGroup, results)}
  146. </Fragment>
  147. ))}
  148. </Fragment>
  149. </Panel>
  150. );
  151. }
  152. }
  153. function parseBound(
  154. boundString: string | undefined,
  155. precision: number | undefined
  156. ): number | undefined {
  157. if (boundString === undefined) {
  158. return undefined;
  159. }
  160. if (precision === undefined || precision === 0) {
  161. return parseInt(boundString, 10);
  162. }
  163. return parseFloat(boundString);
  164. }
  165. export default VitalsPanel;