widgetDetails.tsx 5.2 KB

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