--- /dev/null
+<svg viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M12.625 6.154a3.991 3.991 0 0 0-1.902-1.279v-.046C10.723 2.65 8.93.857 6.75.857c-2.179 0-3.972 1.793-3.972 3.972v.053A3.982 3.982 0 0 0 0 8.671c0 2.179 1.793 3.972 3.972 3.972a3.978 3.978 0 0 0 2.791-1.144 3.97 3.97 0 0 0 2.766 1.122c2.178 0 3.971-1.793 3.971-3.972 0-.905-.31-1.784-.877-2.489l.002-.006zm-3.073 5.489a2.99 2.99 0 0 1-2.973-2.971c0-.275-.225-.5-.5-.5a.501.501 0 0 0-.499.5 3.952 3.952 0 0 0 .56 2.032 2.971 2.971 0 0 1-2.164.936 2.985 2.985 0 0 1-2.97-2.97 2.985 2.985 0 0 1 2.97-2.971c.35 0 .697.062 1.026.183h.012c.114.038.223.09.324.155a.5.5 0 1 0 .65-.759 2.224 2.224 0 0 0-.646-.341 3.974 3.974 0 0 0-1.369-.243h-.192A2.985 2.985 0 0 1 6.75 1.85a2.986 2.986 0 0 1 2.972 2.972c0 .96-.466 1.863-1.249 2.42a.5.5 0 1 0 .58.814 3.983 3.983 0 0 0 1.526-2.184 2.979 2.979 0 0 1 1.941 2.789 2.986 2.986 0 0 1-2.969 2.972l.001.01z" fill="#f3702a" fill-rule="nonzero"/></svg>
\ No newline at end of file
--- /dev/null
+/*
+ * 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.
+ */
+.login-page {
+ padding-top: 10vh;
+}
+
+.login-title {
+ margin-bottom: 40px;
+ line-height: 1.5;
+ font-size: 24px;
+ font-weight: 300;
+ text-align: center;
+}
--- /dev/null
+/*
+ * 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 LoginForm from './LoginForm';
+import OAuthProviders from './OAuthProviders';
+import { IdentityProvider } from '../../../app/types';
+import { translate } from '../../../helpers/l10n';
+import './Login.css';
+
+interface Props {
+ identityProviders: IdentityProvider[];
+ onSubmit: (login: string, password: string) => Promise<void>;
+ returnTo: string;
+}
+
+export default function Login({ identityProviders, onSubmit, returnTo }: Props) {
+ return (
+ <div className="login-page" id="login_form">
+ <h1 className="login-title text-center">{translate('login.login_to_sonarqube')}</h1>
+
+ {identityProviders.length > 0 && (
+ <OAuthProviders identityProviders={identityProviders} returnTo={returnTo} />
+ )}
+
+ <LoginForm collapsed={identityProviders.length > 0} onSubmit={onSubmit} returnTo={returnTo} />
+ </div>
+ );
+}
--- /dev/null
+/*
+ * 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 { connect } from 'react-redux';
+import Login from './Login';
+import LoginSonarCloud from './LoginSonarCloud';
+import { doLogin } from '../../../store/rootActions';
+import { getIdentityProviders } from '../../../api/users';
+import { IdentityProvider } from '../../../app/types';
+import { getBaseUrl } from '../../../helpers/urls';
+
+interface Props {
+ doLogin: (login: string, password: string) => Promise<void>;
+ location: {
+ hash?: string;
+ pathName: string;
+ query: {
+ return_to?: string; // eslint-disable-line camelcase
+ };
+ };
+}
+
+interface State {
+ identityProviders?: IdentityProvider[];
+}
+
+class LoginContainer extends React.PureComponent<Props, State> {
+ mounted = false;
+
+ static contextTypes = {
+ onSonarCloud: PropTypes.bool
+ };
+
+ state: State = {};
+
+ componentDidMount() {
+ this.mounted = true;
+ getIdentityProviders().then(
+ identityProvidersResponse => {
+ if (this.mounted) {
+ this.setState({
+ identityProviders: identityProvidersResponse.identityProviders
+ });
+ }
+ },
+ () => {}
+ );
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ getReturnUrl = () => {
+ const { location } = this.props;
+ const queryReturnTo = location.query['return_to'];
+ return queryReturnTo ? `${queryReturnTo}${location.hash}` : `${getBaseUrl()}/`;
+ };
+
+ handleSuccessfulLogin = () => {
+ window.location.href = this.getReturnUrl();
+ };
+
+ handleSubmit = (login: string, password: string) => {
+ return this.props.doLogin(login, password).then(this.handleSuccessfulLogin, () => {});
+ };
+
+ render() {
+ const { identityProviders } = this.state;
+ if (!identityProviders) {
+ return null;
+ }
+
+ if (this.context.onSonarCloud) {
+ return (
+ <LoginSonarCloud
+ identityProviders={identityProviders}
+ onSubmit={this.handleSubmit}
+ returnTo={this.getReturnUrl()}
+ />
+ );
+ }
+
+ return (
+ <Login
+ identityProviders={identityProviders}
+ onSubmit={this.handleSubmit}
+ returnTo={this.getReturnUrl()}
+ />
+ );
+ }
+}
+
+const mapStateToProps = null;
+const mapDispatchToProps = { doLogin };
+
+export default connect(mapStateToProps, mapDispatchToProps)(LoginContainer as any);
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-.login-page {
- padding-top: 10vh;
-}
.login-form {
width: 300px;
margin-right: auto;
}
-.login-title {
- margin-bottom: 40px;
- line-height: 1.5;
- font-size: 24px;
- font-weight: 300;
- text-align: center;
-}
-
.login-input {
width: 100% !important;
height: auto !important;
*/
import * as React from 'react';
import { Link } from 'react-router';
-import * as classNames from 'classnames';
-import OAuthProviders from './OAuthProviders';
+import DeferredSpinner from '../../../components/common/DeferredSpinner';
import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer';
-import { IdentityProvider } from '../../../app/types';
import { SubmitButton } from '../../../components/ui/buttons';
import { translate } from '../../../helpers/l10n';
-import DeferredSpinner from '../../../components/common/DeferredSpinner';
import './LoginForm.css';
interface Props {
- onSonarCloud: boolean;
- identityProviders: IdentityProvider[];
+ collapsed?: boolean;
onSubmit: (login: string, password: string) => Promise<void>;
returnTo: string;
}
constructor(props: Props) {
super(props);
this.state = {
- collapsed: props.identityProviders.length > 0,
+ collapsed: Boolean(props.collapsed),
loading: false,
login: '',
password: ''
this.setState({ loading: false });
};
- handleSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
+ handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
this.setState({ loading: true });
this.props
.then(this.stopLoading, this.stopLoading);
};
- handleMoreOptionsClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ handleMoreOptionsClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
this.setState({ collapsed: false });
};
- handleLoginChange = (event: React.SyntheticEvent<HTMLInputElement>) =>
+ handleLoginChange = (event: React.ChangeEvent<HTMLInputElement>) =>
this.setState({ login: event.currentTarget.value });
- handlePwdChange = (event: React.SyntheticEvent<HTMLInputElement>) =>
+ handlePwdChange = (event: React.ChangeEvent<HTMLInputElement>) =>
this.setState({ password: event.currentTarget.value });
render() {
- const loginTitle = this.props.onSonarCloud
- ? translate('login.login_to_sonarcloud')
- : translate('login.login_to_sonarqube');
-
+ if (this.state.collapsed) {
+ return (
+ <div className="text-center">
+ <a
+ className="small text-muted js-more-options"
+ href="#"
+ onClick={this.handleMoreOptionsClick}>
+ {translate('login.more_options')}
+ </a>
+ </div>
+ );
+ }
return (
- <div className="login-page" id="login_form">
- <h1 className="login-title text-center">{loginTitle}</h1>
+ <form className="login-form" onSubmit={this.handleSubmit}>
+ <GlobalMessagesContainer />
- {this.props.identityProviders.length > 0 && (
- <OAuthProviders
- identityProviders={this.props.identityProviders}
- returnTo={this.props.returnTo}
+ <div className="big-spacer-bottom">
+ <label className="login-label" htmlFor="login">
+ {translate('login')}
+ </label>
+ <input
+ autoFocus={true}
+ className="login-input"
+ id="login"
+ maxLength={255}
+ name="login"
+ onChange={this.handleLoginChange}
+ placeholder={translate('login')}
+ required={true}
+ type="text"
+ value={this.state.login}
/>
- )}
-
- {this.state.collapsed ? (
- <div className="text-center">
- <a
- className="small text-muted js-more-options"
- href="#"
- onClick={this.handleMoreOptionsClick}>
- {translate('login.more_options')}
- </a>
- </div>
- ) : (
- <form className="login-form" onSubmit={this.handleSubmit}>
- <GlobalMessagesContainer />
-
- <div className="big-spacer-bottom">
- <label className="login-label" htmlFor="login">
- {translate('login')}
- </label>
- <input
- autoFocus={true}
- className="login-input"
- id="login"
- maxLength={255}
- name="login"
- onChange={this.handleLoginChange}
- placeholder={translate('login')}
- required={true}
- type="text"
- value={this.state.login}
- />
- </div>
+ </div>
- <div className="big-spacer-bottom">
- <label className="login-label" htmlFor="password">
- {translate('password')}
- </label>
- <input
- className="login-input"
- id="password"
- name="password"
- onChange={this.handlePwdChange}
- placeholder={translate('password')}
- required={true}
- type="password"
- value={this.state.password}
- />
- </div>
+ <div className="big-spacer-bottom">
+ <label className="login-label" htmlFor="password">
+ {translate('password')}
+ </label>
+ <input
+ className="login-input"
+ id="password"
+ name="password"
+ onChange={this.handlePwdChange}
+ placeholder={translate('password')}
+ required={true}
+ type="password"
+ value={this.state.password}
+ />
+ </div>
- <div>
- <div className="text-right overflow-hidden">
- <DeferredSpinner className="spacer-right" loading={this.state.loading} />
- <SubmitButton className={classNames({ disabled: this.state.loading })}>
- {translate('sessions.log_in')}
- </SubmitButton>
- <Link className="spacer-left" to="/">
- {translate('cancel')}
- </Link>
- </div>
- </div>
- </form>
- )}
- </div>
+ <div>
+ <div className="text-right overflow-hidden">
+ <DeferredSpinner className="spacer-right" loading={this.state.loading} />
+ <SubmitButton disabled={this.state.loading}>
+ {translate('sessions.log_in')}
+ </SubmitButton>
+ <Link className="spacer-left" to="/">
+ {translate('cancel')}
+ </Link>
+ </div>
+ </div>
+ </form>
);
}
}
+++ /dev/null
-/*
- * 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 { connect } from 'react-redux';
-import LoginForm from './LoginForm';
-import { doLogin } from '../../../store/rootActions';
-import { getIdentityProviders } from '../../../api/users';
-import { IdentityProvider } from '../../../app/types';
-import { getBaseUrl } from '../../../helpers/urls';
-
-interface Props {
- doLogin: (login: string, password: string) => Promise<void>;
- location: { hash?: string; pathName: string; query: { return_to?: string } };
-}
-
-interface State {
- identityProviders?: IdentityProvider[];
-}
-
-class LoginFormContainer extends React.PureComponent<Props, State> {
- mounted = false;
-
- static contextTypes = {
- onSonarCloud: PropTypes.bool
- };
-
- state: State = {};
-
- componentDidMount() {
- this.mounted = true;
- getIdentityProviders().then(
- identityProvidersResponse => {
- if (this.mounted) {
- this.setState({
- identityProviders: identityProvidersResponse.identityProviders
- });
- }
- },
- () => {}
- );
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- getReturnUrl = () => {
- const { location } = this.props;
- const queryReturnTo = location.query['return_to'];
- return queryReturnTo ? `${queryReturnTo}${location.hash}` : `${getBaseUrl()}/`;
- };
-
- handleSuccessfulLogin = () => {
- window.location.href = this.getReturnUrl();
- };
-
- handleSubmit = (login: string, password: string) => {
- return this.props.doLogin(login, password).then(this.handleSuccessfulLogin, () => {});
- };
-
- render() {
- const { identityProviders } = this.state;
- if (!identityProviders) {
- return null;
- }
-
- return (
- <LoginForm
- identityProviders={identityProviders}
- onSonarCloud={this.context.onSonarCloud}
- onSubmit={this.handleSubmit}
- returnTo={this.getReturnUrl()}
- />
- );
- }
-}
-
-const mapStateToProps = null;
-const mapDispatchToProps = { doLogin };
-
-export default connect(mapStateToProps, mapDispatchToProps)(LoginFormContainer as any);
--- /dev/null
+/*
+ * 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.
+ */
+.sonarcloud-login-page {
+ margin-top: 15vh;
+ width: 200px;
+ margin-left: auto;
+ margin-right: auto;
+ padding: calc(2 * var(--gridSize)) 20px;
+}
+
+.sonarcloud-login-title {
+ line-height: 1.5;
+ font-size: var(--bigFontSize);
+ font-weight: 300;
+ width: 135px;
+ margin: var(--gridSize) auto calc(2 * var(--gridSize));
+}
+
+.sonarcloud-oauth-providers.oauth-providers > ul > li {
+ margin-bottom: var(--gridSize);
+}
+
+.sonarcloud-oauth-providers.oauth-providers > ul > li > a > span {
+ font-weight: 600;
+ padding-left: calc(1.5 * var(--gridSize));
+}
+
+.sonarcloud-oauth-providers.oauth-providers > ul > li > a > span::before {
+ content: '';
+ border-left: 1px var(--gray71) solid;
+ height: 10px;
+ opacity: 0.4;
+ margin-right: calc(1.5 * var(--gridSize));
+}
+
+.sonarcloud-oauth-providers.oauth-providers .oauth-providers-help {
+ right: -22px;
+}
--- /dev/null
+/*
+ * 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 OAuthProviders from './OAuthProviders';
+import { IdentityProvider } from '../../../app/types';
+import { getHostUrl } from '../../../helpers/urls';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+import './LoginSonarCloud.css';
+
+interface Props {
+ identityProviders: IdentityProvider[];
+ returnTo: string;
+}
+
+export default function LoginSonarCloud({ identityProviders, returnTo }: Props) {
+ return (
+ <div className="sonarcloud-login-page boxed-group boxed-group-inner" id="login_form">
+ <div className="text-center">
+ <img
+ alt="SonarCloud logo"
+ height={36}
+ src={`${getHostUrl()}/images/sc-icon.svg`}
+ width={36}
+ />
+ <h1 className="sonarcloud-login-title">
+ {translate('login.login_or_signup_to_sonarcloud')}
+ </h1>
+ </div>
+
+ <OAuthProviders
+ className="sonarcloud-oauth-providers"
+ formatLabel={formatLabel}
+ identityProviders={identityProviders}
+ returnTo={returnTo}
+ />
+ </div>
+ );
+}
+
+function formatLabel(name: string) {
+ return translateWithParameters('login.with_x', name);
+}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import * as classNames from 'classnames';
import { translateWithParameters } from '../../../helpers/l10n';
import * as theme from '../../../app/theme';
import { IdentityProvider } from '../../../app/types';
import './OAuthProviders.css';
interface Props {
+ className?: string;
+ formatLabel?: (name: string) => React.ReactNode;
identityProviders: IdentityProvider[];
returnTo: string;
}
export default function OAuthProviders(props: Props) {
+ const formatFunction = props.formatLabel || defaultFormatLabel;
return (
- <section className="oauth-providers">
+ <section className={classNames('oauth-providers', props.className)}>
<ul>
{props.identityProviders.map(identityProvider => (
<li key={identityProvider.key}>
}}>
<img
alt={identityProvider.name}
- width="20"
height="20"
src={getBaseUrl() + identityProvider.iconPath}
+ width="20"
/>
- <span>{defaultFormatLabel(identityProvider.name)}</span>
+ <span>{formatFunction(identityProvider.name)}</span>
</a>
{identityProvider.helpMessage && (
<Tooltip overlay={identityProvider.helpMessage}>
key: 'bitbucket',
name: 'Bitbucket',
iconPath: '/static/authbitbucket/bitbucket.svg',
- backgroundColor: '#205081'
+ backgroundColor: '#0052cc'
},
{
key: 'github',
--- /dev/null
+/*
+ * 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 Login from '../Login';
+
+const identityProvider = {
+ backgroundColor: '#000',
+ iconPath: '/some/path',
+ key: 'foo',
+ name: 'foo'
+};
+
+it('logs in with form alone', () => {
+ const wrapper = shallow(<Login identityProviders={[]} onSubmit={jest.fn()} returnTo="" />);
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('logs in with identity provider', () => {
+ const wrapper = shallow(
+ <Login identityProviders={[identityProvider]} onSubmit={jest.fn()} returnTo="" />
+ );
+ expect(wrapper).toMatchSnapshot();
+});
import LoginForm from '../LoginForm';
import { change, click, submit, waitAndUpdate } from '../../../../helpers/testUtils';
-const identityProvider = {
- backgroundColor: '#000',
- iconPath: '/some/path',
- key: 'foo',
- name: 'foo'
-};
-
it('logs in with simple credentials', () => {
const onSubmit = jest.fn(() => Promise.resolve());
- const wrapper = shallow(
- <LoginForm identityProviders={[]} onSonarCloud={false} onSubmit={onSubmit} returnTo="" />
- );
+ const wrapper = shallow(<LoginForm onSubmit={onSubmit} returnTo="" />);
expect(wrapper).toMatchSnapshot();
change(wrapper.find('#login'), 'admin');
it('should display a spinner and disabled button while loading', async () => {
const onSubmit = jest.fn(() => Promise.resolve());
- const wrapper = shallow(
- <LoginForm identityProviders={[]} onSonarCloud={false} onSubmit={onSubmit} returnTo="" />
- );
+ const wrapper = shallow(<LoginForm onSubmit={onSubmit} returnTo="" />);
change(wrapper.find('#login'), 'admin');
change(wrapper.find('#password'), 'admin');
await waitAndUpdate(wrapper);
});
-it('logs in with identity provider', () => {
- const wrapper = shallow(
- <LoginForm
- identityProviders={[identityProvider]}
- onSonarCloud={false}
- onSubmit={jest.fn()}
- returnTo=""
- />
- );
- expect(wrapper).toMatchSnapshot();
-});
-
it('expands more options', () => {
- const wrapper = shallow(
- <LoginForm
- identityProviders={[identityProvider]}
- onSonarCloud={false}
- onSubmit={jest.fn()}
- returnTo=""
- />
- );
+ const wrapper = shallow(<LoginForm collapsed={true} onSubmit={jest.fn()} returnTo="" />);
expect(wrapper).toMatchSnapshot();
click(wrapper.find('.js-more-options'));
--- /dev/null
+/*
+ * 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 LoginSonarCloud from '../LoginSonarCloud';
+
+const identityProvider = {
+ backgroundColor: '#000',
+ iconPath: '/some/path',
+ key: 'foo',
+ name: 'foo'
+};
+
+it('logs in with identity provider', () => {
+ const wrapper = shallow(
+ <LoginSonarCloud identityProviders={[identityProvider]} onSubmit={jest.fn()} returnTo="" />
+ );
+ expect(wrapper).toMatchSnapshot();
+});
--- /dev/null
+/*
+ * 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 OAuthProviders from '../OAuthProviders';
+
+const identityProviders = [
+ {
+ backgroundColor: '#000',
+ iconPath: '/some/path',
+ key: 'foo',
+ name: 'Foo'
+ },
+ {
+ backgroundColor: '#00F',
+ helpMessage: 'Help message!',
+ iconPath: '/icon/path',
+ key: 'bar',
+ name: 'Bar'
+ }
+];
+
+it('should render correctly', () => {
+ expect(
+ shallow(<OAuthProviders identityProviders={identityProviders} returnTo="" />)
+ ).toMatchSnapshot();
+});
+
+it('should use the custom label formatter', () => {
+ expect(
+ shallow(
+ <OAuthProviders
+ formatLabel={name => 'custom_format.' + name}
+ identityProviders={[identityProviders[0]]}
+ returnTo=""
+ />
+ )
+ ).toMatchSnapshot();
+});
className="identity-provider"
style={
Object {
- "backgroundColor": "#205081",
+ "backgroundColor": "#0052cc",
"color": "#fff",
}
}
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`logs in with form alone 1`] = `
+<div
+ className="login-page"
+ id="login_form"
+>
+ <h1
+ className="login-title text-center"
+ >
+ login.login_to_sonarqube
+ </h1>
+ <LoginForm
+ collapsed={false}
+ onSubmit={[MockFunction]}
+ returnTo=""
+ />
+</div>
+`;
+
+exports[`logs in with identity provider 1`] = `
+<div
+ className="login-page"
+ id="login_form"
+>
+ <h1
+ className="login-title text-center"
+ >
+ login.login_to_sonarqube
+ </h1>
+ <OAuthProviders
+ identityProviders={
+ Array [
+ Object {
+ "backgroundColor": "#000",
+ "iconPath": "/some/path",
+ "key": "foo",
+ "name": "foo",
+ },
+ ]
+ }
+ returnTo=""
+ />
+ <LoginForm
+ collapsed={true}
+ onSubmit={[MockFunction]}
+ returnTo=""
+ />
+</div>
+`;
exports[`expands more options 1`] = `
<div
- className="login-page"
- id="login_form"
+ className="text-center"
>
- <h1
- className="login-title text-center"
+ <a
+ className="small text-muted js-more-options"
+ href="#"
+ onClick={[Function]}
>
- login.login_to_sonarqube
- </h1>
- <OAuthProviders
- identityProviders={
- Array [
- Object {
- "backgroundColor": "#000",
- "iconPath": "/some/path",
- "key": "foo",
- "name": "foo",
- },
- ]
- }
- returnTo=""
- />
- <div
- className="text-center"
- >
- <a
- className="small text-muted js-more-options"
- href="#"
- onClick={[Function]}
- >
- login.more_options
- </a>
- </div>
+ login.more_options
+ </a>
</div>
`;
exports[`expands more options 2`] = `
-<div
- className="login-page"
- id="login_form"
+<form
+ className="login-form"
+ onSubmit={[Function]}
>
- <h1
- className="login-title text-center"
+ <Connect(GlobalMessages) />
+ <div
+ className="big-spacer-bottom"
>
- login.login_to_sonarqube
- </h1>
- <OAuthProviders
- identityProviders={
- Array [
- Object {
- "backgroundColor": "#000",
- "iconPath": "/some/path",
- "key": "foo",
- "name": "foo",
- },
- ]
- }
- returnTo=""
- />
- <form
- className="login-form"
- onSubmit={[Function]}
+ <label
+ className="login-label"
+ htmlFor="login"
+ >
+ login
+ </label>
+ <input
+ autoFocus={true}
+ className="login-input"
+ id="login"
+ maxLength={255}
+ name="login"
+ onChange={[Function]}
+ placeholder="login"
+ required={true}
+ type="text"
+ value=""
+ />
+ </div>
+ <div
+ className="big-spacer-bottom"
>
- <Connect(GlobalMessages) />
- <div
- className="big-spacer-bottom"
+ <label
+ className="login-label"
+ htmlFor="password"
>
- <label
- className="login-label"
- htmlFor="login"
- >
- login
- </label>
- <input
- autoFocus={true}
- className="login-input"
- id="login"
- maxLength={255}
- name="login"
- onChange={[Function]}
- placeholder="login"
- required={true}
- type="text"
- value=""
- />
- </div>
+ password
+ </label>
+ <input
+ className="login-input"
+ id="password"
+ name="password"
+ onChange={[Function]}
+ placeholder="password"
+ required={true}
+ type="password"
+ value=""
+ />
+ </div>
+ <div>
<div
- className="big-spacer-bottom"
+ className="text-right overflow-hidden"
>
- <label
- className="login-label"
- htmlFor="password"
- >
- password
- </label>
- <input
- className="login-input"
- id="password"
- name="password"
- onChange={[Function]}
- placeholder="password"
- required={true}
- type="password"
- value=""
+ <DeferredSpinner
+ className="spacer-right"
+ loading={false}
+ timeout={100}
/>
- </div>
- <div>
- <div
- className="text-right overflow-hidden"
+ <SubmitButton
+ disabled={false}
>
- <DeferredSpinner
- className="spacer-right"
- loading={false}
- timeout={100}
- />
- <SubmitButton
- className=""
- >
- sessions.log_in
- </SubmitButton>
- <Link
- className="spacer-left"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/"
- >
- cancel
- </Link>
- </div>
+ sessions.log_in
+ </SubmitButton>
+ <Link
+ className="spacer-left"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/"
+ >
+ cancel
+ </Link>
</div>
- </form>
-</div>
+ </div>
+</form>
`;
-exports[`logs in with identity provider 1`] = `
-<div
- className="login-page"
- id="login_form"
+exports[`logs in with simple credentials 1`] = `
+<form
+ className="login-form"
+ onSubmit={[Function]}
>
- <h1
- className="login-title text-center"
- >
- login.login_to_sonarqube
- </h1>
- <OAuthProviders
- identityProviders={
- Array [
- Object {
- "backgroundColor": "#000",
- "iconPath": "/some/path",
- "key": "foo",
- "name": "foo",
- },
- ]
- }
- returnTo=""
- />
+ <Connect(GlobalMessages) />
<div
- className="text-center"
+ className="big-spacer-bottom"
>
- <a
- className="small text-muted js-more-options"
- href="#"
- onClick={[Function]}
+ <label
+ className="login-label"
+ htmlFor="login"
>
- login.more_options
- </a>
+ login
+ </label>
+ <input
+ autoFocus={true}
+ className="login-input"
+ id="login"
+ maxLength={255}
+ name="login"
+ onChange={[Function]}
+ placeholder="login"
+ required={true}
+ type="text"
+ value=""
+ />
</div>
-</div>
-`;
-
-exports[`logs in with simple credentials 1`] = `
-<div
- className="login-page"
- id="login_form"
->
- <h1
- className="login-title text-center"
- >
- login.login_to_sonarqube
- </h1>
- <form
- className="login-form"
- onSubmit={[Function]}
+ <div
+ className="big-spacer-bottom"
>
- <Connect(GlobalMessages) />
- <div
- className="big-spacer-bottom"
+ <label
+ className="login-label"
+ htmlFor="password"
>
- <label
- className="login-label"
- htmlFor="login"
- >
- login
- </label>
- <input
- autoFocus={true}
- className="login-input"
- id="login"
- maxLength={255}
- name="login"
- onChange={[Function]}
- placeholder="login"
- required={true}
- type="text"
- value=""
- />
- </div>
+ password
+ </label>
+ <input
+ className="login-input"
+ id="password"
+ name="password"
+ onChange={[Function]}
+ placeholder="password"
+ required={true}
+ type="password"
+ value=""
+ />
+ </div>
+ <div>
<div
- className="big-spacer-bottom"
+ className="text-right overflow-hidden"
>
- <label
- className="login-label"
- htmlFor="password"
- >
- password
- </label>
- <input
- className="login-input"
- id="password"
- name="password"
- onChange={[Function]}
- placeholder="password"
- required={true}
- type="password"
- value=""
+ <DeferredSpinner
+ className="spacer-right"
+ loading={false}
+ timeout={100}
/>
- </div>
- <div>
- <div
- className="text-right overflow-hidden"
+ <SubmitButton
+ disabled={false}
>
- <DeferredSpinner
- className="spacer-right"
- loading={false}
- timeout={100}
- />
- <SubmitButton
- className=""
- >
- sessions.log_in
- </SubmitButton>
- <Link
- className="spacer-left"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/"
- >
- cancel
- </Link>
- </div>
+ sessions.log_in
+ </SubmitButton>
+ <Link
+ className="spacer-left"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/"
+ >
+ cancel
+ </Link>
</div>
- </form>
-</div>
+ </div>
+</form>
`;
exports[`should display a spinner and disabled button while loading 1`] = `
-<div
- className="login-page"
- id="login_form"
+<form
+ className="login-form"
+ onSubmit={[Function]}
>
- <h1
- className="login-title text-center"
+ <Connect(GlobalMessages) />
+ <div
+ className="big-spacer-bottom"
>
- login.login_to_sonarqube
- </h1>
- <form
- className="login-form"
- onSubmit={[Function]}
+ <label
+ className="login-label"
+ htmlFor="login"
+ >
+ login
+ </label>
+ <input
+ autoFocus={true}
+ className="login-input"
+ id="login"
+ maxLength={255}
+ name="login"
+ onChange={[Function]}
+ placeholder="login"
+ required={true}
+ type="text"
+ value="admin"
+ />
+ </div>
+ <div
+ className="big-spacer-bottom"
>
- <Connect(GlobalMessages) />
- <div
- className="big-spacer-bottom"
+ <label
+ className="login-label"
+ htmlFor="password"
>
- <label
- className="login-label"
- htmlFor="login"
- >
- login
- </label>
- <input
- autoFocus={true}
- className="login-input"
- id="login"
- maxLength={255}
- name="login"
- onChange={[Function]}
- placeholder="login"
- required={true}
- type="text"
- value="admin"
- />
- </div>
+ password
+ </label>
+ <input
+ className="login-input"
+ id="password"
+ name="password"
+ onChange={[Function]}
+ placeholder="password"
+ required={true}
+ type="password"
+ value="admin"
+ />
+ </div>
+ <div>
<div
- className="big-spacer-bottom"
+ className="text-right overflow-hidden"
>
- <label
- className="login-label"
- htmlFor="password"
- >
- password
- </label>
- <input
- className="login-input"
- id="password"
- name="password"
- onChange={[Function]}
- placeholder="password"
- required={true}
- type="password"
- value="admin"
+ <DeferredSpinner
+ className="spacer-right"
+ loading={true}
+ timeout={100}
/>
- </div>
- <div>
- <div
- className="text-right overflow-hidden"
+ <SubmitButton
+ disabled={true}
>
- <DeferredSpinner
- className="spacer-right"
- loading={true}
- timeout={100}
- />
- <SubmitButton
- className="disabled"
- >
- sessions.log_in
- </SubmitButton>
- <Link
- className="spacer-left"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/"
- >
- cancel
- </Link>
- </div>
+ sessions.log_in
+ </SubmitButton>
+ <Link
+ className="spacer-left"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/"
+ >
+ cancel
+ </Link>
</div>
- </form>
-</div>
+ </div>
+</form>
`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`logs in with identity provider 1`] = `
+<div
+ className="sonarcloud-login-page boxed-group boxed-group-inner"
+ id="login_form"
+>
+ <div
+ className="text-center"
+ >
+ <img
+ alt="SonarCloud logo"
+ height={36}
+ src="null/images/sc-icon.svg"
+ width={36}
+ />
+ <h1
+ className="sonarcloud-login-title"
+ >
+ login.login_or_signup_to_sonarcloud
+ </h1>
+ </div>
+ <OAuthProviders
+ className="sonarcloud-oauth-providers"
+ formatLabel={[Function]}
+ identityProviders={
+ Array [
+ Object {
+ "backgroundColor": "#000",
+ "iconPath": "/some/path",
+ "key": "foo",
+ "name": "foo",
+ },
+ ]
+ }
+ returnTo=""
+ />
+</div>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<section
+ className="oauth-providers"
+>
+ <ul>
+ <li
+ key="foo"
+ >
+ <a
+ href="/sessions/init/foo?return_to="
+ style={
+ Object {
+ "backgroundColor": "#000",
+ "color": "#fff",
+ }
+ }
+ >
+ <img
+ alt="Foo"
+ height="20"
+ src="/some/path"
+ width="20"
+ />
+ <span>
+ login.login_with_x.Foo
+ </span>
+ </a>
+ </li>
+ <li
+ key="bar"
+ >
+ <a
+ href="/sessions/init/bar?return_to="
+ style={
+ Object {
+ "backgroundColor": "#00F",
+ "color": "#fff",
+ }
+ }
+ >
+ <img
+ alt="Bar"
+ height="20"
+ src="/icon/path"
+ width="20"
+ />
+ <span>
+ login.login_with_x.Bar
+ </span>
+ </a>
+ <Tooltip
+ overlay="Help message!"
+ >
+ <div
+ className="oauth-providers-help"
+ >
+ <HelpIcon
+ fill="#4b9fd5"
+ />
+ </div>
+ </Tooltip>
+ </li>
+ </ul>
+</section>
+`;
+
+exports[`should use the custom label formatter 1`] = `
+<section
+ className="oauth-providers"
+>
+ <ul>
+ <li
+ key="foo"
+ >
+ <a
+ href="/sessions/init/foo?return_to="
+ style={
+ Object {
+ "backgroundColor": "#000",
+ "color": "#fff",
+ }
+ }
+ >
+ <img
+ alt="Foo"
+ height="20"
+ src="/some/path"
+ width="20"
+ />
+ <span>
+ custom_format.Foo
+ </span>
+ </a>
+ </li>
+ </ul>
+</section>
+`;
{
path: 'new',
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
- import('./components/LoginFormContainer').then(i => callback(null, i.default));
+ import('./components/LoginContainer').then(i => callback(null, i.default));
}
},
{
user.login_or_email_used_as_scm_account=Login and email are automatically considered as SCM accounts
login.login_to_sonarqube=Log In to SonarQube
-login.login_to_sonarcloud=Log In to SonarCloud
-login.more_options=More options
+login.login_or_signup_to_sonarcloud=Log in or Sign up to SonarCloud
login.login_with_x=Log in with {0}
+login.more_options=More options
+login.with_x=With {0}
unauthorized.message=You're not authorized to access this page. Please contact the administrator.
unauthorized.reason=Reason: