ManageCommandsProcess.jsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  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. // TODO: Add permittedChannelsForEachCommand to use data from server (props must have it) GW-7006
  56. const ManageCommandsProcess = ({
  57. apiv3Put, slackAppIntegrationId, permissionsForBroadcastUseCommands, permissionsForSingleUseCommands,
  58. }) => {
  59. const { t } = useTranslation();
  60. // TODO: use data from server GW-7006
  61. const [permissionsForBroadcastUseCommandsState, setPermissionsForBroadcastUseCommandsState] = useState({
  62. search: true,
  63. });
  64. const [permissionsForSingleUseCommandsState, setPermissionsForSingleUseCommandsState] = useState({
  65. create: false,
  66. togetter: [],
  67. });
  68. const [currentPermissionTypes, setCurrentPermissionTypes] = useState(() => {
  69. const initialState = {};
  70. Object.entries(permissionsForBroadcastUseCommandsState).forEach((entry) => {
  71. const [commandName, value] = entry;
  72. initialState[commandName] = getPermissionTypeFromValue(value);
  73. });
  74. Object.entries(permissionsForSingleUseCommandsState).forEach((entry) => {
  75. const [commandName, value] = entry;
  76. initialState[commandName] = getPermissionTypeFromValue(value);
  77. });
  78. return initialState;
  79. });
  80. const updatePermissionsForBroadcastUseCommandsState = useCallback((e) => {
  81. const { target } = e;
  82. const { name: commandName, value } = target;
  83. // update state
  84. setPermissionsForBroadcastUseCommandsState(prev => getUpdatedPermissionSettings(prev, commandName, value));
  85. setCurrentPermissionTypes((prevState) => {
  86. const newState = { ...prevState };
  87. newState[commandName] = value;
  88. return newState;
  89. });
  90. }, []);
  91. const updatePermissionsForSingleUseCommandsState = useCallback((e) => {
  92. const { target } = e;
  93. const { name: commandName, value } = target;
  94. // update state
  95. setPermissionsForSingleUseCommandsState(prev => getUpdatedPermissionSettings(prev, commandName, value));
  96. setCurrentPermissionTypes((prevState) => {
  97. const newState = { ...prevState };
  98. newState[commandName] = value;
  99. return newState;
  100. });
  101. }, []);
  102. const updateChannelsListForBroadcastUseCommandsState = useCallback((e) => {
  103. const { target } = e;
  104. const { name: commandName, value } = target;
  105. // update state
  106. setPermissionsForBroadcastUseCommandsState(prev => getUpdatedChannelsList(prev, commandName, value));
  107. }, []);
  108. const updateChannelsListForSingleUseCommandsState = useCallback((e) => {
  109. const { target } = e;
  110. const { name: commandName, value } = target;
  111. // update state
  112. setPermissionsForSingleUseCommandsState(prev => getUpdatedChannelsList(prev, commandName, value));
  113. }, []);
  114. const updateCommandsHandler = async(e) => {
  115. try {
  116. await apiv3Put(`/slack-integration-settings/slack-app-integrations/${slackAppIntegrationId}/supported-commands`, {
  117. permissionsForBroadcastUseCommands: permissionsForBroadcastUseCommandsState,
  118. permissionsForSingleUseCommands: permissionsForSingleUseCommandsState,
  119. });
  120. toastSuccess(t('toaster.update_successed', { target: 'Token' }));
  121. }
  122. catch (err) {
  123. toastError(err);
  124. logger.error(err);
  125. }
  126. };
  127. const PermissionSettingForEachCommandComponent = ({ commandName, commandUsageType }) => {
  128. const hiddenClass = currentPermissionTypes[commandName] === PermissionTypes.ALLOW_SPECIFIED ? '' : 'd-none';
  129. const isCommandBroadcastUse = commandUsageType === CommandUsageTypes.BROADCAST_USE;
  130. const permissionSettings = isCommandBroadcastUse ? permissionsForBroadcastUseCommandsState : permissionsForSingleUseCommandsState;
  131. const permission = permissionSettings[commandName];
  132. if (permission === undefined) logger.error('Must be implemented');
  133. const textareaDefaultValue = Array.isArray(permission) ? permission.join(',') : '';
  134. return (
  135. <div className="my-1 mb-2">
  136. <div className="row align-items-center mb-3">
  137. <p className="col my-auto text-capitalize align-middle">{commandName}</p>
  138. <div className="col dropdown">
  139. <button
  140. className="btn btn-outline-secondary dropdown-toggle text-right col-12 col-md-auto"
  141. type="button"
  142. id="dropdownMenuButton"
  143. data-toggle="dropdown"
  144. aria-haspopup="true"
  145. aria-expanded="true"
  146. >
  147. <span className="float-left">
  148. {currentPermissionTypes[commandName] === PermissionTypes.ALLOW_ALL
  149. && t('admin:slack_integration.accordion.allow_all')}
  150. {currentPermissionTypes[commandName] === PermissionTypes.DENY_ALL
  151. && t('admin:slack_integration.accordion.deny_all')}
  152. {currentPermissionTypes[commandName] === PermissionTypes.ALLOW_SPECIFIED
  153. && t('admin:slack_integration.accordion.allow_specified')}
  154. </span>
  155. </button>
  156. <div className="dropdown-menu">
  157. <button
  158. className="dropdown-item"
  159. type="button"
  160. name={commandName}
  161. value={PermissionTypes.ALLOW_ALL}
  162. onClick={isCommandBroadcastUse ? updatePermissionsForBroadcastUseCommandsState : updatePermissionsForSingleUseCommandsState}
  163. >
  164. {t('admin:slack_integration.accordion.allow_all_long')}
  165. </button>
  166. <button
  167. className="dropdown-item"
  168. type="button"
  169. name={commandName}
  170. value={PermissionTypes.DENY_ALL}
  171. onClick={isCommandBroadcastUse ? updatePermissionsForBroadcastUseCommandsState : updatePermissionsForSingleUseCommandsState}
  172. >
  173. {t('admin:slack_integration.accordion.deny_all_long')}
  174. </button>
  175. <button
  176. className="dropdown-item"
  177. type="button"
  178. name={commandName}
  179. value={PermissionTypes.ALLOW_SPECIFIED}
  180. onClick={isCommandBroadcastUse ? updatePermissionsForBroadcastUseCommandsState : updatePermissionsForSingleUseCommandsState}
  181. >
  182. {t('admin:slack_integration.accordion.allow_specified_long')}
  183. </button>
  184. </div>
  185. </div>
  186. </div>
  187. <div className={`row-12 row-md-6 ${hiddenClass}`}>
  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. );
  202. };
  203. PermissionSettingForEachCommandComponent.propTypes = {
  204. commandName: PropTypes.string,
  205. commandUsageType: PropTypes.string,
  206. };
  207. const PermissionSettingsForEachCommandTypeComponent = ({ commandUsageType }) => {
  208. const isCommandBroadcastUse = commandUsageType === CommandUsageTypes.BROADCAST_USE;
  209. const defaultCommandsName = isCommandBroadcastUse ? defaultSupportedCommandsNameForBroadcastUse : defaultSupportedCommandsNameForSingleUse;
  210. return (
  211. <>
  212. <p className="font-weight-bold mb-0">{isCommandBroadcastUse ? 'Multiple GROWI' : 'Single GROWI'}</p>
  213. <p className="text-muted mb-2">
  214. {isCommandBroadcastUse ? t('admin:slack_integration.accordion.multiple_growi_command') : t('admin:slack_integration.accordion.single_growi_command')}
  215. </p>
  216. <div className="custom-control custom-checkbox">
  217. <div className="row mb-5 d-block">
  218. {defaultCommandsName.map((commandName) => {
  219. // eslint-disable-next-line max-len
  220. return <PermissionSettingForEachCommandComponent key={`${commandName}-component`} commandName={commandName} commandUsageType={commandUsageType} />;
  221. })}
  222. </div>
  223. </div>
  224. </>
  225. );
  226. };
  227. PermissionSettingsForEachCommandTypeComponent.propTypes = {
  228. commandUsageType: PropTypes.string,
  229. };
  230. return (
  231. <div className="py-4 px-5">
  232. <p className="mb-4 font-weight-bold">{t('admin:slack_integration.accordion.manage_commands')}</p>
  233. <div className="row d-flex flex-column align-items-center">
  234. <div className="col-8">
  235. {Object.values(CommandUsageTypes).map((commandUsageType) => {
  236. return <PermissionSettingsForEachCommandTypeComponent key={commandUsageType} commandUsageType={commandUsageType} />;
  237. })}
  238. </div>
  239. </div>
  240. <div className="row">
  241. <button
  242. type="submit"
  243. className="btn btn-primary mx-auto"
  244. onClick={updateCommandsHandler}
  245. >
  246. { t('Update') }
  247. </button>
  248. </div>
  249. </div>
  250. );
  251. };
  252. ManageCommandsProcess.propTypes = {
  253. apiv3Put: PropTypes.func,
  254. slackAppIntegrationId: PropTypes.string.isRequired,
  255. permissionsForBroadcastUseCommands: PropTypes.object.isRequired,
  256. permissionsForSingleUseCommands: PropTypes.object.isRequired,
  257. };
  258. export default ManageCommandsProcess;