focusTabs.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import type {ReactNode} from 'react';
  2. import {Fragment} from 'react';
  3. import styled from '@emotion/styled';
  4. import queryString from 'query-string';
  5. import FeatureBadge from 'sentry/components/featureBadge';
  6. import ExternalLink from 'sentry/components/links/externalLink';
  7. import ListLink from 'sentry/components/links/listLink';
  8. import ScrollableTabs from 'sentry/components/replays/scrollableTabs';
  9. import {Tooltip} from 'sentry/components/tooltip';
  10. import {t} from 'sentry/locale';
  11. import type {Organization} from 'sentry/types';
  12. import {trackAnalytics} from 'sentry/utils/analytics';
  13. import useActiveReplayTab, {TabKey} from 'sentry/utils/replays/hooks/useActiveReplayTab';
  14. import {useLocation} from 'sentry/utils/useLocation';
  15. import useOrganization from 'sentry/utils/useOrganization';
  16. function getReplayTabs({
  17. organization,
  18. isVideoReplay,
  19. }: {
  20. isVideoReplay: boolean;
  21. organization: Organization;
  22. }): Record<TabKey, ReactNode> {
  23. // The new trace table inside Breadcrumb items:
  24. const hasTraceTable = organization.features.includes('session-replay-trace-table');
  25. return {
  26. [TabKey.BREADCRUMBS]: t('Breadcrumbs'),
  27. [TabKey.CONSOLE]: t('Console'),
  28. [TabKey.NETWORK]: t('Network'),
  29. [TabKey.ERRORS]: t('Errors'),
  30. [TabKey.TRACE]: hasTraceTable || isVideoReplay ? null : t('Trace'),
  31. [TabKey.PERF]: null,
  32. [TabKey.A11Y]: isVideoReplay ? null : (
  33. <Fragment>
  34. <Tooltip
  35. isHoverable
  36. title={
  37. <ExternalLink
  38. href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/What_is_accessibility"
  39. onClick={e => {
  40. e.stopPropagation();
  41. }}
  42. >
  43. {t('What is accessibility?')}
  44. </ExternalLink>
  45. }
  46. >
  47. {t('Accessibility')}
  48. </Tooltip>
  49. <FlexFeatureBadge
  50. type="alpha"
  51. title={t('This feature is available for early adopters and may change')}
  52. />
  53. </Fragment>
  54. ),
  55. [TabKey.MEMORY]: isVideoReplay ? null : t('Memory'),
  56. [TabKey.TAGS]: t('Tags'),
  57. };
  58. }
  59. type Props = {
  60. isVideoReplay: boolean;
  61. className?: string;
  62. };
  63. function FocusTabs({className, isVideoReplay}: Props) {
  64. const organization = useOrganization();
  65. const {pathname, query} = useLocation();
  66. const {getActiveTab, setActiveTab} = useActiveReplayTab({isVideoReplay});
  67. const activeTab = getActiveTab();
  68. const supportedVideoTabs = [TabKey.TAGS, TabKey.ERRORS, TabKey.BREADCRUMBS];
  69. const unsupportedVideoTab = tab => {
  70. return isVideoReplay && !supportedVideoTabs.includes(tab);
  71. };
  72. return (
  73. <ScrollableTabs className={className} underlined>
  74. {Object.entries(getReplayTabs({organization, isVideoReplay})).map(([tab, label]) =>
  75. label ? (
  76. <ListLink
  77. disabled={unsupportedVideoTab(tab)}
  78. data-test-id={`replay-details-${tab}-btn`}
  79. key={tab}
  80. isActive={() => (unsupportedVideoTab(tab) ? false : tab === activeTab)}
  81. to={`${pathname}?${queryString.stringify({...query, t_main: tab})}`}
  82. onClick={e => {
  83. e.preventDefault();
  84. setActiveTab(tab);
  85. trackAnalytics('replay.details-tab-changed', {
  86. tab,
  87. organization,
  88. });
  89. }}
  90. >
  91. <Tooltip
  92. title={unsupportedVideoTab(tab) ? t('This feature is coming soon') : null}
  93. >
  94. {label}
  95. </Tooltip>
  96. </ListLink>
  97. ) : null
  98. )}
  99. </ScrollableTabs>
  100. );
  101. }
  102. const FlexFeatureBadge = styled(FeatureBadge)`
  103. & > span {
  104. display: flex;
  105. }
  106. `;
  107. export default FocusTabs;