widgetDetails.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  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 {
  26. selectedWidgetIndex,
  27. widgets,
  28. focusArea,
  29. highlightedSampleId,
  30. setHighlightedSampleId,
  31. } = useDDMContext();
  32. const [selectedTab, setSelectedTab] = useState(Tab.SAMPLES);
  33. // the tray is minimized when the main content is maximized
  34. const selectedWidget = widgets[selectedWidgetIndex] as
  35. | MetricWidgetQueryParams
  36. | undefined;
  37. const isCodeLocationsDisabled =
  38. selectedWidget?.mri && !isCustomMetric({mri: selectedWidget.mri});
  39. if (isCodeLocationsDisabled && selectedTab === Tab.CODE_LOCATIONS) {
  40. setSelectedTab(Tab.SAMPLES);
  41. }
  42. const handleSampleRowHover = (sampleId?: string) => {
  43. setHighlightedSampleId(sampleId);
  44. };
  45. const handleTabChange = useCallback(
  46. (tab: Tab) => {
  47. if (tab === Tab.CODE_LOCATIONS) {
  48. trackAnalytics('ddm.code-locations', {
  49. organization,
  50. });
  51. }
  52. setSelectedTab(tab);
  53. },
  54. [organization]
  55. );
  56. return (
  57. <TrayWrapper>
  58. <Tabs value={selectedTab} onChange={handleTabChange}>
  59. <TabList>
  60. <TabList.Item key={Tab.SAMPLES}>{t('Samples')}</TabList.Item>
  61. <TabList.Item
  62. textValue={t('Code Location')}
  63. key={Tab.CODE_LOCATIONS}
  64. disabled={isCodeLocationsDisabled}
  65. >
  66. <Tooltip
  67. title={t(
  68. 'This metric is automatically collected by Sentry. It is not bound to a specific line of your code.'
  69. )}
  70. disabled={!isCodeLocationsDisabled}
  71. >
  72. <span style={{pointerEvents: 'all'}}>{t('Code Location')}</span>
  73. </Tooltip>
  74. </TabList.Item>
  75. </TabList>
  76. <ContentWrapper>
  77. <TabPanels>
  78. <TabPanels.Item key={Tab.SAMPLES}>
  79. <SampleTable
  80. mri={selectedWidget?.mri}
  81. query={
  82. selectedWidget?.focusedSeries?.groupBy
  83. ? `${selectedWidget.query} ${constructQueryString(
  84. selectedWidget.focusedSeries.groupBy
  85. )}`.trim()
  86. : selectedWidget?.query
  87. }
  88. {...focusArea?.selection?.range}
  89. highlightedRow={highlightedSampleId}
  90. onRowHover={handleSampleRowHover}
  91. />
  92. </TabPanels.Item>
  93. <TabPanels.Item key={Tab.CODE_LOCATIONS}>
  94. <CodeLocations mri={selectedWidget?.mri} {...focusArea?.selection?.range} />
  95. </TabPanels.Item>
  96. </TabPanels>
  97. </ContentWrapper>
  98. </Tabs>
  99. </TrayWrapper>
  100. );
  101. }
  102. const TrayWrapper = styled('div')`
  103. padding-top: ${space(4)};
  104. display: grid;
  105. grid-template-rows: auto auto 1fr;
  106. `;
  107. const ContentWrapper = styled('div')`
  108. position: relative;
  109. padding: ${space(2)} 0;
  110. `;