featureDisabled.tsx 4.6 KB

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