vitalsPanel.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import React from 'react';
  2. import {Location} from 'history';
  3. import {Panel} from 'app/components/panels';
  4. import {Organization} from 'app/types';
  5. import EventView from 'app/utils/discover/eventView';
  6. import {WebVital} from 'app/utils/discover/fields';
  7. import HistogramQuery from 'app/utils/performance/histogram/histogramQuery';
  8. import {DataFilter, HistogramData} from 'app/utils/performance/histogram/types';
  9. import {WEB_VITAL_DETAILS} from 'app/utils/performance/vitals/constants';
  10. import {VitalGroup} from 'app/utils/performance/vitals/types';
  11. import VitalsCardDiscoverQuery, {
  12. VitalData,
  13. } from 'app/utils/performance/vitals/vitalsCardsDiscoverQuery';
  14. import {decodeScalar} from 'app/utils/queryString';
  15. import {NUM_BUCKETS, VITAL_GROUPS} from './constants';
  16. import VitalCard from './vitalCard';
  17. type Props = {
  18. organization: Organization;
  19. location: Location;
  20. eventView: EventView;
  21. dataFilter?: DataFilter;
  22. };
  23. class VitalsPanel extends React.Component<Props> {
  24. renderVitalCard(
  25. vital: WebVital,
  26. isLoading: boolean,
  27. error: boolean,
  28. data: VitalData | null,
  29. histogram: HistogramData,
  30. color: [string],
  31. min?: number,
  32. max?: number,
  33. precision?: number
  34. ) {
  35. const {location, organization, eventView, dataFilter} = this.props;
  36. const vitalDetails = WEB_VITAL_DETAILS[vital];
  37. const zoomed = min !== undefined || max !== undefined;
  38. return (
  39. <HistogramQuery
  40. location={location}
  41. orgSlug={organization.slug}
  42. eventView={eventView}
  43. numBuckets={NUM_BUCKETS}
  44. fields={zoomed ? [vital] : []}
  45. min={min}
  46. max={max}
  47. precision={precision}
  48. dataFilter={dataFilter}
  49. >
  50. {results => {
  51. const loading = zoomed ? results.isLoading : isLoading;
  52. const errored = zoomed ? results.error !== null : error;
  53. const chartData = zoomed ? results.histograms?.[vital] ?? histogram : histogram;
  54. return (
  55. <VitalCard
  56. location={location}
  57. isLoading={loading}
  58. error={errored}
  59. vital={vital}
  60. vitalDetails={vitalDetails}
  61. summaryData={data}
  62. chartData={chartData}
  63. colors={color}
  64. eventView={eventView}
  65. organization={organization}
  66. min={min}
  67. max={max}
  68. precision={precision}
  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, {start: string | undefined; end: 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. <React.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. <React.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. </React.Fragment>
  130. );
  131. })}
  132. </React.Fragment>
  133. );
  134. }}
  135. </HistogramQuery>
  136. );
  137. }
  138. render() {
  139. const {location, organization, eventView} = this.props;
  140. const allVitals = VITAL_GROUPS.reduce((keys: WebVital[], {vitals}) => {
  141. return keys.concat(vitals);
  142. }, []);
  143. return (
  144. <Panel>
  145. <VitalsCardDiscoverQuery
  146. eventView={eventView}
  147. orgSlug={organization.slug}
  148. location={location}
  149. vitals={allVitals}
  150. >
  151. {results => (
  152. <React.Fragment>
  153. {VITAL_GROUPS.map(vitalGroup => (
  154. <React.Fragment key={vitalGroup.vitals.join('')}>
  155. {this.renderVitalGroup(vitalGroup, results)}
  156. </React.Fragment>
  157. ))}
  158. </React.Fragment>
  159. )}
  160. </VitalsCardDiscoverQuery>
  161. </Panel>
  162. );
  163. }
  164. }
  165. function parseBound(
  166. boundString: string | undefined,
  167. precision: number | undefined
  168. ): number | undefined {
  169. if (boundString === undefined) {
  170. return undefined;
  171. } else if (precision === undefined || precision === 0) {
  172. return parseInt(boundString, 10);
  173. }
  174. return parseFloat(boundString);
  175. }
  176. export default VitalsPanel;