spanDescription.tsx 3.1 KB

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