groupTags.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import styled from '@emotion/styled';
  2. import {useFetchIssueTags} from 'sentry/actionCreators/group';
  3. import {Alert} from 'sentry/components/alert';
  4. import Count from 'sentry/components/count';
  5. import {DeviceName} from 'sentry/components/deviceName';
  6. import {TAGS_DOCS_LINK} from 'sentry/components/events/eventTags/util';
  7. import GlobalSelectionLink from 'sentry/components/globalSelectionLink';
  8. import * as Layout from 'sentry/components/layouts/thirds';
  9. import ExternalLink from 'sentry/components/links/externalLink';
  10. import Link from 'sentry/components/links/link';
  11. import LoadingError from 'sentry/components/loadingError';
  12. import LoadingIndicator from 'sentry/components/loadingIndicator';
  13. import {extractSelectionParameters} from 'sentry/components/organizations/pageFilters/utils';
  14. import Panel from 'sentry/components/panels/panel';
  15. import PanelBody from 'sentry/components/panels/panelBody';
  16. import Version from 'sentry/components/version';
  17. import {t, tct} from 'sentry/locale';
  18. import {space} from 'sentry/styles/space';
  19. import type {Group} from 'sentry/types/group';
  20. import {percent} from 'sentry/utils';
  21. import {useLocation} from 'sentry/utils/useLocation';
  22. import useOrganization from 'sentry/utils/useOrganization';
  23. type GroupTagsProps = {
  24. baseUrl: string;
  25. environments: string[];
  26. group: Group;
  27. };
  28. type SimpleTag = {
  29. key: string;
  30. topValues: Array<{
  31. count: number;
  32. name: string;
  33. value: string;
  34. query?: string;
  35. }>;
  36. totalValues: number;
  37. };
  38. function GroupTags({group, baseUrl, environments}: GroupTagsProps) {
  39. const organization = useOrganization();
  40. const location = useLocation();
  41. const {
  42. data = [],
  43. isLoading,
  44. isError,
  45. refetch,
  46. } = useFetchIssueTags({
  47. orgSlug: organization.slug,
  48. groupId: group.id,
  49. environment: environments,
  50. });
  51. const alphabeticalTags = data.sort((a, b) => a.key.localeCompare(b.key));
  52. if (isLoading) {
  53. return <LoadingIndicator />;
  54. }
  55. if (isError) {
  56. return (
  57. <LoadingError
  58. message={t('There was an error loading issue tags.')}
  59. onRetry={refetch}
  60. />
  61. );
  62. }
  63. const getTagKeyTarget = (tag: SimpleTag) => {
  64. return {
  65. pathname: `${baseUrl}tags/${tag.key}/`,
  66. query: extractSelectionParameters(location.query),
  67. };
  68. };
  69. return (
  70. <Layout.Body>
  71. <Layout.Main fullWidth>
  72. <Alert type="info">
  73. {tct(
  74. 'Tags are automatically indexed for searching and breakdown charts. Learn how to [link: add custom tags to issues]',
  75. {
  76. link: <ExternalLink href={TAGS_DOCS_LINK} />,
  77. }
  78. )}
  79. </Alert>
  80. <Container>
  81. {alphabeticalTags.map((tag, tagIdx) => (
  82. <TagItem key={tagIdx}>
  83. <StyledPanel>
  84. <PanelBody withPadding>
  85. <TagHeading>
  86. <Link to={getTagKeyTarget(tag)}>
  87. <span data-test-id="tag-title">{tag.key}</span>
  88. </Link>
  89. </TagHeading>
  90. <UnstyledUnorderedList>
  91. {tag.topValues.map((tagValue, tagValueIdx) => (
  92. <li key={tagValueIdx} data-test-id={tag.key}>
  93. <TagBarGlobalSelectionLink
  94. to={{
  95. pathname: `${baseUrl}events/`,
  96. query: {
  97. query: tagValue.query || `${tag.key}:"${tagValue.value}"`,
  98. },
  99. }}
  100. >
  101. <TagBarBackground
  102. widthPercent={percent(tagValue.count, tag.totalValues) + '%'}
  103. />
  104. <TagBarLabel>
  105. {tag.key === 'release' ? (
  106. <Version version={tagValue.name} anchor={false} />
  107. ) : (
  108. <DeviceName value={tagValue.name} />
  109. )}
  110. </TagBarLabel>
  111. <TagBarCount>
  112. <Count value={tagValue.count} />
  113. </TagBarCount>
  114. </TagBarGlobalSelectionLink>
  115. </li>
  116. ))}
  117. </UnstyledUnorderedList>
  118. </PanelBody>
  119. </StyledPanel>
  120. </TagItem>
  121. ))}
  122. </Container>
  123. </Layout.Main>
  124. </Layout.Body>
  125. );
  126. }
  127. const Container = styled('div')`
  128. display: grid;
  129. grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  130. gap: ${space(2)};
  131. margin-bottom: ${space(2)};
  132. `;
  133. const StyledPanel = styled(Panel)`
  134. height: 100%;
  135. `;
  136. const TagHeading = styled('h5')`
  137. font-size: ${p => p.theme.fontSizeLarge};
  138. margin-bottom: 0;
  139. color: ${p => p.theme.linkColor};
  140. `;
  141. const UnstyledUnorderedList = styled('ul')`
  142. list-style: none;
  143. padding-left: 0;
  144. margin-bottom: 0;
  145. `;
  146. const TagItem = styled('div')`
  147. padding: 0;
  148. `;
  149. const TagBarBackground = styled('div')<{widthPercent: string}>`
  150. position: absolute;
  151. top: 0;
  152. bottom: 0;
  153. left: 0;
  154. background: ${p => p.theme.tagBar};
  155. border-radius: ${p => p.theme.borderRadius};
  156. width: ${p => p.widthPercent};
  157. `;
  158. const TagBarGlobalSelectionLink = styled(GlobalSelectionLink)`
  159. position: relative;
  160. display: flex;
  161. line-height: 2.2;
  162. color: ${p => p.theme.textColor};
  163. margin-bottom: ${space(0.5)};
  164. padding: 0 ${space(1)};
  165. background: ${p => p.theme.backgroundSecondary};
  166. border-radius: ${p => p.theme.borderRadius};
  167. overflow: hidden;
  168. &:hover {
  169. color: ${p => p.theme.textColor};
  170. text-decoration: underline;
  171. ${TagBarBackground} {
  172. background: ${p => p.theme.tagBarHover};
  173. }
  174. }
  175. `;
  176. const TagBarLabel = styled('div')`
  177. display: flex;
  178. align-items: center;
  179. font-size: ${p => p.theme.fontSizeMedium};
  180. position: relative;
  181. flex-grow: 1;
  182. ${p => p.theme.overflowEllipsis}
  183. `;
  184. const TagBarCount = styled('div')`
  185. font-size: ${p => p.theme.fontSizeMedium};
  186. position: relative;
  187. padding-left: ${space(2)};
  188. padding-right: ${space(1)};
  189. font-variant-numeric: tabular-nums;
  190. `;
  191. export default GroupTags;