tracesSearchBar.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import styled from '@emotion/styled';
  2. import {Button} from 'sentry/components/button';
  3. import SearchBar, {getHasTag} from 'sentry/components/events/searchBar';
  4. import {IconAdd, IconClose} from 'sentry/icons';
  5. import {t} from 'sentry/locale';
  6. import {space} from 'sentry/styles/space';
  7. import type {TagCollection} from 'sentry/types';
  8. import {DiscoverDatasets} from 'sentry/utils/discover/types';
  9. import {type ApiQueryKey, useApiQuery} from 'sentry/utils/queryClient';
  10. import useOrganization from 'sentry/utils/useOrganization';
  11. import {SpanIndexedField} from 'sentry/views/starfish/types';
  12. interface TracesSearchBarProps {
  13. handleClearSearch: (index: number) => boolean;
  14. handleSearch: (index: number, query: string) => void;
  15. queries: string[];
  16. }
  17. const getSpanName = (index: number) => {
  18. const spanNames = [t('Span A'), t('Span B'), t('Span C')];
  19. return spanNames[index];
  20. };
  21. const omitSupportedTags = [SpanIndexedField.SPAN_AI_PIPELINE_GROUP];
  22. const getTracesSupportedTags = () => {
  23. const tags: TagCollection = Object.fromEntries(
  24. Object.values(SpanIndexedField)
  25. .filter(v => !omitSupportedTags.includes(v))
  26. .map(v => [v, {key: v, name: v}])
  27. );
  28. tags.has = getHasTag(tags);
  29. return tags;
  30. };
  31. interface SpanFieldEntry {
  32. key: string;
  33. name: string;
  34. }
  35. type SpanFieldsResponse = SpanFieldEntry[];
  36. const getDynamicSpanFieldsEndpoint = (orgSlug: string): ApiQueryKey => [
  37. `/organizations/${orgSlug}/spans/fields/?statsPeriod=1h`,
  38. ];
  39. const useTracesSupportedTags = (): TagCollection => {
  40. const organization = useOrganization();
  41. const staticTags = getTracesSupportedTags();
  42. const dynamicTagQuery = useApiQuery<SpanFieldsResponse>(
  43. getDynamicSpanFieldsEndpoint(organization.slug),
  44. {
  45. staleTime: 0,
  46. retry: false,
  47. }
  48. );
  49. if (dynamicTagQuery.isSuccess) {
  50. const dynamicTags: TagCollection = Object.fromEntries(
  51. dynamicTagQuery.data.map(entry => [entry.key, entry])
  52. );
  53. return {
  54. ...dynamicTags,
  55. ...staticTags,
  56. };
  57. }
  58. return staticTags;
  59. };
  60. export function TracesSearchBar({
  61. queries,
  62. handleSearch,
  63. handleClearSearch,
  64. }: TracesSearchBarProps) {
  65. // TODO: load tags for autocompletion
  66. const organization = useOrganization();
  67. const canAddMoreQueries = queries.length <= 2;
  68. const localQueries = queries.length ? queries : [''];
  69. const supportedTags = useTracesSupportedTags();
  70. return (
  71. <TraceSearchBarsContainer>
  72. {localQueries.map((query, index) => (
  73. <TraceBar key={index}>
  74. <SpanLetter>{getSpanName(index)}</SpanLetter>
  75. <StyledSearchBar
  76. query={query}
  77. onSearch={(queryString: string) => handleSearch(index, queryString)}
  78. placeholder={t(
  79. 'Search for traces containing a span matching these attributes'
  80. )}
  81. organization={organization}
  82. metricAlert={false}
  83. supportedTags={supportedTags}
  84. dataset={DiscoverDatasets.SPANS_INDEXED}
  85. />
  86. <StyledButton
  87. aria-label={t('Remove span')}
  88. icon={<IconClose size="sm" />}
  89. size="sm"
  90. onClick={() => (queries.length === 0 ? false : handleClearSearch(index))}
  91. />
  92. </TraceBar>
  93. ))}
  94. {canAddMoreQueries ? (
  95. <Button
  96. aria-label={t('Add query')}
  97. icon={<IconAdd size="xs" isCircled />}
  98. size="sm"
  99. onClick={() => handleSearch(localQueries.length, '')}
  100. >
  101. {t('Add Span')}
  102. </Button>
  103. ) : null}
  104. </TraceSearchBarsContainer>
  105. );
  106. }
  107. const TraceSearchBarsContainer = styled('div')`
  108. display: flex;
  109. flex-direction: column;
  110. align-items: flex-start;
  111. justify-content: center;
  112. gap: ${space(1)};
  113. `;
  114. const TraceBar = styled('div')`
  115. display: flex;
  116. flex-direction: row;
  117. align-items: center;
  118. justify-content: flex-start;
  119. width: 100%;
  120. gap: ${space(1)};
  121. `;
  122. const SpanLetter = styled('div')`
  123. background-color: ${p => p.theme.purple100};
  124. border-radius: ${p => p.theme.borderRadius};
  125. padding: ${space(1)} ${space(2)};
  126. color: ${p => p.theme.purple400};
  127. white-space: nowrap;
  128. font-weight: 800;
  129. `;
  130. const StyledSearchBar = styled(SearchBar)`
  131. width: 100%;
  132. `;
  133. const StyledButton = styled(Button)`
  134. height: 38px;
  135. `;