aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWouter Admiraal <wouter.admiraal@sonarsource.com>2018-12-19 15:28:21 +0100
committerSonarTech <sonartech@sonarsource.com>2019-01-18 20:21:02 +0100
commit5c110414621fcfb2db1083f76ff09c4b6364757f (patch)
treee4093ebbf1a8402f4e60c4c1eb8839b2a70a5f4a
parent0d3ba8c8aa4c29e46e9a687f42590ffda3fb348f (diff)
downloadsonarqube-5c110414621fcfb2db1083f76ff09c4b6364757f.tar.gz
sonarqube-5c110414621fcfb2db1083f76ff09c4b6364757f.zip
SONARCLOUD-329 Create downgrade reason page
-rw-r--r--server/sonar-web/src/main/js/api/billing.ts10
-rw-r--r--server/sonar-web/src/main/js/app/styles/components/boxed-group.css6
-rw-r--r--server/sonar-web/src/main/js/app/styles/init/forms.css3
-rw-r--r--server/sonar-web/src/main/js/app/utils/startReactApp.tsx20
-rw-r--r--server/sonar-web/src/main/js/apps/feedback/downgrade/DowngradeFeedback.css80
-rw-r--r--server/sonar-web/src/main/js/apps/feedback/downgrade/DowngradeFeedback.tsx170
-rw-r--r--server/sonar-web/src/main/js/apps/feedback/downgrade/__tests__/DowngradeFeedback-test.tsx103
-rw-r--r--server/sonar-web/src/main/js/apps/feedback/downgrade/__tests__/__snapshots__/DowngradeFeedback-test.tsx.snap129
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/actions.ts1
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/OrganizationDelete.tsx23
-rw-r--r--server/sonar-web/src/main/js/components/controls/Radio.tsx5
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/Radio-test.tsx5
-rw-r--r--server/sonar-web/src/main/js/helpers/testUtils.ts13
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties1
14 files changed, 553 insertions, 16 deletions
diff --git a/server/sonar-web/src/main/js/api/billing.ts b/server/sonar-web/src/main/js/api/billing.ts
index b3634ee0430..62a8fe17a2e 100644
--- a/server/sonar-web/src/main/js/api/billing.ts
+++ b/server/sonar-web/src/main/js/api/billing.ts
@@ -17,7 +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 { getJSON } from '../helpers/request';
+import { getJSON, post } from '../helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';
export function getSubscriptionPlans(): Promise<T.SubscriptionPlan[]> {
@@ -26,3 +26,11 @@ export function getSubscriptionPlans(): Promise<T.SubscriptionPlan[]> {
throwGlobalError
);
}
+
+export function giveDowngradeFeedback(data: {
+ organization: string;
+ feedback: string;
+ additionalFeedback?: string;
+}): Promise<void> {
+ return post('/api/billing/send_feedback', data);
+}
diff --git a/server/sonar-web/src/main/js/app/styles/components/boxed-group.css b/server/sonar-web/src/main/js/app/styles/components/boxed-group.css
index f1feb720ab8..6912e432353 100644
--- a/server/sonar-web/src/main/js/app/styles/components/boxed-group.css
+++ b/server/sonar-web/src/main/js/app/styles/components/boxed-group.css
@@ -24,6 +24,12 @@
background-color: #fff;
}
+.boxed-group-centered {
+ margin-left: auto;
+ margin-right: auto;
+ max-width: 500px;
+}
+
.boxed-group > h2 {
line-height: var(--controlHeight);
padding: calc(2 * var(--gridSize)) 20px 0;
diff --git a/server/sonar-web/src/main/js/app/styles/init/forms.css b/server/sonar-web/src/main/js/app/styles/init/forms.css
index 2db2d3eac0f..e270290c3a5 100644
--- a/server/sonar-web/src/main/js/app/styles/init/forms.css
+++ b/server/sonar-web/src/main/js/app/styles/init/forms.css
@@ -69,7 +69,8 @@ select:invalid {
outline: none;
}
-input::placeholder {
+input::placeholder,
+textarea::placeholder {
color: var(--secondFontColor);
}
diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx
index dca733a3587..9af3dc05144 100644
--- a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx
+++ b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx
@@ -183,12 +183,20 @@ export default function startReactApp(
<Route path="issues" component={IssuesPageSelector} />
<RouteWithChildRoutes path="onboarding" childRoutes={onboardingRoutes} />
{isSonarCloud() && (
- <Route
- path="create-organization"
- component={lazyLoad(() =>
- import('../../apps/create/organization/CreateOrganization')
- )}
- />
+ <>
+ <Route
+ path="create-organization"
+ component={lazyLoad(() =>
+ import('../../apps/create/organization/CreateOrganization')
+ )}
+ />
+ <Route
+ path="feedback/downgrade"
+ component={lazyLoad(() =>
+ import('../../apps/feedback/downgrade/DowngradeFeedback')
+ )}
+ />
+ </>
)}
<RouteWithChildRoutes path="organizations" childRoutes={organizationsRoutes} />
<RouteWithChildRoutes path="projects" childRoutes={projectsRoutes} />
diff --git a/server/sonar-web/src/main/js/apps/feedback/downgrade/DowngradeFeedback.css b/server/sonar-web/src/main/js/apps/feedback/downgrade/DowngradeFeedback.css
new file mode 100644
index 00000000000..c8432987d42
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/feedback/downgrade/DowngradeFeedback.css
@@ -0,0 +1,80 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.
+ */
+.billing-downgrade-feedback > .alert {
+ max-width: 400px;
+ margin: 30px auto 50px auto;
+}
+
+.billing-downgrade-feedback .boxed-group {
+ margin-top: 5vh;
+}
+
+.billing-downgrade-feedback .boxed-group-header {
+ padding-top: 40px;
+ text-align: center;
+}
+
+.billing-downgrade-feedback .boxed-group-inner {
+ padding: 50px 50px 40px 50px;
+}
+
+.billing-downgrade-feedback h2 {
+ font-weight: bold;
+ font-size: 18px;
+ margin-bottom: 20px;
+}
+
+.billing-downgrade-feedback .boxed-group-list li {
+ margin-left: 0;
+ padding-left: 0;
+}
+
+.billing-downgrade-feedback input[type='radio'] {
+ margin-right: 1em;
+}
+
+.billing-downgrade-feedback-form-actions {
+ margin-top: 36px;
+}
+
+.billing-downgrade-feedback-explain-wrapper {
+ margin-left: 25px;
+ padding-top: 20px;
+}
+
+.billing-downgrade-feedback-explain-wrapper label {
+ display: block;
+ margin-bottom: 4px;
+ font-weight: bold;
+}
+
+.billing-downgrade-feedback-explain-wrapper label .note {
+ font-weight: normal;
+ margin-left: 0.5em;
+}
+
+.billing-downgrade-feedback-explain-wrapper textarea {
+ display: block;
+ min-height: 70px;
+ width: 100%;
+ padding: 6px 8px;
+ border: 1px solid #cdcdcd;
+ border-radius: 2px;
+}
diff --git a/server/sonar-web/src/main/js/apps/feedback/downgrade/DowngradeFeedback.tsx b/server/sonar-web/src/main/js/apps/feedback/downgrade/DowngradeFeedback.tsx
new file mode 100644
index 00000000000..75b570a57be
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/feedback/downgrade/DowngradeFeedback.tsx
@@ -0,0 +1,170 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { WithRouterProps } from 'react-router';
+import { Alert } from '../../../components/ui/Alert';
+import { SubmitButton } from '../../../components/ui/buttons';
+import { translate } from '../../../helpers/l10n';
+import { giveDowngradeFeedback } from '../../../api/billing';
+import { getBaseUrl } from '../../../helpers/urls';
+import addGlobalSuccessMessage from '../../../app/utils/addGlobalSuccessMessage';
+import './DowngradeFeedback.css';
+import Radio from '../../../components/controls/Radio';
+
+enum Reason {
+ doesntWork = 'did_not_work',
+ doesntMeetExpectations = 'did_not_match_expectations',
+ doesntMeetCompanyPolicy = 'company_policy',
+ onlyTesting = 'just_testing',
+ other = 'other'
+}
+
+interface State {
+ additionalFeedback: string;
+ feedback?: Reason;
+}
+
+export interface LocationState {
+ confirmationMessage?: string;
+ organization?: T.Organization;
+ returnTo?: string;
+ title: string;
+}
+
+export default class DowngradeFeedback extends React.PureComponent<WithRouterProps, State> {
+ state: State = {
+ additionalFeedback: ''
+ };
+
+ handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
+ this.setState({
+ additionalFeedback: event.currentTarget.value
+ });
+ };
+
+ handleChoice = (value: string) => {
+ this.setState({
+ feedback: value as Reason
+ });
+ };
+
+ handleSkip = (event: React.MouseEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ this.navigateAway();
+ };
+
+ handleSubmit = (event: React.FormEvent) => {
+ event.preventDefault();
+
+ const { organization } = this.getLocationState();
+ if (!organization) {
+ return;
+ }
+
+ giveDowngradeFeedback({
+ organization: organization.key,
+ feedback: this.state.feedback as string,
+ additionalFeedback: this.state.additionalFeedback
+ });
+
+ this.navigateAway(translate('billing.downgrade.thankyou_for_feedback'));
+ };
+
+ getLocationState = (): LocationState => {
+ const { location } = this.props;
+ return location.state || {};
+ };
+
+ navigateAway = (message?: string) => {
+ if (message) {
+ addGlobalSuccessMessage(message);
+ }
+
+ const { returnTo } = this.getLocationState();
+ this.props.router.replace({
+ pathname: returnTo || getBaseUrl()
+ });
+ };
+
+ render() {
+ const { organization, confirmationMessage, title } = this.getLocationState();
+ if (!organization) {
+ return null;
+ }
+
+ return (
+ <div className="billing-downgrade-feedback">
+ {confirmationMessage && <Alert variant="success">{confirmationMessage}</Alert>}
+ <div className="boxed-group boxed-group-centered">
+ <div className="boxed-group-header">
+ <h2>{title}</h2>
+ <div>{translate('billing.downgrade.reason.explanation')}</div>
+ </div>
+ <div className="boxed-group-inner">
+ <form className="billing-downgrade-feedback-form" onSubmit={this.handleSubmit}>
+ <ul className="boxed-group-list">
+ {Object.keys(Reason).map(key => {
+ const reason = Reason[key as any];
+ return (
+ <li key={reason}>
+ <Radio
+ checked={this.state.feedback === reason}
+ onCheck={this.handleChoice}
+ value={reason}>
+ {translate('billing.downgrade.reason.option', reason, 'label')}
+ </Radio>
+ {this.state.feedback === reason && (
+ <div className="billing-downgrade-feedback-explain-wrapper">
+ <label htmlFor={`reason_text_${reason}`}>
+ {translate('billing.why')}
+ <span className="note">{translate('billing.optional')}</span>
+ </label>
+ <textarea
+ id={`reason_text_${reason}`}
+ name={`reason_text_${reason}`}
+ onChange={this.handleChange}
+ placeholder={translate(
+ 'billing.downgrade.reason.option',
+ reason,
+ 'placeholder'
+ )}
+ value={this.state.additionalFeedback}
+ />
+ </div>
+ )}
+ </li>
+ );
+ })}
+ </ul>
+ <div className="billing-downgrade-feedback-form-actions">
+ <SubmitButton className="spacer-right" disabled={!this.state.feedback}>
+ {translate('billing.send')}
+ </SubmitButton>
+ <a href="#" onClick={this.handleSkip}>
+ {translate('billing.skip')}
+ </a>
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/feedback/downgrade/__tests__/DowngradeFeedback-test.tsx b/server/sonar-web/src/main/js/apps/feedback/downgrade/__tests__/DowngradeFeedback-test.tsx
new file mode 100644
index 00000000000..df1946a7f98
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/feedback/downgrade/__tests__/DowngradeFeedback-test.tsx
@@ -0,0 +1,103 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 DowngradeFeedback, { LocationState } from '../DowngradeFeedback';
+import { giveDowngradeFeedback } from '../../../../api/billing';
+import { mockRouter, mockLocation } from '../../../../helpers/testUtils';
+
+jest.mock('../../../../api/billing', () => ({
+ giveDowngradeFeedback: jest.fn()
+}));
+
+const mockRouterReplace = jest.fn();
+
+const org: T.Organization = {
+ key: 'myorg',
+ name: 'My Org'
+};
+
+const returnPath = '/path?with=query';
+
+beforeEach(() => {
+ (mockRouterReplace as jest.Mock).mockClear();
+ (giveDowngradeFeedback as jest.Mock).mockClear();
+});
+
+it('should render correctly', () => {
+ const wrapper = getWrapper();
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should enforce choosing a reason, and show an extra textarea if a reason was chosen', () => {
+ const wrapper = getWrapper();
+ expect(wrapper.find('SubmitButton')).toMatchSnapshot();
+
+ wrapper.setState({ feedback: 'other' });
+ expect(wrapper.find('[name="reason_text_other"]').exists()).toBe(true);
+ expect(wrapper.find('SubmitButton')).toMatchSnapshot();
+});
+
+it('should submit the data to the webservice', () => {
+ const wrapper = getWrapper();
+ const feedback = 'other';
+ const additionalFeedback = 'Additional feedback';
+ wrapper.setState({ feedback, additionalFeedback });
+ wrapper
+ .find('form')
+ .at(0)
+ .simulate('submit', {
+ preventDefault: jest.fn()
+ });
+ expect(giveDowngradeFeedback).toBeCalledWith({
+ organization: org.key,
+ feedback,
+ additionalFeedback
+ });
+ expect(mockRouterReplace).toBeCalledWith({
+ pathname: returnPath
+ });
+});
+
+function mockLocationState(overrides = {}): LocationState {
+ return {
+ confirmationMessage: 'Downgrade successful',
+ returnTo: returnPath,
+ organization: org,
+ title: 'Title',
+ ...overrides
+ };
+}
+
+function getWrapper(props = {}, locationState = {}) {
+ return shallow(
+ <DowngradeFeedback
+ location={mockLocation({
+ state: mockLocationState(locationState)
+ })}
+ params={{}}
+ router={mockRouter({
+ replace: mockRouterReplace
+ })}
+ routes={[]}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/feedback/downgrade/__tests__/__snapshots__/DowngradeFeedback-test.tsx.snap b/server/sonar-web/src/main/js/apps/feedback/downgrade/__tests__/__snapshots__/DowngradeFeedback-test.tsx.snap
new file mode 100644
index 00000000000..5a2a16d4dff
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/feedback/downgrade/__tests__/__snapshots__/DowngradeFeedback-test.tsx.snap
@@ -0,0 +1,129 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should enforce choosing a reason, and show an extra textarea if a reason was chosen 1`] = `
+<SubmitButton
+ className="spacer-right"
+ disabled={true}
+>
+ billing.send
+</SubmitButton>
+`;
+
+exports[`should enforce choosing a reason, and show an extra textarea if a reason was chosen 2`] = `
+<SubmitButton
+ className="spacer-right"
+ disabled={false}
+>
+ billing.send
+</SubmitButton>
+`;
+
+exports[`should render correctly 1`] = `
+<div
+ className="billing-downgrade-feedback"
+>
+ <Alert
+ variant="success"
+ >
+ Downgrade successful
+ </Alert>
+ <div
+ className="boxed-group boxed-group-centered"
+ >
+ <div
+ className="boxed-group-header"
+ >
+ <h2>
+ Title
+ </h2>
+ <div>
+ billing.downgrade.reason.explanation
+ </div>
+ </div>
+ <div
+ className="boxed-group-inner"
+ >
+ <form
+ className="billing-downgrade-feedback-form"
+ onSubmit={[Function]}
+ >
+ <ul
+ className="boxed-group-list"
+ >
+ <li
+ key="did_not_work"
+ >
+ <Radio
+ checked={false}
+ onCheck={[Function]}
+ value="did_not_work"
+ >
+ billing.downgrade.reason.option.did_not_work.label
+ </Radio>
+ </li>
+ <li
+ key="did_not_match_expectations"
+ >
+ <Radio
+ checked={false}
+ onCheck={[Function]}
+ value="did_not_match_expectations"
+ >
+ billing.downgrade.reason.option.did_not_match_expectations.label
+ </Radio>
+ </li>
+ <li
+ key="company_policy"
+ >
+ <Radio
+ checked={false}
+ onCheck={[Function]}
+ value="company_policy"
+ >
+ billing.downgrade.reason.option.company_policy.label
+ </Radio>
+ </li>
+ <li
+ key="just_testing"
+ >
+ <Radio
+ checked={false}
+ onCheck={[Function]}
+ value="just_testing"
+ >
+ billing.downgrade.reason.option.just_testing.label
+ </Radio>
+ </li>
+ <li
+ key="other"
+ >
+ <Radio
+ checked={false}
+ onCheck={[Function]}
+ value="other"
+ >
+ billing.downgrade.reason.option.other.label
+ </Radio>
+ </li>
+ </ul>
+ <div
+ className="billing-downgrade-feedback-form-actions"
+ >
+ <SubmitButton
+ className="spacer-right"
+ disabled={true}
+ >
+ billing.send
+ </SubmitButton>
+ <a
+ href="#"
+ onClick={[Function]}
+ >
+ billing.skip
+ </a>
+ </div>
+ </form>
+ </div>
+ </div>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/organizations/actions.ts b/server/sonar-web/src/main/js/apps/organizations/actions.ts
index 0bd460161e2..cda9b50e9e4 100644
--- a/server/sonar-web/src/main/js/apps/organizations/actions.ts
+++ b/server/sonar-web/src/main/js/apps/organizations/actions.ts
@@ -47,6 +47,5 @@ export const updateOrganization = (key: string, changes: T.OrganizationBase) =>
export const deleteOrganization = (key: string) => (dispatch: Dispatch<any>) => {
return api.deleteOrganization(key).then(() => {
dispatch(actions.deleteOrganization(key));
- dispatch(addGlobalSuccessMessage(translate('organization.deleted')));
});
};
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationDelete.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationDelete.tsx
index 2df8f24417b..6954e3bf7e7 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationDelete.tsx
+++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationDelete.tsx
@@ -29,6 +29,7 @@ import { getOrganizationBilling } from '../../../api/organizations';
import { isSonarCloud } from '../../../helpers/system';
import { Alert } from '../../../components/ui/Alert';
import { withRouter, Router } from '../../../components/hoc/withRouter';
+import addGlobalSuccessMessage from '../../../app/utils/addGlobalSuccessMessage';
interface DispatchToProps {
deleteOrganization: (key: string) => Promise<void>;
@@ -78,7 +79,7 @@ export class OrganizationDelete extends React.PureComponent<Props, State> {
}
};
- handleInput = (event: React.SyntheticEvent<HTMLInputElement>) => {
+ handleInput = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ verify: event.currentTarget.value });
};
@@ -87,8 +88,24 @@ export class OrganizationDelete extends React.PureComponent<Props, State> {
};
onDelete = () => {
- return this.props.deleteOrganization(this.props.organization.key).then(() => {
- this.props.router.replace('/');
+ const { organization } = this.props;
+ return this.props.deleteOrganization(organization.key).then(() => {
+ if (this.state.hasPaidPlan) {
+ this.props.router.replace({
+ pathname: '/feedback/downgrade',
+ state: {
+ confirmationMessage: translateWithParameters(
+ 'organization.deleted_x',
+ organization.name
+ ),
+ organization,
+ title: translate('billing.downgrade.reason.title_deleted')
+ }
+ });
+ } else {
+ addGlobalSuccessMessage(translate('organization.deleted'));
+ this.props.router.replace('/');
+ }
});
};
diff --git a/server/sonar-web/src/main/js/components/controls/Radio.tsx b/server/sonar-web/src/main/js/components/controls/Radio.tsx
index 5c690bdaba4..e389c71d664 100644
--- a/server/sonar-web/src/main/js/components/controls/Radio.tsx
+++ b/server/sonar-web/src/main/js/components/controls/Radio.tsx
@@ -23,14 +23,15 @@ import * as classNames from 'classnames';
interface Props {
checked: boolean;
className?: string;
- onCheck: () => void;
+ onCheck: (value: string) => void;
+ value: string;
}
export default class Radio extends React.PureComponent<Props> {
handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
event.currentTarget.blur();
- this.props.onCheck();
+ this.props.onCheck(this.props.value);
};
render() {
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/Radio-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/Radio-test.tsx
index f3224f519c5..a0f9eabdc8d 100644
--- a/server/sonar-web/src/main/js/components/controls/__tests__/Radio-test.tsx
+++ b/server/sonar-web/src/main/js/components/controls/__tests__/Radio-test.tsx
@@ -24,11 +24,12 @@ import { click } from '../../../helpers/testUtils';
it('should render and check', () => {
const onCheck = jest.fn();
- const wrapper = shallow(<Radio checked={false} onCheck={onCheck} />);
+ const value = 'value';
+ const wrapper = shallow(<Radio checked={false} onCheck={onCheck} value={value} />);
expect(wrapper).toMatchSnapshot();
click(wrapper);
- expect(onCheck).toBeCalled();
+ expect(onCheck).toBeCalledWith(value);
wrapper.setProps({ checked: true });
expect(wrapper).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/helpers/testUtils.ts b/server/sonar-web/src/main/js/helpers/testUtils.ts
index fdf8bf0728b..210bd412192 100644
--- a/server/sonar-web/src/main/js/helpers/testUtils.ts
+++ b/server/sonar-web/src/main/js/helpers/testUtils.ts
@@ -19,6 +19,7 @@
*/
import { ShallowWrapper, ReactWrapper } from 'enzyme';
import { InjectedRouter } from 'react-router';
+import { Location } from 'history';
import { EditionKey } from '../apps/marketplace/utils';
export const mockEvent = {
@@ -133,6 +134,18 @@ export async function waitAndUpdate(wrapper: ShallowWrapper<any, any> | ReactWra
wrapper.update();
}
+export function mockLocation(overrides = {}): Location {
+ return {
+ action: 'PUSH',
+ key: 'key',
+ pathname: '/path',
+ query: {},
+ search: '',
+ state: {},
+ ...overrides
+ };
+}
+
export function mockRouter(overrides: { push?: Function; replace?: Function } = {}) {
return {
createHref: jest.fn(),
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index 606e8f82fbb..76a3cc099ff 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -2643,6 +2643,7 @@ organization.delete.description=Delete this organization from {instance}. All pr
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.deleted_x=Organization "{0}" has been deleted.
organization.description=Description
organization.description.description=Description of the organization.
organization.edit=Edit Organization