widgetDetails.tsx 5.7 KB

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