@@ -48,6 +48,11 @@ public interface BillingValidations { | |||
*/ | |||
boolean canUpdateProjectVisibilityToPrivate(Organization organization); | |||
/** | |||
* Actions to do on an organization deletion | |||
*/ | |||
void onDelete(Organization organization); | |||
class Organization { | |||
private final String key; | |||
private final String uuid; |
@@ -55,4 +55,12 @@ public class BillingValidationsProxyImpl implements BillingValidationsProxy { | |||
public boolean canUpdateProjectVisibilityToPrivate(Organization organization) { | |||
return billingValidationsExtension == null || billingValidationsExtension.canUpdateProjectVisibilityToPrivate(organization); | |||
} | |||
@Override | |||
public void onDelete(Organization organization) { | |||
if (billingValidationsExtension == null) { | |||
return; | |||
} | |||
billingValidationsExtension.onDelete(organization); | |||
} | |||
} |
@@ -35,6 +35,8 @@ import org.sonar.db.organization.OrganizationDto; | |||
import org.sonar.db.qualitygate.QualityGateDto; | |||
import org.sonar.db.qualityprofile.QProfileDto; | |||
import org.sonar.server.component.ComponentCleanerService; | |||
import org.sonar.server.organization.BillingValidations; | |||
import org.sonar.server.organization.BillingValidationsProxy; | |||
import org.sonar.server.organization.DefaultOrganization; | |||
import org.sonar.server.organization.DefaultOrganizationProvider; | |||
import org.sonar.server.organization.OrganizationFlags; | |||
@@ -62,10 +64,11 @@ public class DeleteAction implements OrganizationsWsAction { | |||
private final UserIndexer userIndexer; | |||
private final QProfileFactory qProfileFactory; | |||
private final ProjectLifeCycleListeners projectLifeCycleListeners; | |||
private final BillingValidationsProxy billingValidations; | |||
public DeleteAction(UserSession userSession, DbClient dbClient, DefaultOrganizationProvider defaultOrganizationProvider, | |||
ComponentCleanerService componentCleanerService, OrganizationFlags organizationFlags, UserIndexer userIndexer, | |||
QProfileFactory qProfileFactory, ProjectLifeCycleListeners projectLifeCycleListeners) { | |||
public DeleteAction(UserSession userSession, DbClient dbClient, DefaultOrganizationProvider defaultOrganizationProvider, ComponentCleanerService componentCleanerService, | |||
OrganizationFlags organizationFlags, UserIndexer userIndexer, QProfileFactory qProfileFactory, ProjectLifeCycleListeners projectLifeCycleListeners, | |||
BillingValidationsProxy billingValidations) { | |||
this.userSession = userSession; | |||
this.dbClient = dbClient; | |||
this.defaultOrganizationProvider = defaultOrganizationProvider; | |||
@@ -74,6 +77,7 @@ public class DeleteAction implements OrganizationsWsAction { | |||
this.userIndexer = userIndexer; | |||
this.qProfileFactory = qProfileFactory; | |||
this.projectLifeCycleListeners = projectLifeCycleListeners; | |||
this.billingValidations = billingValidations; | |||
} | |||
@Override | |||
@@ -116,6 +120,7 @@ public class DeleteAction implements OrganizationsWsAction { | |||
deleteQualityProfiles(dbSession, organization); | |||
deleteQualityGates(dbSession, organization); | |||
deleteOrganization(dbSession, organization); | |||
billingValidations.onDelete(new BillingValidations.Organization(organization.getKey(), organization.getUuid())); | |||
response.noContent(); | |||
} |
@@ -59,6 +59,8 @@ import org.sonar.server.es.SearchOptions; | |||
import org.sonar.server.exceptions.ForbiddenException; | |||
import org.sonar.server.exceptions.NotFoundException; | |||
import org.sonar.server.exceptions.UnauthorizedException; | |||
import org.sonar.server.organization.BillingValidations; | |||
import org.sonar.server.organization.BillingValidationsProxy; | |||
import org.sonar.server.organization.TestDefaultOrganizationProvider; | |||
import org.sonar.server.organization.TestOrganizationFlags; | |||
import org.sonar.server.project.Project; | |||
@@ -119,11 +121,13 @@ public class DeleteActionTest { | |||
private final WebhookDeliveryDao deliveryDao = dbClient.webhookDeliveryDao(); | |||
private final WebhookDeliveryDbTester webhookDeliveryDbTester = db.webhookDelivery(); | |||
private ProjectLifeCycleListeners projectLifeCycleListeners = mock(ProjectLifeCycleListeners.class); | |||
private BillingValidationsProxy billingValidationsProxy = mock(BillingValidationsProxy.class); | |||
private WsActionTester wsTester = new WsActionTester( | |||
new DeleteAction(userSession, dbClient, defaultOrganizationProvider, spiedComponentCleanerService, organizationFlags, userIndexer, qProfileFactory, projectLifeCycleListeners)); | |||
new DeleteAction(userSession, dbClient, defaultOrganizationProvider, spiedComponentCleanerService, organizationFlags, userIndexer, qProfileFactory, projectLifeCycleListeners, | |||
billingValidationsProxy)); | |||
@Test | |||
public void test_definition() { | |||
public void definition() { | |||
WebService.Action action = wsTester.getDef(); | |||
assertThat(action.key()).isEqualTo("delete"); | |||
assertThat(action.isPost()).isTrue(); | |||
@@ -542,6 +546,16 @@ public class DeleteActionTest { | |||
} | |||
} | |||
@Test | |||
public void call_billing_validation_on_delete() { | |||
OrganizationDto organization = db.organizations().insert(); | |||
logInAsAdministrator(organization); | |||
sendRequest(organization); | |||
verify(billingValidationsProxy).onDelete(any(BillingValidations.Organization.class)); | |||
} | |||
@DataProvider | |||
public static Object[][] indexOfFailingProjectDeletion() { | |||
return new Object[][]{ |
@@ -104,3 +104,20 @@ export function changeProjectVisibility( | |||
): Promise<void> { | |||
return post('/api/organizations/update_project_visibility', { organization, projectVisibility }); | |||
} | |||
export interface OrganizationBilling { | |||
nclocCount: number; | |||
subscription: { | |||
plan?: { | |||
maxNcloc: number; | |||
price: number; | |||
}; | |||
nextBillingDate?: string; | |||
status: 'active' | 'inactive' | 'suspended'; | |||
trial: boolean; | |||
}; | |||
} | |||
export function getOrganizationBilling(organization: string): Promise<OrganizationBilling> { | |||
return getJSON('/api/billing/show', { organization, p: 1, ps: 1 }); | |||
} |
@@ -1,122 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 Helmet from 'react-helmet'; | |||
import { connect } from 'react-redux'; | |||
import { withRouter } from 'react-router'; | |||
import Modal from '../../../components/controls/Modal'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { getOrganizationByKey } from '../../../store/rootReducer'; | |||
import { deleteOrganization } from '../actions'; | |||
class OrganizationDelete extends React.PureComponent { | |||
/*:: props: { | |||
organization: { | |||
key: string, | |||
name: string | |||
}, | |||
router: { | |||
replace: string => void | |||
}, | |||
deleteOrganization: string => Promise<*> | |||
}; | |||
*/ | |||
state = { | |||
deleting: false, | |||
loading: false | |||
}; | |||
handleSubmit = (e /*: Object */) => { | |||
e.preventDefault(); | |||
this.setState({ loading: true }); | |||
this.props.deleteOrganization(this.props.organization.key).then(() => { | |||
this.props.router.replace('/'); | |||
}); | |||
}; | |||
handleOpenModal = () => { | |||
this.setState({ deleting: true }); | |||
}; | |||
handleCloseModal = () => { | |||
this.setState({ deleting: false }); | |||
}; | |||
renderModal() { | |||
return ( | |||
<Modal contentLabel="modal form" onRequestClose={this.handleCloseModal}> | |||
<header className="modal-head"> | |||
<h2>{translate('organization.delete')}</h2> | |||
</header> | |||
<form onSubmit={this.handleSubmit}> | |||
<div className="modal-body">{translate('organization.delete.question')}</div> | |||
<footer className="modal-foot"> | |||
{this.state.loading ? ( | |||
<i className="spinner" /> | |||
) : ( | |||
<div> | |||
<button type="submit" className="button-red"> | |||
{translate('delete')} | |||
</button> | |||
<button type="reset" className="button-link" onClick={this.handleCloseModal}> | |||
{translate('cancel')} | |||
</button> | |||
</div> | |||
)} | |||
</footer> | |||
</form> | |||
</Modal> | |||
); | |||
} | |||
render() { | |||
const title = translate('organization.delete'); | |||
return ( | |||
<div className="page page-limited"> | |||
<Helmet title={title} /> | |||
<header className="page-header"> | |||
<h1 className="page-title">{title}</h1> | |||
<div className="page-description">{translate('organization.delete.description')}</div> | |||
</header> | |||
<div> | |||
<button | |||
className="button-red" | |||
disabled={this.state.loading || this.state.deleting} | |||
onClick={this.handleOpenModal}> | |||
{translate('delete')} | |||
</button> | |||
{this.state.deleting && this.renderModal()} | |||
</div> | |||
</div> | |||
); | |||
} | |||
} | |||
const mapDispatchToProps = { deleteOrganization }; | |||
export default connect(null, mapDispatchToProps)(withRouter(OrganizationDelete)); | |||
export const UnconnectedOrganizationDelete = OrganizationDelete; |
@@ -0,0 +1,135 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 * as PropTypes from 'prop-types'; | |||
import Helmet from 'react-helmet'; | |||
import { connect } from 'react-redux'; | |||
import ConfirmButton from '../../../components/controls/ConfirmButton'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { deleteOrganization } from '../actions'; | |||
import { Organization } from '../../../app/types'; | |||
import { Button } from '../../../components/ui/buttons'; | |||
import { getOrganizationBilling } from '../../../api/organizations'; | |||
interface DispatchToProps { | |||
deleteOrganization: (key: string) => Promise<void>; | |||
} | |||
interface OwnProps { | |||
organization: Pick<Organization, 'key' | 'name'>; | |||
} | |||
type Props = OwnProps & DispatchToProps; | |||
interface State { | |||
hasPaidPlan?: boolean; | |||
} | |||
export class OrganizationDelete extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
static contextTypes = { | |||
router: PropTypes.object, | |||
onSonarCloud: PropTypes.bool | |||
}; | |||
state: State = {}; | |||
componentDidMount() { | |||
this.mounted = true; | |||
this.fetchOrganizationPlanInfo(); | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
fetchOrganizationPlanInfo = () => { | |||
if (this.context.onSonarCloud) { | |||
getOrganizationBilling(this.props.organization.key).then( | |||
billingInfo => { | |||
if (this.mounted) { | |||
this.setState({ | |||
hasPaidPlan: billingInfo.subscription.status !== 'inactive' | |||
}); | |||
} | |||
}, | |||
() => { | |||
if (this.mounted) { | |||
this.setState({ hasPaidPlan: false }); | |||
} | |||
} | |||
); | |||
} | |||
}; | |||
onDelete = () => { | |||
return this.props.deleteOrganization(this.props.organization.key).then(() => { | |||
this.context.router.replace('/'); | |||
}); | |||
}; | |||
render() { | |||
const { hasPaidPlan } = this.state; | |||
const { onSonarCloud } = this.context; | |||
const title = translate('organization.delete'); | |||
return ( | |||
<> | |||
<Helmet title={title} /> | |||
<div className="page page-limited"> | |||
<header className="page-header"> | |||
<h1 className="page-title">{title}</h1> | |||
<div className="page-description"> | |||
{onSonarCloud | |||
? translate('organization.delete.description.sonarcloud') | |||
: translate('organization.delete.description')} | |||
</div> | |||
</header> | |||
<ConfirmButton | |||
confirmButtonText={translate('delete')} | |||
isDestructive={true} | |||
modalBody={ | |||
<div> | |||
{translate('organization.delete.question')} | |||
{hasPaidPlan && ( | |||
<p className="alert alert-warn big-spacer-top"> | |||
{translate('organization.delete.sonarcloud.paid_plan_info')} | |||
</p> | |||
)} | |||
</div> | |||
} | |||
modalHeader={translate('organization.delete')} | |||
onConfirm={this.onDelete}> | |||
{({ onClick }) => ( | |||
<Button className="js-custom-measure-delete button-red" onClick={onClick}> | |||
{translate('delete')} | |||
</Button> | |||
)} | |||
</ConfirmButton> | |||
</div> | |||
</> | |||
); | |||
} | |||
} | |||
const mapDispatchToProps: DispatchToProps = { deleteOrganization: deleteOrganization as any }; | |||
export default connect<null, DispatchToProps, OwnProps>(null, mapDispatchToProps)( | |||
OrganizationDelete | |||
); |
@@ -1,34 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import { UnconnectedOrganizationDelete } from '../OrganizationDelete'; | |||
it('smoke test', () => { | |||
const organization = { key: 'foo', name: 'Foo' }; | |||
const wrapper = shallow(<UnconnectedOrganizationDelete organization={organization} />); | |||
expect(wrapper).toMatchSnapshot(); | |||
wrapper.setState({ deleting: true }); | |||
expect(wrapper).toMatchSnapshot(); | |||
wrapper.setState({ loading: true }); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); |
@@ -0,0 +1,67 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 { shallow } from 'enzyme'; | |||
import { OrganizationDelete } from '../OrganizationDelete'; | |||
import { getOrganizationBilling } from '../../../../api/organizations'; | |||
import { waitAndUpdate } from '../../../../helpers/testUtils'; | |||
jest.mock('../../../../api/organizations', () => ({ | |||
getOrganizationBilling: jest.fn(() => | |||
Promise.resolve({ nclocCount: 1000, subscription: { status: 'active', trial: true } }) | |||
) | |||
})); | |||
beforeEach(() => { | |||
(getOrganizationBilling as jest.Mock<any>).mockClear(); | |||
}); | |||
it('smoke test', () => { | |||
expect(getWrapper()).toMatchSnapshot(); | |||
}); | |||
it('should redirect the page', async () => { | |||
const deleteOrganization = jest.fn(() => Promise.resolve()); | |||
const replace = jest.fn(); | |||
const wrapper = getWrapper({ deleteOrganization }, { router: { replace } }); | |||
(wrapper.instance() as OrganizationDelete).onDelete(); | |||
await waitAndUpdate(wrapper); | |||
expect(deleteOrganization).toHaveBeenCalledWith('foo'); | |||
expect(replace).toHaveBeenCalledWith('/'); | |||
}); | |||
it('should show a info message for paying organization', async () => { | |||
const wrapper = getWrapper({}, { onSonarCloud: true }); | |||
await waitAndUpdate(wrapper); | |||
expect(getOrganizationBilling).toHaveBeenCalledWith('foo'); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
function getWrapper(props = {}, context = {}) { | |||
return shallow( | |||
<OrganizationDelete | |||
deleteOrganization={jest.fn(() => Promise.resolve())} | |||
organization={{ key: 'foo', name: 'Foo' }} | |||
{...props} | |||
/>, | |||
{ context: { router: { replace: jest.fn() }, ...context } } | |||
); | |||
} |
@@ -1,174 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`smoke test 1`] = ` | |||
<div | |||
className="page page-limited" | |||
> | |||
<HelmetWrapper | |||
defer={true} | |||
encodeSpecialCharacters={true} | |||
title="organization.delete" | |||
/> | |||
<header | |||
className="page-header" | |||
> | |||
<h1 | |||
className="page-title" | |||
> | |||
organization.delete | |||
</h1> | |||
<div | |||
className="page-description" | |||
> | |||
organization.delete.description | |||
</div> | |||
</header> | |||
<div> | |||
<button | |||
className="button-red" | |||
disabled={false} | |||
onClick={[Function]} | |||
> | |||
delete | |||
</button> | |||
</div> | |||
</div> | |||
`; | |||
exports[`smoke test 2`] = ` | |||
<div | |||
className="page page-limited" | |||
> | |||
<HelmetWrapper | |||
defer={true} | |||
encodeSpecialCharacters={true} | |||
title="organization.delete" | |||
/> | |||
<header | |||
className="page-header" | |||
> | |||
<h1 | |||
className="page-title" | |||
> | |||
organization.delete | |||
</h1> | |||
<div | |||
className="page-description" | |||
> | |||
organization.delete.description | |||
</div> | |||
</header> | |||
<div> | |||
<button | |||
className="button-red" | |||
disabled={true} | |||
onClick={[Function]} | |||
> | |||
delete | |||
</button> | |||
<Modal | |||
contentLabel="modal form" | |||
onRequestClose={[Function]} | |||
> | |||
<header | |||
className="modal-head" | |||
> | |||
<h2> | |||
organization.delete | |||
</h2> | |||
</header> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<div | |||
className="modal-body" | |||
> | |||
organization.delete.question | |||
</div> | |||
<footer | |||
className="modal-foot" | |||
> | |||
<div> | |||
<button | |||
className="button-red" | |||
type="submit" | |||
> | |||
delete | |||
</button> | |||
<button | |||
className="button-link" | |||
onClick={[Function]} | |||
type="reset" | |||
> | |||
cancel | |||
</button> | |||
</div> | |||
</footer> | |||
</form> | |||
</Modal> | |||
</div> | |||
</div> | |||
`; | |||
exports[`smoke test 3`] = ` | |||
<div | |||
className="page page-limited" | |||
> | |||
<HelmetWrapper | |||
defer={true} | |||
encodeSpecialCharacters={true} | |||
title="organization.delete" | |||
/> | |||
<header | |||
className="page-header" | |||
> | |||
<h1 | |||
className="page-title" | |||
> | |||
organization.delete | |||
</h1> | |||
<div | |||
className="page-description" | |||
> | |||
organization.delete.description | |||
</div> | |||
</header> | |||
<div> | |||
<button | |||
className="button-red" | |||
disabled={true} | |||
onClick={[Function]} | |||
> | |||
delete | |||
</button> | |||
<Modal | |||
contentLabel="modal form" | |||
onRequestClose={[Function]} | |||
> | |||
<header | |||
className="modal-head" | |||
> | |||
<h2> | |||
organization.delete | |||
</h2> | |||
</header> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<div | |||
className="modal-body" | |||
> | |||
organization.delete.question | |||
</div> | |||
<footer | |||
className="modal-foot" | |||
> | |||
<i | |||
className="spinner" | |||
/> | |||
</footer> | |||
</form> | |||
</Modal> | |||
</div> | |||
</div> | |||
`; |
@@ -0,0 +1,84 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should show a info message for paying organization 1`] = ` | |||
<React.Fragment> | |||
<HelmetWrapper | |||
defer={true} | |||
encodeSpecialCharacters={true} | |||
title="organization.delete" | |||
/> | |||
<div | |||
className="page page-limited" | |||
> | |||
<header | |||
className="page-header" | |||
> | |||
<h1 | |||
className="page-title" | |||
> | |||
organization.delete | |||
</h1> | |||
<div | |||
className="page-description" | |||
> | |||
organization.delete.description.sonarcloud | |||
</div> | |||
</header> | |||
<ConfirmButton | |||
confirmButtonText="delete" | |||
isDestructive={true} | |||
modalBody={ | |||
<div> | |||
organization.delete.question | |||
<p | |||
className="alert alert-warn big-spacer-top" | |||
> | |||
organization.delete.sonarcloud.paid_plan_info | |||
</p> | |||
</div> | |||
} | |||
modalHeader="organization.delete" | |||
onConfirm={[Function]} | |||
/> | |||
</div> | |||
</React.Fragment> | |||
`; | |||
exports[`smoke test 1`] = ` | |||
<React.Fragment> | |||
<HelmetWrapper | |||
defer={true} | |||
encodeSpecialCharacters={true} | |||
title="organization.delete" | |||
/> | |||
<div | |||
className="page page-limited" | |||
> | |||
<header | |||
className="page-header" | |||
> | |||
<h1 | |||
className="page-title" | |||
> | |||
organization.delete | |||
</h1> | |||
<div | |||
className="page-description" | |||
> | |||
organization.delete.description | |||
</div> | |||
</header> | |||
<ConfirmButton | |||
confirmButtonText="delete" | |||
isDestructive={true} | |||
modalBody={ | |||
<div> | |||
organization.delete.question | |||
</div> | |||
} | |||
modalHeader="organization.delete" | |||
onConfirm={[Function]} | |||
/> | |||
</div> | |||
</React.Fragment> | |||
`; |
@@ -2517,6 +2517,8 @@ organization.avatar.preview=Preview | |||
organization.created=Organization "{0}" has been created. | |||
organization.delete=Delete Organization | |||
organization.delete.description=Delete this organization from SonarQube. All projects belonging to the organization will be deleted as well. The operation cannot be undone. | |||
organization.delete.description.sonarcloud=Delete this organization from SonarCloud. All projects belonging to the organization will be deleted as well. The operation cannot be undone. | |||
organization.delete.sonarcloud.paid_plan_info=Your current paid plan subscription will stop and you won't be charged anymore. | |||
organization.delete.question=Are you sure you want to delete this organization? | |||
organization.deleted=Organization has been deleted. | |||
organization.description=Description |
@@ -77,4 +77,9 @@ public class FakeBillingValidations implements BillingValidationsExtension { | |||
} | |||
return !settings.getBoolean(PREVENT_UPDATING_PROJECTS_VISIBILITY_TO_PRIVATE_SETTING); | |||
} | |||
@Override | |||
public void onDelete(Organization organization) { | |||
// do nothing | |||
} | |||
} |