metricsRibbon.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import {useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import {space} from 'sentry/styles/space';
  4. import type {NewQuery} from 'sentry/types/organization';
  5. import type {Project} from 'sentry/types/project';
  6. import type {TableData, TableDataRow} from 'sentry/utils/discover/discoverQuery';
  7. import EventView from 'sentry/utils/discover/eventView';
  8. import type {DiscoverDatasets} from 'sentry/utils/discover/types';
  9. import {decodeScalar} from 'sentry/utils/queryString';
  10. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  11. import {useLocation} from 'sentry/utils/useLocation';
  12. import useOrganization from 'sentry/utils/useOrganization';
  13. import usePageFilters from 'sentry/utils/usePageFilters';
  14. import {
  15. DEFAULT_PLATFORM,
  16. PLATFORM_LOCAL_STORAGE_KEY,
  17. PLATFORM_QUERY_PARAM,
  18. } from 'sentry/views/performance/screenload/screens/platformSelector';
  19. import {useTableQuery} from 'sentry/views/performance/screenload/screens/screensTable';
  20. import {isCrossPlatform} from 'sentry/views/performance/screenload/screens/utils';
  21. import {CountCell} from 'sentry/views/starfish/components/tableCells/countCell';
  22. import {DurationCell} from 'sentry/views/starfish/components/tableCells/durationCell';
  23. import {PercentChangeCell} from 'sentry/views/starfish/components/tableCells/percentChangeCell';
  24. import {useReleaseSelection} from 'sentry/views/starfish/queries/useReleases';
  25. import {appendReleaseFilters} from 'sentry/views/starfish/utils/releaseComparison';
  26. import {Block} from 'sentry/views/starfish/views/spanSummaryPage/block';
  27. const UNDEFINED_TEXT = '--';
  28. type BlockType = 'duration' | 'count' | 'change';
  29. interface BlockProps {
  30. dataKey: string | ((data?: TableDataRow[]) => number | undefined);
  31. title: string;
  32. type: BlockType;
  33. allowZero?: boolean;
  34. }
  35. export function MetricsRibbon({
  36. filters,
  37. project,
  38. blocks,
  39. fields,
  40. referrer,
  41. dataset,
  42. }: {
  43. blocks: BlockProps[];
  44. dataset: DiscoverDatasets;
  45. fields: string[];
  46. referrer: string;
  47. filters?: string[];
  48. project?: Project | null;
  49. }) {
  50. const {selection} = usePageFilters();
  51. const organization = useOrganization();
  52. const location = useLocation();
  53. const {
  54. primaryRelease,
  55. secondaryRelease,
  56. isLoading: isReleasesLoading,
  57. } = useReleaseSelection();
  58. const hasPlatformSelectFeature = organization.features.includes('spans-first-ui');
  59. const platform =
  60. decodeScalar(location.query[PLATFORM_QUERY_PARAM]) ??
  61. localStorage.getItem(PLATFORM_LOCAL_STORAGE_KEY) ??
  62. DEFAULT_PLATFORM;
  63. const queryString = useMemo(() => {
  64. const searchQuery = new MutableSearch([...(filters ?? [])]);
  65. if (project && isCrossPlatform(project) && hasPlatformSelectFeature) {
  66. searchQuery.addFilterValue('os.name', platform);
  67. }
  68. return appendReleaseFilters(searchQuery, primaryRelease, secondaryRelease);
  69. }, [
  70. filters,
  71. hasPlatformSelectFeature,
  72. platform,
  73. primaryRelease,
  74. project,
  75. secondaryRelease,
  76. ]);
  77. const newQuery: NewQuery = {
  78. name: 'ScreenMetricsRibbon',
  79. fields,
  80. query: queryString,
  81. dataset,
  82. version: 2,
  83. projects: selection.projects,
  84. };
  85. const eventView = EventView.fromNewQueryWithLocation(newQuery, location);
  86. const {data, isLoading} = useTableQuery({
  87. eventView,
  88. enabled: !isReleasesLoading,
  89. referrer,
  90. });
  91. return (
  92. <BlockContainer>
  93. {blocks.map(({title, dataKey, type}) => (
  94. <MetricsBlock
  95. key={title}
  96. title={title}
  97. type={type}
  98. dataKey={dataKey}
  99. data={data}
  100. isLoading={isLoading}
  101. />
  102. ))}
  103. </BlockContainer>
  104. );
  105. }
  106. function MetricsBlock({
  107. title,
  108. type,
  109. data,
  110. dataKey,
  111. isLoading,
  112. allowZero,
  113. }: {
  114. isLoading: boolean;
  115. title: string;
  116. data?: TableData;
  117. release?: string;
  118. } & BlockProps) {
  119. const value =
  120. typeof dataKey === 'function'
  121. ? dataKey(data?.data)
  122. : (data?.data?.[0]?.[dataKey] as number);
  123. const hasData = (!isLoading && value && value !== 0) || (value === 0 && allowZero);
  124. if (type === 'duration') {
  125. return (
  126. <Block title={title}>
  127. {hasData ? <DurationCell milliseconds={value} /> : UNDEFINED_TEXT}
  128. </Block>
  129. );
  130. }
  131. if (type === 'change') {
  132. return (
  133. <Block title={title}>
  134. {hasData ? <PercentChangeCell colorize deltaValue={value} /> : UNDEFINED_TEXT}
  135. </Block>
  136. );
  137. }
  138. return (
  139. <Block title={title}>{hasData ? <CountCell count={value} /> : UNDEFINED_TEXT}</Block>
  140. );
  141. }
  142. const BlockContainer = styled('div')`
  143. display: flex;
  144. gap: ${space(2)};
  145. `;