featureDisabled.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import * as React from 'react';
  2. import styled from '@emotion/styled';
  3. import Alert from 'app/components/alert';
  4. import Button from 'app/components/button';
  5. import Clipboard from 'app/components/clipboard';
  6. import ExternalLink from 'app/components/links/externalLink';
  7. import {CONFIG_DOCS_URL} from 'app/constants';
  8. import {IconChevron, IconCopy, IconInfo, IconLock} from 'app/icons';
  9. import {t, tct} from 'app/locale';
  10. import space from 'app/styles/space';
  11. import {selectText} from 'app/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 feature flag keys that should be displayed in the code example for
  19. * enabling the feature.
  20. */
  21. features: string[];
  22. /**
  23. * The English name of the feature. This is used in the comment that will
  24. * be outputted above the example line of code to enable the feature.
  25. */
  26. featureName: 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. type State = {
  45. showHelp: boolean;
  46. };
  47. /**
  48. * DisabledInfo renders a component informing that a feature has been disabled.
  49. *
  50. * By default this component will render a help button which toggles more
  51. * information about why the feature is disabled, showing the missing feature
  52. * flag and linking to documentation for managing sentry server feature flags.
  53. */
  54. class FeatureDisabled extends React.Component<Props, State> {
  55. static defaultProps: Partial<Props> = {
  56. message: t('This feature is not enabled on your Sentry installation.'),
  57. };
  58. state: State = {
  59. showHelp: false,
  60. };
  61. toggleHelp = (e: React.MouseEvent) => {
  62. e.preventDefault();
  63. this.setState(state => ({showHelp: !state.showHelp}));
  64. };
  65. renderFeatureDisabled() {
  66. const {showHelp} = this.state;
  67. const {message, features, featureName, hideHelpToggle} = this.props;
  68. const showDescription = hideHelpToggle || showHelp;
  69. return (
  70. <React.Fragment>
  71. <FeatureDisabledMessage>
  72. {message}
  73. {!hideHelpToggle && (
  74. <HelpButton
  75. icon={
  76. showHelp ? (
  77. <IconChevron direction="down" size="xs" />
  78. ) : (
  79. <IconInfo size="xs" />
  80. )
  81. }
  82. priority="link"
  83. size="xsmall"
  84. onClick={this.toggleHelp}
  85. >
  86. {t('Help')}
  87. </HelpButton>
  88. )}
  89. </FeatureDisabledMessage>
  90. {showDescription && (
  91. <HelpDescription
  92. onClick={e => {
  93. e.stopPropagation();
  94. e.preventDefault();
  95. }}
  96. >
  97. <p>
  98. {tct(
  99. `Enable this feature on your sentry installation by adding the
  100. following configuration into your [configFile:sentry.conf.py].
  101. See [configLink:the configuration documentation] for more
  102. details.`,
  103. {
  104. configFile: <code />,
  105. configLink: <ExternalLink href={CONFIG_DOCS_URL} />,
  106. }
  107. )}
  108. </p>
  109. <Clipboard hideUnsupported value={installText(features, featureName)}>
  110. <Button
  111. borderless
  112. size="xsmall"
  113. onClick={e => {
  114. e.stopPropagation();
  115. e.preventDefault();
  116. }}
  117. icon={<IconCopy size="xs" />}
  118. >
  119. {t('Copy to Clipboard')}
  120. </Button>
  121. </Clipboard>
  122. <pre onClick={e => selectText(e.target as HTMLElement)}>
  123. <code>{installText(features, featureName)}</code>
  124. </pre>
  125. </HelpDescription>
  126. )}
  127. </React.Fragment>
  128. );
  129. }
  130. render() {
  131. const {alert} = this.props;
  132. if (!alert) {
  133. return this.renderFeatureDisabled();
  134. }
  135. const AlertComponent = typeof alert === 'boolean' ? Alert : alert;
  136. return (
  137. <AlertComponent type="warning" icon={<IconLock size="xs" />}>
  138. <AlertWrapper>{this.renderFeatureDisabled()}</AlertWrapper>
  139. </AlertComponent>
  140. );
  141. }
  142. }
  143. const FeatureDisabledMessage = styled('div')`
  144. display: flex;
  145. justify-content: space-between;
  146. `;
  147. const HelpButton = styled(Button)`
  148. font-size: 0.8em;
  149. `;
  150. const HelpDescription = styled('div')`
  151. font-size: 0.9em;
  152. margin-top: ${space(1)};
  153. p {
  154. line-height: 1.5em;
  155. }
  156. pre,
  157. code {
  158. margin-bottom: 0;
  159. white-space: pre;
  160. }
  161. `;
  162. const AlertWrapper = styled('div')`
  163. ${HelpButton} {
  164. color: #6d6319;
  165. &:hover {
  166. color: #88750b;
  167. }
  168. }
  169. pre,
  170. code {
  171. background: #fbf7e0;
  172. }
  173. `;
  174. export default FeatureDisabled;