widgetDetails.tsx 6.2 KB

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