Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

BulkChangeModal.tsx 7.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  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 { ResetButtonLink, SubmitButton } from 'sonar-ui-common/components/controls/buttons';
  22. import Modal from 'sonar-ui-common/components/controls/Modal';
  23. import Select from 'sonar-ui-common/components/controls/Select';
  24. import { Alert } from 'sonar-ui-common/components/ui/Alert';
  25. import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
  26. import { formatMeasure } from 'sonar-ui-common/helpers/measures';
  27. import { bulkActivateRules, bulkDeactivateRules, Profile } from '../../../api/quality-profiles';
  28. import { Query, serializeQuery } from '../query';
  29. interface Props {
  30. action: string;
  31. languages: T.Languages;
  32. onClose: () => void;
  33. organization: string | undefined;
  34. profile?: Profile;
  35. query: Query;
  36. referencedProfiles: T.Dict<Profile>;
  37. total: number;
  38. }
  39. interface ActivationResult {
  40. failed: number;
  41. profile: string;
  42. succeeded: number;
  43. }
  44. interface State {
  45. finished: boolean;
  46. results: ActivationResult[];
  47. selectedProfiles: any[];
  48. submitting: boolean;
  49. }
  50. export default class BulkChangeModal extends React.PureComponent<Props, State> {
  51. mounted = false;
  52. constructor(props: Props) {
  53. super(props);
  54. // if there is only one possible option for profile, select it immediately
  55. const selectedProfiles = [];
  56. const availableProfiles = this.getAvailableQualityProfiles(props);
  57. if (availableProfiles.length === 1) {
  58. selectedProfiles.push(availableProfiles[0].key);
  59. }
  60. this.state = { finished: false, results: [], selectedProfiles, submitting: false };
  61. }
  62. componentDidMount() {
  63. this.mounted = true;
  64. }
  65. componentWillUnmount() {
  66. this.mounted = false;
  67. }
  68. handleProfileSelect = (options: { value: string }[]) => {
  69. const selectedProfiles = options.map(option => option.value);
  70. this.setState({ selectedProfiles });
  71. };
  72. getAvailableQualityProfiles = ({ query, referencedProfiles } = this.props) => {
  73. let profiles = Object.values(referencedProfiles);
  74. if (query.languages.length > 0) {
  75. profiles = profiles.filter(profile => query.languages.includes(profile.language));
  76. }
  77. return profiles
  78. .filter(profile => profile.actions && profile.actions.edit)
  79. .filter(profile => !profile.isBuiltIn);
  80. };
  81. processResponse = (profile: string, response: any) => {
  82. if (this.mounted) {
  83. const result: ActivationResult = {
  84. failed: response.failed || 0,
  85. profile,
  86. succeeded: response.succeeded || 0
  87. };
  88. this.setState(state => ({ results: [...state.results, result] }));
  89. }
  90. };
  91. sendRequests = () => {
  92. let looper = Promise.resolve();
  93. // serialize the query, but delete the `profile`
  94. const data = serializeQuery(this.props.query);
  95. delete data.profile;
  96. const method = this.props.action === 'activate' ? bulkActivateRules : bulkDeactivateRules;
  97. // if a profile is selected in the facet, pick it
  98. // otherwise take all profiles selected in the dropdown
  99. const profiles: string[] = this.props.profile
  100. ? [this.props.profile.key]
  101. : this.state.selectedProfiles;
  102. for (const profile of profiles) {
  103. looper = looper.then(() =>
  104. method({
  105. ...data,
  106. organization: this.props.organization,
  107. targetKey: profile
  108. }).then(response => this.processResponse(profile, response))
  109. );
  110. }
  111. return looper;
  112. };
  113. handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
  114. event.preventDefault();
  115. this.setState({ submitting: true });
  116. this.sendRequests().then(
  117. () => {
  118. if (this.mounted) {
  119. this.setState({ finished: true, submitting: false });
  120. }
  121. },
  122. () => {
  123. if (this.mounted) {
  124. this.setState({ submitting: false });
  125. }
  126. }
  127. );
  128. };
  129. renderResult = (result: ActivationResult) => {
  130. const { profile: profileKey } = result;
  131. const profile = this.props.referencedProfiles[profileKey];
  132. if (!profile) {
  133. return null;
  134. }
  135. const { languages } = this.props;
  136. const language = languages[profile.language]
  137. ? languages[profile.language].name
  138. : profile.language;
  139. return (
  140. <Alert key={result.profile} variant={result.failed === 0 ? 'success' : 'warning'}>
  141. {result.failed
  142. ? translateWithParameters(
  143. 'coding_rules.bulk_change.warning',
  144. profile.name,
  145. language,
  146. result.succeeded,
  147. result.failed
  148. )
  149. : translateWithParameters(
  150. 'coding_rules.bulk_change.success',
  151. profile.name,
  152. language,
  153. result.succeeded
  154. )}
  155. </Alert>
  156. );
  157. };
  158. renderProfileSelect = () => {
  159. const profiles = this.getAvailableQualityProfiles();
  160. const options = profiles.map(profile => ({
  161. label: `${profile.name} - ${profile.languageName}`,
  162. value: profile.key
  163. }));
  164. return (
  165. <Select
  166. multi={true}
  167. onChange={this.handleProfileSelect}
  168. options={options}
  169. value={this.state.selectedProfiles}
  170. />
  171. );
  172. };
  173. render() {
  174. const { action, profile, total } = this.props;
  175. const header =
  176. action === 'activate'
  177. ? `${translate('coding_rules.activate_in_quality_profile')} (${formatMeasure(
  178. total,
  179. 'INT'
  180. )} ${translate('coding_rules._rules')})`
  181. : `${translate('coding_rules.deactivate_in_quality_profile')} (${formatMeasure(
  182. total,
  183. 'INT'
  184. )} ${translate('coding_rules._rules')})`;
  185. return (
  186. <Modal contentLabel={header} onRequestClose={this.props.onClose} size="small">
  187. <form onSubmit={this.handleFormSubmit}>
  188. <header className="modal-head">
  189. <h2>{header}</h2>
  190. </header>
  191. <div className="modal-body">
  192. {this.state.results.map(this.renderResult)}
  193. {!this.state.finished && !this.state.submitting && (
  194. <div className="modal-field">
  195. <h3>
  196. <label htmlFor="coding-rules-bulk-change-profile">
  197. {action === 'activate'
  198. ? translate('coding_rules.activate_in')
  199. : translate('coding_rules.deactivate_in')}
  200. </label>
  201. </h3>
  202. {profile ? (
  203. <span>
  204. {profile.name}
  205. {' — '}
  206. {translate('are_you_sure')}
  207. </span>
  208. ) : (
  209. this.renderProfileSelect()
  210. )}
  211. </div>
  212. )}
  213. </div>
  214. <footer className="modal-foot">
  215. {this.state.submitting && <i className="spinner spacer-right" />}
  216. {!this.state.finished && (
  217. <SubmitButton disabled={this.state.submitting} id="coding-rules-submit-bulk-change">
  218. {translate('apply')}
  219. </SubmitButton>
  220. )}
  221. <ResetButtonLink onClick={this.props.onClose}>
  222. {this.state.finished ? translate('close') : translate('cancel')}
  223. </ResetButtonLink>
  224. </footer>
  225. </form>
  226. </Modal>
  227. );
  228. }
  229. }