widgetDetails.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import {useCallback, useMemo, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import {
  4. type Field,
  5. MetricSamplesTable,
  6. SearchableMetricSamplesTable,
  7. } from 'sentry/components/ddm/metricSamplesTable';
  8. import {TabList, TabPanels, Tabs} from 'sentry/components/tabs';
  9. import {Tooltip} from 'sentry/components/tooltip';
  10. import {t} from 'sentry/locale';
  11. import {space} from 'sentry/styles/space';
  12. import type {MRI} from 'sentry/types';
  13. import {trackAnalytics} from 'sentry/utils/analytics';
  14. import {isCustomMetric} from 'sentry/utils/metrics';
  15. import type {
  16. FocusedMetricsSeries,
  17. MetricQueryWidgetParams,
  18. MetricWidgetQueryParams,
  19. } from 'sentry/utils/metrics/types';
  20. import {MetricQueryType} from 'sentry/utils/metrics/types';
  21. import type {MetricsSamplesResults} from 'sentry/utils/metrics/useMetricsSamples';
  22. import useOrganization from 'sentry/utils/useOrganization';
  23. import {CodeLocations} from 'sentry/views/ddm/codeLocations';
  24. import type {FocusAreaProps} from 'sentry/views/ddm/context';
  25. import {useDDMContext} from 'sentry/views/ddm/context';
  26. import type {SamplesTableProps} from 'sentry/views/ddm/sampleTable';
  27. import {SampleTable} from 'sentry/views/ddm/sampleTable';
  28. import {getQueryWithFocusedSeries} from 'sentry/views/ddm/utils';
  29. enum Tab {
  30. SAMPLES = 'samples',
  31. CODE_LOCATIONS = 'codeLocations',
  32. }
  33. export function WidgetDetails() {
  34. const {
  35. selectedWidgetIndex,
  36. widgets,
  37. focusArea,
  38. setHighlightedSampleId,
  39. setMetricsSamples,
  40. } = useDDMContext();
  41. const selectedWidget = widgets[selectedWidgetIndex] as
  42. | MetricWidgetQueryParams
  43. | undefined;
  44. const handleSampleRowHover = useCallback(
  45. (sampleId?: string) => {
  46. setHighlightedSampleId(sampleId);
  47. },
  48. [setHighlightedSampleId]
  49. );
  50. // TODO(aknaus): better fallback
  51. if (selectedWidget?.type === MetricQueryType.FORMULA) {
  52. <MetricDetails onRowHover={handleSampleRowHover} focusArea={focusArea} />;
  53. }
  54. const {mri, op, query, focusedSeries} = selectedWidget as MetricQueryWidgetParams;
  55. return (
  56. <MetricDetails
  57. mri={mri}
  58. op={op}
  59. query={query}
  60. focusedSeries={focusedSeries}
  61. onRowHover={handleSampleRowHover}
  62. setMetricsSamples={setMetricsSamples}
  63. focusArea={focusArea}
  64. />
  65. );
  66. }
  67. interface MetricDetailsProps {
  68. focusArea?: FocusAreaProps;
  69. focusedSeries?: FocusedMetricsSeries[];
  70. mri?: MRI;
  71. onRowHover?: SamplesTableProps['onRowHover'];
  72. op?: string;
  73. query?: string;
  74. setMetricsSamples?: React.Dispatch<
  75. React.SetStateAction<MetricsSamplesResults<Field>['data'] | undefined>
  76. >;
  77. }
  78. // TODO: add types
  79. export function MetricDetails({
  80. mri,
  81. op,
  82. query,
  83. focusedSeries,
  84. onRowHover,
  85. focusArea,
  86. setMetricsSamples,
  87. }: MetricDetailsProps) {
  88. const organization = useOrganization();
  89. const [selectedTab, setSelectedTab] = useState(Tab.SAMPLES);
  90. const isCodeLocationsDisabled = mri && !isCustomMetric({mri});
  91. if (isCodeLocationsDisabled && selectedTab === Tab.CODE_LOCATIONS) {
  92. setSelectedTab(Tab.SAMPLES);
  93. }
  94. const queryWithFocusedSeries = useMemo(
  95. () => focusedSeries && getQueryWithFocusedSeries(query || '', focusedSeries),
  96. [focusedSeries, query]
  97. );
  98. const handleTabChange = useCallback(
  99. (tab: Tab) => {
  100. if (tab === Tab.CODE_LOCATIONS) {
  101. trackAnalytics('ddm.code-locations', {
  102. organization,
  103. });
  104. }
  105. setSelectedTab(tab);
  106. },
  107. [organization]
  108. );
  109. return (
  110. <TrayWrapper>
  111. <Tabs value={selectedTab} onChange={handleTabChange}>
  112. <TabList>
  113. <TabList.Item key={Tab.SAMPLES}>{t('Sampled Events')}</TabList.Item>
  114. <TabList.Item
  115. textValue={t('Code Location')}
  116. key={Tab.CODE_LOCATIONS}
  117. disabled={isCodeLocationsDisabled}
  118. >
  119. <Tooltip
  120. title={t(
  121. 'This metric is automatically collected by Sentry. It is not bound to a specific line of your code.'
  122. )}
  123. disabled={!isCodeLocationsDisabled}
  124. >
  125. <span style={{pointerEvents: 'all'}}>{t('Code Location')}</span>
  126. </Tooltip>
  127. </TabList.Item>
  128. </TabList>
  129. <ContentWrapper>
  130. <TabPanels>
  131. <TabPanels.Item key={Tab.SAMPLES}>
  132. {organization.features.includes('metrics-samples-list') ? (
  133. organization.features.includes('metrics-samples-list-search') ? (
  134. <SearchableMetricSamplesTable
  135. focusArea={focusArea?.selection?.range}
  136. mri={mri}
  137. onRowHover={onRowHover}
  138. op={op}
  139. query={queryWithFocusedSeries}
  140. setMetricsSamples={setMetricsSamples}
  141. />
  142. ) : (
  143. <MetricSamplesTable
  144. focusArea={focusArea?.selection?.range}
  145. mri={mri}
  146. onRowHover={onRowHover}
  147. op={op}
  148. query={queryWithFocusedSeries}
  149. setMetricsSamples={setMetricsSamples}
  150. />
  151. )
  152. ) : (
  153. <SampleTable
  154. mri={mri}
  155. {...focusArea?.selection?.range}
  156. query={queryWithFocusedSeries}
  157. onRowHover={onRowHover}
  158. />
  159. )}
  160. </TabPanels.Item>
  161. <TabPanels.Item key={Tab.CODE_LOCATIONS}>
  162. <CodeLocations mri={mri} {...focusArea?.selection?.range} />
  163. </TabPanels.Item>
  164. </TabPanels>
  165. </ContentWrapper>
  166. </Tabs>
  167. </TrayWrapper>
  168. );
  169. }
  170. const TrayWrapper = styled('div')`
  171. padding-top: ${space(4)};
  172. display: grid;
  173. grid-template-rows: auto auto 1fr;
  174. `;
  175. const ContentWrapper = styled('div')`
  176. position: relative;
  177. padding-top: ${space(2)};
  178. `;