content.tsx 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. import {Fragment, useCallback, useMemo, useState} from 'react';
  2. import {useTheme} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import debounce from 'lodash/debounce';
  5. import omit from 'lodash/omit';
  6. import {Alert} from 'sentry/components/alert';
  7. import {Button} from 'sentry/components/button';
  8. import Count from 'sentry/components/count';
  9. import EmptyStateWarning from 'sentry/components/emptyStateWarning';
  10. import * as Layout from 'sentry/components/layouts/thirds';
  11. import LoadingIndicator from 'sentry/components/loadingIndicator';
  12. import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
  13. import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
  14. import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
  15. import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
  16. import Panel from 'sentry/components/panels/panel';
  17. import PanelHeader from 'sentry/components/panels/panelHeader';
  18. import PanelItem from 'sentry/components/panels/panelItem';
  19. import PerformanceDuration from 'sentry/components/performanceDuration';
  20. import {IconChevron} from 'sentry/icons/iconChevron';
  21. import {IconClose} from 'sentry/icons/iconClose';
  22. import {t, tct} from 'sentry/locale';
  23. import {space} from 'sentry/styles/space';
  24. import type {PageFilters} from 'sentry/types/core';
  25. import type {MRI} from 'sentry/types/metrics';
  26. import {browserHistory} from 'sentry/utils/browserHistory';
  27. import {getFormattedMQL} from 'sentry/utils/metrics';
  28. import {useApiQuery} from 'sentry/utils/queryClient';
  29. import {decodeInteger, decodeList, decodeScalar} from 'sentry/utils/queryString';
  30. import {useLocation} from 'sentry/utils/useLocation';
  31. import useOrganization from 'sentry/utils/useOrganization';
  32. import usePageFilters from 'sentry/utils/usePageFilters';
  33. import * as ModuleLayout from 'sentry/views/performance/moduleLayout';
  34. import {type Field, FIELDS, SORTS} from './data';
  35. import {
  36. BREAKDOWN_SLICES,
  37. ProjectRenderer,
  38. SpanBreakdownSliceRenderer,
  39. SpanDescriptionRenderer,
  40. SpanIdRenderer,
  41. SpanTimeRenderer,
  42. TraceBreakdownContainer,
  43. TraceBreakdownRenderer,
  44. TraceIdRenderer,
  45. TraceIssuesRenderer,
  46. } from './fieldRenderers';
  47. import {TracesChart} from './tracesChart';
  48. import {TracesSearchBar} from './tracesSearchBar';
  49. import {
  50. areQueriesEmpty,
  51. getSecondaryNameFromSpan,
  52. getStylingSliceName,
  53. normalizeTraces,
  54. } from './utils';
  55. const DEFAULT_PER_PAGE = 50;
  56. export function Content() {
  57. const location = useLocation();
  58. const queries = useMemo(() => {
  59. return decodeList(location.query.query);
  60. }, [location.query.query]);
  61. const limit = useMemo(() => {
  62. return decodeInteger(location.query.perPage, DEFAULT_PER_PAGE);
  63. }, [location.query.perPage]);
  64. const removeMetric = useCallback(() => {
  65. browserHistory.push({
  66. ...location,
  67. query: omit(location.query, [
  68. 'mri',
  69. 'metricsOp',
  70. 'metricsQuery',
  71. 'metricsMax',
  72. 'metricsMin',
  73. ]),
  74. });
  75. }, [location]);
  76. const metricsMax = decodeScalar(location.query.metricsMax);
  77. const metricsMin = decodeScalar(location.query.metricsMin);
  78. const metricsOp = decodeScalar(location.query.metricsOp);
  79. const metricsQuery = decodeScalar(location.query.metricsQuery);
  80. const mri = decodeScalar(location.query.mri);
  81. const handleSearch = useCallback(
  82. (searchIndex: number, searchQuery: string) => {
  83. const newQueries = [...queries];
  84. if (newQueries.length === 0) {
  85. // In the odd case someone wants to add search bars before any query has been made, we add both the default one shown and a new one.
  86. newQueries[0] = '';
  87. }
  88. newQueries[searchIndex] = searchQuery;
  89. browserHistory.push({
  90. ...location,
  91. query: {
  92. ...location.query,
  93. cursor: undefined,
  94. query: typeof searchQuery === 'string' ? newQueries : queries,
  95. },
  96. });
  97. },
  98. [location, queries]
  99. );
  100. const handleClearSearch = useCallback(
  101. (searchIndex: number) => {
  102. const newQueries = [...queries];
  103. if (typeof newQueries[searchIndex] !== undefined) {
  104. delete newQueries[searchIndex];
  105. browserHistory.push({
  106. ...location,
  107. query: {
  108. ...location.query,
  109. cursor: undefined,
  110. query: newQueries,
  111. },
  112. });
  113. return true;
  114. }
  115. return false;
  116. },
  117. [location, queries]
  118. );
  119. const hasMetric = metricsOp && mri;
  120. const traces = useTraces<Field>({
  121. fields: [
  122. ...FIELDS,
  123. ...SORTS.map(field =>
  124. field.startsWith('-') ? (field.substring(1) as Field) : (field as Field)
  125. ),
  126. ],
  127. limit,
  128. query: queries,
  129. sort: SORTS,
  130. mri: hasMetric ? mri : undefined,
  131. metricsMax: hasMetric ? metricsMax : undefined,
  132. metricsMin: hasMetric ? metricsMin : undefined,
  133. metricsOp: hasMetric ? metricsOp : undefined,
  134. metricsQuery: hasMetric ? metricsQuery : undefined,
  135. });
  136. const isLoading = traces.isFetching;
  137. const isError = !isLoading && traces.isError;
  138. const isEmpty = !isLoading && !isError && (traces?.data?.data?.length ?? 0) === 0;
  139. const data = normalizeTraces(!isLoading && !isError ? traces?.data?.data : undefined);
  140. return (
  141. <LayoutMain fullWidth>
  142. <PageFilterBar condensed>
  143. <EnvironmentPageFilter />
  144. <DatePageFilter defaultPeriod="2h" />
  145. </PageFilterBar>
  146. {hasMetric && (
  147. <StyledAlert
  148. type="info"
  149. showIcon
  150. trailingItems={<StyledCloseButton onClick={removeMetric} />}
  151. >
  152. {tct('The metric query [metricQuery] is filtering the results below.', {
  153. metricQuery: (
  154. <strong>
  155. {getFormattedMQL({mri: mri as MRI, op: metricsOp, query: metricsQuery})}
  156. </strong>
  157. ),
  158. })}
  159. </StyledAlert>
  160. )}
  161. {isError && typeof traces.error?.responseJSON?.detail === 'string' ? (
  162. <StyledAlert type="error" showIcon>
  163. {traces.error?.responseJSON?.detail}
  164. </StyledAlert>
  165. ) : null}
  166. <TracesSearchBar
  167. queries={queries}
  168. handleSearch={handleSearch}
  169. handleClearSearch={handleClearSearch}
  170. />
  171. <ModuleLayout.Full>
  172. <TracesChart />
  173. </ModuleLayout.Full>
  174. <StyledPanel>
  175. <TracePanelContent>
  176. <StyledPanelHeader align="left" lightText>
  177. {t('Trace ID')}
  178. </StyledPanelHeader>
  179. <StyledPanelHeader align="left" lightText>
  180. {t('Trace Root')}
  181. </StyledPanelHeader>
  182. <StyledPanelHeader align="right" lightText>
  183. {areQueriesEmpty(queries) ? t('Total Spans') : t('Matching Spans')}
  184. </StyledPanelHeader>
  185. <StyledPanelHeader align="left" lightText>
  186. {t('Timeline')}
  187. </StyledPanelHeader>
  188. <StyledPanelHeader align="right" lightText>
  189. {t('Duration')}
  190. </StyledPanelHeader>
  191. <StyledPanelHeader align="right" lightText>
  192. {t('Timestamp')}
  193. </StyledPanelHeader>
  194. <StyledPanelHeader align="right" lightText>
  195. {t('Issues')}
  196. </StyledPanelHeader>
  197. {isLoading && (
  198. <StyledPanelItem span={7} overflow>
  199. <LoadingIndicator />
  200. </StyledPanelItem>
  201. )}
  202. {isError && ( // TODO: need an error state
  203. <StyledPanelItem span={7} overflow>
  204. <EmptyStateWarning withIcon />
  205. </StyledPanelItem>
  206. )}
  207. {isEmpty && (
  208. <StyledPanelItem span={7} overflow>
  209. <EmptyStateWarning withIcon>
  210. <EmptyStateText size="fontSizeExtraLarge">
  211. {t('No results found')}
  212. </EmptyStateText>
  213. <EmptyStateText size="fontSizeMedium">
  214. {t('There are no traces that match the conditions above.')}
  215. <br />
  216. {t('Try adjusting your filters starting with your time range.')}
  217. </EmptyStateText>
  218. </EmptyStateWarning>
  219. </StyledPanelItem>
  220. )}
  221. {data?.map((trace, i) => (
  222. <TraceRow key={trace.trace} trace={trace} defaultExpanded={i === 0} />
  223. ))}
  224. </TracePanelContent>
  225. </StyledPanel>
  226. </LayoutMain>
  227. );
  228. }
  229. function TraceRow({
  230. defaultExpanded,
  231. trace,
  232. }: {
  233. defaultExpanded;
  234. trace: TraceResult<Field>;
  235. }) {
  236. const [expanded, setExpanded] = useState<boolean>(defaultExpanded);
  237. const [highlightedSliceName, _setHighlightedSliceName] = useState('');
  238. const location = useLocation();
  239. const queries = useMemo(() => {
  240. return decodeList(location.query.query);
  241. }, [location.query.query]);
  242. const setHighlightedSliceName = useMemo(
  243. () =>
  244. debounce(sliceName => _setHighlightedSliceName(sliceName), 100, {
  245. leading: true,
  246. }),
  247. [_setHighlightedSliceName]
  248. );
  249. const onClickExpand = useCallback(() => setExpanded(e => !e), [setExpanded]);
  250. return (
  251. <Fragment>
  252. <StyledPanelItem align="center" center onClick={onClickExpand}>
  253. <Button
  254. icon={<IconChevron size="xs" direction={expanded ? 'down' : 'right'} />}
  255. aria-label={t('Toggle trace details')}
  256. aria-expanded={expanded}
  257. size="zero"
  258. borderless
  259. />
  260. <TraceIdRenderer traceId={trace.trace} timestamp={trace.spans[0].timestamp} />
  261. </StyledPanelItem>
  262. <StyledPanelItem align="left" overflow>
  263. <Description>
  264. {trace.project ? (
  265. <ProjectRenderer projectSlug={trace.project} hideName />
  266. ) : null}
  267. {trace.name ? (
  268. <WrappingText>{trace.name}</WrappingText>
  269. ) : (
  270. <EmptyValueContainer>{t('Missing Trace Root')}</EmptyValueContainer>
  271. )}
  272. </Description>
  273. </StyledPanelItem>
  274. <StyledPanelItem align="right">
  275. {areQueriesEmpty(queries) ? (
  276. <Count value={trace.numSpans} />
  277. ) : (
  278. tct('[numerator][space]of[space][denominator]', {
  279. numerator: <Count value={trace.matchingSpans} />,
  280. denominator: <Count value={trace.numSpans} />,
  281. space: <Fragment>&nbsp;</Fragment>,
  282. })
  283. )}
  284. </StyledPanelItem>
  285. <BreakdownPanelItem
  286. align="right"
  287. highlightedSliceName={highlightedSliceName}
  288. onMouseLeave={() => setHighlightedSliceName('')}
  289. >
  290. <TraceBreakdownRenderer
  291. trace={trace}
  292. setHighlightedSliceName={setHighlightedSliceName}
  293. />
  294. </BreakdownPanelItem>
  295. <StyledPanelItem align="right">
  296. <PerformanceDuration milliseconds={trace.duration} abbreviation />
  297. </StyledPanelItem>
  298. <StyledPanelItem align="right">
  299. <SpanTimeRenderer timestamp={trace.end} tooltipShowSeconds />
  300. </StyledPanelItem>
  301. <StyledPanelItem align="right">
  302. <TraceIssuesRenderer trace={trace} />
  303. </StyledPanelItem>
  304. {expanded && (
  305. <SpanTable
  306. spans={trace.spans}
  307. trace={trace}
  308. setHighlightedSliceName={setHighlightedSliceName}
  309. />
  310. )}
  311. </Fragment>
  312. );
  313. }
  314. function SpanTable({
  315. spans,
  316. trace,
  317. setHighlightedSliceName,
  318. }: {
  319. setHighlightedSliceName: (sliceName: string) => void;
  320. spans: SpanResult<Field>[];
  321. trace: TraceResult<Field>;
  322. }) {
  323. const location = useLocation();
  324. const queries = useMemo(() => {
  325. return decodeList(location.query.query);
  326. }, [location.query.query]);
  327. return (
  328. <SpanTablePanelItem span={7} overflow>
  329. <StyledPanel>
  330. <SpanPanelContent>
  331. <StyledPanelHeader align="left" lightText>
  332. {t('Span ID')}
  333. </StyledPanelHeader>
  334. <StyledPanelHeader align="left" lightText>
  335. {t('Span Description')}
  336. </StyledPanelHeader>
  337. <StyledPanelHeader align="right" lightText />
  338. <StyledPanelHeader align="right" lightText>
  339. {t('Span Duration')}
  340. </StyledPanelHeader>
  341. <StyledPanelHeader align="right" lightText>
  342. {t('Timestamp')}
  343. </StyledPanelHeader>
  344. {spans.map(span => (
  345. <SpanRow
  346. key={span.id}
  347. span={span}
  348. trace={trace}
  349. setHighlightedSliceName={setHighlightedSliceName}
  350. />
  351. ))}
  352. {spans.length < trace.matchingSpans && (
  353. <MoreMatchingSpans span={5}>
  354. {tct('[more][space]more [matching]spans can be found in the trace.', {
  355. more: <Count value={trace.matchingSpans - spans.length} />,
  356. space: <Fragment>&nbsp;</Fragment>,
  357. matching: areQueriesEmpty(queries) ? '' : 'matching ',
  358. })}
  359. </MoreMatchingSpans>
  360. )}
  361. </SpanPanelContent>
  362. </StyledPanel>
  363. </SpanTablePanelItem>
  364. );
  365. }
  366. function SpanRow({
  367. span,
  368. trace,
  369. setHighlightedSliceName,
  370. }: {
  371. setHighlightedSliceName: (sliceName: string) => void;
  372. span: SpanResult<Field>;
  373. trace: TraceResult<Field>;
  374. }) {
  375. const theme = useTheme();
  376. return (
  377. <Fragment>
  378. <StyledSpanPanelItem align="right">
  379. <SpanIdRenderer
  380. projectSlug={span.project}
  381. transactionId={span['transaction.id']}
  382. spanId={span.id}
  383. traceId={trace.trace}
  384. timestamp={span.timestamp}
  385. />
  386. </StyledSpanPanelItem>
  387. <StyledSpanPanelItem align="left" overflow>
  388. <SpanDescriptionRenderer span={span} />
  389. </StyledSpanPanelItem>
  390. <StyledSpanPanelItem align="right" onMouseLeave={() => setHighlightedSliceName('')}>
  391. <TraceBreakdownContainer>
  392. <SpanBreakdownSliceRenderer
  393. sliceName={span.project}
  394. sliceSecondaryName={getSecondaryNameFromSpan(span)}
  395. sliceStart={Math.ceil(span['precise.start_ts'] * 1000)}
  396. sliceEnd={Math.floor(span['precise.finish_ts'] * 1000)}
  397. trace={trace}
  398. theme={theme}
  399. onMouseEnter={() =>
  400. setHighlightedSliceName(
  401. getStylingSliceName(span.project, getSecondaryNameFromSpan(span)) ?? ''
  402. )
  403. }
  404. />
  405. </TraceBreakdownContainer>
  406. </StyledSpanPanelItem>
  407. <StyledSpanPanelItem align="right">
  408. <PerformanceDuration milliseconds={span['span.duration']} abbreviation />
  409. </StyledSpanPanelItem>
  410. <StyledSpanPanelItem align="right">
  411. <SpanTimeRenderer
  412. timestamp={span['precise.finish_ts'] * 1000}
  413. tooltipShowSeconds
  414. />
  415. </StyledSpanPanelItem>
  416. </Fragment>
  417. );
  418. }
  419. export type SpanResult<F extends string> = Record<F, any>;
  420. export interface TraceResult<F extends string> {
  421. breakdowns: TraceBreakdownResult[];
  422. duration: number;
  423. end: number;
  424. matchingSpans: number;
  425. name: string | null;
  426. numErrors: number;
  427. numOccurrences: number;
  428. numSpans: number;
  429. project: string | null;
  430. slices: number;
  431. spans: SpanResult<F>[];
  432. start: number;
  433. trace: string;
  434. }
  435. interface TraceBreakdownBase {
  436. duration: number; // Contains the accurate duration for display. Start and end may be quantized.
  437. end: number;
  438. opCategory: string | null;
  439. sdkName: string | null;
  440. sliceEnd: number;
  441. sliceStart: number;
  442. sliceWidth: number;
  443. start: number;
  444. }
  445. type TraceBreakdownProject = TraceBreakdownBase & {
  446. kind: 'project';
  447. project: string;
  448. };
  449. type TraceBreakdownMissing = TraceBreakdownBase & {
  450. kind: 'missing';
  451. project: null;
  452. };
  453. export type TraceBreakdownResult = TraceBreakdownProject | TraceBreakdownMissing;
  454. interface TraceResults<F extends string> {
  455. data: TraceResult<F>[];
  456. meta: any;
  457. }
  458. interface UseTracesOptions<F extends string> {
  459. fields: F[];
  460. datetime?: PageFilters['datetime'];
  461. enabled?: boolean;
  462. limit?: number;
  463. metricsMax?: string;
  464. metricsMin?: string;
  465. metricsOp?: string;
  466. metricsQuery?: string;
  467. mri?: string;
  468. query?: string | string[];
  469. sort?: string[];
  470. suggestedQuery?: string;
  471. }
  472. function useTraces<F extends string>({
  473. fields,
  474. datetime,
  475. enabled,
  476. limit,
  477. mri,
  478. metricsMax,
  479. metricsMin,
  480. metricsOp,
  481. metricsQuery,
  482. query,
  483. suggestedQuery,
  484. sort,
  485. }: UseTracesOptions<F>) {
  486. const organization = useOrganization();
  487. const {selection} = usePageFilters();
  488. const path = `/organizations/${organization.slug}/traces/`;
  489. const endpointOptions = {
  490. query: {
  491. project: selection.projects,
  492. environment: selection.environments,
  493. ...(datetime ?? normalizeDateTimeParams(selection.datetime)),
  494. field: fields,
  495. query,
  496. suggestedQuery,
  497. sort,
  498. per_page: limit,
  499. breakdownSlices: BREAKDOWN_SLICES,
  500. maxSpansPerTrace: 10,
  501. mri,
  502. metricsMax,
  503. metricsMin,
  504. metricsOp,
  505. metricsQuery,
  506. },
  507. };
  508. return useApiQuery<TraceResults<F>>([path, endpointOptions], {
  509. staleTime: 0,
  510. refetchOnWindowFocus: false,
  511. retry: false,
  512. enabled,
  513. });
  514. }
  515. const LayoutMain = styled(Layout.Main)`
  516. display: flex;
  517. flex-direction: column;
  518. gap: ${space(2)};
  519. `;
  520. const StyledPanel = styled(Panel)`
  521. margin-bottom: 0px;
  522. `;
  523. const TracePanelContent = styled('div')`
  524. width: 100%;
  525. display: grid;
  526. grid-template-columns: repeat(1, min-content) auto repeat(2, min-content) 85px 85px 66px;
  527. `;
  528. const SpanPanelContent = styled('div')`
  529. width: 100%;
  530. display: grid;
  531. grid-template-columns: repeat(1, min-content) auto repeat(1, min-content) 133px 85px;
  532. `;
  533. const StyledPanelHeader = styled(PanelHeader)<{align: 'left' | 'right'}>`
  534. white-space: nowrap;
  535. justify-content: ${p => (p.align === 'left' ? 'flex-start' : 'flex-end')};
  536. `;
  537. const EmptyStateText = styled('div')<{size: 'fontSizeExtraLarge' | 'fontSizeMedium'}>`
  538. color: ${p => p.theme.gray300};
  539. font-size: ${p => p.theme[p.size]};
  540. `;
  541. const Description = styled('div')`
  542. ${p => p.theme.overflowEllipsis};
  543. display: flex;
  544. flex-direction: row;
  545. align-items: center;
  546. gap: ${space(1)};
  547. `;
  548. const StyledPanelItem = styled(PanelItem)<{
  549. align?: 'left' | 'center' | 'right';
  550. overflow?: boolean;
  551. span?: number;
  552. }>`
  553. align-items: center;
  554. padding: ${space(1)} ${space(2)};
  555. ${p => (p.align === 'left' ? 'justify-content: flex-start;' : null)}
  556. ${p => (p.align === 'right' ? 'justify-content: flex-end;' : null)}
  557. ${p => (p.overflow ? p.theme.overflowEllipsis : null)};
  558. ${p =>
  559. p.align === 'center'
  560. ? `
  561. justify-content: space-around;`
  562. : p.align === 'left' || p.align === 'right'
  563. ? `text-align: ${p.align};`
  564. : undefined}
  565. ${p => p.span && `grid-column: auto / span ${p.span};`}
  566. white-space: nowrap;
  567. `;
  568. const MoreMatchingSpans = styled(StyledPanelItem)`
  569. color: ${p => p.theme.gray300};
  570. `;
  571. const WrappingText = styled('div')`
  572. width: 100%;
  573. ${p => p.theme.overflowEllipsis};
  574. `;
  575. const StyledSpanPanelItem = styled(StyledPanelItem)`
  576. &:nth-child(10n + 1),
  577. &:nth-child(10n + 2),
  578. &:nth-child(10n + 3),
  579. &:nth-child(10n + 4),
  580. &:nth-child(10n + 5) {
  581. background-color: ${p => p.theme.backgroundSecondary};
  582. }
  583. `;
  584. const SpanTablePanelItem = styled(StyledPanelItem)`
  585. background-color: ${p => p.theme.gray100};
  586. `;
  587. const BreakdownPanelItem = styled(StyledPanelItem)<{highlightedSliceName: string}>`
  588. ${p =>
  589. p.highlightedSliceName
  590. ? `--highlightedSlice-${p.highlightedSliceName}-opacity: 1.0;
  591. --highlightedSlice-${p.highlightedSliceName}-saturate: saturate(1.0) contrast(1.0);
  592. --highlightedSlice-${p.highlightedSliceName}-transform: translateY(0px);
  593. `
  594. : null}
  595. ${p =>
  596. p.highlightedSliceName
  597. ? `
  598. --defaultSlice-opacity: 1.0;
  599. --defaultSlice-saturate: saturate(0.7) contrast(0.9) brightness(1.2);
  600. --defaultSlice-transform: translateY(0px);
  601. `
  602. : `
  603. --defaultSlice-opacity: 1.0;
  604. --defaultSlice-saturate: saturate(1.0) contrast(1.0);
  605. --defaultSlice-transform: translateY(0px);
  606. `}
  607. `;
  608. const EmptyValueContainer = styled('span')`
  609. color: ${p => p.theme.gray300};
  610. `;
  611. const StyledAlert = styled(Alert)`
  612. margin-bottom: 0;
  613. `;
  614. const StyledCloseButton = styled(IconClose)`
  615. cursor: pointer;
  616. `;