spanDescription.tsx 3.4 KB

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