spanDescription.tsx 3.5 KB

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