diff options
author | Stas Vilchik <stas-vilchik@users.noreply.github.com> | 2017-05-30 16:40:23 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-05-30 16:40:23 +0200 |
commit | 862b59c20a2012f84acc4abf7531cb2fa2b1f3ec (patch) | |
tree | 880652733976f7dbaf074d70d97f1a373e2805d2 /server/sonar-web/src/main/js/apps/quality-profiles/home | |
parent | 88b3c97acd9154a1dc16bd1e158a02f2020ca6c8 (diff) | |
download | sonarqube-862b59c20a2012f84acc4abf7531cb2fa2b1f3ec.tar.gz sonarqube-862b59c20a2012f84acc4abf7531cb2fa2b1f3ec.zip |
rework quality profiles modals (#2123)
Diffstat (limited to 'server/sonar-web/src/main/js/apps/quality-profiles/home')
6 files changed, 411 insertions, 46 deletions
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.js new file mode 100644 index 00000000000..4219dbd37f6 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.js @@ -0,0 +1,197 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +// @flow +import React from 'react'; +import Modal from 'react-modal'; +import Select from 'react-select'; +import { sortBy } from 'lodash'; +import { getImporters, createQualityProfile } from '../../../api/quality-profiles'; +import { translate } from '../../../helpers/l10n'; + +type Props = { + languages: Array<{ key: string, name: string }>, + onClose: () => void, + onCreate: Function, + onRequestFail: Object => void, + organization: ?string +}; + +type State = { + importers: Array<{ key: string, languages: Array<string>, name: string }>, + language?: string, + loading: boolean, + name: string, + preloading: boolean +}; + +export default class CreateProfileForm extends React.PureComponent { + form: HTMLFormElement; + mounted: boolean; + props: Props; + state: State = { importers: [], loading: false, name: '', preloading: true }; + + componentDidMount() { + this.mounted = true; + this.fetchImporters(); + } + + componentWillUnmount() { + this.mounted = false; + } + + fetchImporters() { + getImporters().then(importers => { + if (this.mounted) { + this.setState({ importers, preloading: false }); + } + }); + } + + handleCancelClick = (event: Event) => { + event.preventDefault(); + this.props.onClose(); + }; + + handleNameChange = (event: { currentTarget: HTMLInputElement }) => { + this.setState({ name: event.currentTarget.value }); + }; + + handleLanguageChange = (option: { value: string }) => { + this.setState({ language: option.value }); + }; + + handleFormSubmit = (event: Event) => { + event.preventDefault(); + + this.setState({ loading: true }); + + const data = new FormData(this.form); + if (this.props.organization) { + data.append('organization', this.props.organization); + } + + createQualityProfile(data).then( + response => this.props.onCreate(response.profile), + error => { + if (this.mounted) { + this.setState({ loading: false }); + } + this.props.onRequestFail(error); + } + ); + }; + + render() { + const header = translate('quality_profiles.new_profile'); + + const languages = sortBy(this.props.languages, 'name'); + const selectedLanguage = this.state.language || languages[0].key; + const importers = this.state.importers.filter(importer => + importer.languages.includes(selectedLanguage) + ); + + return ( + <Modal + isOpen={true} + contentLabel={header} + className="modal" + overlayClassName="modal-overlay" + onRequestClose={this.props.onClose}> + + <form + id="create-profile-form" + onSubmit={this.handleFormSubmit} + ref={node => (this.form = node)}> + + <div className="modal-head"> + <h2>{header}</h2> + </div> + + {this.state.preloading + ? <div className="modal-body"><i className="spinner" /></div> + : <div className="modal-body"> + <div className="modal-field"> + <label htmlFor="create-profile-name"> + {translate('name')}<em className="mandatory">*</em> + </label> + <input + autoFocus={true} + id="create-profile-name" + maxLength="100" + name="name" + onChange={this.handleNameChange} + required={true} + size="50" + type="text" + value={this.state.name} + /> + </div> + <div className="modal-field spacer-bottom"> + <label htmlFor="create-profile-language"> + {translate('language')}<em className="mandatory">*</em> + </label> + <Select + clearable={false} + id="create-profile-language" + name="language" + onChange={this.handleLanguageChange} + options={languages.map(language => ({ + label: language.name, + value: language.key + }))} + value={selectedLanguage} + /> + </div> + {importers.map(importer => ( + <div + className="modal-field spacer-bottom js-importer" + data-key={importer.key} + key={importer.key}> + <label htmlFor={'create-profile-form-backup-' + importer.key}> + {importer.name} + </label> + <input + id={'create-profile-form-backup-' + importer.key} + name={'backup_' + importer.key} + type="file" + /> + <p className="note"> + {translate('quality_profiles.optional_configuration_file')} + </p> + </div> + ))} + </div>} + + <div className="modal-foot"> + {this.state.loading && <i className="spinner spacer-right" />} + {!this.state.preloading && + <button disabled={this.state.loading} id="create-profile-submit"> + {translate('create')} + </button>} + <a href="#" id="create-profile-cancel" onClick={this.handleCancelClick}> + {translate('cancel')} + </a> + </div> + </form> + + </Modal> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.js index df44389a9c6..70389154ed4 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.js @@ -28,6 +28,7 @@ type Props = { canAdmin: boolean, languages: Array<{ key: string, name: string }>, location: { query: { [string]: string } }, + onRequestFail: Object => void, organization?: string, profiles: Array<Profile>, updateProfiles: () => Promise<*> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.js index df6191a8300..6a276e243ee 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.js @@ -19,76 +19,63 @@ */ // @flow import React from 'react'; -import CreateProfileView from '../views/CreateProfileView'; -import RestoreProfileView from '../views/RestoreProfileView'; +import CreateProfileForm from './CreateProfileForm'; +import RestoreProfileForm from './RestoreProfileForm'; import RestoreBuiltInProfilesView from '../views/RestoreBuiltInProfilesView'; +import type { Profile } from '../propTypes'; import { getProfilePath } from '../utils'; import { translate } from '../../../helpers/l10n'; -import { getImporters } from '../../../api/quality-profiles'; type Props = { canAdmin: boolean, languages: Array<{ key: string, name: string }>, + onRequestFail: Object => void, organization: ?string, updateProfiles: () => Promise<*> }; +type State = { + createFormOpen: boolean, + restoreFormOpen: boolean +}; + export default class PageHeader extends React.PureComponent { - mounted: boolean; props: Props; static contextTypes = { router: React.PropTypes.object }; - state = {}; + state: State = { + createFormOpen: false, + restoreFormOpen: false + }; - componentDidMount() { - this.mounted = true; - } + handleCreateClick = (event: Event & { currentTarget: HTMLButtonElement }) => { + event.preventDefault(); + event.currentTarget.blur(); + this.setState({ createFormOpen: true }); + }; - componentWillUnmount() { - this.mounted = false; - } + handleCreate = (profile: Profile) => { + this.props.updateProfiles().then(() => { + this.context.router.push( + getProfilePath(profile.name, profile.language, this.props.organization) + ); + }); + }; - retrieveImporters() { - if (this.state.importers) { - return Promise.resolve(this.state.importers); - } else { - return getImporters().then(importers => { - this.setState({ importers }); - return importers; - }); - } - } + closeCreateForm = () => { + this.setState({ createFormOpen: false }); + }; - handleCreateClick = (e: SyntheticInputEvent) => { - e.preventDefault(); - e.target.blur(); - this.retrieveImporters().then(importers => { - new CreateProfileView({ - languages: this.props.languages, - organization: this.props.organization, - importers - }) - .on('done', profile => { - this.props.updateProfiles().then(() => { - this.context.router.push( - getProfilePath(profile.name, profile.language, this.props.organization) - ); - }); - }) - .render(); - }); + handleRestoreClick = (event: Event) => { + event.preventDefault(); + this.setState({ restoreFormOpen: true }); }; - handleRestoreClick = (e: SyntheticInputEvent) => { - e.preventDefault(); - new RestoreProfileView({ - organization: this.props.organization - }) - .on('done', this.props.updateProfiles) - .render(); + closeRestoreForm = () => { + this.setState({ restoreFormOpen: false }); }; handleRestoreBuiltIn = (e: SyntheticInputEvent) => { @@ -139,6 +126,23 @@ export default class PageHeader extends React.PureComponent { <br /> {translate('quality_profiles.intro2')} </div> + + {this.state.restoreFormOpen && + <RestoreProfileForm + onClose={this.closeRestoreForm} + onRequestFail={this.props.onRequestFail} + onRestore={this.props.updateProfiles} + organization={this.props.organization} + />} + + {this.state.createFormOpen && + <CreateProfileForm + languages={this.props.languages} + onClose={this.closeCreateForm} + onRequestFail={this.props.onRequestFail} + onCreate={this.handleCreate} + organization={this.props.organization} + />} </header> ); } diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.js index c2b0681f686..dbcf7840677 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.js @@ -30,6 +30,7 @@ type Props = { canAdmin: boolean, languages: Array<{ key: string, name: string }>, location: { query: { [string]: string } }, + onRequestFail: Object => void, organization: ?string, profiles: Array<Profile>, updateProfiles: () => Promise<*> @@ -43,6 +44,7 @@ export default class ProfilesList extends React.PureComponent { <ProfilesListRow canAdmin={this.props.canAdmin} key={profile.key} + onRequestFail={this.props.onRequestFail} organization={this.props.organization} profile={profile} updateProfiles={this.props.updateProfiles} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.js index 85dcb48c1d3..ec8bcf086e4 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.js +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.js @@ -30,6 +30,7 @@ import type { Profile } from '../propTypes'; type Props = { canAdmin: boolean, + onRequestFail: Object => void, organization: ?string, profile: Profile, updateProfiles: () => Promise<*> @@ -160,6 +161,7 @@ export default class ProfilesListRow extends React.PureComponent { <ProfileActions canAdmin={this.props.canAdmin} fromList={true} + onRequestFail={this.props.onRequestFail} organization={this.props.organization} profile={this.props.profile} updateProfiles={this.props.updateProfiles} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/RestoreProfileForm.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/RestoreProfileForm.js new file mode 100644 index 00000000000..911482ee914 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/RestoreProfileForm.js @@ -0,0 +1,159 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +// @flow +import React from 'react'; +import Modal from 'react-modal'; +import { restoreQualityProfile } from '../../../api/quality-profiles'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; + +type Props = { + onClose: () => void, + onRequestFail: Object => void, + onRestore: Function, + organization: ?string +}; + +type State = { + loading: boolean, + profile?: { name: string }, + ruleFailures?: number, + ruleSuccesses?: number +}; + +export default class RestoreProfileForm extends React.PureComponent { + form: HTMLFormElement; + mounted: boolean; + props: Props; + state: State = { loading: false }; + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + handleCancelClick = (event: Event) => { + event.preventDefault(); + this.props.onClose(); + }; + + handleFormSubmit = (event: Event) => { + event.preventDefault(); + + this.setState({ loading: true }); + + const data = new FormData(this.form); + if (this.props.organization) { + data.append('organization', this.props.organization); + } + + restoreQualityProfile(data).then( + response => { + if (this.mounted) { + this.setState({ + loading: false, + profile: response.profile, + ruleFailures: response.ruleFailures, + ruleSuccesses: response.ruleSuccesses + }); + } + this.props.onRestore(); + }, + error => { + if (this.mounted) { + this.setState({ loading: false }); + } + this.props.onRequestFail(error); + } + ); + }; + + render() { + const header = translate('quality_profiles.restore_profile'); + + const { loading, profile, ruleFailures, ruleSuccesses } = this.state; + + return ( + <Modal + isOpen={true} + contentLabel={header} + className="modal" + overlayClassName="modal-overlay" + onRequestClose={this.props.onClose}> + + <form + id="restore-profile-form" + onSubmit={this.handleFormSubmit} + ref={node => (this.form = node)}> + + <div className="modal-head"> + <h2>{header}</h2> + </div> + + <div className="modal-body"> + {profile != null && ruleSuccesses != null + ? ruleFailures + ? <div className="alert alert-warning"> + {translateWithParameters( + 'quality_profiles.restore_profile.warning', + profile.name, + ruleSuccesses, + ruleFailures + )} + </div> + : <div className="alert alert-success"> + {translateWithParameters( + 'quality_profiles.restore_profile.success', + profile.name, + ruleSuccesses + )} + </div> + : <div className="modal-field"> + <label htmlFor="restore-profile-backup"> + {translate('backup')}<em className="mandatory">*</em> + </label> + <input id="restore-profile-backup" name="backup" required={true} type="file" /> + </div>} + </div> + + {ruleSuccesses == null + ? <div className="modal-foot"> + {loading && <i className="spinner spacer-right" />} + <button disabled={loading} id="restore-profile-submit"> + {translate('restore')} + </button> + <a href="#" id="restore-profile-cancel" onClick={this.handleCancelClick}> + {translate('cancel')} + </a> + </div> + : <div className="modal-foot"> + <a href="#" onClick={this.handleCancelClick}> + {translate('close')} + </a> + </div>} + + </form> + + </Modal> + ); + } +} |