You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ActivationFormModal.tsx 8.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2019 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import * as React from 'react';
  21. import { translate } from 'sonar-ui-common/helpers/l10n';
  22. import { SubmitButton, ResetButtonLink } from 'sonar-ui-common/components/controls/buttons';
  23. import Modal from 'sonar-ui-common/components/controls/Modal';
  24. import { Alert } from 'sonar-ui-common/components/ui/Alert';
  25. import Select from 'sonar-ui-common/components/controls/Select';
  26. import SeverityHelper from '../../../components/shared/SeverityHelper';
  27. import { activateRule, Profile } from '../../../api/quality-profiles';
  28. import { SEVERITIES } from '../../../helpers/constants';
  29. import { sortProfiles } from '../../quality-profiles/utils';
  30. interface Props {
  31. activation?: T.RuleActivation;
  32. modalHeader: string;
  33. onClose: () => void;
  34. onDone: (severity: string) => Promise<void>;
  35. organization: string | undefined;
  36. profiles: Profile[];
  37. rule: T.Rule | T.RuleDetails;
  38. }
  39. interface State {
  40. params: T.Dict<string>;
  41. profile: string;
  42. severity: string;
  43. submitting: boolean;
  44. }
  45. export default class ActivationFormModal extends React.PureComponent<Props, State> {
  46. mounted = false;
  47. constructor(props: Props) {
  48. super(props);
  49. const profilesWithDepth = this.getQualityProfilesWithDepth(props);
  50. this.state = {
  51. params: this.getParams(props),
  52. profile: profilesWithDepth.length > 0 ? profilesWithDepth[0].key : '',
  53. severity: props.activation ? props.activation.severity : props.rule.severity,
  54. submitting: false
  55. };
  56. }
  57. componentDidMount() {
  58. this.mounted = true;
  59. }
  60. componentWillUnmount() {
  61. this.mounted = false;
  62. }
  63. getParams = ({ activation, rule } = this.props) => {
  64. const params: T.Dict<string> = {};
  65. if (rule && rule.params) {
  66. for (const param of rule.params) {
  67. params[param.key] = param.defaultValue || '';
  68. }
  69. if (activation && activation.params) {
  70. for (const param of activation.params) {
  71. params[param.key] = param.value;
  72. }
  73. }
  74. }
  75. return params;
  76. };
  77. // Choose QP which a user can administrate, which are the same language and which are not built-in
  78. getQualityProfilesWithDepth = ({ profiles } = this.props) => {
  79. return sortProfiles(
  80. profiles.filter(
  81. profile =>
  82. !profile.isBuiltIn &&
  83. profile.actions &&
  84. profile.actions.edit &&
  85. profile.language === this.props.rule.lang
  86. )
  87. ).map(profile => ({
  88. ...profile,
  89. // Decrease depth by 1, so the top level starts at 0
  90. depth: profile.depth - 1
  91. }));
  92. };
  93. handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
  94. event.preventDefault();
  95. this.setState({ submitting: true });
  96. const data = {
  97. key: this.state.profile,
  98. organization: this.props.organization,
  99. params: this.state.params,
  100. rule: this.props.rule.key,
  101. severity: this.state.severity
  102. };
  103. activateRule(data)
  104. .then(() => this.props.onDone(data.severity))
  105. .then(
  106. () => {
  107. if (this.mounted) {
  108. this.setState({ submitting: false });
  109. this.props.onClose();
  110. }
  111. },
  112. () => {
  113. if (this.mounted) {
  114. this.setState({ submitting: false });
  115. }
  116. }
  117. );
  118. };
  119. handleParameterChange = (event: React.SyntheticEvent<HTMLInputElement | HTMLTextAreaElement>) => {
  120. const { name, value } = event.currentTarget;
  121. this.setState((state: State) => ({ params: { ...state.params, [name]: value } }));
  122. };
  123. handleProfileChange = ({ value }: { value: string }) => {
  124. this.setState({ profile: value });
  125. };
  126. handleSeverityChange = ({ value }: { value: string }) => {
  127. this.setState({ severity: value });
  128. };
  129. renderSeverityOption = ({ value }: { value: string }) => {
  130. return <SeverityHelper severity={value} />;
  131. };
  132. render() {
  133. const { activation, rule } = this.props;
  134. const { profile, severity, submitting } = this.state;
  135. const { params = [] } = rule;
  136. const profilesWithDepth = this.getQualityProfilesWithDepth();
  137. const isCustomRule = !!(rule as T.RuleDetails).templateKey;
  138. const activeInAllProfiles = profilesWithDepth.length <= 0;
  139. const isUpdateMode = !!activation;
  140. return (
  141. <Modal contentLabel={this.props.modalHeader} onRequestClose={this.props.onClose} size="small">
  142. <form onSubmit={this.handleFormSubmit}>
  143. <div className="modal-head">
  144. <h2>{this.props.modalHeader}</h2>
  145. </div>
  146. <div className="modal-body modal-container">
  147. {!isUpdateMode && activeInAllProfiles && (
  148. <Alert variant="info">{translate('coding_rules.active_in_all_profiles')}</Alert>
  149. )}
  150. <div className="modal-field">
  151. <label>{translate('coding_rules.quality_profile')}</label>
  152. <Select
  153. className="js-profile"
  154. clearable={false}
  155. disabled={submitting || profilesWithDepth.length === 1}
  156. onChange={this.handleProfileChange}
  157. options={profilesWithDepth.map(profile => ({
  158. label: ' '.repeat(profile.depth) + profile.name,
  159. value: profile.key
  160. }))}
  161. value={profile}
  162. />
  163. </div>
  164. <div className="modal-field">
  165. <label>{translate('severity')}</label>
  166. <Select
  167. className="js-severity"
  168. clearable={false}
  169. disabled={submitting}
  170. onChange={this.handleSeverityChange}
  171. optionRenderer={this.renderSeverityOption}
  172. options={SEVERITIES.map(severity => ({
  173. label: translate('severity', severity),
  174. value: severity
  175. }))}
  176. searchable={false}
  177. value={severity}
  178. valueRenderer={this.renderSeverityOption}
  179. />
  180. </div>
  181. {isCustomRule ? (
  182. <div className="modal-field">
  183. <p className="note">{translate('coding_rules.custom_rule.activation_notice')}</p>
  184. </div>
  185. ) : (
  186. params.map(param => (
  187. <div className="modal-field" key={param.key}>
  188. <label title={param.key}>{param.key}</label>
  189. {param.type === 'TEXT' ? (
  190. <textarea
  191. disabled={submitting}
  192. name={param.key}
  193. onChange={this.handleParameterChange}
  194. placeholder={param.defaultValue}
  195. rows={3}
  196. value={this.state.params[param.key] || ''}
  197. />
  198. ) : (
  199. <input
  200. disabled={submitting}
  201. name={param.key}
  202. onChange={this.handleParameterChange}
  203. placeholder={param.defaultValue}
  204. type="text"
  205. value={this.state.params[param.key] || ''}
  206. />
  207. )}
  208. <div
  209. className="note"
  210. // Safe: defined by rule creator (instance admin?)
  211. dangerouslySetInnerHTML={{ __html: param.htmlDesc || '' }}
  212. />
  213. </div>
  214. ))
  215. )}
  216. </div>
  217. <footer className="modal-foot">
  218. {submitting && <i className="spinner spacer-right" />}
  219. <SubmitButton disabled={submitting || activeInAllProfiles}>
  220. {isUpdateMode ? translate('save') : translate('coding_rules.activate')}
  221. </SubmitButton>
  222. <ResetButtonLink disabled={submitting} onClick={this.props.onClose}>
  223. {translate('cancel')}
  224. </ResetButtonLink>
  225. </footer>
  226. </form>
  227. </Modal>
  228. );
  229. }
  230. }