spanDescription.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import {Fragment, useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import Feature from 'sentry/components/acl/feature';
  4. import {CodeSnippet} from 'sentry/components/codeSnippet';
  5. import LoadingIndicator from 'sentry/components/loadingIndicator';
  6. import {space} from 'sentry/styles/space';
  7. import {
  8. MissingFrame,
  9. StackTraceMiniFrame,
  10. } from 'sentry/views/starfish/components/stackTraceMiniFrame';
  11. import {useFullSpanFromTrace} from 'sentry/views/starfish/queries/useFullSpanFromTrace';
  12. import {useIndexedSpans} from 'sentry/views/starfish/queries/useIndexedSpans';
  13. import {SpanIndexedField, SpanIndexedFieldTypes} from 'sentry/views/starfish/types';
  14. import {SQLishFormatter} from 'sentry/views/starfish/utils/sqlish/SQLishFormatter';
  15. interface Props {
  16. groupId: SpanIndexedFieldTypes[SpanIndexedField.SPAN_GROUP];
  17. op: SpanIndexedFieldTypes[SpanIndexedField.SPAN_OP];
  18. preliminaryDescription?: string;
  19. }
  20. export function SpanDescription(props: Props) {
  21. const {op, preliminaryDescription} = props;
  22. if (op.startsWith('db')) {
  23. return <DatabaseSpanDescription {...props} />;
  24. }
  25. return <WordBreak>{preliminaryDescription ?? ''}</WordBreak>;
  26. }
  27. const formatter = new SQLishFormatter();
  28. export function DatabaseSpanDescription({
  29. groupId,
  30. preliminaryDescription,
  31. }: Omit<Props, 'op'>) {
  32. const {data: indexedSpans, isFetching: areIndexedSpansLoading} = useIndexedSpans(
  33. {'span.group': groupId},
  34. [INDEXED_SPAN_SORT],
  35. 1
  36. );
  37. const indexedSpan = indexedSpans?.[0];
  38. // NOTE: We only need this for `span.data`! If this info existed in indexed spans, we could skip it
  39. const {data: rawSpan, isFetching: isRawSpanLoading} = useFullSpanFromTrace(
  40. groupId,
  41. [INDEXED_SPAN_SORT],
  42. Boolean(indexedSpan)
  43. );
  44. const rawDescription =
  45. rawSpan?.description || indexedSpan?.['span.description'] || preliminaryDescription;
  46. const formatterDescription = useMemo(() => {
  47. return formatter.toString(rawDescription ?? '');
  48. }, [rawDescription]);
  49. return (
  50. <Frame>
  51. {areIndexedSpansLoading ? (
  52. <WithPadding>
  53. <LoadingIndicator mini />
  54. </WithPadding>
  55. ) : (
  56. <CodeSnippet language="sql" isRounded={false}>
  57. {formatterDescription}
  58. </CodeSnippet>
  59. )}
  60. <Feature features={['organizations:performance-database-view-query-source']}>
  61. {!areIndexedSpansLoading && !isRawSpanLoading && (
  62. <Fragment>
  63. {rawSpan?.data?.['code.filepath'] ? (
  64. <StackTraceMiniFrame
  65. projectId={indexedSpan?.project_id?.toString()}
  66. eventId={indexedSpan?.['transaction.id']}
  67. frame={{
  68. filename: rawSpan?.data?.['code.filepath'],
  69. lineNo: rawSpan?.data?.['code.lineno'],
  70. function: rawSpan?.data?.['code.function'],
  71. }}
  72. />
  73. ) : (
  74. <MissingFrame />
  75. )}
  76. </Fragment>
  77. )}
  78. </Feature>
  79. </Frame>
  80. );
  81. }
  82. const INDEXED_SPAN_SORT = {
  83. field: 'span.self_time',
  84. kind: 'desc' as const,
  85. };
  86. const Frame = styled('div')`
  87. border: solid 1px ${p => p.theme.border};
  88. border-radius: ${p => p.theme.borderRadius};
  89. overflow: hidden;
  90. `;
  91. const WithPadding = styled('div')`
  92. display: flex;
  93. padding: ${space(1)} ${space(2)};
  94. `;
  95. const WordBreak = styled('div')`
  96. word-break: break-word;
  97. `;