aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps/quality-profiles/home
diff options
context:
space:
mode:
authorStas Vilchik <stas-vilchik@users.noreply.github.com>2017-05-30 16:40:23 +0200
committerGitHub <noreply@github.com>2017-05-30 16:40:23 +0200
commit862b59c20a2012f84acc4abf7531cb2fa2b1f3ec (patch)
tree880652733976f7dbaf074d70d97f1a373e2805d2 /server/sonar-web/src/main/js/apps/quality-profiles/home
parent88b3c97acd9154a1dc16bd1e158a02f2020ca6c8 (diff)
downloadsonarqube-862b59c20a2012f84acc4abf7531cb2fa2b1f3ec.tar.gz
sonarqube-862b59c20a2012f84acc4abf7531cb2fa2b1f3ec.zip
rework quality profiles modals (#2123)
Diffstat (limited to 'server/sonar-web/src/main/js/apps/quality-profiles/home')
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.js197
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.js1
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.js96
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.js2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.js2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/RestoreProfileForm.js159
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>
+ );
+ }
+}