- Move all API interactions to the parent component - Merge copy, extend, and rename forms into a single component - Simplify delete formtags/8.8.0.42792
@@ -1,123 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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. | |||
*/ | |||
import * as React from 'react'; | |||
import { ResetButtonLink, SubmitButton } from 'sonar-ui-common/components/controls/buttons'; | |||
import Modal from 'sonar-ui-common/components/controls/Modal'; | |||
import MandatoryFieldMarker from 'sonar-ui-common/components/ui/MandatoryFieldMarker'; | |||
import MandatoryFieldsExplanation from 'sonar-ui-common/components/ui/MandatoryFieldsExplanation'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
import { copyProfile } from '../../../api/quality-profiles'; | |||
import { Profile } from '../types'; | |||
interface Props { | |||
onClose: () => void; | |||
onCopy: (name: string) => void; | |||
profile: Profile; | |||
} | |||
interface State { | |||
loading: boolean; | |||
name: string | null; | |||
} | |||
export default class CopyProfileForm extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
state: State = { loading: false, name: null }; | |||
componentDidMount() { | |||
this.mounted = true; | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => { | |||
this.setState({ name: event.currentTarget.value }); | |||
}; | |||
handleFormSubmit = (event: React.SyntheticEvent<HTMLElement>) => { | |||
event.preventDefault(); | |||
const { name } = this.state; | |||
if (name != null) { | |||
this.setState({ loading: true }); | |||
copyProfile(this.props.profile.key, name).then( | |||
(profile: any) => this.props.onCopy(profile.name), | |||
() => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
} | |||
); | |||
} | |||
}; | |||
render() { | |||
const { profile } = this.props; | |||
const header = translateWithParameters( | |||
'quality_profiles.copy_x_title', | |||
profile.name, | |||
profile.languageName | |||
); | |||
const submitDisabled = | |||
this.state.loading || !this.state.name || this.state.name === profile.name; | |||
return ( | |||
<Modal contentLabel={header} onRequestClose={this.props.onClose} size="small"> | |||
<form id="copy-profile-form" onSubmit={this.handleFormSubmit}> | |||
<div className="modal-head"> | |||
<h2>{header}</h2> | |||
</div> | |||
<div className="modal-body"> | |||
<MandatoryFieldsExplanation className="modal-field" /> | |||
<div className="modal-field"> | |||
<label htmlFor="copy-profile-name"> | |||
{translate('quality_profiles.copy_new_name')} | |||
<MandatoryFieldMarker /> | |||
</label> | |||
<input | |||
autoFocus={true} | |||
id="copy-profile-name" | |||
maxLength={100} | |||
name="name" | |||
onChange={this.handleNameChange} | |||
required={true} | |||
size={50} | |||
type="text" | |||
value={this.state.name != null ? this.state.name : profile.name} | |||
/> | |||
</div> | |||
</div> | |||
<div className="modal-foot"> | |||
{this.state.loading && <i className="spinner spacer-right" />} | |||
<SubmitButton disabled={submitDisabled} id="copy-profile-submit"> | |||
{translate('copy')} | |||
</SubmitButton> | |||
<ResetButtonLink id="copy-profile-cancel" onClick={this.props.onClose}> | |||
{translate('cancel')} | |||
</ResetButtonLink> | |||
</div> | |||
</form> | |||
</Modal> | |||
); | |||
} | |||
} |
@@ -22,90 +22,61 @@ import { ResetButtonLink, SubmitButton } from 'sonar-ui-common/components/contro | |||
import Modal from 'sonar-ui-common/components/controls/Modal'; | |||
import { Alert } from 'sonar-ui-common/components/ui/Alert'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
import { deleteProfile } from '../../../api/quality-profiles'; | |||
import { Profile } from '../types'; | |||
interface Props { | |||
export interface DeleteProfileFormProps { | |||
loading: boolean; | |||
onClose: () => void; | |||
onDelete: () => void; | |||
profile: Profile; | |||
} | |||
interface State { | |||
loading: boolean; | |||
} | |||
export default class DeleteProfileForm extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
state: State = { loading: false }; | |||
componentDidMount() { | |||
this.mounted = true; | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { | |||
event.preventDefault(); | |||
this.setState({ loading: true }); | |||
deleteProfile(this.props.profile).then(this.props.onDelete, () => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
}); | |||
}; | |||
render() { | |||
const { profile } = this.props; | |||
const header = translate('quality_profiles.delete_confirm_title'); | |||
export default function DeleteProfileForm(props: DeleteProfileFormProps) { | |||
const { loading, profile } = props; | |||
const header = translate('quality_profiles.delete_confirm_title'); | |||
return ( | |||
<Modal contentLabel={header} onRequestClose={this.props.onClose}> | |||
<form id="delete-profile-form" onSubmit={this.handleFormSubmit}> | |||
<div className="modal-head"> | |||
<h2>{header}</h2> | |||
</div> | |||
<div className="modal-body"> | |||
<div className="js-modal-messages" /> | |||
{profile.childrenCount > 0 ? ( | |||
<div> | |||
<Alert variant="warning"> | |||
{translate('quality_profiles.this_profile_has_descendants')} | |||
</Alert> | |||
<p> | |||
{translateWithParameters( | |||
'quality_profiles.are_you_sure_want_delete_profile_x_and_descendants', | |||
profile.name, | |||
profile.languageName | |||
)} | |||
</p> | |||
</div> | |||
) : ( | |||
return ( | |||
<Modal contentLabel={header} onRequestClose={props.onClose}> | |||
<form | |||
onSubmit={(e: React.SyntheticEvent<HTMLFormElement>) => { | |||
e.preventDefault(); | |||
props.onDelete(); | |||
}}> | |||
<div className="modal-head"> | |||
<h2>{header}</h2> | |||
</div> | |||
<div className="modal-body"> | |||
{profile.childrenCount > 0 ? ( | |||
<div> | |||
<Alert variant="warning"> | |||
{translate('quality_profiles.this_profile_has_descendants')} | |||
</Alert> | |||
<p> | |||
{translateWithParameters( | |||
'quality_profiles.are_you_sure_want_delete_profile_x', | |||
'quality_profiles.are_you_sure_want_delete_profile_x_and_descendants', | |||
profile.name, | |||
profile.languageName | |||
)} | |||
</p> | |||
)} | |||
</div> | |||
<div className="modal-foot"> | |||
{this.state.loading && <i className="spinner spacer-right" />} | |||
<SubmitButton | |||
className="button-red" | |||
disabled={this.state.loading} | |||
id="delete-profile-submit"> | |||
{translate('delete')} | |||
</SubmitButton> | |||
<ResetButtonLink id="delete-profile-cancel" onClick={this.props.onClose}> | |||
{translate('cancel')} | |||
</ResetButtonLink> | |||
</div> | |||
</form> | |||
</Modal> | |||
); | |||
} | |||
</div> | |||
) : ( | |||
<p> | |||
{translateWithParameters( | |||
'quality_profiles.are_you_sure_want_delete_profile_x', | |||
profile.name, | |||
profile.languageName | |||
)} | |||
</p> | |||
)} | |||
</div> | |||
<div className="modal-foot"> | |||
{loading && <i className="spinner spacer-right" />} | |||
<SubmitButton className="button-red" disabled={loading}> | |||
{translate('delete')} | |||
</SubmitButton> | |||
<ResetButtonLink onClick={props.onClose}>{translate('cancel')}</ResetButtonLink> | |||
</div> | |||
</form> | |||
</Modal> | |||
); | |||
} |
@@ -1,137 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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. | |||
*/ | |||
import * as React from 'react'; | |||
import { ResetButtonLink, SubmitButton } from 'sonar-ui-common/components/controls/buttons'; | |||
import Modal from 'sonar-ui-common/components/controls/Modal'; | |||
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; | |||
import MandatoryFieldMarker from 'sonar-ui-common/components/ui/MandatoryFieldMarker'; | |||
import MandatoryFieldsExplanation from 'sonar-ui-common/components/ui/MandatoryFieldsExplanation'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
import { changeProfileParent, createQualityProfile } from '../../../api/quality-profiles'; | |||
import { Profile } from '../types'; | |||
interface Props { | |||
onClose: () => void; | |||
onExtend: (name: string) => void; | |||
profile: Profile; | |||
} | |||
interface State { | |||
loading: boolean; | |||
name?: string; | |||
} | |||
type ValidState = State & Required<Pick<State, 'name'>>; | |||
export default class ExtendProfileForm extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
state: State = { loading: false }; | |||
componentDidMount() { | |||
this.mounted = true; | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
canSubmit = (state: State): state is ValidState => { | |||
return Boolean(state.name && state.name.length); | |||
}; | |||
handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => { | |||
this.setState({ name: event.currentTarget.value }); | |||
}; | |||
handleFormSubmit = async (event: React.SyntheticEvent<HTMLFormElement>) => { | |||
event.preventDefault(); | |||
if (this.canSubmit(this.state)) { | |||
const { profile: parentProfile } = this.props; | |||
const { name } = this.state; | |||
const data = new FormData(); | |||
data.append('language', parentProfile.language); | |||
data.append('name', name); | |||
this.setState({ loading: true }); | |||
try { | |||
const { profile: newProfile } = await createQualityProfile(data); | |||
await changeProfileParent(newProfile, parentProfile); | |||
this.props.onExtend(newProfile.name); | |||
} finally { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
} | |||
} | |||
}; | |||
render() { | |||
const { profile } = this.props; | |||
const header = translateWithParameters( | |||
'quality_profiles.extend_x_title', | |||
profile.name, | |||
profile.languageName | |||
); | |||
return ( | |||
<Modal contentLabel={header} onRequestClose={this.props.onClose} size="small"> | |||
<form onSubmit={this.handleFormSubmit}> | |||
<div className="modal-head"> | |||
<h2>{header}</h2> | |||
</div> | |||
<div className="modal-body"> | |||
<MandatoryFieldsExplanation className="modal-field" /> | |||
<div className="modal-field"> | |||
<label htmlFor="extend-profile-name"> | |||
{translate('quality_profiles.copy_new_name')} | |||
<MandatoryFieldMarker /> | |||
</label> | |||
<input | |||
autoFocus={true} | |||
id="extend-profile-name" | |||
maxLength={100} | |||
name="name" | |||
onChange={this.handleNameChange} | |||
required={true} | |||
size={50} | |||
type="text" | |||
value={this.state.name ? this.state.name : ''} | |||
/> | |||
</div> | |||
</div> | |||
<div className="modal-foot"> | |||
<DeferredSpinner className="spacer-right" loading={this.state.loading} /> | |||
<SubmitButton | |||
disabled={this.state.loading || !this.canSubmit(this.state)} | |||
id="extend-profile-submit"> | |||
{translate('extend')} | |||
</SubmitButton> | |||
<ResetButtonLink id="extend-profile-cancel" onClick={this.props.onClose}> | |||
{translate('cancel')} | |||
</ResetButtonLink> | |||
</div> | |||
</form> | |||
</Modal> | |||
); | |||
} | |||
} |
@@ -23,16 +23,22 @@ import ActionsDropdown, { | |||
ActionsDropdownItem | |||
} from 'sonar-ui-common/components/controls/ActionsDropdown'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { getQualityProfileBackupUrl, setDefaultProfile } from '../../../api/quality-profiles'; | |||
import { | |||
changeProfileParent, | |||
copyProfile, | |||
createQualityProfile, | |||
deleteProfile, | |||
getQualityProfileBackupUrl, | |||
renameProfile, | |||
setDefaultProfile | |||
} from '../../../api/quality-profiles'; | |||
import { Router, withRouter } from '../../../components/hoc/withRouter'; | |||
import { getBaseUrl } from '../../../helpers/system'; | |||
import { getRulesUrl } from '../../../helpers/urls'; | |||
import { Profile } from '../types'; | |||
import { Profile, ProfileActionModals } from '../types'; | |||
import { getProfileComparePath, getProfilePath, PROFILE_PATH } from '../utils'; | |||
import CopyProfileForm from './CopyProfileForm'; | |||
import DeleteProfileForm from './DeleteProfileForm'; | |||
import ExtendProfileForm from './ExtendProfileForm'; | |||
import RenameProfileForm from './RenameProfileForm'; | |||
import ProfileModalForm from './ProfileModalForm'; | |||
interface Props { | |||
className?: string; | |||
@@ -43,94 +49,129 @@ interface Props { | |||
} | |||
interface State { | |||
copyFormOpen: boolean; | |||
extendFormOpen: boolean; | |||
deleteFormOpen: boolean; | |||
renameFormOpen: boolean; | |||
loading: boolean; | |||
openModal?: ProfileActionModals; | |||
} | |||
export class ProfileActions extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
state: State = { | |||
copyFormOpen: false, | |||
extendFormOpen: false, | |||
deleteFormOpen: false, | |||
renameFormOpen: false | |||
loading: false | |||
}; | |||
closeCopyForm = () => { | |||
this.setState({ copyFormOpen: false }); | |||
}; | |||
componentDidMount() { | |||
this.mounted = true; | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
closeDeleteForm = () => { | |||
this.setState({ deleteFormOpen: false }); | |||
handleCloseModal = () => { | |||
this.setState({ openModal: undefined }); | |||
}; | |||
closeExtendForm = () => { | |||
this.setState({ extendFormOpen: false }); | |||
handleCopyClick = () => { | |||
this.setState({ openModal: ProfileActionModals.Copy }); | |||
}; | |||
closeRenameForm = () => { | |||
this.setState({ renameFormOpen: false }); | |||
handleExtendClick = () => { | |||
this.setState({ openModal: ProfileActionModals.Extend }); | |||
}; | |||
handleCopyClick = () => { | |||
this.setState({ copyFormOpen: true }); | |||
handleRenameClick = () => { | |||
this.setState({ openModal: ProfileActionModals.Rename }); | |||
}; | |||
handleDeleteClick = () => { | |||
this.setState({ deleteFormOpen: true }); | |||
this.setState({ openModal: ProfileActionModals.Delete }); | |||
}; | |||
handleExtendClick = () => { | |||
this.setState({ extendFormOpen: true }); | |||
}; | |||
handleProfileCopy = async (name: string) => { | |||
this.setState({ loading: true }); | |||
handleRenameClick = () => { | |||
this.setState({ renameFormOpen: true }); | |||
try { | |||
await copyProfile(this.props.profile.key, name); | |||
this.profileActionPerformed(name); | |||
} catch { | |||
this.profileActionError(); | |||
} | |||
}; | |||
handleProfileCopy = (name: string) => { | |||
this.closeCopyForm(); | |||
this.navigateToNewProfile(name); | |||
}; | |||
handleProfileExtend = async (name: string) => { | |||
const { profile: parentProfile } = this.props; | |||
const data = { | |||
language: parentProfile.language, | |||
name | |||
}; | |||
this.setState({ loading: true }); | |||
handleProfileDelete = () => { | |||
this.props.router.replace(PROFILE_PATH); | |||
this.props.updateProfiles(); | |||
try { | |||
const { profile: newProfile } = await createQualityProfile(data); | |||
await changeProfileParent(newProfile, parentProfile); | |||
this.profileActionPerformed(name); | |||
} catch { | |||
this.profileActionError(); | |||
} | |||
}; | |||
handleProfileExtend = (name: string) => { | |||
this.closeExtendForm(); | |||
this.navigateToNewProfile(name); | |||
handleProfileRename = async (name: string) => { | |||
this.setState({ loading: true }); | |||
try { | |||
await renameProfile(this.props.profile.key, name); | |||
this.profileActionPerformed(name); | |||
} catch { | |||
this.profileActionError(); | |||
} | |||
}; | |||
handleProfileRename = (name: string) => { | |||
this.closeRenameForm(); | |||
this.props.updateProfiles().then( | |||
() => { | |||
if (!this.props.fromList) { | |||
this.props.router.replace(getProfilePath(name, this.props.profile.language)); | |||
} | |||
}, | |||
() => {} | |||
); | |||
handleProfileDelete = async () => { | |||
this.setState({ loading: true }); | |||
try { | |||
await deleteProfile(this.props.profile); | |||
if (this.mounted) { | |||
this.setState({ loading: false, openModal: undefined }); | |||
this.props.router.replace(PROFILE_PATH); | |||
this.props.updateProfiles(); | |||
} | |||
} catch { | |||
this.profileActionError(); | |||
} | |||
}; | |||
handleSetDefaultClick = () => { | |||
setDefaultProfile(this.props.profile).then(this.props.updateProfiles, () => {}); | |||
}; | |||
navigateToNewProfile = (name: string) => { | |||
this.props.updateProfiles().then( | |||
() => { | |||
this.props.router.push(getProfilePath(name, this.props.profile.language)); | |||
}, | |||
() => {} | |||
); | |||
profileActionPerformed = (name: string) => { | |||
const { profile, router } = this.props; | |||
if (this.mounted) { | |||
this.setState({ loading: false, openModal: undefined }); | |||
this.props.updateProfiles().then( | |||
() => { | |||
router.push(getProfilePath(name, profile.language)); | |||
}, | |||
() => { | |||
/* noop */ | |||
} | |||
); | |||
} | |||
}; | |||
profileActionError = () => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
}; | |||
render() { | |||
const { profile } = this.props; | |||
const { loading, openModal } = this.state; | |||
const { actions = {} } = profile; | |||
const backupUrl = `${getBaseUrl()}${getQualityProfileBackupUrl(profile)}`; | |||
@@ -144,86 +185,110 @@ export class ProfileActions extends React.PureComponent<Props, State> { | |||
<> | |||
<ActionsDropdown className={this.props.className}> | |||
{actions.edit && ( | |||
<ActionsDropdownItem to={activateMoreUrl}> | |||
<span data-test="quality-profiles__activate-more-rules"> | |||
{translate('quality_profiles.activate_more_rules')} | |||
</span> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__activate-more-rules" | |||
to={activateMoreUrl}> | |||
{translate('quality_profiles.activate_more_rules')} | |||
</ActionsDropdownItem> | |||
)} | |||
{!profile.isBuiltIn && ( | |||
<ActionsDropdownItem download={`${profile.key}.xml`} to={backupUrl}> | |||
<span data-test="quality-profiles__backup">{translate('backup_verb')}</span> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__backup" | |||
download={`${profile.key}.xml`} | |||
to={backupUrl}> | |||
{translate('backup_verb')} | |||
</ActionsDropdownItem> | |||
)} | |||
<ActionsDropdownItem to={getProfileComparePath(profile.name, profile.language)}> | |||
<span data-test="quality-profiles__compare">{translate('compare')}</span> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__compare" | |||
to={getProfileComparePath(profile.name, profile.language)}> | |||
{translate('compare')} | |||
</ActionsDropdownItem> | |||
{actions.copy && ( | |||
<> | |||
<ActionsDropdownItem onClick={this.handleCopyClick}> | |||
<span data-test="quality-profiles__copy">{translate('copy')}</span> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__copy" | |||
onClick={this.handleCopyClick}> | |||
{translate('copy')} | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem onClick={this.handleExtendClick}> | |||
<span data-test="quality-profiles__extend">{translate('extend')}</span> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__extend" | |||
onClick={this.handleExtendClick}> | |||
{translate('extend')} | |||
</ActionsDropdownItem> | |||
</> | |||
)} | |||
{actions.edit && ( | |||
<ActionsDropdownItem onClick={this.handleRenameClick}> | |||
<span data-test="quality-profiles__rename">{translate('rename')}</span> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__rename" | |||
onClick={this.handleRenameClick}> | |||
{translate('rename')} | |||
</ActionsDropdownItem> | |||
)} | |||
{actions.setAsDefault && ( | |||
<ActionsDropdownItem onClick={this.handleSetDefaultClick}> | |||
<span data-test="quality-profiles__set-as-default"> | |||
{translate('set_as_default')} | |||
</span> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__set-as-default" | |||
onClick={this.handleSetDefaultClick}> | |||
{translate('set_as_default')} | |||
</ActionsDropdownItem> | |||
)} | |||
{actions.delete && <ActionsDropdownDivider />} | |||
{actions.delete && ( | |||
<ActionsDropdownItem destructive={true} onClick={this.handleDeleteClick}> | |||
<span data-test="quality-profiles__delete">{translate('delete')}</span> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__delete" | |||
destructive={true} | |||
onClick={this.handleDeleteClick}> | |||
{translate('delete')} | |||
</ActionsDropdownItem> | |||
)} | |||
</ActionsDropdown> | |||
{this.state.copyFormOpen && ( | |||
<CopyProfileForm | |||
onClose={this.closeCopyForm} | |||
onCopy={this.handleProfileCopy} | |||
{openModal === ProfileActionModals.Copy && ( | |||
<ProfileModalForm | |||
btnLabelKey="copy" | |||
headerKey="quality_profiles.copy_x_title" | |||
loading={loading} | |||
onClose={this.handleCloseModal} | |||
onSubmit={this.handleProfileCopy} | |||
profile={profile} | |||
/> | |||
)} | |||
{this.state.extendFormOpen && ( | |||
<ExtendProfileForm | |||
onClose={this.closeExtendForm} | |||
onExtend={this.handleProfileExtend} | |||
{openModal === ProfileActionModals.Extend && ( | |||
<ProfileModalForm | |||
btnLabelKey="extend" | |||
headerKey="quality_profiles.extend_x_title" | |||
loading={loading} | |||
onClose={this.handleCloseModal} | |||
onSubmit={this.handleProfileExtend} | |||
profile={profile} | |||
/> | |||
)} | |||
{this.state.deleteFormOpen && ( | |||
<DeleteProfileForm | |||
onClose={this.closeDeleteForm} | |||
onDelete={this.handleProfileDelete} | |||
{openModal === ProfileActionModals.Rename && ( | |||
<ProfileModalForm | |||
btnLabelKey="rename" | |||
headerKey="quality_profiles.rename_x_title" | |||
loading={loading} | |||
onClose={this.handleCloseModal} | |||
onSubmit={this.handleProfileRename} | |||
profile={profile} | |||
/> | |||
)} | |||
{this.state.renameFormOpen && ( | |||
<RenameProfileForm | |||
onClose={this.closeRenameForm} | |||
onRename={this.handleProfileRename} | |||
{openModal === ProfileActionModals.Delete && ( | |||
<DeleteProfileForm | |||
loading={loading} | |||
onClose={this.handleCloseModal} | |||
onDelete={this.handleProfileDelete} | |||
profile={profile} | |||
/> | |||
)} |
@@ -0,0 +1,86 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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. | |||
*/ | |||
import * as React from 'react'; | |||
import { ResetButtonLink, SubmitButton } from 'sonar-ui-common/components/controls/buttons'; | |||
import Modal from 'sonar-ui-common/components/controls/Modal'; | |||
import MandatoryFieldMarker from 'sonar-ui-common/components/ui/MandatoryFieldMarker'; | |||
import MandatoryFieldsExplanation from 'sonar-ui-common/components/ui/MandatoryFieldsExplanation'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
import { Profile } from '../types'; | |||
export interface ProfileModalFormProps { | |||
btnLabelKey: string; | |||
headerKey: string; | |||
loading: boolean; | |||
onClose: () => void; | |||
onSubmit: (name: string) => void; | |||
profile: Profile; | |||
} | |||
export default function ProfileModalForm(props: ProfileModalFormProps) { | |||
const { btnLabelKey, headerKey, loading, profile } = props; | |||
const [name, setName] = React.useState<string | undefined>(undefined); | |||
const submitDisabled = loading || !name || name === profile.name; | |||
const header = translateWithParameters(headerKey, profile.name, profile.languageName); | |||
return ( | |||
<Modal contentLabel={header} onRequestClose={props.onClose} size="small"> | |||
<form | |||
onSubmit={(e: React.SyntheticEvent<HTMLFormElement>) => { | |||
e.preventDefault(); | |||
if (name) { | |||
props.onSubmit(name); | |||
} | |||
}}> | |||
<div className="modal-head"> | |||
<h2>{header}</h2> | |||
</div> | |||
<div className="modal-body"> | |||
<MandatoryFieldsExplanation className="modal-field" /> | |||
<div className="modal-field"> | |||
<label htmlFor="profile-name"> | |||
{translate('quality_profiles.new_name')} | |||
<MandatoryFieldMarker /> | |||
</label> | |||
<input | |||
autoFocus={true} | |||
id="profile-name" | |||
maxLength={100} | |||
name="name" | |||
onChange={(e: React.SyntheticEvent<HTMLInputElement>) => { | |||
setName(e.currentTarget.value); | |||
}} | |||
required={true} | |||
size={50} | |||
type="text" | |||
value={name ?? profile.name} | |||
/> | |||
</div> | |||
</div> | |||
<div className="modal-foot"> | |||
{loading && <i className="spinner spacer-right" />} | |||
<SubmitButton disabled={submitDisabled}>{translate(btnLabelKey)}</SubmitButton> | |||
<ResetButtonLink onClick={props.onClose}>{translate('cancel')}</ResetButtonLink> | |||
</div> | |||
</form> | |||
</Modal> | |||
); | |||
} |
@@ -1,123 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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. | |||
*/ | |||
import * as React from 'react'; | |||
import { ResetButtonLink, SubmitButton } from 'sonar-ui-common/components/controls/buttons'; | |||
import Modal from 'sonar-ui-common/components/controls/Modal'; | |||
import MandatoryFieldMarker from 'sonar-ui-common/components/ui/MandatoryFieldMarker'; | |||
import MandatoryFieldsExplanation from 'sonar-ui-common/components/ui/MandatoryFieldsExplanation'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
import { renameProfile } from '../../../api/quality-profiles'; | |||
import { Profile } from '../types'; | |||
interface Props { | |||
onClose: () => void; | |||
onRename: (name: string) => void; | |||
profile: Profile; | |||
} | |||
interface State { | |||
loading: boolean; | |||
name: string | null; | |||
} | |||
export default class RenameProfileForm extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
state: State = { loading: false, name: null }; | |||
componentDidMount() { | |||
this.mounted = true; | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => { | |||
this.setState({ name: event.currentTarget.value }); | |||
}; | |||
handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { | |||
event.preventDefault(); | |||
const { name } = this.state; | |||
if (name != null) { | |||
this.setState({ loading: true }); | |||
renameProfile(this.props.profile.key, name).then( | |||
() => this.props.onRename(name), | |||
() => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
} | |||
); | |||
} | |||
}; | |||
render() { | |||
const { profile } = this.props; | |||
const header = translateWithParameters( | |||
'quality_profiles.rename_x_title', | |||
profile.name, | |||
profile.languageName | |||
); | |||
const submitDisabled = | |||
this.state.loading || !this.state.name || this.state.name === profile.name; | |||
return ( | |||
<Modal contentLabel={header} onRequestClose={this.props.onClose} size="small"> | |||
<form id="rename-profile-form" onSubmit={this.handleFormSubmit}> | |||
<div className="modal-head"> | |||
<h2>{header}</h2> | |||
</div> | |||
<div className="modal-body"> | |||
<MandatoryFieldsExplanation className="modal-field" /> | |||
<div className="modal-field"> | |||
<label htmlFor="rename-profile-name"> | |||
{translate('quality_profiles.new_name')} | |||
<MandatoryFieldMarker /> | |||
</label> | |||
<input | |||
autoFocus={true} | |||
id="rename-profile-name" | |||
maxLength={100} | |||
name="name" | |||
onChange={this.handleNameChange} | |||
required={true} | |||
size={50} | |||
type="text" | |||
value={this.state.name != null ? this.state.name : profile.name} | |||
/> | |||
</div> | |||
</div> | |||
<div className="modal-foot"> | |||
{this.state.loading && <i className="spinner spacer-right" />} | |||
<SubmitButton disabled={submitDisabled} id="rename-profile-submit"> | |||
{translate('rename')} | |||
</SubmitButton> | |||
<ResetButtonLink id="rename-profile-cancel" onClick={this.props.onClose}> | |||
{translate('cancel')} | |||
</ResetButtonLink> | |||
</div> | |||
</form> | |||
</Modal> | |||
); | |||
} | |||
} |
@@ -17,35 +17,35 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; | |||
import { deleteProfile } from '../../../../api/quality-profiles'; | |||
import { mockEvent, mockQualityProfile } from '../../../../helpers/testMocks'; | |||
import DeleteProfileForm from '../DeleteProfileForm'; | |||
beforeEach(() => jest.clearAllMocks()); | |||
jest.mock('../../../../api/quality-profiles', () => ({ | |||
deleteProfile: jest.fn().mockResolvedValue({}) | |||
})); | |||
import DeleteProfileForm, { DeleteProfileFormProps } from '../DeleteProfileForm'; | |||
it('should render correctly', () => { | |||
const wrapper = shallowRender(); | |||
expect(wrapper).toMatchSnapshot(); | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ loading: true })).toMatchSnapshot('loading'); | |||
expect(shallowRender({ profile: mockQualityProfile({ childrenCount: 2 }) })).toMatchSnapshot( | |||
'profile has children' | |||
); | |||
}); | |||
it('should handle form submit correctly', async () => { | |||
const wrapper = shallowRender(); | |||
wrapper.instance().handleFormSubmit(mockEvent()); | |||
await waitAndUpdate(wrapper); | |||
it('should correctly submit the form', () => { | |||
const onDelete = jest.fn(); | |||
const wrapper = shallowRender({ onDelete }); | |||
expect(deleteProfile).toHaveBeenCalled(); | |||
const formOnSubmit = wrapper.find('form').props().onSubmit; | |||
if (formOnSubmit) { | |||
formOnSubmit(mockEvent()); | |||
} | |||
expect(onDelete).toBeCalled(); | |||
}); | |||
function shallowRender(props: Partial<DeleteProfileForm['props']> = {}) { | |||
return shallow<DeleteProfileForm>( | |||
function shallowRender(props: Partial<DeleteProfileFormProps> = {}) { | |||
return shallow<DeleteProfileFormProps>( | |||
<DeleteProfileForm | |||
loading={false} | |||
onClose={jest.fn()} | |||
onDelete={jest.fn()} | |||
profile={mockQualityProfile()} |
@@ -1,64 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { mockEvent, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; | |||
import { changeProfileParent, createQualityProfile } from '../../../../api/quality-profiles'; | |||
import { mockQualityProfile } from '../../../../helpers/testMocks'; | |||
import ExtendProfileForm from '../ExtendProfileForm'; | |||
jest.mock('../../../../api/quality-profiles', () => ({ | |||
createQualityProfile: jest.fn().mockResolvedValue({ profile: { key: 'new-profile' } }), | |||
changeProfileParent: jest.fn().mockResolvedValue(true) | |||
})); | |||
it('should render correctly', () => { | |||
const wrapper = shallowRender(); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('should correctly create a new profile and extend the existing one', async () => { | |||
const profile = mockQualityProfile(); | |||
const name = 'New name'; | |||
const wrapper = shallowRender({ profile }); | |||
expect(wrapper.find('SubmitButton').props().disabled).toBe(true); | |||
wrapper.setState({ name }).update(); | |||
wrapper.instance().handleFormSubmit(mockEvent()); | |||
await waitAndUpdate(wrapper); | |||
const data = new FormData(); | |||
data.append('language', profile.language); | |||
data.append('name', name); | |||
expect(createQualityProfile).toHaveBeenCalledWith(data); | |||
expect(changeProfileParent).toHaveBeenCalledWith({ key: 'new-profile' }, profile); | |||
}); | |||
function shallowRender(props: Partial<ExtendProfileForm['props']> = {}) { | |||
return shallow<ExtendProfileForm>( | |||
<ExtendProfileForm | |||
onClose={jest.fn()} | |||
onExtend={jest.fn()} | |||
profile={mockQualityProfile()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -20,16 +20,36 @@ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { click, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; | |||
import { setDefaultProfile } from '../../../../api/quality-profiles'; | |||
import { | |||
changeProfileParent, | |||
copyProfile, | |||
createQualityProfile, | |||
deleteProfile, | |||
renameProfile, | |||
setDefaultProfile | |||
} from '../../../../api/quality-profiles'; | |||
import { mockQualityProfile, mockRouter } from '../../../../helpers/testMocks'; | |||
import { ProfileActionModals } from '../../types'; | |||
import { PROFILE_PATH } from '../../utils'; | |||
import DeleteProfileForm from '../DeleteProfileForm'; | |||
import { ProfileActions } from '../ProfileActions'; | |||
import ProfileModalForm from '../ProfileModalForm'; | |||
beforeEach(() => jest.clearAllMocks()); | |||
jest.mock('../../../../api/quality-profiles', () => { | |||
const { mockQualityProfile } = jest.requireActual('../../../../helpers/testMocks'); | |||
jest.mock('../../../../api/quality-profiles', () => ({ | |||
...jest.requireActual('../../../../api/quality-profiles'), | |||
setDefaultProfile: jest.fn().mockResolvedValue({}) | |||
})); | |||
return { | |||
...jest.requireActual('../../../../api/quality-profiles'), | |||
copyProfile: jest.fn().mockResolvedValue(null), | |||
changeProfileParent: jest.fn().mockResolvedValue(null), | |||
createQualityProfile: jest | |||
.fn() | |||
.mockResolvedValue({ profile: mockQualityProfile({ key: 'newProfile' }) }), | |||
deleteProfile: jest.fn().mockResolvedValue(null), | |||
setDefaultProfile: jest.fn().mockResolvedValue(null), | |||
renameProfile: jest.fn().mockResolvedValue(null) | |||
}; | |||
}); | |||
const PROFILE = mockQualityProfile({ | |||
activeRuleCount: 68, | |||
@@ -39,15 +59,13 @@ const PROFILE = mockQualityProfile({ | |||
rulesUpdatedAt: '2017-06-28T12:58:44+0000' | |||
}); | |||
it('renders with no permissions', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
}); | |||
it('renders with permission to edit only', () => { | |||
expect(shallowRender({ profile: { ...PROFILE, actions: { edit: true } } })).toMatchSnapshot(); | |||
}); | |||
beforeEach(() => jest.clearAllMocks()); | |||
it('renders with all permissions', () => { | |||
it('renders correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('no permissions'); | |||
expect(shallowRender({ profile: { ...PROFILE, actions: { edit: true } } })).toMatchSnapshot( | |||
'edit only' | |||
); | |||
expect( | |||
shallowRender({ | |||
profile: { | |||
@@ -61,58 +79,240 @@ it('renders with all permissions', () => { | |||
} | |||
} | |||
}) | |||
).toMatchSnapshot(); | |||
).toMatchSnapshot('all permissions'); | |||
expect(shallowRender().setState({ openModal: ProfileActionModals.Copy })).toMatchSnapshot( | |||
'copy modal' | |||
); | |||
expect(shallowRender().setState({ openModal: ProfileActionModals.Extend })).toMatchSnapshot( | |||
'extend modal' | |||
); | |||
expect(shallowRender().setState({ openModal: ProfileActionModals.Rename })).toMatchSnapshot( | |||
'rename modal' | |||
); | |||
expect(shallowRender().setState({ openModal: ProfileActionModals.Delete })).toMatchSnapshot( | |||
'delete modal' | |||
); | |||
}); | |||
it('should copy profile', async () => { | |||
const name = 'new-name'; | |||
const updateProfiles = jest.fn(() => Promise.resolve()); | |||
const push = jest.fn(); | |||
const wrapper = shallowRender({ | |||
profile: { ...PROFILE, actions: { copy: true } }, | |||
router: { push, replace: jest.fn() }, | |||
updateProfiles | |||
describe('copy a profile', () => { | |||
it('should correctly copy a profile', async () => { | |||
const name = 'new-name'; | |||
const updateProfiles = jest.fn().mockResolvedValue(null); | |||
const push = jest.fn(); | |||
const wrapper = shallowRender({ | |||
profile: { ...PROFILE, actions: { copy: true } }, | |||
router: mockRouter({ push }), | |||
updateProfiles | |||
}); | |||
click(wrapper.find('.it__quality-profiles__copy')); | |||
expect(wrapper.find(ProfileModalForm).exists()).toBe(true); | |||
wrapper | |||
.find(ProfileModalForm) | |||
.props() | |||
.onSubmit(name); | |||
expect(copyProfile).toBeCalledWith(PROFILE.key, name); | |||
await waitAndUpdate(wrapper); | |||
expect(updateProfiles).toBeCalled(); | |||
expect(push).toBeCalledWith({ | |||
pathname: '/profiles/show', | |||
query: { language: 'js', name } | |||
}); | |||
expect(wrapper.find(ProfileModalForm).exists()).toBe(false); | |||
}); | |||
click(wrapper.find('[data-test="quality-profiles__copy"]').parent()); | |||
expect(wrapper.find('CopyProfileForm').exists()).toBe(true); | |||
it('should correctly keep the modal open in case of an error', async () => { | |||
(copyProfile as jest.Mock).mockRejectedValueOnce(null); | |||
wrapper.find('CopyProfileForm').prop<Function>('onCopy')(name); | |||
expect(updateProfiles).toBeCalled(); | |||
await waitAndUpdate(wrapper); | |||
const name = 'new-name'; | |||
const updateProfiles = jest.fn(); | |||
const push = jest.fn(); | |||
const wrapper = shallowRender({ | |||
profile: { ...PROFILE, actions: { copy: true } }, | |||
router: mockRouter({ push }), | |||
updateProfiles | |||
}); | |||
wrapper.setState({ openModal: ProfileActionModals.Copy }); | |||
wrapper.instance().handleProfileCopy(name); | |||
await waitAndUpdate(wrapper); | |||
expect(updateProfiles).not.toBeCalled(); | |||
await waitAndUpdate(wrapper); | |||
expect(push).toBeCalledWith({ | |||
pathname: '/profiles/show', | |||
query: { language: 'js', name } | |||
expect(push).not.toBeCalled(); | |||
expect(wrapper.state().openModal).toBe(ProfileActionModals.Copy); | |||
}); | |||
expect(wrapper.find('CopyProfileForm').exists()).toBe(false); | |||
}); | |||
it('should extend profile', async () => { | |||
const name = 'new-name'; | |||
const updateProfiles = jest.fn(() => Promise.resolve()); | |||
const push = jest.fn(); | |||
const wrapper = shallowRender({ | |||
profile: { ...PROFILE, actions: { copy: true } }, | |||
router: { push, replace: jest.fn() }, | |||
updateProfiles | |||
describe('extend a profile', () => { | |||
it('should correctly extend a profile', async () => { | |||
const name = 'new-name'; | |||
const profile = { ...PROFILE, actions: { copy: true } }; | |||
const updateProfiles = jest.fn().mockResolvedValue(null); | |||
const push = jest.fn(); | |||
const wrapper = shallowRender({ | |||
profile, | |||
router: mockRouter({ push }), | |||
updateProfiles | |||
}); | |||
click(wrapper.find('.it__quality-profiles__extend')); | |||
expect(wrapper.find(ProfileModalForm).exists()).toBe(true); | |||
wrapper | |||
.find(ProfileModalForm) | |||
.props() | |||
.onSubmit(name); | |||
expect(createQualityProfile).toBeCalledWith({ language: profile.language, name }); | |||
await waitAndUpdate(wrapper); | |||
expect(changeProfileParent).toBeCalledWith( | |||
expect.objectContaining({ | |||
key: 'newProfile' | |||
}), | |||
profile | |||
); | |||
await waitAndUpdate(wrapper); | |||
expect(updateProfiles).toBeCalled(); | |||
await waitAndUpdate(wrapper); | |||
expect(push).toBeCalledWith({ | |||
pathname: '/profiles/show', | |||
query: { language: 'js', name } | |||
}); | |||
expect(wrapper.find(ProfileModalForm).exists()).toBe(false); | |||
}); | |||
click(wrapper.find('[data-test="quality-profiles__extend"]').parent()); | |||
expect(wrapper.find('ExtendProfileForm').exists()).toBe(true); | |||
it('should correctly keep the modal open in case of an error', async () => { | |||
(createQualityProfile as jest.Mock).mockRejectedValueOnce(null); | |||
wrapper.find('ExtendProfileForm').prop<Function>('onExtend')(name); | |||
expect(updateProfiles).toBeCalled(); | |||
await waitAndUpdate(wrapper); | |||
const name = 'new-name'; | |||
const updateProfiles = jest.fn(); | |||
const push = jest.fn(); | |||
const wrapper = shallowRender({ | |||
profile: { ...PROFILE, actions: { copy: true } }, | |||
router: mockRouter({ push }), | |||
updateProfiles | |||
}); | |||
wrapper.setState({ openModal: ProfileActionModals.Extend }); | |||
wrapper.instance().handleProfileExtend(name); | |||
await waitAndUpdate(wrapper); | |||
expect(updateProfiles).not.toBeCalled(); | |||
expect(changeProfileParent).not.toBeCalled(); | |||
expect(push).not.toBeCalled(); | |||
expect(wrapper.state().openModal).toBe(ProfileActionModals.Extend); | |||
}); | |||
}); | |||
describe('rename a profile', () => { | |||
it('should correctly rename a profile', async () => { | |||
const name = 'new-name'; | |||
const updateProfiles = jest.fn().mockResolvedValue(null); | |||
const push = jest.fn(); | |||
const wrapper = shallowRender({ | |||
profile: { ...PROFILE, actions: { edit: true } }, | |||
router: mockRouter({ push }), | |||
updateProfiles | |||
}); | |||
click(wrapper.find('.it__quality-profiles__rename')); | |||
expect(wrapper.find(ProfileModalForm).exists()).toBe(true); | |||
wrapper | |||
.find(ProfileModalForm) | |||
.props() | |||
.onSubmit(name); | |||
expect(renameProfile).toBeCalledWith(PROFILE.key, name); | |||
await waitAndUpdate(wrapper); | |||
expect(updateProfiles).toBeCalled(); | |||
expect(push).toBeCalledWith({ | |||
pathname: '/profiles/show', | |||
query: { language: 'js', name } | |||
}); | |||
expect(wrapper.find(ProfileModalForm).exists()).toBe(false); | |||
}); | |||
it('should correctly keep the modal open in case of an error', async () => { | |||
(renameProfile as jest.Mock).mockRejectedValueOnce(null); | |||
const name = 'new-name'; | |||
const updateProfiles = jest.fn(); | |||
const push = jest.fn(); | |||
const wrapper = shallowRender({ | |||
profile: { ...PROFILE, actions: { copy: true } }, | |||
router: mockRouter({ push }), | |||
updateProfiles | |||
}); | |||
wrapper.setState({ openModal: ProfileActionModals.Rename }); | |||
wrapper.instance().handleProfileRename(name); | |||
await waitAndUpdate(wrapper); | |||
expect(updateProfiles).not.toBeCalled(); | |||
await waitAndUpdate(wrapper); | |||
expect(push).not.toBeCalled(); | |||
expect(wrapper.state().openModal).toBe(ProfileActionModals.Rename); | |||
}); | |||
}); | |||
describe('delete a profile', () => { | |||
it('should correctly delete a profile', async () => { | |||
const updateProfiles = jest.fn().mockResolvedValue(null); | |||
const replace = jest.fn(); | |||
const profile = { ...PROFILE, actions: { delete: true } }; | |||
const wrapper = shallowRender({ | |||
profile, | |||
router: mockRouter({ replace }), | |||
updateProfiles | |||
}); | |||
click(wrapper.find('.it__quality-profiles__delete')); | |||
expect(wrapper.find(DeleteProfileForm).exists()).toBe(true); | |||
wrapper | |||
.find(DeleteProfileForm) | |||
.props() | |||
.onDelete(); | |||
expect(deleteProfile).toBeCalledWith(profile); | |||
await waitAndUpdate(wrapper); | |||
expect(updateProfiles).toBeCalled(); | |||
expect(replace).toBeCalledWith(PROFILE_PATH); | |||
expect(wrapper.find(ProfileModalForm).exists()).toBe(false); | |||
}); | |||
it('should correctly keep the modal open in case of an error', async () => { | |||
(deleteProfile as jest.Mock).mockRejectedValueOnce(null); | |||
const updateProfiles = jest.fn(); | |||
const replace = jest.fn(); | |||
const wrapper = shallowRender({ | |||
profile: { ...PROFILE, actions: { copy: true } }, | |||
router: mockRouter({ replace }), | |||
updateProfiles | |||
}); | |||
wrapper.setState({ openModal: ProfileActionModals.Delete }); | |||
wrapper.instance().handleProfileDelete(); | |||
await waitAndUpdate(wrapper); | |||
expect(updateProfiles).not.toBeCalled(); | |||
await waitAndUpdate(wrapper); | |||
expect(push).toBeCalledWith({ | |||
pathname: '/profiles/show', | |||
query: { language: 'js', name } | |||
expect(replace).not.toBeCalled(); | |||
expect(wrapper.state().openModal).toBe(ProfileActionModals.Delete); | |||
}); | |||
expect(wrapper.find('ExtendProfileForm').exists()).toBe(false); | |||
}); | |||
it('should delete profile properly', async () => { | |||
it('should correctly set a profile as the default', async () => { | |||
const updateProfiles = jest.fn(); | |||
const wrapper = shallowRender({ updateProfiles }); |
@@ -0,0 +1,70 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { change } from 'sonar-ui-common/helpers/testUtils'; | |||
import { mockEvent, mockQualityProfile } from '../../../../helpers/testMocks'; | |||
import ProfileModalForm, { ProfileModalFormProps } from '../ProfileModalForm'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ loading: true })).toMatchSnapshot('loading'); | |||
const wrapper = shallowRender(); | |||
change(wrapper.find('#profile-name'), 'new name'); | |||
expect(wrapper).toMatchSnapshot('can submit'); | |||
}); | |||
it('should correctly submit the form', () => { | |||
const onSubmit = jest.fn(); | |||
const wrapper = shallowRender({ onSubmit }); | |||
// Won't submit unless a new name was given. | |||
let formOnSubmit = wrapper.find('form').props().onSubmit; | |||
if (formOnSubmit) { | |||
formOnSubmit(mockEvent()); | |||
} | |||
expect(onSubmit).not.toBeCalled(); | |||
// Input a new name. | |||
change(wrapper.find('#profile-name'), 'new name'); | |||
// Now will submit the form. | |||
formOnSubmit = wrapper.find('form').props().onSubmit; | |||
if (formOnSubmit) { | |||
formOnSubmit(mockEvent()); | |||
} | |||
expect(onSubmit).toBeCalledWith('new name'); | |||
}); | |||
function shallowRender(props: Partial<ProfileModalFormProps> = {}) { | |||
return shallow<ProfileModalFormProps>( | |||
<ProfileModalForm | |||
btnLabelKey="btn-label" | |||
headerKey="header-label" | |||
loading={false} | |||
onClose={jest.fn()} | |||
onSubmit={jest.fn()} | |||
profile={mockQualityProfile()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -1,12 +1,52 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
exports[`should render correctly: default 1`] = ` | |||
<Modal | |||
contentLabel="quality_profiles.delete_confirm_title" | |||
onRequestClose={[MockFunction]} | |||
> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<div | |||
className="modal-head" | |||
> | |||
<h2> | |||
quality_profiles.delete_confirm_title | |||
</h2> | |||
</div> | |||
<div | |||
className="modal-body" | |||
> | |||
<p> | |||
quality_profiles.are_you_sure_want_delete_profile_x.name.JavaScript | |||
</p> | |||
</div> | |||
<div | |||
className="modal-foot" | |||
> | |||
<SubmitButton | |||
className="button-red" | |||
disabled={false} | |||
> | |||
delete | |||
</SubmitButton> | |||
<ResetButtonLink | |||
onClick={[MockFunction]} | |||
> | |||
cancel | |||
</ResetButtonLink> | |||
</div> | |||
</form> | |||
</Modal> | |||
`; | |||
exports[`should render correctly: loading 1`] = ` | |||
<Modal | |||
contentLabel="quality_profiles.delete_confirm_title" | |||
onRequestClose={[MockFunction]} | |||
> | |||
<form | |||
id="delete-profile-form" | |||
onSubmit={[Function]} | |||
> | |||
<div | |||
@@ -19,25 +59,71 @@ exports[`should render correctly 1`] = ` | |||
<div | |||
className="modal-body" | |||
> | |||
<div | |||
className="js-modal-messages" | |||
/> | |||
<p> | |||
quality_profiles.are_you_sure_want_delete_profile_x.name.JavaScript | |||
</p> | |||
</div> | |||
<div | |||
className="modal-foot" | |||
> | |||
<i | |||
className="spinner spacer-right" | |||
/> | |||
<SubmitButton | |||
className="button-red" | |||
disabled={true} | |||
> | |||
delete | |||
</SubmitButton> | |||
<ResetButtonLink | |||
onClick={[MockFunction]} | |||
> | |||
cancel | |||
</ResetButtonLink> | |||
</div> | |||
</form> | |||
</Modal> | |||
`; | |||
exports[`should render correctly: profile has children 1`] = ` | |||
<Modal | |||
contentLabel="quality_profiles.delete_confirm_title" | |||
onRequestClose={[MockFunction]} | |||
> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<div | |||
className="modal-head" | |||
> | |||
<h2> | |||
quality_profiles.delete_confirm_title | |||
</h2> | |||
</div> | |||
<div | |||
className="modal-body" | |||
> | |||
<div> | |||
<Alert | |||
variant="warning" | |||
> | |||
quality_profiles.this_profile_has_descendants | |||
</Alert> | |||
<p> | |||
quality_profiles.are_you_sure_want_delete_profile_x_and_descendants.name.JavaScript | |||
</p> | |||
</div> | |||
</div> | |||
<div | |||
className="modal-foot" | |||
> | |||
<SubmitButton | |||
className="button-red" | |||
disabled={false} | |||
id="delete-profile-submit" | |||
> | |||
delete | |||
</SubmitButton> | |||
<ResetButtonLink | |||
id="delete-profile-cancel" | |||
onClick={[MockFunction]} | |||
> | |||
cancel |
@@ -1,69 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<Modal | |||
contentLabel="quality_profiles.extend_x_title.name.JavaScript" | |||
onRequestClose={[MockFunction]} | |||
size="small" | |||
> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<div | |||
className="modal-head" | |||
> | |||
<h2> | |||
quality_profiles.extend_x_title.name.JavaScript | |||
</h2> | |||
</div> | |||
<div | |||
className="modal-body" | |||
> | |||
<MandatoryFieldsExplanation | |||
className="modal-field" | |||
/> | |||
<div | |||
className="modal-field" | |||
> | |||
<label | |||
htmlFor="extend-profile-name" | |||
> | |||
quality_profiles.copy_new_name | |||
<MandatoryFieldMarker /> | |||
</label> | |||
<input | |||
autoFocus={true} | |||
id="extend-profile-name" | |||
maxLength={100} | |||
name="name" | |||
onChange={[Function]} | |||
required={true} | |||
size={50} | |||
type="text" | |||
value="" | |||
/> | |||
</div> | |||
</div> | |||
<div | |||
className="modal-foot" | |||
> | |||
<DeferredSpinner | |||
className="spacer-right" | |||
loading={false} | |||
/> | |||
<SubmitButton | |||
disabled={true} | |||
id="extend-profile-submit" | |||
> | |||
extend | |||
</SubmitButton> | |||
<ResetButtonLink | |||
id="extend-profile-cancel" | |||
onClick={[MockFunction]} | |||
> | |||
cancel | |||
</ResetButtonLink> | |||
</div> | |||
</form> | |||
</Modal> | |||
`; |
@@ -1,9 +1,10 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders with all permissions 1`] = ` | |||
exports[`renders correctly: all permissions 1`] = ` | |||
<Fragment> | |||
<ActionsDropdown> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__activate-more-rules" | |||
to={ | |||
Object { | |||
"pathname": "/coding_rules", | |||
@@ -14,23 +15,17 @@ exports[`renders with all permissions 1`] = ` | |||
} | |||
} | |||
> | |||
<span | |||
data-test="quality-profiles__activate-more-rules" | |||
> | |||
quality_profiles.activate_more_rules | |||
</span> | |||
quality_profiles.activate_more_rules | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__backup" | |||
download="key.xml" | |||
to="/api/qualityprofiles/backup?language=js&qualityProfile=name" | |||
> | |||
<span | |||
data-test="quality-profiles__backup" | |||
> | |||
backup_verb | |||
</span> | |||
backup_verb | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__compare" | |||
to={ | |||
Object { | |||
"pathname": "/profiles/compare", | |||
@@ -41,77 +36,56 @@ exports[`renders with all permissions 1`] = ` | |||
} | |||
} | |||
> | |||
<span | |||
data-test="quality-profiles__compare" | |||
> | |||
compare | |||
</span> | |||
compare | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__copy" | |||
onClick={[Function]} | |||
> | |||
<span | |||
data-test="quality-profiles__copy" | |||
> | |||
copy | |||
</span> | |||
copy | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__extend" | |||
onClick={[Function]} | |||
> | |||
<span | |||
data-test="quality-profiles__extend" | |||
> | |||
extend | |||
</span> | |||
extend | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__rename" | |||
onClick={[Function]} | |||
> | |||
<span | |||
data-test="quality-profiles__rename" | |||
> | |||
rename | |||
</span> | |||
rename | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__set-as-default" | |||
onClick={[Function]} | |||
> | |||
<span | |||
data-test="quality-profiles__set-as-default" | |||
> | |||
set_as_default | |||
</span> | |||
set_as_default | |||
</ActionsDropdownItem> | |||
<ActionsDropdownDivider /> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__delete" | |||
destructive={true} | |||
onClick={[Function]} | |||
> | |||
<span | |||
data-test="quality-profiles__delete" | |||
> | |||
delete | |||
</span> | |||
delete | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
</Fragment> | |||
`; | |||
exports[`renders with no permissions 1`] = ` | |||
exports[`renders correctly: copy modal 1`] = ` | |||
<Fragment> | |||
<ActionsDropdown> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__backup" | |||
download="key.xml" | |||
to="/api/qualityprofiles/backup?language=js&qualityProfile=name" | |||
> | |||
<span | |||
data-test="quality-profiles__backup" | |||
> | |||
backup_verb | |||
</span> | |||
backup_verb | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__compare" | |||
to={ | |||
Object { | |||
"pathname": "/profiles/compare", | |||
@@ -122,20 +96,91 @@ exports[`renders with no permissions 1`] = ` | |||
} | |||
} | |||
> | |||
<span | |||
data-test="quality-profiles__compare" | |||
> | |||
compare | |||
</span> | |||
compare | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
<ProfileModalForm | |||
btnLabelKey="copy" | |||
headerKey="quality_profiles.copy_x_title" | |||
loading={false} | |||
onClose={[Function]} | |||
onSubmit={[Function]} | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 0, | |||
"activeRuleCount": 68, | |||
"childrenCount": 0, | |||
"depth": 0, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
"rulesUpdatedAt": "2017-06-28T12:58:44+0000", | |||
} | |||
} | |||
/> | |||
</Fragment> | |||
`; | |||
exports[`renders correctly: delete modal 1`] = ` | |||
<Fragment> | |||
<ActionsDropdown> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__backup" | |||
download="key.xml" | |||
to="/api/qualityprofiles/backup?language=js&qualityProfile=name" | |||
> | |||
backup_verb | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__compare" | |||
to={ | |||
Object { | |||
"pathname": "/profiles/compare", | |||
"query": Object { | |||
"language": "js", | |||
"name": "name", | |||
}, | |||
} | |||
} | |||
> | |||
compare | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
<DeleteProfileForm | |||
loading={false} | |||
onClose={[Function]} | |||
onDelete={[Function]} | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 0, | |||
"activeRuleCount": 68, | |||
"childrenCount": 0, | |||
"depth": 0, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
"rulesUpdatedAt": "2017-06-28T12:58:44+0000", | |||
} | |||
} | |||
/> | |||
</Fragment> | |||
`; | |||
exports[`renders with permission to edit only 1`] = ` | |||
exports[`renders correctly: edit only 1`] = ` | |||
<Fragment> | |||
<ActionsDropdown> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__activate-more-rules" | |||
to={ | |||
Object { | |||
"pathname": "/coding_rules", | |||
@@ -146,23 +191,17 @@ exports[`renders with permission to edit only 1`] = ` | |||
} | |||
} | |||
> | |||
<span | |||
data-test="quality-profiles__activate-more-rules" | |||
> | |||
quality_profiles.activate_more_rules | |||
</span> | |||
quality_profiles.activate_more_rules | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__backup" | |||
download="key.xml" | |||
to="/api/qualityprofiles/backup?language=js&qualityProfile=name" | |||
> | |||
<span | |||
data-test="quality-profiles__backup" | |||
> | |||
backup_verb | |||
</span> | |||
backup_verb | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__compare" | |||
to={ | |||
Object { | |||
"pathname": "/profiles/compare", | |||
@@ -173,21 +212,146 @@ exports[`renders with permission to edit only 1`] = ` | |||
} | |||
} | |||
> | |||
<span | |||
data-test="quality-profiles__compare" | |||
> | |||
compare | |||
</span> | |||
compare | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__rename" | |||
onClick={[Function]} | |||
> | |||
<span | |||
data-test="quality-profiles__rename" | |||
> | |||
rename | |||
</span> | |||
rename | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
</Fragment> | |||
`; | |||
exports[`renders correctly: extend modal 1`] = ` | |||
<Fragment> | |||
<ActionsDropdown> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__backup" | |||
download="key.xml" | |||
to="/api/qualityprofiles/backup?language=js&qualityProfile=name" | |||
> | |||
backup_verb | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__compare" | |||
to={ | |||
Object { | |||
"pathname": "/profiles/compare", | |||
"query": Object { | |||
"language": "js", | |||
"name": "name", | |||
}, | |||
} | |||
} | |||
> | |||
compare | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
<ProfileModalForm | |||
btnLabelKey="extend" | |||
headerKey="quality_profiles.extend_x_title" | |||
loading={false} | |||
onClose={[Function]} | |||
onSubmit={[Function]} | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 0, | |||
"activeRuleCount": 68, | |||
"childrenCount": 0, | |||
"depth": 0, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
"rulesUpdatedAt": "2017-06-28T12:58:44+0000", | |||
} | |||
} | |||
/> | |||
</Fragment> | |||
`; | |||
exports[`renders correctly: no permissions 1`] = ` | |||
<Fragment> | |||
<ActionsDropdown> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__backup" | |||
download="key.xml" | |||
to="/api/qualityprofiles/backup?language=js&qualityProfile=name" | |||
> | |||
backup_verb | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__compare" | |||
to={ | |||
Object { | |||
"pathname": "/profiles/compare", | |||
"query": Object { | |||
"language": "js", | |||
"name": "name", | |||
}, | |||
} | |||
} | |||
> | |||
compare | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
</Fragment> | |||
`; | |||
exports[`renders correctly: rename modal 1`] = ` | |||
<Fragment> | |||
<ActionsDropdown> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__backup" | |||
download="key.xml" | |||
to="/api/qualityprofiles/backup?language=js&qualityProfile=name" | |||
> | |||
backup_verb | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__compare" | |||
to={ | |||
Object { | |||
"pathname": "/profiles/compare", | |||
"query": Object { | |||
"language": "js", | |||
"name": "name", | |||
}, | |||
} | |||
} | |||
> | |||
compare | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
<ProfileModalForm | |||
btnLabelKey="rename" | |||
headerKey="quality_profiles.rename_x_title" | |||
loading={false} | |||
onClose={[Function]} | |||
onSubmit={[Function]} | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 0, | |||
"activeRuleCount": 68, | |||
"childrenCount": 0, | |||
"depth": 0, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
"rulesUpdatedAt": "2017-06-28T12:58:44+0000", | |||
} | |||
} | |||
/> | |||
</Fragment> | |||
`; |
@@ -0,0 +1,190 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: can submit 1`] = ` | |||
<Modal | |||
contentLabel="header-label.name.JavaScript" | |||
onRequestClose={[MockFunction]} | |||
size="small" | |||
> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<div | |||
className="modal-head" | |||
> | |||
<h2> | |||
header-label.name.JavaScript | |||
</h2> | |||
</div> | |||
<div | |||
className="modal-body" | |||
> | |||
<MandatoryFieldsExplanation | |||
className="modal-field" | |||
/> | |||
<div | |||
className="modal-field" | |||
> | |||
<label | |||
htmlFor="profile-name" | |||
> | |||
quality_profiles.new_name | |||
<MandatoryFieldMarker /> | |||
</label> | |||
<input | |||
autoFocus={true} | |||
id="profile-name" | |||
maxLength={100} | |||
name="name" | |||
onChange={[Function]} | |||
required={true} | |||
size={50} | |||
type="text" | |||
value="new name" | |||
/> | |||
</div> | |||
</div> | |||
<div | |||
className="modal-foot" | |||
> | |||
<SubmitButton | |||
disabled={false} | |||
> | |||
btn-label | |||
</SubmitButton> | |||
<ResetButtonLink | |||
onClick={[MockFunction]} | |||
> | |||
cancel | |||
</ResetButtonLink> | |||
</div> | |||
</form> | |||
</Modal> | |||
`; | |||
exports[`should render correctly: default 1`] = ` | |||
<Modal | |||
contentLabel="header-label.name.JavaScript" | |||
onRequestClose={[MockFunction]} | |||
size="small" | |||
> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<div | |||
className="modal-head" | |||
> | |||
<h2> | |||
header-label.name.JavaScript | |||
</h2> | |||
</div> | |||
<div | |||
className="modal-body" | |||
> | |||
<MandatoryFieldsExplanation | |||
className="modal-field" | |||
/> | |||
<div | |||
className="modal-field" | |||
> | |||
<label | |||
htmlFor="profile-name" | |||
> | |||
quality_profiles.new_name | |||
<MandatoryFieldMarker /> | |||
</label> | |||
<input | |||
autoFocus={true} | |||
id="profile-name" | |||
maxLength={100} | |||
name="name" | |||
onChange={[Function]} | |||
required={true} | |||
size={50} | |||
type="text" | |||
value="name" | |||
/> | |||
</div> | |||
</div> | |||
<div | |||
className="modal-foot" | |||
> | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
btn-label | |||
</SubmitButton> | |||
<ResetButtonLink | |||
onClick={[MockFunction]} | |||
> | |||
cancel | |||
</ResetButtonLink> | |||
</div> | |||
</form> | |||
</Modal> | |||
`; | |||
exports[`should render correctly: loading 1`] = ` | |||
<Modal | |||
contentLabel="header-label.name.JavaScript" | |||
onRequestClose={[MockFunction]} | |||
size="small" | |||
> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<div | |||
className="modal-head" | |||
> | |||
<h2> | |||
header-label.name.JavaScript | |||
</h2> | |||
</div> | |||
<div | |||
className="modal-body" | |||
> | |||
<MandatoryFieldsExplanation | |||
className="modal-field" | |||
/> | |||
<div | |||
className="modal-field" | |||
> | |||
<label | |||
htmlFor="profile-name" | |||
> | |||
quality_profiles.new_name | |||
<MandatoryFieldMarker /> | |||
</label> | |||
<input | |||
autoFocus={true} | |||
id="profile-name" | |||
maxLength={100} | |||
name="name" | |||
onChange={[Function]} | |||
required={true} | |||
size={50} | |||
type="text" | |||
value="name" | |||
/> | |||
</div> | |||
</div> | |||
<div | |||
className="modal-foot" | |||
> | |||
<i | |||
className="spinner spacer-right" | |||
/> | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
btn-label | |||
</SubmitButton> | |||
<ResetButtonLink | |||
onClick={[MockFunction]} | |||
> | |||
cancel | |||
</ResetButtonLink> | |||
</div> | |||
</form> | |||
</Modal> | |||
`; |
@@ -17,6 +17,7 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as classNames from 'classnames'; | |||
import * as React from 'react'; | |||
import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
@@ -46,7 +47,7 @@ export default function ProfileInheritanceBox(props: Props) { | |||
const offset = 25 * depth; | |||
return ( | |||
<tr className={className} data-test={`quality-profiles__inheritance-${type}`}> | |||
<tr className={classNames(`it__quality-profiles__inheritance-${type}`, className)}> | |||
<td> | |||
<div style={{ paddingLeft: offset }}> | |||
{displayLink ? ( |
@@ -2,7 +2,7 @@ | |||
exports[`should render correctly 1`] = ` | |||
<tr | |||
data-test="quality-profiles__inheritance-current" | |||
className="it__quality-profiles__inheritance-current" | |||
> | |||
<td> | |||
<div | |||
@@ -34,7 +34,7 @@ exports[`should render correctly 1`] = ` | |||
exports[`should render correctly 2`] = ` | |||
<tr | |||
data-test="quality-profiles__inheritance-current" | |||
className="it__quality-profiles__inheritance-current" | |||
> | |||
<td> | |||
<div | |||
@@ -69,7 +69,7 @@ exports[`should render correctly 2`] = ` | |||
exports[`should render correctly 3`] = ` | |||
<tr | |||
data-test="quality-profiles__inheritance-current" | |||
className="it__quality-profiles__inheritance-current" | |||
> | |||
<td> | |||
<div | |||
@@ -105,7 +105,7 @@ exports[`should render correctly 3`] = ` | |||
exports[`should render correctly 4`] = ` | |||
<tr | |||
data-test="quality-profiles__inheritance-current" | |||
className="it__quality-profiles__inheritance-current" | |||
> | |||
<td> | |||
<div |
@@ -38,3 +38,10 @@ export interface ProfileChangelogEvent { | |||
ruleKey: string; | |||
ruleName: string; | |||
} | |||
export enum ProfileActionModals { | |||
Copy, | |||
Extend, | |||
Rename, | |||
Delete | |||
} |
@@ -1506,7 +1506,6 @@ quality_profiles.x_rules_only_in={0} rules only in | |||
quality_profiles.x_rules_have_different_configuration={0} rules have a different configuration | |||
quality_profiles.copy_x_title=Copy Profile "{0}" - {1} | |||
quality_profiles.extend_x_title=Extend Profile "{0}" - {1} | |||
quality_profiles.copy_new_name=New name | |||
quality_profiles.rename_x_title=Rename Profile {0} - {1} | |||
quality_profiles.deprecated=deprecated | |||
quality_profiles.severity_set_to=Severity set to |