ManageCommandsProcess.jsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. import React, { useCallback, useState } from 'react';
  2. import PropTypes from 'prop-types';
  3. import { useTranslation } from 'react-i18next';
  4. import { defaultSupportedCommandsNameForBroadcastUse, defaultSupportedCommandsNameForSingleUse } from '@growi/slack';
  5. import loggerFactory from '~/utils/logger';
  6. import { toastSuccess, toastError } from '../../../client/util/apiNotification';
  7. const logger = loggerFactory('growi:SlackIntegration:ManageCommandsProcess');
  8. const PermissionTypes = {
  9. ALLOW_ALL: 'allowAll',
  10. DENY_ALL: 'denyAll',
  11. ALLOW_SPECIFIED: 'allowSpecified',
  12. };
  13. const CommandUsageTypes = {
  14. BROADCAST_USE: 'broadcastUse',
  15. SINGLE_USE: 'singleUse',
  16. };
  17. // A utility function that returns the new state but identical to the previous state
  18. const getUpdatedChannelsList = (prevState, commandName, value) => {
  19. // string to array
  20. const allowedChannelsArray = value.split(',');
  21. // trim whitespace from all elements
  22. const trimedAllowedChannelsArray = allowedChannelsArray.map(channelName => channelName.trim());
  23. prevState[commandName] = trimedAllowedChannelsArray;
  24. return prevState;
  25. };
  26. // A utility function that returns the new state
  27. const getUpdatedPermissionSettings = (prevState, commandName, value) => {
  28. const newState = { ...prevState };
  29. switch (value) {
  30. case PermissionTypes.ALLOW_ALL:
  31. newState[commandName] = true;
  32. break;
  33. case PermissionTypes.DENY_ALL:
  34. newState[commandName] = false;
  35. break;
  36. case PermissionTypes.ALLOW_SPECIFIED:
  37. newState[commandName] = [];
  38. break;
  39. default:
  40. logger.error('Not implemented');
  41. break;
  42. }
  43. return newState;
  44. };
  45. // A utility function that returns the permission type from the permission value
  46. const getPermissionTypeFromValue = (value) => {
  47. if (Array.isArray(value)) {
  48. return PermissionTypes.ALLOW_SPECIFIED;
  49. }
  50. if (typeof value === 'boolean') {
  51. return value ? PermissionTypes.ALLOW_ALL : PermissionTypes.DENY_ALL;
  52. }
  53. logger.error('The value type must be boolean or string[]');
  54. };
  55. // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
  56. const ManageCommandsProcess = ({
  57. apiv3Put, slackAppIntegrationId, permissionsForBroadcastUseCommands, permissionsForSingleUseCommands,
  58. }) => {
  59. const { t } = useTranslation();
  60. const [permissionsForBroadcastUseCommandsState, setPermissionsForBroadcastUseCommandsState] = useState({
  61. search: permissionsForBroadcastUseCommands.search,
  62. });
  63. const [permissionsForSingleUseCommandsState, setPermissionsForSingleUseCommandsState] = useState({
  64. note: permissionsForSingleUseCommands.note,
  65. keep: permissionsForSingleUseCommands.keep,
  66. });
  67. const [currentPermissionTypes, setCurrentPermissionTypes] = useState(() => {
  68. const initialState = {};
  69. Object.entries(permissionsForBroadcastUseCommandsState).forEach((entry) => {
  70. const [commandName, value] = entry;
  71. initialState[commandName] = getPermissionTypeFromValue(value);
  72. });
  73. Object.entries(permissionsForSingleUseCommandsState).forEach((entry) => {
  74. const [commandName, value] = entry;
  75. initialState[commandName] = getPermissionTypeFromValue(value);
  76. });
  77. return initialState;
  78. });
  79. const updatePermissionsForBroadcastUseCommandsState = useCallback((e) => {
  80. const { target } = e;
  81. const { name: commandName, value } = target;
  82. // update state
  83. setPermissionsForBroadcastUseCommandsState(prev => getUpdatedPermissionSettings(prev, commandName, value));
  84. setCurrentPermissionTypes((prevState) => {
  85. const newState = { ...prevState };
  86. newState[commandName] = value;
  87. return newState;
  88. });
  89. }, []);
  90. const updatePermissionsForSingleUseCommandsState = useCallback((e) => {
  91. const { target } = e;
  92. const { name: commandName, value } = target;
  93. // update state
  94. setPermissionsForSingleUseCommandsState(prev => getUpdatedPermissionSettings(prev, commandName, value));
  95. setCurrentPermissionTypes((prevState) => {
  96. const newState = { ...prevState };
  97. newState[commandName] = value;
  98. return newState;
  99. });
  100. }, []);
  101. const updateChannelsListForBroadcastUseCommandsState = useCallback((e) => {
  102. const { target } = e;
  103. const { name: commandName, value } = target;
  104. // update state
  105. setPermissionsForBroadcastUseCommandsState(prev => getUpdatedChannelsList(prev, commandName, value));
  106. }, []);
  107. const updateChannelsListForSingleUseCommandsState = useCallback((e) => {
  108. const { target } = e;
  109. const { name: commandName, value } = target;
  110. // update state
  111. setPermissionsForSingleUseCommandsState(prev => getUpdatedChannelsList(prev, commandName, value));
  112. }, []);
  113. const updateCommandsHandler = async(e) => {
  114. try {
  115. await apiv3Put(`/slack-integration-settings/slack-app-integrations/${slackAppIntegrationId}/supported-commands`, {
  116. permissionsForBroadcastUseCommands: permissionsForBroadcastUseCommandsState,
  117. permissionsForSingleUseCommands: permissionsForSingleUseCommandsState,
  118. });
  119. toastSuccess(t('toaster.update_successed', { target: 'Token' }));
  120. }
  121. catch (err) {
  122. toastError(err);
  123. logger.error(err);
  124. }
  125. };
  126. const PermissionSettingForEachCommandComponent = ({ commandName, commandUsageType }) => {
  127. const hiddenClass = currentPermissionTypes[commandName] === PermissionTypes.ALLOW_SPECIFIED ? '' : 'd-none';
  128. const isCommandBroadcastUse = commandUsageType === CommandUsageTypes.BROADCAST_USE;
  129. const permissionSettings = isCommandBroadcastUse ? permissionsForBroadcastUseCommandsState : permissionsForSingleUseCommandsState;
  130. const permission = permissionSettings[commandName];
  131. if (permission === undefined) logger.error('Must be implemented');
  132. const textareaDefaultValue = Array.isArray(permission) ? permission.join(',') : '';
  133. return (
  134. <div className="my-1 mb-2">
  135. <div className="row align-items-center mb-3">
  136. <p className="col-md-5 text-md-right text-capitalize mb-2"><strong>{commandName}</strong></p>
  137. <div className="col dropdown">
  138. <button
  139. className="btn btn-outline-secondary dropdown-toggle text-right col-12 col-md-auto"
  140. type="button"
  141. id="dropdownMenuButton"
  142. data-toggle="dropdown"
  143. aria-haspopup="true"
  144. aria-expanded="true"
  145. >
  146. <span className="float-left">
  147. {currentPermissionTypes[commandName] === PermissionTypes.ALLOW_ALL
  148. && t('admin:slack_integration.accordion.allow_all')}
  149. {currentPermissionTypes[commandName] === PermissionTypes.DENY_ALL
  150. && t('admin:slack_integration.accordion.deny_all')}
  151. {currentPermissionTypes[commandName] === PermissionTypes.ALLOW_SPECIFIED
  152. && t('admin:slack_integration.accordion.allow_specified')}
  153. </span>
  154. </button>
  155. <div className="dropdown-menu">
  156. <button
  157. className="dropdown-item"
  158. type="button"
  159. name={commandName}
  160. value={PermissionTypes.ALLOW_ALL}
  161. onClick={isCommandBroadcastUse ? updatePermissionsForBroadcastUseCommandsState : updatePermissionsForSingleUseCommandsState}
  162. >
  163. {t('admin:slack_integration.accordion.allow_all_long')}
  164. </button>
  165. <button
  166. className="dropdown-item"
  167. type="button"
  168. name={commandName}
  169. value={PermissionTypes.DENY_ALL}
  170. onClick={isCommandBroadcastUse ? updatePermissionsForBroadcastUseCommandsState : updatePermissionsForSingleUseCommandsState}
  171. >
  172. {t('admin:slack_integration.accordion.deny_all_long')}
  173. </button>
  174. <button
  175. className="dropdown-item"
  176. type="button"
  177. name={commandName}
  178. value={PermissionTypes.ALLOW_SPECIFIED}
  179. onClick={isCommandBroadcastUse ? updatePermissionsForBroadcastUseCommandsState : updatePermissionsForSingleUseCommandsState}
  180. >
  181. {t('admin:slack_integration.accordion.allow_specified_long')}
  182. </button>
  183. </div>
  184. </div>
  185. </div>
  186. <div className={`row ${hiddenClass}`}>
  187. <div className="col-md-7 offset-md-5">
  188. <textarea
  189. className="form-control"
  190. type="textarea"
  191. name={commandName}
  192. defaultValue={textareaDefaultValue}
  193. onChange={isCommandBroadcastUse ? updateChannelsListForBroadcastUseCommandsState : updateChannelsListForSingleUseCommandsState}
  194. />
  195. <p className="form-text text-muted small">
  196. {t('admin:slack_integration.accordion.allowed_channels_description', { commandName })}
  197. <br />
  198. </p>
  199. </div>
  200. </div>
  201. </div>
  202. );
  203. };
  204. PermissionSettingForEachCommandComponent.propTypes = {
  205. commandName: PropTypes.string,
  206. commandUsageType: PropTypes.string,
  207. };
  208. const PermissionSettingsForEachCommandTypeComponent = ({ commandUsageType }) => {
  209. const isCommandBroadcastUse = commandUsageType === CommandUsageTypes.BROADCAST_USE;
  210. const defaultCommandsName = isCommandBroadcastUse ? defaultSupportedCommandsNameForBroadcastUse : defaultSupportedCommandsNameForSingleUse;
  211. return (
  212. <>
  213. <div className="row">
  214. <div className="col-md-7 offset-md-2">
  215. <p className="font-weight-bold mb-1">{isCommandBroadcastUse ? 'Multiple GROWI' : 'Single GROWI'}</p>
  216. <p className="text-muted">
  217. {isCommandBroadcastUse
  218. ? t('admin:slack_integration.accordion.multiple_growi_command')
  219. : t('admin:slack_integration.accordion.single_growi_command')}
  220. </p>
  221. </div>
  222. </div>
  223. <div className="custom-control custom-checkbox">
  224. <div className="row mb-5 d-block">
  225. {defaultCommandsName.map((commandName) => {
  226. // eslint-disable-next-line max-len
  227. return <PermissionSettingForEachCommandComponent key={`${commandName}-component`} commandName={commandName} commandUsageType={commandUsageType} />;
  228. })}
  229. </div>
  230. </div>
  231. </>
  232. );
  233. };
  234. PermissionSettingsForEachCommandTypeComponent.propTypes = {
  235. commandUsageType: PropTypes.string,
  236. };
  237. return (
  238. <div className="py-4 px-5">
  239. <p className="mb-4 font-weight-bold">{t('admin:slack_integration.accordion.manage_commands')}</p>
  240. <div className="row d-flex flex-column align-items-center">
  241. <div className="col-8">
  242. {Object.values(CommandUsageTypes).map((commandUsageType) => {
  243. return <PermissionSettingsForEachCommandTypeComponent key={commandUsageType} commandUsageType={commandUsageType} />;
  244. })}
  245. </div>
  246. </div>
  247. <div className="row">
  248. <button
  249. type="submit"
  250. className="btn btn-primary mx-auto"
  251. onClick={updateCommandsHandler}
  252. >
  253. { t('Update') }
  254. </button>
  255. </div>
  256. </div>
  257. );
  258. };
  259. ManageCommandsProcess.propTypes = {
  260. apiv3Put: PropTypes.func,
  261. slackAppIntegrationId: PropTypes.string.isRequired,
  262. permissionsForBroadcastUseCommands: PropTypes.object.isRequired,
  263. permissionsForSingleUseCommands: PropTypes.object.isRequired,
  264. };
  265. export default ManageCommandsProcess;