projectQuickLinks.tsx 3.3 KB

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