diff options
9 files changed, 371 insertions, 2 deletions
diff --git a/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/EmailAlreadyExistsPage.java b/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/EmailAlreadyExistsPage.java new file mode 100644 index 00000000000..2dfdb005d00 --- /dev/null +++ b/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/EmailAlreadyExistsPage.java @@ -0,0 +1,48 @@ +/* + * 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. + */ +package org.sonarqube.qa.util.pageobjects; + +import static com.codeborne.selenide.Condition.text; +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.$; + +public class EmailAlreadyExistsPage extends Navigation { + + public EmailAlreadyExistsPage shouldHaveExistingAccount(String login) { + $(".js-existing-account").shouldHave(text(login)); + return this; + } + + public EmailAlreadyExistsPage shouldHaveNewAccount(String login) { + $(".js-new-account").shouldHave(text(login)); + return this; + } + + public void clickContinue() { + $(".js-continue").click(); + $(".js-continue").shouldNotBe(visible); + } + + public void clickCancel() { + $(".js-cancel").click(); + $(".js-cancel").shouldNotBe(visible); + } + +} diff --git a/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/Navigation.java b/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/Navigation.java index f4e37dce67f..ede43929a06 100644 --- a/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/Navigation.java +++ b/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/Navigation.java @@ -258,6 +258,10 @@ public class Navigation { return Selenide.$("#error"); } + public EmailAlreadyExistsPage asEmailAlreadyExistsPage() { + return new EmailAlreadyExistsPage(); + } + private static SelenideElement logInLink() { return Selenide.$(By.linkText("Log in")); } @@ -268,6 +272,7 @@ public class Navigation { /** * Safe encoding for URL parameters + * * @param parameter the parameter to escape value * @return the escaped value of parameter */ diff --git a/server/sonar-web/scripts/start.js b/server/sonar-web/scripts/start.js index 21776e5036b..53969869f33 100644 --- a/server/sonar-web/scripts/start.js +++ b/server/sonar-web/scripts/start.js @@ -35,7 +35,7 @@ const config = getConfig({ production: false }); const port = process.env.PORT || 3000; const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; const host = process.env.HOST || 'localhost'; -const proxy = 'http://localhost:9000'; +const proxy = process.env.PROXY || 'http://localhost:9000'; const compiler = setupCompiler(host, port, protocol); diff --git a/server/sonar-web/src/main/js/apps/sessions/components/EmailAlreadyExists.tsx b/server/sonar-web/src/main/js/apps/sessions/components/EmailAlreadyExists.tsx new file mode 100644 index 00000000000..2e2c92a8a61 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/sessions/components/EmailAlreadyExists.tsx @@ -0,0 +1,138 @@ +/* + * 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 { FormattedMessage } from 'react-intl'; +import { getIdentityProviders, IdentityProvider } from '../../../api/users'; +import { translate } from '../../../helpers/l10n'; +import { getBaseUrl } from '../../../helpers/urls'; + +interface Props { + location: { + query: { + email: string; + login: string; + provider: string; + existingLogin: string; + existingProvider: string; + }; + }; +} + +interface State { + identityProviders: IdentityProvider[]; + loading: boolean; +} + +export default class EmailAlreadyExists extends React.PureComponent<Props, State> { + mounted: boolean; + state: State = { identityProviders: [], loading: true }; + + componentDidMount() { + this.mounted = true; + this.fetchIdentityProviders(); + } + + componentWillUnmount() { + this.mounted = false; + } + + fetchIdentityProviders = () => { + this.setState({ loading: true }); + getIdentityProviders().then( + ({ identityProviders }) => { + if (this.mounted) { + this.setState({ identityProviders, loading: false }); + } + }, + () => { + if (this.mounted) { + this.setState({ loading: false }); + } + } + ); + }; + + renderIdentityProvier = (provider: string, login: string) => { + const identityProvider = this.state.identityProviders.find(p => p.key === provider); + + return identityProvider ? ( + <div + className="identity-provider" + style={{ backgroundColor: identityProvider.backgroundColor }}> + <img + alt={identityProvider.name} + className="little-spacer-right" + src={getBaseUrl() + identityProvider.iconPath} + width="14" + height="14" + /> + {login} + </div> + ) : ( + <div> + {provider !== 'sonarqube' && provider} {login} + </div> + ); + }; + + render() { + const { query } = this.props.location; + + return ( + <div> + <div className="big-spacer-bottom js-existing-account"> + <p className="little-spacer-bottom"> + <FormattedMessage + defaultMessage={translate('sessions.email_already_exists.1')} + id="sessions.email_already_exists.1" + values={{ email: <strong>{query.email}</strong> }} + /> + </p> + {this.renderIdentityProvier(query.existingProvider, query.existingLogin)} + </div> + + <div className="big-spacer-bottom js-new-account"> + <p className="little-spacer-bottom">{translate('sessions.email_already_exists.2')}</p> + {this.renderIdentityProvier(query.provider, query.login)} + </div> + + <div className="alert alert-warning"> + {translate('sessions.email_already_exists.3')} + <ul className="list-styled"> + <li className="spacer-top">{translate('sessions.email_already_exists.4')}</li> + <li className="spacer-top">{translate('sessions.email_already_exists.5')}</li> + <li className="spacer-top">{translate('sessions.email_already_exists.6')}</li> + </ul> + </div> + + <div className="big-spacer-top text-right"> + <a + className="button js-continue" + href={`${getBaseUrl()}/sessions/init/${query.provider}?allowEmailShift=true`}> + {translate('continue')} + </a> + <a className="big-spacer-left js-cancel" href={getBaseUrl() + '/'}> + {translate('cancel')} + </a> + </div> + </div> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/sessions/components/SimpleSessionsContainer.tsx b/server/sonar-web/src/main/js/apps/sessions/components/SimpleSessionsContainer.tsx index ffca1290264..6e7df5248e9 100644 --- a/server/sonar-web/src/main/js/apps/sessions/components/SimpleSessionsContainer.tsx +++ b/server/sonar-web/src/main/js/apps/sessions/components/SimpleSessionsContainer.tsx @@ -21,7 +21,7 @@ import * as React from 'react'; import SimpleContainer from '../../../app/components/SimpleContainer'; interface Props { - children?: React.ReactElement<any>; + children?: React.ReactNode; } export default function SimpleSessionsContainer({ children }: Props) { diff --git a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/EmailAlreadyExists-test.tsx b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/EmailAlreadyExists-test.tsx new file mode 100644 index 00000000000..f0a4baca93d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/EmailAlreadyExists-test.tsx @@ -0,0 +1,58 @@ +/* + * 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 EmailAlreadyExists from '../EmailAlreadyExists'; + +jest.mock('../../../../api/users', () => ({ + getIdentityProviders: () => + Promise.resolve({ + identityProviders: [ + { + key: 'bitbucket', + name: 'Bitbucket', + iconPath: '/static/authbitbucket/bitbucket.svg', + backgroundColor: '#205081' + }, + { + key: 'github', + name: 'GitHub', + iconPath: '/static/authgithub/github.svg', + backgroundColor: '#444444' + } + ] + }) +})); + +it('render', async () => { + const query = { + email: 'mail@example.com', + login: 'foo', + provider: 'github', + existingLogin: 'bar', + existingProvider: 'bitbucket' + }; + const wrapper = shallow(<EmailAlreadyExists location={{ query }} />); + (wrapper.instance() as EmailAlreadyExists).mounted = true; + (wrapper.instance() as EmailAlreadyExists).fetchIdentityProviders(); + await new Promise(setImmediate); + wrapper.update(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/EmailAlreadyExists-test.tsx.snap b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/EmailAlreadyExists-test.tsx.snap new file mode 100644 index 00000000000..95ec95ce038 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/EmailAlreadyExists-test.tsx.snap @@ -0,0 +1,108 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`render 1`] = ` +<div> + <div + className="big-spacer-bottom js-existing-account" + > + <p + className="little-spacer-bottom" + > + <FormattedMessage + defaultMessage="sessions.email_already_exists.1" + id="sessions.email_already_exists.1" + values={ + Object { + "email": <strong> + mail@example.com + </strong>, + } + } + /> + </p> + <div + className="identity-provider" + style={ + Object { + "backgroundColor": "#205081", + } + } + > + <img + alt="Bitbucket" + className="little-spacer-right" + height="14" + src="/static/authbitbucket/bitbucket.svg" + width="14" + /> + bar + </div> + </div> + <div + className="big-spacer-bottom js-new-account" + > + <p + className="little-spacer-bottom" + > + sessions.email_already_exists.2 + </p> + <div + className="identity-provider" + style={ + Object { + "backgroundColor": "#444444", + } + } + > + <img + alt="GitHub" + className="little-spacer-right" + height="14" + src="/static/authgithub/github.svg" + width="14" + /> + foo + </div> + </div> + <div + className="alert alert-warning" + > + sessions.email_already_exists.3 + <ul + className="list-styled" + > + <li + className="spacer-top" + > + sessions.email_already_exists.4 + </li> + <li + className="spacer-top" + > + sessions.email_already_exists.5 + </li> + <li + className="spacer-top" + > + sessions.email_already_exists.6 + </li> + </ul> + </div> + <div + className="big-spacer-top text-right" + > + <a + className="button js-continue" + href="/sessions/init/github?allowEmailShift=true" + > + continue + </a> + <a + className="big-spacer-left js-cancel" + href="/" + > + cancel + </a> + </div> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/sessions/routes.ts b/server/sonar-web/src/main/js/apps/sessions/routes.ts index d5f4afec31e..92b4d569909 100644 --- a/server/sonar-web/src/main/js/apps/sessions/routes.ts +++ b/server/sonar-web/src/main/js/apps/sessions/routes.ts @@ -37,6 +37,12 @@ const routes = [ getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) { import('./components/Unauthorized').then(i => callback(null, i.default)); } + }, + { + path: 'email_already_exists', + getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) { + import('./components/EmailAlreadyExists').then(i => callback(null, i.default)); + } } ]; 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 f4e5b41e0eb..e9f5f8a0ed2 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -527,6 +527,12 @@ process.fail=Failed #------------------------------------------------------------------------------ sessions.log_in=Log in +sessions.email_already_exists.1=The email address {email} is already associated to this user account: +sessions.email_already_exists.2=By clicking on "Continue" you will associate this email address to a new user account: +sessions.email_already_exists.3=This means the following: +sessions.email_already_exists.4=Your email address will be erased from this account. +sessions.email_already_exists.5=You will no longer receive email notifications from this account. +sessions.email_already_exists.6=Issues won't be automatically assigned on the first account anymore. #------------------------------------------------------------------------------ |