spanDescription.tsx 3.5 KB

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