]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10338 Implement /sessions/email_already_exists page
authorStas Vilchik <stas.vilchik@sonarsource.com>
Thu, 1 Feb 2018 14:55:54 +0000 (15:55 +0100)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 7 Feb 2018 15:43:01 +0000 (16:43 +0100)
server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/EmailAlreadyExistsPage.java [new file with mode: 0644]
server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/Navigation.java
server/sonar-web/scripts/start.js
server/sonar-web/src/main/js/apps/sessions/components/EmailAlreadyExists.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/sessions/components/SimpleSessionsContainer.tsx
server/sonar-web/src/main/js/apps/sessions/components/__tests__/EmailAlreadyExists-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/EmailAlreadyExists-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/sessions/routes.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

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 (file)
index 0000000..2dfdb00
--- /dev/null
@@ -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);
+  }
+
+}
index f4e37dce67f82835d2d9c54d911e022248c2bd7e..ede43929a06bba06ad81e941a91f980d199581a8 100644 (file)
@@ -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
    */
index 21776e5036b9315d16436d8a64bb16d0df7a03a8..53969869f3316ed2ba959bbdfdc6501c9eca835a 100644 (file)
@@ -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 (file)
index 0000000..2e2c92a
--- /dev/null
@@ -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>
+    );
+  }
+}
index ffca1290264ba6da49713353a459cd820aa00772..6e7df5248e931a851ab172064a701d9e2cc2599c 100644 (file)
@@ -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 (file)
index 0000000..f0a4bac
--- /dev/null
@@ -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 (file)
index 0000000..95ec95c
--- /dev/null
@@ -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>
+`;
index d5f4afec31eeba8bb1aed4e042e14adbc943e0fa..92b4d569909fd4425bafdfd7228e2ac4a3fcecb4 100644 (file)
@@ -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));
+    }
   }
 ];
 
index f4e5b41e0eb9d2b1181672f30c70982000736494..e9f5f8a0ed249a80bd25b0fa3664220b29b6919d 100644 (file)
@@ -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.
 
 
 #------------------------------------------------------------------------------