projectQuickLinks.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. import styled from '@emotion/styled';
  2. import type {Location} from 'history';
  3. import {SectionHeading} from 'sentry/components/charts/styles';
  4. import GlobalSelectionLink from 'sentry/components/globalSelectionLink';
  5. import {Tooltip} from 'sentry/components/tooltip';
  6. import {IconLink} from 'sentry/icons';
  7. import {t} from 'sentry/locale';
  8. import {space} from 'sentry/styles/space';
  9. import type {Organization, Project} from 'sentry/types';
  10. import {decodeScalar} from 'sentry/utils/queryString';
  11. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  12. import {DEFAULT_MAX_DURATION} from 'sentry/views/performance/trends/utils';
  13. import {
  14. getPerformanceLandingUrl,
  15. getPerformanceTrendsUrl,
  16. } from 'sentry/views/performance/utils';
  17. import {SidebarSection} from './styles';
  18. type Props = {
  19. location: Location;
  20. organization: Organization;
  21. project?: Project;
  22. };
  23. function ProjectQuickLinks({organization, project, location}: Props) {
  24. function getTrendsLink() {
  25. const queryString = decodeScalar(location.query.query);
  26. const conditions = new MutableSearch(queryString || '');
  27. conditions.setFilterValues('tpm()', ['>0.01']);
  28. conditions.setFilterValues('transaction.duration', [
  29. '>0',
  30. `<${DEFAULT_MAX_DURATION}`,
  31. ]);
  32. return {
  33. pathname: getPerformanceTrendsUrl(organization),
  34. query: {
  35. project: project?.id,
  36. cursor: undefined,
  37. query: conditions.formatString(),
  38. },
  39. };
  40. }
  41. const quickLinks = [
  42. {
  43. title: t('User Feedback'),
  44. to: {
  45. pathname: `/organizations/${organization.slug}/user-feedback/`,
  46. query: {project: project?.id},
  47. },
  48. },
  49. {
  50. title: t('View Transactions'),
  51. to: {
  52. pathname: getPerformanceLandingUrl(organization),
  53. query: {project: project?.id},
  54. },
  55. disabled: !organization.features.includes('performance-view'),
  56. },
  57. {
  58. title: t('Most Improved/Regressed Transactions'),
  59. to: getTrendsLink(),
  60. disabled: !organization.features.includes('performance-view'),
  61. },
  62. ];
  63. return (
  64. <SidebarSection>
  65. <SectionHeading>{t('Quick Links')}</SectionHeading>
  66. {quickLinks
  67. // push disabled links to the bottom
  68. .sort((link1, link2) => Number(!!link1.disabled) - Number(!!link2.disabled))
  69. .map(({title, to, disabled}) => (
  70. <div key={title}>
  71. <Tooltip
  72. title={t("You don't have access to this feature")}
  73. disabled={!disabled}
  74. >
  75. <QuickLink to={to} disabled={disabled}>
  76. <IconLink />
  77. <QuickLinkText>{title}</QuickLinkText>
  78. </QuickLink>
  79. </Tooltip>
  80. </div>
  81. ))}
  82. </SidebarSection>
  83. );
  84. }
  85. const QuickLink = styled(p =>
  86. p.disabled ? (
  87. <span className={p.className}>{p.children}</span>
  88. ) : (
  89. <GlobalSelectionLink {...p} />
  90. )
  91. )<{
  92. disabled?: boolean;
  93. }>`
  94. margin-bottom: ${space(1)};
  95. display: grid;
  96. align-items: center;
  97. gap: ${space(1)};
  98. grid-template-columns: auto 1fr;
  99. ${p =>
  100. p.disabled &&
  101. `
  102. color: ${p.theme.gray200};
  103. cursor: not-allowed;
  104. `}
  105. `;
  106. const QuickLinkText = styled('span')`
  107. font-size: ${p => p.theme.fontSizeMedium};
  108. ${p => p.theme.overflowEllipsis}
  109. `;
  110. export default ProjectQuickLinks;