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