widgetDetails.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. import {Fragment, useCallback, useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import Feature from 'sentry/components/acl/feature';
  4. import {Button} from 'sentry/components/button';
  5. import HookOrDefault from 'sentry/components/hookOrDefault';
  6. import {
  7. type Field,
  8. MetricSamplesTable,
  9. SearchableMetricSamplesTable,
  10. } from 'sentry/components/metrics/metricSamplesTable';
  11. import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
  12. import {t} from 'sentry/locale';
  13. import {space} from 'sentry/styles/space';
  14. import type {PageFilters} from 'sentry/types/core';
  15. import type {MetricAggregation, MRI} from 'sentry/types/metrics';
  16. import {defined} from 'sentry/utils';
  17. import type {FocusedMetricsSeries, MetricsWidget} from 'sentry/utils/metrics/types';
  18. import {isMetricsEquationWidget} from 'sentry/utils/metrics/types';
  19. import type {MetricsSamplesResults} from 'sentry/utils/metrics/useMetricsSamples';
  20. import useOrganization from 'sentry/utils/useOrganization';
  21. import usePageFilters from 'sentry/utils/usePageFilters';
  22. import type {FocusAreaProps} from 'sentry/views/metrics/context';
  23. import {useMetricsContext} from 'sentry/views/metrics/context';
  24. import {extendQueryWithGroupBys} from 'sentry/views/metrics/utils';
  25. import {generateTracesRouteWithQuery} from 'sentry/views/traces/utils';
  26. export function WidgetDetails() {
  27. const {
  28. selectedWidgetIndex,
  29. widgets,
  30. focusArea,
  31. setHighlightedSampleId,
  32. setMetricsSamples,
  33. hasPerformanceMetrics,
  34. } = useMetricsContext();
  35. const selectedWidget = widgets[selectedWidgetIndex] as MetricsWidget | undefined;
  36. const handleSampleRowHover = useCallback(
  37. (sampleId?: string) => {
  38. setHighlightedSampleId(sampleId);
  39. },
  40. [setHighlightedSampleId]
  41. );
  42. if (!selectedWidget || isMetricsEquationWidget(selectedWidget)) {
  43. return <MetricDetails onRowHover={handleSampleRowHover} focusArea={focusArea} />;
  44. }
  45. const {mri, aggregation, query, condition, focusedSeries} = selectedWidget;
  46. return (
  47. <MetricDetails
  48. mri={mri}
  49. aggregation={aggregation}
  50. condition={condition}
  51. query={query}
  52. focusedSeries={focusedSeries}
  53. onRowHover={handleSampleRowHover}
  54. setMetricsSamples={setMetricsSamples}
  55. focusArea={focusArea}
  56. hasPerformanceMetrics={hasPerformanceMetrics}
  57. />
  58. );
  59. }
  60. interface MetricDetailsProps {
  61. aggregation?: MetricAggregation;
  62. condition?: number;
  63. focusArea?: FocusAreaProps;
  64. focusedSeries?: FocusedMetricsSeries[];
  65. hasPerformanceMetrics?: boolean;
  66. mri?: MRI;
  67. onRowHover?: (sampleId?: string) => void;
  68. query?: string;
  69. setMetricsSamples?: React.Dispatch<
  70. React.SetStateAction<MetricsSamplesResults<Field>['data'] | undefined>
  71. >;
  72. }
  73. export function MetricDetails({
  74. mri,
  75. aggregation,
  76. condition,
  77. query,
  78. focusedSeries,
  79. onRowHover,
  80. focusArea,
  81. setMetricsSamples,
  82. hasPerformanceMetrics,
  83. }: MetricDetailsProps) {
  84. const {selection} = usePageFilters();
  85. const organization = useOrganization();
  86. const queryWithFocusedSeries = useMemo(
  87. () =>
  88. focusedSeries &&
  89. extendQueryWithGroupBys(
  90. query || '',
  91. focusedSeries.map(s => s.groupBy)
  92. ),
  93. [focusedSeries, query]
  94. );
  95. const selectionRange = focusArea?.selection?.range;
  96. const selectionDatetime =
  97. defined(selectionRange) && defined(selectionRange) && defined(selectionRange)
  98. ? ({
  99. start: selectionRange.start,
  100. end: selectionRange.end,
  101. } as PageFilters['datetime'])
  102. : undefined;
  103. const tracesTarget = generateTracesRouteWithQuery({
  104. orgSlug: organization.slug,
  105. metric:
  106. aggregation && mri
  107. ? {
  108. max: selectionRange?.max,
  109. min: selectionRange?.min,
  110. op: aggregation,
  111. query: queryWithFocusedSeries,
  112. mri: mri,
  113. }
  114. : undefined,
  115. query: {
  116. project: selection.projects as unknown as string[],
  117. environment: selection.environments,
  118. ...normalizeDateTimeParams(selectionDatetime ?? selection.datetime),
  119. },
  120. });
  121. return (
  122. <TrayWrapper>
  123. <TabsAndAction>
  124. <Heading>{t('Span Samples')}</Heading>
  125. <Feature
  126. features={[
  127. 'performance-trace-explorer-with-metrics',
  128. 'performance-trace-explorer',
  129. ]}
  130. requireAll
  131. >
  132. <OpenInTracesButton to={tracesTarget} size="sm">
  133. {t('Open in Traces')}
  134. </OpenInTracesButton>
  135. </Feature>
  136. </TabsAndAction>
  137. <ContentWrapper>
  138. <MetricSampleTableWrapper organization={organization}>
  139. {organization.features.includes('metrics-samples-list-search') ? (
  140. <SearchableMetricSamplesTable
  141. focusArea={selectionRange}
  142. mri={mri}
  143. onRowHover={onRowHover}
  144. aggregation={aggregation}
  145. condition={condition}
  146. query={queryWithFocusedSeries}
  147. setMetricsSamples={setMetricsSamples}
  148. hasPerformance={hasPerformanceMetrics}
  149. />
  150. ) : (
  151. <MetricSamplesTable
  152. focusArea={selectionRange}
  153. mri={mri}
  154. onRowHover={onRowHover}
  155. aggregation={aggregation}
  156. condition={condition}
  157. query={queryWithFocusedSeries}
  158. setMetricsSamples={setMetricsSamples}
  159. hasPerformance={hasPerformanceMetrics}
  160. />
  161. )}
  162. </MetricSampleTableWrapper>
  163. </ContentWrapper>
  164. </TrayWrapper>
  165. );
  166. }
  167. const MetricSampleTableWrapper = HookOrDefault({
  168. hookName: 'component:ddm-metrics-samples-list',
  169. defaultComponent: ({children}) => <Fragment>{children}</Fragment>,
  170. });
  171. const Heading = styled('h6')`
  172. margin-bottom: ${space(0.5)};
  173. `;
  174. const TrayWrapper = styled('div')`
  175. padding-top: ${space(4)};
  176. display: grid;
  177. grid-template-rows: auto auto 1fr;
  178. `;
  179. const ContentWrapper = styled('div')`
  180. position: relative;
  181. padding-top: ${space(1)};
  182. `;
  183. const OpenInTracesButton = styled(Button)``;
  184. const TabsAndAction = styled('div')`
  185. display: grid;
  186. grid-template-columns: 1fr auto;
  187. gap: ${space(4)};
  188. align-items: end;
  189. `;