widgetDetails.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import {useCallback, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import {TabList, TabPanels, Tabs} from 'sentry/components/tabs';
  4. import {Tooltip} from 'sentry/components/tooltip';
  5. import {t} from 'sentry/locale';
  6. import {space} from 'sentry/styles/space';
  7. import {trackAnalytics} from 'sentry/utils/analytics';
  8. import {isCustomMetric} from 'sentry/utils/metrics';
  9. import type {MetricWidgetQueryParams} from 'sentry/utils/metrics/types';
  10. import useOrganization from 'sentry/utils/useOrganization';
  11. import {CodeLocations} from 'sentry/views/ddm/codeLocations';
  12. import {useDDMContext} from 'sentry/views/ddm/context';
  13. import {SampleTable} from 'sentry/views/ddm/sampleTable';
  14. enum Tab {
  15. SAMPLES = 'samples',
  16. CODE_LOCATIONS = 'codeLocations',
  17. }
  18. const constructQueryString = (queryObject: Record<string, string>) => {
  19. return Object.entries(queryObject)
  20. .map(([key, value]) => `${key}:"${value}"`)
  21. .join(' ');
  22. };
  23. export function WidgetDetails() {
  24. const organization = useOrganization();
  25. const {selectedWidgetIndex, widgets, focusArea, setHighlightedSampleId} =
  26. useDDMContext();
  27. const [selectedTab, setSelectedTab] = useState(Tab.SAMPLES);
  28. // the tray is minimized when the main content is maximized
  29. const selectedWidget = widgets[selectedWidgetIndex] as
  30. | MetricWidgetQueryParams
  31. | undefined;
  32. const isCodeLocationsDisabled =
  33. selectedWidget?.mri && !isCustomMetric({mri: selectedWidget.mri});
  34. if (isCodeLocationsDisabled && selectedTab === Tab.CODE_LOCATIONS) {
  35. setSelectedTab(Tab.SAMPLES);
  36. }
  37. const handleSampleRowHover = useCallback(
  38. (sampleId?: string) => {
  39. setHighlightedSampleId(sampleId);
  40. },
  41. [setHighlightedSampleId]
  42. );
  43. const handleTabChange = useCallback(
  44. (tab: Tab) => {
  45. if (tab === Tab.CODE_LOCATIONS) {
  46. trackAnalytics('ddm.code-locations', {
  47. organization,
  48. });
  49. }
  50. setSelectedTab(tab);
  51. },
  52. [organization]
  53. );
  54. return (
  55. <TrayWrapper>
  56. <Tabs value={selectedTab} onChange={handleTabChange}>
  57. <TabList>
  58. <TabList.Item key={Tab.SAMPLES}>{t('Sampled Events')}</TabList.Item>
  59. <TabList.Item
  60. textValue={t('Code Location')}
  61. key={Tab.CODE_LOCATIONS}
  62. disabled={isCodeLocationsDisabled}
  63. >
  64. <Tooltip
  65. title={t(
  66. 'This metric is automatically collected by Sentry. It is not bound to a specific line of your code.'
  67. )}
  68. disabled={!isCodeLocationsDisabled}
  69. >
  70. <span style={{pointerEvents: 'all'}}>{t('Code Location')}</span>
  71. </Tooltip>
  72. </TabList.Item>
  73. </TabList>
  74. <ContentWrapper>
  75. <TabPanels>
  76. <TabPanels.Item key={Tab.SAMPLES}>
  77. <SampleTable
  78. mri={selectedWidget?.mri}
  79. query={
  80. selectedWidget?.focusedSeries?.groupBy
  81. ? `${selectedWidget.query} ${constructQueryString(
  82. selectedWidget.focusedSeries.groupBy
  83. )}`.trim()
  84. : selectedWidget?.query
  85. }
  86. {...focusArea?.selection?.range}
  87. onRowHover={handleSampleRowHover}
  88. />
  89. </TabPanels.Item>
  90. <TabPanels.Item key={Tab.CODE_LOCATIONS}>
  91. <CodeLocations mri={selectedWidget?.mri} {...focusArea?.selection?.range} />
  92. </TabPanels.Item>
  93. </TabPanels>
  94. </ContentWrapper>
  95. </Tabs>
  96. </TrayWrapper>
  97. );
  98. }
  99. const TrayWrapper = styled('div')`
  100. padding-top: ${space(4)};
  101. display: grid;
  102. grid-template-rows: auto auto 1fr;
  103. `;
  104. const ContentWrapper = styled('div')`
  105. position: relative;
  106. padding: ${space(2)} 0;
  107. `;