featureBadge.tsx 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import {Fragment} from 'react';
  2. import {useTheme} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import {captureException, withScope} from '@sentry/react';
  5. import type {Severity} from '@sentry/types';
  6. import CircleIndicator from 'sentry/components/circleIndicator';
  7. import Tag from 'sentry/components/tagDeprecated';
  8. import Tooltip from 'sentry/components/tooltip';
  9. import {t} from 'sentry/locale';
  10. import space from 'sentry/styles/space';
  11. type BadgeProps = {
  12. type: 'alpha' | 'beta' | 'new';
  13. expiresAt?: Date;
  14. noTooltip?: boolean;
  15. title?: string;
  16. variant?: 'indicator' | 'badge';
  17. };
  18. type Props = Omit<React.HTMLAttributes<HTMLDivElement>, keyof BadgeProps> & BadgeProps;
  19. const defaultTitles = {
  20. alpha: t('This feature is internal and available for QA purposes'),
  21. beta: t('This feature is available for early adopters and may change'),
  22. new: t('This feature is new! Try it out and let us know what you think'),
  23. };
  24. const labels = {
  25. alpha: t('alpha'),
  26. beta: t('beta'),
  27. new: t('new'),
  28. };
  29. function BaseFeatureBadge({
  30. type,
  31. variant = 'badge',
  32. title,
  33. noTooltip,
  34. expiresAt,
  35. ...props
  36. }: Props) {
  37. const theme = useTheme();
  38. if (expiresAt && expiresAt.valueOf() < Date.now()) {
  39. // Only get 1% of events as we don't need many to know that a badge needs to be cleaned up.
  40. if (Math.random() < 0.01) {
  41. withScope(scope => {
  42. scope.setTag('title', title);
  43. scope.setTag('type', type);
  44. scope.setLevel('warning' as Severity);
  45. captureException(new Error('Expired Feature Badge'));
  46. });
  47. }
  48. return null;
  49. }
  50. return (
  51. <div {...props}>
  52. <Tooltip title={title ?? defaultTitles[type]} disabled={noTooltip} position="right">
  53. <Fragment>
  54. {variant === 'badge' && <StyledTag priority={type}>{labels[type]}</StyledTag>}
  55. {variant === 'indicator' && (
  56. <CircleIndicator color={theme.badge[type].indicatorColor} size={8} />
  57. )}
  58. </Fragment>
  59. </Tooltip>
  60. </div>
  61. );
  62. }
  63. const StyledTag = styled(Tag)`
  64. padding: 3px ${space(0.75)};
  65. `;
  66. const FeatureBadge = styled(BaseFeatureBadge)`
  67. display: inline-flex;
  68. align-items: center;
  69. margin-left: ${space(0.75)};
  70. position: relative;
  71. top: -1px;
  72. `;
  73. export default FeatureBadge;