featureDisabled.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import {Fragment, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import {Alert, AlertProps} from 'sentry/components/alert';
  4. import {Button, ButtonLabel} from 'sentry/components/button';
  5. import ExternalLink from 'sentry/components/links/externalLink';
  6. import {CONFIG_DOCS_URL} from 'sentry/constants';
  7. import {IconChevron, IconCopy} from 'sentry/icons';
  8. import {t, tct} from 'sentry/locale';
  9. import {space} from 'sentry/styles/space';
  10. import {selectText} from 'sentry/utils/selectText';
  11. import useCopyToClipboard from 'sentry/utils/useCopyToClipboard';
  12. const installText = (features: Props['features'], featureName: string): string => {
  13. const featuresList = Array.isArray(features) ? features : [features];
  14. return `# ${t('Enables the %s feature', featureName)}\n${featuresList
  15. .map(f => `SENTRY_FEATURES['${f}'] = True`)
  16. .join('\n')}`;
  17. };
  18. type Props = {
  19. /**
  20. * The English name of the feature. This is used in the comment that will
  21. * be outputted above the example line of code to enable the feature.
  22. */
  23. featureName: string;
  24. /**
  25. * The feature flag keys that should be displayed in the code example for
  26. * enabling the feature.
  27. */
  28. features: string | string[];
  29. /**
  30. * Render the disabled message within a warning Alert. A custom Alert
  31. * component may be provided.
  32. *
  33. * Attaches additional styles to the FeatureDisabled component to make it
  34. * look consistent within the Alert.
  35. */
  36. alert?: boolean | React.ComponentType<AlertProps>;
  37. /**
  38. * Do not show the help toggle. The description will always be rendered.
  39. */
  40. hideHelpToggle?: boolean;
  41. /**
  42. * A custom message to display. Defaults to a generic disabled message.
  43. */
  44. message?: string;
  45. };
  46. /**
  47. * DisabledInfo renders a component informing that a feature has been disabled.
  48. *
  49. * By default this component will render a help button which toggles more
  50. * information about why the feature is disabled, showing the missing feature
  51. * flag and linking to documentation for managing sentry server feature flags.
  52. */
  53. function FeatureDisabled({
  54. features,
  55. featureName,
  56. alert,
  57. hideHelpToggle,
  58. message = t('This feature is not enabled on your Sentry installation.'),
  59. }: Props) {
  60. const [showHelp, setShowHelp] = useState(false);
  61. const snippet = installText(features, featureName);
  62. const {onClick} = useCopyToClipboard({text: snippet});
  63. function renderHelp() {
  64. return (
  65. <Fragment>
  66. <HelpText>
  67. {tct(
  68. `Enable this feature on your sentry installation by adding the
  69. following configuration into your [configFile:sentry.conf.py].
  70. See [configLink:the configuration documentation] for more
  71. details.`,
  72. {
  73. configFile: <code />,
  74. configLink: <ExternalLink href={CONFIG_DOCS_URL} />,
  75. }
  76. )}
  77. </HelpText>
  78. <CopyButton borderless icon={<IconCopy size="xs" />} onClick={onClick} size="xs">
  79. {t('Copy to Clipboard')}
  80. </CopyButton>
  81. <Pre onClick={e => selectText(e.target as HTMLElement)}>
  82. <code>{snippet}</code>
  83. </Pre>
  84. </Fragment>
  85. );
  86. }
  87. if (!alert) {
  88. const showDescription = hideHelpToggle || showHelp;
  89. return (
  90. <Fragment>
  91. <FeatureDisabledMessage>
  92. {message}
  93. {!hideHelpToggle && (
  94. <ToggleButton
  95. priority="link"
  96. size="xs"
  97. onClick={() => setShowHelp(!showHelp)}
  98. >
  99. {t('Help')}
  100. <IconChevron direction={showDescription ? 'up' : 'down'} />
  101. </ToggleButton>
  102. )}
  103. </FeatureDisabledMessage>
  104. {showDescription && <HelpDescription>{renderHelp()}</HelpDescription>}
  105. </Fragment>
  106. );
  107. }
  108. const AlertComponent = typeof alert === 'boolean' ? Alert : alert;
  109. return (
  110. <AlertComponent type="warning" showIcon expand={renderHelp()}>
  111. {message}
  112. </AlertComponent>
  113. );
  114. }
  115. const FeatureDisabledMessage = styled('div')`
  116. display: flex;
  117. justify-content: space-between;
  118. line-height: ${p => p.theme.text.lineHeightBody};
  119. `;
  120. const HelpDescription = styled('div')`
  121. margin-top: ${space(1)};
  122. pre,
  123. code {
  124. margin-bottom: 0;
  125. white-space: pre;
  126. }
  127. button {
  128. margin-bottom: ${space(0.5)};
  129. }
  130. `;
  131. const HelpText = styled('p')`
  132. margin-bottom: ${space(1)};
  133. `;
  134. const ToggleButton = styled(Button)`
  135. color: ${p => p.theme.active};
  136. height: ${p => p.theme.text.lineHeightBody}em;
  137. min-height: ${p => p.theme.text.lineHeightBody}em;
  138. &:hover {
  139. color: ${p => p.theme.activeHover};
  140. }
  141. ${ButtonLabel} {
  142. display: grid;
  143. grid-auto-flow: column;
  144. gap: ${space(1)};
  145. }
  146. `;
  147. const CopyButton = styled(Button)`
  148. margin-left: auto;
  149. `;
  150. const Pre = styled('pre')`
  151. margin-bottom: 0;
  152. overflow: auto;
  153. `;
  154. export default FeatureDisabled;