commandSource.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import {Component} from 'react';
  2. import {PlainRoute} from 'react-router';
  3. import {openHelpSearchModal} from 'sentry/actionCreators/modal';
  4. import {openSudo} from 'sentry/actionCreators/sudoModal';
  5. import Access from 'sentry/components/acl/access';
  6. import {NODE_ENV, USING_CUSTOMER_DOMAIN} from 'sentry/constants';
  7. import {t, toggleLocaleDebug} from 'sentry/locale';
  8. import ConfigStore from 'sentry/stores/configStore';
  9. import {createFuzzySearch, Fuse} from 'sentry/utils/fuzzySearch';
  10. import {ChildProps, ResultItem} from './types';
  11. type Action = {
  12. action: () => void;
  13. description: string;
  14. requiresSuperuser: boolean;
  15. title: string;
  16. };
  17. const ACTIONS: Action[] = [
  18. {
  19. title: t('Open Sudo Modal'),
  20. description: t('Open Sudo Modal to re-identify yourself.'),
  21. requiresSuperuser: false,
  22. action: () =>
  23. openSudo({
  24. sudo: true,
  25. }),
  26. },
  27. {
  28. title: t('Open Superuser Modal'),
  29. description: t('Open Superuser Modal to re-identify yourself.'),
  30. requiresSuperuser: true,
  31. action: () =>
  32. openSudo({
  33. isSuperuser: true,
  34. }),
  35. },
  36. {
  37. title: t('Toggle dark mode'),
  38. description: t('Toggle dark mode (superuser only atm)'),
  39. requiresSuperuser: true,
  40. action: () =>
  41. ConfigStore.set('theme', ConfigStore.get('theme') === 'dark' ? 'light' : 'dark'),
  42. },
  43. {
  44. title: t('Toggle Translation Markers'),
  45. description: t('Toggles translation markers on or off in the application'),
  46. requiresSuperuser: true,
  47. action: () => {
  48. toggleLocaleDebug();
  49. window.location.reload();
  50. },
  51. },
  52. {
  53. title: t('Search Documentation and FAQ'),
  54. description: t('Open the Documentation and FAQ search modal.'),
  55. requiresSuperuser: false,
  56. action: () => {
  57. openHelpSearchModal();
  58. },
  59. },
  60. ];
  61. // Add a command palette option for opening in production when using dev-ui
  62. if (NODE_ENV === 'development' && window?.__initialData?.isOnPremise === false) {
  63. const customerUrl = new URL(
  64. USING_CUSTOMER_DOMAIN && window?.__initialData?.customerDomain?.organizationUrl
  65. ? window.__initialData.customerDomain.organizationUrl
  66. : window.__initialData?.links?.sentryUrl
  67. );
  68. ACTIONS.push({
  69. title: t('Open in Production'),
  70. description: t('Open the current page in sentry.io'),
  71. requiresSuperuser: false,
  72. action: () => {
  73. const url = new URL(window.location.toString());
  74. url.host = customerUrl.host;
  75. url.protocol = customerUrl.protocol;
  76. url.port = '';
  77. window.open(url.toString(), '_blank');
  78. },
  79. });
  80. }
  81. type Props = {
  82. children: (props: ChildProps) => React.ReactElement;
  83. isSuperuser: boolean;
  84. /**
  85. * search term
  86. */
  87. query: string;
  88. /**
  89. * Array of routes to search
  90. */
  91. searchMap?: PlainRoute[];
  92. /**
  93. * fuse.js options
  94. */
  95. searchOptions?: Fuse.IFuseOptions<Action>;
  96. };
  97. type State = {
  98. fuzzy: null | Fuse<Action>;
  99. };
  100. /**
  101. * This source is a hardcoded list of action creators and/or routes maybe
  102. */
  103. class CommandSource extends Component<Props, State> {
  104. static defaultProps = {
  105. searchMap: [],
  106. searchOptions: {},
  107. };
  108. state: State = {
  109. fuzzy: null,
  110. };
  111. componentDidMount() {
  112. this.createSearch(ACTIONS);
  113. }
  114. async createSearch(searchMap: Action[]) {
  115. const options = {
  116. ...this.props.searchOptions,
  117. keys: ['title', 'description'],
  118. };
  119. this.setState({
  120. fuzzy: await createFuzzySearch<Action>(searchMap || [], options),
  121. });
  122. }
  123. render() {
  124. const {searchMap, query, isSuperuser, children} = this.props;
  125. const {fuzzy} = this.state;
  126. const results =
  127. fuzzy
  128. ?.search(query)
  129. .filter(({item}) => !item.requiresSuperuser || isSuperuser)
  130. .map(value => {
  131. const {item, ...rest} = value;
  132. return {
  133. item: {
  134. ...item,
  135. sourceType: 'command',
  136. resultType: 'command',
  137. } as ResultItem,
  138. ...rest,
  139. };
  140. }) ?? [];
  141. return children({
  142. isLoading: searchMap === null,
  143. results,
  144. });
  145. }
  146. }
  147. function CommandSourceWithFeature(props: Omit<Props, 'isSuperuser'>) {
  148. return (
  149. <Access isSuperuser>
  150. {({hasSuperuser}) => <CommandSource {...props} isSuperuser={hasSuperuser} />}
  151. </Access>
  152. );
  153. }
  154. export default CommandSourceWithFeature;
  155. export {CommandSource};